From b865d15adfbab0160ae622b0a687fbe2c8418cd2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 28 Mar 2023 17:11:38 -0400 Subject: [PATCH] Increases speed of the bottom navigation by avoiding recreating ViewModels --- .../amethyst/ui/navigation/AppNavigation.kt | 31 ++++++++++++++- .../amethyst/ui/note/NoteCompose.kt | 39 ++++++++++++++++++- .../amethyst/ui/screen/loggedIn/HomeScreen.kt | 11 ++---- .../ui/screen/loggedIn/NotificationScreen.kt | 14 +++---- .../ui/screen/loggedIn/SearchScreen.kt | 11 +++--- 5 files changed, 83 insertions(+), 23 deletions(-) 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 d154ac4f2..c536fb83c 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 @@ -2,11 +2,21 @@ 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.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.rememberPagerState +import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter +import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter +import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter +import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter +import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen @@ -30,12 +40,29 @@ fun AppNavigation( ) { val homePagerState = rememberPagerState() + // Avoids creating ViewModels for performance reasons (up to 1 second delays) + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = accountState?.account ?: return + + HomeNewThreadFeedFilter.account = account + HomeConversationsFeedFilter.account = account + + val homeFeedViewModel: NostrHomeFeedViewModel = viewModel() + val repliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel() + + GlobalFeedFilter.account = account + val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel() + + NotificationFeedFilter.account = account + val notifFeedViewModel: NotificationViewModel = viewModel() + NavHost(navController, startDestination = Route.Home.route) { Route.Search.let { route -> composable(route.route, route.arguments, content = { val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false SearchScreen( + searchFeedViewModel = searchFeedViewModel, accountViewModel = accountViewModel, navController = navController, scrollToTop = scrollToTop @@ -53,6 +80,8 @@ fun AppNavigation( val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false HomeScreen( + homeFeedViewModel = homeFeedViewModel, + repliesFeedViewModel = repliesFeedViewModel, accountViewModel = accountViewModel, navController = navController, pagerState = homePagerState, @@ -67,7 +96,7 @@ fun AppNavigation( } composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) }) - composable(Route.Notification.route, content = { NotificationScreen(accountViewModel, navController) }) + composable(Route.Notification.route, content = { NotificationScreen(notifFeedViewModel, accountViewModel, navController) }) composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, navController) }) composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) }) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index d91dbc4e7..701b2f50b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Intent import android.graphics.Bitmap +import android.util.Log import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells @@ -77,10 +78,46 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.math.BigDecimal import kotlin.math.ceil +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue + +@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class) +@Composable +fun NoteCompose( + baseNote: Note, + routeForLastRead: String? = null, + modifier: Modifier = Modifier, + isBoostedNote: Boolean = false, + isQuotedNote: Boolean = false, + unPackReply: Boolean = true, + makeItShort: Boolean = false, + addMarginTop: Boolean = true, + parentBackgroundColor: Color? = null, + accountViewModel: AccountViewModel, + navController: NavController +) { + val (value, elapsed) = measureTimedValue { + NoteComposeInner( + baseNote, + routeForLastRead, + modifier, + isBoostedNote, + isQuotedNote, + unPackReply, + makeItShort, + addMarginTop, + parentBackgroundColor, + accountViewModel, + navController + ) + } + + Log.d("Time", "Note Compose in $elapsed for ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}") +} @OptIn(ExperimentalFoundationApi::class) @Composable -fun NoteCompose( +fun NoteComposeInner( baseNote: Note, routeForLastRead: String? = null, modifier: Modifier = Modifier, 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 5733c3aab..15676ba3c 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 @@ -18,7 +18,6 @@ import androidx.compose.ui.res.stringResource 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.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager @@ -38,6 +37,8 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalPagerApi::class) @Composable fun HomeScreen( + homeFeedViewModel: NostrHomeFeedViewModel, + repliesFeedViewModel: NostrHomeRepliesFeedViewModel, accountViewModel: AccountViewModel, navController: NavController, pagerState: PagerState, @@ -46,12 +47,6 @@ fun HomeScreen( val coroutineScope = rememberCoroutineScope() val account = accountViewModel.accountLiveData.value?.account ?: return - HomeNewThreadFeedFilter.account = account - HomeConversationsFeedFilter.account = account - - val homeFeedViewModel: NostrHomeFeedViewModel = viewModel() - val repliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel() - LaunchedEffect(accountViewModel) { HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account @@ -64,6 +59,8 @@ fun HomeScreen( DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { + HomeNewThreadFeedFilter.account = account + HomeConversationsFeedFilter.account = account NostrHomeDataSource.resetFilters() homeFeedViewModel.invalidateData() repliesFeedViewModel.invalidateData() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 92c5ce129..5411488c9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner 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.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.navigation.Route @@ -21,22 +20,21 @@ import com.vitorpamplona.amethyst.ui.screen.CardFeedView import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel @Composable -fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavController) { +fun NotificationScreen(notifFeedViewModel: NotificationViewModel, accountViewModel: AccountViewModel, navController: NavController) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - NotificationFeedFilter.account = account - val feedViewModel: NotificationViewModel = viewModel() - LaunchedEffect(accountViewModel) { - feedViewModel.invalidateData() + NotificationFeedFilter.account = account + notifFeedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { - feedViewModel.invalidateData() + NotificationFeedFilter.account = account + notifFeedViewModel.invalidateData() } } @@ -50,7 +48,7 @@ fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavCon Column( modifier = Modifier.padding(vertical = 0.dp) ) { - CardFeedView(feedViewModel, accountViewModel = accountViewModel, navController, Route.Notification.route) + CardFeedView(notifFeedViewModel, accountViewModel = accountViewModel, navController, Route.Notification.route) } } } 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 57e69750f..319cd8383 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 @@ -46,7 +46,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.model.Account @@ -79,6 +78,7 @@ import kotlinx.coroutines.channels.Channel as CoroutineChannel @Composable fun SearchScreen( + searchFeedViewModel: NostrGlobalFeedViewModel, accountViewModel: AccountViewModel, navController: NavController, scrollToTop: Boolean = false @@ -88,21 +88,20 @@ fun SearchScreen( GlobalFeedFilter.account = account - val feedViewModel: NostrGlobalFeedViewModel = viewModel() - LaunchedEffect(accountViewModel) { GlobalFeedFilter.account = account NostrGlobalDataSource.resetFilters() - feedViewModel.invalidateData() + searchFeedViewModel.invalidateData() } DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { println("Global Start") + GlobalFeedFilter.account = account NostrGlobalDataSource.start() NostrSearchEventOrUserDataSource.start() - feedViewModel.invalidateData() + searchFeedViewModel.invalidateData() } if (event == Lifecycle.Event.ON_PAUSE) { println("Global Stop") @@ -123,7 +122,7 @@ fun SearchScreen( modifier = Modifier.padding(vertical = 0.dp) ) { SearchBar(accountViewModel, navController) - FeedView(feedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) + FeedView(searchFeedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) } } }