From e9f5939dc4f589dac6cdab8be0690d1f6356979a Mon Sep 17 00:00:00 2001 From: Suyash Mittal Date: Sun, 16 Jun 2024 21:30:28 +0530 Subject: [PATCH] updated backup logic --- .gitignore | 2 + .idea/other.xml | 263 ++++++++++++++++++ .../creditmanager/data/backup/BackupWorker.kt | 104 +++---- .../repository/CreditCardRepositoryImpl.kt | 6 +- .../data/repository/EMIRepositoryImpl.kt | 6 +- .../repository/TransactionRepositoryImpl.kt | 4 + .../data/source/dao/CreditCardDao.kt | 2 +- .../creditmanager/data/source/dao/EMIDao.kt | 5 +- .../data/source/dao/TransactionDao.kt | 3 + .../com/suyash/creditmanager/di/AppModule.kt | 4 + .../creditmanager/domain/model/CreditCard.kt | 25 +- .../suyash/creditmanager/domain/model/EMI.kt | 20 +- .../creditmanager/domain/model/Transaction.kt | 18 +- .../creditmanager/domain/model/TxnCategory.kt | 13 +- .../domain/model/backup/CreditCardBackup.kt | 40 +++ .../domain/model/backup/DataBackup.kt | 12 + .../domain/model/backup/EmiBackup.kt | 31 +++ .../domain/model/backup/TransactionBackup.kt | 26 ++ .../domain/model/backup/TxnCategoryBackup.kt | 18 ++ .../domain/repository/CreditCardRepository.kt | 2 +- .../domain/repository/EMIRepository.kt | 4 +- .../repository/TransactionRepository.kt | 2 + .../domain/use_case/EMIUseCases.kt | 2 + .../domain/use_case/TransactionUseCase.kt | 2 + .../use_case/credit_card/AddCreditCard.kt | 4 +- .../domain/use_case/emi/GetEMI.kt | 3 +- .../domain/use_case/emi/GetEMIsByCC.kt | 11 + .../transaction/GetTransactionsByCC.kt | 13 + .../add_edit_emi/AddEditEMIViewModel.kt | 3 +- .../EMIDetailEvent.kt | 2 +- .../EMIDetailScreen.kt | 33 ++- .../EMIDetailViewModel.kt | 48 ++-- .../presentation/main/CreditManager.kt | 2 +- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 35 files changed, 601 insertions(+), 136 deletions(-) create mode 100644 .idea/other.xml create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/model/backup/CreditCardBackup.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/model/backup/DataBackup.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/model/backup/EmiBackup.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/model/backup/TransactionBackup.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/model/backup/TxnCategoryBackup.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIsByCC.kt create mode 100644 app/src/main/java/com/suyash/creditmanager/domain/use_case/transaction/GetTransactionsByCC.kt rename app/src/main/java/com/suyash/creditmanager/presentation/{emi_details => emi_detail}/EMIDetailEvent.kt (57%) rename app/src/main/java/com/suyash/creditmanager/presentation/{emi_details => emi_detail}/EMIDetailScreen.kt (85%) rename app/src/main/java/com/suyash/creditmanager/presentation/{emi_details => emi_detail}/EMIDetailViewModel.kt (76%) diff --git a/.gitignore b/.gitignore index aa724b7..f46de04 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ .externalNativeBuild .cxx local.properties +# Kotlin 2.0 +.kotlin/ diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 0000000..0d3a1fb --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,263 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/data/backup/BackupWorker.kt b/app/src/main/java/com/suyash/creditmanager/data/backup/BackupWorker.kt index 15ed30c..88d10e4 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/backup/BackupWorker.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/backup/BackupWorker.kt @@ -1,6 +1,5 @@ package com.suyash.creditmanager.data.backup -import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context import android.content.Context.NOTIFICATION_SERVICE @@ -16,13 +15,9 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import androidx.work.workDataOf import com.google.gson.GsonBuilder -import com.google.gson.annotations.SerializedName import com.google.gson.reflect.TypeToken import com.suyash.creditmanager.R -import com.suyash.creditmanager.domain.model.CreditCard -import com.suyash.creditmanager.domain.model.EMI -import com.suyash.creditmanager.domain.model.Transaction -import com.suyash.creditmanager.domain.model.TxnCategory +import com.suyash.creditmanager.domain.model.backup.DataBackup import com.suyash.creditmanager.domain.use_case.CreditCardUseCases import com.suyash.creditmanager.domain.use_case.EMIUseCases import com.suyash.creditmanager.domain.use_case.TransactionUseCase @@ -65,19 +60,14 @@ class BackupWorker @AssistedInject constructor( when (type) { "BACKUP" -> { // Backup - setNotificationStart("Backup in progress") - val backupData = BackupData( - creditCards = creditCardUseCases.getCreditCards().first(), - emis = emiUseCases.getEMIs().first(), - transactions = transactionUseCase.getTransactions().first(), - txnCategories = txnCategoryUseCase.getTxnCategories().first() - ) - writeBackupDataToJsonFile(fileUri, backupData) + setNotification("Backup in progress", true) + val dataBackup = createDataBackup() + writeBackupDataToJsonFile(fileUri, dataBackup) } "RESTORE" -> { // Restore - setNotificationStart("Restore in progress") + setNotification("Restore in progress", true) val restoredBackupData = readBackupDataFromJsonFile(fileUri) restoreData(restoredBackupData) } @@ -87,97 +77,79 @@ class BackupWorker @AssistedInject constructor( e.printStackTrace() Result.failure() } finally { - setNotificationEnd( - "${type.lowercase().replaceFirstChar { it.uppercaseChar() }} Completed" + setNotification( + "${type.lowercase().replaceFirstChar { it.uppercaseChar() }} Completed", false ) } } - @SuppressLint("MissingPermission") - private fun setNotificationStart(msg: String) { + private fun setNotification(msg: String, start: Boolean) { val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle(msg) - .setAutoCancel(false) - .setOngoing(true) + .setAutoCancel(!start) + .setOngoing(start) .build() notificationManager.notify(NOTIFICATION_ID, notification) } - private fun setNotificationEnd(msg: String) { - val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSmallIcon(R.drawable.ic_launcher_foreground) - .setContentTitle(msg) - .setAutoCancel(true) - .setOngoing(false) - .build() - - notificationManager.notify(NOTIFICATION_ID, notification) + private suspend fun createDataBackup(): DataBackup { + val creditCards = creditCardUseCases.getCreditCards().first() + val emis = emiUseCases.getEMIs().first() + val transactions = transactionUseCase.getTransactions().first() + val txnCategories = txnCategoryUseCase.getTxnCategories().first() + + val creditCardBackups = creditCards.map { creditCard -> + val creditCardBackup = creditCard.toCreditCardBackup() + creditCardBackup.transactions = transactions.filter { it.card == creditCard.id }.map { it.toTransactionBackup() } + creditCardBackup.emis = emis.filter { it.card == creditCard.id }.map { it.toEmiBackup() } + creditCardBackup + } + val emiBackups = emis.filter { it.card == null }.map { it.toEmiBackup() } + val txnCategoriesBackups = txnCategories.map { it.toTxnCategoryBackup() } + return DataBackup(creditCardBackups, emiBackups, txnCategoriesBackups) } - data class BackupData( - @SerializedName("creditCards") - val creditCards: List, - @SerializedName("emis") - val emis: List, - @SerializedName("transactions") - val transactions: List, - @SerializedName("txnCategories") - val txnCategories: List - ) - - private fun writeBackupDataToJsonFile(uri: Uri, backupData: BackupData) { + private fun writeBackupDataToJsonFile(uri: Uri, dataBackup: DataBackup) { applicationContext.contentResolver.openOutputStream(uri)?.use { outputStream -> - val jsonString = gson.toJson(backupData) + val jsonString = gson.toJson(dataBackup) val writer = OutputStreamWriter(outputStream) writer.write(jsonString) writer.flush() } } - private fun readBackupDataFromJsonFile(uri: Uri): BackupData { + private fun readBackupDataFromJsonFile(uri: Uri): DataBackup { try { applicationContext.contentResolver.openInputStream(uri)?.use { inputStream -> val reader = InputStreamReader(inputStream) - return gson.fromJson(reader, BackupData::class.java) + return gson.fromJson(reader, DataBackup::class.java) } - return BackupData( + return DataBackup( creditCards = emptyList(), emis = emptyList(), - transactions = emptyList(), txnCategories = emptyList() ) } catch (e: Exception) { e.printStackTrace() - return BackupData( + return DataBackup( creditCards = emptyList(), emis = emptyList(), - transactions = emptyList(), txnCategories = emptyList() ) } } - private suspend fun restoreData(backupData: BackupData) { - backupData.creditCards.forEach { - it.id = 0 - creditCardUseCases.upsertCreditCard(it) - } - backupData.emis.forEach { - it.id = 0 - emiUseCases.upsertEMI(it) - } - backupData.transactions.forEach { - it.id = 0 - transactionUseCase.upsertTransaction(it) - } - backupData.txnCategories.forEach { - it.id = 0 - txnCategoryUseCase.upsertTxnCategory(it) + private suspend fun restoreData(dataBackup: DataBackup) { + dataBackup.creditCards.forEach { cc -> + val ccId: Int = creditCardUseCases.upsertCreditCard(cc.toCreditCard()).toInt() + cc.emis.forEach { emi -> emiUseCases.upsertEMI(emi.toEmi(ccId)) } + cc.transactions.forEach{ txn -> transactionUseCase.upsertTransaction(txn.toTransaction(ccId)) } } + dataBackup.emis.forEach { emiUseCases.upsertEMI(it.toEmi(null)) } + dataBackup.txnCategories.forEach { txnCategoryUseCase.upsertTxnCategory(it.toTxnCategory()) } } companion object { diff --git a/app/src/main/java/com/suyash/creditmanager/data/repository/CreditCardRepositoryImpl.kt b/app/src/main/java/com/suyash/creditmanager/data/repository/CreditCardRepositoryImpl.kt index 6d4de4f..87d01c5 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/repository/CreditCardRepositoryImpl.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/repository/CreditCardRepositoryImpl.kt @@ -6,12 +6,12 @@ import com.suyash.creditmanager.domain.repository.CreditCardRepository import kotlinx.coroutines.flow.Flow class CreditCardRepositoryImpl(private val dao: CreditCardDao): CreditCardRepository { - override suspend fun upsertCreditCard(creditCard: CreditCard) { - dao.upsertCreditCard(creditCard) + override suspend fun upsertCreditCard(creditCard: CreditCard): Long { + return dao.upsertCreditCard(creditCard) } override suspend fun deleteCreditCard(creditCard: CreditCard) { - dao.deleteCreditCard(creditCard) + return dao.deleteCreditCard(creditCard) } override fun getCreditCards(): Flow> { diff --git a/app/src/main/java/com/suyash/creditmanager/data/repository/EMIRepositoryImpl.kt b/app/src/main/java/com/suyash/creditmanager/data/repository/EMIRepositoryImpl.kt index b08dec0..74e9168 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/repository/EMIRepositoryImpl.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/repository/EMIRepositoryImpl.kt @@ -18,7 +18,11 @@ class EMIRepositoryImpl(private val dao: EMIDao): EMIRepository { return dao.getEMIs() } - override suspend fun getEMI(id: Int): EMI? { + override fun getEMIsByCC(id: Int): Flow> { + return dao.getEMIsByCC(id) + } + + override fun getEMI(id: Int): Flow { return dao.getEMI(id) } } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/data/repository/TransactionRepositoryImpl.kt b/app/src/main/java/com/suyash/creditmanager/data/repository/TransactionRepositoryImpl.kt index f6afa40..d45d9b4 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/repository/TransactionRepositoryImpl.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/repository/TransactionRepositoryImpl.kt @@ -18,6 +18,10 @@ class TransactionRepositoryImpl(private val dao: TransactionDao): TransactionRep return dao.getTransactions() } + override fun getTransactionsByCC(id: Int): Flow> { + return dao.getTransactionsByCC(id) + } + override suspend fun getTransaction(id: Int): Transaction? { return dao.getTransaction(id) } diff --git a/app/src/main/java/com/suyash/creditmanager/data/source/dao/CreditCardDao.kt b/app/src/main/java/com/suyash/creditmanager/data/source/dao/CreditCardDao.kt index ce221b1..add2de6 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/source/dao/CreditCardDao.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/source/dao/CreditCardDao.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow interface CreditCardDao { @Upsert - suspend fun upsertCreditCard(creditCard: CreditCard) + suspend fun upsertCreditCard(creditCard: CreditCard): Long @Delete suspend fun deleteCreditCard(creditCard: CreditCard) diff --git a/app/src/main/java/com/suyash/creditmanager/data/source/dao/EMIDao.kt b/app/src/main/java/com/suyash/creditmanager/data/source/dao/EMIDao.kt index e3366fa..9dfcde8 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/source/dao/EMIDao.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/source/dao/EMIDao.kt @@ -18,6 +18,9 @@ interface EMIDao { @Query("SELECT * FROM emis") fun getEMIs(): Flow> + @Query("SELECT * FROM emis WHERE card = :id") + fun getEMIsByCC(id: Int): Flow> + @Query("SELECT * FROM emis WHERE id = :id") - suspend fun getEMI(id: Int): EMI? + fun getEMI(id: Int): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/data/source/dao/TransactionDao.kt b/app/src/main/java/com/suyash/creditmanager/data/source/dao/TransactionDao.kt index b673cf8..22d4e0d 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/source/dao/TransactionDao.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/source/dao/TransactionDao.kt @@ -19,6 +19,9 @@ interface TransactionDao { @Query("SELECT * FROM transactions") fun getTransactions(): Flow> + @Query("SELECT * FROM transactions WHERE card = :id") + fun getTransactionsByCC(id: Int): Flow> + @Query("SELECT * FROM transactions WHERE id = :id") suspend fun getTransaction(id: Int): Transaction? } diff --git a/app/src/main/java/com/suyash/creditmanager/di/AppModule.kt b/app/src/main/java/com/suyash/creditmanager/di/AppModule.kt index a9c9771..3171eb3 100644 --- a/app/src/main/java/com/suyash/creditmanager/di/AppModule.kt +++ b/app/src/main/java/com/suyash/creditmanager/di/AppModule.kt @@ -29,10 +29,12 @@ import com.suyash.creditmanager.domain.use_case.emi.AddEMI import com.suyash.creditmanager.domain.use_case.emi.DeleteEMI import com.suyash.creditmanager.domain.use_case.emi.GetEMI import com.suyash.creditmanager.domain.use_case.emi.GetEMIs +import com.suyash.creditmanager.domain.use_case.emi.GetEMIsByCC import com.suyash.creditmanager.domain.use_case.transaction.AddTransaction import com.suyash.creditmanager.domain.use_case.transaction.DeleteTransaction import com.suyash.creditmanager.domain.use_case.transaction.GetTransaction import com.suyash.creditmanager.domain.use_case.transaction.GetTransactions +import com.suyash.creditmanager.domain.use_case.transaction.GetTransactionsByCC import com.suyash.creditmanager.domain.use_case.txn_category.AddTxnCategory import com.suyash.creditmanager.domain.use_case.txn_category.DeleteTxnCategory import com.suyash.creditmanager.domain.use_case.txn_category.GetTxnCategories @@ -101,6 +103,7 @@ object AppModule { return TransactionUseCase( getTransactions = GetTransactions(repository), getTransaction = GetTransaction(repository), + getTransactionsByCC = GetTransactionsByCC(repository), upsertTransaction = AddTransaction(repository), deleteTransaction = DeleteTransaction(repository) ) @@ -112,6 +115,7 @@ object AppModule { return EMIUseCases( getEMIs = GetEMIs(repository), getEMI = GetEMI(repository), + getEMIsByCC = GetEMIsByCC(repository), upsertEMI = AddEMI(repository), deleteEMI = DeleteEMI(repository) ) diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/CreditCard.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/CreditCard.kt index 3b08cee..3d53464 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/model/CreditCard.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/CreditCard.kt @@ -2,30 +2,33 @@ package com.suyash.creditmanager.domain.model import androidx.room.Entity import androidx.room.PrimaryKey -import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.backup.CreditCardBackup import com.suyash.creditmanager.domain.util.CardType @Entity(tableName = "credit_cards") data class CreditCard( - @SerializedName("cardName") var cardName: String, - @SerializedName("last4Digits") var last4Digits: String, - @SerializedName("expiryDate") var expiryDate: String, - @SerializedName("billDate") var billDate: Int, - @SerializedName("dueDate") var dueDate: Int, - @SerializedName("cardType") var cardType: CardType, - @SerializedName("limit") var limit: Int = 0, - @SerializedName("bankName") var bankName: String?, - @SerializedName("id") @PrimaryKey(autoGenerate = true) var id: Int = 0 -) +) { + fun toCreditCardBackup() = + CreditCardBackup( + cardName = this.cardName, + last4Digits = this.last4Digits, + expiryDate = this.expiryDate, + billDate = this.billDate, + dueDate = this.dueDate, + cardType = this.cardType, + limit = this.limit, + bankName = this.bankName + ) +} class InvalidCreditCardException(message: String): Exception(message) diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/EMI.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/EMI.kt index 0fc6086..73f6abd 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/model/EMI.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/EMI.kt @@ -3,7 +3,7 @@ package com.suyash.creditmanager.domain.model import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverters -import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.backup.EmiBackup import java.time.LocalDate import java.time.temporal.ChronoUnit import kotlin.math.min @@ -13,21 +13,13 @@ import kotlin.math.min ) @TypeConverters(Converters::class) data class EMI( - @SerializedName("name") val name: String, - @SerializedName("amount") val amount: Float, - @SerializedName("rate") val rate: Float, - @SerializedName("months") val months: Int, - @SerializedName("card") val card: Int?, - @SerializedName("date") val date: LocalDate, - @SerializedName("taxRate") val taxRate: Float?, - @SerializedName("id") @PrimaryKey(autoGenerate = true) var id: Int = 0 ) { @@ -38,4 +30,14 @@ data class EMI( val emiPaid: Int = ChronoUnit.MONTHS.between(date, LocalDate.now()).toInt() + 1 return min(emiPaid, months) } + + fun toEmiBackup() = + EmiBackup ( + name = this.name, + amount = this.amount, + rate = this.rate, + months = this.months, + date = this.date, + taxRate = this.taxRate + ) } diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/Transaction.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/Transaction.kt index 87dea0b..7843b1d 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/model/Transaction.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/Transaction.kt @@ -4,7 +4,7 @@ import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import androidx.room.TypeConverters -import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.backup.TransactionBackup import com.suyash.creditmanager.domain.util.TransactionType import java.time.LocalDate @@ -14,17 +14,19 @@ import java.time.LocalDate ) @TypeConverters(Converters::class) data class Transaction( - @SerializedName("type") val type: TransactionType, - @SerializedName("amount") val amount: Float, - @SerializedName("card") val card: Int, - @SerializedName("date") val date: LocalDate, - @SerializedName("category") val category: String?, - @SerializedName("id") @PrimaryKey(autoGenerate = true) var id: Int = 0 -) +) { + fun toTransactionBackup() = + TransactionBackup( + type = this.type, + amount = this.amount, + date = this.date, + category = this.category + ) +} diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/TxnCategory.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/TxnCategory.kt index de3fd3f..5af14b7 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/model/TxnCategory.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/TxnCategory.kt @@ -2,18 +2,21 @@ package com.suyash.creditmanager.domain.model import androidx.room.Entity import androidx.room.PrimaryKey -import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.backup.TxnCategoryBackup import com.suyash.creditmanager.domain.util.TransactionType @Entity( tableName = "txn_categories" ) data class TxnCategory( - @SerializedName("type") val type: TransactionType, - @SerializedName("name") val name: String, - @SerializedName("id") @PrimaryKey(autoGenerate = true) var id: Int = 0 -) +) { + fun toTxnCategoryBackup() = + TxnCategoryBackup( + type = this.type, + name = this.name + ) +} diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/backup/CreditCardBackup.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/CreditCardBackup.kt new file mode 100644 index 0000000..18e57a4 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/CreditCardBackup.kt @@ -0,0 +1,40 @@ +package com.suyash.creditmanager.domain.model.backup + +import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.CreditCard +import com.suyash.creditmanager.domain.util.CardType + +data class CreditCardBackup( + @SerializedName("cardName") + var cardName: String, + @SerializedName("last4Digits") + var last4Digits: String, + @SerializedName("expiryDate") + var expiryDate: String, + @SerializedName("billDate") + var billDate: Int, + @SerializedName("dueDate") + var dueDate: Int, + @SerializedName("cardType") + var cardType: CardType, + @SerializedName("limit") + var limit: Int = 0, + @SerializedName("bankName") + var bankName: String?, + @SerializedName("transactions") + var transactions: List = emptyList(), + @SerializedName("emis") + var emis: List = emptyList() +) { + fun toCreditCard() = + CreditCard( + cardName = this.cardName, + last4Digits = this.last4Digits, + expiryDate = this.expiryDate, + billDate = this.billDate, + dueDate = this.dueDate, + cardType = this.cardType, + limit = this.limit, + bankName = this.bankName, + ) +} diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/backup/DataBackup.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/DataBackup.kt new file mode 100644 index 0000000..68142f0 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/DataBackup.kt @@ -0,0 +1,12 @@ +package com.suyash.creditmanager.domain.model.backup + +import com.google.gson.annotations.SerializedName + +data class DataBackup( + @SerializedName("creditCards") + var creditCards: List = emptyList(), + @SerializedName("emis") + var emis: List = emptyList(), + @SerializedName("txnCategories") + var txnCategories: List = emptyList() +) diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/backup/EmiBackup.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/EmiBackup.kt new file mode 100644 index 0000000..9cac85f --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/EmiBackup.kt @@ -0,0 +1,31 @@ +package com.suyash.creditmanager.domain.model.backup + +import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.EMI +import java.time.LocalDate + +data class EmiBackup( + @SerializedName("name") + val name: String, + @SerializedName("amount") + val amount: Float, + @SerializedName("rate") + val rate: Float, + @SerializedName("months") + val months: Int, + @SerializedName("date") + val date: LocalDate, + @SerializedName("taxRate") + val taxRate: Float? +) { + fun toEmi(card: Int?) = + EMI ( + name = this.name, + amount = this.amount, + rate = this.rate, + months = this.months, + date = this.date, + taxRate = this.taxRate, + card = card + ) +} diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/backup/TransactionBackup.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/TransactionBackup.kt new file mode 100644 index 0000000..06acbf6 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/TransactionBackup.kt @@ -0,0 +1,26 @@ +package com.suyash.creditmanager.domain.model.backup + +import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.Transaction +import com.suyash.creditmanager.domain.util.TransactionType +import java.time.LocalDate + +data class TransactionBackup( + @SerializedName("type") + val type: TransactionType, + @SerializedName("amount") + val amount: Float, + @SerializedName("date") + val date: LocalDate, + @SerializedName("category") + val category: String? +) { + fun toTransaction(card: Int) = + Transaction( + type = this.type, + amount = this.amount, + date = this.date, + category = this.category, + card = card + ) +} diff --git a/app/src/main/java/com/suyash/creditmanager/domain/model/backup/TxnCategoryBackup.kt b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/TxnCategoryBackup.kt new file mode 100644 index 0000000..a1438e9 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/backup/TxnCategoryBackup.kt @@ -0,0 +1,18 @@ +package com.suyash.creditmanager.domain.model.backup + +import com.google.gson.annotations.SerializedName +import com.suyash.creditmanager.domain.model.TxnCategory +import com.suyash.creditmanager.domain.util.TransactionType + +data class TxnCategoryBackup( + @SerializedName("type") + val type: TransactionType, + @SerializedName("name") + val name: String +) { + fun toTxnCategory() = + TxnCategory( + type = this.type, + name = this.name + ) +} diff --git a/app/src/main/java/com/suyash/creditmanager/domain/repository/CreditCardRepository.kt b/app/src/main/java/com/suyash/creditmanager/domain/repository/CreditCardRepository.kt index 4fca9fc..3b1ecca 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/repository/CreditCardRepository.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/repository/CreditCardRepository.kt @@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow interface CreditCardRepository { - suspend fun upsertCreditCard(creditCard: CreditCard) + suspend fun upsertCreditCard(creditCard: CreditCard): Long suspend fun deleteCreditCard(creditCard: CreditCard) diff --git a/app/src/main/java/com/suyash/creditmanager/domain/repository/EMIRepository.kt b/app/src/main/java/com/suyash/creditmanager/domain/repository/EMIRepository.kt index 699c00a..a3a97af 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/repository/EMIRepository.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/repository/EMIRepository.kt @@ -11,5 +11,7 @@ interface EMIRepository { fun getEMIs(): Flow> - suspend fun getEMI(id: Int): EMI? + fun getEMI(id: Int): Flow + + fun getEMIsByCC(id: Int): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/repository/TransactionRepository.kt b/app/src/main/java/com/suyash/creditmanager/domain/repository/TransactionRepository.kt index d87073a..c2c3549 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/repository/TransactionRepository.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/repository/TransactionRepository.kt @@ -10,5 +10,7 @@ interface TransactionRepository { fun getTransactions(): Flow> + fun getTransactionsByCC(id: Int): Flow> + suspend fun getTransaction(id: Int): Transaction? } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/EMIUseCases.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/EMIUseCases.kt index 6afa62c..f2bd3c2 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/use_case/EMIUseCases.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/EMIUseCases.kt @@ -4,10 +4,12 @@ import com.suyash.creditmanager.domain.use_case.emi.AddEMI import com.suyash.creditmanager.domain.use_case.emi.DeleteEMI import com.suyash.creditmanager.domain.use_case.emi.GetEMI import com.suyash.creditmanager.domain.use_case.emi.GetEMIs +import com.suyash.creditmanager.domain.use_case.emi.GetEMIsByCC data class EMIUseCases( val getEMIs: GetEMIs, val getEMI: GetEMI, + val getEMIsByCC: GetEMIsByCC, val upsertEMI: AddEMI, val deleteEMI: DeleteEMI ) \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/TransactionUseCase.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/TransactionUseCase.kt index f8744d1..b081c49 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/use_case/TransactionUseCase.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/TransactionUseCase.kt @@ -4,10 +4,12 @@ import com.suyash.creditmanager.domain.use_case.transaction.AddTransaction import com.suyash.creditmanager.domain.use_case.transaction.DeleteTransaction import com.suyash.creditmanager.domain.use_case.transaction.GetTransaction import com.suyash.creditmanager.domain.use_case.transaction.GetTransactions +import com.suyash.creditmanager.domain.use_case.transaction.GetTransactionsByCC data class TransactionUseCase ( val getTransactions: GetTransactions, val getTransaction: GetTransaction, + val getTransactionsByCC: GetTransactionsByCC, val upsertTransaction: AddTransaction, val deleteTransaction: DeleteTransaction ) \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/AddCreditCard.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/AddCreditCard.kt index ae79d02..97338cb 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/AddCreditCard.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/AddCreditCard.kt @@ -8,10 +8,10 @@ import kotlin.jvm.Throws class AddCreditCard(private val repository: CreditCardRepository) { @Throws(InvalidCreditCardException::class) - suspend operator fun invoke(creditCard: CreditCard) { + suspend operator fun invoke(creditCard: CreditCard): Long { if(creditCard.cardName.isBlank() || creditCard.last4Digits.isBlank() || creditCard.expiryDate.isBlank()) { throw InvalidCreditCardException("Fields cannot be blank") } - repository.upsertCreditCard(creditCard) + return repository.upsertCreditCard(creditCard) } } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMI.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMI.kt index 1e201d8..b253553 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMI.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMI.kt @@ -2,9 +2,10 @@ package com.suyash.creditmanager.domain.use_case.emi import com.suyash.creditmanager.domain.model.EMI import com.suyash.creditmanager.domain.repository.EMIRepository +import kotlinx.coroutines.flow.Flow class GetEMI(private val repository: EMIRepository) { - suspend operator fun invoke(id: Int): EMI? { + operator fun invoke(id: Int): Flow { return repository.getEMI(id) } } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIsByCC.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIsByCC.kt new file mode 100644 index 0000000..7a4752b --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIsByCC.kt @@ -0,0 +1,11 @@ +package com.suyash.creditmanager.domain.use_case.emi + +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.repository.EMIRepository +import kotlinx.coroutines.flow.Flow + +class GetEMIsByCC(private val repository: EMIRepository) { + operator fun invoke(id: Int): Flow> { + return repository.getEMIsByCC(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/transaction/GetTransactionsByCC.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/transaction/GetTransactionsByCC.kt new file mode 100644 index 0000000..7001131 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/transaction/GetTransactionsByCC.kt @@ -0,0 +1,13 @@ +package com.suyash.creditmanager.domain.use_case.transaction + +import com.suyash.creditmanager.domain.model.Transaction +import com.suyash.creditmanager.domain.repository.TransactionRepository +import kotlinx.coroutines.flow.Flow + +class GetTransactionsByCC( + private val repository: TransactionRepository +) { + operator fun invoke(id: Int): Flow> { + return repository.getTransactionsByCC(id) + } +} diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emi/AddEditEMIViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emi/AddEditEMIViewModel.kt index 03a9c8a..98d92b2 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emi/AddEditEMIViewModel.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emi/AddEditEMIViewModel.kt @@ -16,6 +16,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -69,7 +70,7 @@ class AddEditEMIViewModel @Inject constructor( savedStateHandle.get("emiId")?.let { emiId -> if(emiId != -1) { viewModelScope.launch { - emiUseCases.getEMI(emiId)?.also { + emiUseCases.getEMI(emiId).first()?.let { _currentEMIId.intValue = it.id _name.value = it.name _emiAmount.value = it.name 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_detail/EMIDetailEvent.kt similarity index 57% rename from app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailEvent.kt rename to app/src/main/java/com/suyash/creditmanager/presentation/emi_detail/EMIDetailEvent.kt index d7aa90e..24cf104 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailEvent.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emi_detail/EMIDetailEvent.kt @@ -1,4 +1,4 @@ -package com.suyash.creditmanager.presentation.emi_details +package com.suyash.creditmanager.presentation.emi_detail sealed class EMIDetailEvent { data object BackPressed: EMIDetailEvent() 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_detail/EMIDetailScreen.kt similarity index 85% rename from app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailScreen.kt rename to app/src/main/java/com/suyash/creditmanager/presentation/emi_detail/EMIDetailScreen.kt index a4cefad..c159992 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailScreen.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emi_detail/EMIDetailScreen.kt @@ -1,4 +1,4 @@ -package com.suyash.creditmanager.presentation.emi_details +package com.suyash.creditmanager.presentation.emi_detail import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -30,6 +31,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.suyash.creditmanager.presentation.commons.CMUtils +import com.suyash.creditmanager.presentation.commons.Screen import kotlinx.coroutines.flow.collectLatest import kotlin.math.absoluteValue @@ -62,9 +64,6 @@ fun EMIDetailScreen( }, topBar = { TopAppBar( - title = { - Text(text = "EMI Detail") - }, navigationIcon = { IconButton(onClick = { viewModel.onEvent(EMIDetailEvent.BackPressed) @@ -74,6 +73,22 @@ fun EMIDetailScreen( contentDescription = "Go Back" ) } + }, + title = { + Text(text = "EMI Detail") + }, + actions = { + IconButton(onClick = { + navController.navigate( + Screen.AddEditEMIScreen.route + + "?emiId=${viewModel.emi?.id}" + ) + }) { + Icon( + Icons.Outlined.Edit, + contentDescription = "Edit EMI" + ) + } } ) } @@ -114,6 +129,12 @@ fun EMIDetailScreen( color = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Interest: ${CMUtils.currencyMask(viewModel.totalAmount - (viewModel.emi?.amount?:0.0F), viewModel.countryCode)}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(8.dp)) if((viewModel.emi?.taxRate?.toInt() ?: 0) != 0) { Text( text = "TToI: ${CMUtils.currencyMask(viewModel.schedule.sumOf { it.taxOnInterest }.toFloat(), viewModel.countryCode)}", @@ -131,7 +152,9 @@ fun EMIDetailScreen( LazyColumn { items(viewModel.schedule) { Row( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), verticalAlignment = Alignment.CenterVertically ) { Text(text = it.paymentNumber.toString()) 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_detail/EMIDetailViewModel.kt similarity index 76% rename from app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailViewModel.kt rename to app/src/main/java/com/suyash/creditmanager/presentation/emi_detail/EMIDetailViewModel.kt index 13f0791..a034bb2 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/emi_details/EMIDetailViewModel.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emi_detail/EMIDetailViewModel.kt @@ -1,4 +1,4 @@ -package com.suyash.creditmanager.presentation.emi_details +package com.suyash.creditmanager.presentation.emi_detail import androidx.datastore.core.DataStore import androidx.lifecycle.SavedStateHandle @@ -11,8 +11,11 @@ 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.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.math.pow @@ -33,27 +36,21 @@ class EMIDetailViewModel @Inject constructor( var totalAmount: Float = 0.0F var schedule: List = emptyList() + private var getEMIDataJob: Job? = null + private val _eventFlow = MutableSharedFlow() val eventFlow = _eventFlow.asSharedFlow() init { - savedStateHandle.get("emiId")?.let { - viewModelScope.launch { - emiUseCases.getEMI(it)?.also { e -> - emi = e - generateAmortizationSchedule(e.amount.toDouble(), e.rate.toDouble(), e.months, e.taxRate?.toDouble()?:0.0) - } - emi?.card?.let { - creditCardUseCases.getCreditCard(it)?.also { cc -> - creditCard = cc - } - } - dataStore.data.collect { - countryCode = it.countryCode - dateFormat = it.dateFormat - } + viewModelScope.launch { + dataStore.data.collect { + countryCode = it.countryCode + dateFormat = it.dateFormat } } + savedStateHandle.get("emiId")?.let { + getEMIData(it) + } } fun onEvent(event: EMIDetailEvent) { @@ -66,6 +63,25 @@ class EMIDetailViewModel @Inject constructor( } } + private fun getEMIData(id: Int) { + getEMIDataJob?.cancel() + getEMIDataJob = emiUseCases.getEMI(id).onEach { emi -> + this.emi = emi + emi?.let { + generateAmortizationSchedule( + emi.amount.toDouble(), + emi.rate.toDouble(), + emi.months, + emi.taxRate?.toDouble()?:0.0) + } + emi?.card?.let { + creditCardUseCases.getCreditCard(it)?.also { cc -> + creditCard = cc + } + } + }.launchIn(viewModelScope) + } + private fun generateAmortizationSchedule(loanAmount: Double, annualInterestRate: Double, loanTermInMonths: Int, taxRate: Double) { val monthlyInterestRate = annualInterestRate / 12 / 100 val monthlyPayment = loanAmount * 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 2c4b72c..72f09b8 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 @@ -22,7 +22,7 @@ import com.suyash.creditmanager.presentation.add_edit_cc.AddEditCCScreen import com.suyash.creditmanager.presentation.add_edit_emi.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.emi_detail.EMIDetailScreen import com.suyash.creditmanager.presentation.emis.EMIsScreen import com.suyash.creditmanager.presentation.settings.SettingsScreen import com.suyash.creditmanager.presentation.transactions.TransactionsScreen diff --git a/build.gradle.kts b/build.gradle.kts index 5d05e01..f92c546 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ buildscript { } // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.4.1" apply false + id("com.android.application") version "8.5.0" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("com.google.dagger.hilt.android") version "2.49" apply false id("com.google.devtools.ksp") version "1.9.0-1.0.12" apply false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d889ab5..68ff496 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Oct 29 18:18:10 IST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists