feat(android): wire up search UI into navigation graph

This commit is contained in:
Harsh Shandilya 2023-07-18 22:50:30 +05:30
parent 8d6aba9865
commit d3c3e3ca08
3 changed files with 79 additions and 29 deletions

View file

@ -18,9 +18,11 @@ import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.ImportExport import androidx.compose.material.icons.filled.ImportExport
import androidx.compose.material.icons.filled.NewReleases import androidx.compose.material.icons.filled.NewReleases
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Whatshot import androidx.compose.material.icons.filled.Whatshot
import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Whatshot import androidx.compose.material.icons.outlined.Whatshot
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -59,6 +61,7 @@ import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
import dev.msfjarvis.claw.android.ui.decorations.TransparentSystemBars import dev.msfjarvis.claw.android.ui.decorations.TransparentSystemBars
import dev.msfjarvis.claw.android.ui.lists.DatabasePosts import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
import dev.msfjarvis.claw.android.ui.lists.NetworkPosts import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
import dev.msfjarvis.claw.android.ui.lists.SearchList
import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType
import dev.msfjarvis.claw.android.ui.navigation.Destinations import dev.msfjarvis.claw.android.ui.navigation.Destinations
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
@ -86,6 +89,7 @@ fun LobstersApp(
val hottestListState = rememberLazyListState() val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState() val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState() val savedListState = rememberLazyListState()
val searchListState = rememberLazyListState()
val navController = rememberNavController() val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
@ -97,6 +101,7 @@ fun LobstersApp(
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems() val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems() val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPosts.collectAsState(persistentMapOf()) val savedPosts by viewModel.savedPosts.collectAsState(persistentMapOf())
val searchResults = viewModel.searchResults.collectAsLazyPagingItems()
val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass) val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass)
@ -130,44 +135,54 @@ fun LobstersApp(
) { ) {
coroutineScope.launch { savedListState.animateScrollToItem(index = 0) } coroutineScope.launch { savedListState.animateScrollToItem(index = 0) }
}, },
NavigationItem(
label = "Search",
route = Destinations.Search.route,
icon = Icons.Outlined.Search,
selectedIcon = Icons.Filled.Search,
) {
coroutineScope.launch { searchListState.animateScrollToItem(index = 0) }
},
) )
TransparentSystemBars() TransparentSystemBars()
Scaffold( Scaffold(
topBar = { topBar = {
ClawAppBar( if (currentDestination != Destinations.Search.route) {
navigationIcon = { ClawAppBar(
if ( navigationIcon = {
navController.previousBackStackEntry != null && if (
navItems.none { it.route == currentDestination } navController.previousBackStackEntry != null &&
) { navItems.none { it.route == currentDestination }
IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() }
) { ) {
Icon( IconButton(
imageVector = Icons.Filled.ArrowBack, onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() },
contentDescription = "Go back to previous screen", ) {
) Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
)
}
} }
} },
}, title = {
title = { if (navItems.any { it.route == currentDestination }) {
if (navItems.any { it.route == currentDestination }) { Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
if (navItems.any { it.route == currentDestination }) {
IconButton(onClick = { navController.navigate(Destinations.DataTransfer.route) }) {
Icon(
imageVector = Icons.Filled.ImportExport,
contentDescription = "Data transfer options"
)
} }
} },
}, actions = {
) if (navItems.any { it.route == currentDestination }) {
IconButton(onClick = { navController.navigate(Destinations.DataTransfer.route) }) {
Icon(
imageVector = Icons.Filled.ImportExport,
contentDescription = "Data transfer options",
)
}
}
},
)
}
}, },
bottomBar = { bottomBar = {
AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) { AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) {
@ -224,6 +239,21 @@ fun LobstersApp(
postActions = postActions, postActions = postActions,
) )
} }
composable(Destinations.Search.route) {
setWebUri("https://lobste.rs/search")
SearchList(
items = searchResults,
listState = searchListState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
searchQuery = viewModel.searchQuery,
setSearchQuery = { query -> viewModel.searchQuery = query },
triggerSearch = { query ->
viewModel.searchQuery = query
searchResults.refresh()
}
)
}
composable( composable(
route = Destinations.Comments.route, route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }), arguments = listOf(navArgument("postId") { type = NavType.StringType }),

View file

@ -21,6 +21,10 @@ sealed class Destinations {
override val route = "saved" override val route = "saved"
} }
object Search : Destinations() {
override val route = "search"
}
object Comments : Destinations() { object Comments : Destinations() {
const val placeholder = "{postId}" const val placeholder = "{postId}"
override val route = "comments/$placeholder" override val route = "comments/$placeholder"

View file

@ -6,6 +6,9 @@
*/ */
package dev.msfjarvis.claw.android.viewmodel package dev.msfjarvis.claw.android.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.Pager import androidx.paging.Pager
@ -16,7 +19,9 @@ import com.slack.eithernet.ApiResult.Success
import dev.msfjarvis.claw.android.paging.LobstersPagingSource import dev.msfjarvis.claw.android.paging.LobstersPagingSource
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
import dev.msfjarvis.claw.android.paging.SearchPagingSource
import dev.msfjarvis.claw.api.LobstersApi import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.core.injection.IODispatcher import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.database.local.SavedPost import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.Comment import dev.msfjarvis.claw.model.Comment
@ -41,11 +46,13 @@ class ClawViewModel
@Inject @Inject
constructor( constructor(
private val api: LobstersApi, private val api: LobstersApi,
private val searchApi: LobstersSearchApi,
private val savedPostsRepository: SavedPostsRepository, private val savedPostsRepository: SavedPostsRepository,
private val commentsRepository: CommentsRepository, private val commentsRepository: CommentsRepository,
private val linkMetadataRepository: LinkMetadataRepository, private val linkMetadataRepository: LinkMetadataRepository,
private val dataTransferRepository: DataTransferRepository, private val dataTransferRepository: DataTransferRepository,
private val pagingSourceFactory: LobstersPagingSource.Factory, private val pagingSourceFactory: LobstersPagingSource.Factory,
private val searchPagingSourceFactory: SearchPagingSource.Factory,
@IODispatcher private val ioDispatcher: CoroutineDispatcher, @IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() { ) : ViewModel() {
private val hottestPostsPager = private val hottestPostsPager =
@ -56,6 +63,10 @@ constructor(
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) { Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
pagingSourceFactory.create(api::getNewestPosts) pagingSourceFactory.create(api::getNewestPosts)
} }
private val searchResultsPager =
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
searchPagingSourceFactory.create { searchApi.searchPosts(searchQuery, it) }
}
val hottestPosts val hottestPosts
get() = hottestPostsPager.flow get() = hottestPostsPager.flow
@ -69,6 +80,11 @@ constructor(
val savedPosts val savedPosts
get() = savedPostsFlow.map(::mapSavedPosts) get() = savedPostsFlow.map(::mapSavedPosts)
val searchResults
get() = searchResultsPager.flow
var searchQuery by mutableStateOf("")
private fun mapSavedPosts(items: List<SavedPost>): ImmutableMap<Month, List<SavedPost>> { private fun mapSavedPosts(items: List<SavedPost>): ImmutableMap<Month, List<SavedPost>> {
val sorted = items.sortedByDescending { post -> post.createdAt.toLocalDateTime() } val sorted = items.sortedByDescending { post -> post.createdAt.toLocalDateTime() }
return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month }.toImmutableMap() return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month }.toImmutableMap()