Skip to content

Commit a4eaf01

Browse files
authored
Merge pull request #36 from Arinyadav1/implement-update-group
feat(group): implement update group feature with validation and API integration
1 parent ab01a0a commit a4eaf01

13 files changed

Lines changed: 304 additions & 44 deletions

File tree

core/data/src/commonMain/kotlin/org/mifos/core/data/group/GroupRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ interface GroupRepository {
4343
): ScreenState<Unit>
4444
fun getGroupTemplate(): Flow<ScreenState<GroupTemplate>>
4545
suspend fun createGroup(request: CreateGroupRequest): ScreenState<Unit>
46+
suspend fun updateGroup(groupId: Long, name: String): ScreenState<Unit>
4647
}

core/data/src/commonMain/kotlin/org/mifos/core/data/group/impl/GroupRepositoryImpl.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import org.mifos.core.network.DataManager
3232
import org.mifos.core.network.fineract.group.dto.ActivateGroupRequestDto
3333
import org.mifos.core.network.fineract.group.dto.AssociateClientsRequestDto
3434
import org.mifos.core.network.fineract.group.dto.DisassociateClientsRequestDto
35+
import org.mifos.core.network.fineract.group.dto.UpdateGroupRequestDto
3536
import org.mobilenativefoundation.store.store5.Fetcher
3637
import org.mobilenativefoundation.store.store5.Store
3738

@@ -183,4 +184,20 @@ class GroupRepositoryImpl(
183184
}
184185
}
185186
}
187+
188+
override suspend fun updateGroup(groupId: Long, name: String): ScreenState<Unit> {
189+
return runAsDataState(
190+
networkMonitor = networkMonitor,
191+
context = dispatcher.io,
192+
) {
193+
val response = dataManager.fineract.groupApi.updateGroup(
194+
groupId = groupId,
195+
request = UpdateGroupRequestDto(name = name),
196+
)
197+
if (!response.status.isSuccess()) {
198+
val errorMessage = extractErrorMessage(response)
199+
throw Exception(errorMessage)
200+
}
201+
}
202+
}
186203
}

core/network/src/commonMain/kotlin/org/mifos/core/network/fineract/group/apis/GroupApi.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package org.mifos.core.network.fineract.group.apis
1212
import de.jensklingenberg.ktorfit.http.Body
1313
import de.jensklingenberg.ktorfit.http.GET
1414
import de.jensklingenberg.ktorfit.http.POST
15+
import de.jensklingenberg.ktorfit.http.PUT
1516
import de.jensklingenberg.ktorfit.http.Path
1617
import de.jensklingenberg.ktorfit.http.Query
1718
import io.ktor.client.statement.HttpResponse
@@ -25,6 +26,7 @@ import org.mifos.core.network.fineract.group.dto.DisassociateClientsRequestDto
2526
import org.mifos.core.network.fineract.group.dto.GroupAccountsDto
2627
import org.mifos.core.network.fineract.group.dto.GroupDto
2728
import org.mifos.core.network.fineract.group.dto.GroupTemplateResponseDto
29+
import org.mifos.core.network.fineract.group.dto.UpdateGroupRequestDto
2830
import org.mifos.core.network.utils.ApiEndPoints
2931

3032
interface GroupApi {
@@ -84,4 +86,10 @@ interface GroupApi {
8486
suspend fun createGroup(
8587
@Body request: CreateGroupRequestDto,
8688
): HttpResponse
89+
90+
@PUT("${ApiEndPoints.GROUPS}/{groupId}")
91+
suspend fun updateGroup(
92+
@Path("groupId") groupId: Long,
93+
@Body request: UpdateGroupRequestDto,
94+
): HttpResponse
8795
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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 See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
9+
*/
10+
package org.mifos.core.network.fineract.group.dto
11+
12+
import kotlinx.serialization.Serializable
13+
14+
@Serializable
15+
data class UpdateGroupRequestDto(
16+
val name: String,
17+
)

feature/groups/src/commonMain/composeResources/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,7 @@
9191
<string name="feature_groups_activate_group_success_title">Success</string>
9292
<string name="feature_groups_activate_group_success_message">Group activated successfully.</string>
9393
<string name="feature_groups_activate_add_members">Add Members</string>
94+
<string name="feature_groups_update_group_success_title">Success</string>
95+
<string name="feature_groups_update_group_success_message">Group updated successfully.</string>
96+
<string name="feature_groups_update_group_subtitle">Update group details</string>
9497
</resources>

feature/groups/src/commonMain/kotlin/org/mifos/feature/groups/createGroup/CreateGroupRoute.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,22 @@ import kotlinx.serialization.Serializable
1616
import org.mifos.core.base.ui.nav.composableWithStayTransitions
1717

1818
@Serializable
19-
data object CreateGroupRoute
19+
data class CreateGroupRoute(val groupId: Long? = null)
2020

2121
fun NavGraphBuilder.createGroupDestination(
2222
onBackClick: () -> Unit,
23+
onNavigateToGroupDashboardWithUpdateData: () -> Unit,
24+
onNavigateToGroupDetailWithUpdateData: (Long) -> Unit,
2325
) {
2426
composableWithStayTransitions<CreateGroupRoute> {
2527
CreateGroupScreen(
2628
onBackClick = onBackClick,
29+
onNavigateToGroupDetailWithUpdateData = onNavigateToGroupDetailWithUpdateData,
30+
onNavigateToGroupDashboardWithUpdateData = onNavigateToGroupDashboardWithUpdateData,
2731
)
2832
}
2933
}
3034

31-
fun NavController.navigateToCreateGroup(navOptions: NavOptions? = null) {
32-
this.navigate(route = CreateGroupRoute, navOptions = navOptions)
35+
fun NavController.navigateToCreateGroup(groupId: Long? = null, navOptions: NavOptions? = null) {
36+
this.navigate(route = CreateGroupRoute(groupId), navOptions = navOptions)
3337
}

feature/groups/src/commonMain/kotlin/org/mifos/feature/groups/createGroup/CreateGroupScreen.kt

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import org.mifos.feature.groups.generated.resources.feature_groups_activate_add_
5151
import org.mifos.feature.groups.generated.resources.feature_groups_activate_group_toggle
5252
import org.mifos.feature.groups.generated.resources.feature_groups_activation_date_hint
5353
import org.mifos.feature.groups.generated.resources.feature_groups_cancel
54-
import org.mifos.feature.groups.generated.resources.feature_groups_create_group_button
5554
import org.mifos.feature.groups.generated.resources.feature_groups_create_group_subtitle
5655
import org.mifos.feature.groups.generated.resources.feature_groups_create_group_success_message
5756
import org.mifos.feature.groups.generated.resources.feature_groups_create_group_success_title
@@ -68,10 +67,16 @@ import org.mifos.feature.groups.generated.resources.feature_groups_select_activa
6867
import org.mifos.feature.groups.generated.resources.feature_groups_select_office_placeholder
6968
import org.mifos.feature.groups.generated.resources.feature_groups_submitted_on_date_label
7069
import org.mifos.feature.groups.generated.resources.feature_groups_submitted_on_date_placeholder
70+
import org.mifos.feature.groups.generated.resources.feature_groups_update_group
71+
import org.mifos.feature.groups.generated.resources.feature_groups_update_group_subtitle
72+
import org.mifos.feature.groups.generated.resources.feature_groups_update_group_success_message
73+
import org.mifos.feature.groups.generated.resources.feature_groups_update_group_success_title
7174

7275
@Composable
7376
fun CreateGroupScreen(
7477
onBackClick: () -> Unit,
78+
onNavigateToGroupDashboardWithUpdateData: () -> Unit,
79+
onNavigateToGroupDetailWithUpdateData: (Long) -> Unit,
7580
modifier: Modifier = Modifier,
7681
viewModel: CreateGroupViewModel = koinViewModel(),
7782
) {
@@ -80,13 +85,27 @@ fun CreateGroupScreen(
8085
EventsEffect(viewModel.eventFlow) { event ->
8186
when (event) {
8287
CreateGroupEvent.NavigateBack -> onBackClick()
88+
CreateGroupEvent.NavigateToGroupDashboardWithUpdateData ->
89+
onNavigateToGroupDashboardWithUpdateData()
90+
is CreateGroupEvent.NavigateToGroupDetailWithUpdateData ->
91+
onNavigateToGroupDetailWithUpdateData(event.groupId)
8392
}
8493
}
8594

8695
if (state.showSuccessDialog) {
96+
val title = if (state.groupId != null) {
97+
stringResource(Res.string.feature_groups_update_group_success_title)
98+
} else {
99+
stringResource(Res.string.feature_groups_create_group_success_title)
100+
}
101+
val message = if (state.groupId != null) {
102+
stringResource(Res.string.feature_groups_update_group_success_message)
103+
} else {
104+
stringResource(Res.string.feature_groups_create_group_success_message)
105+
}
87106
KptSuccessDialog(
88-
title = stringResource(Res.string.feature_groups_create_group_success_title),
89-
message = stringResource(Res.string.feature_groups_create_group_success_message),
107+
title = title,
108+
message = message,
90109
buttonText = stringResource(Res.string.feature_groups_ok),
91110
onConfirm = { viewModel.trySendAction(CreateGroupAction.DismissSuccessDialog) },
92111
)
@@ -139,9 +158,14 @@ internal fun CreateGroupScreenContent(
139158
},
140159
bottomBar = {
141160
if (state.submitState is SubmitState.Idle || state.submitState is SubmitState.Submitting) {
161+
val rightButtonText = if (state.groupId != null) {
162+
stringResource(Res.string.feature_groups_update_group)
163+
} else {
164+
stringResource(Res.string.feature_groups_create_group_title)
165+
}
142166
KptDoubleButton(
143167
leftButtonText = stringResource(Res.string.feature_groups_cancel),
144-
rightButtonText = stringResource(Res.string.feature_groups_create_group_button),
168+
rightButtonText = rightButtonText,
145169
onRightButtonClick = { onAction(CreateGroupAction.CreateGroup) },
146170
onLeftButtonClick = { onAction(CreateGroupAction.OnBackClick) },
147171
enabledRight = state.isSubmitButtonEnabled,
@@ -197,8 +221,13 @@ private fun CreateGroupFormContent(
197221

198222
VerticalSpacer(KptTheme.spacing.xs)
199223

224+
val title = if (state.groupId != null) {
225+
stringResource(Res.string.feature_groups_update_group)
226+
} else {
227+
stringResource(Res.string.feature_groups_create_group_title)
228+
}
200229
Text(
201-
text = stringResource(Res.string.feature_groups_create_group_title),
230+
text = title,
202231
style = KptTheme.typography.headlineLarge.copy(
203232
fontWeight = FontWeight.ExtraBold,
204233
color = KptTheme.colorScheme.onSurface,
@@ -207,8 +236,13 @@ private fun CreateGroupFormContent(
207236

208237
VerticalSpacer(KptTheme.spacing.xs)
209238

239+
val subtitle = if (state.groupId != null) {
240+
stringResource(Res.string.feature_groups_update_group_subtitle)
241+
} else {
242+
stringResource(Res.string.feature_groups_create_group_subtitle)
243+
}
210244
Text(
211-
text = stringResource(Res.string.feature_groups_create_group_subtitle),
245+
text = subtitle,
212246
style = KptTheme.typography.bodyLarge.copy(
213247
color = KptTheme.colorScheme.onSurfaceVariant,
214248
),
@@ -227,10 +261,11 @@ private fun CreateGroupFormContent(
227261

228262
VerticalSpacer(KptTheme.spacing.md)
229263

230-
// External Id*
264+
// External Id
231265
KptTextField(
232266
value = state.externalId,
233267
onValueChange = { onAction(CreateGroupAction.ExternalIdChanged(it)) },
268+
enabled = (state.groupId == null),
234269
label = stringResource(Res.string.feature_groups_external_id_label),
235270
placeholder = stringResource(Res.string.feature_groups_external_id_placeholder),
236271
modifier = Modifier.fillMaxWidth(),
@@ -242,27 +277,63 @@ private fun CreateGroupFormContent(
242277
value = state.selectedOffice?.name.orEmpty(),
243278
onOptionSelected = { index, _ -> onAction(CreateGroupAction.OfficeSelected(index)) },
244279
options = state.officeOptions.map { it.name },
280+
enabled = (state.groupId == null),
245281
label = stringResource(Res.string.feature_groups_office_label),
246282
placeholder = stringResource(Res.string.feature_groups_select_office_placeholder),
247283
modifier = Modifier.fillMaxWidth(),
248284
)
249285

250286
VerticalSpacer(KptTheme.spacing.sm)
251287

252-
AddMembers(
253-
state = state,
254-
onAction = onAction,
255-
)
288+
if (state.groupId == null) {
289+
AddMembers(
290+
state = state,
291+
onAction = onAction,
292+
)
293+
} else if (state.selectedMembers.isNotEmpty()) {
294+
Column(
295+
verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.xs),
296+
) {
297+
Text(
298+
text = stringResource(Res.string.feature_groups_activate_add_members),
299+
style = KptTheme.typography.bodyMedium.copy(
300+
fontWeight = FontWeight.SemiBold,
301+
color = KptTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
302+
),
303+
modifier = Modifier.padding(bottom = KptTheme.spacing.xs),
304+
)
305+
state.selectedMembers.forEach { member ->
306+
val fullName = "${member.firstname.orEmpty()} ${member.lastname.orEmpty()}"
307+
.trim()
308+
.ifBlank { member.displayName.orEmpty() }
309+
310+
KptSelectableItemCard(
311+
title = fullName,
312+
subtitle = member.accountNo.orEmpty(),
313+
leadingIcon = AppIcons.Person,
314+
isSelected = false,
315+
isInSelectionMode = false,
316+
onClick = {},
317+
onLongClick = {},
318+
)
319+
}
320+
}
321+
}
256322

257323
VerticalSpacer(KptTheme.spacing.sm)
258324

259325
KptTextField(
260326
value = state.submittedOnDate,
261327
onValueChange = {},
262328
readOnly = true,
329+
enabled = (state.groupId == null),
263330
label = stringResource(Res.string.feature_groups_submitted_on_date_label),
264331
placeholder = stringResource(Res.string.feature_groups_submitted_on_date_placeholder),
265-
onCalenderClick = { onAction(CreateGroupAction.SubmittedOnDatePickerToggle(true)) },
332+
onCalenderClick = if (state.groupId == null) {
333+
{ onAction(CreateGroupAction.SubmittedOnDatePickerToggle(true)) }
334+
} else {
335+
null
336+
},
266337
modifier = Modifier.fillMaxWidth(),
267338
)
268339

@@ -271,6 +342,7 @@ private fun CreateGroupFormContent(
271342
// Activate Group Radio Toggle
272343
KptRadioToggle(
273344
selected = state.isActiveGroup,
345+
enabled = (state.groupId == null),
274346
onClick = {
275347
onAction(CreateGroupAction.ActivateGroupToggled(!state.isActiveGroup))
276348
},
@@ -285,9 +357,14 @@ private fun CreateGroupFormContent(
285357
value = state.activationDate,
286358
onValueChange = {},
287359
readOnly = true,
360+
enabled = (state.groupId == null),
288361
label = stringResource(Res.string.feature_groups_select_activation_date),
289362
placeholder = stringResource(Res.string.feature_groups_activation_date_hint),
290-
onCalenderClick = { onAction(CreateGroupAction.ActivationDatePickerToggle(true)) },
363+
onCalenderClick = if (state.groupId == null) {
364+
{ onAction(CreateGroupAction.ActivationDatePickerToggle(true)) }
365+
} else {
366+
null
367+
},
291368
modifier = Modifier.fillMaxWidth(),
292369
)
293370
}

0 commit comments

Comments
 (0)