diff --git a/core/common/src/main/java/com/mifos/core/common/enums/MifosAppLanguage.kt b/core/common/src/main/java/com/mifos/core/common/enums/MifosAppLanguage.kt
new file mode 100644
index 00000000000..b3d0ec8c580
--- /dev/null
+++ b/core/common/src/main/java/com/mifos/core/common/enums/MifosAppLanguage.kt
@@ -0,0 +1,33 @@
+package com.mifos.core.common.enums
+
+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
+ }
+}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/mifos/core/common/utils/Constants.kt b/core/common/src/main/java/com/mifos/core/common/utils/Constants.kt
index 66dda6c354b..8a72a668f0b 100644
--- a/core/common/src/main/java/com/mifos/core/common/utils/Constants.kt
+++ b/core/common/src/main/java/com/mifos/core/common/utils/Constants.kt
@@ -204,4 +204,11 @@ object Constants {
const val CURR_PASSWORD = "currentPassword"
const val IS_TO_UPDATE_PASS_CODE = "updatePassCode"
const val HAS_SETTING_CHANGED = "hasSettingsChanged"
+
+
+ const val TENANT = "tenant"
+ const val BASE_URL = "base_url"
+ const val PASSCODE = "preferences_mifos_passcode_string"
+ const val THEME = "theme"
+ const val LANGUAGE = "language_type"
}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/mifos/core/common/utils/LanguageHelper.kt b/core/common/src/main/java/com/mifos/core/common/utils/LanguageHelper.kt
new file mode 100644
index 00000000000..9c757e5fc3a
--- /dev/null
+++ b/core/common/src/main/java/com/mifos/core/common/utils/LanguageHelper.kt
@@ -0,0 +1,72 @@
+package com.mifos.core.common.utils
+
+import android.content.Context
+import android.os.Build
+import android.preference.PreferenceManager
+import com.mifos.core.common.R
+import java.util.Locale
+
+object LanguageHelper {
+ // https://gunhansancar.com/change-language-programmatically-in-android/
+ fun onAttach(context: Context): Context? {
+ val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+ return if (preferences.getBoolean(
+ context.getString(R.string.core_common_default_system_language),
+ true
+ )
+ ) {
+ if (!context.resources.getStringArray(R.array.core_common_languages_value)
+ .contains(Locale.getDefault().language)
+ ) {
+ setLocale(context, "en")
+ } else {
+ setLocale(context, Locale.getDefault().language)
+ }
+ } else {
+ val lang = getPersistedData(context, Locale.getDefault().language)
+ lang?.let { setLocale(context, it) }
+ }
+ }
+
+ @JvmStatic
+ fun onAttach(context: Context, defaultLanguage: String): Context? {
+ val lang = getPersistedData(context, defaultLanguage)
+ return lang?.let { setLocale(context, it) }
+ }
+
+ fun setLocale(context: Context?, language: String): Context? {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ updateResources(context!!, language)
+ } else {
+ updateResourcesLegacy(context, language)
+ }
+ }
+
+ private fun getPersistedData(context: Context, defaultLanguage: String): String? {
+ val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+ return preferences.getString(
+ context.getString(R.string.core_common_language_type),
+ defaultLanguage
+ )
+ }
+
+ private fun updateResources(context: Context, language: String): Context {
+ val locale = Locale(language)
+ Locale.setDefault(locale)
+ val configuration = context.resources.configuration
+ configuration.setLocale(locale)
+ configuration.setLayoutDirection(locale)
+ return context.createConfigurationContext(configuration)
+ }
+
+ private fun updateResourcesLegacy(context: Context?, language: String): Context? {
+ val locale = Locale(language)
+ Locale.setDefault(locale)
+ val resources = context?.resources
+ val configuration = resources?.configuration
+ configuration?.locale = locale
+ configuration?.setLayoutDirection(locale)
+ resources?.updateConfiguration(configuration, resources.displayMetrics)
+ return context
+ }
+}
\ No newline at end of file
diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..b77f01ecbdb
--- /dev/null
+++ b/core/common/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+
+
+
+ default_system_language
+ language_type
+
+
+ - System_Language
+ - en
+ - hi
+ - ar
+ - ur
+ - bn
+ - es
+ - fr
+ - in
+ - km
+ - kn
+ - te
+ - my
+ - pl
+ - pt
+ - ru
+ - sw
+ - fa
+
+
+
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt b/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt
index 7943da69773..f7db33773b7 100644
--- a/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt
+++ b/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt
@@ -9,6 +9,8 @@ import com.mifos.core.common.utils.Constants
import com.mifos.core.common.utils.asServerConfig
import com.mifos.core.model.ServerConfig
import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
import org.apache.fineract.client.models.PostAuthenticationResponse
import org.mifos.core.sharedpreference.Key
import org.mifos.core.sharedpreference.UserPreferences
@@ -75,4 +77,12 @@ class PrefManager @Inject constructor(
fun updateServerConfig(config: ServerConfig?) {
this.put(serverConfigKey, config)
}
+
+ fun getStringValue(key: String): Flow = flow {
+ emit(preference.getString(key, ""))
+ }
+
+ fun setStringValue(key: String,value: String) {
+ preference.edit().putString(key, value).apply()
+ }
}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAlertDailog.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAlertDailog.kt
index 76ed83e847e..93cdea9c50b 100644
--- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAlertDailog.kt
+++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAlertDailog.kt
@@ -1,10 +1,33 @@
package com.mifos.core.designsystem.component
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Card
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.mifos.core.designsystem.R
@Composable
fun MifosDialogBox(
@@ -42,3 +65,116 @@ fun MifosDialogBox(
)
}
}
+
+
+@Composable
+fun MifosRadioButtonDialog(
+ titleResId: Int,
+ selectedItem: String,
+ items: Array,
+ selectItem: (item: String, index: Int) -> Unit,
+ onDismissRequest: () -> Unit,
+) {
+ Dialog(
+ onDismissRequest = { onDismissRequest.invoke() }
+ ) {
+ Card {
+ Column(modifier = Modifier.padding(20.dp)) {
+ Text(text = stringResource(id = titleResId))
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(max = 500.dp)
+ ) {
+ itemsIndexed(items = items) { index, item ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .clickable {
+ onDismissRequest.invoke()
+ selectItem.invoke(item, index)
+ }
+ .fillMaxWidth()
+ ) {
+ RadioButton(
+ selected = (item == selectedItem),
+ onClick = {
+ onDismissRequest.invoke()
+ selectItem.invoke(item, index)
+ }
+ )
+ Text(
+ text = item,
+ modifier = Modifier.padding(start = 4.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun UpdateEndpointDialogScreen(
+ initialBaseURL: String?,
+ initialTenant: String?,
+ onDismissRequest: () -> Unit,
+ handleEndpointUpdate: (baseURL: String, tenant: String) -> Unit
+) {
+ var baseURL by rememberSaveable { mutableStateOf(initialBaseURL) }
+ var tenant by rememberSaveable { mutableStateOf(initialTenant) }
+
+ Dialog(
+ onDismissRequest = { onDismissRequest.invoke() }
+ ) {
+ Card {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(20.dp),
+ ) {
+ Text(text = stringResource(id = R.string.core_designsystem_pref_base_url_title))
+ Spacer(modifier = Modifier.height(8.dp))
+
+ baseURL?.let {
+ OutlinedTextField(
+ value = it,
+ onValueChange = { baseURL = it },
+ label = { Text(text = stringResource(id = R.string.core_designsystem_enter_base_url)) }
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ tenant?.let {
+ OutlinedTextField(
+ value = it,
+ onValueChange = { tenant = it },
+ label = { Text(text = stringResource(id = R.string.core_designsystem_enter_tenant)) }
+ )
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ TextButton(
+ onClick = { onDismissRequest.invoke() }) {
+ Text(text = stringResource(id = R.string.core_designsystem_cancel))
+ }
+ TextButton(
+ onClick = {
+ if (baseURL != null && tenant != null) {
+ handleEndpointUpdate.invoke(baseURL ?: "", tenant ?: "")
+ }
+ }
+ )
+ {
+ Text(text = stringResource(id = R.string.core_designsystem_dialog_action_ok))
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/icon/MifosIcon.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/icon/MifosIcon.kt
index 5e25ba841b8..f8ad407fc9a 100644
--- a/core/designsystem/src/main/java/com/mifos/core/designsystem/icon/MifosIcon.kt
+++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/icon/MifosIcon.kt
@@ -8,16 +8,19 @@ import androidx.compose.material.icons.outlined.EventRepeat
import androidx.compose.material.icons.outlined.Group
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ArrowBackIosNew
+import androidx.compose.material.icons.rounded.Bedtime
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.FilterList
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.KeyboardArrowUp
+import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.PersonOutline
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.Sync
+import androidx.compose.material.icons.rounded.Translate
object MifosIcons {
val Add = Icons.Rounded.Add
@@ -37,4 +40,7 @@ object MifosIcons {
val moreVert = Icons.Rounded.MoreVert
val fileTask = Icons.Default.AssignmentTurnedIn
val cloudDownload = Icons.Default.CloudDownload
+ val password = Icons.Rounded.Lock
+ val theme = Icons.Rounded.Bedtime
+ val language = Icons.Rounded.Translate
}
\ No newline at end of file
diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml
index 6a600149d1d..8cc4762e160 100644
--- a/core/designsystem/src/main/res/values/strings.xml
+++ b/core/designsystem/src/main/res/values/strings.xml
@@ -2,4 +2,10 @@
Sorry we weren\'t able to load
Try Again
+ Please enter a base URL
+ Enter a base URL
+ Enter a tenant
+ Cancel
+ Save
+
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/mifos/core/ui/theme/Theme.kt b/core/ui/src/main/java/com/mifos/core/ui/theme/Theme.kt
new file mode 100644
index 00000000000..3f9c7cfd317
--- /dev/null
+++ b/core/ui/src/main/java/com/mifos/core/ui/theme/Theme.kt
@@ -0,0 +1,52 @@
+package com.mifos.core.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import com.mifos.core.designsystem.theme.Black
+import com.mifos.core.designsystem.theme.BluePrimary
+import com.mifos.core.designsystem.theme.BluePrimaryDark
+import com.mifos.core.designsystem.theme.BlueSecondary
+
+private val LightThemeColors = lightColorScheme(
+ primary = BluePrimary,
+ onPrimary = Color.White,
+ error = Color.Red,
+ background = Color.White,
+ onSurface = BlueSecondary,
+ onSecondary = Color.Gray,
+ outlineVariant = Color.Gray,
+ surfaceTint = BlueSecondary
+)
+
+private val DarkThemeColors = darkColorScheme(
+ primary = BluePrimaryDark,
+ onPrimary = Color.White,
+ secondary = Black,
+ error = Color.Red,
+ background = Color.Black,
+ surface = BlueSecondary,
+ onSurface = Color.White,
+ onSecondary = Color.White,
+ outlineVariant = Color.White,
+ surfaceTint = BlueSecondary
+)
+
+@Composable
+fun MifosTheme(
+ useDarkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit,
+) {
+ val colors = when {
+ useDarkTheme -> DarkThemeColors
+ else -> LightThemeColors
+ }
+
+ MaterialTheme(
+ colorScheme = colors,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts
index 7a0a9f38c1b..98c64985680 100644
--- a/feature/settings/build.gradle.kts
+++ b/feature/settings/build.gradle.kts
@@ -12,6 +12,8 @@ dependencies {
implementation(projects.core.datastore)
implementation(projects.core.domain)
+ implementation(projects.core.ui)
+ implementation(libs.appcompat)
androidTestImplementation(libs.androidx.compose.ui.test)
debugApi(libs.androidx.compose.ui.testManifest)
diff --git a/feature/settings/src/main/java/com/mifos/feature/settings/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/mifos/feature/settings/settings/SettingsScreen.kt
new file mode 100644
index 00000000000..33dbf8e8e28
--- /dev/null
+++ b/feature/settings/src/main/java/com/mifos/feature/settings/settings/SettingsScreen.kt
@@ -0,0 +1,265 @@
+package com.mifos.feature.settings.settings
+
+import android.content.Context
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringArrayResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.mifos.core.common.enums.MifosAppLanguage
+import com.mifos.core.common.utils.LanguageHelper
+import com.mifos.core.designsystem.component.MifosRadioButtonDialog
+import com.mifos.core.designsystem.component.MifosScaffold
+import com.mifos.core.designsystem.component.UpdateEndpointDialogScreen
+import com.mifos.core.designsystem.icon.MifosIcons
+import com.mifos.feature.settings.R
+import java.util.Locale
+
+@Composable
+fun SettingsScreen(
+ onBackPressed: () -> Unit,
+ navigateToLoginScreen: () -> Unit,
+ changePasscode: (String) -> Unit,
+ languageChanged: () -> Unit,
+ serverConfig: () -> Unit
+) {
+ val viewModel: SettingsViewModel = hiltViewModel()
+ val baseURL by viewModel.baseUrl.collectAsStateWithLifecycle()
+ val tenant by viewModel.tenant.collectAsStateWithLifecycle()
+ val passcode by viewModel.passcode.collectAsStateWithLifecycle()
+ val theme by viewModel.theme.collectAsStateWithLifecycle()
+ val language by viewModel.language.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+
+ SettingsScreen(
+ onBackPressed = onBackPressed,
+ selectedLanguage = language ?: "System Language",
+ selectedTheme = theme ?: "System Theme",
+ baseURL = baseURL ?: "",
+ tenant = tenant ?: "",
+ changePasscode = { changePasscode(passcode ?: "") },
+ handleEndpointUpdate = { baseURL, tenant ->
+ if (viewModel.tryUpdatingEndpoint(selectedBaseUrl = baseURL, selectedTenant = tenant)) {
+ navigateToLoginScreen()
+ }
+ },
+ updateTheme = {
+ viewModel.updateTheme(it)
+ },
+ updateLanguage = {
+ val isSystemLanguage = viewModel.updateLanguage(it.code)
+ updateLanguageLocale(
+ context = context,
+ language = it.code,
+ isSystemLanguage = isSystemLanguage
+ )
+ languageChanged()
+ },
+ serverConfig = serverConfig
+ )
+}
+
+
+@Composable
+fun SettingsScreen(
+ onBackPressed: () -> Unit,
+ selectedLanguage: String,
+ selectedTheme: String,
+ baseURL: String,
+ tenant: String,
+ changePasscode: () -> Unit,
+ handleEndpointUpdate: (baseURL: String, tenant: String) -> Unit,
+ updateTheme: (theme: AppTheme) -> Unit,
+ updateLanguage: (language: MifosAppLanguage) -> Unit,
+ serverConfig: () -> Unit
+) {
+
+ val snackbarHostState = remember { SnackbarHostState() }
+ var showLanguageUpdateDialog by rememberSaveable { mutableStateOf(false) }
+ var showEndpointUpdateDialog by rememberSaveable { mutableStateOf(false) }
+ var showThemeUpdateDialog by rememberSaveable { mutableStateOf(false) }
+
+ MifosScaffold(
+ icon = MifosIcons.arrowBack,
+ title = stringResource(id = R.string.feature_settings),
+ onBackPressed = onBackPressed,
+ snackbarHostState = snackbarHostState,
+ ) { paddingValues ->
+ Column(
+ Modifier.padding(paddingValues)
+ ) {
+ SettingsCards(
+ settingsCardClicked = { item ->
+ when (item) {
+ SettingsCardItem.SYNC_SURVEY -> {
+ // TODO Implement Survey dialog
+ }
+
+ SettingsCardItem.LANGUAGE -> showLanguageUpdateDialog = true
+
+ SettingsCardItem.THEME -> showThemeUpdateDialog = true
+
+ SettingsCardItem.PASSCODE -> changePasscode()
+
+ SettingsCardItem.ENDPOINT -> showEndpointUpdateDialog = true
+
+ SettingsCardItem.SERVER_CONFIG -> {
+ serverConfig()
+
+ }
+ }
+ }
+ )
+ }
+ }
+
+ if (showLanguageUpdateDialog) {
+ MifosRadioButtonDialog(
+ titleResId = R.string.feature_settings_choose_language,
+ items = stringArrayResource(R.array.feature_settings_languages),
+ selectItem = { _, index -> updateLanguage(MifosAppLanguage.entries[index]) },
+ onDismissRequest = { showLanguageUpdateDialog = false },
+ selectedItem = MifosAppLanguage.fromCode(selectedLanguage).displayName
+ )
+ }
+
+ if (showThemeUpdateDialog) {
+ MifosRadioButtonDialog(
+ titleResId = R.string.feature_settings_change_app_theme,
+ items = AppTheme.entries.map { it.themeName }.toTypedArray(),
+ selectItem = { _, index -> updateTheme(AppTheme.entries[index]) },
+ onDismissRequest = { showThemeUpdateDialog = false },
+ selectedItem = selectedTheme
+ )
+ }
+
+ if (showEndpointUpdateDialog) {
+ UpdateEndpointDialogScreen(
+ initialBaseURL = baseURL,
+ initialTenant = tenant,
+ onDismissRequest = { showEndpointUpdateDialog = false },
+ handleEndpointUpdate = handleEndpointUpdate
+ )
+ }
+}
+
+@Composable
+fun SettingsCards(
+ settingsCardClicked: (SettingsCardItem) -> Unit,
+) {
+ LazyColumn {
+ items(SettingsCardItem.entries) { card ->
+ SettingsCardItem(
+ title = card.title,
+ details = card.details,
+ icon = card.icon,
+ onclick = {
+ settingsCardClicked(card)
+ }
+ )
+ }
+ }
+}
+
+@Composable
+fun SettingsCardItem(
+ title: Int,
+ details: Int,
+ icon: ImageVector?,
+ onclick: () -> Unit
+) {
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ shape = RoundedCornerShape(0.dp),
+ onClick = { onclick.invoke() }
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(vertical = 16.dp),
+ ) {
+ icon?.let {
+ Icon(
+ imageVector = it,
+ contentDescription = null,
+ modifier = Modifier.weight(0.2f)
+ )
+ }
+ if (icon == null) {
+ Spacer(modifier = Modifier.weight(0.2f))
+ }
+ Column(
+ modifier = Modifier.weight(0.8f)
+ ) {
+ Text(
+ text = stringResource(id = title),
+ style = MaterialTheme.typography.bodyMedium
+ )
+ Text(
+ modifier = Modifier.padding(end = 16.dp),
+ text = stringResource(id = details),
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ }
+ }
+}
+
+
+fun updateLanguageLocale(context: Context, language: String, isSystemLanguage: Boolean) {
+ if (isSystemLanguage) {
+ LanguageHelper.setLocale(context, language)
+ } else {
+ val systemLanguageCode = Locale.getDefault().language
+ if (MifosAppLanguage.entries.find { it.code == systemLanguageCode } == null) {
+ LanguageHelper.setLocale(context, MifosAppLanguage.ENGLISH.code)
+ } else {
+ LanguageHelper.setLocale(context, language)
+ }
+ }
+}
+
+@Composable
+@Preview(showSystemUi = true, showBackground = true)
+fun PreviewSettingsScreen() {
+ SettingsScreen(
+ onBackPressed = {},
+ selectedLanguage = "",
+ selectedTheme = "",
+ baseURL = "",
+ tenant = "",
+ handleEndpointUpdate = { _, _ -> },
+ updateLanguage = {},
+ updateTheme = {},
+ changePasscode = {},
+ serverConfig = {}
+ )
+
+}
\ No newline at end of file
diff --git a/feature/settings/src/main/java/com/mifos/feature/settings/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/mifos/feature/settings/settings/SettingsViewModel.kt
new file mode 100644
index 00000000000..b8f74247cae
--- /dev/null
+++ b/feature/settings/src/main/java/com/mifos/feature/settings/settings/SettingsViewModel.kt
@@ -0,0 +1,107 @@
+package com.mifos.feature.settings.settings
+
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.mifos.core.common.enums.MifosAppLanguage
+import com.mifos.core.common.utils.Constants
+import com.mifos.core.datastore.PrefManager
+import com.mifos.core.designsystem.icon.MifosIcons
+import com.mifos.feature.settings.R
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingsViewModel @Inject constructor(
+ private val prefManager: PrefManager
+) : ViewModel() {
+
+ val tenant: StateFlow = prefManager.getStringValue(Constants.TENANT)
+ .stateIn(viewModelScope, SharingStarted.Eagerly, null)
+
+ val baseUrl: StateFlow = prefManager.getStringValue(Constants.BASE_URL)
+ .stateIn(viewModelScope, SharingStarted.Eagerly, null)
+
+ val passcode: StateFlow = prefManager.getStringValue(Constants.PASSCODE)
+ .stateIn(viewModelScope, SharingStarted.Eagerly, null)
+
+ val theme: StateFlow = prefManager.getStringValue(Constants.THEME)
+ .stateIn(viewModelScope, SharingStarted.Eagerly, "System Theme")
+
+ val language: StateFlow = prefManager.getStringValue(Constants.LANGUAGE)
+ .stateIn(viewModelScope, SharingStarted.Eagerly, "System Language")
+
+
+ fun updateTheme(theme: AppTheme) {
+ prefManager.setStringValue(Constants.THEME, theme.themeName)
+ AppCompatDelegate.setDefaultNightMode(
+ when (theme) {
+ AppTheme.DARK -> AppCompatDelegate.MODE_NIGHT_YES
+ AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
+ else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+ }
+ )
+ }
+
+ fun updateLanguage(language: String): Boolean {
+ prefManager.setStringValue(Constants.LANGUAGE, language)
+ val isSystemLanguage = (language == MifosAppLanguage.SYSTEM_LANGUAGE.code)
+ return isSystemLanguage
+ }
+
+ fun tryUpdatingEndpoint(selectedBaseUrl: String, selectedTenant: String): Boolean {
+ // TODO Implement endpoint update
+ return !(baseUrl.equals(selectedBaseUrl) && tenant.equals(selectedTenant))
+ }
+
+}
+
+
+enum class SettingsCardItem(
+ val title: Int,
+ val details: Int,
+ val icon: ImageVector?
+) {
+ SYNC_SURVEY(
+ title = R.string.feature_settings_sync_survey,
+ details = R.string.feature_settings_sync_survey_desc,
+ icon = null
+ ),
+ LANGUAGE(
+ title = R.string.feature_settings_language,
+ details = R.string.feature_settings_language_desc,
+ icon = MifosIcons.language
+ ),
+ THEME(
+ title = R.string.feature_settings_theme,
+ details = R.string.feature_settings_theme_desc,
+ icon = MifosIcons.theme
+ ),
+ PASSCODE(
+ title = R.string.feature_settings_change_passcode,
+ details = R.string.feature_settings_change_passcode_desc,
+ icon = MifosIcons.password
+ ),
+ ENDPOINT(
+ title = R.string.feature_settings_instance_url,
+ details = R.string.feature_settings_instance_url_desc,
+ icon = null
+ ),
+ SERVER_CONFIG(
+ title = R.string.feature_settings_server_config,
+ details = R.string.feature_settings_server_config_desc,
+ icon = null
+ )
+}
+
+enum class AppTheme(
+ val themeName: String
+) {
+ SYSTEM(themeName = "System Theme"),
+ LIGHT(themeName = "Light Theme"),
+ DARK(themeName = "Dark Theme")
+}
\ No newline at end of file
diff --git a/feature/settings/src/main/res/values/strings.xml b/feature/settings/src/main/res/values/strings.xml
index 98ca04e7e6c..d7f6ed09777 100644
--- a/feature/settings/src/main/res/values/strings.xml
+++ b/feature/settings/src/main/res/values/strings.xml
@@ -3,4 +3,43 @@
CloseBottomSheetIcon
Update Config
The application will restart after a successful update.
+
+ Settings
+ Sync Survey
+ Click to Sync Survey
+ Language
+ Select your preferred language
+ Theme
+ Select theme
+ Change Passcode
+ Change App Passcode
+ Instance URL
+ Update Instance URL
+ Server Config
+ Update Server Config
+ Choose Language
+ Change App Theme
+
+
+
+ - System Language
+ - English
+ - हिंदी
+ - عربى
+ - اُردُو
+ - বাঙালি
+ - Español
+ - français
+ - bahasa Indonesia
+ - ភាសាខ្មែរ
+ - ಕನ್ನಡ
+ - తెలుగు
+ - မြန်မာ
+ - Polski
+ - Português
+ - русский
+ - Kiswahili
+ - فارسی
+
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9c5e69578a0..3979d6e44c0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -119,6 +119,7 @@ turbine = "1.0.0"
timberVersion = '5.0.1'
truthVersion = '1.1.5'
runtimeLivedata = "1.6.8"
+appcompat = "1.7.0"
[libraries]
# AndroidX Libraries
@@ -371,6 +372,7 @@ kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-pl
ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", version.ref = "room" }
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
[plugins]
diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/setting/SettingsFragment.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/setting/SettingsFragment.kt
index c2c502a3f89..de544c9c485 100644
--- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/setting/SettingsFragment.kt
+++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/setting/SettingsFragment.kt
@@ -1,160 +1,75 @@
-@file:Suppress("DEPRECATION")
-
package com.mifos.mifosxdroid.activity.setting
import android.content.Intent
-import android.content.SharedPreferences
import android.os.Bundle
-import android.widget.Toast
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
-import androidx.preference.EditTextPreference
-import androidx.preference.ListPreference
-import androidx.preference.Preference
-import androidx.preference.Preference.OnPreferenceChangeListener
-import androidx.preference.PreferenceFragmentCompat
-import androidx.preference.SwitchPreference
+import com.mifos.core.common.utils.Constants
+import com.mifos.feature.settings.settings.SettingsScreen
import com.mifos.mifosxdroid.R
-import com.mifos.mifosxdroid.dialogfragments.syncsurveysdialog.SyncSurveysDialogFragment
-import com.mifos.mifosxdroid.online.DashboardActivity
+import com.mifos.mifosxdroid.activity.login.LoginActivity
import com.mifos.mifosxdroid.passcode.PassCodeActivity
import com.mifos.mobile.passcode.utils.PasscodePreferencesHelper
-import com.mifos.utils.Constants
-import com.mifos.utils.FragmentConstants
-import com.mifos.utils.LanguageHelper
-import com.mifos.utils.PrefManager
-import com.mifos.utils.ThemeHelper
/**
* Created by mayankjindal on 22/07/17.
*/
-class SettingsFragment : PreferenceFragmentCompat(),
- SharedPreferences.OnSharedPreferenceChangeListener {
- private lateinit var mEnableSyncSurvey: SwitchPreference
- private lateinit var mInstanceUrlPref: EditTextPreference
- private lateinit var languages: Array
- private var languageCallback: LanguageCallback? = null
- private var mRootKey: String? = null
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.preferences, rootKey)
- mRootKey = rootKey
- languages = requireActivity().resources.getStringArray(R.array.language_option)
-
- initSurveyPreferences()
- initInstanceUrlPreferences()
- initLanguagePreferences()
- initThemePreferences()
- }
-
- private fun initSurveyPreferences() {
- mEnableSyncSurvey =
- (findPreference(requireContext().getString(R.string.sync_survey)) as SwitchPreference?)!!
- mEnableSyncSurvey.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
- if (newValue as Boolean) {
- val syncSurveysDialogFragment = SyncSurveysDialogFragment.newInstance()
- val fragmentTransaction = parentFragmentManager.beginTransaction()
- fragmentTransaction.addToBackStack(FragmentConstants.FRAG_SURVEYS_SYNC)
- syncSurveysDialogFragment.isCancelable = false
- fragmentTransaction.let {
- syncSurveysDialogFragment.show(
- it,
- requireContext().getString(R.string.sync_clients)
- )
- }
- }
- true
- }
- }
-
- private fun initInstanceUrlPreferences() {
- mInstanceUrlPref = (findPreference(
- requireContext().getString(R.string.hint_instance_url)
- ) as EditTextPreference?)!!
- val instanceUrl = PrefManager.getInstanceUrl()
- mInstanceUrlPref.text = instanceUrl
- mInstanceUrlPref.isSelectable = true
- mInstanceUrlPref.dialogTitle = "Edit Instance Url"
- mInstanceUrlPref.setDialogIcon(R.drawable.ic_baseline_edit_24)
- mInstanceUrlPref.onPreferenceChangeListener =
- OnPreferenceChangeListener { _, o ->
- val newUrl = o.toString()
- if (newUrl != instanceUrl) {
- PrefManager.setInstanceUrl(newUrl)
- Toast.makeText(activity, newUrl, Toast.LENGTH_SHORT).show()
- startActivity(Intent(activity, DashboardActivity::class.java))
- activity?.finishAffinity()
- }
- true
+class SettingsFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ SettingsScreen(
+ onBackPressed = {
+ findNavController().popBackStack()
+ },
+ navigateToLoginScreen = {
+ Intent(requireContext(), LoginActivity::class.java).also {
+ startActivity(it)
+ }
+ },
+ changePasscode = {
+ val passCodePreferencesHelper = PasscodePreferencesHelper(activity)
+ val currPassCode = passCodePreferencesHelper.passCode
+ passCodePreferencesHelper.savePassCode("")
+ val intent = Intent(requireContext(), PassCodeActivity::class.java).apply {
+ putExtra(Constants.CURR_PASSWORD, currPassCode)
+ putExtra(Constants.IS_TO_UPDATE_PASS_CODE, true)
+ }
+ startActivity(intent)
+ },
+ languageChanged = {
+ val intent = Intent(activity, activity?.javaClass)
+ intent.putExtra(Constants.HAS_SETTING_CHANGED, true)
+ startActivity(intent)
+ activity?.finish()
+ },
+ serverConfig = {
+ findNavController().navigate(R.id.updateServerConfigFragment)
+ }
+ )
}
- }
-
- private fun initLanguagePreferences() {
- val langPref = findPreference("language_type") as ListPreference?
- langPref?.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
- LanguageHelper.setLocale(requireActivity(), newValue.toString())
- val intent = Intent(requireActivity(), requireActivity().javaClass)
- intent.putExtra(Constants.HAS_SETTING_CHANGED, true)
- startActivity(intent)
- requireActivity().finish()
- preferenceScreen = null
- setPreferencesFromResource(R.xml.preferences, mRootKey)
- preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
- true
}
}
- private fun initThemePreferences() {
- val themePreference =
- findPreference(requireContext().getString(R.string.mode_key)) as ListPreference?
- themePreference?.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
- val themeOption = newValue as String
- ThemeHelper.applyTheme(themeOption)
- Toast.makeText(activity, "Switched to $themeOption Mode", Toast.LENGTH_SHORT).show()
- true
- }
+ override fun onResume() {
+ super.onResume()
+ (requireActivity() as AppCompatActivity).supportActionBar?.hide()
}
- override fun onPause() {
- super.onPause()
- preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
- }
-
- fun setLanguageCallback(languageCallback: LanguageCallback?) {
- this.languageCallback = languageCallback
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, s: String) {
- val preference = findPreference(s) as ListPreference?
- LanguageHelper.setLocale(requireActivity(), preference?.value)
- }
-
- interface LanguageCallback
-
- override fun onPreferenceTreeClick(
- preference: Preference
- ): Boolean {
- when (preference.key) {
- getString(R.string.password) -> {
- // TODO: create changePasswordActivity and implement the logic for password change
- }
-
- getString(R.string.passcode) -> {
- activity?.let {
- val passCodePreferencesHelper = PasscodePreferencesHelper(activity)
- val currPassCode = passCodePreferencesHelper.passCode
- passCodePreferencesHelper.savePassCode("")
- val intent = Intent(it, PassCodeActivity::class.java).apply {
- putExtra(Constants.CURR_PASSWORD, currPassCode)
- putExtra(Constants.IS_TO_UPDATE_PASS_CODE, true)
- }
- startActivity(intent)
- }
- }
-
- getString(R.string.updateServerConfig) -> {
- findNavController().navigate(R.id.updateServerConfigFragment)
- }
- }
- return super.onPreferenceTreeClick(preference)
+ override fun onStop() {
+ super.onStop()
+ (requireActivity() as AppCompatActivity).supportActionBar?.show()
}
}