Add search bar to android home feed

This commit is contained in:
Satindar Dhillon 2022-09-06 09:59:22 -07:00
parent 69f07a6a3c
commit d069952f5a
4 changed files with 182 additions and 43 deletions

View file

@ -28,32 +28,27 @@ fun HomeView(
homeViewModel: HomeViewModel,
navController: NavHostController
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Home") },
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
actions = {
IconButton(onClick = { navController.navigate(Routes.Settings.route) }) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = null
)
}
}
)
}
) { paddingValues ->
HomeViewContent(
homeViewModel,
navController,
modifier = Modifier
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding()
)
val searchText: String by homeViewModel.searchTextLiveData.observeAsState("")
Scaffold(
topBar = {
SearchBar(
searchText = searchText,
onSearchTextChanged = { homeViewModel.updateSearchText(it) },
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
)
}
) { paddingValues ->
HomeViewContent(
homeViewModel,
navController,
modifier = Modifier
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding()
)
)
}
}
@Composable

View file

@ -1,26 +1,12 @@
package app.omnivore.omnivore.ui.home
import android.annotation.SuppressLint
import android.net.Uri
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.*
import app.omnivore.omnivore.AppleConstants
import app.omnivore.omnivore.Constants
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.graphql.generated.SearchQuery
import app.omnivore.omnivore.ui.auth.AppleAuthDialog
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dagger.hilt.android.lifecycle.HiltViewModel
@ -32,14 +18,29 @@ import javax.inject.Inject
class HomeViewModel @Inject constructor(
private val datastoreRepo: DatastoreRepository
): ViewModel() {
var cursor: String? = null
private var cursor: String? = null
private var items: List<LinkedItem> = listOf()
private var searchedItems: List<LinkedItem> = listOf()
// Live Data
val searchTextLiveData = MutableLiveData<String>("")
val itemsLiveData = MutableLiveData<List<LinkedItem>>(listOf())
private fun getAuthToken(): String? = runBlocking {
datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken)
}
fun load() {
fun updateSearchText(text: String) {
searchTextLiveData.value = text
if (text == "") {
itemsLiveData.value = items
} else {
load(clearPreviousSearch = true)
}
}
fun load(clearPreviousSearch: Boolean = false) {
viewModelScope.launch {
val authToken = getAuthToken()
@ -52,6 +53,7 @@ class HomeViewModel @Inject constructor(
SearchQuery(
after = Optional.presentIfNotNull(cursor),
first = Optional.presentIfNotNull(15),
query = Optional.presentIfNotNull(searchQuery())
)
).execute()
@ -74,9 +76,28 @@ class HomeViewModel @Inject constructor(
)
}
itemsLiveData.value = (itemsLiveData.value ?: listOf()).plus(newItems)
Log.d("loggo", newItems.toString())
if (searchTextLiveData.value != "") {
val previousItems = if (clearPreviousSearch) listOf() else searchedItems
searchedItems = previousItems.plus(newItems)
itemsLiveData.value = searchedItems
} else {
items = items.plus(newItems)
itemsLiveData.value = items
}
}
}
private fun searchQuery(): String {
var query = "in:inbox sort:saved"
if (searchTextLiveData.value != "") {
query.plus(" ${searchTextLiveData.value}")
}
return query
}
}
public data class LinkedItem(

View file

@ -31,7 +31,7 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit) {
.clickable(onClick = onClickHandler)
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier
.weight(1f, fill = false)
.padding(end = 8.dp)

View file

@ -0,0 +1,123 @@
package app.omnivore.omnivore.ui.home
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
@Composable
fun SearchBar(
searchText: String,
onSearchTextChanged: (String) -> Unit,
onSettingsIconClick: () -> Unit
) {
var showSearchField by remember { mutableStateOf(false) }
TopAppBar(
title = {
if (showSearchField) {
SearchField(searchText, onSearchTextChanged)
} else {
Text("Home")
}
},
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
actions = {
if (showSearchField) {
Text(
text = "Cancel",
modifier = Modifier
.clickable { showSearchField = false }
)
} else {
IconButton(onClick = { showSearchField = true }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = null
)
}
IconButton(onClick = onSettingsIconClick) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = null
)
}
}
}
)
}
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
fun SearchField(
searchText: String,
onSearchTextChanged: (String) -> Unit
) {
var showClearButton by remember { mutableStateOf(false) }
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }
Row {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 2.dp)
.onFocusChanged { focusState ->
showClearButton = (focusState.isFocused)
}
.focusRequester(focusRequester),
value = searchText,
onValueChange = onSearchTextChanged,
placeholder = {
Text(text = "Search")
},
trailingIcon = {
AnimatedVisibility(
visible = showClearButton,
enter = fadeIn(),
exit = fadeOut()
) {
IconButton(onClick = { onSearchTextChanged("") }) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = null
)
}
}
},
maxLines = 1,
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {
keyboardController?.hide()
}),
)
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}