mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-02 11:26:43 +01:00
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(projects.resources)
|
||||
implementation(projects.coreArchitecture)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
import org.koin.core.module.Module
|
||||
|
||||
val homeTabModule = module {
|
||||
factoryOf(::HomeScreenModel)
|
||||
}
|
||||
expect val homeTabModule: Module
|
||||
|
||||
expect fun getHomeScreenModel(): HomeScreenModel
|
@ -1,9 +1,11 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature_home
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
expect fun getHomeScreenModel(): HomeScreenModel
|
||||
class HomeScreenModel(
|
||||
private val mvi: DefaultMviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect>,
|
||||
) : ScreenModel,
|
||||
MviModel<HomeScreenMviModel.Intent, HomeScreenMviModel.UiState, HomeScreenMviModel.Effect> by mvi {
|
||||
}
|
@ -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.material3.Text
|
||||
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.ui.Modifier
|
||||
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.navigator.tab.Tab
|
||||
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.getLanguageRepository
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
object HomeTab : Tab {
|
||||
@ -40,6 +37,8 @@ object HomeTab : Tab {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getHomeScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Text(
|
||||
text = "Posts content"
|
||||
|
@ -1,7 +1,17 @@
|
||||
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.inject
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val homeTabModule = module {
|
||||
factory {
|
||||
HomeScreenModel(
|
||||
mvi = DefaultMviModel(HomeScreenMviModel.UiState())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getHomeScreenModel() = HomeScreenModelHelper.model
|
||||
|
@ -45,6 +45,7 @@ kotlin {
|
||||
implementation(libs.voyager.tab)
|
||||
|
||||
implementation(projects.resources)
|
||||
implementation(projects.coreArchitecture)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
import org.koin.core.module.Module
|
||||
|
||||
val inboxTabModule = module {
|
||||
factoryOf(::InboxScreenModel)
|
||||
}
|
||||
expect val inboxTabModule: Module
|
||||
|
||||
expect fun getInboxScreenModel(): InboxScreenModel
|
@ -1,9 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature_inbox
|
||||
|
||||
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.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@ -36,6 +37,8 @@ object InboxTab : Tab {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getInboxScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Text(
|
||||
text = "Inbox content"
|
||||
|
@ -1,7 +1,17 @@
|
||||
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.inject
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val inboxTabModule = module {
|
||||
factory {
|
||||
InboxScreenModel(
|
||||
mvi = DefaultMviModel(InboxScreenMviModel.UiState())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getInboxScreenModel() = InboxScreenModelHelper.model
|
||||
|
@ -45,6 +45,7 @@ kotlin {
|
||||
implementation(libs.voyager.tab)
|
||||
|
||||
implementation(projects.resources)
|
||||
implementation(projects.coreArchitecture)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
import org.koin.core.module.Module
|
||||
|
||||
val profileTabModule = module {
|
||||
factoryOf(::ProfileScreenModel)
|
||||
}
|
||||
expect val profileTabModule: Module
|
||||
|
||||
expect fun getProfileScreenModel(): ProfileScreenModel
|
@ -1,9 +1,12 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature_profile
|
||||
|
||||
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.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@ -36,6 +37,8 @@ object ProfileTab : Tab {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getProfileScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Text(
|
||||
text = "Profile content"
|
||||
|
@ -1,7 +1,17 @@
|
||||
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.inject
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val profileTabModule = module {
|
||||
factory {
|
||||
ProfileScreenModel(
|
||||
mvi = DefaultMviModel(ProfileScreenMviModel.UiState())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getProfileScreenModel() = ProfileScreenModelHelper.model
|
||||
|
@ -45,6 +45,7 @@ kotlin {
|
||||
implementation(libs.voyager.tab)
|
||||
|
||||
implementation(projects.resources)
|
||||
implementation(projects.coreArchitecture)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
import org.koin.core.module.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
|
||||
|
||||
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.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@ -36,6 +37,8 @@ object SearchTab : Tab {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getSearchScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Text(
|
||||
text = "Search content"
|
||||
|
@ -1,7 +1,17 @@
|
||||
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.inject
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val searchTabModule = module {
|
||||
factory {
|
||||
SearchScreenModel(
|
||||
mvi = DefaultMviModel(SearchScreenMviModel.UiState())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getSearchScreenModel() = SearchScreenModelHelper.model
|
||||
|
@ -46,7 +46,7 @@ kotlin {
|
||||
|
||||
implementation(projects.coreAppearance)
|
||||
implementation(projects.corePreferences)
|
||||
|
||||
implementation(projects.coreArchitecture)
|
||||
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
|
||||
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
import org.koin.core.module.Module
|
||||
|
||||
val settingsTabModule = module {
|
||||
factoryOf(::SettingsScreenModel)
|
||||
}
|
||||
expect val settingsTabModule: Module
|
||||
|
||||
expect fun getSettingsScreenModel(): SettingsScreenModel
|
||||
|
@ -3,13 +3,10 @@ package com.github.diegoberaldin.raccoonforlemmy.feature_settings
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.data.ThemeState
|
||||
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.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.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
@ -17,29 +14,31 @@ import kotlinx.coroutines.launch
|
||||
class SettingsScreenModel(
|
||||
private val themeRepository: ThemeRepository,
|
||||
private val keyStore: TemporaryKeyStore,
|
||||
) : ScreenModel {
|
||||
private val _uiState = MutableStateFlow(SettingsScreenUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
val scope = CoroutineScope(SupervisorJob())
|
||||
|
||||
init {
|
||||
private val mvi: DefaultMviModel<SettingsScreenMviModel.Intent, SettingsScreenMviModel.UiState, SettingsScreenMviModel.Effect>,
|
||||
) : ScreenModel,
|
||||
MviModel<SettingsScreenMviModel.Intent, SettingsScreenMviModel.UiState, SettingsScreenMviModel.Effect> by mvi {
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
themeRepository.state.onEach {
|
||||
val isDarkTheme = when (themeRepository.state.value) {
|
||||
ThemeState.Dark -> true
|
||||
else -> false
|
||||
}
|
||||
_uiState.getAndUpdate { it.copy(darkTheme = isDarkTheme) }
|
||||
}.launchIn(scope)// TODO: is this running forever?
|
||||
mvi.updateState { it.copy(darkTheme = isDarkTheme) }
|
||||
}.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)
|
||||
scope.launch {
|
||||
mvi.scope.launch {
|
||||
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.navigator.tab.Tab
|
||||
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.getLanguageRepository
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
object SettingsTab : Tab {
|
||||
@ -43,6 +43,8 @@ object SettingsTab : Tab {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getSettingsScreenModel() }
|
||||
model.bindToLifecycle(key)
|
||||
|
||||
val uiState by model.uiState.collectAsState()
|
||||
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
@ -56,7 +58,7 @@ object SettingsTab : Tab {
|
||||
Checkbox(
|
||||
checked = uiState.darkTheme,
|
||||
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-preferences")
|
||||
include(":resources")
|
||||
include(":core-architecture")
|
||||
|
Loading…
x
Reference in New Issue
Block a user