Adjusts bottombar to not recalculate hasNewItems in every recomposition.

This commit is contained in:
Vitor Pamplona
2023-05-22 17:51:59 -04:00
parent 278091e6ef
commit 22dbaaf95a
@@ -34,8 +34,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
@@ -96,7 +98,77 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
backgroundColor = MaterialTheme.colors.background backgroundColor = MaterialTheme.colors.background
) { ) {
bottomNavigationItems.forEach { item -> bottomNavigationItems.forEach { item ->
BottomIcon(item, accountViewModel, navController) HasNewItemsIcon(item, accountViewModel, navController)
}
}
}
}
}
@Composable
private fun RowScope.HasNewItemsIcon(
route: Route,
accountViewModel: AccountViewModel,
navController: NavHostController
) {
val scope = rememberCoroutineScope()
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return
val notifState = NotificationCache.live.observeAsState()
val notif = remember(notifState) { notifState.value } ?: return
var hasNewItems by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notif) {
scope.launch(Dispatchers.IO) {
val newHasNewItems = route.hasNewItems(account, notif.cache, emptySet())
if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems
}
}
}
LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect {
val newHasNewItems = route.hasNewItems(account, notif.cache, it)
if (newHasNewItems != hasNewItems) {
hasNewItems = newHasNewItems
}
}
}
}
BottomIcon(
icon = route.icon,
size = if ("Home" == route.base) 25.dp else 23.dp,
iconSize = if ("Home" == route.base) 24.dp else 20.dp,
base = route.base,
hasNewItems = hasNewItems,
navController
) { selected ->
scope.launch {
if (!selected) {
navController.navigate(route.base) {
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start)
restoreState = true
}
launchSingleTop = true
restoreState = true
}
} else {
val newRoute = route.route.replace("{scrollToTop}", "true")
navController.navigate(newRoute) {
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start) { inclusive = newRoute == Route.Home.route }
restoreState = true
}
launchSingleTop = true
restoreState = true
} }
} }
} }
@@ -105,84 +177,47 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
@Composable @Composable
private fun RowScope.BottomIcon( private fun RowScope.BottomIcon(
item: Route, icon: Int,
accountViewModel: AccountViewModel, size: Dp,
navController: NavHostController iconSize: Dp,
base: String,
hasNewItems: Boolean,
navController: NavController,
onClick: (Boolean) -> Unit
) { ) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
navBackStackEntry?.let { navBackStackEntry -> navBackStackEntry?.let {
val currentRoute = remember(navBackStackEntry) { navBackStackEntry.destination.route } val selected = remember(it) {
val currentRouteBase = remember(navBackStackEntry) { navBackStackEntry.destination.route?.substringBefore("?") } it.destination.route?.substringBefore("?") == base
val selected = remember(navBackStackEntry) { currentRouteBase == item.base } }
val coroutineScope = rememberCoroutineScope()
BottomNavigationItem( BottomNavigationItem(
icon = { NotifiableIcon(item, selected, accountViewModel) }, icon = {
NotifiableIcon(
icon,
size,
iconSize,
selected,
hasNewItems
)
},
selected = selected, selected = selected,
onClick = { onClick = { onClick(selected) }
coroutineScope.launch {
if (!selected) {
navController.navigate(item.base) {
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start)
restoreState = true
}
launchSingleTop = true
restoreState = true
}
} else if (currentRoute != null) {
val route = currentRoute.replace("{scrollToTop}", "true")
navController.navigate(route) {
navController.graph.startDestinationRoute?.let { start ->
popUpTo(start) { inclusive = item.route == Route.Home.route }
restoreState = true
}
launchSingleTop = true
restoreState = true
}
}
}
}
) )
} }
} }
@Composable @Composable
private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) { private fun NotifiableIcon(icon: Int, size: Dp, iconSize: Dp, selected: Boolean, hasNewItems: Boolean) {
val scope = rememberCoroutineScope() Box(Modifier.size(size)) {
Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) {
Icon( Icon(
painter = painterResource(id = route.icon), painter = painterResource(id = icon),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp), modifier = Modifier.size(iconSize),
tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified
) )
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val notifState = NotificationCache.live.observeAsState()
val notif = notifState.value ?: return
var hasNewItems by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notif) {
scope.launch(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, emptySet())
}
}
LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect {
hasNewItems = route.hasNewItems(account, notif.cache, it)
}
}
}
if (hasNewItems) { if (hasNewItems) {
Box( Box(
Modifier Modifier