From d3c3e3ca089a21620f17b8b96d4f8c340c4e80d7 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 18 Jul 2023 22:50:30 +0530 Subject: [PATCH] feat(android): wire up search UI into navigation graph --- .../msfjarvis/claw/android/ui/LobstersApp.kt | 88 +++++++++++++------ .../android/ui/navigation/Destinations.kt | 4 + .../claw/android/viewmodel/ClawViewModel.kt | 16 ++++ 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt index c29dbc4c..c4c6365d 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt @@ -18,9 +18,11 @@ import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.ImportExport 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.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.NewReleases +import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Whatshot import androidx.compose.material3.Icon 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.lists.DatabasePosts 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.Destinations import dev.msfjarvis.claw.android.viewmodel.ClawViewModel @@ -86,6 +89,7 @@ fun LobstersApp( val hottestListState = rememberLazyListState() val newestListState = rememberLazyListState() val savedListState = rememberLazyListState() + val searchListState = rememberLazyListState() val navController = rememberNavController() val coroutineScope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } @@ -97,6 +101,7 @@ fun LobstersApp( val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems() val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems() val savedPosts by viewModel.savedPosts.collectAsState(persistentMapOf()) + val searchResults = viewModel.searchResults.collectAsLazyPagingItems() val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass) @@ -130,44 +135,54 @@ fun LobstersApp( ) { 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() Scaffold( topBar = { - ClawAppBar( - navigationIcon = { - if ( - navController.previousBackStackEntry != null && - navItems.none { it.route == currentDestination } - ) { - IconButton( - onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() } + if (currentDestination != Destinations.Search.route) { + ClawAppBar( + navigationIcon = { + if ( + navController.previousBackStackEntry != null && + navItems.none { it.route == currentDestination } ) { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = "Go back to previous screen", - ) + IconButton( + onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() }, + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Go back to previous screen", + ) + } } - } - }, - title = { - if (navItems.any { it.route == currentDestination }) { - 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" - ) + }, + title = { + if (navItems.any { it.route == currentDestination }) { + 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", + ) + } + } + }, + ) + } }, bottomBar = { AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) { @@ -224,6 +239,21 @@ fun LobstersApp( 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( route = Destinations.Comments.route, arguments = listOf(navArgument("postId") { type = NavType.StringType }), diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destinations.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destinations.kt index 4d760ea1..1e213e6a 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destinations.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destinations.kt @@ -21,6 +21,10 @@ sealed class Destinations { override val route = "saved" } + object Search : Destinations() { + override val route = "search" + } + object Comments : Destinations() { const val placeholder = "{postId}" override val route = "comments/$placeholder" diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt index 5dff2cd7..c8075c5e 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt @@ -6,6 +6,9 @@ */ 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.viewModelScope 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.Companion.PAGE_SIZE 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.LobstersSearchApi import dev.msfjarvis.claw.core.injection.IODispatcher import dev.msfjarvis.claw.database.local.SavedPost import dev.msfjarvis.claw.model.Comment @@ -41,11 +46,13 @@ class ClawViewModel @Inject constructor( private val api: LobstersApi, + private val searchApi: LobstersSearchApi, private val savedPostsRepository: SavedPostsRepository, private val commentsRepository: CommentsRepository, private val linkMetadataRepository: LinkMetadataRepository, private val dataTransferRepository: DataTransferRepository, private val pagingSourceFactory: LobstersPagingSource.Factory, + private val searchPagingSourceFactory: SearchPagingSource.Factory, @IODispatcher private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { private val hottestPostsPager = @@ -56,6 +63,10 @@ constructor( Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) { pagingSourceFactory.create(api::getNewestPosts) } + private val searchResultsPager = + Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) { + searchPagingSourceFactory.create { searchApi.searchPosts(searchQuery, it) } + } val hottestPosts get() = hottestPostsPager.flow @@ -69,6 +80,11 @@ constructor( val savedPosts get() = savedPostsFlow.map(::mapSavedPosts) + val searchResults + get() = searchResultsPager.flow + + var searchQuery by mutableStateOf("") + private fun mapSavedPosts(items: List): ImmutableMap> { val sorted = items.sortedByDescending { post -> post.createdAt.toLocalDateTime() } return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month }.toImmutableMap()