diff --git a/core/flow/build.gradle.kts b/core/flow/build.gradle.kts new file mode 100644 index 000000000..07c1308bd --- /dev/null +++ b/core/flow/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + kotlin("multiplatform") +} + +configureJvmTargets() + +kotlin { + sourceSets { + commonMain.dependencies { + api(libs.kotlin.coroutines.core) + } + } +} diff --git a/core/flow/src/commonMain/kotlin/br/alexandregpereira/flow/FlowWrapper.kt b/core/flow/src/commonMain/kotlin/br/alexandregpereira/flow/FlowWrapper.kt new file mode 100644 index 000000000..b5a348b96 --- /dev/null +++ b/core/flow/src/commonMain/kotlin/br/alexandregpereira/flow/FlowWrapper.kt @@ -0,0 +1,41 @@ +package br.alexandregpereira.flow + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class StateFlowWrapper( + private val origin: StateFlow, + private val coroutineScope: CoroutineScope, +) : StateFlow by origin { + + fun subscribe(block: (T) -> Unit) { + origin.onEach(block).launchIn(coroutineScope) + } +} + +class SharedFlowWrapper( + private val origin: SharedFlow, + private val coroutineScope: CoroutineScope, +) : SharedFlow by origin { + + fun subscribe(block: (T) -> Unit) { + origin.onEach(block).launchIn(coroutineScope) + } +} + +fun StateFlow.wrap( + coroutineScope: CoroutineScope +): StateFlowWrapper = StateFlowWrapper( + origin = this, + coroutineScope = coroutineScope +) + +fun SharedFlow.wrap( + coroutineScope: CoroutineScope +): SharedFlowWrapper = SharedFlowWrapper( + origin = this, + coroutineScope = coroutineScope +) diff --git a/core/state-holder/build.gradle.kts b/core/state-holder/build.gradle.kts index 337c34c61..f0233eca4 100644 --- a/core/state-holder/build.gradle.kts +++ b/core/state-holder/build.gradle.kts @@ -6,13 +6,8 @@ configureJvmTargets() kotlin { sourceSets { - val commonMain by getting { - dependencies { - implementation(libs.kotlin.coroutines.core) - } - } - if (isMac()) { - val iosMain by getting + commonMain.dependencies { + api(project(":core:flow")) } } } diff --git a/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/ActionHandler.kt b/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/ActionHandler.kt index d0d8c769c..1ab5f35ed 100644 --- a/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/ActionHandler.kt +++ b/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/ActionHandler.kt @@ -16,33 +16,44 @@ package br.alexandregpereira.hunter.state +import br.alexandregpereira.flow.SharedFlowWrapper +import br.alexandregpereira.flow.wrap +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -interface ActionHandler { +interface ActionHandler { - val action: SharedFlow + val action: SharedFlowWrapper } -interface MutableActionHandler : ActionHandler { +interface MutableActionHandler : ActionHandler { fun sendAction(action: Action) + + fun onActionHandlerClose() } -fun MutableActionHandler(): MutableActionHandler { +fun MutableActionHandler(): MutableActionHandler { return MutableActionHandlerImpl() } -private class MutableActionHandlerImpl : MutableActionHandler { +private class MutableActionHandlerImpl : MutableActionHandler { + + private val coroutineScope = MainScope() private val _action = MutableSharedFlow( extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST ) - override val action: SharedFlow = _action + override val action: SharedFlowWrapper = _action.wrap(coroutineScope) override fun sendAction(action: Action) { _action.tryEmit(action) } + + override fun onActionHandlerClose() { + coroutineScope.cancel() + } } diff --git a/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/StateHolder.kt b/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/StateHolder.kt index e95203812..3fa737faa 100644 --- a/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/StateHolder.kt +++ b/core/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/state/StateHolder.kt @@ -16,78 +16,35 @@ package br.alexandregpereira.hunter.state +import br.alexandregpereira.flow.StateFlowWrapper +import br.alexandregpereira.flow.wrap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -abstract class UiModel( +abstract class UiModel( initialState: State ) : StateHolder { - val scope = CoroutineScope( + protected val scope = CoroutineScope( SupervisorJob() + Dispatchers.Main.immediate ) private val _state = MutableStateFlow(initialState) - override val state: StateFlow = _state + override val state: StateFlowWrapper = _state.wrap(scope) protected fun setState(block: State.() -> State) { - _state.value = state.value.block() + _state.value = _state.value.block() } - fun onCleared() { + open fun onCleared() { scope.cancel() } } -interface StateHolder { +interface StateHolder { - val state: StateFlow -} - -interface MutableStateHolder: StateHolder { - - fun setState(block: State.() -> State) -} - -fun MutableStateHolder(initialState: State): MutableStateHolder { - return DefaultStateHolderImpl(initialState) -} - -fun MutableStateHolder( - stateRecovery: StateRecovery -): MutableStateHolder { - return DefaultStateHolderWithRecovery(stateRecovery) -} - -private class DefaultStateHolderImpl( - initialState: State -): MutableStateHolder { - - private val _state = MutableStateFlow(initialState) - override val state: StateFlow = _state - - override fun setState(block: State.() -> State) { - _state.value = state.value.block() - } -} - -private class DefaultStateHolderWithRecovery( - private val stateRecovery: StateRecovery -): MutableStateHolder { - - private val stateHolderDelegate = DefaultStateHolderImpl(stateRecovery.getState()) - - override val state: StateFlow = stateHolderDelegate.state - - override fun setState(block: State.() -> State) { - stateHolderDelegate.setState { - block().also { - stateRecovery.saveState(it) - } - } - } + val state: StateFlowWrapper } diff --git a/domain/monster-compendium/core/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/domain/MonsterCompendiumError.kt b/domain/monster-compendium/core/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/domain/MonsterCompendiumError.kt index 43b342b9d..fd152c815 100644 --- a/domain/monster-compendium/core/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/domain/MonsterCompendiumError.kt +++ b/domain/monster-compendium/core/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/domain/MonsterCompendiumError.kt @@ -16,6 +16,9 @@ package br.alexandregpereira.hunter.monster.compendium.domain +import kotlin.native.ObjCName + +@ObjCName(name = "MonsterCompendiumError", exact = true) sealed class MonsterCompendiumError(cause: Throwable? = null) : Throwable(cause) { class NoMonsterError(cause: Throwable? = null) : MonsterCompendiumError(cause) class UnknownError(cause: Throwable? = null) : MonsterCompendiumError(cause) diff --git a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/model/MonsterPreviewFolder.kt b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/model/MonsterPreviewFolder.kt index fbf05b762..63d221771 100644 --- a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/model/MonsterPreviewFolder.kt +++ b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/model/MonsterPreviewFolder.kt @@ -20,7 +20,7 @@ data class MonsterPreviewFolder( val index: String, val name: String = "", val type: MonsterPreviewFolderType = MonsterPreviewFolderType.ABERRATION, - val challengeRating: Float = 0f, + val challengeRating: String = "", val imageUrl: String = "", val backgroundColorLight: String, val backgroundColorDark: String, diff --git a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/MonsterFolderEntityMapper.kt b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/MonsterFolderEntityMapper.kt index 8ed012863..e6e417544 100644 --- a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/MonsterFolderEntityMapper.kt +++ b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/MonsterFolderEntityMapper.kt @@ -40,7 +40,7 @@ internal fun List.asDomainMonsterPreviewFolderEntity(): List.asDomainMonsterPreviewFolderEntity(): List { - - private val _state: MutableStateFlow = MutableStateFlow( - stateRecovery.getState() - ) - override val state: StateFlow = _state +) : UiModel(stateRecovery.getState()) { init { observeEvents() @@ -119,8 +111,4 @@ class FolderDetailStateHolder internal constructor( } .launchIn(scope) } - - private fun setState(block: FolderDetailState.() -> FolderDetailState) { - _state.value = state.value.block() - } } diff --git a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt index 4b2c395a3..2e067c825 100644 --- a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt +++ b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt @@ -23,11 +23,8 @@ import br.alexandregpereira.hunter.event.folder.insert.FolderInsertEvent.Show import br.alexandregpereira.hunter.event.folder.insert.FolderInsertResult.OnMonsterRemoved import br.alexandregpereira.hunter.event.folder.insert.FolderInsertResult.OnSaved import br.alexandregpereira.hunter.localization.AppLocalization -import br.alexandregpereira.hunter.state.ScopeManager -import br.alexandregpereira.hunter.state.StateHolder +import br.alexandregpereira.hunter.state.UiModel import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn @@ -44,10 +41,8 @@ class FolderInsertStateHolder internal constructor( private val dispatcher: CoroutineDispatcher, private val analytics: FolderInsertAnalytics, private val appLocalization: AppLocalization, -) : ScopeManager(), StateHolder { +) : UiModel(stateRecovery.getState()) { - private val _state = MutableStateFlow(stateRecovery.getState()) - override val state: StateFlow = _state private val strings: FolderInsertStrings get() = getFolderInsertStrings(appLocalization.getLanguage()) @@ -157,8 +152,4 @@ class FolderInsertStateHolder internal constructor( } }.launchIn(scope) } - - private fun setState(block: FolderInsertState.() -> FolderInsertState) { - _state.value = state.value.block() - } } diff --git a/feature/monster-compendium/android/build.gradle b/feature/monster-compendium/android/build.gradle index ba8453c52..eee3214da 100644 --- a/feature/monster-compendium/android/build.gradle +++ b/feature/monster-compendium/android/build.gradle @@ -31,6 +31,7 @@ android { } dependencies { + implementation project(':core:localization') implementation project(':feature:folder-preview:event') implementation project(':feature:monster-detail:event') implementation project(':feature:monster-registration:event') diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumFeature.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumFeature.kt index 000814294..83c746005 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumFeature.kt +++ b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumFeature.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.unit.dp -import br.alexandregpereira.hunter.monster.compendium.MonsterCompendiumViewAction.GoToCompendiumIndex +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAction.GoToCompendiumIndex import br.alexandregpereira.hunter.monster.compendium.ui.MonsterCompendiumScreen import org.koin.androidx.compose.koinViewModel diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumStateMapper.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumStateMapper.kt index 55bc34585..2226b8627 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumStateMapper.kt +++ b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumStateMapper.kt @@ -16,51 +16,51 @@ package br.alexandregpereira.hunter.monster.compendium -import br.alexandregpereira.hunter.domain.model.Monster -import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem import br.alexandregpereira.hunter.monster.compendium.domain.model.TableContentItem +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumItemState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterPreviewState import br.alexandregpereira.hunter.ui.compendium.CompendiumItemState -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemState -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemTypeState import br.alexandregpereira.hunter.ui.compendium.monster.ColorState import br.alexandregpereira.hunter.ui.compendium.monster.MonsterCardState import br.alexandregpereira.hunter.ui.compendium.monster.MonsterImageState import br.alexandregpereira.hunter.ui.compendium.monster.MonsterTypeState +import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemState +import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemTypeState -fun List.asState(): List { +internal fun List.asState(): List { return this.map { item -> when (item) { - is MonsterCompendiumItem.Title -> CompendiumItemState.Title( + is MonsterCompendiumItemState.Title -> CompendiumItemState.Title( value = item.value, id = item.id, isHeader = item.isHeader ) - is MonsterCompendiumItem.Item -> CompendiumItemState.Item( + is MonsterCompendiumItemState.Item -> CompendiumItemState.Item( value = item.monster.asState() ) } } } -private fun Monster.asState(): MonsterCardState { +private fun MonsterPreviewState.asState(): MonsterCardState { return MonsterCardState( index = index, name = name, imageState = MonsterImageState( - url = imageData.url, + url = imageUrl, type = MonsterTypeState.valueOf(type.name), challengeRating = challengeRating, backgroundColor = ColorState( - light = imageData.backgroundColor.light, - dark = imageData.backgroundColor.dark + light = backgroundColorLight, + dark = backgroundColorDark ), - isHorizontal = imageData.isHorizontal + isHorizontal = isImageHorizontal ) ) } @JvmName("asStateTableContentItem") -fun List.asState(): List { +internal fun List.asState(): List { return this.map { TableContentItemState( id = it.id, diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewAction.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewAction.kt deleted file mode 100644 index 175b3bcbc..000000000 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewAction.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2022 Alexandre Gomes Pereira - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package br.alexandregpereira.hunter.monster.compendium - -sealed class MonsterCompendiumViewAction { - data class GoToCompendiumIndex(val index: Int) : MonsterCompendiumViewAction() -} diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModel.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModel.kt index 11944ffc0..8a5a33623 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModel.kt +++ b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModel.kt @@ -16,155 +16,28 @@ package br.alexandregpereira.hunter.monster.compendium -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import br.alexandregpereira.hunter.domain.usecase.GetLastCompendiumScrollItemPositionUseCase -import br.alexandregpereira.hunter.domain.usecase.SaveCompendiumScrollItemPositionUseCase -import br.alexandregpereira.hunter.event.EventListener -import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEventDispatcher -import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEventListener -import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher -import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewResultListener -import br.alexandregpereira.hunter.monster.compendium.MonsterCompendiumViewAction.GoToCompendiumIndex -import br.alexandregpereira.hunter.monster.compendium.domain.GetMonsterCompendiumUseCase import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAction -import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAnalytics +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumIntent import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumState import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateHolder -import br.alexandregpereira.hunter.monster.compendium.ui.MonsterCompendiumErrorState.NO_INTERNET_CONNECTION -import br.alexandregpereira.hunter.monster.compendium.ui.MonsterCompendiumEvents -import br.alexandregpereira.hunter.monster.registration.event.MonsterRegistrationEventListener -import br.alexandregpereira.hunter.sync.event.SyncEventDispatcher -import br.alexandregpereira.hunter.sync.event.SyncEventListener -import br.alexandregpereira.hunter.ui.compose.LoadingScreenState -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import br.alexandregpereira.hunter.state.ActionHandler +import br.alexandregpereira.hunter.state.StateHolder internal class MonsterCompendiumViewModel( - private val savedStateHandle: SavedStateHandle, - getMonsterCompendiumUseCase: GetMonsterCompendiumUseCase, - getLastCompendiumScrollItemPositionUseCase: GetLastCompendiumScrollItemPositionUseCase, - saveCompendiumScrollItemPositionUseCase: SaveCompendiumScrollItemPositionUseCase, - folderPreviewEventDispatcher: FolderPreviewEventDispatcher, - folderPreviewResultListener: FolderPreviewResultListener, - monsterDetailEventDispatcher: MonsterDetailEventDispatcher, - monsterDetailEventListener: MonsterDetailEventListener, - syncEventListener: SyncEventListener, - syncEventDispatcher: SyncEventDispatcher, - monsterRegistrationEventListener: MonsterRegistrationEventListener, - analytics: MonsterCompendiumAnalytics, - dispatcher: CoroutineDispatcher, - loadOnInit: Boolean = true, -) : ViewModel(), MonsterCompendiumEvents { - - private val stateHolder: MonsterCompendiumStateHolder = MonsterCompendiumStateHolder( - getMonsterCompendiumUseCase, - getLastCompendiumScrollItemPositionUseCase, - saveCompendiumScrollItemPositionUseCase, - folderPreviewEventDispatcher, - folderPreviewResultListener, - monsterDetailEventDispatcher, - monsterDetailEventListener, - syncEventListener, - syncEventDispatcher, - monsterRegistrationEventListener, - dispatcher, - loadOnInit = loadOnInit, - initialState = savedStateHandle.getState().asMonsterCompendiumState(), - analytics = analytics, - ) + private val stateHolder: MonsterCompendiumStateHolder, +) : ViewModel(), + StateHolder by stateHolder, + ActionHandler by stateHolder, + MonsterCompendiumIntent by stateHolder { val initialScrollItemPosition: Int get() = stateHolder.initialScrollItemPosition - private val _state = MutableStateFlow(stateHolder.state.value.asMonsterCompendiumViewState()) - val state: StateFlow = _state - - val action: Flow = stateHolder.action.map { - when (it) { - is MonsterCompendiumAction.GoToCompendiumIndex -> GoToCompendiumIndex(it.index) - } - } - - init { - stateHolder.state - .onEach { - _state.value = it.asMonsterCompendiumViewState().saveState(savedStateHandle) - } - .launchIn(viewModelScope) - } - fun loadMonsters() = stateHolder.loadMonsters() - override fun onItemCLick(index: String) { - stateHolder.onItemClick(index) - } - - override fun onItemLongCLick(index: String) { - stateHolder.onItemLongCLick(index) - } - - override fun onFirstVisibleItemChange(position: Int) { - stateHolder.onFirstVisibleItemChange(position) - } - - override fun onPopupOpened() { - stateHolder.onPopupOpened() - } - - override fun onPopupClosed() { - stateHolder.onPopupClosed() - } - - override fun onAlphabetIndexClicked(position: Int) { - stateHolder.onAlphabetIndexClicked(position) - } - - override fun onTableContentIndexClicked(position: Int) { - stateHolder.onTableContentIndexClicked(position) - } - - override fun onTableContentClosed() { - stateHolder.onTableContentClosed() - } - - override fun onErrorButtonClick() { - stateHolder.onErrorButtonClick() - } - override fun onCleared() { super.onCleared() stateHolder.onCleared() } - - private fun MonsterCompendiumState.asMonsterCompendiumViewState(): MonsterCompendiumViewState { - return MonsterCompendiumViewState( - loadingState = when { - this.errorState != null -> LoadingScreenState.Error(NO_INTERNET_CONNECTION) - this.isLoading -> LoadingScreenState.LoadingScreen - else -> LoadingScreenState.Success - }, - items = this.items.asState(), - alphabet = this.alphabet, - alphabetSelectedIndex = this.alphabetSelectedIndex, - popupOpened = this.popupOpened, - tableContent = this.tableContent.asState(), - tableContentIndex = this.tableContentIndex, - tableContentInitialIndex = this.tableContentInitialIndex, - tableContentOpened = this.tableContentOpened, - isShowingMonsterFolderPreview = this.isShowingMonsterFolderPreview, - ) - } - - private fun MonsterCompendiumViewState.asMonsterCompendiumState(): MonsterCompendiumState { - return MonsterCompendiumState( - isShowingMonsterFolderPreview = this.isShowingMonsterFolderPreview, - ) - } } diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewState.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewState.kt index cb19d9c39..2c272c620 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewState.kt +++ b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewState.kt @@ -17,33 +17,17 @@ package br.alexandregpereira.hunter.monster.compendium import androidx.lifecycle.SavedStateHandle -import br.alexandregpereira.hunter.ui.compendium.CompendiumItemState -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemState -import br.alexandregpereira.hunter.ui.compose.LoadingScreenState -import br.alexandregpereira.hunter.ui.compose.LoadingScreenState.LoadingScreen +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumState -data class MonsterCompendiumViewState( - val loadingState: LoadingScreenState = LoadingScreen, - val items: List = emptyList(), - val alphabet: List = emptyList(), - val alphabetSelectedIndex: Int = -1, - val popupOpened: Boolean = false, - val tableContent: List = emptyList(), - val tableContentIndex: Int = -1, - val tableContentInitialIndex: Int = 0, - val tableContentOpened: Boolean = false, - val isShowingMonsterFolderPreview: Boolean = false, -) - -internal fun SavedStateHandle.getState(): MonsterCompendiumViewState { - return MonsterCompendiumViewState( +internal fun SavedStateHandle.getState(): MonsterCompendiumState { + return MonsterCompendiumState( isShowingMonsterFolderPreview = this["isShowingMonsterFolderPreview"] ?: false ) } -internal fun MonsterCompendiumViewState.saveState( +internal fun MonsterCompendiumState.saveState( savedStateHandle: SavedStateHandle -): MonsterCompendiumViewState { +): MonsterCompendiumState { savedStateHandle["isShowingMonsterFolderPreview"] = this.isShowingMonsterFolderPreview return this } diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/di/Module.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/di/Module.kt index 581c55b40..17228478f 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/di/Module.kt +++ b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/di/Module.kt @@ -16,27 +16,36 @@ package br.alexandregpereira.hunter.monster.compendium.di +import androidx.lifecycle.SavedStateHandle import br.alexandregpereira.hunter.monster.compendium.MonsterCompendiumViewModel -import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAnalytics +import br.alexandregpereira.hunter.monster.compendium.getState +import br.alexandregpereira.hunter.monster.compendium.saveState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateRecovery +import br.alexandregpereira.hunter.monster.compendium.state.di.monsterCompendiumStateModule import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module -val monsterCompendiumModule = module { +val monsterCompendiumModule = monsterCompendiumStateModule.apply { + factory { + AndroidMonsterCompendiumStateRecovery( + savedStateHandle = get(), + ) + } viewModel { MonsterCompendiumViewModel( - savedStateHandle = get(), - getMonsterCompendiumUseCase = get(), - getLastCompendiumScrollItemPositionUseCase = get(), - saveCompendiumScrollItemPositionUseCase = get(), - folderPreviewEventDispatcher = get(), - folderPreviewResultListener = get(), - monsterDetailEventDispatcher = get(), - monsterDetailEventListener = get(), - syncEventListener = get(), - syncEventDispatcher = get(), - monsterRegistrationEventListener = get(), - analytics = MonsterCompendiumAnalytics(get()), - dispatcher = get() + stateHolder = get(), ) } } + +private class AndroidMonsterCompendiumStateRecovery( + private val savedStateHandle: SavedStateHandle, +) : MonsterCompendiumStateRecovery { + + override val state: MonsterCompendiumState + get() = savedStateHandle.getState() + + override fun saveState(state: MonsterCompendiumState) { + state.saveState(savedStateHandle) + } +} diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumErrorState.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumErrorState.kt deleted file mode 100644 index 0588fe4a1..000000000 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumErrorState.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 Alexandre Gomes Pereira - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package br.alexandregpereira.hunter.monster.compendium.ui - -import br.alexandregpereira.hunter.monster.compendium.R - -enum class MonsterCompendiumErrorState( - val titleRes: Int, - val buttonTextRes: Int -) { - NO_INTERNET_CONNECTION( - titleRes = R.string.monster_compendium_error_no_internet_connection, - buttonTextRes = R.string.monster_compendium_error_try_again - ) -} diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumScreen.kt b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumScreen.kt index d6cf7d320..81d73f954 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumScreen.kt +++ b/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumScreen.kt @@ -28,33 +28,38 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import br.alexandregpereira.hunter.monster.compendium.MonsterCompendiumViewState -import br.alexandregpereira.hunter.ui.compendium.CompendiumItemState -import br.alexandregpereira.hunter.ui.compose.PopupContainer -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemState -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentPopup +import br.alexandregpereira.hunter.monster.compendium.asState +import br.alexandregpereira.hunter.monster.compendium.domain.MonsterCompendiumError +import br.alexandregpereira.hunter.monster.compendium.domain.model.TableContentItem +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumIntent +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumItemState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumState import br.alexandregpereira.hunter.ui.compose.EmptyScreenMessage import br.alexandregpereira.hunter.ui.compose.LoadingScreen +import br.alexandregpereira.hunter.ui.compose.LoadingScreenState +import br.alexandregpereira.hunter.ui.compose.PopupContainer import br.alexandregpereira.hunter.ui.compose.Window +import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentPopup @Composable internal fun MonsterCompendiumScreen( - state: MonsterCompendiumViewState, + state: MonsterCompendiumState, initialScrollItemPosition: Int, compendiumIndex: Int = -1, contentPadding: PaddingValues = PaddingValues(0.dp), - events: MonsterCompendiumEvents, + events: MonsterCompendiumIntent, ) = Window(Modifier.fillMaxSize()) { - LoadingScreen( - state = state.loadingState, - errorContent = { errorState -> + LoadingScreen( + state = when { + state.errorState != null -> LoadingScreenState.Error(state.errorState) + state.isLoading -> LoadingScreenState.LoadingScreen + else -> LoadingScreenState.Success + }, + errorContent = { EmptyScreenMessage( - title = stringResource(errorState.titleRes), - buttonText = stringResource( - errorState.buttonTextRes - ), + title = state.strings.noInternetConnection, + buttonText = state.strings.tryAgain, onButtonClick = events::onErrorButtonClick ) } @@ -89,29 +94,29 @@ internal fun MonsterCompendiumScreen( @Composable private fun MonsterCompendiumScreen( - items: List, + items: List, isShowingMonsterFolderPreview: Boolean, popupOpened: Boolean, alphabet: List, alphabetSelectedIndex: Int, - tableContent: List, + tableContent: List, tableContentIndex: Int, tableContentInitialIndex: Int, tableContentOpened: Boolean, listState: LazyGridState, contentPadding: PaddingValues = PaddingValues(0.dp), - events: MonsterCompendiumEvents, + events: MonsterCompendiumIntent, ) { PopupContainer( isOpened = popupOpened, onPopupClosed = events::onPopupClosed, content = { MonsterCompendium( - items = items, + items = remember(items) { items.asState() }, listState = listState, contentPadding = contentPadding, - onItemCLick = events::onItemCLick, - onItemLongCLick = events::onItemLongCLick, + onItemCLick = events::onItemClick, + onItemLongCLick = events::onItemLongClick, ) }, popupContent = { @@ -121,7 +126,7 @@ private fun MonsterCompendiumScreen( TableContentPopup( alphabet = alphabet, - tableContent = tableContent, + tableContent = remember(tableContent) { tableContent.asState() }, alphabetSelectedIndex = alphabetSelectedIndex, tableContentSelectedIndex = tableContentIndex, tableContentInitialIndex = tableContentInitialIndex, diff --git a/feature/monster-compendium/android/src/main/res/values-pt-rBR/strings.xml b/feature/monster-compendium/android/src/main/res/values-pt-rBR/strings.xml deleted file mode 100644 index 459af3497..000000000 --- a/feature/monster-compendium/android/src/main/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - Sem Conexão com a Internet - Tente Novamente - diff --git a/feature/monster-compendium/android/src/main/res/values/strings.xml b/feature/monster-compendium/android/src/main/res/values/strings.xml deleted file mode 100644 index fa7a87712..000000000 --- a/feature/monster-compendium/android/src/main/res/values/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - No Internet Connection - Try Again - diff --git a/feature/monster-compendium/android/src/test/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModelTest.kt b/feature/monster-compendium/android/src/test/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModelTest.kt index e52c45326..8b3eb3fef 100644 --- a/feature/monster-compendium/android/src/test/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModelTest.kt +++ b/feature/monster-compendium/android/src/test/kotlin/br/alexandregpereira/hunter/monster/compendium/MonsterCompendiumViewModelTest.kt @@ -18,31 +18,31 @@ package br.alexandregpereira.hunter.monster.compendium import androidx.lifecycle.SavedStateHandle import br.alexandregpereira.hunter.domain.model.Monster +import br.alexandregpereira.hunter.domain.model.MonsterType import br.alexandregpereira.hunter.domain.usecase.GetLastCompendiumScrollItemPositionUseCase import br.alexandregpereira.hunter.domain.usecase.SaveCompendiumScrollItemPositionUseCase import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEventDispatcher import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEventListener import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewResultListener -import br.alexandregpereira.hunter.monster.compendium.MonsterCompendiumViewAction.GoToCompendiumIndex +import br.alexandregpereira.hunter.localization.AppLocalization +import br.alexandregpereira.hunter.localization.Language import br.alexandregpereira.hunter.monster.compendium.domain.GetMonsterCompendiumUseCase import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendium import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem.Item import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem.Title import br.alexandregpereira.hunter.monster.compendium.domain.model.TableContentItem import br.alexandregpereira.hunter.monster.compendium.domain.model.TableContentItemType +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAction +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAction.GoToCompendiumIndex +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumItemState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateHolder +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateRecovery +import br.alexandregpereira.hunter.monster.compendium.state.MonsterPreviewState import br.alexandregpereira.hunter.monster.registration.event.MonsterRegistrationEventListener import br.alexandregpereira.hunter.sync.event.SyncEventDispatcher import br.alexandregpereira.hunter.sync.event.SyncEventListener -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemState -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemTypeState.BODY -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemTypeState.HEADER1 -import br.alexandregpereira.hunter.ui.compose.tablecontent.TableContentItemTypeState.HEADER2 -import br.alexandregpereira.hunter.ui.compendium.monster.ColorState -import br.alexandregpereira.hunter.ui.compendium.monster.MonsterCardState -import br.alexandregpereira.hunter.ui.compendium.monster.MonsterImageState -import br.alexandregpereira.hunter.ui.compendium.monster.MonsterTypeState -import br.alexandregpereira.hunter.ui.compose.LoadingScreenState.Success import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -56,8 +56,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals -import br.alexandregpereira.hunter.ui.compendium.CompendiumItemState.Item as ItemState -import br.alexandregpereira.hunter.ui.compendium.CompendiumItemState.Title as TitleState @ExperimentalCoroutinesApi class MonsterCompendiumViewModelTest { @@ -94,10 +92,12 @@ class MonsterCompendiumViewModelTest { Monster( index = "zariel1", name = "Zariel", + challengeRating = 0.5f, ).asItem(), Monster( index = "zariel2", name = "Zariel", + challengeRating = 1f, ).asItem() ), tableContent = listOf( @@ -117,7 +117,7 @@ class MonsterCompendiumViewModelTest { every { savedStateHandle.get(any()) } returns null createViewModel() - val results = mutableListOf() + val results = mutableListOf() val job = launch { viewModel.state.toList(results) } @@ -132,43 +132,47 @@ class MonsterCompendiumViewModelTest { assertEquals(expected = 2, actual = results.size) assertEquals(expected = 4, actual = viewModel.initialScrollItemPosition) - assertEquals(expected = MonsterCompendiumViewState(), actual = results[0]) + assertEquals(expected = MonsterCompendiumState(), actual = results[0]) assertEquals( actual = results[1], - expected = MonsterCompendiumViewState().copy( - loadingState = Success, + expected = MonsterCompendiumState().copy( + isLoading = false, items = listOf( - TitleState(value = "Any", id = "da", isHeader = true), - TitleState(value = "Any", id = "da2", isHeader = false), - TitleState(value = "Any3", id = "da3", isHeader = false), - MonsterCardState( - index = "zariel1", - name = "Zariel", - imageState = MonsterImageState( - url = "", - type = MonsterTypeState.ABERRATION, - challengeRating = 0.0f, - backgroundColor = ColorState(light = "", dark = ""), - ), - ).asItemState(), - MonsterCardState( - index = "zariel2", - name = "Zariel", - imageState = MonsterImageState( - url = "", - type = MonsterTypeState.ABERRATION, - challengeRating = 0.0f, - backgroundColor = ColorState(light = "", dark = ""), - ), - ).asItemState(), + MonsterCompendiumItemState.Title(value = "Any", id = "da", isHeader = true), + MonsterCompendiumItemState.Title(value = "Any", id = "da2", isHeader = false), + MonsterCompendiumItemState.Title(value = "Any3", id = "da3", isHeader = false), + MonsterCompendiumItemState.Item( + monster = MonsterPreviewState( + index = "zariel1", + name = "Zariel", + imageUrl = "", + type = MonsterType.ABERRATION, + challengeRating = "1/2", + backgroundColorLight = "", + backgroundColorDark = "", + isImageHorizontal = false, + ) + ), + MonsterCompendiumItemState.Item( + monster = MonsterPreviewState( + index = "zariel2", + name = "Zariel", + imageUrl = "", + type = MonsterType.ABERRATION, + challengeRating = "1", + backgroundColorLight = "", + backgroundColorDark = "", + isImageHorizontal = false, + ) + ), ), alphabet = listOf("A", "Z"), alphabetSelectedIndex = 0, tableContent = listOf( - TableContentItemState(text = "Any", type = HEADER1), - TableContentItemState(text = "Any", type = HEADER2), - TableContentItemState(text = "Zariel", type = BODY, id = "zariel1"), - TableContentItemState(text = "Zariel", type = BODY, id = "zariel2"), + TableContentItem(text = "Any", type = TableContentItemType.HEADER1), + TableContentItem(text = "Any", type = TableContentItemType.HEADER2), + TableContentItem(text = "Zariel", type = TableContentItemType.BODY, id = "zariel1"), + TableContentItem(text = "Zariel", type = TableContentItemType.BODY, id = "zariel2"), ), tableContentIndex = 3 ) @@ -212,12 +216,12 @@ class MonsterCompendiumViewModelTest { createViewModel() - val results = mutableListOf() + val results = mutableListOf() val job = launch { viewModel.state.toList(results) } - val actions = mutableListOf() + val actions = mutableListOf() val actionJob = launch { viewModel.action.toList(actions) } @@ -286,7 +290,7 @@ class MonsterCompendiumViewModelTest { createViewModel() - val results = mutableListOf() + val results = mutableListOf() val job = launch { viewModel.state.toList(results) } @@ -352,7 +356,7 @@ class MonsterCompendiumViewModelTest { createViewModel() - val results = mutableListOf() + val results = mutableListOf() val job = launch { viewModel.state.toList(results) } @@ -382,23 +386,27 @@ class MonsterCompendiumViewModelTest { private fun createViewModel() { viewModel = MonsterCompendiumViewModel( - savedStateHandle = savedStateHandle, - getMonsterCompendiumUseCase = getMonsterCompendiumUseCase, - getLastCompendiumScrollItemPositionUseCase = getLastScrollPositionUseCase, - saveCompendiumScrollItemPositionUseCase = saveScrollPositionUseCase, - folderPreviewEventDispatcher = folderPreviewEventDispatcher, - folderPreviewResultListener = folderPreviewResultListener, - monsterDetailEventDispatcher = monsterDetailEventDispatcher, - monsterDetailEventListener = monsterDetailEventListener, - syncEventDispatcher = syncEventDispatcher, - syncEventListener = syncEventListener, - monsterRegistrationEventListener = monsterRegistrationEventListener, - loadOnInit = false, - dispatcher = testCoroutineRule.testCoroutineDispatcher, - analytics = mockk(relaxed = true) + stateHolder = MonsterCompendiumStateHolder( + getMonsterCompendiumUseCase = getMonsterCompendiumUseCase, + getLastCompendiumScrollItemPositionUseCase = getLastScrollPositionUseCase, + saveCompendiumScrollItemPositionUseCase = saveScrollPositionUseCase, + folderPreviewEventDispatcher = folderPreviewEventDispatcher, + folderPreviewResultListener = folderPreviewResultListener, + monsterDetailEventDispatcher = monsterDetailEventDispatcher, + monsterDetailEventListener = monsterDetailEventListener, + syncEventDispatcher = syncEventDispatcher, + syncEventListener = syncEventListener, + monsterRegistrationEventListener = monsterRegistrationEventListener, + loadOnInit = false, + dispatcher = testCoroutineRule.testCoroutineDispatcher, + analytics = mockk(relaxed = true), + appLocalization = object : AppLocalization { + override fun getLanguage(): Language = Language.ENGLISH + }, + stateRecovery = MonsterCompendiumStateRecovery() + ) ) } private fun Monster.asItem(): Item = Item(this) - private fun MonsterCardState.asItemState(): ItemState = ItemState(this) } diff --git a/feature/monster-compendium/state-holder/build.gradle.kts b/feature/monster-compendium/state-holder/build.gradle.kts index d6d12feba..a31283c6d 100644 --- a/feature/monster-compendium/state-holder/build.gradle.kts +++ b/feature/monster-compendium/state-holder/build.gradle.kts @@ -6,22 +6,18 @@ configureJvmTargets() kotlin { sourceSets { - val commonMain by getting { - dependencies { - api(project(":core:analytics")) - api(project(":core:event")) - api(project(":core:state-holder")) - api(project(":domain:monster-compendium:core")) - implementation(project(":feature:folder-preview:event")) - implementation(project(":feature:monster-detail:event")) - implementation(project(":feature:sync:event")) - implementation(project(":feature:monster-registration:event")) - implementation(libs.kotlin.coroutines.core) - implementation(libs.koin.core) - } - } - if (isMac()) { - val iosMain by getting + commonMain.dependencies { + implementation(project(":core:analytics")) + implementation(project(":core:event")) + implementation(project(":core:localization")) + api(project(":core:state-holder")) + api(project(":domain:monster-compendium:core")) + implementation(project(":feature:folder-preview:event")) + implementation(project(":feature:monster-detail:event")) + implementation(project(":feature:sync:event")) + implementation(project(":feature:monster-registration:event")) + implementation(libs.kotlin.coroutines.core) + implementation(libs.koin.core) } } } diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumAction.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumAction.kt index 9bce12ffb..348bfb747 100644 --- a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumAction.kt +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumAction.kt @@ -16,6 +16,9 @@ package br.alexandregpereira.hunter.monster.compendium.state +import kotlin.native.ObjCName + +@ObjCName(name = "MonsterCompendiumAction", exact = true) sealed class MonsterCompendiumAction { data class GoToCompendiumIndex(val index: Int) : MonsterCompendiumAction() } diff --git a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumEvents.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumIntent.kt similarity index 83% rename from feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumEvents.kt rename to feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumIntent.kt index 61b97890c..d65b5b740 100644 --- a/feature/monster-compendium/android/src/main/kotlin/br/alexandregpereira/hunter/monster/compendium/ui/MonsterCompendiumEvents.kt +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumIntent.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package br.alexandregpereira.hunter.monster.compendium.ui +package br.alexandregpereira.hunter.monster.compendium.state -interface MonsterCompendiumEvents { +interface MonsterCompendiumIntent { fun onFirstVisibleItemChange(position: Int) - fun onItemCLick(index: String) - fun onItemLongCLick(index: String) + fun onItemClick(index: String) + fun onItemLongClick(index: String) fun onAlphabetIndexClicked(position: Int) fun onPopupClosed() fun onPopupOpened() diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumState.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumState.kt index 69ed1a18d..97beaa937 100644 --- a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumState.kt +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumState.kt @@ -16,13 +16,15 @@ package br.alexandregpereira.hunter.monster.compendium.state +import br.alexandregpereira.hunter.domain.model.MonsterType import br.alexandregpereira.hunter.monster.compendium.domain.MonsterCompendiumError -import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem import br.alexandregpereira.hunter.monster.compendium.domain.model.TableContentItem +import kotlin.native.ObjCName +@ObjCName(name = "MonsterCompendiumState", exact = true) data class MonsterCompendiumState( val isLoading: Boolean = true, - val items: List = emptyList(), + val items: List = emptyList(), val alphabet: List = emptyList(), val alphabetSelectedIndex: Int = -1, val popupOpened: Boolean = false, @@ -31,7 +33,52 @@ data class MonsterCompendiumState( val tableContentInitialIndex: Int = 0, val tableContentOpened: Boolean = false, val isShowingMonsterFolderPreview: Boolean = false, - val errorState: MonsterCompendiumError? = null + val errorState: MonsterCompendiumError? = null, + val strings: MonsterCompendiumStrings = MonsterCompendiumStrings(), +) { + + companion object { + val Empty = MonsterCompendiumState() + } +} + +@ObjCName(name = "MonsterCompendiumItemState", exact = true) +sealed class MonsterCompendiumItemState { + + val key: String + get() = when (this) { + is Title -> id + is Item -> monster.index + } + + data class Title( + val id: String, + val value: String, + val isHeader: Boolean + ) : MonsterCompendiumItemState() + + data class Item( + val monster: MonsterPreviewState + ) : MonsterCompendiumItemState() + + fun isHorizontal(): Boolean { + return when (this) { + is Title -> true + is Item -> monster.isImageHorizontal + } + } +} + +@ObjCName(name = "MonsterPreviewState", exact = true) +data class MonsterPreviewState( + val index: String = "", + val name: String = "", + val type: MonsterType = MonsterType.ABERRATION, + val challengeRating: String = "", + val imageUrl: String = "", + val backgroundColorLight: String = "", + val backgroundColorDark: String = "", + val isImageHorizontal: Boolean = false, ) fun MonsterCompendiumState.loading(isLoading: Boolean): MonsterCompendiumState { @@ -39,7 +86,7 @@ fun MonsterCompendiumState.loading(isLoading: Boolean): MonsterCompendiumState { } fun MonsterCompendiumState.complete( - items: List, + items: List, alphabet: List, tableContent: List, alphabetSelectedIndex: Int, diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateHolder.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateHolder.kt index 077b2674a..8be9d3f8d 100644 --- a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateHolder.kt +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateHolder.kt @@ -28,6 +28,7 @@ import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewResult import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewResultListener +import br.alexandregpereira.hunter.localization.AppLocalization import br.alexandregpereira.hunter.monster.compendium.domain.GetMonsterCompendiumUseCase import br.alexandregpereira.hunter.monster.compendium.domain.getAlphabetIndexFromCompendiumItemIndex import br.alexandregpereira.hunter.monster.compendium.domain.getCompendiumIndexFromTableContentIndex @@ -36,12 +37,10 @@ import br.alexandregpereira.hunter.monster.compendium.domain.getTableContentInde import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAction.GoToCompendiumIndex import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumException.NavigateToCompendiumIndexError -import br.alexandregpereira.hunter.monster.registration.event.MonsterRegistrationEvent import br.alexandregpereira.hunter.monster.registration.event.MonsterRegistrationResult import br.alexandregpereira.hunter.monster.registration.event.collectOnSaved import br.alexandregpereira.hunter.state.MutableActionHandler -import br.alexandregpereira.hunter.state.MutableStateHolder -import br.alexandregpereira.hunter.state.ScopeManager +import br.alexandregpereira.hunter.state.UiModel import br.alexandregpereira.hunter.sync.event.SyncEventDispatcher import br.alexandregpereira.hunter.sync.event.SyncEventListener import br.alexandregpereira.hunter.sync.event.collectSyncFinishedEvents @@ -55,7 +54,9 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.zip import kotlinx.coroutines.launch +import kotlin.native.ObjCName +@ObjCName(name = "MonsterCompendiumStateHolder", exact = true) class MonsterCompendiumStateHolder( private val getMonsterCompendiumUseCase: GetMonsterCompendiumUseCase, private val getLastCompendiumScrollItemPositionUseCase: GetLastCompendiumScrollItemPositionUseCase, @@ -69,14 +70,16 @@ class MonsterCompendiumStateHolder( private val monsterRegistrationEventListener: EventListener, private val dispatcher: CoroutineDispatcher, private val analytics: MonsterCompendiumAnalytics, - initialState: MonsterCompendiumState = MonsterCompendiumState(), + private val stateRecovery: MonsterCompendiumStateRecovery, + appLocalization: AppLocalization, loadOnInit: Boolean = true, -) : ScopeManager(), - MutableStateHolder by MutableStateHolder(initialState), - MutableActionHandler by MutableActionHandler() { +) : UiModel(stateRecovery.state.copy(strings = appLocalization.getStrings())), + MutableActionHandler by MutableActionHandler(), + MonsterCompendiumIntent { var initialScrollItemPosition: Int = 0 private set + private var metadata: List = emptyList() init { observeEvents() @@ -94,10 +97,11 @@ class MonsterCompendiumStateHolder( ) { compendium, scrollItemPosition -> analytics.trackMonsterCompendium(compendium, scrollItemPosition) val items = compendium.items + metadata = items val alphabet = compendium.alphabet val tableContent = compendium.tableContent state.value.complete( - items = items, + items = items.asState(), alphabet = alphabet, tableContent = tableContent, tableContentIndex = tableContent.getTableContentIndexFromCompendiumItemIndex( @@ -121,28 +125,28 @@ class MonsterCompendiumStateHolder( } .collect { (state, scrollItemPosition) -> initialScrollItemPosition = scrollItemPosition - setState { state } + setState { state.saveState(stateRecovery) } } } - fun onItemClick(index: String) { + override fun onItemClick(index: String) { analytics.trackItemClick(index) monsterDetailEventDispatcher.dispatchEvent( Show(index, enableMonsterPageChangesEventDispatch = true) ) } - fun onItemLongCLick(index: String) { + override fun onItemLongClick(index: String) { analytics.trackItemLongClick(index) folderPreviewEventDispatcher.dispatchEvent(FolderPreviewEvent.AddMonster(index)) folderPreviewEventDispatcher.dispatchEvent(FolderPreviewEvent.ShowFolderPreview) } - fun onFirstVisibleItemChange(position: Int) { + override fun onFirstVisibleItemChange(position: Int) { saveCompendiumScrollItemPosition(position) } - fun onPopupOpened() { + override fun onPopupOpened() { analytics.trackPopupOpened() setState { popupOpened(popupOpened = true) @@ -150,40 +154,39 @@ class MonsterCompendiumStateHolder( } } - fun onPopupClosed() { + override fun onPopupClosed() { analytics.trackPopupClosed() setState { popupOpened(popupOpened = false) } } - fun onAlphabetIndexClicked(position: Int) { + override fun onAlphabetIndexClicked(position: Int) { analytics.trackAlphabetIndexClicked(position) navigateToTableContentFromAlphabetIndex(position) } - fun onTableContentIndexClicked(position: Int) { + override fun onTableContentIndexClicked(position: Int) { analytics.trackTableContentIndexClicked(position) navigateToCompendiumIndexFromTableContentIndex(position) } - fun onTableContentClosed() { + override fun onTableContentClosed() { analytics.trackTableContentClosed() setState { tableContentOpened(false) } } - fun onErrorButtonClick() { + override fun onErrorButtonClick() { analytics.trackErrorButtonClick() syncEventDispatcher.startSync() } private fun saveCompendiumScrollItemPosition(position: Int) { - val items = state.value.items val alphabet = state.value.alphabet val tableContent = state.value.tableContent scope.launch { saveCompendiumScrollItemPositionUseCase(position) .map { - tableContent.getTableContentIndexFromCompendiumItemIndex(position, items) to - alphabet.getAlphabetIndexFromCompendiumItemIndex(position, items) + tableContent.getTableContentIndexFromCompendiumItemIndex(position, metadata) to + alphabet.getAlphabetIndexFromCompendiumItemIndex(position, metadata) } .flowOn(dispatcher) .collect { (tableContentIndex, alphabetLetter) -> @@ -194,7 +197,7 @@ class MonsterCompendiumStateHolder( } private fun navigateToCompendiumIndexFromMonsterIndex(monsterIndex: String) { - flowOf(state.value.items) + flowOf(metadata) .map { items -> items.compendiumIndexOf(monsterIndex) }.onEach { compendiumIndex -> @@ -207,7 +210,7 @@ class MonsterCompendiumStateHolder( .catch { error -> if (error is NavigateToCompendiumIndexError) { fetchMonsterCompendium() - state.value.items.compendiumIndexOf(monsterIndex).takeIf { it >= 0 }?.let { + metadata.compendiumIndexOf(monsterIndex).takeIf { it >= 0 }?.let { sendAction(GoToCompendiumIndex(it)) } } else { @@ -255,7 +258,7 @@ class MonsterCompendiumStateHolder( } private fun showMonsterFolderPreview(isShowing: Boolean) { - setState { showMonsterFolderPreview(isShowing) } + setState { showMonsterFolderPreview(isShowing).saveState(stateRecovery) } } private fun navigateToTableContentFromAlphabetIndex(alphabetIndex: Int) { @@ -283,7 +286,7 @@ class MonsterCompendiumStateHolder( setState { popupOpened(popupOpened = false) } val tableContent = state.value.tableContent scope.launch { - flowOf(state.value.items) + flowOf(metadata) .getCompendiumIndexFromTableContentIndex(tableContent, tableContentIndex) .flowOn(dispatcher) .collect { compendiumIndex -> @@ -291,4 +294,9 @@ class MonsterCompendiumStateHolder( } } } + + override fun onCleared() { + super.onCleared() + onActionHandlerClose() + } } diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateMapper.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateMapper.kt new file mode 100644 index 000000000..f776f8a3c --- /dev/null +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateMapper.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Alexandre Gomes Pereira + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package br.alexandregpereira.hunter.monster.compendium.state + +import br.alexandregpereira.hunter.domain.model.Monster +import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem + +internal fun List.asState(): List { + return this.map { item -> + when (item) { + is MonsterCompendiumItem.Title -> MonsterCompendiumItemState.Title( + value = item.value, + id = item.id, + isHeader = item.isHeader + ) + is MonsterCompendiumItem.Item -> MonsterCompendiumItemState.Item( + monster = item.monster.asState() + ) + } + } +} + +private fun Monster.asState(): MonsterPreviewState { + return MonsterPreviewState( + index = index, + name = name, + type = type, + challengeRating = challengeRatingFormatted, + imageUrl = imageData.url, + backgroundColorLight = imageData.backgroundColor.light, + backgroundColorDark = imageData.backgroundColor.dark, + isImageHorizontal = imageData.isHorizontal + ) +} diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateRecovery.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateRecovery.kt new file mode 100644 index 000000000..c9d6410c5 --- /dev/null +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStateRecovery.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Alexandre Gomes Pereira + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package br.alexandregpereira.hunter.monster.compendium.state + +interface MonsterCompendiumStateRecovery { + val state: MonsterCompendiumState + + fun saveState(state: MonsterCompendiumState) +} + +fun MonsterCompendiumStateRecovery(): MonsterCompendiumStateRecovery = DefaultMonsterCompendiumStateRecovery() + +internal fun MonsterCompendiumState.saveState( + recovery: MonsterCompendiumStateRecovery +): MonsterCompendiumState { + recovery.saveState(this) + return this +} + +private class DefaultMonsterCompendiumStateRecovery : MonsterCompendiumStateRecovery { + + override val state: MonsterCompendiumState = MonsterCompendiumState.Empty + + override fun saveState(state: MonsterCompendiumState) {} +} diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStrings.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStrings.kt new file mode 100644 index 000000000..e6ca0dc54 --- /dev/null +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/MonsterCompendiumStrings.kt @@ -0,0 +1,28 @@ +package br.alexandregpereira.hunter.monster.compendium.state + +import br.alexandregpereira.hunter.localization.AppLocalization +import br.alexandregpereira.hunter.localization.Language + +interface MonsterCompendiumStrings { + val noInternetConnection: String + val tryAgain: String +} + +internal data class MonsterCompendiumEnStrings( + override val noInternetConnection: String = "No internet connection", + override val tryAgain: String = "Try again" +) : MonsterCompendiumStrings + +internal data class MonsterCompendiumPtrStrings( + override val noInternetConnection: String = "Sem conexão com a internet", + override val tryAgain: String = "Tentar novamente" +) : MonsterCompendiumStrings + +fun MonsterCompendiumStrings(): MonsterCompendiumStrings = MonsterCompendiumEnStrings() + +internal fun AppLocalization.getStrings(): MonsterCompendiumStrings { + return when (getLanguage()) { + Language.ENGLISH -> MonsterCompendiumEnStrings() + Language.PORTUGUESE -> MonsterCompendiumPtrStrings() + } +} diff --git a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/di/Module.kt b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/di/Module.kt index f3853eb6d..167d25f5a 100644 --- a/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/di/Module.kt +++ b/feature/monster-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/compendium/state/di/Module.kt @@ -18,24 +18,29 @@ package br.alexandregpereira.hunter.monster.compendium.state.di import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAnalytics import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateHolder +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateRecovery import br.alexandregpereira.hunter.monster.registration.event.MonsterRegistrationEventListener import org.koin.dsl.module +import kotlin.native.HiddenFromObjC +@HiddenFromObjC val monsterCompendiumStateModule = module { factory { MonsterCompendiumStateHolder( - get(), - get(), - get(), - get(), - get(), - get(), - get(), - get(), - get(), + getMonsterCompendiumUseCase = get(), + getLastCompendiumScrollItemPositionUseCase = get(), + saveCompendiumScrollItemPositionUseCase = get(), + folderPreviewEventDispatcher = get(), + folderPreviewResultListener = get(), + monsterDetailEventDispatcher = get(), + monsterDetailEventListener = get(), + syncEventListener = get(), + syncEventDispatcher = get(), monsterRegistrationEventListener = get(), - get(), + dispatcher = get(), analytics = MonsterCompendiumAnalytics(get()), + appLocalization = get(), + stateRecovery = getOrNull() ?: MonsterCompendiumStateRecovery(), ) } } diff --git a/feature/monster-content-manager/android/src/main/java/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt b/feature/monster-content-manager/android/src/main/java/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt index c8e2457c7..30969571b 100644 --- a/feature/monster-content-manager/android/src/main/java/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt +++ b/feature/monster-content-manager/android/src/main/java/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt @@ -226,7 +226,7 @@ private fun Monster.asState(): MonsterCardState { imageState = MonsterImageState( url = imageData.url, type = MonsterTypeState.valueOf(type.name), - challengeRating = challengeRating, + challengeRating = challengeRatingFormatted, backgroundColor = ColorState( light = imageData.backgroundColor.light, dark = imageData.backgroundColor.dark diff --git a/feature/monster-content-manager/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/MonsterContentPreviewStateHolder.kt b/feature/monster-content-manager/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/MonsterContentPreviewStateHolder.kt index 46250bb7f..8bfe2cda5 100644 --- a/feature/monster-content-manager/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/MonsterContentPreviewStateHolder.kt +++ b/feature/monster-content-manager/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/MonsterContentPreviewStateHolder.kt @@ -22,8 +22,7 @@ import br.alexandregpereira.hunter.monster.compendium.domain.getCompendiumIndexF import br.alexandregpereira.hunter.monster.compendium.domain.getTableContentIndexFromCompendiumItemIndex import br.alexandregpereira.hunter.monster.content.preview.MonsterContentPreviewAction.Companion.goToCompendiumIndex import br.alexandregpereira.hunter.state.MutableActionHandler -import br.alexandregpereira.hunter.state.MutableStateHolder -import br.alexandregpereira.hunter.state.ScopeManager +import br.alexandregpereira.hunter.state.UiModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOf @@ -39,8 +38,7 @@ class MonsterContentPreviewStateHolder internal constructor( private val analytics: MonsterContentPreviewAnalytics, private val getRemoteMonsterCompendiumUseCase: GetRemoteMonsterCompendiumUseCase, private val monsterContentPreviewEventManager: MonsterContentPreviewEventManager, -) : ScopeManager(), - MutableStateHolder by MutableStateHolder(stateRecovery), +) : UiModel(stateRecovery.getState()), MutableActionHandler by MutableActionHandler() { init { @@ -153,4 +151,9 @@ class MonsterContentPreviewStateHolder internal constructor( } .launchIn(scope) } + + override fun onCleared() { + super.onCleared() + onActionHandlerClose() + } } diff --git a/feature/monster-detail/android/src/main/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt b/feature/monster-detail/android/src/main/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt index 4b2387bdc..30ffb0b48 100644 --- a/feature/monster-detail/android/src/main/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt +++ b/feature/monster-detail/android/src/main/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt @@ -410,7 +410,7 @@ private fun MonsterDetailPreview() = Window { imageState = MonsterImageState( url = "", type = CELESTIAL, - challengeRating = 0.0f, + challengeRating = "1", xp = "100 XP", backgroundColor = ColorState( light = "#ffe2e2", @@ -454,7 +454,7 @@ private fun MonsterTopBarPreview() = Window { imageState = MonsterImageState( url = "", type = CELESTIAL, - challengeRating = 0.0f, + challengeRating = "1", xp = "100 XP", backgroundColor = ColorState( light = "#ffe2e2", 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 d88345420..b78cddb7c 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 @@ -72,7 +72,7 @@ data class MonsterState( val type: MonsterType get() = imageState.type - val challengeRating: Float + val challengeRating: String get() = imageState.challengeRating val xp: String @@ -159,7 +159,7 @@ data class MonsterImageState( val url: String= "", val type: MonsterType = MonsterType.ABERRATION, val backgroundColor: ColorState = ColorState(), - val challengeRating: Float = 0f, + val challengeRating: String = "", val xp: String = "", val contentDescription: String = "" ) 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 f13fc5166..8ae533a17 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 @@ -286,7 +286,7 @@ class MonsterDetailStateHolder( initialMonsterListPositionIndex = it.monsterIndexSelected, monsters = it.monsters.asState(strings), measurementUnit = it.measurementUnit, - ).changeOptions().saveState(stateRecovery) + ).changeOptions() } } @@ -297,7 +297,7 @@ class MonsterDetailStateHolder( initialMonsterListPositionIndex = state.initialMonsterListPositionIndex, monsters = state.monsters, options = state.options - ) + ).saveState(stateRecovery) } } } diff --git a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateMapper.kt b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateMapper.kt index f561b0d26..a4a646682 100644 --- a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateMapper.kt +++ b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateMapper.kt @@ -43,7 +43,7 @@ private fun Monster.asState(strings: MonsterDetailStrings): MonsterState { name = name, imageState = imageData.asState( type = type, - challengeRating = challengeRating, + challengeRating = challengeRatingFormatted, xp = xpFormatted(), contentDescription = name ), @@ -81,7 +81,7 @@ private fun Monster.asState(strings: MonsterDetailStrings): MonsterState { private fun MonsterImageData.asState( type: MonsterType, - challengeRating: Float, + challengeRating: String, xp: String, contentDescription: String, ): MonsterImageState { diff --git a/feature/monster-lore-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/lore/detail/MonsterLoreDetailStateHolder.kt b/feature/monster-lore-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/lore/detail/MonsterLoreDetailStateHolder.kt index 3ed088db7..be47f06cf 100644 --- a/feature/monster-lore-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/lore/detail/MonsterLoreDetailStateHolder.kt +++ b/feature/monster-lore-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/lore/detail/MonsterLoreDetailStateHolder.kt @@ -19,11 +19,8 @@ package br.alexandregpereira.hunter.monster.lore.detail import br.alexandregpereira.hunter.event.monster.lore.detail.MonsterLoreDetailEvent import br.alexandregpereira.hunter.event.monster.lore.detail.MonsterLoreDetailEventListener import br.alexandregpereira.hunter.monster.lore.detail.domain.GetMonsterLoreDetailUseCase -import br.alexandregpereira.hunter.state.ScopeManager -import br.alexandregpereira.hunter.state.StateHolder +import br.alexandregpereira.hunter.state.UiModel import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn @@ -36,10 +33,7 @@ class MonsterLoreDetailStateHolder internal constructor( private val monsterLoreIndexStateRecovery: MonsterLoreIndexStateRecovery, private val dispatcher: CoroutineDispatcher, private val analytics: MonsterLoreDetailAnalytics, -) : ScopeManager(), StateHolder { - - private val _state = MutableStateFlow(stateRecovery.getState()) - override val state: StateFlow = _state +) : UiModel(stateRecovery.getState()) { init { observeEvents() @@ -79,8 +73,4 @@ class MonsterLoreDetailStateHolder internal constructor( analytics.trackMonsterLoreDetailClosed() setState { hideDetail() } } - - private fun setState(block: MonsterLoreDetailState.() -> MonsterLoreDetailState) { - _state.value = state.value.block() - } } diff --git a/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonsterResult.kt b/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonsterResult.kt index b9e5a7cf1..82be19a5f 100644 --- a/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonsterResult.kt +++ b/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonsterResult.kt @@ -22,7 +22,7 @@ internal data class SearchMonsterResult( val index: String, val name: String, val type: MonsterType, - val challengeRating: Float, + val challengeRating: String, val imageUrl: String, val backgroundColorLight: String, val backgroundColorDark: String, diff --git a/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonstersByUseCase.kt b/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonstersByUseCase.kt index 79e101965..c86327ecd 100644 --- a/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonstersByUseCase.kt +++ b/feature/search/android/src/main/java/br/alexandregpereira/hunter/search/domain/SearchMonstersByUseCase.kt @@ -66,7 +66,7 @@ internal class SearchMonstersByUseCase internal constructor( index = monster.index, name = monster.name, type = monster.type, - challengeRating = monster.challengeRating, + challengeRating = monster.challengeRatingFormatted, imageUrl = monster.imageData.url, backgroundColorLight = monster.imageData.backgroundColor.light, backgroundColorDark = monster.imageData.backgroundColor.dark, diff --git a/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/SpellCompendiumStateHolder.kt b/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/SpellCompendiumStateHolder.kt index 20596d817..18419b246 100644 --- a/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/SpellCompendiumStateHolder.kt +++ b/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/SpellCompendiumStateHolder.kt @@ -7,9 +7,7 @@ import br.alexandregpereira.hunter.localization.AppLocalization import br.alexandregpereira.hunter.spell.compendium.domain.GetSpellsUseCase import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumEvent import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumResult -import br.alexandregpereira.hunter.state.MutableStateHolder -import br.alexandregpereira.hunter.state.ScopeManager -import br.alexandregpereira.hunter.state.StateHolder +import br.alexandregpereira.hunter.state.UiModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow @@ -22,13 +20,11 @@ import kotlinx.coroutines.flow.onEach class SpellCompendiumStateHolder internal constructor( private val dispatcher: CoroutineDispatcher, private val getSpellsUseCase: GetSpellsUseCase, - private val stateHandler: MutableStateHolder, private val eventListener: EventListener, private val resultDispatcher: EventDispatcher, private val appLocalization: AppLocalization, -) : ScopeManager(), - SpellCompendiumIntent, - StateHolder by stateHandler { +) : UiModel(SpellCompendiumState()), + SpellCompendiumIntent { private val searchQuery = MutableStateFlow(state.value.searchText) private val originalSpellsGroupByLevel = mutableMapOf>() @@ -47,7 +43,7 @@ class SpellCompendiumStateHolder internal constructor( selectedSpellIndexes: List = emptyList(), ) { strings = getSpellCompendiumStrings(appLocalization.getLanguage()) - stateHandler.setState { copy(searchText = "", searchTextLabel = strings.searchLabel) } + setState { copy(searchText = "", searchTextLabel = strings.searchLabel) } getSpellsUseCase() .onEach { spells -> val spellsGroupByLevel = spells.groupByLevel(selectedSpellIndexes) @@ -58,7 +54,7 @@ class SpellCompendiumStateHolder internal constructor( } originalSpellsGroupByLevel.clear() originalSpellsGroupByLevel.putAll(spellsGroupByLevel) - stateHandler.setState { + setState { copy( spellsGroupByLevel = spellsGroupByLevel, initialItemIndex = compendiumIndex?.takeIf { it >= 0 } ?: 0, @@ -72,7 +68,7 @@ class SpellCompendiumStateHolder internal constructor( private fun debounceSearch() { searchQuery.debounce(500L) .onEach { text -> - stateHandler.setState { + setState { val spellsGroupByLevel = if (text.isNotBlank()){ val spellsFiltered = originalSpellsGroupByLevel.values.flatten() .filter { it.name.contains(text, ignoreCase = true) } @@ -94,7 +90,7 @@ class SpellCompendiumStateHolder internal constructor( spellIndex = event.spellIndex, selectedSpellIndexes = event.selectedSpellIndexes, ) - stateHandler.setState { copy(isShowing = true) } + setState { copy(isShowing = true) } } is SpellCompendiumEvent.Hide -> onClose() } @@ -102,7 +98,7 @@ class SpellCompendiumStateHolder internal constructor( } override fun onSearchTextChange(text: String) { - stateHandler.setState { copy(searchText = text) } + setState { copy(searchText = text) } searchQuery.value = text } @@ -115,7 +111,7 @@ class SpellCompendiumStateHolder internal constructor( } override fun onClose() { - stateHandler.setState { copy(isShowing = false) } + setState { copy(isShowing = false) } } private fun List.groupByLevel( diff --git a/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/di/Module.kt b/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/di/Module.kt index bb8471253..cd2cb4bb5 100644 --- a/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/di/Module.kt +++ b/feature/spell-compendium/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/spell/compendium/di/Module.kt @@ -3,13 +3,11 @@ package br.alexandregpereira.hunter.spell.compendium.di import br.alexandregpereira.hunter.event.EventDispatcher import br.alexandregpereira.hunter.event.EventListener import br.alexandregpereira.hunter.event.EventManager -import br.alexandregpereira.hunter.spell.compendium.SpellCompendiumState import br.alexandregpereira.hunter.spell.compendium.SpellCompendiumStateHolder import br.alexandregpereira.hunter.spell.compendium.domain.GetSpellsUseCase import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumEvent import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumEventResultDispatcher import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumResult -import br.alexandregpereira.hunter.state.MutableStateHolder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import org.koin.dsl.module @@ -27,7 +25,6 @@ val spellCompendiumStateModule = module { SpellCompendiumStateHolder( dispatcher = Dispatchers.Default, getSpellsUseCase = get(), - stateHandler = MutableStateHolder(SpellCompendiumState()), eventListener = get(), resultDispatcher = get(), appLocalization = get(), diff --git a/feature/sync/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/sync/SyncStateHolder.kt b/feature/sync/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/sync/SyncStateHolder.kt index d34095e6d..916fe307d 100644 --- a/feature/sync/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/sync/SyncStateHolder.kt +++ b/feature/sync/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/sync/SyncStateHolder.kt @@ -18,13 +18,10 @@ package br.alexandregpereira.hunter.sync import br.alexandregpereira.hunter.domain.sync.SyncUseCase import br.alexandregpereira.hunter.domain.sync.model.SyncStatus -import br.alexandregpereira.hunter.state.ScopeManager -import br.alexandregpereira.hunter.state.StateHolder +import br.alexandregpereira.hunter.state.UiModel import br.alexandregpereira.hunter.sync.event.SyncEvent.Finished import br.alexandregpereira.hunter.sync.event.SyncEvent.Start import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn @@ -36,12 +33,7 @@ class SyncStateHolder internal constructor( private val syncEventManager: SyncEventManager, private val syncUseCase: SyncUseCase, private val analytics: SyncAnalytics -) : ScopeManager(), StateHolder { - - private val _state: MutableStateFlow = MutableStateFlow( - stateRecovery.getState() - ) - override val state: StateFlow = _state +) : UiModel(stateRecovery.getState()) { init { observeEvents() @@ -104,8 +96,4 @@ class SyncStateHolder internal constructor( analytics.trackTryAgain() syncEventManager.startSync() } - - private fun setState(block: SyncState.() -> SyncState) { - _state.value = state.value.block() - } } diff --git a/iosApp/MonsterCompendium.xcodeproj/project.pbxproj b/iosApp/MonsterCompendium.xcodeproj/project.pbxproj index 7988ad0cc..cf8ea8b95 100644 --- a/iosApp/MonsterCompendium.xcodeproj/project.pbxproj +++ b/iosApp/MonsterCompendium.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ C1D25EBF29862ED300F0EBF9 /* MonsterCompendiumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D25EBE29862ED300F0EBF9 /* MonsterCompendiumView.swift */; }; C1FC49122986F13600C18252 /* MonsterCompendiumItemUiState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FC49112986F13600C18252 /* MonsterCompendiumItemUiState.swift */; }; C1FC49142986F15000C18252 /* MonsterUiState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FC49132986F15000C18252 /* MonsterUiState.swift */; }; - C1FC491629870A2600C18252 /* MonsterCompendiumUiState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FC491529870A2600C18252 /* MonsterCompendiumUiState.swift */; }; E27E58612B7AC87900ED83F4 /* MonsterDetailStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27E58602B7AC87900ED83F4 /* MonsterDetailStrings.swift */; }; /* End PBXBuildFile section */ @@ -81,7 +80,6 @@ C1D25EBE29862ED300F0EBF9 /* MonsterCompendiumView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterCompendiumView.swift; sourceTree = ""; }; C1FC49112986F13600C18252 /* MonsterCompendiumItemUiState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterCompendiumItemUiState.swift; sourceTree = ""; }; C1FC49132986F15000C18252 /* MonsterUiState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterUiState.swift; sourceTree = ""; }; - C1FC491529870A2600C18252 /* MonsterCompendiumUiState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterCompendiumUiState.swift; sourceTree = ""; }; E27E58602B7AC87900ED83F4 /* MonsterDetailStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterDetailStrings.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -217,7 +215,6 @@ children = ( C1D25EB929862DEC00F0EBF9 /* UI */, C1D25EB729862DCC00F0EBF9 /* MonsterCompendiumViewModel.swift */, - C1FC491529870A2600C18252 /* MonsterCompendiumUiState.swift */, ); path = MonsterCompendium; sourceTree = ""; @@ -395,7 +392,6 @@ buildActionMask = 2147483647; files = ( C1D25EB829862DCC00F0EBF9 /* MonsterCompendiumViewModel.swift in Sources */, - C1FC491629870A2600C18252 /* MonsterCompendiumUiState.swift in Sources */, C1D25EB429862B4E00F0EBF9 /* MonsterCardView.swift in Sources */, C1FC49122986F13600C18252 /* MonsterCompendiumItemUiState.swift in Sources */, C15BF15229B1140500F47375 /* MonsterDetailItemUiState.swift in Sources */, diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumUiState.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumUiState.swift deleted file mode 100644 index fc725144a..000000000 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumUiState.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// MonsterCompendiumUiState.swift -// MonsterCompendium -// -// Created by Alexandre G Pereira on 29/01/23. -// - -import Foundation - -struct MonsterCompendiumUiState { - var items: [MonsterCompendiumItemUiState] = [] - var alphabet: [String] = [] - var tableContent: [TableContentItemState] = [] - var tableContentPopupScreenType: TableContentPopup.ScreenType = .circleLetter - var alphabetSelectedIndex: Int = 0 - var tableContentSelectedIndex: Int = 0 - var tableContentInitialIndex: Int = 0 -} diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumViewModel.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumViewModel.swift index 41a0cf46a..9e1dd7091 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumViewModel.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterCompendium/MonsterCompendiumViewModel.swift @@ -12,24 +12,16 @@ import shared private let feature: MonsterCompendiumFeature = MonsterCompendiumFeature() - @Published var isLoading: Bool = false - @Published var state: MonsterCompendiumUiState = MonsterCompendiumUiState() + @Published var state: MonsterCompendiumState = MonsterCompendiumState.Companion.shared.Empty @Published var compendiumIndex: Int = -1 init() { - feature.state.collect { (state: MonsterCompendiumStateIos) -> Void in - self.isLoading = state.isLoading - self.state = state.asMonsterCompendiumUiState() - self.state.alphabet = state.alphabet - self.state.tableContent = state.asTableContentItemState() - self.state.tableContentPopupScreenType = state.popupOpened && state.tableContentOpened ? .tableContent : (state.popupOpened ? .alphabetGrid : .circleLetter) - self.state.alphabetSelectedIndex = Int(state.alphabetSelectedIndex) - self.state.tableContentSelectedIndex = Int(state.tableContentIndex) - self.state.tableContentInitialIndex = Int(state.tableContentInitialIndex) + feature.stateHolder.state.subscribe { (state: MonsterCompendiumState) -> Void in + self.state = state } - feature.action.collect{ (action: MonsterCompendiumActionIos) -> Void in - guard let compendiumIndex = action.compendiumIndex else { return } - self.compendiumIndex = compendiumIndex.intValue + feature.stateHolder.action.subscribe { (action: MonsterCompendiumAction) -> Void in + guard let action = action as? MonsterCompendiumAction.GoToCompendiumIndex else { return } + self.compendiumIndex = Int(action.index) } } @@ -65,59 +57,3 @@ import shared feature.stateHolder.onCleared() } } - -extension MonsterCompendiumStateIos { - - func asMonsterCompendiumItemUiState() -> [MonsterCompendiumItemUiState] { - self.items.map { item in - if (item.title != nil) { - return MonsterCompendiumItemUiState.title(MonsterCompendiumItemUiState.Title(value: item.title!.value, id: item.title!.id, isHeader: item.title!.isHeader)) - } else { - return MonsterCompendiumItemUiState.item(MonsterCompendiumItemUiState.Item(value: item.monster!.asUiState())) - } - } - } - - func asMonsterCompendiumUiState() -> MonsterCompendiumUiState { - return MonsterCompendiumUiState( - items: asMonsterCompendiumItemUiState() - ) - } - - func asTableContentItemState() -> [TableContentItemState] { - self.tableContent.map { itemIos in - TableContentItemState( - id: itemIos.id, - text: itemIos.text, - type: { - switch itemIos.type { - case TableContentItemType.header1: - return .HEADER1 - case TableContentItemType.header2: - return .HEADER2 - case TableContentItemType.body: - return .BODY - default: - return .BODY - } - }() - ) - } - } -} - -extension Monster { - - func asUiState() -> MonsterUiState { - let monster = self - return MonsterUiState( - index: monster.index, - name: monster.name, - challengeRating: monster.challengeRating, - imageUrl: monster.imageData.url, - backgroundColorLight: monster.imageData.backgroundColor.light, - backgroundColorDark: monster.imageData.backgroundColor.dark, - isImageHorizontal: monster.imageData.isHorizontal - ) - } -} diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumItemUiState.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumItemUiState.swift index 29f305f89..fe4d2a73b 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumItemUiState.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumItemUiState.swift @@ -6,55 +6,18 @@ // import Foundation +import shared -enum MonsterCompendiumItemUiState : Identifiable { - - var id: String { - switch self { - case .title (let title): return title.id - case .item (let item): return item.value.index - } - } - - func isHorizontal() -> Bool { - switch self { - case .title: return true - case .item (let item): return item.value.isImageHorizontal - } - } - - struct Title { - let value: String - let id: String - let isHeader: Bool - } - - struct Item { - let value: MonsterUiState - } - - case title(Title) - case item(Item) -} - -extension MonsterCompendiumItemUiState { - static let sampleData: [MonsterCompendiumItemUiState] = (0...100).map { +extension MonsterCompendiumItemState { + static let sampleData: [MonsterCompendiumItemState] = (0...100).map { if $0 == 0 { - return MonsterCompendiumItemUiState.title(MonsterCompendiumItemUiState.Title(value: "A", id: "\($0)", isHeader: true)) + return MonsterCompendiumItemState.Title(id: "\($0)", value: "A", isHeader: true) } else if $0 % 6 == 0 { - return MonsterCompendiumItemUiState.title(MonsterCompendiumItemUiState.Title(value: "\($0)", id: "\($0)", isHeader: false)) + return MonsterCompendiumItemState.Title(id: "\($0)", value: "\($0)", isHeader: false) } else { - return MonsterCompendiumItemUiState.item(MonsterCompendiumItemUiState.Item( - value: MonsterUiState( - index: "\($0)", - name: "Aboleth\($0)", - challengeRating: 20.0, - imageUrl: "https://raw.githubusercontent.com/alexandregpereira/hunter-api/main/images/aboleth.png", - backgroundColorLight: "#d3dedc", - backgroundColorDark: "#d3dedc", - isImageHorizontal: $0 == 5 || $0 == 7 - ) - )) + return MonsterCompendiumItemState.Item( + monster: MonsterPreviewState.sampleData[0] + ) } } } diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumScreenView.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumScreenView.swift index 6b4deb7e9..ea14957f7 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumScreenView.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumScreenView.swift @@ -18,7 +18,7 @@ struct MonsterCompendiumScreenView: View { var body: some View { let state = viewModel.state ZStack { - if viewModel.isLoading { + if viewModel.state.isLoading { ProgressView() .progressViewStyle(CircularProgressViewStyle()) .transition(.opacity) @@ -38,10 +38,10 @@ struct MonsterCompendiumScreenView: View { TableContentPopup( tableContent: state.tableContent, alphabet: state.alphabet, - alphabetSelectedIndex: state.alphabetSelectedIndex, - tableContentSelectedIndex: state.tableContentSelectedIndex, - tableContentInitialIndex: state.tableContentInitialIndex, - screen: state.tableContentPopupScreenType, + alphabetSelectedIndex: Int(state.alphabetSelectedIndex), + tableContentSelectedIndex: Int(state.tableContentIndex), + tableContentInitialIndex: Int(state.tableContentInitialIndex), + screen: state.popupOpened && state.tableContentOpened ? .tableContent : (state.popupOpened ? .alphabetGrid : .circleLetter), onOpenButtonClicked: { viewModel.onPopupOpened() }, onCloseButtonClicked: { viewModel.onPopupClosed() }, onTableContentClicked: { viewModel.onTableContentIndexClicked(position: $0) }, @@ -52,6 +52,6 @@ struct MonsterCompendiumScreenView: View { .transition(.opacity) } } - .animation(.spring(), value: viewModel.isLoading) + .animation(.spring(), value: viewModel.state.isLoading) } } diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumView.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumView.swift index 33ed9e5b5..d51763edb 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumView.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/MonsterCompendiumView.swift @@ -5,18 +5,19 @@ // Created by Alexandre G Pereira on 29/01/23. // +import shared import SwiftUI struct MonsterCompendiumView: View { - let items: [MonsterCompendiumItemUiState] + let items: [MonsterCompendiumItemState] let initialCompendiumIndex: Int let compendiumIndex: Int let onMonsterItemClick: (String) -> Void let onFirstVisibleIndexChange: ((Int) -> Void)? init( - items: [MonsterCompendiumItemUiState], + items: [MonsterCompendiumItemState], initialCompendiumIndex: Int = 0, compendiumIndex: Int = -1, onMonsterItemClick: @escaping (String) -> Void, @@ -39,9 +40,9 @@ struct MonsterCompendiumView: View { ] LazyVGrid(columns: gridItems, spacing: 0) { - ForEach(Array(items.enumerated()), id: \.element.id) { index, item in + ForEach(Array(items.enumerated()), id: \.element.key) { index, item in MonsterCompendiumItemView(item: item, onMonsterItemClick: onMonsterItemClick) - .id(item.id) + .id(item.key) .background(GeometryReader { itemProxy in let gridTop = gridProxy.frame(in: .global).minY let itemTop = itemProxy.frame(in: .global).minY @@ -64,11 +65,11 @@ struct MonsterCompendiumView: View { .onChange(of: compendiumIndex) { newIndex in guard newIndex >= 0 else { return } withAnimation { - scrollProxy.scrollTo(items[newIndex].id, anchor: .top) + scrollProxy.scrollTo(items[newIndex].key, anchor: .top) } } .onAppear { - scrollProxy.scrollTo(items[initialCompendiumIndex].id, anchor: .top) + scrollProxy.scrollTo(items[initialCompendiumIndex].key, anchor: .top) } } } @@ -77,26 +78,28 @@ struct MonsterCompendiumView: View { struct MonsterCompendiumItemView: View { - let item: MonsterCompendiumItemUiState + let item: MonsterCompendiumItemState let onMonsterItemClick: (String) -> Void var body: some View { ZStack(alignment: .leading) { switch item { - case .title (let title): + case let title as MonsterCompendiumItemState.Title: let font = title.isHeader ? Font.system(size: 48) : Font.title let bottomPadding = title.isHeader ? 32.0 : 16 Text(title.value) .font(font) .padding(EdgeInsets(top: 40, leading: 16, bottom: bottomPadding, trailing: 16)) .frame(maxWidth: .infinity, alignment: .leading) - case .item (let item): - let monster = item.value + case let item as MonsterCompendiumItemState.Item: + let monster = item.monster MonsterCardView(monster: monster) .padding(EdgeInsets(top: 16, leading: 8, bottom: 16, trailing: 8)) .onTapGesture { onMonsterItemClick(monster.index) } + default: + EmptyView() } } .frame( @@ -117,6 +120,6 @@ struct FirstVisibleItemIndexKey: PreferenceKey { struct MonsterCompendiumView_Previews: PreviewProvider { static var previews: some View { - MonsterCompendiumView(items: MonsterCompendiumItemUiState.sampleData, onMonsterItemClick: { print($0) }) + MonsterCompendiumView(items: MonsterCompendiumItemState.sampleData, onMonsterItemClick: { print($0) }) } } diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentPopup.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentPopup.swift index 32e437e43..ff3e5216e 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentPopup.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentPopup.swift @@ -5,10 +5,11 @@ // Created by Alexandre G Pereira on 08/03/23. // +import shared import SwiftUI struct TableContentPopup: View { - let tableContent: [TableContentItemState] + let tableContent: [TableContentItem] let alphabet: [String] let alphabetSelectedIndex: Int let tableContentSelectedIndex: Int @@ -274,12 +275,12 @@ struct CloseButton_Previews: PreviewProvider { struct TableContentPopup_Previews: PreviewProvider { static let alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] - static var items: [TableContentItemState] = { - var items = [TableContentItemState]() + static var items: [TableContentItem] = { + var items = [TableContentItem]() for i in 0..<50 { - let type = i % 3 == 0 ? TableContentItemTypeState.HEADER1 : (i % 3 == 1 ? TableContentItemTypeState.HEADER2 : TableContentItemTypeState.BODY) + let type = i % 3 == 0 ? TableContentItemType.header1 : (i % 3 == 1 ? TableContentItemType.header2 : TableContentItemType.body) let text = "Item \(i)" - let item = TableContentItemState(id: text, text: text, type: type) + let item = TableContentItem(text: text, type: type, id: text) items.append(item) } return items diff --git a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentView.swift b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentView.swift index 83ea6c02b..e04ab5de5 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentView.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterCompendium/UI/TableContentView.swift @@ -5,10 +5,11 @@ // Created by Alexandre G Pereira on 08/03/23. // +import shared import SwiftUI struct TableContent: View { - let items: [TableContentItemState] + let items: [TableContentItem] let selectedIndex: Int let onTap: (Int) -> Void let initialIndex: Int @@ -56,37 +57,25 @@ struct TableContent: View { } } - func fontForType(_ type: TableContentItemTypeState) -> Font { + func fontForType(_ type: TableContentItemType) -> Font { switch type { - case .HEADER1: + case .header1: return .system(size: 32, weight: .bold) - case .HEADER2: + case .header2: return .system(size: 18, weight: .bold) - case .BODY: + default: return .system(size: 14, weight: .regular) } } } -struct TableContentItemState: Identifiable { - let id: String - let text: String - let type: TableContentItemTypeState -} - -enum TableContentItemTypeState { - case HEADER1 - case HEADER2 - case BODY -} - struct TableContent_Previews: PreviewProvider { - static var items: [TableContentItemState] = { - var items = [TableContentItemState]() + static var items: [TableContentItem] = { + var items = [TableContentItem]() for i in 0..<50 { - let type = i % 3 == 0 ? TableContentItemTypeState.HEADER1 : (i % 3 == 1 ? TableContentItemTypeState.HEADER2 : TableContentItemTypeState.BODY) + let type = i % 3 == 0 ? TableContentItemType.header1 : (i % 3 == 1 ? TableContentItemType.header2 : TableContentItemType.body) let text = "Item \(i)" - let item = TableContentItemState(id: text, text: text, type: type) + let item = TableContentItem(text: text, type: type, id: text) items.append(item) } return items diff --git a/iosApp/MonsterCompendium/Feature/MonsterDetail/MonsterDetailViewModel.swift b/iosApp/MonsterCompendium/Feature/MonsterDetail/MonsterDetailViewModel.swift index 852cf7c47..8c8485e61 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterDetail/MonsterDetailViewModel.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterDetail/MonsterDetailViewModel.swift @@ -14,7 +14,7 @@ import shared private let feature: MonsterDetailFeature = MonsterDetailFeature() init() { - feature.state.collect { (state: MonsterDetailState) -> Void in + feature.stateHolder.state.subscribe { (state: MonsterDetailState) -> Void in self.state = state } } diff --git a/iosApp/MonsterCompendium/Feature/MonsterDetail/UI/MonsterDetailView.swift b/iosApp/MonsterCompendium/Feature/MonsterDetail/UI/MonsterDetailView.swift index 0e3ec1584..23e6d91f1 100644 --- a/iosApp/MonsterCompendium/Feature/MonsterDetail/UI/MonsterDetailView.swift +++ b/iosApp/MonsterCompendium/Feature/MonsterDetail/UI/MonsterDetailView.swift @@ -524,7 +524,8 @@ struct MonsterDetailView_Previews: PreviewProvider { url: "https://raw.githubusercontent.com/alexandregpereira/hunter-api/main/images/aboleth.png", type: MonsterType.aberration, backgroundColor: ColorState(light: "#ABCDEF", dark: "#ABCDEF"), - challengeRating: 1.0, xp: "250 xp", + challengeRating: "1", + xp: "250 xp", contentDescription: "" ), subtitle: "Sample Subtitle", diff --git a/iosApp/MonsterCompendium/UI/MonsterCardView.swift b/iosApp/MonsterCompendium/UI/MonsterCardView.swift index 5efe22787..47c0d1455 100644 --- a/iosApp/MonsterCompendium/UI/MonsterCardView.swift +++ b/iosApp/MonsterCompendium/UI/MonsterCardView.swift @@ -5,13 +5,14 @@ // Created by Alexandre G Pereira on 29/01/23. // -import SwiftUI import SDWebImageSwiftUI +import shared +import SwiftUI struct MonsterCardView: View { @Environment(\.colorScheme) var colorScheme - var monster: MonsterUiState + var monster: MonsterPreviewState var body: some View { VStack(alignment: .leading) { @@ -42,7 +43,7 @@ struct MonsterCardView: View { ) .padding(EdgeInsets(top: -50, leading: -50, bottom: 0, trailing: 0)) - Text(monster.challengeRatingFormatted) + Text(monster.challengeRating) .frame( minWidth: 0, maxWidth: .infinity, @@ -63,7 +64,7 @@ struct MonsterCardView: View { } struct MonsterCardView_Previews: PreviewProvider { - static var monsters = MonsterUiState.sampleData + static var monsters = MonsterPreviewState.sampleData static var previews: some View { MonsterCardView(monster: monsters[0]) .padding() @@ -71,17 +72,6 @@ struct MonsterCardView_Previews: PreviewProvider { } } -extension MonsterUiState { - var challengeRatingFormatted: String { - if self.challengeRating >= 1 { - return Int(self.challengeRating).description - } else { - let safeNumber = self.challengeRating > 0 ? self.challengeRating : 0.12 - return "1/" + Int(1 / safeNumber).description - } - } -} - extension Color { init?(hex: String) { var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/iosApp/MonsterCompendium/UI/MonsterUiState.swift b/iosApp/MonsterCompendium/UI/MonsterUiState.swift index 6446023c7..13709d9c0 100644 --- a/iosApp/MonsterCompendium/UI/MonsterUiState.swift +++ b/iosApp/MonsterCompendium/UI/MonsterUiState.swift @@ -6,6 +6,7 @@ // import Foundation +import shared struct MonsterUiState { let index: String @@ -17,12 +18,13 @@ struct MonsterUiState { var isImageHorizontal: Bool } -extension MonsterUiState { - static let sampleData: [MonsterUiState] = (1...100).map { - MonsterUiState( +extension MonsterPreviewState { + static let sampleData: [MonsterPreviewState] = (1...100).map { + MonsterPreviewState( index: "\($0)", name: "Aboleth\($0)", - challengeRating: 20.0, + type: MonsterType.aberration, + challengeRating: "20", imageUrl: "https://raw.githubusercontent.com/alexandregpereira/hunter-api/main/images/aboleth.png", backgroundColorLight: "#d3dedc", backgroundColorDark: "#d3dedc", diff --git a/settings.gradle b/settings.gradle index c20c676a6..13bb32178 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ include ':app' include ':core:analytics' include ':core:event' +include ':core:flow' include ':core:localization' include ':core:state-holder' include ':core:uuid' diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 75398ecf7..bd6e3be5e 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -22,33 +22,26 @@ configureJvmTargets(iosFramework = { linkerOpts("-l", "sqlite3") }) kotlin { sourceSets { - val commonMain by getting { - dependencies { - implementation(project(":core:analytics")) - implementation(project(":core:localization")) - implementation(project(":domain:app:data")) - implementation(project(":domain:app:core")) - implementation(project(":domain:sync:core")) - implementation(project(":feature:folder-insert:event")) // TODO Remove later - implementation(project(":feature:folder-preview:event")) // TODO Remove later - implementation(project(":feature:monster-lore-detail:event")) // TODO Remove later - implementation(project(":feature:monster-registration:event")) // TODO Remove later - implementation(project(":feature:spell-detail:event")) // TODO Remove later - implementation(project(":feature:monster-compendium:state-holder")) - implementation(project(":feature:monster-detail:state-holder")) - implementation(project(":feature:sync:state-holder")) - implementation(libs.kotlin.coroutines.core) - implementation(libs.koin.core) - } + commonMain.dependencies { + implementation(project(":core:analytics")) + implementation(project(":core:localization")) + implementation(project(":domain:app:data")) + implementation(project(":domain:app:core")) + implementation(project(":domain:sync:core")) + implementation(project(":feature:folder-insert:event")) // TODO Remove later + implementation(project(":feature:folder-preview:event")) // TODO Remove later + implementation(project(":feature:monster-lore-detail:event")) // TODO Remove later + implementation(project(":feature:monster-registration:event")) // TODO Remove later + implementation(project(":feature:spell-detail:event")) // TODO Remove later + implementation(project(":feature:monster-compendium:state-holder")) + implementation(project(":feature:monster-detail:state-holder")) + implementation(project(":feature:sync:state-holder")) + implementation(libs.kotlin.coroutines.core) + implementation(libs.koin.core) } - val jvmTest by getting { - dependencies { - implementation(libs.bundles.unittest) - implementation(libs.koin.test) - } - } - if (isMac()) { - val iosMain by getting + jvmTest.dependencies { + implementation(libs.bundles.unittest) + implementation(libs.koin.test) } } } diff --git a/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/IosFlow.kt b/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/IosFlow.kt deleted file mode 100644 index 1543bdad0..000000000 --- a/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/IosFlow.kt +++ /dev/null @@ -1,21 +0,0 @@ -package br.alexandregpereira.hunter.shared - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -class IosFlow internal constructor( - private val origin: Flow, - private val coroutineScope: CoroutineScope, -) { - - fun collect(block: (T) -> Unit) { - origin.onEach(block).launchIn(coroutineScope) - } -} - -internal fun Flow.iosFlow(coroutineScope: CoroutineScope): IosFlow = IosFlow( - origin = this, - coroutineScope = coroutineScope -) diff --git a/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterCompendiumFeature.kt b/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterCompendiumFeature.kt index 2d95984da..88490d017 100644 --- a/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterCompendiumFeature.kt +++ b/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterCompendiumFeature.kt @@ -16,16 +16,13 @@ package br.alexandregpereira.hunter.shared -import br.alexandregpereira.hunter.domain.model.Monster -import br.alexandregpereira.hunter.monster.compendium.domain.model.MonsterCompendiumItem -import br.alexandregpereira.hunter.monster.compendium.domain.model.TableContentItem +import br.alexandregpereira.hunter.domain.model.MonsterType import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumAction -import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumState +import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumItemState import br.alexandregpereira.hunter.monster.compendium.state.MonsterCompendiumStateHolder +import br.alexandregpereira.hunter.monster.compendium.state.MonsterPreviewState import br.alexandregpereira.hunter.sync.SyncStateHolder import br.alexandregpereira.hunter.sync.event.SyncEventDispatcher -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -34,77 +31,43 @@ class MonsterCompendiumFeature : KoinComponent { val stateHolder: MonsterCompendiumStateHolder by inject() // TODO Remove after iOS sync feature implementation - val syncEventDispatcher: SyncEventDispatcher by inject() - val syncStateHolder: SyncStateHolder by inject() + private val syncEventDispatcher: SyncEventDispatcher by inject() + private val syncStateHolder: SyncStateHolder by inject() init { // TODO Remove after iOS sync feature implementation - syncStateHolder.state.iosFlow(stateHolder.scope).collect { + syncStateHolder.state.subscribe { println("Sync state $it") } - } - val state: IosFlow = stateHolder.state.map { - it.asMonsterCompendiumStateIos() - }.onEach { monsters -> - // TODO Remove after iOS sync feature implementation - if (monsters.isLoading.not() && monsters.items.isEmpty()) { - syncEventDispatcher.startSync() + stateHolder.state.subscribe { monsters -> + // TODO Remove after iOS sync feature implementation + if (monsters.isLoading.not() && monsters.items.isEmpty()) { + syncEventDispatcher.startSync() + } } - }.iosFlow(stateHolder.scope) - - val action: IosFlow = stateHolder.action.map { action -> - when (action) { - is MonsterCompendiumAction.GoToCompendiumIndex -> MonsterCompendiumActionIos( - compendiumIndex = action.index - ) - } - }.iosFlow(stateHolder.scope) - - private fun MonsterCompendiumState.asMonsterCompendiumStateIos(): MonsterCompendiumStateIos { - return MonsterCompendiumStateIos( - isLoading = isLoading, - items = items.map { it.asMonsterCompendiumItemIos() }, - alphabet = alphabet, - alphabetSelectedIndex = alphabetSelectedIndex, - popupOpened = popupOpened, - tableContent = tableContent, - tableContentIndex = tableContentIndex, - tableContentInitialIndex = tableContentInitialIndex, - tableContentOpened = tableContentOpened, - isShowingMonsterFolderPreview = isShowingMonsterFolderPreview, - ) - } - - private fun MonsterCompendiumItem.asMonsterCompendiumItemIos(): MonsterCompendiumItemIos { - return MonsterCompendiumItemIos( - title = this as? MonsterCompendiumItem.Title, - monster = (this as? MonsterCompendiumItem.Item)?.monster - ) } } -@ObjCName(name = "MonsterCompendiumStateIos", exact = true) -data class MonsterCompendiumStateIos( - val isLoading: Boolean = false, - val items: List = emptyList(), - val alphabet: List = emptyList(), - val alphabetSelectedIndex: Int = -1, - val popupOpened: Boolean = false, - val tableContent: List = emptyList(), - val tableContentIndex: Int = -1, - val tableContentInitialIndex: Int = 0, - val tableContentOpened: Boolean = false, - val isShowingMonsterFolderPreview: Boolean = false, +/** + * For some reason the compiler do not generate these sealed classes if I don't do this. + * Seems that it needs to use directly the sealed class types to be generated. + **/ +val stubAction = MonsterCompendiumAction.GoToCompendiumIndex(0) +val stubItem = MonsterCompendiumItemState.Item( + monster = MonsterPreviewState( + index = "", + name = "", + type = MonsterType.PLANT, + challengeRating = "0", + imageUrl = "", + backgroundColorLight = "", + backgroundColorDark = "", + isImageHorizontal = false + ) ) - -@ObjCName(name = "MonsterCompendiumItemIos", exact = true) -data class MonsterCompendiumItemIos( - val title: MonsterCompendiumItem.Title?, - val monster: Monster? -) - -@ObjCName(name = "MonsterCompendiumActionIos", exact = true) -data class MonsterCompendiumActionIos( - val compendiumIndex: Int? +val stubTitle = MonsterCompendiumItemState.Title( + id = "", + value = "", + isHeader = false ) diff --git a/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterDetailFeature.kt b/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterDetailFeature.kt index 678350d19..1de56ca0f 100644 --- a/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterDetailFeature.kt +++ b/shared/src/iosMain/kotlin/br/alexandregpereira/hunter/shared/MonsterDetailFeature.kt @@ -16,7 +16,6 @@ package br.alexandregpereira.hunter.shared -import br.alexandregpereira.hunter.monster.detail.MonsterDetailState import br.alexandregpereira.hunter.monster.detail.MonsterDetailStateHolder import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -24,6 +23,4 @@ import org.koin.core.component.inject class MonsterDetailFeature : KoinComponent { val stateHolder: MonsterDetailStateHolder by inject() - - val state: IosFlow = stateHolder.state.iosFlow(stateHolder.scope) } diff --git a/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/ChallengeRatingCircle.kt b/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/ChallengeRatingCircle.kt index 4a6d55b68..a50cbaf71 100644 --- a/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/ChallengeRatingCircle.kt +++ b/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/ChallengeRatingCircle.kt @@ -45,7 +45,7 @@ import br.alexandregpereira.hunter.ui.theme.HunterTheme @Composable fun ChallengeRatingCircle( - challengeRating: Float, + challengeRating: String, size: Dp, modifier: Modifier = Modifier, xp: String = "", @@ -77,7 +77,7 @@ fun ChallengeRatingCircle( .padding(top = contentTopPadding) ) { Text( - challengeRating.getChallengeRatingFormatted(), + challengeRating, fontWeight = FontWeight.SemiBold, fontSize = fontSize, color = MaterialTheme.colors.onSurface, @@ -133,21 +133,12 @@ private fun DrawChallengeRatingCircle( ) } -private fun Float.getChallengeRatingFormatted(): String { - return if (this < 1) { - val value = 1 / this - "1/${value.toInt()}" - } else { - this.toInt().toString() - } -} - @Preview @Composable private fun ChallengeRatingWithXpPreview() { HunterTheme { ChallengeRatingCircle( - challengeRating = 10f, + challengeRating = "10", xp = "111k XP", size = 62.dp, fontSize = 18.sp, @@ -161,7 +152,7 @@ private fun ChallengeRatingWithXpPreview() { private fun ChallengeRatingWithXpPreviewWithDifferentSize() { HunterTheme { ChallengeRatingCircle( - challengeRating = 10f, + challengeRating = "10", xp = "155k XP", size = 56.dp, fontSize = 16.sp, @@ -174,7 +165,7 @@ private fun ChallengeRatingWithXpPreviewWithDifferentSize() { @Composable private fun ChallengeRatingPreview() { HunterTheme { - ChallengeRatingCircle(challengeRating = 10f, size = 48.dp) + ChallengeRatingCircle(challengeRating = "10", size = 48.dp) } } @@ -183,7 +174,7 @@ private fun ChallengeRatingPreview() { private fun ChallengeRatingPreviewWithDifferentSize() { HunterTheme { ChallengeRatingCircle( - challengeRating = 10f, + challengeRating = "10", size = 56.dp, fontSize = 16.sp, contentTopPadding = 24.dp diff --git a/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterCard.kt b/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterCard.kt index b4f750cb3..25ee402a1 100644 --- a/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterCard.kt +++ b/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterCard.kt @@ -35,7 +35,7 @@ fun MonsterCard( url: String, @DrawableRes iconRes: Int, backgroundColor: String, - challengeRating: Float, + challengeRating: String, modifier: Modifier = Modifier, onCLick: () -> Unit = {}, onLongCLick: () -> Unit = {}, @@ -73,7 +73,7 @@ private fun MonsterCardPreview() { name = "Monster of the Monsters", url = "asdasdas", backgroundColor = "#ffe3ee", - challengeRating = 18f, + challengeRating = "18", iconRes = R.drawable.ic_aberration ) } diff --git a/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt b/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt index 0b2d67ba0..1e2d11337 100644 --- a/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt +++ b/ui/core/src/main/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt @@ -33,7 +33,7 @@ fun MonsterImage( url: String, @DrawableRes iconRes: Int, backgroundColor: String, - challengeRating: Float, + challengeRating: String, modifier: Modifier = Modifier, contentDescription: String = "", ) { @@ -75,7 +75,7 @@ fun MonsterImagePreview() = HunterTheme { url = "asdasdas", backgroundColor = "#ffe3ee", contentDescription = "Anything", - challengeRating = 18f, + challengeRating = "18", iconRes = R.drawable.ic_aberration ) } @@ -87,7 +87,7 @@ fun MonsterImageBlackBackgroundPreview() = HunterTheme { url = "asdasdas", backgroundColor = "#000000", contentDescription = "Anything", - challengeRating = 18f, + challengeRating = "18", iconRes = R.drawable.ic_aberration ) } diff --git a/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/MonterCompendium.kt b/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/MonterCompendium.kt index 70d7b7919..e500c2eae 100644 --- a/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/MonterCompendium.kt +++ b/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/MonterCompendium.kt @@ -71,7 +71,7 @@ private fun MonsterCompendiumPreview() = Window { val imageState = MonsterImageState( url = "", type = MonsterTypeState.ABERRATION, - challengeRating = 8.0f, + challengeRating = "8", backgroundColor = ColorState( light = "#ffe0e0", dark = "#ffe0e0" diff --git a/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/State.kt b/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/State.kt index 5d7e06f72..df4c2e424 100644 --- a/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/State.kt +++ b/ui/monster-compendium/src/main/java/br/alexandregpereira/hunter/ui/compendium/monster/State.kt @@ -29,7 +29,7 @@ data class MonsterImageState( val url: String, val type: MonsterTypeState, val backgroundColor: ColorState, - val challengeRating: Float, + val challengeRating: String, val isHorizontal: Boolean = false, val contentDescription: String = "" )