diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsEditRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsEditRepository.kt index c69601c7959..2d260d18f82 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsEditRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsEditRepository.kt @@ -11,6 +11,8 @@ package com.mifos.core.data.repository import com.mifos.room.entities.client.ClientPayloadEntity + interface ClientDetailsEditRepository { + suspend fun updateClient(clientId: Int, clientPayload: ClientPayloadEntity): Int? } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsRepository.kt index 885c1916a39..bf1f014d83d 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ClientDetailsRepository.kt @@ -20,12 +20,14 @@ import com.mifos.room.entities.accounts.ClientAccounts import com.mifos.room.entities.client.ClientEntity import io.ktor.client.request.forms.MultiPartFormDataContent import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow /** * Created by Aditya Gupta on 06/08/23. */ interface ClientDetailsRepository { + val clientDataUpdated: SharedFlow // Emits clientId when updated suspend fun uploadClientImage(clientId: Int, image: MultiPartFormDataContent) suspend fun deleteClientImage(clientId: Int) diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsEditRepositoryImpl.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsEditRepositoryImpl.kt index f0ee995d113..e6d6d3a1e21 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsEditRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsEditRepositoryImpl.kt @@ -16,6 +16,7 @@ import com.mifos.room.entities.client.ClientPayloadEntity class ClientDetailsEditRepositoryImpl( private val dataManagerClient: DataManagerClient, ) : ClientDetailsEditRepository { + override suspend fun updateClient(clientId: Int, clientPayload: ClientPayloadEntity): Int? { return dataManagerClient.updateClient(clientId, clientPayload) } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsRepositoryImp.kt index d2e697cc1a1..3cad34a17a6 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ClientDetailsRepositoryImp.kt @@ -23,6 +23,9 @@ import com.mifos.room.entities.accounts.ClientAccounts import com.mifos.room.entities.client.ClientEntity import io.ktor.client.request.forms.MultiPartFormDataContent import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow /** * Created by Aditya Gupta on 06/08/23. @@ -31,12 +34,21 @@ class ClientDetailsRepositoryImp( private val dataManagerClient: DataManagerClient, ) : ClientDetailsRepository { + private val _clientDataUpdated = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1 + ) + + override val clientDataUpdated: SharedFlow = _clientDataUpdated.asSharedFlow() + override suspend fun uploadClientImage(clientId: Int, image: MultiPartFormDataContent) { dataManagerClient.uploadClientImage(clientId, image) + _clientDataUpdated.tryEmit(clientId) } override suspend fun deleteClientImage(clientId: Int) { dataManagerClient.deleteClientImage(clientId) + _clientDataUpdated.tryEmit(clientId) } override suspend fun getClientAccounts(clientId: Int): ClientAccounts { diff --git a/core/designsystem/src/commonMain/composeResources/values/strings.xml b/core/designsystem/src/commonMain/composeResources/values/strings.xml index 43820ac5143..6eedeca78df 100644 --- a/core/designsystem/src/commonMain/composeResources/values/strings.xml +++ b/core/designsystem/src/commonMain/composeResources/values/strings.xml @@ -16,6 +16,10 @@ Enter a tenant Cancel Save + Success + Failure + Continue + MifosStatusDialog Mifos diff --git a/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosBasicDialog.kt b/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosBasicDialog.kt index abef577a603..80556b56ab8 100644 --- a/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosBasicDialog.kt +++ b/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosBasicDialog.kt @@ -15,8 +15,11 @@ 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.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -32,6 +35,16 @@ import com.mifos.core.designsystem.theme.MifosTypography import org.jetbrains.compose.ui.tooling.preview.Preview import org.jetbrains.compose.ui.tooling.preview.PreviewParameter import org.jetbrains.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Error +import androidx.compose.ui.graphics.Color +import core.designsystem.generated.resources.Res +import core.designsystem.generated.resources.core_designsystem_dialog_continue +import core.designsystem.generated.resources.core_designsystem_dialog_failure +import core.designsystem.generated.resources.core_designsystem_dialog_success +import core.designsystem.generated.resources.core_designsystem_mifosStatusDialog +import org.jetbrains.compose.resources.stringResource @Composable fun MifosBasicDialog( @@ -197,6 +210,73 @@ fun MifosBasicDialog( ) } + + +enum class MifosDialogStatus { SUCCESS, FAILURE } + +@Composable +fun MifosStatusDialog( + status: MifosDialogStatus, + message: String, + onDismissRequest: () -> Unit +) { + data class DialogUI( + val title: String, + val icon: androidx.compose.ui.graphics.vector.ImageVector, + val color: Color + ) + + val dialogUI: DialogUI = when (status) { + MifosDialogStatus.SUCCESS -> DialogUI( + title = stringResource(Res.string.core_designsystem_dialog_success), + icon = Icons.Filled.CheckCircle, + color = Color(0xFF4CAF50) + ) + MifosDialogStatus.FAILURE -> DialogUI( + title = stringResource(Res.string.core_designsystem_dialog_failure), + icon = Icons.Filled.Error, + color = Color(0xFFF44336) + ) + } + + AlertDialog( + onDismissRequest = onDismissRequest, + icon = { + Icon( + imageVector = dialogUI.icon, + contentDescription = dialogUI.title, + tint = dialogUI.color, + modifier = Modifier + .size(84.dp) + .padding(bottom = 4.dp) + ) + }, + title = { + Text( + text = dialogUI.title, + style = MaterialTheme.typography.titleLarge + ) + }, + text = { + Text( + text = message, + style = MaterialTheme.typography.bodyMedium + ) + }, + confirmButton = { + Button( + onClick = onDismissRequest, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Text(stringResource(Res.string.core_designsystem_dialog_continue)) + } + }, + modifier = Modifier.testTag(stringResource(Res.string.core_designsystem_mifosStatusDialog)) + ) +} + @Preview @Composable private fun MifosBasicDialog_preview() { diff --git a/feature/client/src/commonMain/composeResources/values/strings.commonMain.cvr b/feature/client/src/commonMain/composeResources/values/strings.commonMain.cvr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/feature/client/src/commonMain/composeResources/values/strings.xml b/feature/client/src/commonMain/composeResources/values/strings.xml index 944d1e95540..a675eccdaac 100644 --- a/feature/client/src/commonMain/composeResources/values/strings.xml +++ b/feature/client/src/commonMain/composeResources/values/strings.xml @@ -398,6 +398,11 @@ I Confirm Cancel Continue + Your profile image has been updated successfully! + We couldn’t update your profile image. Please try again. + should refresh + Success + Failure Assign Staff diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetailsProfile/ClientProfileDetailsViewModel.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetailsProfile/ClientProfileDetailsViewModel.kt index 6ab45c13418..c9ba9fa0046 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetailsProfile/ClientProfileDetailsViewModel.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientDetailsProfile/ClientProfileDetailsViewModel.kt @@ -35,6 +35,7 @@ import com.mifos.core.ui.util.imageToByteArray import com.mifos.core.ui.util.toDateString import com.mifos.feature.client.clientDetailsProfile.components.ClientProfileDetailsActionItem import com.mifos.room.entities.client.ClientEntity +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.StringResource @@ -62,6 +63,7 @@ internal class ClientProfileDetailsViewModel( init { getClientAndObserveNetwork() + observeClientUpdates() } private fun getClientAndObserveNetwork() { @@ -69,6 +71,16 @@ internal class ClientProfileDetailsViewModel( loadClientDetailsAndImage(route.id) } + private fun observeClientUpdates() { + viewModelScope.launch { + clientDetailsRepo.clientDataUpdated + .filter { updatedClientId -> updatedClientId == route.id } + .collect { + loadClientDetailsAndImage(route.id) + } + } + } + /** * Fetches both client details and profile image. * diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditDetails/ClientEditDetailsViewModel.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditDetails/ClientEditDetailsViewModel.kt index 200fffa0970..377a79c9702 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditDetails/ClientEditDetailsViewModel.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditDetails/ClientEditDetailsViewModel.kt @@ -29,6 +29,7 @@ import com.mifos.room.entities.organisation.OfficeEntity import com.mifos.room.entities.organisation.StaffEntity import com.mifos.room.entities.templates.clients.ClientsTemplateEntity import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString @@ -43,10 +44,6 @@ internal class ClientEditDetailsViewModel( ) { val route = savedStateHandle.toRoute() - init { - loadClientDetails(route.id) - } - fun loadClientDetails(clientId: Int = route.id) { viewModelScope.launch { getClientDetailsUseCase(clientId).collect { result -> diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditScreen.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditScreen.kt index 3e38b32166a..9fc22670623 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditScreen.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditScreen.kt @@ -17,11 +17,17 @@ import androidclient.feature.client.generated.resources.choose_from_option import androidclient.feature.client.generated.resources.delete_dialog_message import androidclient.feature.client.generated.resources.delete_dialog_title import androidclient.feature.client.generated.resources.delete_photo +import androidclient.feature.client.generated.resources.dialog_continue import androidclient.feature.client.generated.resources.edit_profile_title +import androidclient.feature.client.generated.resources.feature_client_Image_Upload_Failed +import androidclient.feature.client.generated.resources.feature_client_Image_Upload_Successful +import androidclient.feature.client.generated.resources.feature_client_error import androidclient.feature.client.generated.resources.from_camera import androidclient.feature.client.generated.resources.from_gallery +//import androidclient.feature.client.generated.resources.profile_update_error_message import androidclient.feature.client.generated.resources.remove import androidclient.feature.client.generated.resources.update_profile_photo_message +//import androidclient.feature.client.generated.resources.update_success_message import androidclient.feature.client.generated.resources.upload_new_photo import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -37,7 +43,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -48,8 +56,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import com.mifos.core.designsystem.component.BasicDialogState import com.mifos.core.designsystem.component.MifosBasicDialog +import com.mifos.core.designsystem.component.MifosDialogStatus import com.mifos.core.designsystem.component.MifosOutlinedButton import com.mifos.core.designsystem.component.MifosScaffold +import com.mifos.core.designsystem.component.MifosStatusDialog import com.mifos.core.designsystem.component.MifosTextButton import com.mifos.core.designsystem.icon.MifosIcons import com.mifos.core.designsystem.theme.DesignToken @@ -76,11 +86,21 @@ internal fun ClientProfileEditScreen( ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() - EventsEffect(viewModel.eventFlow) { event -> - when (event) { - ClientProfileEditEvent.NavigateBack -> onNavigateBack() - ClientProfileEditEvent.OnSaveSuccess -> { - onNavigateBack() + var hasShownSuccess by remember { mutableStateOf(false) } + + LaunchedEffect(state.dialogState) { + when (state.dialogState) { + is ClientProfileEditState.DialogState.Success -> { + hasShownSuccess = true + } + null -> { + if (hasShownSuccess) { + hasShownSuccess = false + onNavigateBack() + } + } + else -> { + hasShownSuccess = false } } } @@ -198,11 +218,18 @@ private fun ClientProfileEditDialogs( is ClientProfileEditState.DialogState.Loading -> MifosProgressIndicator() is ClientProfileEditState.DialogState.Error -> { - MifosErrorComponent( - isNetworkConnected = state.networkConnection, - message = state.dialogState.message, - isRetryEnabled = true, - onRetry = onRetry, + MifosStatusDialog( + status = MifosDialogStatus.FAILURE, + message = stringResource(Res.string.feature_client_error), + onDismissRequest = { onAction(ClientProfileEditAction.DismissModalBottomSheet) } + ) + } + + is ClientProfileEditState.DialogState.Success -> { + MifosStatusDialog( + status = MifosDialogStatus.SUCCESS, + message = stringResource(Res.string.feature_client_Image_Upload_Successful), + onDismissRequest = { onAction(ClientProfileEditAction.DismissModalBottomSheet) } ) } diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditViewModel.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditViewModel.kt index cf2e40fad8a..220ff91a560 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditViewModel.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientEditProfile/ClientProfileEditViewModel.kt @@ -9,6 +9,8 @@ */ package com.mifos.feature.client.clientEditProfile +import androidclient.feature.client.generated.resources.Res +import androidclient.feature.client.generated.resources.unknown_error import androidx.compose.ui.graphics.ImageBitmap import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope @@ -123,6 +125,7 @@ internal class ClientProfileEditViewModel( } } + is DataState.Loading -> { mutableStateFlow.update { it.copy( @@ -132,13 +135,29 @@ internal class ClientProfileEditViewModel( } is DataState.Success -> { - mutableStateFlow.update { - it.copy( - dialogState = ClientProfileEditState.DialogState.Loading, - openImagePicker = false, - ) + viewModelScope.launch { + clientDetailsRepo.getImage(route.id).collect { result -> + when (result) { + is DataState.Success -> mutableStateFlow.update { + it.copy( + profileImage = imageToByteArray(result.data), + dialogState = ClientProfileEditState.DialogState.Success, + ) + } + is DataState.Loading -> mutableStateFlow.update { + it.copy(dialogState = ClientProfileEditState.DialogState.Loading) + } + is DataState.Error -> mutableStateFlow.update { + it.copy( + dialogState = ClientProfileEditState.DialogState.Error( + result.message, + ), + ) + } + else -> Unit + } + } } - loadImage(route.id) } } } @@ -179,14 +198,18 @@ data class ClientProfileEditState( sealed interface DialogState { data class Error(val message: String) : DialogState data object Loading : DialogState + data object Success : DialogState data object ShowDeleteDialog : DialogState data object ShowUploadOptions : DialogState + } } sealed interface ClientProfileEditEvent { data object NavigateBack : ClientProfileEditEvent data object OnSaveSuccess : ClientProfileEditEvent + data class OnError(val message: String) : ClientProfileEditEvent + } sealed interface ClientProfileEditAction { @@ -202,3 +225,4 @@ sealed interface ClientProfileEditAction { data class UpdateImagePicker(val status: Boolean) : ClientProfileEditAction data class OnImageSelected(val image: ImageBitmap) : ClientProfileEditAction } + diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileScreen.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileScreen.kt index 59da6e15079..4064ea6e6c9 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileScreen.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileScreen.kt @@ -63,6 +63,7 @@ internal fun ClientProfileScreen( ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() + EventsEffect(viewModel.eventFlow) { event -> when (event) { ClientProfileEvent.NavigateBack -> { diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileViewModel.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileViewModel.kt index 83df4d9f265..c8b6d0ca9d9 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileViewModel.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientProfile/ClientProfileViewModel.kt @@ -20,6 +20,7 @@ import com.mifos.core.ui.util.BaseViewModel import com.mifos.core.ui.util.imageToByteArray import com.mifos.feature.client.clientProfile.components.ClientProfileActionItem import com.mifos.room.entities.client.ClientEntity +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.StringResource @@ -46,6 +47,18 @@ internal class ClientProfileViewModel( init { getClientAndObserveNetwork() + observeClientUpdates() + } + + + private fun observeClientUpdates() { + viewModelScope.launch { + clientDetailsRepo.clientDataUpdated + .filter { updatedClientId -> updatedClientId == route.id } + .collect { + loadClientDetailsAndImage(route.id) + } + } } private fun getClientAndObserveNetwork() {