Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions androidApp/dependencies/demoDebugRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android-debug:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android-debug:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
Expand Down
8 changes: 8 additions & 0 deletions androidApp/dependencies/demoReleaseRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
Expand Down
8 changes: 8 additions & 0 deletions androidApp/dependencies/prodDebugRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android-debug:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android-debug:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
Expand Down
8 changes: 8 additions & 0 deletions androidApp/dependencies/prodReleaseRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
Expand Down
23 changes: 15 additions & 8 deletions core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/

plugins {
alias(libs.plugins.mifos.android.library)
alias(libs.plugins.mifos.android.hilt)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.mifos.kmp.library)
id("kotlinx-serialization")
}

android {
Expand All @@ -22,9 +22,16 @@ android {

}

dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
kotlin{

sourceSets{
commonMain.dependencies {
implementation(libs.multiplatform.settings)
implementation(libs.multiplatform.settings.serialization)
implementation(libs.multiplatform.settings.coroutines)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.core)
}
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore

// Should be deleted once common module is migrated
sealed class DataState<out T> {
abstract val data: T?

data object Loading : DataState<Nothing>() {
override val data: Nothing? get() = null
}

data class Success<T>(
override val data: T,
) : DataState<T>()

data class Error<T>(
val exception: Throwable,
override val data: T? = null,
) : DataState<T>() {
val message = exception.message.toString()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import org.mifos.mobile.core.datastore.model.AppSettings
import org.mifos.mobile.core.datastore.model.AppTheme
import org.mifos.mobile.core.datastore.model.UserData

class PreferenceHelper(
private val preferenceManager: UserPreferencesDataSource,
private val ioDispatcher: CoroutineDispatcher,
unconfinedDispatcher: CoroutineDispatcher,
) : UserPreferencesRepository {
private val unconfinedScope = CoroutineScope(unconfinedDispatcher)

override val userInfo: Flow<UserData>
get() = preferenceManager.userInfo

override val settingsInfo: Flow<AppSettings>
get() = preferenceManager.settingsInfo

override val appTheme: StateFlow<AppTheme?>
get() = preferenceManager.appTheme.stateIn(
scope = unconfinedScope,
initialValue = null,
started = SharingStarted.Eagerly,
)
override val token: StateFlow<String?>
get() = preferenceManager.token.stateIn(
scope = unconfinedScope,
initialValue = null,
started = SharingStarted.Eagerly,
)

override val clientId: StateFlow<Long?>
get() = preferenceManager.clientId.stateIn(
scope = unconfinedScope,
initialValue = null,
started = SharingStarted.Eagerly,
)

override val profileImage: String?
get() = preferenceManager.getProfileImage()

override suspend fun updateToken(token: String): DataState<Unit> {
return try {
val result = preferenceManager.updateToken(token)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateTheme(theme: AppTheme): DataState<Unit> {
return try {
val result = preferenceManager.updateTheme(theme)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateUser(user: UserData): DataState<Unit> {
return try {
val result = preferenceManager.updateUserInfo(user)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateSettings(appSettings: AppSettings): DataState<Unit> {
return try {
val result = preferenceManager.updateSettingsInfo(appSettings)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateProfileImage(image: String): DataState<Unit> {
return try {
val result = preferenceManager.updateProfileImage(image)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun logOut() {
preferenceManager.clearInfo()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
@file:OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)

package org.mifos.mobile.core.datastore

import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.Settings
import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import org.mifos.mobile.core.datastore.model.AppSettings
import org.mifos.mobile.core.datastore.model.AppTheme
import org.mifos.mobile.core.datastore.model.UserData

private const val USER_DATA = "userData"
private const val APP_SETTINGS = "appSettings"

class UserPreferencesDataSource(
private val settings: Settings,
private val dispatcher: CoroutineDispatcher,
) {

private val _userInfo = MutableStateFlow(
settings.decodeValue(
key = USER_DATA,
serializer = UserData.serializer(),
defaultValue = settings.decodeValueOrNull(
key = USER_DATA,
serializer = UserData.serializer(),
) ?: UserData.DEFAULT,
),
)

private val _settingsInfo = MutableStateFlow(
settings.decodeValue(
key = APP_SETTINGS,
serializer = AppSettings.serializer(),
defaultValue = settings.decodeValueOrNull(
key = APP_SETTINGS,
serializer = AppSettings.serializer(),
) ?: AppSettings.DEFAULT,
),
)

val token = _userInfo.map {
it.base64EncodedAuthenticationKey
}

val userInfo = _userInfo

val settingsInfo = _settingsInfo

val clientId = _userInfo.map { it.clientId }

val appTheme = _settingsInfo.map { it.appTheme }

suspend fun updateSettingsInfo(appSettings: AppSettings) {
withContext(dispatcher) {
settings.putSettingsPreference(appSettings)
_settingsInfo.value = appSettings
}
}

suspend fun updateUserInfo(user: UserData) {
withContext(dispatcher) {
settings.putUserPreference(user)
_userInfo.value = user
}
}

suspend fun updateToken(token: String) {
withContext(dispatcher) {
settings.putUserPreference(
UserData.DEFAULT.copy(
base64EncodedAuthenticationKey = token,
),
)
_userInfo.value = UserData.DEFAULT.copy(
base64EncodedAuthenticationKey = token,
)
}
}

suspend fun updateTheme(theme: AppTheme) {
withContext(dispatcher) {
settings.putSettingsPreference(
AppSettings.DEFAULT.copy(
appTheme = theme,
),
)
_settingsInfo.value = AppSettings.DEFAULT.copy(
appTheme = theme,
)
}
}

fun updateProfileImage(image: String) {
settings.putString(PROFILE_IMAGE, image)
}

fun getProfileImage(): String? {
return settings.getString(PROFILE_IMAGE, "").ifEmpty { null }
}

suspend fun clearInfo() {
withContext(dispatcher) {
settings.clear()
}
}

companion object {
private const val PROFILE_IMAGE = "preferences_profile_image"
}
}

@OptIn(ExperimentalSerializationApi::class)
private fun Settings.putUserPreference(user: UserData) {
encodeValue(
key = USER_DATA,
serializer = UserData.serializer(),
value = user,
)
}

private fun Settings.putSettingsPreference(settings: AppSettings) {
encodeValue(
key = APP_SETTINGS,
serializer = AppSettings.serializer(),
value = settings,
)
}
Loading
Loading