From 46a636ec6fe5a60101e761847c85776180c6f5b2 Mon Sep 17 00:00:00 2001 From: Alexandre G Pereira Date: Sat, 17 Aug 2024 13:27:02 -0300 Subject: [PATCH] Implement monster detail paging (#322) --- .../hunter/detail/ui/MonsterInfo.kt | 100 ++++++++++++------ .../monster/detail/MonsterDetailState.kt | 2 + .../detail/MonsterDetailStateHolder.kt | 19 +++- 3 files changed, 85 insertions(+), 36 deletions(-) diff --git a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterInfo.kt b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterInfo.kt index 16eff9db..fb4b5e08 100644 --- a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterInfo.kt +++ b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterInfo.kt @@ -121,21 +121,23 @@ private fun LazyListScope.monsterInfoPart1( } } item(key = "speed") { - MonsterRequireSectionAlphaTransition( - monsters, - pagerState, + MonsterOptionalSectionAlphaTransition( + dataList = monsters, + pagerState = pagerState, getItemsKeys = getItemsKeys, + valueToValidate = { it.speed.values.isNotEmpty() }, ) { monster -> SpeedBlock(speed = monster.speed) } } item(key = "abilityScores") { - MonsterRequireSectionAlphaTransition( - monsters, - pagerState, + ListOptionalSectionAlphaTransition( + dataList = monsters, + pagerState = pagerState, getItemsKeys = getItemsKeys, - ) { monster -> - AbilityScoreBlock(abilityScores = monster.abilityScores) + valueToValidate = { it.abilityScores } + ) { abilityScores -> + AbilityScoreBlock(abilityScores = abilityScores) } } } @@ -146,7 +148,7 @@ private fun LazyListScope.monsterInfoPart2( getItemsKeys: () -> List = { emptyList() }, ) { item(key = "savingThrows") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.savingThrows }, dataList = monsters, pagerState = pagerState, @@ -165,7 +167,7 @@ private fun LazyListScope.monsterInfoPart2( } } item(key = "skills") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.skills }, dataList = monsters, pagerState = pagerState, @@ -185,7 +187,7 @@ private fun LazyListScope.monsterInfoPart3( getItemsKeys: () -> List = { emptyList() }, ) { item(key = "damageVulnerabilities") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.damageVulnerabilities }, dataList = monsters, pagerState = pagerState, @@ -195,7 +197,7 @@ private fun LazyListScope.monsterInfoPart3( } } item(key = "damageResistances") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.damageResistances }, dataList = monsters, pagerState = pagerState, @@ -205,7 +207,7 @@ private fun LazyListScope.monsterInfoPart3( } } item(key = "damageImmunities") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.damageImmunities }, dataList = monsters, pagerState = pagerState, @@ -215,7 +217,7 @@ private fun LazyListScope.monsterInfoPart3( } } item(key = "conditionImmunities") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.conditionImmunities }, dataList = monsters, pagerState = pagerState, @@ -233,16 +235,16 @@ private fun LazyListScope.monsterInfoPart4( ) { item(key = "senses") { MonsterOptionalSectionAlphaTransition( - valueToValidate = { it.senses }, + valueToValidate = { monster -> monster.senses.all { it.isNotBlank() } }, dataList = monsters, pagerState = pagerState, getItemsKeys = getItemsKeys, ) { - SensesBlock(senses = it) + SensesBlock(senses = it.senses) } } item(key = "languages") { - MonsterOptionalSectionAlphaTransition( + StringOptionalSectionAlphaTransition( valueToValidate = { it.languages }, dataList = monsters, pagerState = pagerState, @@ -262,7 +264,7 @@ private fun LazyListScope.monsterInfoPart5( getItemsKeys: () -> List = { emptyList() }, ) { item(key = "specialAbilities") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.specialAbilities }, dataList = monsters, pagerState = pagerState, @@ -273,7 +275,7 @@ private fun LazyListScope.monsterInfoPart5( } item(key = "actions") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.actions }, dataList = monsters, pagerState = pagerState, @@ -284,7 +286,7 @@ private fun LazyListScope.monsterInfoPart5( } item(key = "reactions") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.reactions }, dataList = monsters, pagerState = pagerState, @@ -295,7 +297,7 @@ private fun LazyListScope.monsterInfoPart5( } item(key = "legendaryActions") { - MonsterOptionalSectionAlphaTransition( + ListOptionalSectionAlphaTransition( valueToValidate = { it.legendaryActions }, dataList = monsters, pagerState = pagerState, @@ -352,19 +354,55 @@ internal fun LazyItemScope.MonsterRequireSectionAlphaTransition( } @Composable -internal fun LazyItemScope.MonsterOptionalSectionAlphaTransition( - valueToValidate: (MonsterState) -> T, +internal fun LazyItemScope.MonsterOptionalSectionAlphaTransition( + valueToValidate: (MonsterState) -> Boolean, + dataList: List, + pagerState: PagerState, + modifier: Modifier = Modifier, + getItemsKeys: () -> List = { emptyList() }, + showDivider: Boolean = true, + content: @Composable ColumnScope.(MonsterState) -> Unit +) = MonsterSectionAlphaTransition( + dataList, pagerState, modifier.animateItemPlacement(), getItemsKeys +) { monster -> + OptionalBlockSection(isValid = { valueToValidate(monster) }, showDivider = showDivider) { + content(monster) + } +} + +@Composable +internal fun LazyItemScope.ListOptionalSectionAlphaTransition( + valueToValidate: (MonsterState) -> List, dataList: List, pagerState: PagerState, modifier: Modifier = Modifier, getItemsKeys: () -> List = { emptyList() }, showDivider: Boolean = true, - content: @Composable ColumnScope.(T) -> Unit + content: @Composable ColumnScope.(List) -> Unit ) = MonsterSectionAlphaTransition( dataList, pagerState, modifier.animateItemPlacement(), getItemsKeys ) { monster -> - OptionalBlockSection(valueToValidate(monster), showDivider = showDivider) { value -> - content(value) + val list = valueToValidate(monster) + OptionalBlockSection(isValid = { list.isNotEmpty() }, showDivider = showDivider) { + content(list) + } +} + +@Composable +internal fun LazyItemScope.StringOptionalSectionAlphaTransition( + valueToValidate: (MonsterState) -> String, + dataList: List, + pagerState: PagerState, + modifier: Modifier = Modifier, + getItemsKeys: () -> List = { emptyList() }, + showDivider: Boolean = true, + content: @Composable ColumnScope.(String) -> Unit +) = MonsterSectionAlphaTransition( + dataList, pagerState, modifier.animateItemPlacement(), getItemsKeys +) { monster -> + val string = valueToValidate(monster) + OptionalBlockSection(isValid = { string.isNotBlank() }, showDivider = showDivider) { + content(string) } } @@ -378,14 +416,14 @@ private fun MonsterInfoSectionColumn( ) @Composable -private fun ColumnScope.OptionalBlockSection( - value: T, +private fun ColumnScope.OptionalBlockSection( + isValid: () -> Boolean, showDivider: Boolean = true, - content: @Composable ColumnScope.(T) -> Unit, + content: @Composable ColumnScope.() -> Unit, ) { - if ((value is String && value.trim().isEmpty()) || (value is List<*> && value.isEmpty())) return + if (isValid().not()) return BlockSection(showDivider = showDivider) { - content(value) + content() } } diff --git a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt index b8389593..58afc40d 100644 --- a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt +++ b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt @@ -85,6 +85,8 @@ data class MonsterState( } } +internal fun MonsterState.isComplete(): Boolean = abilityScores.isNotEmpty() + @ObjCName(name = "StatsState", exact = true) data class StatsState( val armorClass: Int = 0, diff --git a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt index c2d84c06..2ae52f86 100644 --- a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt +++ b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt @@ -144,7 +144,8 @@ class MonsterDetailStateHolder internal constructor( private fun getMonstersByInitialIndex( monsterIndex: String, monsterIndexes: List, - invalidateCache: Boolean = false + invalidateCache: Boolean = false, + loadingSubtle: Boolean = false, ) { stateRecovery.saveMonsterIndexes(monsterIndexes) onMonsterChanged(monsterIndex, scrolled = false) @@ -154,7 +155,9 @@ class MonsterDetailStateHolder internal constructor( .toMonsterDetailState() .flowOn(dispatcher) .onStart { - setState { copy(isLoading = true) } + if (loadingSubtle.not()) { + setState { copy(isLoading = true) } + } } .catch { setState { copy(isLoading = false) } @@ -170,13 +173,19 @@ class MonsterDetailStateHolder internal constructor( fun onMonsterChanged(monsterIndex: String, scrolled: Boolean = true) { if (scrolled && monsterIndex != this.monsterIndex) { - initialMonsterListPositionIndex = state.value.monsters.indexOfFirst { - it.index == monsterIndex - }.takeIf { it >= 0 } ?: initialMonsterListPositionIndex + val monsterPositionIndex = state.value.monsters.indexOfFirst { it.index == monsterIndex } + initialMonsterListPositionIndex = monsterPositionIndex.takeIf { it >= 0 } + ?: initialMonsterListPositionIndex if (enableMonsterPageChangesEventDispatch) { analytics.trackMonsterPageChanged(monsterIndex, scrolled) monsterEventDispatcher.dispatchEvent(OnMonsterPageChanges(monsterIndex)) } + + state.value.monsters.getOrNull(monsterPositionIndex)?.let { monster -> + if (monster.isComplete().not()) { + getMonstersByInitialIndex(monsterIndex, monsterIndexes, loadingSubtle = true) + } + } } stateRecovery.saveMonsterIndex(monsterIndex) }