From b950bcdee0515da9c3b4b9c40b9e72959dcd19e8 Mon Sep 17 00:00:00 2001 From: Suyash Mittal Date: Sun, 28 Jan 2024 00:35:02 +0530 Subject: [PATCH] added emi detail screen --- .../emi_details/EMIDetailEvent.kt | 5 + .../emi_details/EMIDetailScreen.kt | 148 ++++++++++++++++++ .../emi_details/EMIDetailViewModel.kt | 93 +++++++++++ .../presentation/emis/EMIsScreen.kt | 17 +- .../presentation/emis/component/EMIItem.kt | 1 - .../presentation/main/CreditManager.kt | 12 ++ .../creditmanager/presentation/util/Screen.kt | 9 ++ 7 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailEvent.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailScreen.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailViewModel.kt diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailEvent.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailEvent.kt new file mode 100644 index 0000000..d7aa90e --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailEvent.kt @@ -0,0 +1,5 @@ +package com.suyash.creditmanager.presentation.emi_details + +sealed class EMIDetailEvent { + data object BackPressed: EMIDetailEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailScreen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailScreen.kt new file mode 100644 index 0000000..aa47157 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailScreen.kt @@ -0,0 +1,148 @@ +package com.suyash.creditmanager.presentation.emi_details + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.Divider +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.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.suyash.creditmanager.presentation.util.CMUtils +import kotlinx.coroutines.flow.collectLatest + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EMIDetailScreen( + navController: NavController, + viewModel: EMIDetailViewModel = hiltViewModel() +) { + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(key1 = true) { + viewModel.eventFlow.collectLatest { event -> + when(event) { + is EMIDetailViewModel.UiEvent.ShowSnackbar -> { + snackbarHostState.showSnackbar( + message = event.message + ) + } + is EMIDetailViewModel.UiEvent.NavigateUp -> { + navController.navigateUp() + } + } + } + } + + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + topBar = { + TopAppBar( + title = { + Text(text = "EMI Detail") + }, + navigationIcon = { + IconButton(onClick = { + viewModel.onEvent(EMIDetailEvent.BackPressed) + }) { + Icon( + Icons.Filled.ArrowBack, + contentDescription = "Go Back" + ) + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = viewModel.emi?.name?:"", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = CMUtils.currencyMask(viewModel.emi?.amount?:0.0F, viewModel.countryCode), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary + ) + viewModel.creditCard?.let { + Text( + text = "${it.cardName} (${it.last4Digits})", + style = MaterialTheme.typography.bodySmall, + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = CMUtils.currencyMask(viewModel.emiAmount, viewModel.countryCode), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(16.dp)) + Divider() + LazyColumn { + itemsIndexed(viewModel.schedule) {i, schedule -> + Row( + modifier = Modifier.fillMaxWidth().padding(top = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = (i+1).toString()) + Column( + modifier = Modifier + .padding(start = 16.dp) + .weight(1f) + ) { + Text( + text = CMUtils.currencyMask(schedule.principal, viewModel.countryCode), + style = MaterialTheme.typography.bodySmall + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = CMUtils.currencyMask(schedule.interest, viewModel.countryCode), + style = MaterialTheme.typography.bodySmall + ) + } + Text( + text = CMUtils.currencyMask(schedule.amount, viewModel.countryCode), + color = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailViewModel.kt new file mode 100644 index 0000000..dd1a7d6 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailViewModel.kt @@ -0,0 +1,93 @@ +package com.suyash.creditmanager.presentation.emi_details + +import androidx.datastore.core.DataStore +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.suyash.creditmanager.data.settings.AppSettings +import com.suyash.creditmanager.domain.model.CreditCard +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.use_case.CreditCardUseCases +import com.suyash.creditmanager.domain.use_case.EMIUseCases +import com.suyash.creditmanager.domain.util.DateFormat +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlin.math.pow + +@HiltViewModel +class EMIDetailViewModel @Inject constructor( + private val creditCardUseCases: CreditCardUseCases, + private val emiUseCases: EMIUseCases, + private val dataStore: DataStore, + savedStateHandle: SavedStateHandle +): ViewModel() { + + var emi: EMI? = null + var creditCard: CreditCard? = null + var countryCode: String = "IN" + var dateFormat: DateFormat = DateFormat.DDMMYYYY + var emiAmount: Float = 0.0F + var schedule: List = emptyList() + + private val _eventFlow = MutableSharedFlow() + val eventFlow = _eventFlow.asSharedFlow() + + init { + savedStateHandle.get("emiId")?.let { + viewModelScope.launch { + emiUseCases.getEMI(it)?.also { e -> + emi = e + calculateEMISchedule(e.amount, e.rate, e.months) + } + emi?.card?.let { + creditCardUseCases.getCreditCard(it)?.also { cc -> + creditCard = cc + } + } + dataStore.data.collect { + countryCode = it.countryCode + dateFormat = it.dateFormat + } + } + } + } + + fun onEvent(event: EMIDetailEvent) { + when (event) { + is EMIDetailEvent.BackPressed -> { + viewModelScope.launch { + _eventFlow.emit(UiEvent.NavigateUp) + } + } + } + } + + private fun calculateEMISchedule(startingAmount: Float, interestRate: Float, tenure: Int) { + val schedule: MutableList = mutableListOf() + var principal: Float = startingAmount + val monthlyRate: Float = interestRate/1200 + val emi: Float = startingAmount*monthlyRate*(1+monthlyRate).pow(tenure)/((1+monthlyRate).pow(tenure)-1) + for (i in 1..tenure) { + val interest = principal*(monthlyRate) + val principalPaid: Float = emi - interest + principal -= principalPaid + schedule.add(EMISchedule(principalPaid, interest, principal)) + } + this.emiAmount = emi + this.schedule = schedule.toList() + } + + data class EMISchedule( + val principal: Float, + val interest: Float, + val amount: Float + ) + + sealed class UiEvent { + data class ShowSnackbar(val message: String): UiEvent() + data object NavigateUp: UiEvent() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsScreen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsScreen.kt index 9ae9820..39c0db3 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsScreen.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsScreen.kt @@ -1,5 +1,7 @@ package com.suyash.creditmanager.presentation.emis +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -20,7 +22,7 @@ import com.suyash.creditmanager.presentation.emis.component.EMIItem import com.suyash.creditmanager.presentation.util.Screen @Composable -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) fun EMIsScreen( navController: NavController, viewModel: EMIsViewModel = hiltViewModel() @@ -49,7 +51,18 @@ fun EMIsScreen( emi = emi, countryCode = viewModel.state.value.countryCode, dateFormat = viewModel.state.value.dateFormat, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .combinedClickable( + onClick = { + navController.navigate( + Screen.EMIDetailScreen.route + "?emiId=" + emi.id + ) + }, + onLongClick = { + // TODO impl + } + ) ) } } diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emis/component/EMIItem.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emis/component/EMIItem.kt index 9205c3b..48e9e7d 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/emis/component/EMIItem.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emis/component/EMIItem.kt @@ -34,7 +34,6 @@ fun EMIItem( Column( modifier = Modifier .fillMaxWidth() - .padding(start = 16.dp) ) { Row( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/main/CreditManager.kt b/app/src/main/java/com/suyash/creditmanager/presentation/main/CreditManager.kt index 0e3fa55..842ca25 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/main/CreditManager.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/main/CreditManager.kt @@ -21,6 +21,7 @@ import com.suyash.creditmanager.presentation.add_edit_cc.AddEditCCScreen import com.suyash.creditmanager.presentation.add_edit_emis.AddEditEMIScreen import com.suyash.creditmanager.presentation.add_edit_txn.AddEditTxnScreen import com.suyash.creditmanager.presentation.credit_cards.CreditCardsScreen +import com.suyash.creditmanager.presentation.emi_details.EMIDetailScreen import com.suyash.creditmanager.presentation.emis.EMIsScreen import com.suyash.creditmanager.presentation.settings.SettingsScreen import com.suyash.creditmanager.presentation.transactions.TransactionsScreen @@ -115,6 +116,17 @@ fun CreditManager() { ) { AddEditEMIScreen(navController = navController) } + composable( + route = Screen.EMIDetailScreen.route + "?emiId={emiId}", + arguments = listOf( + navArgument(name = "emiId") { + type = NavType.IntType + defaultValue = -1 + } + ) + ) { + EMIDetailScreen(navController = navController) + } } } } diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/util/Screen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/util/Screen.kt index 5acc2a0..43e2f72 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/util/Screen.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/util/Screen.kt @@ -3,11 +3,13 @@ package com.suyash.creditmanager.presentation.util import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.CreditCard +import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Payments import androidx.compose.material.icons.filled.Receipt import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.CreditCard +import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Payments import androidx.compose.material.icons.outlined.Receipt import androidx.compose.material.icons.outlined.Settings @@ -63,6 +65,13 @@ sealed class Screen( Icons.Outlined.Add ) + data object EMIDetailScreen: Screen( + "emi_detail", + "EMI Detail", + Icons.Filled.Info, + Icons.Outlined.Info + ) + companion object { val bottomBarScreens: List = listOf( CreditCardsScreen,