-
Notifications
You must be signed in to change notification settings - Fork 650
Client Collateral Datas #2490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Client Collateral Datas #2490
Conversation
Scaffold( | ||
topBar = { | ||
TopAppBar(title = { | ||
val titleText = when (val state = uiState) { | ||
is ClientCollateralUiState.Success -> "Collateral Data (${state.totalItems} ${if (state.totalItems == 1) "Item" else "Items"})" | ||
is ClientCollateralUiState.Empty -> "Collateral Data (0 Items)" | ||
else -> "Collateral Data" | ||
} | ||
Text(text = titleText) | ||
}) | ||
} | ||
) { paddingValues -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read my last three comments first. Then read this one and rest.
You don't have to create your own scaffold, use MIfosScaffold.
Check the code of siblings screens and similar screens to see how we are using it.
If you want to use a component, first check if there is a Mifos component already created for it.
You will find all the created components in the ui module in core module.
You can check the sibling feature screens to see what component they are using. If you don't find one only then create a new component in the current screen's package.
fun CollateralListItem(item: CollateralDisplayItem, onActionClick: () -> Unit) { | ||
Card( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
) { | ||
Column(modifier = Modifier.padding(16.dp)) { | ||
Text("Type/Name: ${item.typeName}", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleMedium) | ||
Spacer(modifier = Modifier.height(4.dp)) | ||
Text("Quantity: ${item.quantity}", style = MaterialTheme.typography.bodyMedium) | ||
Spacer(modifier = Modifier.height(4.dp)) | ||
Text("Unit Value: ${item.unitValue}", style = MaterialTheme.typography.bodyMedium) | ||
Spacer(modifier = Modifier.height(4.dp)) | ||
Text("Total Collateral Value: ${item.totalCollateralValue}", style = MaterialTheme.typography.bodyMedium) | ||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { | ||
IconButton(onClick = onActionClick) { | ||
|
||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use MIfosActiosCollateralDataListingComponent composable here.
It's an already created composable in inside the package com.mifos.core.ui.components.
|
||
@Composable | ||
fun ErrorState(message: String, onRetry: () -> Unit) { | ||
Column( | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
verticalArrangement = Arrangement.Center, | ||
modifier = Modifier.padding(16.dp) | ||
) { | ||
Text("Error: $message", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyLarge) | ||
Spacer(modifier = Modifier.height(16.dp)) | ||
Button(onClick = onRetry) { | ||
Text("Retry") | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the MifosErrorComponent here like this
@Composable
private fun ClientProfileDialogs(
state: ClientProfileState,
onRetry: () -> Unit,
) {
when (state.dialogState) {
is ClientProfileState.DialogState.Loading -> MifosProgressIndicator()
is ClientProfileState.DialogState.Error -> {
MifosErrorComponent(
isNetworkConnected = state.networkConnection,
message = state.dialogState.message,
isRetryEnabled = true,
onRetry = {
onRetry()
},
)
}
null -> Unit
}
}
@Composable | ||
fun EmptyCollateralState() { | ||
Card(modifier = Modifier.padding(16.dp)) { | ||
Column( | ||
modifier = Modifier | ||
.padding(32.dp) | ||
.fillMaxWidth(), | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
verticalArrangement = Arrangement.Center | ||
) { | ||
Text("No Item Found", style = MaterialTheme.typography.headlineSmall) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use MifosEmptyCard instead.
like this
if (state.recurringDepositAccounts.isEmpty()) {
MifosEmptyCard(msg = stringResource(Res.string.client_empty_card_message))
} else {
LazyColumn {
package com.mifos.feature.loan.ClientCollateral | ||
|
||
import androidx.lifecycle.SavedStateHandle | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.mifos.core.common.utils.DataState | ||
import com.mifos.core.data.repository.ClientDetailsRepository | ||
|
||
import com.mifos.core.network.model.CollateralItem | ||
import com.mifos.feature.loan.ClientCollateral.ClientCollateralUiState.* | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.launch | ||
|
||
class ClientCollateralViewModel( | ||
private val savedStateHandle: SavedStateHandle, // Assuming we might need clientId/groupId from nav args | ||
private val clientDetailsRepository: ClientDetailsRepository | ||
) : ViewModel() { | ||
|
||
private val _uiState = MutableStateFlow<ClientCollateralUiState>(ClientCollateralUiState.Loading) | ||
val uiState: StateFlow<ClientCollateralUiState> = _uiState.asStateFlow() | ||
|
||
|
||
private val clientId: Int? = savedStateHandle.get<Int>("clientIdKey") | ||
|
||
init { | ||
loadCollateralItems() | ||
} | ||
|
||
fun loadCollateralItems() { | ||
if (clientId == null) { | ||
_uiState.value = ClientCollateralUiState.Error("Client ID not found") | ||
return | ||
} | ||
|
||
viewModelScope.launch { | ||
_uiState.value = ClientCollateralUiState.Loading | ||
when (val result = clientDetailsRepository.getCollateralItems()) { | ||
is DataState.Success -> { | ||
val networkItems = result.data | ||
if (networkItems.isEmpty()) { | ||
_uiState.value = ClientCollateralUiState.Empty | ||
} else { | ||
val displayItems = networkItems.mapNotNull { transformToDisplayItem(it) } | ||
if (displayItems.isEmpty() && networkItems.isNotEmpty()) { | ||
// This case means all items failed to parse quantity, which is an error | ||
_uiState.value = Error("Error parsing collateral data") | ||
} else { | ||
_uiState.value = Success(displayItems, displayItems.size) | ||
} | ||
} | ||
} | ||
is DataState.Error -> { | ||
_uiState.value = Error(result.exception.message ?: "Unknown error") | ||
} | ||
|
||
DataState.Loading -> TODO() | ||
} | ||
} | ||
} | ||
|
||
private fun transformToDisplayItem(networkItem: CollateralItem): CollateralDisplayItem? { | ||
val quantity = networkItem.quality.toIntOrNull() | ||
return if (quantity != null) { | ||
CollateralDisplayItem( | ||
id = networkItem.id, | ||
typeName = networkItem.name, | ||
quantity = quantity, | ||
unitValue = networkItem.basePrice, | ||
totalCollateralValue = quantity * networkItem.basePrice | ||
) | ||
} else { | ||
|
||
null | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ankitkumarrain
search on internet about MVI architecture and learn about it.
See how and why we separate ui actions, events and ui state.
Then see inside the viewmodel of other screens, how we are doing it.
If you need help then just ask.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have to implement mvvm pattern
class ClientCollateralViewModel( | ||
private val savedStateHandle: SavedStateHandle, // Assuming we might need clientId/groupId from nav args | ||
private val clientDetailsRepository: ClientDetailsRepository | ||
) : ViewModel() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use BaseViewModel class. It is present inside the com.mifos.core.ui.util package.
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
// Koin ViewModel import | ||
import org.koin.compose.viewmodel.koinViewModel | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You needed help with navigation right.
So, in this project we are using type safe navigation and also nested nav graphs.
I will give you a simple analogy.
Consider a Navgraph like your house (collection of multiple rooms). All houses have a door to enter inside, (now don't think what about windows or balcony), and it always opens inside one room always. Just like that a navgraph is a collection of multiple navigation destinations (screen). When you navigate to a navgraph there is always a screen set a startdestination that opens first..
And just like from inside of your room you can go inside multiple other room, so, just like that you can go to multiple other screens from that screen.
You won't create a navgraph here.
Learn about typesafe navigation and then see how we are using it.
In typesafe navigation you use serialized data classes instead of string route like in web or in normal string based navigation.
Here is an example:
@Serializable
data class ClientProfileRoute(
val id: Int = -1
)
Instead of using a string you will use such data classes. The arguments you pass along with string routs are instead passed a parameters to the data class.
Also learn about extension functions.
You will create a navigation destination(route) by creating a extension function on the NavGraphBuilder, something like this
NavGraphBuilder.navigateToCleintCollateralRoute
Just look into the client profile screen and see how navigation is done there.
If you need help ask.
} | ||
try{ | ||
val result = repository.getCollateralItems(route.clientId) | ||
mutableStateFlow.update { | ||
it.copy( | ||
isLoading = false, | ||
accounts = result as List<CollateralItem>, | ||
dialogState = null | ||
|
||
|
||
) | ||
} | ||
}catch(e : Exception){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ankit repository.getCollateralItems(route.clientId)
returns DataState<List<CollateralItem>>
, and you are casting it as List<CollateriaItem>
, that is not a valid operation.
See in other viewmodels how we are handling DataState and how to retrieve data from it.
This is the reason you are not getting any data.
|
||
class ClientCollateralViewmodel ( | ||
savedStateHandle: SavedStateHandle, | ||
private val repository: ClientDetailsRepository | ||
): BaseViewModel<collateralUiState,collateralEvent,collateralAction>( | ||
initialState = collateralUiState() | ||
){ | ||
private val route = savedStateHandle.toRoute<clientCollateralRoute>() | ||
override fun handleAction(action: collateralAction) { | ||
when (action) { | ||
is collateralAction.cardClicked -> handleCardClicked(action.activeIndex) | ||
collateralAction.toggleFiler -> toggleFiler() | ||
collateralAction.toggleSearchBar -> toggleSearchBar() | ||
is collateralAction.viewAccount -> sendEvent(collateralEvent.viewAccount(action.accountId)) | ||
collateralAction.refresh -> fetchAllCollateralAccount() | ||
|
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Always write class, interfaces , composable function and objects name in Pascal Case
Eg: CollateralAction.ToggleFilter
} | ||
private fun fetchAllCollateralAccount(){ | ||
viewModelScope.launch { | ||
mutableStateFlow.update{ | ||
it.copy( | ||
isLoading = true, | ||
) | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use a separate state parameter for handling loading. See how we are using DialogState for handling Error and Loading on UI.
You will find something like this in other UiState classes.
data class SomeUiState(
val dialogState: DialogState? = null
){
sealed interface DialogState {
object Loading: DialogState
data class Error(val message: String): DialogState
}
}
private fun toggleFiler() { | ||
mutableStateFlow.update { | ||
it.copy( | ||
isFilterActive = !state.isFilterActive, | ||
) | ||
} | ||
|
||
|
||
} | ||
private fun toggleSearchBar() { | ||
mutableStateFlow.update { | ||
it.copy( | ||
isSearchBarActive = !state.isSearchBarActive, | ||
) | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Write !it.isFilterAcitve and !it.isSearchBarActive instead of using state.
private fun handleCardClicked(index : Int){ | ||
mutableStateFlow.update { | ||
it.copy( | ||
isCardActive = !state.isCardActive, | ||
currentlyActiveIndex = index, | ||
) | ||
} | ||
|
||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
Fixes - Jira-#575
This is my first pr.
Main Goal : It was to implement collateral data modules in the client profile .
I have created a new directory in which i have CollateralUistate , CollateralViewModel, CollateralScreen under under the feature >loan module .