Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a74cdc6
feat: implement app language selection and onboarding language screen
mena-rizkalla Feb 24, 2026
2e0e217
Merge branch 'development' of https://github.com/mena-rizkalla/mobile…
mena-rizkalla Feb 24, 2026
e4a9cf4
Merge branch 'development' into feat/install-language-selection
mena-rizkalla Feb 24, 2026
252625c
Merge branch 'development' into feat/install-language-selection
mena-rizkalla Feb 25, 2026
254da7b
Merge branch 'development' into feat/install-language-selection
mena-rizkalla Feb 26, 2026
be8ec22
feat: enhance onboarding language selection and persistence
mena-rizkalla Feb 26, 2026
cb369ac
style: cleanup and remove redundant comments
mena-rizkalla Feb 26, 2026
f3a1deb
fix: reorder language update and navigation in LanguageViewModel
mena-rizkalla Feb 26, 2026
754bdde
fix: improve language selection persistence and navigation flow
mena-rizkalla Feb 26, 2026
3e4db00
clean: remove onboarding language strings from settings module
mena-rizkalla Feb 26, 2026
f2fd5da
refactor: use DataState for user preference updates and improve error…
mena-rizkalla Feb 26, 2026
06f2c34
fix: update MainActivity configChanges to handle locale and layoutDir…
mena-rizkalla Feb 26, 2026
d9cb4de
clean: remove language selection feature from settings module
mena-rizkalla Feb 26, 2026
442b726
build: remove datastore and designsystem dependencies from prodReleas…
mena-rizkalla Feb 26, 2026
53c16c9
refactor: update dependencies and refine preference clearing logic
mena-rizkalla Feb 28, 2026
03e33cf
build: remove redundant core:model dependency from prod release class…
mena-rizkalla Feb 28, 2026
f53afbd
Merge branch 'development' into feat/install-language-selection
mena-rizkalla Feb 28, 2026
06c451d
Merge branch 'development' into feat/install-language-selection
mena-rizkalla Mar 2, 2026
4216099
Merge branch 'development' into feat/install-language-selection
mena-rizkalla Mar 4, 2026
1e3b14a
feat: implement MifosRadioButton and enhance language selection UI
mena-rizkalla Mar 5, 2026
56d81a5
Merge development and resolve conflicts
mena-rizkalla Mar 19, 2026
9bfa3ec
build: update and reorganize dependencies for passcode and onboarding…
mena-rizkalla Mar 19, 2026
0f35584
refactor: rename onboarding visibility preference and cleanup design …
mena-rizkalla Mar 21, 2026
d77c000
build: remove redundant passcode feature dependency tree entry
mena-rizkalla Mar 21, 2026
55768da
feat: handle language changes in desktop main
mena-rizkalla Mar 23, 2026
419545a
feat: implement locale persistence and application reload on language…
mena-rizkalla Mar 23, 2026
b5943f8
merge development and solve conflicts
mena-rizkalla Mar 24, 2026
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
34 changes: 34 additions & 0 deletions cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3240,6 +3240,40 @@
| | +--- org.jetbrains.compose.material3:material3:1.9.0-beta03 (*)
| | +--- org.jetbrains.compose.components:components-resources:1.9.3 (*)
| | \--- org.jetbrains.compose.components:components-ui-tooling-preview:1.9.3 (*)
| +--- project :feature:onboarding-language
| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.9.2 -> 2.9.4 (*)
| | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.9.2 -> 2.9.4 (*)
| | +--- androidx.tracing:tracing-ktx:1.3.0 (*)
| | +--- io.insert-koin:koin-bom:4.1.1 (*)
| | +--- io.insert-koin:koin-android:4.1.1 (*)
| | +--- io.insert-koin:koin-androidx-compose:4.1.1 (*)
| | +--- io.insert-koin:koin-androidx-navigation:4.1.1 (*)
| | +--- io.insert-koin:koin-core-viewmodel:4.1.1 (*)
| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.2.21 (*)
| | +--- io.insert-koin:koin-core:4.1.1 (*)
| | +--- io.insert-koin:koin-annotations:2.1.0 (*)
| | +--- project :core:ui (*)
| | +--- project :core:designsystem (*)
| | +--- project :core:data (*)
| | +--- io.insert-koin:koin-compose:4.1.1 (*)
| | +--- io.insert-koin:koin-compose-viewmodel:4.1.1 (*)
| | +--- org.jetbrains.compose.runtime:runtime:1.9.3 (*)
| | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.9.6 (*)
| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.6 (*)
| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.6 (*)
| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.6 (*)
| | +--- org.jetbrains.androidx.savedstate:savedstate:1.4.0 (*)
| | +--- org.jetbrains.androidx.core:core-bundle:1.0.1 (*)
| | +--- org.jetbrains.androidx.navigation:navigation-compose:2.9.1 (*)
| | +--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0 (*)
| | +--- org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0 (*)
| | +--- org.jetbrains.compose.ui:ui:1.9.3 (*)
| | +--- org.jetbrains.compose.foundation:foundation:1.9.3 (*)
| | +--- org.jetbrains.compose.material3:material3:1.9.0-beta03 (*)
| | +--- org.jetbrains.compose.components:components-resources:1.9.3 (*)
| | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.9.3 (*)
| | +--- project :core:datastore (*)
| | \--- project :core-base:designsystem (*)
| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.2.21 (*)
+--- project :core:data (*)
+--- project :core:ui (*)
Expand Down
1 change: 1 addition & 0 deletions cmp-android/dependencies/prodReleaseRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
:feature:mpay-qr
:feature:mpay-qr-scan
:feature:notification
:feature:onboarding-language
:feature:payments
:feature:profile
:feature:receipt
Expand Down
1 change: 1 addition & 0 deletions cmp-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="locale|layoutDirection"
android:theme="@style/Theme.MifosSplash"
android:windowSoftInputMode="adjustResize">
<intent-filter>
Expand Down
32 changes: 29 additions & 3 deletions cmp-android/src/main/kotlin/org/mifospay/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
package org.mifospay

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.os.LocaleListCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
Expand All @@ -35,7 +37,7 @@ import org.mifospay.shared.MainUiState
import org.mifospay.shared.MifosPaySharedApp
import org.mifospay.shared.MifosPayViewModel

class MainActivity : ComponentActivity() {
class MainActivity : AppCompatActivity() {
private val networkMonitor: NetworkMonitor by inject()
private val timeZoneMonitor: TimeZoneMonitor by inject()
private val viewModel: MifosPayViewModel by viewModel()
Expand All @@ -57,7 +59,31 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState
.onEach { uiState = it }
.onEach { state ->
uiState = state
if (state is MainUiState.Success) {
val languageTag = state.language.localName
val currentAppLocales = AppCompatDelegate.getApplicationLocales()

val isRequestedDefault = languageTag.isNullOrBlank()
val isCurrentDefault = currentAppLocales.isEmpty

val shouldUpdate = if (isRequestedDefault) {
!isCurrentDefault
} else {
languageTag != currentAppLocales.toLanguageTags()
}

if (shouldUpdate) {
val appLocale: LocaleListCompat = if (isRequestedDefault) {
LocaleListCompat.getEmptyLocaleList()
} else {
LocaleListCompat.forLanguageTags(languageTag)
}
AppCompatDelegate.setApplicationLocales(appLocale)
}
}
}
.collect()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Expand Down
1 change: 1 addition & 0 deletions cmp-shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ kotlin {
implementation(projects.feature.fastMpay)
implementation(projects.feature.merchants)
implementation(projects.feature.upiSetup)
implementation(projects.feature.onboardingLanguage)
}

desktopMain.dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.mifospay.core.data.util.NetworkMonitor
import org.mifospay.core.data.util.TimeZoneMonitor
import org.mifospay.core.designsystem.component.MifosDialogBox
import org.mifospay.core.designsystem.theme.MifosTheme
import org.mifospay.feature.onboarding.language.navigation.ONBOARDING_LANGUAGE_ROUTE
import org.mifospay.shared.MainUiState.Success
import org.mifospay.shared.navigation.MifosNavGraph.LOGIN_GRAPH
import org.mifospay.shared.navigation.MifosNavGraph.PASSCODE_GRAPH
Expand Down Expand Up @@ -79,7 +80,9 @@ private fun MifosPayApp(

val navDestination = when (uiState) {
is MainUiState.Loading -> LOGIN_GRAPH
is Success -> if ((uiState as Success).userData.authenticated) {
is Success -> if ((uiState as Success).showOnboarding) {
ONBOARDING_LANGUAGE_ROUTE
} else if ((uiState as Success).userData.authenticated) {
PASSCODE_GRAPH
} else {
LOGIN_GRAPH
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.mifospay.core.datastore.UserPreferencesRepository
import org.mifospay.core.model.LanguageConfig
import org.mifospay.core.model.user.UserInfo
import proto.org.mifos.library.passcode.data.PasscodeManager

class MifosPayViewModel(
private val userDataRepository: UserPreferencesRepository,
private val passcodeManager: PasscodeManager,
) : ViewModel() {
val uiState: StateFlow<MainUiState> = userDataRepository.userInfo.map {
MainUiState.Success(it)
val uiState: StateFlow<MainUiState> = combine(
userDataRepository.userInfo,
userDataRepository.language,
userDataRepository.showOnboarding,
) { userInfo, language, showOnboarding ->
MainUiState.Success(userInfo, language, showOnboarding)
}.stateIn(
scope = viewModelScope,
initialValue = MainUiState.Loading,
Expand All @@ -42,5 +47,9 @@ class MifosPayViewModel(

sealed interface MainUiState {
data object Loading : MainUiState
data class Success(val userData: UserInfo) : MainUiState
data class Success(
val userData: UserInfo,
val language: LanguageConfig,
val showOnboarding: Boolean,
) : MainUiState
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.mifospay.feature.merchants.di.MerchantsModule
import org.mifospay.feature.mpay.qr.di.MpayQrModule
import org.mifospay.feature.mpay.qr.scan.di.MpayQrScanModule
import org.mifospay.feature.notification.di.NotificationModule
import org.mifospay.feature.onboarding.language.di.onboardingLanguageModule
import org.mifospay.feature.payments.di.PaymentsModule
import org.mifospay.feature.profile.di.ProfileModule
import org.mifospay.feature.receipt.di.ReceiptModule
Expand Down Expand Up @@ -94,6 +95,7 @@ object KoinModules {
FastMpayModule,
MerchantsModule,
UpiSetupModule,
onboardingLanguageModule,
)
}
private val LibraryModule = module {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import org.mifospay.core.data.util.NetworkMonitor
import org.mifospay.core.data.util.TimeZoneMonitor
import org.mifospay.feature.onboarding.language.navigation.ONBOARDING_LANGUAGE_ROUTE
import org.mifospay.feature.onboarding.language.navigation.onboardingLanguageScreen
import org.mifospay.shared.instance.InstanceSelectorScreen
import org.mifospay.shared.ui.MifosApp

Expand All @@ -45,6 +47,16 @@ internal fun RootNavGraph(
onShowInstanceSelector = { showInstanceSelector = true },
)

onboardingLanguageScreen(
onNavigateToNext = {
navHostController.navigate(MifosNavGraph.LOGIN_GRAPH) {
popUpTo(ONBOARDING_LANGUAGE_ROUTE) {
inclusive = true
}
}
},
)

passcodeNavGraph(navHostController)

composable(MifosNavGraph.MAIN_GRAPH) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kotlinx.serialization.builtins.serializer
import org.mifospay.core.datastore.UserPreferencesDataSource.Companion.DEFAULT_ACCOUNT
import org.mifospay.core.datastore.model.ClientPreferences
import org.mifospay.core.datastore.model.UserInfoPreferences
import org.mifospay.core.model.LanguageConfig
import org.mifospay.core.model.account.DefaultAccount
import org.mifospay.core.model.client.Client
import org.mifospay.core.model.client.UpdatedClient
Expand All @@ -38,6 +39,8 @@ private const val CLIENT_INFO_KEY = "clientInfo"
private const val SELECTED_INSTANCE_KEY = "selectedInstance"
private const val SELECTED_INTERBANK_INSTANCE_KEY = "selectedInterbankInstance"
private const val ACCOUNT_EXTERNAL_IDS_KEY = "accountExternalIds"
private const val LANGUAGE_KEY = "language"
private const val SHOW_ONBOARDING_KEY = "showOnboarding"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

@OptIn(ExperimentalSerializationApi::class)
class UserPreferencesDataSource(
Expand Down Expand Up @@ -91,6 +94,24 @@ class UserPreferencesDataSource(
),
)

private val _language = MutableStateFlow(
settings.decodeValue(
key = LANGUAGE_KEY,
serializer = LanguageConfig.serializer(),
defaultValue = settings.decodeValueOrNull(
key = LANGUAGE_KEY,
serializer = LanguageConfig.serializer(),
) ?: LanguageConfig.DEFAULT,
),
)

private val _showOnboarding = MutableStateFlow(
settings.getBoolean(
key = SHOW_ONBOARDING_KEY,
defaultValue = true,
),
)

private val _accountExternalIds = MutableStateFlow(
settings.decodeValueOrNull(
key = ACCOUNT_EXTERNAL_IDS_KEY,
Expand All @@ -113,6 +134,10 @@ class UserPreferencesDataSource(

val selectedInterbankInstance = _selectedInterbankInstance

val language = _language

val showOnboarding = _showOnboarding

val accountExternalIds = _accountExternalIds

suspend fun updateClientInfo(client: Client) {
Expand Down Expand Up @@ -186,6 +211,20 @@ class UserPreferencesDataSource(
}
}

suspend fun setLanguage(language: LanguageConfig) {
withContext(dispatcher) {
settings.putLanguage(language)
_language.value = language
}
}

suspend fun setShowOnboarding(showOnboarding: Boolean) {
withContext(dispatcher) {
settings.putBoolean(SHOW_ONBOARDING_KEY, showOnboarding)
_showOnboarding.value = showOnboarding
}
}

suspend fun updateAccountExternalIds(accountExternalIds: Map<Long, String>) {
withContext(dispatcher) {
settings.putAccountExternalIds(accountExternalIds)
Expand All @@ -199,7 +238,18 @@ class UserPreferencesDataSource(

suspend fun clearInfo() {
withContext(dispatcher) {
val currentLanguage = _language.value
val currentShowOnboarding = _showOnboarding.value

settings.clear()

settings.putLanguage(currentLanguage)
settings.putBoolean(SHOW_ONBOARDING_KEY, currentShowOnboarding)

_userInfo.value = UserInfoPreferences.DEFAULT
_clientInfo.value = ClientPreferences.DEFAULT
_defaultAccount.value = DefaultAccount.DEFAULT
_accountExternalIds.value = emptyMap()
}
}

Expand Down Expand Up @@ -249,6 +299,14 @@ private fun Settings.putSelectedInterbankInstance(instance: InterbankServer) {
)
}

private fun Settings.putLanguage(language: LanguageConfig) {
encodeValue(
key = LANGUAGE_KEY,
serializer = LanguageConfig.serializer(),
value = language,
)
}

private fun Settings.putAccountExternalIds(accountExternalIds: Map<Long, String>) {
encodeValue(
key = ACCOUNT_EXTERNAL_IDS_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package org.mifospay.core.datastore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import org.mifospay.core.common.DataState
import org.mifospay.core.model.LanguageConfig
import org.mifospay.core.model.account.DefaultAccount
import org.mifospay.core.model.client.Client
import org.mifospay.core.model.client.UpdatedClient
Expand All @@ -38,6 +39,10 @@ interface UserPreferencesRepository {

val selectedInterbankInstance: StateFlow<InterbankServer?>

val language: StateFlow<LanguageConfig>

val showOnboarding: StateFlow<Boolean>

val accountExternalIds: StateFlow<Map<Long, String>>

suspend fun updateToken(token: String): DataState<Unit>
Expand All @@ -54,6 +59,10 @@ interface UserPreferencesRepository {

suspend fun updateSelectedInterbankInstance(instance: InterbankServer): DataState<Unit>

suspend fun setLanguage(language: LanguageConfig): DataState<Unit>

suspend fun setShowOnboarding(showOnboarding: Boolean): DataState<Unit>

suspend fun updateAccountExternalIds(accountExternalIds: Map<Long, String>): DataState<Unit>

fun getAccountExternalId(accountId: Long): String?
Expand Down
Loading
Loading