-
Notifications
You must be signed in to change notification settings - Fork 657
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
Changes from 1 commit
a15eb12
7bdd861
6bfac1e
276d8c8
696a1a5
6aa0bd3
a519ffd
756e103
ff97829
36beb86
9e47b4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| package com.mifos.feature.loan.ClientCollateral | ||
|
|
||
| import androidx.compose.foundation.layout.Arrangement | ||
| import androidx.compose.foundation.layout.Box | ||
| import androidx.compose.foundation.layout.Column | ||
| import androidx.compose.foundation.layout.PaddingValues | ||
| import androidx.compose.foundation.layout.Spacer | ||
| import androidx.compose.foundation.layout.fillMaxSize | ||
| import androidx.compose.foundation.layout.fillMaxWidth | ||
| import androidx.compose.foundation.layout.height | ||
| import androidx.compose.foundation.layout.padding | ||
| import androidx.compose.foundation.lazy.LazyColumn | ||
| import androidx.compose.foundation.lazy.items | ||
|
|
||
| import androidx.compose.material3.Button | ||
| import androidx.compose.material3.Card | ||
| import androidx.compose.material3.CircularProgressIndicator | ||
| import androidx.compose.material3.ExperimentalMaterial3Api | ||
| import androidx.compose.material3.Icon | ||
| import androidx.compose.material3.IconButton | ||
| import androidx.compose.material3.MaterialTheme | ||
| import androidx.compose.material3.Scaffold | ||
| import androidx.compose.material3.Text | ||
| import androidx.compose.material3.TopAppBar | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.ui.Alignment | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.text.font.FontWeight | ||
| import androidx.compose.ui.unit.dp | ||
| import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
| // Koin ViewModel import | ||
| import org.koin.compose.viewmodel.koinViewModel | ||
|
|
||
| @OptIn(ExperimentalMaterial3Api::class) | ||
| @Composable | ||
| fun ClientCollateralScreen( | ||
| viewModel: ClientCollateralViewModel = koinViewModel() | ||
| ) { | ||
| val uiState by viewModel.uiState.collectAsStateWithLifecycle() | ||
|
|
||
| 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 -> | ||
|
||
| Box( | ||
| modifier = Modifier | ||
| .fillMaxSize() | ||
| .padding(paddingValues) | ||
| .padding(horizontal = 16.dp, vertical = 8.dp), | ||
| contentAlignment = Alignment.TopCenter // Changed to TopCenter for list display | ||
| ) { | ||
| when (val state = uiState) { | ||
| is ClientCollateralUiState.Loading -> { | ||
| // Centered loading indicator | ||
| Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | ||
| CircularProgressIndicator() | ||
| } | ||
| } | ||
| is ClientCollateralUiState.Success -> { | ||
| CollateralList(items = state.items) | ||
| } | ||
| is ClientCollateralUiState.Empty -> { | ||
| // Centered empty state message | ||
| Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | ||
| EmptyCollateralState() | ||
| } | ||
| } | ||
| is ClientCollateralUiState.Error -> { | ||
| // Centered error state message | ||
| Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | ||
| ErrorState(message = state.message, onRetry = { viewModel.loadCollateralItems() }) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun CollateralList(items: List<CollateralDisplayItem>) { | ||
| LazyColumn( | ||
| verticalArrangement = Arrangement.spacedBy(8.dp), | ||
| contentPadding = PaddingValues(top = 8.dp, bottom = 8.dp) | ||
| ) { | ||
| items(items, key = { it.id }) { item -> // Use item.id as a key for better performance | ||
| CollateralListItem(item = item, onActionClick = { /* TODO: Handle action click */ }) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| 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) { | ||
|
|
||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
||
|
|
||
| @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) | ||
| } | ||
| } | ||
| } | ||
|
||
|
|
||
| @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") | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.mifos.feature.loan.ClientCollateral | ||
|
|
||
|
|
||
|
|
||
| data class CollateralDisplayItem( | ||
| val id: Int, | ||
| val typeName: String, | ||
| val quantity: Int, | ||
| val unitValue: Double, | ||
| val totalCollateralValue: Double | ||
| ) | ||
| sealed interface ClientCollateralUiState { | ||
| data object Loading : ClientCollateralUiState | ||
| data class Error(val message: String) : ClientCollateralUiState | ||
| data object Empty : ClientCollateralUiState | ||
| data class Success(val items: List<CollateralDisplayItem>, val totalItems: Int) : ClientCollateralUiState | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| 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 | ||
| } | ||
| } | ||
| } | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| #Fri Feb 02 11:29:16 IST 2024 | ||
| distributionBase=GRADLE_USER_HOME | ||
| distributionPath=wrapper/dists | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip | ||
| zipStoreBase=GRADLE_USER_HOME | ||
| zipStorePath=wrapper/dists |
Uh oh!
There was an error while loading. Please reload this page.
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:
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.