chore(architecture): introduce MVI architecture
This commit is contained in:
parent
0772d6bf00
commit
e56784050a
54
core-architecture/build.gradle.kts
Normal file
54
core-architecture/build.gradle.kts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.multiplatform)
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
alias(libs.plugins.compose)
|
||||||
|
alias(libs.plugins.native.cocoapods)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
|
||||||
|
kotlin {
|
||||||
|
targetHierarchy.default()
|
||||||
|
|
||||||
|
android {
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iosX64()
|
||||||
|
iosArm64()
|
||||||
|
iosSimulatorArm64()
|
||||||
|
|
||||||
|
cocoapods {
|
||||||
|
summary = "Some description for the Shared Module"
|
||||||
|
homepage = "Link to the Shared Module homepage"
|
||||||
|
version = "1.0"
|
||||||
|
ios.deploymentTarget = "14.1"
|
||||||
|
framework {
|
||||||
|
baseName = "core-architecture"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(compose.runtime)
|
||||||
|
implementation(compose.foundation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val commonTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.github.diegoberaldin.raccoonforlemmy.core_architecture"
|
||||||
|
compileSdk = 33
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
}
|
42
core-architecture/core-architecture.podspec
Normal file
42
core-architecture/core-architecture.podspec
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
Pod::Spec.new do |spec|
|
||||||
|
spec.name = 'core-architecture'
|
||||||
|
spec.version = '1.0'
|
||||||
|
spec.homepage = 'Link to the Shared Module homepage'
|
||||||
|
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
|
||||||
|
spec.authors = ''
|
||||||
|
spec.license = ''
|
||||||
|
spec.summary = 'Some description for the Shared Module'
|
||||||
|
|
||||||
|
spec.vendored_frameworks = "build/cocoapods/framework/core-architecture.framework"
|
||||||
|
spec.libraries = "c++"
|
||||||
|
spec.module_name = "#{spec.name}_umbrella"
|
||||||
|
|
||||||
|
spec.ios.deployment_target = '14.1'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
spec.pod_target_xcconfig = {
|
||||||
|
'KOTLIN_PROJECT_PATH' => ':core-architecture',
|
||||||
|
'PRODUCT_MODULE_NAME' => 'core-architecture',
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.script_phases = [
|
||||||
|
{
|
||||||
|
:name => 'Build core-architecture',
|
||||||
|
:execution_position => :before_compile,
|
||||||
|
:shell_path => '/bin/sh',
|
||||||
|
:script => <<-SCRIPT
|
||||||
|
if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
|
||||||
|
echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
set -ev
|
||||||
|
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||||
|
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||||
|
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||||
|
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||||
|
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
|
||||||
|
SCRIPT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
39
core-architecture/core_architecture.podspec
Normal file
39
core-architecture/core_architecture.podspec
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
Pod::Spec.new do |spec|
|
||||||
|
spec.name = 'core_architecture'
|
||||||
|
spec.version = '1.0'
|
||||||
|
spec.homepage = 'Link to the Shared Module homepage'
|
||||||
|
spec.source = { :http=> ''}
|
||||||
|
spec.authors = ''
|
||||||
|
spec.license = ''
|
||||||
|
spec.summary = 'Some description for the Shared Module'
|
||||||
|
spec.vendored_frameworks = 'build/cocoapods/framework/core-architecture.framework'
|
||||||
|
spec.libraries = 'c++'
|
||||||
|
spec.ios.deployment_target = '14.1'
|
||||||
|
|
||||||
|
|
||||||
|
spec.pod_target_xcconfig = {
|
||||||
|
'KOTLIN_PROJECT_PATH' => ':core-architecture',
|
||||||
|
'PRODUCT_MODULE_NAME' => 'core-architecture',
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.script_phases = [
|
||||||
|
{
|
||||||
|
:name => 'Build core_architecture',
|
||||||
|
:execution_position => :before_compile,
|
||||||
|
:shell_path => '/bin/sh',
|
||||||
|
:script => <<-SCRIPT
|
||||||
|
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
||||||
|
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
set -ev
|
||||||
|
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
||||||
|
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||||
|
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||||
|
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||||
|
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
||||||
|
SCRIPT
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
end
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core_architecture
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic implementation of the MVI model. This is useful to easily implement the interface by delegation,
|
||||||
|
* minimizing the amount of code that is needed when integrating the MVI pattern.
|
||||||
|
* The [updateState] and [emitEffect] methods are shortcuts to easily update the UI state and emit a side effect.
|
||||||
|
*
|
||||||
|
* @param Intent class of intents
|
||||||
|
* @param State class of UI state
|
||||||
|
* @param Effect class of effects
|
||||||
|
* @constructor Create [DefaultMviModel]
|
||||||
|
*
|
||||||
|
* @param initialState initial UI state
|
||||||
|
*/
|
||||||
|
class DefaultMviModel<Intent, State, Effect>(
|
||||||
|
initialState: State,
|
||||||
|
) : MviModel<Intent, State, Effect> {
|
||||||
|
|
||||||
|
override val uiState = MutableStateFlow(initialState)
|
||||||
|
override val effects = MutableSharedFlow<Effect>()
|
||||||
|
lateinit var scope: CoroutineScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an effect (event).
|
||||||
|
*
|
||||||
|
* @param value Value
|
||||||
|
*/
|
||||||
|
suspend fun emitEffect(value: Effect) {
|
||||||
|
effects.emit(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the UI state.
|
||||||
|
*
|
||||||
|
* @param block Block
|
||||||
|
*/
|
||||||
|
inline fun updateState(block: (State) -> State) {
|
||||||
|
uiState.update { block(uiState.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reduce(intent: Intent) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStarted() {
|
||||||
|
scope = CoroutineScope(SupervisorJob())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisposed() {
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core_architecture
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model contract for Model-View-Intent architecture.
|
||||||
|
*/
|
||||||
|
interface MviModel<Intent, State, Effect> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of the state holder's state for the view to consume.
|
||||||
|
*/
|
||||||
|
val uiState: StateFlow<State>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-shot events generated by the state holder.
|
||||||
|
*/
|
||||||
|
val effects: SharedFlow<Effect>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce a view intent updating the [uiState] accordingly.
|
||||||
|
*
|
||||||
|
* @param intent View intent to process
|
||||||
|
*/
|
||||||
|
fun reduce(intent: Intent)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be called whenever the view component becomes visible to start listening events,
|
||||||
|
* initialize the coroutine scope, etc.
|
||||||
|
*/
|
||||||
|
fun onStarted()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be called wheneer the view component is not visible any more to cancel ongoing operations.
|
||||||
|
*/
|
||||||
|
fun onDisposed()
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core_architecture
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MviModel<*, *, *>.bindToLifecycle(key: Any = Unit) {
|
||||||
|
DisposableEffect(key) {
|
||||||
|
onStarted()
|
||||||
|
onDispose(::onDisposed)
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,7 @@ kotlin {
|
|||||||
implementation(libs.voyager.tab)
|
implementation(libs.voyager.tab)
|
||||||
|
|
||||||
implementation(projects.resources)
|
implementation(projects.resources)
|
||||||
|
implementation(projects.coreArchitecture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
|
actual val homeTabModule = module {
|
||||||
|
factory {
|
||||||
|
HomeScreenModel(
|
||||||
|
mvi = DefaultMviModel(HomeScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getHomeScreenModel(): HomeScreenModel {
|
||||||
|
val res: HomeScreenModel by inject(HomeScreenModel::class.java)
|
||||||
|
return res
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
|
||||||
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
|
||||||
|
|
||||||
actual fun getHomeScreenModel(): HomeScreenModel {
|
|
||||||
val res: HomeScreenModel by inject(HomeScreenModel::class.java)
|
|
||||||
return res
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
||||||
|
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val homeTabModule = module {
|
expect val homeTabModule: Module
|
||||||
factoryOf(::HomeScreenModel)
|
|
||||||
}
|
expect fun getHomeScreenModel(): HomeScreenModel
|
@ -1,9 +1,11 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
class HomeScreenModel : ScreenModel {
|
class HomeScreenModel(
|
||||||
|
private val mvi: DefaultMviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect>,
|
||||||
|
) : ScreenModel,
|
||||||
|
MviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect> by mvi {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect fun getHomeScreenModel(): HomeScreenModel
|
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
|
interface HomeScreenMviModel :
|
||||||
|
MviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect> {
|
||||||
|
|
||||||
|
sealed interface Intent
|
||||||
|
|
||||||
|
data class UiState(
|
||||||
|
val loading: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -6,9 +6,6 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
@ -16,8 +13,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.tab.Tab
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.getLanguageRepository
|
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
object HomeTab : Tab {
|
object HomeTab : Tab {
|
||||||
@ -40,6 +37,8 @@ object HomeTab : Tab {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val model = rememberScreenModel { getHomeScreenModel() }
|
val model = rememberScreenModel { getHomeScreenModel() }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(4.dp)) {
|
Column(modifier = Modifier.padding(4.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Posts content"
|
text = "Posts content"
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
actual val homeTabModule = module {
|
||||||
|
factory {
|
||||||
|
HomeScreenModel(
|
||||||
|
mvi = DefaultMviModel(HomeScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual fun getHomeScreenModel() = HomeScreenModelHelper.model
|
actual fun getHomeScreenModel() = HomeScreenModelHelper.model
|
||||||
|
|
@ -45,6 +45,7 @@ kotlin {
|
|||||||
implementation(libs.voyager.tab)
|
implementation(libs.voyager.tab)
|
||||||
|
|
||||||
implementation(projects.resources)
|
implementation(projects.resources)
|
||||||
|
implementation(projects.coreArchitecture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
|
actual val inboxTabModule = module {
|
||||||
|
factory {
|
||||||
|
InboxScreenModel(
|
||||||
|
mvi = DefaultMviModel(InboxScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getInboxScreenModel(): InboxScreenModel {
|
||||||
|
val res: InboxScreenModel by inject(InboxScreenModel::class.java)
|
||||||
|
return res
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
|
||||||
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
|
||||||
|
|
||||||
actual fun getInboxScreenModel(): InboxScreenModel {
|
|
||||||
val res: InboxScreenModel by inject(InboxScreenModel::class.java)
|
|
||||||
return res
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
||||||
|
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val inboxTabModule = module {
|
expect val inboxTabModule: Module
|
||||||
factoryOf(::InboxScreenModel)
|
|
||||||
}
|
expect fun getInboxScreenModel(): InboxScreenModel
|
@ -1,9 +1,12 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
class InboxScreenModel : ScreenModel {
|
class InboxScreenModel(
|
||||||
|
private val mvi: DefaultMviModel<InboxScreenMviModel.Intent, InboxScreenMviModel.UiState, InboxScreenMviModel.Effect>,
|
||||||
|
) : ScreenModel,
|
||||||
|
MviModel<InboxScreenMviModel.Intent, InboxScreenMviModel.UiState, InboxScreenMviModel.Effect> by mvi {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect fun getInboxScreenModel(): InboxScreenModel
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
|
interface InboxScreenMviModel :
|
||||||
|
MviModel<InboxScreenMviModel.Intent, InboxScreenMviModel.UiState, InboxScreenMviModel.Effect> {
|
||||||
|
|
||||||
|
sealed interface Intent
|
||||||
|
|
||||||
|
data class UiState(val loading: Boolean = false)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.tab.Tab
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
@ -36,6 +37,8 @@ object InboxTab : Tab {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val model = rememberScreenModel { getInboxScreenModel() }
|
val model = rememberScreenModel { getInboxScreenModel() }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(4.dp)) {
|
Column(modifier = Modifier.padding(4.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Inbox content"
|
text = "Inbox content"
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
actual val inboxTabModule = module {
|
||||||
|
factory {
|
||||||
|
InboxScreenModel(
|
||||||
|
mvi = DefaultMviModel(InboxScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual fun getInboxScreenModel() = InboxScreenModelHelper.model
|
actual fun getInboxScreenModel() = InboxScreenModelHelper.model
|
||||||
|
|
@ -45,6 +45,7 @@ kotlin {
|
|||||||
implementation(libs.voyager.tab)
|
implementation(libs.voyager.tab)
|
||||||
|
|
||||||
implementation(projects.resources)
|
implementation(projects.resources)
|
||||||
|
implementation(projects.coreArchitecture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
|
actual val profileTabModule = module {
|
||||||
|
factory {
|
||||||
|
ProfileScreenModel(
|
||||||
|
mvi = DefaultMviModel(ProfileScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getProfileScreenModel(): ProfileScreenModel {
|
||||||
|
val res: ProfileScreenModel by inject(ProfileScreenModel::class.java)
|
||||||
|
return res
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
|
||||||
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
|
||||||
|
|
||||||
actual fun getProfileScreenModel(): ProfileScreenModel {
|
|
||||||
val res: ProfileScreenModel by inject(ProfileScreenModel::class.java)
|
|
||||||
return res
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
||||||
|
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val profileTabModule = module {
|
expect val profileTabModule: Module
|
||||||
factoryOf(::ProfileScreenModel)
|
|
||||||
}
|
expect fun getProfileScreenModel(): ProfileScreenModel
|
@ -1,9 +1,12 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
class ProfileScreenModel : ScreenModel {
|
class ProfileScreenModel(
|
||||||
|
private val mvi: DefaultMviModel<ProfileScreenMviModel.Intent, ProfileScreenMviModel.UiState, ProfileScreenMviModel.Effect>,
|
||||||
|
) : ScreenModel,
|
||||||
|
MviModel<ProfileScreenMviModel.Intent, ProfileScreenMviModel.UiState, ProfileScreenMviModel.Effect> by mvi {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect fun getProfileScreenModel(): ProfileScreenModel
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
|
interface ProfileScreenMviModel :
|
||||||
|
MviModel<ProfileScreenMviModel.Intent, ProfileScreenMviModel.UiState, ProfileScreenMviModel.Effect> {
|
||||||
|
|
||||||
|
sealed interface Intent
|
||||||
|
|
||||||
|
data class UiState(val loading: Boolean = false)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.tab.Tab
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
@ -36,6 +37,8 @@ object ProfileTab : Tab {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val model = rememberScreenModel { getProfileScreenModel() }
|
val model = rememberScreenModel { getProfileScreenModel() }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(4.dp)) {
|
Column(modifier = Modifier.padding(4.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Profile content"
|
text = "Profile content"
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
actual val profileTabModule = module {
|
||||||
|
factory {
|
||||||
|
ProfileScreenModel(
|
||||||
|
mvi = DefaultMviModel(ProfileScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual fun getProfileScreenModel() = ProfileScreenModelHelper.model
|
actual fun getProfileScreenModel() = ProfileScreenModelHelper.model
|
||||||
|
|
@ -45,6 +45,7 @@ kotlin {
|
|||||||
implementation(libs.voyager.tab)
|
implementation(libs.voyager.tab)
|
||||||
|
|
||||||
implementation(projects.resources)
|
implementation(projects.resources)
|
||||||
|
implementation(projects.coreArchitecture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
|
actual val searchTabModule = module {
|
||||||
|
factory {
|
||||||
|
SearchScreenModel(
|
||||||
|
mvi = DefaultMviModel(SearchScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getSearchScreenModel(): SearchScreenModel {
|
||||||
|
val res: SearchScreenModel by inject(SearchScreenModel::class.java)
|
||||||
|
return res
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
|
||||||
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
|
||||||
|
|
||||||
actual fun getSearchScreenModel(): SearchScreenModel {
|
|
||||||
val res: SearchScreenModel by inject(SearchScreenModel::class.java)
|
|
||||||
return res
|
|
||||||
}
|
|
@ -1,8 +1,9 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
||||||
|
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
|
||||||
|
expect val searchTabModule: Module
|
||||||
|
|
||||||
|
expect fun getSearchScreenModel(): SearchScreenModel
|
||||||
|
|
||||||
|
|
||||||
val searchTabModule = module {
|
|
||||||
factoryOf(::SearchScreenModel)
|
|
||||||
}
|
|
@ -1,9 +1,12 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
class SearchScreenModel : ScreenModel {
|
class SearchScreenModel(
|
||||||
|
private val mvi: DefaultMviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect>,
|
||||||
|
) : ScreenModel,
|
||||||
|
MviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect> by mvi {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect fun getSearchScreenModel(): SearchScreenModel
|
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
|
interface SearchScreenMviModel :
|
||||||
|
MviModel<SearchScreenMviModel.Intent, SearchScreenMviModel.UiState, SearchScreenMviModel.Effect> {
|
||||||
|
sealed interface Intent
|
||||||
|
|
||||||
|
data class UiState(val loading: Boolean = false)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.tab.Tab
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
@ -36,6 +37,8 @@ object SearchTab : Tab {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val model = rememberScreenModel { getSearchScreenModel() }
|
val model = rememberScreenModel { getSearchScreenModel() }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(4.dp)) {
|
Column(modifier = Modifier.padding(4.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Search content"
|
text = "Search content"
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
package com.github.diegoberaldin.raccoonforlemmy.feature_search
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
actual val searchTabModule = module {
|
||||||
|
factory {
|
||||||
|
SearchScreenModel(
|
||||||
|
mvi = DefaultMviModel(SearchScreenMviModel.UiState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual fun getSearchScreenModel() = SearchScreenModelHelper.model
|
actual fun getSearchScreenModel() = SearchScreenModelHelper.model
|
||||||
|
|
@ -46,7 +46,7 @@ kotlin {
|
|||||||
|
|
||||||
implementation(projects.coreAppearance)
|
implementation(projects.coreAppearance)
|
||||||
implementation(projects.corePreferences)
|
implementation(projects.corePreferences)
|
||||||
|
implementation(projects.coreArchitecture)
|
||||||
implementation(projects.resources)
|
implementation(projects.resources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.repository.ThemeRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.java.KoinJavaComponent.get
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
|
actual val settingsTabModule = module {
|
||||||
|
factory {
|
||||||
|
SettingsScreenModel(
|
||||||
|
themeRepository = get(ThemeRepository::class.java),
|
||||||
|
keyStore = get(TemporaryKeyStore::class.java),
|
||||||
|
mvi = DefaultMviModel(
|
||||||
|
SettingsScreenMviModel.UiState()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getSettingsScreenModel(): SettingsScreenModel {
|
||||||
|
val res: SettingsScreenModel by inject(SettingsScreenModel::class.java)
|
||||||
|
return res
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
|
||||||
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
|
||||||
|
|
||||||
actual fun getSettingsScreenModel(): SettingsScreenModel {
|
|
||||||
val res: SettingsScreenModel by inject(SettingsScreenModel::class.java)
|
|
||||||
return res
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
||||||
|
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val settingsTabModule = module {
|
expect val settingsTabModule: Module
|
||||||
factoryOf(::SettingsScreenModel)
|
|
||||||
}
|
expect fun getSettingsScreenModel(): SettingsScreenModel
|
||||||
|
@ -3,13 +3,10 @@ package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
|||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.data.ThemeState
|
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.data.ThemeState
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.repository.ThemeRepository
|
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.repository.ThemeRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.KeyStoreKeys
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.KeyStoreKeys
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.getAndUpdate
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -17,29 +14,31 @@ import kotlinx.coroutines.launch
|
|||||||
class SettingsScreenModel(
|
class SettingsScreenModel(
|
||||||
private val themeRepository: ThemeRepository,
|
private val themeRepository: ThemeRepository,
|
||||||
private val keyStore: TemporaryKeyStore,
|
private val keyStore: TemporaryKeyStore,
|
||||||
) : ScreenModel {
|
private val mvi: DefaultMviModel<SettingsScreenMviModel.Intent, SettingsScreenMviModel.UiState, SettingsScreenMviModel.Effect>,
|
||||||
private val _uiState = MutableStateFlow(SettingsScreenUiState())
|
) : ScreenModel,
|
||||||
val uiState = _uiState.asStateFlow()
|
MviModel<SettingsScreenMviModel.Intent, SettingsScreenMviModel.UiState, SettingsScreenMviModel.Effect> by mvi {
|
||||||
|
|
||||||
val scope = CoroutineScope(SupervisorJob())
|
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
|
override fun onStarted() {
|
||||||
|
mvi.onStarted()
|
||||||
themeRepository.state.onEach {
|
themeRepository.state.onEach {
|
||||||
val isDarkTheme = when (themeRepository.state.value) {
|
val isDarkTheme = when (themeRepository.state.value) {
|
||||||
ThemeState.Dark -> true
|
ThemeState.Dark -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
_uiState.getAndUpdate { it.copy(darkTheme = isDarkTheme) }
|
mvi.updateState { it.copy(darkTheme = isDarkTheme) }
|
||||||
}.launchIn(scope)// TODO: is this running forever?
|
}.launchIn(mvi.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDarkTheme(value: Boolean) {
|
override fun reduce(intent: SettingsScreenMviModel.Intent) {
|
||||||
|
when (intent) {
|
||||||
|
is SettingsScreenMviModel.Intent.EnableDarkMode -> setDarkTheme(intent.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDarkTheme(value: Boolean) {
|
||||||
themeRepository.changeTheme(if (value) ThemeState.Dark else ThemeState.Light)
|
themeRepository.changeTheme(if (value) ThemeState.Dark else ThemeState.Light)
|
||||||
scope.launch {
|
mvi.scope.launch {
|
||||||
keyStore.save(KeyStoreKeys.EnableDarkTheme, value)
|
keyStore.save(KeyStoreKeys.EnableDarkTheme, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expect fun getSettingsScreenModel(): SettingsScreenModel
|
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.MviModel
|
||||||
|
|
||||||
|
interface SettingsScreenMviModel :
|
||||||
|
MviModel<SettingsScreenMviModel.Intent, SettingsScreenMviModel.UiState, SettingsScreenMviModel.Effect> {
|
||||||
|
|
||||||
|
sealed interface Intent {
|
||||||
|
data class EnableDarkMode(val value: Boolean) : Intent
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UiState(
|
||||||
|
val darkTheme: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface Effect
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
|
||||||
|
|
||||||
data class SettingsScreenUiState(
|
|
||||||
val darkTheme: Boolean = false,
|
|
||||||
)
|
|
@ -19,8 +19,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.tab.Tab
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.getLanguageRepository
|
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
object SettingsTab : Tab {
|
object SettingsTab : Tab {
|
||||||
@ -43,6 +43,8 @@ object SettingsTab : Tab {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val model = rememberScreenModel { getSettingsScreenModel() }
|
val model = rememberScreenModel { getSettingsScreenModel() }
|
||||||
|
model.bindToLifecycle(key)
|
||||||
|
|
||||||
val uiState by model.uiState.collectAsState()
|
val uiState by model.uiState.collectAsState()
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(4.dp)) {
|
Column(modifier = Modifier.padding(4.dp)) {
|
||||||
@ -56,7 +58,7 @@ object SettingsTab : Tab {
|
|||||||
Checkbox(
|
Checkbox(
|
||||||
checked = uiState.darkTheme,
|
checked = uiState.darkTheme,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
model.setDarkTheme(it)
|
model.reduce(SettingsScreenMviModel.Intent.EnableDarkMode(it))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
||||||
|
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
actual val settingsTabModule = module {
|
||||||
|
factory {
|
||||||
|
SettingsScreenModel(
|
||||||
|
themeRepository = get(),
|
||||||
|
keyStore = get(),
|
||||||
|
mvi = DefaultMviModel(
|
||||||
|
SettingsScreenMviModel.UiState()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getSettingsScreenModel() = SettingsScreenModelHelper.model
|
||||||
|
|
||||||
|
object SettingsScreenModelHelper : KoinComponent {
|
||||||
|
val model: SettingsScreenModel by inject()
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
|
||||||
|
|
||||||
import org.koin.core.component.KoinComponent
|
|
||||||
import org.koin.core.component.inject
|
|
||||||
|
|
||||||
actual fun getSettingsScreenModel() = SettingsScreenModelHelper.model
|
|
||||||
|
|
||||||
object SettingsScreenModelHelper : KoinComponent {
|
|
||||||
val model: SettingsScreenModel by inject()
|
|
||||||
}
|
|
@ -31,3 +31,4 @@ include(":core-utils")
|
|||||||
include(":core-appearance")
|
include(":core-appearance")
|
||||||
include(":core-preferences")
|
include(":core-preferences")
|
||||||
include(":resources")
|
include(":resources")
|
||||||
|
include(":core-architecture")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user