Increases speed of the bottom navigation by avoiding recreating ViewModels

This commit is contained in:
Vitor Pamplona
2023-03-28 17:11:38 -04:00
parent 73cabc1b25
commit b865d15adf
5 changed files with 83 additions and 23 deletions
@@ -2,11 +2,21 @@ package com.vitorpamplona.amethyst.ui.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.rememberPagerState 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.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen
@@ -30,12 +40,29 @@ fun AppNavigation(
) { ) {
val homePagerState = rememberPagerState() 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) { NavHost(navController, startDestination = Route.Home.route) {
Route.Search.let { route -> Route.Search.let { route ->
composable(route.route, route.arguments, content = { composable(route.route, route.arguments, content = {
val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false
SearchScreen( SearchScreen(
searchFeedViewModel = searchFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, navController = navController,
scrollToTop = scrollToTop scrollToTop = scrollToTop
@@ -53,6 +80,8 @@ fun AppNavigation(
val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false
HomeScreen( HomeScreen(
homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController, navController = navController,
pagerState = homePagerState, pagerState = homePagerState,
@@ -67,7 +96,7 @@ fun AppNavigation(
} }
composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) }) 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.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, navController) })
composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) }) composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) })
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.util.Log
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
@@ -77,10 +78,46 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.math.BigDecimal import java.math.BigDecimal
import kotlin.math.ceil 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) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun NoteCompose( fun NoteComposeInner(
baseNote: Note, baseNote: Note,
routeForLastRead: String? = null, routeForLastRead: String? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -18,7 +18,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
@@ -38,6 +37,8 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@Composable @Composable
fun HomeScreen( fun HomeScreen(
homeFeedViewModel: NostrHomeFeedViewModel,
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, navController: NavController,
pagerState: PagerState, pagerState: PagerState,
@@ -46,12 +47,6 @@ fun HomeScreen(
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val account = accountViewModel.accountLiveData.value?.account ?: return val account = accountViewModel.accountLiveData.value?.account ?: return
HomeNewThreadFeedFilter.account = account
HomeConversationsFeedFilter.account = account
val homeFeedViewModel: NostrHomeFeedViewModel = viewModel()
val repliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel()
LaunchedEffect(accountViewModel) { LaunchedEffect(accountViewModel) {
HomeNewThreadFeedFilter.account = account HomeNewThreadFeedFilter.account = account
HomeConversationsFeedFilter.account = account HomeConversationsFeedFilter.account = account
@@ -64,6 +59,8 @@ fun HomeScreen(
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
HomeNewThreadFeedFilter.account = account
HomeConversationsFeedFilter.account = account
NostrHomeDataSource.resetFilters() NostrHomeDataSource.resetFilters()
homeFeedViewModel.invalidateData() homeFeedViewModel.invalidateData()
repliesFeedViewModel.invalidateData() repliesFeedViewModel.invalidateData()
@@ -13,7 +13,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
import com.vitorpamplona.amethyst.ui.navigation.Route 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 import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel
@Composable @Composable
fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavController) { fun NotificationScreen(notifFeedViewModel: NotificationViewModel, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
NotificationFeedFilter.account = account
val feedViewModel: NotificationViewModel = viewModel()
LaunchedEffect(accountViewModel) { LaunchedEffect(accountViewModel) {
feedViewModel.invalidateData() NotificationFeedFilter.account = account
notifFeedViewModel.invalidateData()
} }
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
feedViewModel.invalidateData() NotificationFeedFilter.account = account
notifFeedViewModel.invalidateData()
} }
} }
@@ -50,7 +48,7 @@ fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavCon
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
CardFeedView(feedViewModel, accountViewModel = accountViewModel, navController, Route.Notification.route) CardFeedView(notifFeedViewModel, accountViewModel = accountViewModel, navController, Route.Notification.route)
} }
} }
} }
@@ -46,7 +46,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -79,6 +78,7 @@ import kotlinx.coroutines.channels.Channel as CoroutineChannel
@Composable @Composable
fun SearchScreen( fun SearchScreen(
searchFeedViewModel: NostrGlobalFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController, navController: NavController,
scrollToTop: Boolean = false scrollToTop: Boolean = false
@@ -88,21 +88,20 @@ fun SearchScreen(
GlobalFeedFilter.account = account GlobalFeedFilter.account = account
val feedViewModel: NostrGlobalFeedViewModel = viewModel()
LaunchedEffect(accountViewModel) { LaunchedEffect(accountViewModel) {
GlobalFeedFilter.account = account GlobalFeedFilter.account = account
NostrGlobalDataSource.resetFilters() NostrGlobalDataSource.resetFilters()
feedViewModel.invalidateData() searchFeedViewModel.invalidateData()
} }
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Global Start") println("Global Start")
GlobalFeedFilter.account = account
NostrGlobalDataSource.start() NostrGlobalDataSource.start()
NostrSearchEventOrUserDataSource.start() NostrSearchEventOrUserDataSource.start()
feedViewModel.invalidateData() searchFeedViewModel.invalidateData()
} }
if (event == Lifecycle.Event.ON_PAUSE) { if (event == Lifecycle.Event.ON_PAUSE) {
println("Global Stop") println("Global Stop")
@@ -123,7 +122,7 @@ fun SearchScreen(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
SearchBar(accountViewModel, navController) SearchBar(accountViewModel, navController)
FeedView(feedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) FeedView(searchFeedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop)
} }
} }
} }