Skip to content

Commit 9f53e7f

Browse files
authored
Merge branch 'development' into feat-client-show-loan-status-under-type
2 parents b0fef15 + b24fe7a commit 9f53e7f

55 files changed

Lines changed: 1706 additions & 194 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmp-navigation/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ kotlin {
4949
implementation(projects.feature.recurringDeposit)
5050
implementation(projects.feature.settings)
5151
implementation(projects.feature.search)
52+
implementation(projects.feature.searchRecord)
5253

5354
// implementation(project.libs.mifos.passcode)
5455
implementation(compose.material3)

cmp-navigation/src/commonMain/composeResources/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@
2626
<string name="offline_sync">Offline Sync</string>
2727
<string name="powered_by">Powered By</string>
2828

29+
<string name="cmp_navigation_profile_header">Profile header</string>
30+
<string name="cmp_navigation_dp_placeholder">DP</string>
31+
<string name="cmp_navigation_address">Address</string>
32+
33+
2934
</resources>

cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavbarNavigationScreen.kt

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,20 @@ import cmp.navigation.components.MifosTopAppBar
6666
import cmp.navigation.components.ScaffoldNavigationData
6767
import cmp.navigation.navigation.HomeDestinationsScreen
6868
import cmp.navigation.ui.rememberMifosNavController
69+
import co.touchlab.kermit.Logger
6970
import com.mifos.core.common.utils.Constants
7071
import com.mifos.core.designsystem.theme.DesignToken
72+
import com.mifos.core.model.objects.searchrecord.GenericSearchRecord
73+
import com.mifos.core.model.objects.searchrecord.RecordType
7174
import com.mifos.core.ui.RootTransitionProviders
7275
import com.mifos.core.ui.util.EventsEffect
7376
import com.mifos.feature.activate.navigateToActivateRoute
7477
import com.mifos.feature.center.navigation.centerNavGraph
7578
import com.mifos.feature.center.navigation.navigateCenterDetailsScreenRoute
7679
import com.mifos.feature.center.navigation.navigateCreateCenterScreenRoute
7780
import com.mifos.feature.center.navigation.navigateToCenterListScreenRoute
81+
import com.mifos.feature.client.clientAddress.navigateToClientAddressRoute
82+
import com.mifos.feature.client.clientIdentifiersList.navigateToClientIdentifiersListScreen
7883
import com.mifos.feature.client.clientProfile.navigateToClientProfileRoute
7984
import com.mifos.feature.client.navigation.clientNavGraph
8085
import com.mifos.feature.client.navigation.navigateClientDetailsScreen
@@ -94,15 +99,18 @@ import com.mifos.feature.savings.navigation.navigateToSavingsAccountSummaryScree
9499
import com.mifos.feature.search.navigation.SearchScreenRoute
95100
import com.mifos.feature.search.navigation.navigateToSearchScreen
96101
import com.mifos.feature.search.navigation.searchNavGraph
102+
import com.mifos.feature.searchrecord.navigation.navigateToSearchRecord
103+
import com.mifos.feature.searchrecord.navigation.searchRecordNavigation
97104
import kotlinx.collections.immutable.persistentListOf
98105
import kotlinx.coroutines.launch
99106
import org.jetbrains.compose.resources.painterResource
100107
import org.jetbrains.compose.resources.stringResource
101108
import org.koin.compose.viewmodel.koinViewModel
102-
import org.mifos.core.designsystem.component.MifosTopAppBar
103109
import org.mifos.navigation.generated.resources.Res
110+
import org.mifos.navigation.generated.resources.cmp_navigation_dp_placeholder
104111
import org.mifos.navigation.generated.resources.cmp_navigation_mifos
105112
import org.mifos.navigation.generated.resources.cmp_navigation_no_internet
113+
import org.mifos.navigation.generated.resources.cmp_navigation_profile_header
106114
import org.mifos.navigation.generated.resources.drawer_profile_header
107115
import org.mifos.navigation.generated.resources.ic_dp_placeholder
108116

@@ -120,6 +128,7 @@ internal fun AuthenticatedNavbarNavigationScreen(
120128
val scope = rememberCoroutineScope()
121129
val snackbarHostState = remember { SnackbarHostState() }
122130
val isOffline by viewModel.isOffline.collectAsStateWithLifecycle()
131+
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
123132

124133
EventsEffect(eventFlow = viewModel.eventFlow) { event ->
125134
navController.apply {
@@ -166,6 +175,7 @@ internal fun AuthenticatedNavbarNavigationScreen(
166175
}
167176

168177
AuthenticatedNavbarNavigationScreenContent(
178+
state = state,
169179
navController = navController,
170180
snackbarHostState = snackbarHostState,
171181
modifier = modifier,
@@ -181,6 +191,7 @@ internal fun AuthenticatedNavbarNavigationScreen(
181191
@OptIn(ExperimentalMaterial3Api::class)
182192
@Composable
183193
internal fun AuthenticatedNavbarNavigationScreenContent(
194+
state: AuthenticatedNavbarState,
184195
navController: NavHostController,
185196
onDrawerItemClick: (String) -> Unit,
186197
navigateToDocumentScreen: (Int, String) -> Unit,
@@ -190,8 +201,13 @@ internal fun AuthenticatedNavbarNavigationScreenContent(
190201
onAction: (AuthenticatedNavBarAction) -> Unit,
191202
) {
192203
val navBackStackEntry by navController.currentBackStackEntryAsState()
193-
var selectedItemIndex by rememberSaveable { mutableStateOf<Int?>(null) }
204+
val currentRoute = navBackStackEntry?.destination?.route
205+
206+
LaunchedEffect(currentRoute) {
207+
onAction(AuthenticatedNavBarAction.OnRouteChanged(currentRoute))
208+
}
194209

210+
var selectedItemIndex by rememberSaveable { mutableStateOf<Int?>(null) }
195211
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
196212
val scope = rememberCoroutineScope()
197213

@@ -217,66 +233,27 @@ internal fun AuthenticatedNavbarNavigationScreenContent(
217233
drawerState = drawerState,
218234
drawerContent = {
219235
ModalDrawerSheet(modifier = Modifier.requiredWidth(320.dp)) {
220-
Column(
221-
modifier = Modifier.verticalScroll(rememberScrollState()),
222-
) {
223-
Box(
224-
modifier = Modifier
225-
.fillMaxWidth()
226-
.height(200.dp),
227-
) {
228-
Image(
229-
modifier = Modifier.fillMaxSize(),
230-
contentScale = ContentScale.Crop,
231-
painter = painterResource(Res.drawable.drawer_profile_header),
232-
contentDescription = "Profile header",
233-
)
236+
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
237+
Box(modifier = Modifier.fillMaxWidth().height(200.dp)) {
238+
Image(modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, painter = painterResource(Res.drawable.drawer_profile_header), contentDescription = stringResource(Res.string.cmp_navigation_profile_header))
234239
Column(modifier = Modifier.padding(32.dp)) {
235240
Column(horizontalAlignment = Alignment.CenterHorizontally) {
236-
Image(
237-
modifier = Modifier
238-
.size(64.dp)
239-
.clip(CircleShape),
240-
painter = painterResource(Res.drawable.ic_dp_placeholder),
241-
contentDescription = "DP place holder",
242-
)
243-
Text(
244-
text = stringResource(Res.string.cmp_navigation_mifos),
245-
color = Color.White,
246-
style = MaterialTheme.typography.titleMedium,
247-
)
241+
Image(modifier = Modifier.size(64.dp).clip(CircleShape), painter = painterResource(Res.drawable.ic_dp_placeholder), contentDescription = stringResource(Res.string.cmp_navigation_dp_placeholder))
242+
Text(text = stringResource(Res.string.cmp_navigation_mifos), color = Color.White, style = MaterialTheme.typography.titleMedium)
248243
}
249244
}
250245
}
251246
Spacer(modifier = Modifier.height(DesignToken.padding.small))
252247
navigationDrawerTabs.forEachIndexed { index, item ->
253248
NavigationDrawerItem(
254-
label = {
255-
Text(
256-
text = stringResource(item.title),
257-
style = MaterialTheme.typography.bodyMedium,
258-
)
259-
},
249+
label = { Text(text = stringResource(item.title), style = MaterialTheme.typography.bodyMedium) },
260250
selected = index == selectedItemIndex,
261251
onClick = {
262252
selectedItemIndex = index
263253
onDrawerItemClick(navigationDrawerTabs[index].route)
264-
scope.launch {
265-
drawerState.close()
266-
}
267-
},
268-
icon = {
269-
item.icon?.let {
270-
Icon(
271-
imageVector = if (index == selectedItemIndex) {
272-
it
273-
} else {
274-
it
275-
},
276-
contentDescription = stringResource(item.title),
277-
)
278-
}
254+
scope.launch { drawerState.close() }
279255
},
256+
icon = { item.icon?.let { Icon(imageVector = it, contentDescription = stringResource(item.title)) } },
280257
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
281258
)
282259
if (index == (navigationDrawerTabs.size - 2)) {
@@ -289,15 +266,17 @@ internal fun AuthenticatedNavbarNavigationScreenContent(
289266
) {
290267
MifosScaffold(
291268
topBar = {
292-
MifosTopAppBar(
293-
onNavigationIconClick = {
294-
scope.launch {
295-
drawerState.open()
296-
}
297-
},
298-
onSearchIconClick = {},
299-
onNotificationIconClick = {},
300-
)
269+
if (state.isTopBarVisible) {
270+
MifosTopAppBar(
271+
onNavigationIconClick = {
272+
scope.launch {
273+
drawerState.open()
274+
}
275+
},
276+
onSearchIconClick = {},
277+
onNotificationIconClick = {},
278+
)
279+
}
301280
},
302281
contentWindowInsets = WindowInsets(0.dp),
303282
navigationData = ScaffoldNavigationData(
@@ -393,6 +372,30 @@ internal fun AuthenticatedNavbarNavigationScreenContent(
393372
)
394373
},
395374
hasDatatables = navController::navigateDataTableList,
375+
onNavigateToSearch = { type ->
376+
navController.navigateToSearchRecord(type)
377+
},
378+
)
379+
380+
searchRecordNavigation(
381+
onBackClick = { navController.popBackStack() },
382+
onRecordSelected = { record: GenericSearchRecord ->
383+
val clientId = record.metadata[Constants.CLIENT_ID]?.toIntOrNull()
384+
385+
if (clientId != null) {
386+
when {
387+
record.type.equals(RecordType.ADDRESS.name, ignoreCase = true) -> {
388+
navController.navigateToClientAddressRoute(id = clientId)
389+
}
390+
391+
record.type.equals(RecordType.IDENTIFIER.name, ignoreCase = true) -> {
392+
navController.navigateToClientIdentifiersListScreen(clientId = clientId)
393+
}
394+
}
395+
} else {
396+
Logger.w("AuthenticatedNavbar") { "Invalid Client ID selected for record: ${record.id}" }
397+
}
398+
},
396399
)
397400
}
398401
}

cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavbarNavigationViewModel.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ package cmp.navigation.authenticated
1111

1212
import androidx.lifecycle.viewModelScope
1313
import com.mifos.core.data.util.NetworkMonitor
14+
import com.mifos.core.model.objects.searchrecord.RecordType
1415
import com.mifos.core.ui.util.BaseViewModel
1516
import kotlinx.coroutines.flow.SharingStarted
1617
import kotlinx.coroutines.flow.map
1718
import kotlinx.coroutines.flow.stateIn
19+
import kotlinx.coroutines.flow.update
1820

1921
internal class AuthenticatedNavbarNavigationViewModel(
2022
networkMonitor: NetworkMonitor,
21-
) : BaseViewModel<Unit, AuthenticatedNavBarEvent, AuthenticatedNavBarAction>(
22-
initialState = Unit,
23+
) : BaseViewModel<AuthenticatedNavbarState, AuthenticatedNavBarEvent, AuthenticatedNavBarAction>(
24+
initialState = AuthenticatedNavbarState(),
2325
) {
2426

2527
val isOffline = networkMonitor.isOnline
@@ -39,6 +41,23 @@ internal class AuthenticatedNavbarNavigationViewModel(
3941
AuthenticatedNavBarAction.CenterTabClick -> handleCenterTabClicked()
4042

4143
AuthenticatedNavBarAction.GroupTabClick -> handleGroupsTabClicked()
44+
45+
is AuthenticatedNavBarAction.OnRouteChanged -> updateRouteState(action.route)
46+
}
47+
}
48+
49+
private fun updateRouteState(route: String?) {
50+
val safeRoute = route ?: ""
51+
val shouldShowTopBar = !safeRoute.contains("SearchRecord")
52+
val searchType = when {
53+
safeRoute.contains("ClientIdentifiersList") -> RecordType.IDENTIFIER.name
54+
else -> RecordType.ADDRESS.name
55+
}
56+
mutableStateFlow.update { currentState ->
57+
currentState.copy(
58+
isTopBarVisible = shouldShowTopBar,
59+
searchRecordType = searchType,
60+
)
4261
}
4362
}
4463

@@ -59,6 +78,11 @@ internal class AuthenticatedNavbarNavigationViewModel(
5978
}
6079
}
6180

81+
internal data class AuthenticatedNavbarState(
82+
val isTopBarVisible: Boolean = true,
83+
val searchRecordType: String = RecordType.ADDRESS.name,
84+
)
85+
6286
internal sealed class AuthenticatedNavBarAction {
6387

6488
data object SearchTabClick : AuthenticatedNavBarAction()
@@ -68,6 +92,8 @@ internal sealed class AuthenticatedNavBarAction {
6892
data object CenterTabClick : AuthenticatedNavBarAction()
6993

7094
data object GroupTabClick : AuthenticatedNavBarAction()
95+
96+
data class OnRouteChanged(val route: String?) : AuthenticatedNavBarAction()
7197
}
7298

7399
internal sealed class AuthenticatedNavBarEvent {

cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.mifos.feature.recurringDeposit.di.RecurringDepositModule
3535
import com.mifos.feature.report.di.ReportModule
3636
import com.mifos.feature.savings.di.SavingsModule
3737
import com.mifos.feature.search.di.SearchModule
38+
import com.mifos.feature.searchrecord.di.SearchRecordModule
3839
import com.mifos.feature.settings.di.SettingsModule
3940
import com.mifos.room.di.DaoModule
4041
import com.mifos.room.di.HelperModule
@@ -88,6 +89,7 @@ object KoinModules {
8889
SavingsModule,
8990
SearchModule,
9091
SettingsModule,
92+
SearchRecordModule,
9193
)
9294
}
9395

core/common/src/commonMain/kotlin/com/mifos/core/common/utils/Constants.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,14 @@ object Constants {
229229
const val DATA_TABLE_DATA_NAV_DATA = "data_table_data_nav_data"
230230
const val DATA_TABLE_LIST_NAV_DATA = "data_table_list_nav_data"
231231
const val PAYMENT_DETAILS_ARGS = "paymentDetailsArgs"
232+
233+
const val ADDRESS_LINE_1 = "addressLine1"
234+
const val ADDRESS_LINE_2 = "addressLine2"
235+
const val ADDRESS_LINE_3 = "addressLine3"
236+
const val CITY = "city"
237+
const val STATE = "state"
238+
const val COUNTRY = "country"
239+
const val POSTAL_CODE = "postalCode"
240+
const val STATUS = "status"
241+
const val DOCUMENT_KEY = "documentKey"
232242
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.core.data.datasource
11+
12+
import com.mifos.core.model.objects.searchrecord.GenericSearchRecord
13+
import com.mifos.core.model.objects.searchrecord.RecordType
14+
import kotlinx.coroutines.flow.Flow
15+
16+
interface SearchRecordLocalDataSource {
17+
fun searchRecords(
18+
recordType: RecordType,
19+
query: String,
20+
): Flow<List<GenericSearchRecord>>
21+
}

0 commit comments

Comments
 (0)