Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ import com.mifos.core.designsystem.icon.MifosIcons
import com.mifos.core.designsystem.theme.identifierTextStyleDark
import com.mifos.core.designsystem.theme.identifierTextStyleLight
import com.mifos.core.model.objects.noncoreobjects.Identifier
import com.mifos.core.model.objects.noncoreobjects.IdentifierPayload
import com.mifos.core.ui.components.MifosEmptyUi
import com.mifos.core.ui.util.DevicePreview
import com.mifos.feature.client.clientIdentifiersDialog.ClientIdentifierDialogUiState
import com.mifos.feature.client.clientIdentifiersDialog.ClientIdentifiersDialogScreen
import com.mifos.feature.client.clientIdentifiersDialog.ClientIdentifiersDialogViewModel
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
Expand All @@ -78,55 +79,44 @@ import org.koin.compose.viewmodel.koinViewModel
internal fun ClientIdentifiersScreen(
onBackPressed: () -> Unit,
onDocumentClicked: (Int) -> Unit,
clientIdentifiersviewModel: ClientIdentifiersViewModel = koinViewModel(),
clientIdentifiersDialogViewModel: ClientIdentifiersDialogViewModel = koinViewModel(),
viewModel: ClientIdentifiersViewModel = koinViewModel(),
) {
val clientId by clientIdentifiersviewModel.clientId.collectAsStateWithLifecycle()
val state by clientIdentifiersviewModel.clientIdentifiersUiState.collectAsStateWithLifecycle()
val refreshState by clientIdentifiersviewModel.isRefreshing.collectAsStateWithLifecycle()
val clientIdentifiersUiState by viewModel.clientIdentifiersUiState.collectAsStateWithLifecycle()
val clientIdentifiersDialogUiState by viewModel.clientIdentifierDialogUiState.collectAsStateWithLifecycle()
val refreshState by viewModel.isRefreshing.collectAsStateWithLifecycle()

ClientIdentifiersScreen(
clientId = clientId,
state = state,
state = clientIdentifiersUiState,
dialogState = clientIdentifiersDialogUiState,
onShowDialog = viewModel::loadClientIdentifierTemplate,
onBackPressed = onBackPressed,
onDeleteIdentifier = { identifierId ->
clientIdentifiersviewModel.deleteIdentifier(clientId, identifierId)
},
refreshState = refreshState,
onRefresh = {
clientIdentifiersviewModel.refreshIdentifiersList(clientId)
viewModel.deleteIdentifier(identifierId)
},
onRetry = {
clientIdentifiersviewModel.loadIdentifiers(clientId)
},
onIdentifierCreated = {
// resetUiState() is needed here to clear the success state immediately after successful
// client identifier creation, so that reopening the dialog doesn’t reuse stale state and
// accidentally retrigger main screen loading.
// Downside: this causes two back-to-back loading states — one in the dialog and one on
// the main screen.
clientIdentifiersDialogViewModel.resetUiState()
clientIdentifiersviewModel.loadIdentifiers(clientId)
onCreateIdentifier = { identifierPayload ->
viewModel.createClientIdentifier(identifierPayload)
},
refreshState = refreshState,
onRefresh = viewModel::refreshIdentifiersList,
onRetry = viewModel::loadIdentifiers,
onDocumentClicked = onDocumentClicked,
onIdentifierDeleted = {
clientIdentifiersviewModel.loadIdentifiers(clientId)
},
reloadIdentifiers = viewModel::loadIdentifiers,
)
}

@Composable
internal fun ClientIdentifiersScreen(
clientId: Int,
state: ClientIdentifiersUiState,
dialogState: ClientIdentifierDialogUiState,
onShowDialog: () -> Unit,
onBackPressed: () -> Unit,
onDeleteIdentifier: (Int) -> Unit,
onCreateIdentifier: (IdentifierPayload) -> Unit,
refreshState: Boolean,
onRefresh: () -> Unit,
onRetry: () -> Unit,
onIdentifierCreated: () -> Unit,
onDocumentClicked: (Int) -> Unit,
onIdentifierDeleted: () -> Unit,
reloadIdentifiers: () -> Unit,
) {
val snackbarHostState = remember { SnackbarHostState() }
val pullToRefreshState = rememberPullToRefreshState()
Expand All @@ -136,13 +126,15 @@ internal fun ClientIdentifiersScreen(

if (showCreateIdentifierDialog) {
ClientIdentifiersDialogScreen(
clientId = clientId,
state = dialogState,
onDismiss = { showCreateIdentifierDialog = false },
onIdentifierCreated = {
showCreateIdentifierDialog = false
showCreateSuccessMessage = true
onIdentifierCreated()
reloadIdentifiers()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try this like a before and why don't you handle the Dialog state from ViewModel

showCreateIdentifierDialog = false

},
onRetry = onRetry,
onCreateIdentifier = onCreateIdentifier,
)
}

Expand All @@ -152,6 +144,7 @@ internal fun ClientIdentifiersScreen(
actions = {
IconButton(
onClick = {
onShowDialog()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And see here, you're showing the dialog but I'm not seeing any codebase which closes this dialog state.

showCreateIdentifierDialog = true
},
) {
Expand Down Expand Up @@ -196,7 +189,7 @@ internal fun ClientIdentifiersScreen(
}

is ClientIdentifiersUiState.IdentifierDeletedSuccessfully -> {
onIdentifierDeleted()
reloadIdentifiers()
scope.launch {
snackbarHostState.showSnackbar(
message = getString(state.message),
Expand Down Expand Up @@ -351,16 +344,17 @@ private fun ClientIdentifiersScreenPreview(
@PreviewParameter(ClientIdentifiersUiStateProvider::class) state: ClientIdentifiersUiState,
) {
ClientIdentifiersScreen(
clientId = 1,
state = state,
dialogState = ClientIdentifierDialogUiState.Loading,
onBackPressed = {},
onDeleteIdentifier = {},
refreshState = true,
onRefresh = {},
onRetry = {},
onIdentifierCreated = {},
onDocumentClicked = {},
onIdentifierDeleted = {},
onShowDialog = {},
reloadIdentifiers = {},
onCreateIdentifier = {},
)
}
val sampleClientIdentifiers = List(10) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,31 @@
package com.mifos.feature.client.clientIdentifiers

import androidclient.feature.client.generated.resources.Res
import androidclient.feature.client.generated.resources.feature_client_failed_to_create_identifier
import androidclient.feature.client.generated.resources.feature_client_failed_to_delete_identifier
import androidclient.feature.client.generated.resources.feature_client_failed_to_load_client_identifiers
import androidclient.feature.client.generated.resources.feature_client_failed_to_load_identifiers
import androidclient.feature.client.generated.resources.feature_client_identifier_deleted_successfully
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mifos.core.common.utils.Constants
import com.mifos.core.common.utils.DataState
import com.mifos.core.data.repository.ClientIdentifiersRepository
import com.mifos.core.domain.useCases.CreateClientIdentifierUseCase
import com.mifos.core.domain.useCases.DeleteIdentifierUseCase
import com.mifos.core.domain.useCases.GetClientIdentifierTemplateUseCase
import com.mifos.core.model.objects.noncoreobjects.IdentifierPayload
import com.mifos.feature.client.clientIdentifiersDialog.ClientIdentifierDialogUiState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class ClientIdentifiersViewModel(
private val clientIdentifiersRepository: ClientIdentifiersRepository,
private val deleteIdentifierUseCase: DeleteIdentifierUseCase,
private val getClientIdentifierTemplateUseCase: GetClientIdentifierTemplateUseCase,
private val createClientIdentifierUseCase: CreateClientIdentifierUseCase,
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {

Expand All @@ -36,21 +44,25 @@ class ClientIdentifiersViewModel(
MutableStateFlow<ClientIdentifiersUiState>(ClientIdentifiersUiState.Loading)
val clientIdentifiersUiState = _clientIdentifiersUiState.asStateFlow()

private val _clientIdentifierDialogUiState =
MutableStateFlow<ClientIdentifierDialogUiState>(ClientIdentifierDialogUiState.Loading)
val clientIdentifierDialogUiState = _clientIdentifierDialogUiState.asStateFlow()

private val _isRefreshing = MutableStateFlow(false)
val isRefreshing = _isRefreshing.asStateFlow()

init {
loadIdentifiers(clientId = clientId.value)
loadIdentifiers()
}

fun refreshIdentifiersList(clientId: Int) {
fun refreshIdentifiersList() {
_isRefreshing.value = true
loadIdentifiers(clientId = clientId)
loadIdentifiers()
_isRefreshing.value = false
}

fun loadIdentifiers(clientId: Int) = viewModelScope.launch {
clientIdentifiersRepository.getClientIdentifiers(clientId).collect { result ->
fun loadIdentifiers() = viewModelScope.launch {
clientIdentifiersRepository.getClientIdentifiers(clientId.value).collect { result ->
when (result) {
is DataState.Error ->
_clientIdentifiersUiState.value =
Expand All @@ -67,8 +79,8 @@ class ClientIdentifiersViewModel(
}
}

fun deleteIdentifier(clientId: Int, identifierId: Int) = viewModelScope.launch {
deleteIdentifierUseCase(clientId, identifierId).collect { result ->
fun deleteIdentifier(identifierId: Int) = viewModelScope.launch {
deleteIdentifierUseCase(clientId.value, identifierId).collect { result ->
when (result) {
is DataState.Error ->
_clientIdentifiersUiState.value =
Expand All @@ -86,4 +98,44 @@ class ClientIdentifiersViewModel(
}
}
}

fun loadClientIdentifierTemplate() = viewModelScope.launch {
getClientIdentifierTemplateUseCase(clientId.value).collect { result ->
when (result) {
is DataState.Error ->
_clientIdentifierDialogUiState.value =
ClientIdentifierDialogUiState.Error(Res.string.feature_client_failed_to_load_identifiers)

is DataState.Loading ->
_clientIdentifierDialogUiState.value =
ClientIdentifierDialogUiState.Loading

is DataState.Success ->
_clientIdentifierDialogUiState.value =
ClientIdentifierDialogUiState.ClientIdentifierTemplate(
result.data,
)
}
}
}

fun createClientIdentifier(identifierPayload: IdentifierPayload) =
viewModelScope.launch {
createClientIdentifierUseCase(clientId.value, identifierPayload).collect { result ->
when (result) {
is DataState.Error ->
_clientIdentifierDialogUiState.value =
ClientIdentifierDialogUiState.Error(Res.string.feature_client_failed_to_create_identifier)

is DataState.Loading ->
_clientIdentifierDialogUiState.value =
ClientIdentifierDialogUiState.Loading

is DataState.Success ->
_clientIdentifierDialogUiState.value =
ClientIdentifierDialogUiState
.IdentifierCreatedSuccessfully
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
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.saveable.rememberSaveable
Expand All @@ -46,7 +45,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.mifos.core.designsystem.component.MifosCircularProgress
import com.mifos.core.designsystem.component.MifosOutlinedTextField
import com.mifos.core.designsystem.component.MifosSweetError
Expand All @@ -58,46 +56,14 @@ import com.mifos.core.ui.util.DevicePreview
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.PreviewParameter
import org.jetbrains.compose.ui.tooling.preview.PreviewParameterProvider
import org.koin.compose.viewmodel.koinViewModel

@Composable
internal fun ClientIdentifiersDialogScreen(
clientId: Int,
onDismiss: () -> Unit,
onIdentifierCreated: () -> Unit,
viewModel: ClientIdentifiersDialogViewModel = koinViewModel(),
) {
val state by viewModel.clientIdentifierDialogUiState.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
viewModel.loadClientIdentifierTemplate(clientId)
}

if (state is ClientIdentifierDialogUiState.IdentifierCreatedSuccessfully) {
LaunchedEffect(Unit) {
onIdentifierCreated()
}
return
}

ClientIdentifiersDialogScreen(
state = state,
onDismiss = onDismiss,
onRetry = {
viewModel.loadClientIdentifierTemplate(clientId = clientId)
},
onCreate = {
viewModel.createClientIdentifier(clientId, it)
},
)
}

@Composable
internal fun ClientIdentifiersDialogScreen(
state: ClientIdentifierDialogUiState,
onDismiss: () -> Unit,
onRetry: () -> Unit,
onCreate: (IdentifierPayload) -> Unit,
onCreateIdentifier: (IdentifierPayload) -> Unit,
onIdentifierCreated: () -> Unit,
) {
Dialog(
onDismissRequest = { onDismiss() },
Expand Down Expand Up @@ -140,7 +106,7 @@ internal fun ClientIdentifiersDialogScreen(
is ClientIdentifierDialogUiState.ClientIdentifierTemplate -> {
ClientIdentifiersContent(
clientIdentifierTemplate = state.identifierTemplate,
onCreate = onCreate,
onCreate = onCreateIdentifier,
)
}

Expand All @@ -150,7 +116,9 @@ internal fun ClientIdentifiersDialogScreen(
onRetry()
}

is ClientIdentifierDialogUiState.IdentifierCreatedSuccessfully -> {}
is ClientIdentifierDialogUiState.IdentifierCreatedSuccessfully -> {
onIdentifierCreated()
}

is ClientIdentifierDialogUiState.Loading -> MifosCircularProgress()
}
Expand Down Expand Up @@ -305,6 +273,7 @@ private fun ClientIdentifiersDialogScreenPreview(
state = state,
onDismiss = {},
onRetry = {},
onCreate = {},
onCreateIdentifier = {},
onIdentifierCreated = {},
)
}
Loading