diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d3e802ef..cb30a1d0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -102,6 +102,7 @@ java { dependencies { implementation(project(":core:analytics")) + implementation(project(":core:event")) implementation(project(":domain:app:data")) implementation(project(":domain:app:core")) implementation(project(":feature:folder-detail:event")) diff --git a/app/src/main/kotlin/br/alexandregpereira/hunter/app/HunterApplication.kt b/app/src/main/kotlin/br/alexandregpereira/hunter/app/HunterApplication.kt index ae2c2305..32c076a7 100644 --- a/app/src/main/kotlin/br/alexandregpereira/hunter/app/HunterApplication.kt +++ b/app/src/main/kotlin/br/alexandregpereira/hunter/app/HunterApplication.kt @@ -21,6 +21,7 @@ import br.alexandregpereira.hunter.analytics.di.analyticsModule import br.alexandregpereira.hunter.data.di.dataModules import br.alexandregpereira.hunter.detail.di.monsterDetailModule import br.alexandregpereira.hunter.domain.di.domainModules +import br.alexandregpereira.hunter.event.systembar.bottomBarEventModule import br.alexandregpereira.hunter.folder.detail.di.folderDetailModule import br.alexandregpereira.hunter.folder.insert.di.folderInsertModule import br.alexandregpereira.hunter.folder.list.di.folderListModule @@ -66,7 +67,7 @@ class HunterApplication : Application() { internal companion object { private val appModule = module { factory { Dispatchers.Default } - viewModel { MainViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { MainViewModel(get(), get(), get(), get(), get(), get(), get()) } } fun KoinApplication.initKoinModules() { @@ -90,6 +91,7 @@ class HunterApplication : Application() { searchModule, settingsModule, spellDetailModule, + bottomBarEventModule ) } } diff --git a/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewModel.kt b/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewModel.kt index 1523f212..4569eeeb 100644 --- a/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewModel.kt +++ b/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewModel.kt @@ -28,6 +28,11 @@ import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEvent.OnVis import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEvent.OnVisibilityChanges.Show import br.alexandregpereira.hunter.event.monster.detail.MonsterDetailEventListener import br.alexandregpereira.hunter.event.monster.detail.collectOnVisibilityChanges +import br.alexandregpereira.hunter.event.systembar.BottomBarEvent.AddTopContent +import br.alexandregpereira.hunter.event.systembar.BottomBarEvent.RemoveTopContent +import br.alexandregpereira.hunter.event.systembar.BottomBarEventManager +import br.alexandregpereira.hunter.event.systembar.dispatchRemoveTopContentEvent +import br.alexandregpereira.hunter.event.systembar.dispatchAddTopContentEvent import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher import br.alexandregpereira.hunter.monster.content.event.MonsterContentManagerEventListener @@ -35,6 +40,7 @@ import br.alexandregpereira.hunter.monster.content.event.collectOnVisibilityChan import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class MainViewModel( private val savedStateHandle: SavedStateHandle, @@ -43,50 +49,69 @@ class MainViewModel( private val folderListResultListener: FolderListResultListener, private val monsterContentManagerEventListener: MonsterContentManagerEventListener, private val folderPreviewEventDispatcher: FolderPreviewEventDispatcher, + private val bottomBarEventManager: BottomBarEventManager, ) : ViewModel() { private val _state = MutableStateFlow(savedStateHandle.getState()) val state: StateFlow = _state init { + observeBottomBarEvents() observeMonsterDetailEvents() observeFolderDetailResults() observeFolderListResults() observeMonsterContentManagerEvents() } + private fun observeBottomBarEvents() { + bottomBarEventManager.events.onEach { event -> + when (event) { + is AddTopContent -> setState { addTopContentStack(event.topContentId) } + is RemoveTopContent -> setState { removeTopContentStack(event.topContentId) } + } + }.launchIn(viewModelScope) + } + private fun observeMonsterDetailEvents() { monsterDetailEventListener.collectOnVisibilityChanges { event -> when (event) { - is Show -> setState { copy(isMonsterDetailShowing = true) } - Hide -> setState { copy(isMonsterDetailShowing = false) } + is Show -> { + dispatchAddTopContentEvent(TopContent.MONSTER_DETAIL.name) + dispatchFolderPreviewEvent(show = false) + } + Hide -> { + dispatchRemoveTopContentEvent(TopContent.MONSTER_DETAIL.name) + dispatchFolderPreviewEvent(show = true) + } } - dispatchFolderPreviewEvent(show = state.value.isMonsterDetailShowing.not()) }.launchIn(viewModelScope) } private fun observeMonsterContentManagerEvents() { monsterContentManagerEventListener.collectOnVisibilityChanges { isShowing -> - setState { copy(isMonsterContentManagerShowing = isShowing) } + dispatchTopContentEvent(TopContent.MONSTER_CONTENT_MANAGER.name, isShowing) }.launchIn(viewModelScope) } private fun observeFolderDetailResults() { folderDetailResultListener.collectOnVisibilityChanges { result -> - setState { copy(isFolderDetailShowing = result.isShowing) } + dispatchTopContentEvent(TopContent.FOLDER_DETAIL.name, result.isShowing) }.launchIn(viewModelScope) } private fun observeFolderListResults() { folderListResultListener.collectOnItemSelectionVisibilityChanges { result -> - setState { copy(isFolderListSelectionShowing = result.isShowing) } + dispatchTopContentEvent(TopContent.FOLDER_LIST_SELECTION.name, result.isShowing) dispatchFolderPreviewEvent(result.isShowing.not()) }.launchIn(viewModelScope) } fun onEvent(event: MainViewEvent) { when (event) { - is BottomNavigationItemClick -> setState { copy(bottomBarItemSelected = event.item) } + is BottomNavigationItemClick -> { + setState { copy(bottomBarItemSelected = event.item) } + dispatchFolderPreviewEvent(show = event.item != BottomBarItem.SETTINGS) + } } } @@ -99,7 +124,30 @@ class MainViewModel( folderPreviewEventDispatcher.dispatchEvent(folderPreviewEvent) } + private fun dispatchAddTopContentEvent(topContentId: String) { + bottomBarEventManager.dispatchAddTopContentEvent(topContentId) + } + + private fun dispatchRemoveTopContentEvent(topContentId: String) { + bottomBarEventManager.dispatchRemoveTopContentEvent(topContentId) + } + + private fun dispatchTopContentEvent(topContentId: String, show: Boolean) { + if (show) { + dispatchAddTopContentEvent(topContentId) + } else { + dispatchRemoveTopContentEvent(topContentId) + } + } + private fun setState(block: MainViewState.() -> MainViewState) { _state.value = state.value.block().saveState(savedStateHandle) } } + +private enum class TopContent { + MONSTER_DETAIL, + FOLDER_DETAIL, + MONSTER_CONTENT_MANAGER, + FOLDER_LIST_SELECTION, +} diff --git a/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewState.kt b/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewState.kt index 10b9f2ac..e8dba8c1 100644 --- a/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewState.kt +++ b/app/src/main/kotlin/br/alexandregpereira/hunter/app/MainViewState.kt @@ -20,29 +20,40 @@ import androidx.lifecycle.SavedStateHandle data class MainViewState( val bottomBarItemSelected: BottomBarItem = BottomBarItem.COMPENDIUM, - val isFolderDetailShowing: Boolean = false, - val isMonsterDetailShowing: Boolean = false, - val isMonsterContentManagerShowing: Boolean = false, - val isFolderListSelectionShowing: Boolean = false, + internal val topContentStack: List = listOf(), ) { - val showBottomBar: Boolean = isMonsterDetailShowing.not() && isFolderDetailShowing.not() - && isFolderListSelectionShowing.not() && isMonsterContentManagerShowing.not() + + val showBottomBar: Boolean = topContentStack.isEmpty() +} + +internal fun MainViewState.addTopContentStack( + topContent: String, +): MainViewState { + return copy(topContentStack = topContentStack + topContent) +} + +internal fun MainViewState.removeTopContentStack( + topContent: String, +): MainViewState { + return copy( + topContentStack = topContentStack.toMutableList().apply { + remove(topContent) + }.toList() + ) } enum class BottomBarItem(val iconRes: Int, val stringRes: Int) { COMPENDIUM(iconRes = R.drawable.ic_book, stringRes = R.string.compendium), SEARCH(iconRes = R.drawable.ic_search, stringRes = R.string.search), FOLDERS(iconRes = R.drawable.ic_folder, stringRes = R.string.folders), - SETTINGS(iconRes = R.drawable.ic_settings, stringRes = R.string.settings) + SETTINGS(iconRes = R.drawable.ic_menu, stringRes = R.string.menu) } internal fun SavedStateHandle.getState(): MainViewState { return MainViewState( - bottomBarItemSelected = BottomBarItem.values()[this["bottomBarItemSelected"] ?: 0], - isFolderDetailShowing = this["isFolderDetailShowing"] ?: false, - isMonsterDetailShowing = this["isMonsterDetailShowing"] ?: false, - isFolderListSelectionShowing = this["isFolderListSelectionShowing"] ?: false, - isMonsterContentManagerShowing = this["isMonsterContentManagerShowing"] ?: false, + bottomBarItemSelected = BottomBarItem.entries[this["bottomBarItemSelected"] ?: 0], + topContentStack = this.get>("topContentStack")?.toMutableList() + ?: mutableListOf(), ) } @@ -50,9 +61,6 @@ internal fun MainViewState.saveState( savedStateHandle: SavedStateHandle ): MainViewState { savedStateHandle["bottomBarItemSelected"] = this.bottomBarItemSelected.ordinal - savedStateHandle["isFolderDetailShowing"] = this.isFolderDetailShowing - savedStateHandle["isMonsterDetailShowing"] = this.isMonsterDetailShowing - savedStateHandle["isFolderListSelectionShowing"] = this.isFolderListSelectionShowing - savedStateHandle["isMonsterContentManagerShowing"] = this.isMonsterContentManagerShowing + savedStateHandle["topContentStack"] = this.topContentStack.toTypedArray() return this } diff --git a/app/src/main/kotlin/br/alexandregpereira/hunter/app/ui/BottomNavigationTransition.kt b/app/src/main/kotlin/br/alexandregpereira/hunter/app/ui/BottomNavigationTransition.kt index 308a8e35..add3dd9d 100644 --- a/app/src/main/kotlin/br/alexandregpereira/hunter/app/ui/BottomNavigationTransition.kt +++ b/app/src/main/kotlin/br/alexandregpereira/hunter/app/ui/BottomNavigationTransition.kt @@ -31,7 +31,7 @@ fun BottomNavigationTransition( bottomBarItemSelected: BottomBarItem, contentPadding: PaddingValues = PaddingValues() ) { - Crossfade(targetState = bottomBarItemSelected) { index -> + Crossfade(targetState = bottomBarItemSelected, label = "BottomNavigationTransition") { index -> when (index) { BottomBarItem.COMPENDIUM -> MonsterCompendiumFeature( contentPadding = contentPadding, diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 00000000..543cee9e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml deleted file mode 100644 index 798d952a..00000000 --- a/app/src/main/res/drawable/ic_settings.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ba5cc077..3cd02246 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -19,5 +19,5 @@ CompĂȘndio Buscar Pastas - Config + Menu diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67068de1..f5cfcf6f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,5 +19,5 @@ Compendium Search Folders - Settings + Menu \ No newline at end of file diff --git a/core/event/build.gradle.kts b/core/event/build.gradle.kts new file mode 100644 index 00000000..1968537a --- /dev/null +++ b/core/event/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + kotlin("multiplatform") +} + +configureJvmTargets() + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation(libs.koin.core) + implementation(libs.kotlin.coroutines.core) + } + } + if (isMac()) { + val iosMain by getting + } + } +} diff --git a/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/Event.kt b/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/Event.kt new file mode 100644 index 00000000..8159ced5 --- /dev/null +++ b/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/Event.kt @@ -0,0 +1,30 @@ +package br.alexandregpereira.hunter.event + +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +interface EventDispatcher { + + fun dispatchEvent(event: Event) +} + +interface EventListener { + + val events: Flow +} + +interface EventManager : EventDispatcher, EventListener + +internal class DefaultEventManager : EventManager { + + private val _events: MutableSharedFlow = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + override val events: Flow = _events + + override fun dispatchEvent(event: Event) { + _events.tryEmit(event) + } +} \ No newline at end of file diff --git a/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/systembar/BottomBarEvent.kt b/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/systembar/BottomBarEvent.kt new file mode 100644 index 00000000..5c707a43 --- /dev/null +++ b/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/systembar/BottomBarEvent.kt @@ -0,0 +1,22 @@ +package br.alexandregpereira.hunter.event.systembar + +import br.alexandregpereira.hunter.event.DefaultEventManager +import br.alexandregpereira.hunter.event.EventDispatcher +import br.alexandregpereira.hunter.event.EventManager + +class BottomBarEventManager : EventManager by DefaultEventManager() + +sealed class BottomBarEvent { + + data class AddTopContent(val topContentId: String) : BottomBarEvent() + + data class RemoveTopContent(val topContentId: String) : BottomBarEvent() +} + +fun EventDispatcher.dispatchAddTopContentEvent(topContentId: String) { + dispatchEvent(BottomBarEvent.AddTopContent(topContentId)) +} + +fun EventDispatcher.dispatchRemoveTopContentEvent(topContentId: String) { + dispatchEvent(BottomBarEvent.RemoveTopContent(topContentId)) +} diff --git a/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/systembar/BottomBarEventModule.kt b/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/systembar/BottomBarEventModule.kt new file mode 100644 index 00000000..0abc9381 --- /dev/null +++ b/core/event/src/commonMain/kotlin/br/alexandregpereira/hunter/event/systembar/BottomBarEventModule.kt @@ -0,0 +1,15 @@ +package br.alexandregpereira.hunter.event.systembar + +import br.alexandregpereira.hunter.event.EventDispatcher +import br.alexandregpereira.hunter.event.EventListener +import org.koin.dsl.module + +val bottomBarEventModule = module { + single { BottomBarEventManager() } + factory> { + get() + } + factory> { + get() + } +} diff --git a/feature/settings/android/build.gradle b/feature/settings/android/build.gradle index 24e43853..486369fd 100644 --- a/feature/settings/android/build.gradle +++ b/feature/settings/android/build.gradle @@ -32,6 +32,7 @@ java { dependencies { implementation project(':core:analytics') + implementation project(':core:event') implementation project(':domain:settings:core') implementation project(':feature:monster-content-manager:event') implementation project(':feature:sync:event') diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsFeature.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsFeature.kt index 550ee553..8be3fa95 100644 --- a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsFeature.kt +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsFeature.kt @@ -29,14 +29,10 @@ fun SettingsFeature( contentPadding: PaddingValues, ) { val viewModel: SettingsViewModel = koinViewModel() - val context = LocalContext.current SettingsScreen( state = viewModel.state.collectAsState().value, versionName = versionName, contentPadding = contentPadding, - onImageBaseUrlChange = viewModel::onImageBaseUrlChange, - onAlternativeSourceBaseUrlChange = viewModel::onAlternativeSourceBaseUrlChange, - onSaveButtonClick = viewModel::onSaveButtonClick, - onManageMonsterContentClick = viewModel::onManageMonsterContentClick, + viewIntent = viewModel, ) } diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt new file mode 100644 index 00000000..70dac26c --- /dev/null +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt @@ -0,0 +1,30 @@ +package br.alexandregpereira.hunter.settings + +internal interface SettingsViewIntent { + + fun onImageBaseUrlChange(value: String) + + fun onAlternativeSourceBaseUrlChange(value: String) + + fun onSaveButtonClick() + + fun onManageMonsterContentClick() + + fun onAdvancedSettingsClick() + fun onAdvancedSettingsCloseClick() +} + +internal class EmptySettingsViewIntent : SettingsViewIntent { + + override fun onImageBaseUrlChange(value: String) {} + + override fun onAlternativeSourceBaseUrlChange(value: String) {} + + override fun onSaveButtonClick() {} + + override fun onManageMonsterContentClick() {} + + override fun onAdvancedSettingsClick() {} + + override fun onAdvancedSettingsCloseClick() {} +} \ No newline at end of file diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewModel.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewModel.kt index 96758935..006620ff 100644 --- a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewModel.kt +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewModel.kt @@ -21,9 +21,12 @@ import androidx.lifecycle.viewModelScope import br.alexandregpereira.hunter.domain.settings.GetAlternativeSourceJsonUrlUseCase import br.alexandregpereira.hunter.domain.settings.GetMonsterImageJsonUrlUseCase import br.alexandregpereira.hunter.domain.settings.SaveUrlsUseCase +import br.alexandregpereira.hunter.event.EventDispatcher +import br.alexandregpereira.hunter.event.systembar.BottomBarEvent +import br.alexandregpereira.hunter.event.systembar.dispatchRemoveTopContentEvent +import br.alexandregpereira.hunter.event.systembar.dispatchAddTopContentEvent import br.alexandregpereira.hunter.monster.content.event.MonsterContentManagerEvent.Show import br.alexandregpereira.hunter.monster.content.event.MonsterContentManagerEventDispatcher -import br.alexandregpereira.hunter.sync.event.SyncEvent import br.alexandregpereira.hunter.sync.event.SyncEventDispatcher import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow @@ -42,7 +45,8 @@ internal class SettingsViewModel( private val dispatcher: CoroutineDispatcher, private val syncEventDispatcher: SyncEventDispatcher, private val analytics: SettingsAnalytics, -) : ViewModel() { + private val bottomBarEventDispatcher: EventDispatcher, +) : ViewModel(), SettingsViewIntent { private val _state = MutableStateFlow(SettingsViewState()) val state: StateFlow = _state @@ -51,31 +55,39 @@ internal class SettingsViewModel( load() } - fun onImageBaseUrlChange(value: String) { + override fun onImageBaseUrlChange(value: String) { _state.value = state.value.copy(imageBaseUrl = value) } - fun onAlternativeSourceBaseUrlChange(value: String) { + override fun onAlternativeSourceBaseUrlChange(value: String) { _state.value = state.value.copy(alternativeSourceBaseUrl = value) } - fun onSaveButtonClick() { + override fun onSaveButtonClick() { analytics.trackSaveButtonClick(state.value) saveUrls( imageBaseUrl = state.value.imageBaseUrl, alternativeSourceBaseUrl = state.value.alternativeSourceBaseUrl ).flowOn(dispatcher) .onEach { + closeAdvancedSettings() syncEventDispatcher.startSync() } .launchIn(viewModelScope) } - fun onManageMonsterContentClick() { + override fun onManageMonsterContentClick() { analytics.trackManageMonsterContentClick() monsterContentManagerEventDispatcher.dispatchEvent(Show) } + override fun onAdvancedSettingsClick() { + _state.value = state.value.copy(advancedSettingsOpened = true) + bottomBarEventDispatcher.dispatchAddTopContentEvent(topContentId = ADVANCED_SETTINGS_CONTENT) + } + + override fun onAdvancedSettingsCloseClick() = closeAdvancedSettings() + private fun load() { getMonsterImageJsonUrl() .zip(getAlternativeSourceJsonUrl()) { imageBaseUrl, alternativeSourceBaseUrl -> @@ -94,4 +106,13 @@ internal class SettingsViewModel( } .launchIn(viewModelScope) } + + private fun closeAdvancedSettings() { + _state.value = state.value.copy(advancedSettingsOpened = false) + bottomBarEventDispatcher.dispatchRemoveTopContentEvent(topContentId = ADVANCED_SETTINGS_CONTENT) + } + + private companion object { + private const val ADVANCED_SETTINGS_CONTENT = "AdvancedSettings" + } } diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewState.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewState.kt index 600cda34..a0807d71 100644 --- a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewState.kt +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/SettingsViewState.kt @@ -20,4 +20,5 @@ internal data class SettingsViewState( val imageBaseUrl: String = "", val alternativeSourceBaseUrl: String = "", val saveButtonEnabled: Boolean = true, + val advancedSettingsOpened: Boolean = false, ) diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/di/Module.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/di/Module.kt index 0437540b..07225d9b 100644 --- a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/di/Module.kt +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/di/Module.kt @@ -23,6 +23,15 @@ import org.koin.dsl.module val settingsModule = module { viewModel { - SettingsViewModel(get(), get(), get(), get(), get(), get(), analytics = SettingsAnalytics(get())) + SettingsViewModel( + get(), + get(), + get(), + get(), + get(), + get(), + analytics = SettingsAnalytics(get()), + bottomBarEventDispatcher = get(), + ) } } diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/Settings.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/AdvancedSettings.kt similarity index 92% rename from feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/Settings.kt rename to feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/AdvancedSettings.kt index 3a78404e..ca8ace52 100644 --- a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/Settings.kt +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/AdvancedSettings.kt @@ -17,13 +17,12 @@ package br.alexandregpereira.hunter.settings.ui import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -33,7 +32,7 @@ import br.alexandregpereira.hunter.ui.compose.AppButton import br.alexandregpereira.hunter.ui.compose.Window @Composable -fun Settings( +fun AdvancedSettings( imageBaseUrl: String, alternativeSourceBaseUrl: String, modifier: Modifier = Modifier, @@ -41,7 +40,10 @@ fun Settings( onImageBaseUrlChange: (String) -> Unit = {}, onAlternativeSourceBaseUrlChange: (String) -> Unit = {}, onSaveButtonClick: () -> Unit = {} -) = Column(modifier.padding(16.dp)) { +) = Column( + modifier + .padding(16.dp) +) { Text( text = stringResource(R.string.settings_additional_content), @@ -86,8 +88,8 @@ fun Settings( @Preview @Composable -private fun SettingsPreview() = Window { - Settings( +private fun AdvancedSettingsPreview() = Window { + AdvancedSettings( imageBaseUrl = "", alternativeSourceBaseUrl = "" ) diff --git a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/SettingsScreen.kt b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/SettingsScreen.kt index 7eb725de..77200acb 100644 --- a/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/SettingsScreen.kt +++ b/feature/settings/android/src/main/java/br/alexandregpereira/hunter/settings/ui/SettingsScreen.kt @@ -33,8 +33,11 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import br.alexandregpereira.hunter.settings.EmptySettingsViewIntent import br.alexandregpereira.hunter.settings.R +import br.alexandregpereira.hunter.settings.SettingsViewIntent import br.alexandregpereira.hunter.settings.SettingsViewState +import br.alexandregpereira.hunter.ui.compose.BottomSheet import br.alexandregpereira.hunter.ui.compose.Window @Composable @@ -42,41 +45,24 @@ internal fun SettingsScreen( state: SettingsViewState, versionName: String, contentPadding: PaddingValues = PaddingValues(), - onImageBaseUrlChange: (String) -> Unit = {}, - onAlternativeSourceBaseUrlChange: (String) -> Unit = {}, - onSaveButtonClick: () -> Unit = {}, - onManageMonsterContentClick: () -> Unit = {}, + viewIntent: SettingsViewIntent = EmptySettingsViewIntent(), ) = Window { - Box(modifier = Modifier.fillMaxSize()) { - Column { - Settings( - imageBaseUrl = state.imageBaseUrl, - alternativeSourceBaseUrl = state.alternativeSourceBaseUrl, - saveButtonEnabled = state.saveButtonEnabled, - onImageBaseUrlChange = onImageBaseUrlChange, - onAlternativeSourceBaseUrlChange = onAlternativeSourceBaseUrlChange, - onSaveButtonClick = onSaveButtonClick, - modifier = Modifier.padding( - top = contentPadding.calculateTopPadding(), - bottom = 16.dp - ) + Box( + modifier = Modifier.fillMaxSize() + ) { + Column(Modifier.padding(contentPadding)) { + + SettingsItem( + text = "Advanced Settings", + onClick = viewIntent::onAdvancedSettingsClick ) Divider() - Box( - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = onManageMonsterContentClick) - ) { - Text( - text = stringResource(R.string.settings_manage_monster_content), - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - modifier = Modifier - .padding(16.dp) - ) - } + SettingsItem( + text = stringResource(R.string.settings_manage_monster_content), + onClick = viewIntent::onManageMonsterContentClick + ) } Text( @@ -88,6 +74,41 @@ internal fun SettingsScreen( .padding(contentPadding) .padding(8.dp) ) + + BottomSheet( + opened = state.advancedSettingsOpened, + onClose = viewIntent::onAdvancedSettingsCloseClick, + contentPadding = contentPadding + ) { + AdvancedSettings( + imageBaseUrl = state.imageBaseUrl, + alternativeSourceBaseUrl = state.alternativeSourceBaseUrl, + saveButtonEnabled = state.saveButtonEnabled, + onImageBaseUrlChange = viewIntent::onImageBaseUrlChange, + onAlternativeSourceBaseUrlChange = viewIntent::onAlternativeSourceBaseUrlChange, + onSaveButtonClick = viewIntent::onSaveButtonClick, + ) + } + } +} + +@Composable +private fun SettingsItem( + text: String, + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + ) { + Text( + text = text, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + modifier = Modifier + .padding(16.dp) + ) } } diff --git a/settings.gradle b/settings.gradle index f5195382..acccce43 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,7 @@ rootProject.name = 'hunter' include ':app' include ':core:analytics' +include ':core:event' include ':core:state-holder' include ':domain:app:core'