diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt index 849615447..fa90f3f0f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 49c91433e..b2d63e0d2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -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 = listOf() + private var searchedItems: List = listOf() + + // Live Data + val searchTextLiveData = MutableLiveData("") val itemsLiveData = MutableLiveData>(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( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt index 71e4d41e4..eb6c42e37 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt @@ -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) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/SearchBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/SearchBar.kt new file mode 100644 index 000000000..4cd11417c --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/SearchBar.kt @@ -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() + } +}