Add a new SearchView that does typeahead search, add back button to WebReader
The end goal here is to separate search and typeahead search so I can bring more properties onto the main search cards like save time, number of highlights, article length.
This commit is contained in:
@ -3,6 +3,7 @@ package app.omnivore.omnivore
|
||||
sealed class Routes(val route: String) {
|
||||
object Library : Routes("Library")
|
||||
object Settings: Routes("Settings")
|
||||
object Search: Routes("Search")
|
||||
object Documentation: Routes("Documentation")
|
||||
object PrivacyPolicy: Routes("PrivacyPolicy")
|
||||
object TermsAndConditions: Routes("TermsAndConditions")
|
||||
|
||||
@ -39,6 +39,7 @@ fun LibraryView(
|
||||
topBar = {
|
||||
SearchBar(
|
||||
libraryViewModel = libraryViewModel,
|
||||
onSearchClicked = { navController.navigate(Routes.Search.route) },
|
||||
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,9 +4,7 @@ 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.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
@ -31,6 +29,7 @@ import app.omnivore.omnivore.R
|
||||
@Composable
|
||||
fun SearchBar(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
onSearchClicked: () -> Unit,
|
||||
onSettingsIconClick: () -> Unit
|
||||
) {
|
||||
val searchText: String by libraryViewModel.searchTextLiveData.observeAsState("")
|
||||
@ -58,7 +57,7 @@ fun SearchBar(
|
||||
.padding(horizontal = 6.dp)
|
||||
)
|
||||
} else {
|
||||
IconButton(onClick = { libraryViewModel.showSearchField = true }) {
|
||||
IconButton(onClick = onSearchClicked) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = null
|
||||
@ -87,44 +86,41 @@ fun SearchField(
|
||||
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
|
||||
)
|
||||
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()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
maxLines = 1,
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
keyboardController?.hide()
|
||||
}),
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
package app.omnivore.omnivore.ui.library
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemCardDataWithLabels
|
||||
import app.omnivore.omnivore.ui.components.LabelsSelectionSheet
|
||||
import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard
|
||||
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
|
||||
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.omnivore.omnivore.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SearchView(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
navController: NavHostController
|
||||
) {
|
||||
val searchText: String by libraryViewModel.searchTextLiveData.observeAsState("")
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = {
|
||||
SearchField(searchText) { libraryViewModel.updateSearchText(it) }
|
||||
}, navigationIcon = {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack,
|
||||
modifier = Modifier,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
) { paddingValues ->
|
||||
SearchViewContent(
|
||||
libraryViewModel,
|
||||
modifier = Modifier
|
||||
.padding(vertical = paddingValues.calculateTopPadding())
|
||||
.background(Color.Blue)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun SearchViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val searchedCardsData: List<SavedItemCardDataWithLabels> by libraryViewModel.searchItemsLiveData.observeAsState(listOf())
|
||||
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize()
|
||||
|
||||
) {
|
||||
items(searchedCardsData) { cardDataWithLabels ->
|
||||
SavedItemCard(
|
||||
cardData = cardDataWithLabels.cardData,
|
||||
labels = cardDataWithLabels.labels,
|
||||
onClickHandler = {
|
||||
val activityClass = if (cardDataWithLabels.cardData.isPDF()) PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java
|
||||
val intent = Intent(context, activityClass)
|
||||
intent.putExtra("SAVED_ITEM_SLUG", cardDataWithLabels.cardData.slug)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
actionHandler = { libraryViewModel.handleSavedItemAction(cardDataWithLabels.cardData.savedItemId, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
@ -32,6 +33,8 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.omnivore.omnivore.MainActivity
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet
|
||||
@ -40,6 +43,7 @@ import app.omnivore.omnivore.ui.theme.OmnivoreTheme
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlin.math.roundToInt
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -51,6 +55,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() {
|
||||
val requestID = intent.getStringExtra("SAVED_ITEM_REQUEST_ID")
|
||||
val slug = intent.getStringExtra("SAVED_ITEM_SLUG")
|
||||
|
||||
|
||||
setContent {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val useDarkIcons = !isSystemInDarkTheme()
|
||||
@ -77,7 +82,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() {
|
||||
requestID = requestID,
|
||||
slug = slug,
|
||||
onLibraryIconTap = if (requestID != null) { { startMainActivity() } } else null,
|
||||
webReaderViewModel = viewModel
|
||||
webReaderViewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -137,6 +142,17 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o
|
||||
}),
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
title = {},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
onBackPressedDispatcher?.onBackPressed()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack,
|
||||
modifier = Modifier,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (onLibraryIconTap != null) {
|
||||
IconButton(onClick = { onLibraryIconTap() }) {
|
||||
|
||||
@ -17,6 +17,7 @@ import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.ui.auth.LoginViewModel
|
||||
import app.omnivore.omnivore.ui.auth.WelcomeScreen
|
||||
import app.omnivore.omnivore.ui.library.LibraryView
|
||||
import app.omnivore.omnivore.ui.library.SearchView
|
||||
import app.omnivore.omnivore.ui.library.LibraryViewModel
|
||||
import app.omnivore.omnivore.ui.settings.PolicyWebView
|
||||
import app.omnivore.omnivore.ui.settings.SettingsViewModel
|
||||
@ -80,6 +81,13 @@ fun PrimaryNavigator(
|
||||
)
|
||||
}
|
||||
|
||||
composable(Routes.Search.route) {
|
||||
SearchView(
|
||||
libraryViewModel = libraryViewModel,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
|
||||
composable(Routes.Settings.route) {
|
||||
SettingsView(loginViewModel = loginViewModel, settingsViewModel = settingsViewModel, navController = navController)
|
||||
}
|
||||
|
||||
@ -43,14 +43,15 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List<SavedItemLabel>, onC
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp)
|
||||
.padding(15.dp)
|
||||
.background(if (isMenuExpanded) Color.LightGray else Color.Transparent)
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 8.dp)
|
||||
.padding(end = 20.dp)
|
||||
.defaultMinSize(minHeight = 55.dp)
|
||||
) {
|
||||
Text(
|
||||
text = cardData.title,
|
||||
@ -64,21 +65,21 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List<SavedItemLabel>, onC
|
||||
|
||||
if (cardData.author != null && cardData.author != "") {
|
||||
Text(
|
||||
text = "By ${cardData.author}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
if (publisherDisplayName != null) {
|
||||
Text(
|
||||
text = publisherDisplayName,
|
||||
text = byline(cardData),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
//
|
||||
// if (publisherDisplayName != null) {
|
||||
// Text(
|
||||
// text = publisherDisplayName,
|
||||
// style = MaterialTheme.typography.bodyMedium,
|
||||
// maxLines = 1,
|
||||
// overflow = TextOverflow.Ellipsis
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
if (cardData.imageURLString != null) {
|
||||
@ -86,9 +87,8 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List<SavedItemLabel>, onC
|
||||
painter = rememberAsyncImagePainter(cardData.imageURLString),
|
||||
contentDescription = "Image associated with saved item",
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.size(80.dp)
|
||||
.size(55.dp, 73.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -98,7 +98,7 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List<SavedItemLabel>, onC
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(start = 6.dp)
|
||||
.padding(start = 5.dp)
|
||||
) {
|
||||
items(labels.sortedBy { it.name }) { label ->
|
||||
val chipColors = LabelChipColors.fromHex(label.color)
|
||||
@ -128,3 +128,16 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List<SavedItemLabel>, onC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun byline(item: SavedItemCardData): String {
|
||||
item.author?.let {
|
||||
return item.author
|
||||
}
|
||||
|
||||
val publisherDisplayName = item.publisherDisplayName()
|
||||
publisherDisplayName?.let {
|
||||
return publisherDisplayName
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user