From 85d722f96d8ed1183ad45a9ff22677118da84753 Mon Sep 17 00:00:00 2001 From: maxmoney21m Date: Mon, 13 Mar 2023 19:03:15 +0800 Subject: [PATCH] Hoist globalfeed state out of Screen component --- .../amethyst/ui/navigation/AppBottomBar.kt | 31 ++++++++++--------- .../amethyst/ui/navigation/AppNavigation.kt | 27 +++++++++++++++- .../amethyst/ui/navigation/Routes.kt | 24 +++++--------- .../amethyst/ui/screen/FeedView.kt | 12 +++---- .../amethyst/ui/screen/loggedIn/HomeScreen.kt | 4 +-- .../ui/screen/loggedIn/SearchScreen.kt | 18 +++++------ 6 files changed, 67 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt index 52c54034c..5d68ddba0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt @@ -83,9 +83,9 @@ fun keyboardAsState(): State { @Composable fun AppBottomBar(navController: NavHostController, accountViewModel: AccountViewModel) { - val currentRoute = currentRoute(navController)?.substringBefore("?") + val currentRoute = currentRoute(navController) + val currentRouteBase = currentRoute?.substringBefore("?") val coroutineScope = rememberCoroutineScope() - val isKeyboardOpen by keyboardAsState() if (isKeyboardOpen == Keyboard.Closed) { @@ -99,13 +99,15 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView backgroundColor = MaterialTheme.colors.background ) { bottomNavigationItems.forEach { item -> + val selected = currentRouteBase == item.base + BottomNavigationItem( - icon = { NotifiableIcon(item, currentRoute, accountViewModel) }, - selected = currentRoute == item.base, + icon = { NotifiableIcon(item, selected, accountViewModel) }, + selected = selected, onClick = { coroutineScope.launch { - if (currentRoute != item.base) { - navController.navigate(item.route) { + if (currentRouteBase != item.base) { + navController.navigate(item.base) { navController.graph.startDestinationRoute?.let { start -> popUpTo(start) restoreState = true @@ -114,7 +116,8 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView restoreState = true } } else { - navController.navigate("${item.base}?forceRefresh=${true}") { + val route = currentRoute.replace("{scrollToTop}", "true") + navController.navigate(route) { navController.graph.startDestinationRoute?.let { start -> popUpTo(start) { inclusive = item.route == Route.Home.route } restoreState = true @@ -134,13 +137,13 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView } @Composable -private fun NotifiableIcon(item: Route, currentRoute: String?, accountViewModel: AccountViewModel) { - Box(Modifier.size(if ("Home" == item.base) 25.dp else 23.dp)) { +private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) { + Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) { Icon( - painter = painterResource(id = item.icon), + painter = painterResource(id = route.icon), null, - modifier = Modifier.size(if ("Home" == item.base) 24.dp else 20.dp), - tint = if (currentRoute == item.base) MaterialTheme.colors.primary else Color.Unspecified + modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp), + tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified ) val accountState by accountViewModel.accountLiveData.observeAsState() @@ -159,13 +162,13 @@ private fun NotifiableIcon(item: Route, currentRoute: String?, accountViewModel: LaunchedEffect(key1 = notif) { withContext(Dispatchers.IO) { - hasNewItems = item.hasNewItems(account, notif.cache, context) + hasNewItems = route.hasNewItems(account, notif.cache, context) } } LaunchedEffect(key1 = db) { withContext(Dispatchers.IO) { - hasNewItems = item.hasNewItems(account, notif.cache, context) + hasNewItems = route.hasNewItems(account, notif.cache, context) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 76e7164ed..27d2b4b16 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -1,11 +1,17 @@ package com.vitorpamplona.amethyst.ui.navigation import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel +import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen @Composable fun AppNavigation( @@ -14,9 +20,28 @@ fun AppNavigation( accountStateViewModel: AccountStateViewModel, nextPage: String? = null ) { + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = accountState?.account ?: return + + GlobalFeedFilter.account = account + val globalFeedViewModel: NostrGlobalFeedViewModel = viewModel() + NavHost(navController, startDestination = Route.Home.route) { + Route.Search.let { route -> + composable(route.route, route.arguments, content = { + SearchScreen( + accountViewModel = accountViewModel, + feedViewModel = globalFeedViewModel, + navController = navController, + scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false + ) + }) + } + Routes.forEach { - composable(it.route, it.arguments, content = it.buildScreen(accountViewModel, accountStateViewModel, navController)) + it.buildScreen?.let { fn -> + composable(it.route, it.arguments, content = fn(accountViewModel, accountStateViewModel, navController)) + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index d84b0b1cc..ec7b30a00 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -25,7 +25,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.FiltersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen sealed class Route( @@ -33,32 +32,25 @@ sealed class Route( val icon: Int, val hasNewItems: (Account, NotificationCache, Context) -> Boolean = { _, _, _ -> false }, val arguments: List = emptyList(), - val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit + val buildScreen: ((AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit)? = null ) { val base: String get() = route.substringBefore("?") object Home : Route( - "Home?forceRefresh={forceRefresh}", + "Home?scrollToTop={scrollToTop}", R.drawable.ic_home, - arguments = listOf(navArgument("forceRefresh") { type = NavType.BoolType; defaultValue = false }), + arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }), hasNewItems = { accountViewModel, cache, context -> homeHasNewItems(accountViewModel, cache, context) }, buildScreen = { accountViewModel, _, navController -> - { backStackEntry -> - HomeScreen(accountViewModel, navController, backStackEntry.arguments?.getBoolean("forceRefresh", false) ?: false) - } + { HomeScreen(accountViewModel, navController, it.arguments?.getBoolean("scrollToTop", false) ?: false) } } ) object Search : Route( - "Search?forceRefresh={forceRefresh}", - R.drawable.ic_globe, - arguments = listOf(navArgument("forceRefresh") { type = NavType.BoolType; defaultValue = false }), - buildScreen = { acc, _, navController -> - { backStackEntry -> - SearchScreen(acc, navController, backStackEntry.arguments?.getBoolean("forceRefresh", false) ?: false) - } - } + route = "Search?scrollToTop={scrollToTop}", + icon = R.drawable.ic_globe, + arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }) ) object Notification : Route( @@ -154,7 +146,7 @@ val Routes = listOf( // * Functions below only exist because we have not broken the datasource classes into backend and frontend. // ** @Composable -public fun currentRoute(navController: NavHostController): String? { +fun currentRoute(navController: NavHostController): String? { val navBackStackEntry by navController.currentBackStackEntryAsState() return navBackStackEntry?.destination?.route } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index af98fe35c..10453c6df 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -42,11 +42,11 @@ fun FeedView( navController: NavController, routeForLastRead: String?, scrollStateKey: String? = null, - forceRefresh: Boolean = false + scrollToTop: Boolean = false ) { val feedState by viewModel.feedContent.collectAsState() - var refreshing by remember { mutableStateOf(forceRefresh) } + var refreshing by remember { mutableStateOf(false) } val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) @@ -77,7 +77,7 @@ fun FeedView( accountViewModel, navController, scrollStateKey, - forceRefresh + scrollToTop ) } @@ -99,7 +99,7 @@ private fun FeedLoaded( accountViewModel: AccountViewModel, navController: NavController, scrollStateKey: String?, - forceRefresh: Boolean = false + scrollToTop: Boolean = false ) { val listState = if (scrollStateKey != null) { rememberForeverLazyListState(scrollStateKey) @@ -107,9 +107,9 @@ private fun FeedLoaded( rememberLazyListState() } - if (forceRefresh) { + if (scrollToTop) { LaunchedEffect(Unit) { - listState.animateScrollToItem(0) + listState.scrollToItem(index = 0) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 203684542..cfed81664 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -39,7 +39,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalPagerApi::class) @Composable -fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController, forceRefresh: Boolean = false) { +fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController, scrollToTop: Boolean = false) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -107,7 +107,7 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController, } HorizontalPager(count = 2, state = pagerState) { when (pagerState.currentPage) { - 0 -> FeedView(feedViewModel, accountViewModel, navController, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS, forceRefresh) + 0 -> FeedView(feedViewModel, accountViewModel, navController, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS, scrollToTop) 1 -> FeedView(feedViewModelReplies, accountViewModel, navController, Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index c4dbfaab3..f55a5f3a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -47,7 +47,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.RoboHashCache @@ -58,14 +57,13 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrGlobalDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource -import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter import com.vitorpamplona.amethyst.ui.note.ChannelName import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.FeedView -import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.FeedViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -79,12 +77,12 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.channels.Channel as CoroutineChannel @Composable -fun SearchScreen(accountViewModel: AccountViewModel, navController: NavController, forceRefresh: Boolean = false) { - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - - GlobalFeedFilter.account = account - val feedViewModel: NostrGlobalFeedViewModel = viewModel() +fun SearchScreen( + accountViewModel: AccountViewModel, + feedViewModel: FeedViewModel, + navController: NavController, + scrollToTop: Boolean = false +) { val lifeCycleOwner = LocalLifecycleOwner.current LaunchedEffect(accountViewModel) { @@ -115,7 +113,7 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle modifier = Modifier.padding(vertical = 0.dp) ) { SearchBar(accountViewModel, navController) - FeedView(feedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, forceRefresh) + FeedView(feedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) } } }