mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Add search bar to android home feed
This commit is contained in:
parent
69f07a6a3c
commit
d069952f5a
4 changed files with 182 additions and 43 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue