diff --git a/androidApp/dependencies/demoDebugRuntimeClasspath.txt b/androidApp/dependencies/demoDebugRuntimeClasspath.txt index c5a0dba25..6bc5ee945 100644 --- a/androidApp/dependencies/demoDebugRuntimeClasspath.txt +++ b/androidApp/dependencies/demoDebugRuntimeClasspath.txt @@ -180,6 +180,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.9.1 diff --git a/androidApp/dependencies/demoReleaseRuntimeClasspath.txt b/androidApp/dependencies/demoReleaseRuntimeClasspath.txt index aa400c17a..830fb760e 100644 --- a/androidApp/dependencies/demoReleaseRuntimeClasspath.txt +++ b/androidApp/dependencies/demoReleaseRuntimeClasspath.txt @@ -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: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.9.1 diff --git a/androidApp/dependencies/prodDebugRuntimeClasspath.txt b/androidApp/dependencies/prodDebugRuntimeClasspath.txt index c5a0dba25..6bc5ee945 100644 --- a/androidApp/dependencies/prodDebugRuntimeClasspath.txt +++ b/androidApp/dependencies/prodDebugRuntimeClasspath.txt @@ -180,6 +180,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.9.1 diff --git a/androidApp/dependencies/prodReleaseRuntimeClasspath.txt b/androidApp/dependencies/prodReleaseRuntimeClasspath.txt index aa400c17a..830fb760e 100644 --- a/androidApp/dependencies/prodReleaseRuntimeClasspath.txt +++ b/androidApp/dependencies/prodReleaseRuntimeClasspath.txt @@ -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: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.9.1 diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts index d2ac74110..5ee9ca798 100644 --- a/core/datastore/build.gradle.kts +++ b/core/datastore/build.gradle.kts @@ -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 { @@ -22,9 +22,17 @@ 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) + implementation(projects.core.common) + } + } +} - -} \ No newline at end of file diff --git a/core/datastore/src/main/AndroidManifest.xml b/core/datastore/src/androidMain/AndroidManifest.xml similarity index 100% rename from core/datastore/src/main/AndroidManifest.xml rename to core/datastore/src/androidMain/AndroidManifest.xml diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt new file mode 100644 index 000000000..12cd51e1f --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt @@ -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, + ) +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepository.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepository.kt new file mode 100644 index 000000000..8ffb4ee89 --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepository.kt @@ -0,0 +1,43 @@ +/* + * 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.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import org.mifos.mobile.core.datastore.model.AppSettings +import org.mifos.mobile.core.datastore.model.AppTheme +import org.mifos.mobile.core.datastore.model.UserData +import org.mifospay.core.common.DataState + +interface UserPreferencesRepository { + val userInfo: Flow + + val settingsInfo: Flow + + val token: StateFlow + + val clientId: StateFlow + + val appTheme: StateFlow + + val profileImage: String? + + suspend fun updateToken(token: String): DataState + + suspend fun updateTheme(theme: AppTheme): DataState + + suspend fun updateUser(user: UserData): DataState + + suspend fun updateSettings(appSettings: AppSettings): DataState + + suspend fun updateProfileImage(image: String): DataState + + suspend fun logOut(): Unit +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepositoryImpl.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepositoryImpl.kt new file mode 100644 index 000000000..accc4750c --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepositoryImpl.kt @@ -0,0 +1,107 @@ +/* + * 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 +import org.mifospay.core.common.DataState + +class UserPreferencesRepositoryImpl( + private val preferenceManager: UserPreferencesDataSource, + private val ioDispatcher: CoroutineDispatcher, + unconfinedDispatcher: CoroutineDispatcher, +) : UserPreferencesRepository { + private val unconfinedScope = CoroutineScope(unconfinedDispatcher) + + override val userInfo: Flow + get() = preferenceManager.userInfo + + override val settingsInfo: Flow + get() = preferenceManager.settingsInfo + + override val appTheme: StateFlow + get() = preferenceManager.appTheme.stateIn( + scope = unconfinedScope, + initialValue = null, + started = SharingStarted.Eagerly, + ) + override val token: StateFlow + get() = preferenceManager.token.stateIn( + scope = unconfinedScope, + initialValue = null, + started = SharingStarted.Eagerly, + ) + + override val clientId: StateFlow + 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 { + return try { + val result = preferenceManager.updateToken(token) + DataState.Success(result) + } catch (e: Exception) { + DataState.Error(e) + } + } + + override suspend fun updateTheme(theme: AppTheme): DataState { + return try { + val result = preferenceManager.updateTheme(theme) + DataState.Success(result) + } catch (e: Exception) { + DataState.Error(e) + } + } + + override suspend fun updateUser(user: UserData): DataState { + return try { + val result = preferenceManager.updateUserInfo(user) + DataState.Success(result) + } catch (e: Exception) { + DataState.Error(e) + } + } + + override suspend fun updateSettings(appSettings: AppSettings): DataState { + return try { + val result = preferenceManager.updateSettingsInfo(appSettings) + DataState.Success(result) + } catch (e: Exception) { + DataState.Error(e) + } + } + + override suspend fun updateProfileImage(image: String): DataState { + return try { + val result = preferenceManager.updateProfileImage(image) + DataState.Success(result) + } catch (e: Exception) { + DataState.Error(e) + } + } + + override suspend fun logOut() { + preferenceManager.clearInfo() + } +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/di/PreferenceModule.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/di/PreferenceModule.kt new file mode 100644 index 000000000..fcaa14d14 --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/di/PreferenceModule.kt @@ -0,0 +1,43 @@ +/* + * 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.di + +import com.russhwolf.settings.Settings +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.mifos.mobile.core.datastore.UserPreferencesDataSource +import org.mifos.mobile.core.datastore.UserPreferencesRepository +import org.mifos.mobile.core.datastore.UserPreferencesRepositoryImpl + +val PreferencesModule = module { + factory { Settings() } + + factory { + UserPreferencesDataSource( + settings = get(), + dispatcher = get(named(MifosDispatchers.IO.name)), + ) + } + + single { + UserPreferencesRepositoryImpl( + preferenceManager = get(), + ioDispatcher = get(named(MifosDispatchers.IO.name)), + unconfinedDispatcher = get(named(MifosDispatchers.Unconfined.name)), + ) + } +} + +// Should be removed after common module conversion +enum class MifosDispatchers { + Default, + IO, + Unconfined, +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/AppSettings.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/AppSettings.kt new file mode 100644 index 000000000..c8eaf0e3e --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/AppSettings.kt @@ -0,0 +1,28 @@ +/* + * 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.model + +import kotlinx.serialization.Serializable + +@Serializable +data class AppSettings( + val tenant: String, + val baseUrl: String, + val passcode: String? = null, + val appTheme: AppTheme = AppTheme.SYSTEM, +) { + companion object { + val DEFAULT = AppSettings( + tenant = "default", + baseUrl = "https://gsoc.mifos.community/", + appTheme = AppTheme.SYSTEM, + ) + } +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/AppTheme.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/AppTheme.kt new file mode 100644 index 000000000..c2e1ee93f --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/AppTheme.kt @@ -0,0 +1,18 @@ +/* + * 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.model + +enum class AppTheme( + val themeName: String, +) { + SYSTEM(themeName = "System Theme"), + LIGHT(themeName = "Light Theme"), + DARK(themeName = "Dark Theme"), +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/MifosAppLanguage.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/MifosAppLanguage.kt new file mode 100644 index 000000000..18b96b9f8 --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/MifosAppLanguage.kt @@ -0,0 +1,42 @@ +/* + * 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.model + +enum class MifosAppLanguage(val code: String, val displayName: String) { + SYSTEM_LANGUAGE("System_Language", "System Language"), + ENGLISH("en", "English"), + HINDI("hi", "हिंदी"), + ARABIC("ar", "عربى"), + URDU("ur", "اُردُو"), + BENGALI("bn", "বাঙালি"), + SPANISH("es", "Español"), + FRENCH("fr", "français"), + INDONESIAN("in", "bahasa Indonesia"), + KHMER("km", "ភាសាខ្មែរ"), + KANNADA("kn", "ಕನ್ನಡ"), + TELUGU("te", "తెలుగు"), + BURMESE("my", "မြန်မာ"), + POLISH("pl", "Polski"), + PORTUGUESE("pt", "Português"), + RUSSIAN("ru", "русский"), + SWAHILI("sw", "Kiswahili"), + FARSI("fa", "فارسی"), + ; + + companion object { + fun fromCode(code: String): MifosAppLanguage { + return entries.find { it.code.equals(code, ignoreCase = true) } ?: ENGLISH + } + } + + override fun toString(): String { + return displayName + } +} diff --git a/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/UserData.kt b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/UserData.kt new file mode 100644 index 000000000..59123f47d --- /dev/null +++ b/core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/model/UserData.kt @@ -0,0 +1,31 @@ +/* + * 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.model + +import kotlinx.serialization.Serializable + +@Serializable +data class UserData( + val userId: Long, + val userName: String, + val clientId: Long, + val isAuthenticated: Boolean, + val base64EncodedAuthenticationKey: String, +) { + companion object { + val DEFAULT = UserData( + userId = -1, + userName = "", + clientId = -1, + isAuthenticated = false, + base64EncodedAuthenticationKey = "", + ) + } +} diff --git a/core/datastore/src/main/kotlin/org/mifos/mobile/core/datastore/PreferencesHelper.kt b/core/datastore/src/main/kotlin/org/mifos/mobile/core/datastore/PreferencesHelper.kt deleted file mode 100644 index 54428fc84..000000000 --- a/core/datastore/src/main/kotlin/org/mifos/mobile/core/datastore/PreferencesHelper.kt +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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 android.content.Context -import android.content.SharedPreferences -import android.preference.PreferenceManager -import android.text.TextUtils -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.buffer -import kotlinx.coroutines.flow.callbackFlow -import org.mifos.mobile.core.model.enums.AppTheme -import org.mifos.mobile.core.model.enums.MifosAppLanguage -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class PreferencesHelper @Inject constructor(@ApplicationContext context: Context?) { - private val sharedPreferences: SharedPreferences? = - PreferenceManager.getDefaultSharedPreferences(context) - - fun clear() { - val editor = sharedPreferences?.edit() - // prevent deletion of url and tenant - sharedPreferences?.all?.keys?.forEach { key -> - if (key != BASE_URL && key != TENANT) { - editor?.remove(key) - } - } - editor?.apply() - } - - fun getInt(preferenceKey: String?, preferenceDefaultValue: Int): Int? { - return sharedPreferences?.getInt(preferenceKey, preferenceDefaultValue) - } - - fun putInt(preferenceKey: String?, preferenceValue: Int) { - sharedPreferences?.edit()?.putInt(preferenceKey, preferenceValue)?.apply() - } - - fun getLong(preferenceKey: String?, preferenceDefaultValue: Long): Long? { - return sharedPreferences?.getLong(preferenceKey, preferenceDefaultValue) - } - - fun putLong(preferenceKey: String?, preferenceValue: Long) { - sharedPreferences?.edit()?.putLong(preferenceKey, preferenceValue)?.apply() - } - - fun getString(preferenceKey: String?, preferenceDefaultValue: String?): String? { - return sharedPreferences?.getString(preferenceKey, preferenceDefaultValue) - } - - private fun putString(preferenceKey: String?, preferenceValue: String?) { - sharedPreferences?.edit()?.putString(preferenceKey, preferenceValue)?.apply() - } - - fun putBoolean(preferenceKey: String?, preferenceValue: Boolean) { - sharedPreferences?.edit()?.putBoolean(preferenceKey, preferenceValue)?.apply() - } - - fun getBoolean(preferenceKey: String?, preferenceDefaultValue: Boolean): Boolean? { - return sharedPreferences?.getBoolean(preferenceKey, preferenceDefaultValue) - } - - fun saveToken(token: String?) { - putString(TOKEN, token) - } - - fun clearToken() { - putString(TOKEN, "") - } - - val token: String? - get() = getString(TOKEN, "") - - val isAuthenticated: Boolean - get() = !TextUtils.isEmpty(token) - - var userId: Long? - get() = getLong(USER_ID, -1) - set(id) { - id?.let { - putLong(USER_ID, it) - } - } - - var tenant: String? - get() = getString(TENANT, DEFAULT_TENANT) - set(tenant) { - putString(TENANT, tenant) - } - - var passcode: String? - get() = getString(PASSCODE, "") - set(passcode) { - putString(PASSCODE, passcode) - } - - var clientId: Long? - get() = getLong(CLIENT_ID, -1) - set(clientId) { - clientId?.let { - putLong(CLIENT_ID, it) - } - } - - var userName: String? - get() = getString(USER_NAME, "") - set(userName) { - putString(USER_NAME, userName) - } - - var clientName: String? - get() = getString(CLIENT_NAME, "") - set(clientName) { - putString(CLIENT_NAME, clientName) - } - - var officeName: String? - get() = getString(OFFICE_NAME, "") - set(officeName) { - putString(OFFICE_NAME, officeName) - } - - fun setOverviewState(state: Boolean) { - putBoolean(OVERVIEW_STATE, state) - } - - fun overviewState(): Boolean? { - return getBoolean(OVERVIEW_STATE, true) - } - - fun saveGcmToken(token: String?) { - putString(GCM_TOKEN, token) - } - - var userProfileImage: String? - get() = getString(PROFILE_IMAGE, null) - set(image) { - putString(PROFILE_IMAGE, image) - } - - val gcmToken: String? - get() = getString(GCM_TOKEN, "") - - fun setSentTokenToServer(sentTokenToServer: Boolean) { - putBoolean(SENT_TOKEN_TO_SERVER, sentTokenToServer) - } - - fun sentTokenToServerState(): Boolean? { - return getBoolean(SENT_TOKEN_TO_SERVER, false) - } - - fun updateConfiguration(baseUrl: String?, tenant: String?) { - sharedPreferences?.edit() - ?.putString(BASE_URL, baseUrl) - ?.putString(TENANT, tenant) - ?.apply() - } - - val baseUrl: String? - get() = getString(BASE_URL, DEFAULT_BASE_URL) - - var appTheme - get() = getInt(APPLICATION_THEME, AppTheme.SYSTEM.ordinal) ?: AppTheme.SYSTEM.ordinal - set(value) { - putInt(APPLICATION_THEME, value) - } - - var language - get() = getString(LANGUAGE_TYPE, MifosAppLanguage.ENGLISH.code) - ?: MifosAppLanguage.SYSTEM_LANGUAGE.code - set(language) { - putString(LANGUAGE_TYPE, language) - } - - var isDefaultSystemLanguage - get() = getBoolean(DEFAULT_SYSTEM_LANGUAGE, false) == true - set(value) { - putBoolean(DEFAULT_SYSTEM_LANGUAGE, value) - } - - companion object { - private const val USER_ID = "preferences_user_id" - private const val TOKEN = "preferences_token" - private const val CLIENT_ID = "preferences_client" - private const val OFFICE_NAME = "preferences_office_name" - private const val USER_NAME = "preferences_user_name" - const val PASSCODE = "preferences_passcode" - private const val OVERVIEW_STATE = "preferences_overview_state" - private const val SENT_TOKEN_TO_SERVER = "sentTokenToServer" - private const val GCM_TOKEN = "gcm_token" - const val TENANT = "preferences_base_tenant" - const val BASE_URL = "preferences_base_url_key" - private const val PROFILE_IMAGE = "preferences_profile_image" - const val CLIENT_NAME = "client_name" - const val APPLICATION_THEME = "application_theme" - const val LANGUAGE_TYPE = "language_type" - const val DEFAULT_SYSTEM_LANGUAGE = "default_system_language" - - private const val DEFAULT_TENANT = "default" - private const val DEFAULT_BASE_URL = "https://demo.mifos.community" - } - - fun getStringFlowForKey(keyForString: String) = callbackFlow { - val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> - if (keyForString == key) { - trySend(getString(keyForString, null)) - } - } - sharedPreferences?.registerOnSharedPreferenceChangeListener(listener) - send(getString(keyForString, null)) - awaitClose { sharedPreferences?.unregisterOnSharedPreferenceChangeListener(listener) } - }.buffer(Channel.Factory.UNLIMITED) - - fun getIntFlowForKey(keyForInt: String) = callbackFlow { - val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> - if (keyForInt == key) { - trySend(getInt(keyForInt, -1)) - } - } - sharedPreferences?.registerOnSharedPreferenceChangeListener(listener) - send(getInt(keyForInt, -1)) - awaitClose { sharedPreferences?.unregisterOnSharedPreferenceChangeListener(listener) } - }.buffer(Channel.Factory.UNLIMITED) -}