From 3b751452d59b9a9bd44e543b397fe9aa0e05dd73 Mon Sep 17 00:00:00 2001 From: Omico Date: Wed, 12 Jun 2024 20:26:15 -0700 Subject: [PATCH] achievements: Optimize recomposition --- .../achievements/AchievementsPresenter.kt | 11 ++-- .../feature/achievements/AchievementsUi.kt | 8 ++- .../achievements/AchievementsUiState.kt | 20 +++---- .../component/AchievementsDetailContent.kt | 14 ++--- .../component/AchievementsDetailPaneUi.kt | 9 +-- .../component/AchievementsDetailTopAppBar.kt | 16 ++---- .../component/AchievementsListPaneUi.kt | 55 ++++++++++--------- .../kotlin/dev/omico/wwm/ui/SnapshotState.kt | 16 ++++++ .../kotlin/dev/omico/wwm/ui}/WwText.kt | 7 ++- 9 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/SnapshotState.kt rename wwm/core/{resources/src/commonMain/kotlin/dev/omico/wwm/resources => ui/src/commonMain/kotlin/dev/omico/wwm/ui}/WwText.kt (66%) diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsPresenter.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsPresenter.kt index 6bb8911..2a11d37 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsPresenter.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsPresenter.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.setValue import com.slack.circuit.retained.collectAsRetainedState import dev.omico.wwm.resources.model.game.WwLocale import dev.omico.wwm.ui.WwmUiComponent +import dev.omico.wwm.ui.rememberUpdatedListState import kotlinx.coroutines.launch context(WwmUiComponent) @@ -30,12 +31,12 @@ internal fun produceAchievementsUiState(): AchievementsUiState { LaunchedEffect(Unit) { achievementsRepository.load() } LaunchedEffect(locale) { achievementsRepository.reloadMultiText(locale) } return AchievementsUiState( - achievements = achievements, - achievementCategories = achievementCategories, - achievementGroups = achievementGroups, - multiText = multiText, + achievements = rememberUpdatedListState(achievements), + achievementCategories = rememberUpdatedListState(achievementCategories), + achievementGroups = rememberUpdatedListState(achievementGroups), + multiText = rememberUpdatedListState(multiText), locale = locale, - markedAchievementIds = markedAchievementIds, + markedAchievementIds = rememberUpdatedListState(markedAchievementIds), eventSink = { event -> when (event) { is AchievementsUiEvent.ChangeLocale -> locale = event.locale diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUi.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUi.kt index 372c421..45d5bce 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUi.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUi.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import dev.omico.wwm.feature.achievements.component.AchievementsDetailPaneUi import dev.omico.wwm.feature.achievements.component.AchievementsListPaneUi import dev.omico.wwm.resources.model.game.WwAchievementGroup +import dev.omico.wwm.ui.rememberWwText @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable @@ -47,9 +48,14 @@ internal fun AchievementsUi( AnimatedPane( modifier = Modifier, content = { + val achievementGroup = navigator.currentDestination?.content ?: return@AnimatedPane AchievementsDetailPaneUi( state = state, - achievementGroup = navigator.currentDestination?.content ?: return@AnimatedPane, + achievementGroupId = achievementGroup.id, + achievementGroupName = rememberWwText( + multiText = state.multiText, + name = achievementGroup.name, + ), onNavigateBack = navigator::navigateBack, ) }, diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUiState.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUiState.kt index d6c118f..7cbd6d3 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUiState.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/AchievementsUiState.kt @@ -3,20 +3,20 @@ */ package dev.omico.wwm.feature.achievements +import androidx.compose.runtime.snapshots.SnapshotStateList import com.slack.circuit.runtime.CircuitUiState -import dev.omico.wwm.data.WwmMarkedAchievementIds -import dev.omico.wwm.resources.model.game.WwAchievementCategories -import dev.omico.wwm.resources.model.game.WwAchievementGroups -import dev.omico.wwm.resources.model.game.WwAchievements +import dev.omico.wwm.resources.model.game.WwAchievement +import dev.omico.wwm.resources.model.game.WwAchievementCategory +import dev.omico.wwm.resources.model.game.WwAchievementGroup import dev.omico.wwm.resources.model.game.WwLocale -import dev.omico.wwm.resources.model.game.WwMultiText +import dev.omico.wwm.resources.model.game.WwText data class AchievementsUiState( - val achievements: WwAchievements, - val achievementCategories: WwAchievementCategories, - val achievementGroups: WwAchievementGroups, - val multiText: WwMultiText, + val achievements: SnapshotStateList, + val achievementCategories: SnapshotStateList, + val achievementGroups: SnapshotStateList, + val multiText: SnapshotStateList, val locale: WwLocale, - val markedAchievementIds: WwmMarkedAchievementIds, + val markedAchievementIds: SnapshotStateList, val eventSink: (AchievementsUiEvent) -> Unit, ) : CircuitUiState diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailContent.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailContent.kt index d62b1e3..0d85539 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailContent.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailContent.kt @@ -11,28 +11,24 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import dev.omico.wwm.feature.achievements.AchievementsUiEvent import dev.omico.wwm.feature.achievements.AchievementsUiState import dev.omico.wwm.resources.model.game.WwAchievement -import dev.omico.wwm.resources.model.game.WwAchievementGroup -import dev.omico.wwm.resources.rememberWwText +import dev.omico.wwm.ui.rememberWwText @OptIn(ExperimentalFoundationApi::class) @Composable internal fun AchievementsDetailContent( state: AchievementsUiState, - achievementGroup: WwAchievementGroup, + achievementGroupId: Int, contentPadding: PaddingValues, ) { - val achievementGroupId by rememberUpdatedState(achievementGroup.id) - val markedAchievementIds by rememberUpdatedState(state.markedAchievementIds) - val achievements by remember(achievementGroupId, markedAchievementIds) { + val achievements by remember(achievementGroupId, state.markedAchievementIds) { derivedStateOf { state.achievements .filter { achievement -> achievement.groupId == achievementGroupId } - .sortedBy { achievement -> achievement.id in markedAchievementIds } + .sortedBy { achievement -> achievement.id in state.markedAchievementIds } } } LazyColumn( @@ -43,7 +39,7 @@ internal fun AchievementsDetailContent( key = WwAchievement::id, itemContent = { achievement -> AchievementsDetailItem( - marked = achievement.id in markedAchievementIds, + marked = achievement.id in state.markedAchievementIds, onMarkedChange = { marked -> state.eventSink( AchievementsUiEvent.ChangeAchievementMark( diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailPaneUi.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailPaneUi.kt index 84628ad..9165889 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailPaneUi.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailPaneUi.kt @@ -6,26 +6,27 @@ package dev.omico.wwm.feature.achievements.component import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import dev.omico.wwm.feature.achievements.AchievementsUiState -import dev.omico.wwm.resources.model.game.WwAchievementGroup @Composable internal fun AchievementsDetailPaneUi( state: AchievementsUiState, - achievementGroup: WwAchievementGroup, + achievementGroupId: Int, + achievementGroupName: String, onNavigateBack: () -> Unit, ) { Scaffold( topBar = { AchievementsDetailTopAppBar( state = state, - achievementGroup = achievementGroup, + achievementGroupId = achievementGroupId, + achievementGroupName = achievementGroupName, onNavigateBack = onNavigateBack, ) }, content = { innerPadding -> AchievementsDetailContent( state = state, - achievementGroup = achievementGroup, + achievementGroupId = achievementGroupId, contentPadding = innerPadding, ) }, diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailTopAppBar.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailTopAppBar.kt index 285d93b..ea32d1f 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailTopAppBar.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsDetailTopAppBar.kt @@ -16,10 +16,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import dev.omico.wwm.feature.achievements.AchievementsUiState -import dev.omico.wwm.resources.model.game.WwAchievementGroup -import dev.omico.wwm.resources.rememberWwText import dev.omico.wwm.ui.LocalNavigationSuiteType @OptIn( @@ -29,26 +26,21 @@ import dev.omico.wwm.ui.LocalNavigationSuiteType @Composable internal fun AchievementsDetailTopAppBar( state: AchievementsUiState, - achievementGroup: WwAchievementGroup, + achievementGroupId: Int, + achievementGroupName: String, onNavigateBack: () -> Unit, ) { TopAppBar( title = { - val achievementGroupName = rememberWwText( - multiText = state.multiText, - name = achievementGroup.name, - ) - val markedAchievementIds by rememberUpdatedState(state.markedAchievementIds) - val achievementGroupId by rememberUpdatedState(achievementGroup.id) val countAchievementGroup by remember(achievementGroupId) { derivedStateOf { state.achievements.count { achievement -> achievement.groupId == achievementGroupId } } } - val countMarkedAchievementGroup by remember(achievementGroupId, markedAchievementIds) { + val countMarkedAchievementGroup by remember(achievementGroupId, state.markedAchievementIds) { derivedStateOf { state.achievements.count { achievement -> - achievement.groupId == achievementGroupId && achievement.id in markedAchievementIds + achievement.groupId == achievementGroupId && achievement.id in state.markedAchievementIds } } } diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsListPaneUi.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsListPaneUi.kt index b8b142b..52fcf01 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsListPaneUi.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementsListPaneUi.kt @@ -5,6 +5,7 @@ package dev.omico.wwm.feature.achievements.component import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope @@ -19,14 +20,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dev.omico.wwm.feature.achievements.AchievementsUiState import dev.omico.wwm.resources.WwmIcons import dev.omico.wwm.resources.model.game.WwAchievementCategory import dev.omico.wwm.resources.model.game.WwAchievementGroup -import dev.omico.wwm.resources.rememberWwText +import dev.omico.wwm.ui.rememberWwText import org.jetbrains.compose.resources.painterResource @OptIn(ExperimentalMaterial3Api::class) @@ -40,11 +40,10 @@ internal fun AchievementsListPaneUi( topBar = { TopAppBar( title = { - val markedAchievementIds by rememberUpdatedState(state.markedAchievementIds) val countAchievement by remember { derivedStateOf { state.achievements.count() } } - val countMarkedAchievement by remember(markedAchievementIds) { + val countMarkedAchievement by remember(state.markedAchievementIds) { derivedStateOf { - state.achievements.count { achievement -> achievement.id in markedAchievementIds } + state.achievements.count { achievement -> achievement.id in state.markedAchievementIds } } } Text("Achievements $countMarkedAchievement/$countAchievement") @@ -73,6 +72,7 @@ internal fun AchievementsListPaneUi( }, content = { innerPadding -> LazyColumn( + modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(space = 8.dp), contentPadding = innerPadding, content = { @@ -101,6 +101,7 @@ private fun LazyListScope.achievementsCategoryItem( ) { item( key = category.id, + contentType = category::class, content = { AchievementsListItem( text = rememberWwText( @@ -112,24 +113,28 @@ private fun LazyListScope.achievementsCategoryItem( ) }, ) - if (isExpanded) { - items( - items = state.achievementGroups.filter { it.category == category.id }, - key = WwAchievementGroup::id, - itemContent = { achievementGroup -> - AchievementsListItem( - text = rememberWwText( - multiText = state.multiText, - name = achievementGroup.name, - ), - modifier = run { - Modifier - .padding(start = 16.dp) - .animateItemPlacement() - }, - onClick = { onGroupItemClick(achievementGroup) }, - ) - }, - ) - } + items( + items = when { + isExpanded -> + state.achievementGroups + .filter { achievementGroup -> achievementGroup.category == category.id } + else -> emptyList() + }, + key = WwAchievementGroup::id, + contentType = { achievementGroup -> achievementGroup::class }, + itemContent = { achievementGroup -> + AchievementsListItem( + text = rememberWwText( + multiText = state.multiText, + name = achievementGroup.name, + ), + modifier = run { + Modifier + .padding(start = 16.dp) + .animateItemPlacement() + }, + onClick = { onGroupItemClick(achievementGroup) }, + ) + }, + ) } diff --git a/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/SnapshotState.kt b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/SnapshotState.kt new file mode 100644 index 0000000..63d0edf --- /dev/null +++ b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/SnapshotState.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 Omico. All Rights Reserved. + */ +package dev.omico.wwm.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.SnapshotStateList + +// TODO Waiting for Delusion 0.10.0 +@Composable +fun rememberUpdatedListState(newValues: Iterable): SnapshotStateList = + remember>(::mutableStateListOf) + .apply(SnapshotStateList::clear) + .apply { addAll(newValues) } diff --git a/wwm/core/resources/src/commonMain/kotlin/dev/omico/wwm/resources/WwText.kt b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/WwText.kt similarity index 66% rename from wwm/core/resources/src/commonMain/kotlin/dev/omico/wwm/resources/WwText.kt rename to wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/WwText.kt index 82ee0e1..74f978c 100644 --- a/wwm/core/resources/src/commonMain/kotlin/dev/omico/wwm/resources/WwText.kt +++ b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/WwText.kt @@ -1,7 +1,7 @@ /* * Copyright 2024 Omico. All Rights Reserved. */ -package dev.omico.wwm.resources +package dev.omico.wwm.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -11,9 +11,10 @@ import dev.omico.wwm.resources.model.game.WwMultiText @Composable fun rememberWwText(multiText: WwMultiText, name: String): String { - val text by remember(multiText, name) { + val currentMultiText = rememberUpdatedListState(multiText) + val text by remember(currentMultiText, name) { derivedStateOf { - multiText.firstOrNull { it.id == name }?.content ?: name + currentMultiText.firstOrNull { it.id == name }?.content ?: name } } return text