Skip to content

Commit c6bf0ce

Browse files
authored
Implement client details update functionality (#2470)
1 parent d8855f8 commit c6bf0ce

21 files changed

Lines changed: 1685 additions & 45 deletions

File tree

cmp-navigation/src/commonMain/kotlin/cmp/navigation/ComposeApp.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fun ComposeApp(
6464

6565
MifosTheme(
6666
isDarkTheme,
67-
dynamicColor = useDynamicColors
67+
dynamicColor = useDynamicColors,
6868
) {
6969
RootNavGraph(
7070
networkMonitor = networkMonitor,

core/data/src/commonMain/kotlin/com/mifos/core/data/di/RepositoryModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.mifos.core.data.repository.ChargeDialogRepository
1717
import com.mifos.core.data.repository.CheckerInboxRepository
1818
import com.mifos.core.data.repository.CheckerInboxTasksRepository
1919
import com.mifos.core.data.repository.ClientChargeRepository
20+
import com.mifos.core.data.repository.ClientDetailsEditRepository
2021
import com.mifos.core.data.repository.ClientDetailsRepository
2122
import com.mifos.core.data.repository.ClientIdentifierDialogRepository
2223
import com.mifos.core.data.repository.ClientIdentifiersRepository
@@ -78,6 +79,7 @@ import com.mifos.core.data.repositoryImp.ChargeDialogRepositoryImp
7879
import com.mifos.core.data.repositoryImp.CheckerInboxRepositoryImp
7980
import com.mifos.core.data.repositoryImp.CheckerInboxTasksRepositoryImp
8081
import com.mifos.core.data.repositoryImp.ClientChargeRepositoryImp
82+
import com.mifos.core.data.repositoryImp.ClientDetailsEditRepositoryImpl
8183
import com.mifos.core.data.repositoryImp.ClientDetailsRepositoryImp
8284
import com.mifos.core.data.repositoryImp.ClientIdentifierDialogRepositoryImp
8385
import com.mifos.core.data.repositoryImp.ClientIdentifiersRepositoryImp
@@ -152,6 +154,7 @@ val RepositoryModule = module {
152154
singleOf(::ClientIdentifierDialogRepositoryImp) bind ClientIdentifierDialogRepository::class
153155
singleOf(::ClientIdentifiersRepositoryImp) bind ClientIdentifiersRepository::class
154156
singleOf(::CreateNewClientRepositoryImp) bind CreateNewClientRepository::class
157+
singleOf(::ClientDetailsEditRepositoryImpl) bind ClientDetailsEditRepository::class
155158
singleOf(::PinPointClientRepositoryImp) bind PinPointClientRepository::class
156159

157160
// Center
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2025 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.repository
11+
12+
import com.mifos.room.entities.client.ClientPayloadEntity
13+
14+
interface ClientDetailsEditRepository {
15+
suspend fun updateClient(clientId: Int, clientPayload: ClientPayloadEntity): Int?
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2025 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.repositoryImp
11+
12+
import com.mifos.core.data.repository.ClientDetailsEditRepository
13+
import com.mifos.core.network.datamanager.DataManagerClient
14+
import com.mifos.room.entities.client.ClientPayloadEntity
15+
16+
class ClientDetailsEditRepositoryImpl(
17+
private val dataManagerClient: DataManagerClient,
18+
) : ClientDetailsEditRepository {
19+
override suspend fun updateClient(clientId: Int, clientPayload: ClientPayloadEntity): Int? {
20+
return dataManagerClient.updateClient(clientId, clientPayload)
21+
}
22+
}

core/database/src/commonMain/kotlin/com/mifos/room/entities/client/ClientPayloadEntity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ data class ClientPayloadEntity(
4141

4242
val middlename: String? = null,
4343

44+
val emailAddress: String? = null,
45+
4446
val officeId: Int? = null,
4547

4648
val staffId: Int? = null,

core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt

Lines changed: 130 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
*/
1010
package com.mifos.core.designsystem.component
1111

12+
import androidx.compose.animation.AnimatedContent
1213
import androidx.compose.animation.AnimatedVisibility
1314
import androidx.compose.foundation.interaction.MutableInteractionSource
1415
import androidx.compose.foundation.interaction.collectIsFocusedAsState
16+
import androidx.compose.foundation.layout.fillMaxSize
1517
import androidx.compose.foundation.layout.fillMaxWidth
1618
import androidx.compose.foundation.layout.padding
1719
import androidx.compose.foundation.text.KeyboardActions
1820
import androidx.compose.foundation.text.KeyboardOptions
1921
import androidx.compose.material.icons.Icons
20-
import androidx.compose.material.icons.filled.CalendarToday
22+
import androidx.compose.material.icons.filled.CalendarMonth
2123
import androidx.compose.material.icons.filled.Clear
2224
import androidx.compose.material.icons.filled.Email
2325
import androidx.compose.material.icons.filled.Person
@@ -35,13 +37,18 @@ import androidx.compose.runtime.Composable
3537
import androidx.compose.runtime.getValue
3638
import androidx.compose.runtime.mutableStateOf
3739
import androidx.compose.runtime.remember
40+
import androidx.compose.runtime.rememberUpdatedState
3841
import androidx.compose.runtime.setValue
3942
import androidx.compose.ui.Modifier
43+
import androidx.compose.ui.draw.clip
4044
import androidx.compose.ui.graphics.Shape
4145
import androidx.compose.ui.graphics.vector.ImageVector
4246
import androidx.compose.ui.platform.LocalDensity
4347
import androidx.compose.ui.platform.testTag
48+
import androidx.compose.ui.semantics.contentDescription
49+
import androidx.compose.ui.semantics.semantics
4450
import androidx.compose.ui.text.TextStyle
51+
import androidx.compose.ui.text.font.FontWeight
4552
import androidx.compose.ui.text.input.ImeAction
4653
import androidx.compose.ui.text.input.KeyboardCapitalization
4754
import androidx.compose.ui.text.input.KeyboardType
@@ -51,6 +58,8 @@ import androidx.compose.ui.text.input.VisualTransformation
5158
import androidx.compose.ui.text.style.TextOverflow
5259
import androidx.compose.ui.unit.dp
5360
import androidx.compose.ui.unit.sp
61+
import com.mifos.core.designsystem.icon.MifosIcons
62+
import com.mifos.core.designsystem.theme.DesignToken
5463
import com.mifos.core.designsystem.theme.MifosTheme
5564
import com.mifos.core.designsystem.theme.MifosTypography
5665
import org.jetbrains.compose.ui.tooling.preview.Preview
@@ -240,7 +249,7 @@ fun MifosOutlinedTextField(
240249
onValueChange: (String) -> Unit,
241250
label: String,
242251
error: String?,
243-
modifier: Modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp),
252+
modifier: Modifier = Modifier.fillMaxWidth().clip(DesignToken.shapes.medium),
244253
maxLines: Int = 1,
245254
readOnly: Boolean = false,
246255
singleLine: Boolean = true,
@@ -251,6 +260,10 @@ fun MifosOutlinedTextField(
251260
enabled: Boolean = true,
252261
) {
253262
OutlinedTextField(
263+
colors = OutlinedTextFieldDefaults.colors(
264+
focusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
265+
unfocusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
266+
),
254267
value = value,
255268
onValueChange = onValueChange,
256269
label = { Text(label) },
@@ -267,10 +280,17 @@ fun MifosOutlinedTextField(
267280
} else {
268281
null
269282
},
283+
shape = DesignToken.shapes.medium,
270284
trailingIcon = trailingIcon,
271285
maxLines = maxLines,
272286
singleLine = singleLine,
273-
textStyle = MaterialTheme.typography.bodyLarge,
287+
textStyle = TextStyle(
288+
color = MaterialTheme.colorScheme.onSurfaceVariant,
289+
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
290+
lineHeight = 24.sp,
291+
letterSpacing = 0.5f.sp,
292+
fontWeight = FontWeight.Normal,
293+
),
274294
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = keyboardType),
275295
visualTransformation = visualTransformation,
276296
isError = error != null,
@@ -285,10 +305,6 @@ fun MifosOutlinedTextField(
285305
} else {
286306
null
287307
},
288-
colors = OutlinedTextFieldDefaults.colors(
289-
focusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
290-
unfocusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
291-
),
292308
)
293309
}
294310

@@ -324,15 +340,18 @@ private fun PasswordToggleIcon(
324340
private fun ClearIconButton(
325341
showClearIcon: Boolean,
326342
clearIcon: ImageVector,
327-
modifier: Modifier = Modifier,
328343
onClickClearIcon: () -> Unit,
344+
modifier: Modifier = Modifier,
329345
) {
330346
AnimatedVisibility(
331347
visible = showClearIcon,
348+
modifier = modifier,
332349
) {
333350
IconButton(
334351
onClick = onClickClearIcon,
335-
modifier = modifier,
352+
modifier = Modifier.semantics {
353+
contentDescription = "clearIcon"
354+
},
336355
) {
337356
Icon(
338357
imageVector = clearIcon,
@@ -344,34 +363,129 @@ private fun ClearIconButton(
344363

345364
@Composable
346365
fun MifosDatePickerTextField(
366+
enabled: Boolean = true,
347367
value: String,
348-
modifier: Modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp),
368+
modifier: Modifier = Modifier
369+
.fillMaxSize()
370+
.clip(DesignToken.shapes.medium),
349371
label: String? = null,
350372
openDatePicker: () -> Unit,
351373
) {
352374
OutlinedTextField(
375+
enabled = enabled,
376+
colors = OutlinedTextFieldDefaults.colors(
377+
focusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
378+
unfocusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
379+
),
353380
value = value,
354381
onValueChange = { },
355382
label = { Text(text = label?.let { label } ?: "") },
356383
readOnly = true,
357384
modifier = modifier,
385+
shape = DesignToken.shapes.medium,
358386
maxLines = 1,
359387
textStyle = LocalDensity.current.run {
360-
TextStyle(fontSize = 18.sp)
388+
TextStyle(
389+
color = MaterialTheme.colorScheme.onSurfaceVariant,
390+
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
391+
lineHeight = 24.sp,
392+
letterSpacing = 0.5f.sp,
393+
fontWeight = FontWeight.Normal,
394+
)
361395
},
362396
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
363397
trailingIcon = {
364398
IconButton(onClick = { openDatePicker() }) {
365-
Icon(imageVector = Icons.Default.CalendarToday, null)
399+
Icon(imageVector = Icons.Default.CalendarMonth, null)
366400
}
367401
},
368-
colors = OutlinedTextFieldDefaults.colors(
369-
focusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
370-
unfocusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
371-
),
372402
)
373403
}
374404

405+
@Composable
406+
fun MifosOutlinedTextField(
407+
value: String,
408+
onValueChange: (String) -> Unit,
409+
label: String,
410+
modifier: Modifier = Modifier,
411+
shape: Shape = OutlinedTextFieldDefaults.shape,
412+
colors: TextFieldColors = OutlinedTextFieldDefaults.colors(
413+
focusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
414+
unfocusedBorderColor = MaterialTheme.colorScheme.secondaryContainer,
415+
errorBorderColor = MaterialTheme.colorScheme.error,
416+
),
417+
textStyle: TextStyle = LocalTextStyle.current,
418+
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
419+
config: MifosTextFieldConfig = MifosTextFieldConfig(),
420+
onClickClearIcon: () -> Unit = { onValueChange("") },
421+
) {
422+
val isFocused by interactionSource.collectIsFocusedAsState()
423+
val showIcon by rememberUpdatedState(value.isNotEmpty())
424+
425+
OutlinedTextField(
426+
shape = shape,
427+
colors = colors,
428+
value = value,
429+
label = { Text(text = label) },
430+
onValueChange = onValueChange,
431+
textStyle = textStyle,
432+
modifier = modifier.fillMaxWidth(),
433+
enabled = config.enabled,
434+
readOnly = config.readOnly,
435+
visualTransformation = config.visualTransformation,
436+
keyboardOptions = config.keyboardOptions,
437+
keyboardActions = config.keyboardActions,
438+
interactionSource = interactionSource,
439+
singleLine = config.singleLine,
440+
maxLines = config.maxLines,
441+
minLines = config.minLines,
442+
leadingIcon = config.leadingIcon,
443+
isError = config.isError,
444+
trailingIcon = @Composable {
445+
AnimatedContent(
446+
targetState = config.showClearIcon && isFocused && showIcon,
447+
) {
448+
if (it) {
449+
ClearIconButton(
450+
showClearIcon = true,
451+
clearIcon = config.clearIcon,
452+
onClickClearIcon = onClickClearIcon,
453+
)
454+
} else {
455+
config.trailingIcon?.invoke()
456+
}
457+
}
458+
},
459+
supportingText = config.errorText?.let {
460+
{
461+
Text(
462+
modifier = Modifier.testTag("errorTag"),
463+
text = it,
464+
style = MifosTypography.bodySmall,
465+
color = MaterialTheme.colorScheme.error,
466+
)
467+
}
468+
},
469+
)
470+
}
471+
472+
data class MifosTextFieldConfig(
473+
val enabled: Boolean = true,
474+
val showClearIcon: Boolean = true,
475+
val readOnly: Boolean = false,
476+
val clearIcon: ImageVector = MifosIcons.Close,
477+
val isError: Boolean = false,
478+
val errorText: String? = null,
479+
val visualTransformation: VisualTransformation = VisualTransformation.None,
480+
val keyboardActions: KeyboardActions = KeyboardActions.Default,
481+
val singleLine: Boolean = true,
482+
val maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
483+
val minLines: Int = 1,
484+
val keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
485+
val trailingIcon: @Composable (() -> Unit)? = null,
486+
val leadingIcon: @Composable (() -> Unit)? = null,
487+
)
488+
375489
@Preview
376490
@Composable
377491
private fun MifosOutlinedTextField_ValuePreview() {

0 commit comments

Comments
 (0)