diff --git a/app/build.gradle.kts b/app/build.gradle.kts index da46fba..59451db 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { applicationId = "com.suyash.creditmanager" minSdk = 26 targetSdk = 34 - versionCode = 3 - versionName = "1.0.2" + versionCode = 4 + versionName = "1.0.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/2.json b/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/2.json index 59149c1..118f07b 100644 --- a/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/2.json +++ b/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "b8f60b83fc59ce0e4a162b2b304895b1", + "identityHash": "efabf04763a0c05c6fa15fce67ac3c32", "entities": [ { "tableName": "credit_cards", @@ -124,7 +124,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b8f60b83fc59ce0e4a162b2b304895b1')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'efabf04763a0c05c6fa15fce67ac3c32')" ] } } \ No newline at end of file diff --git a/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/3.json b/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/3.json new file mode 100644 index 0000000..6a49b84 --- /dev/null +++ b/app/schemas/com.suyash.creditmanager.data.source.CreditDatabase/3.json @@ -0,0 +1,186 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "efabf04763a0c05c6fa15fce67ac3c32", + "entities": [ + { + "tableName": "credit_cards", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`cardName` TEXT NOT NULL, `last4Digits` TEXT NOT NULL, `expiryDate` TEXT NOT NULL, `billDate` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `cardType` TEXT NOT NULL, `limit` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "cardName", + "columnName": "cardName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "last4Digits", + "columnName": "last4Digits", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "expiryDate", + "columnName": "expiryDate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billDate", + "columnName": "billDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dueDate", + "columnName": "dueDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cardType", + "columnName": "cardType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "limit", + "columnName": "limit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `amount` REAL NOT NULL, `card` INTEGER NOT NULL, `date` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "card", + "columnName": "card", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_transactions_date", + "unique": false, + "columnNames": [ + "date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transactions_date` ON `${TABLE_NAME}` (`date`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "emis", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `amount` REAL NOT NULL, `rate` REAL NOT NULL, `months` INTEGER NOT NULL, `card` INTEGER, `date` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "months", + "columnName": "months", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "card", + "columnName": "card", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'efabf04763a0c05c6fa15fce67ac3c32')" + ] + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..b08dec0 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/data/repository/EMIRepositoryImpl.kt @@ -0,0 +1,24 @@ +package com.suyash.creditmanager.data.repository + +import com.suyash.creditmanager.data.source.dao.EMIDao +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.repository.EMIRepository +import kotlinx.coroutines.flow.Flow + +class EMIRepositoryImpl(private val dao: EMIDao): EMIRepository { + override suspend fun upsertEMI(emi: EMI) { + dao.upsertEMI(emi) + } + + override suspend fun deleteEMI(emi: EMI) { + dao.deleteEMI(emi) + } + + override fun getEMIs(): Flow> { + return dao.getEMIs() + } + + override suspend fun getEMI(id: Int): EMI? { + return dao.getEMI(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/data/source/CreditDatabase.kt b/app/src/main/java/com/suyash/creditmanager/data/source/CreditDatabase.kt index 084232e..1a23c48 100644 --- a/app/src/main/java/com/suyash/creditmanager/data/source/CreditDatabase.kt +++ b/app/src/main/java/com/suyash/creditmanager/data/source/CreditDatabase.kt @@ -4,21 +4,25 @@ import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase import com.suyash.creditmanager.data.source.dao.CreditCardDao +import com.suyash.creditmanager.data.source.dao.EMIDao import com.suyash.creditmanager.data.source.dao.TransactionDao import com.suyash.creditmanager.domain.model.CreditCard +import com.suyash.creditmanager.domain.model.EMI import com.suyash.creditmanager.domain.model.Transaction @Database( - entities = [CreditCard::class, Transaction::class], - version = 2, + entities = [CreditCard::class, Transaction::class, EMI::class], + version = 3, autoMigrations = [ - AutoMigration(from = 1, to = 2) + AutoMigration(from = 1, to = 2), + AutoMigration(from = 2, to = 3) ] ) abstract class CreditDatabase: RoomDatabase() { abstract val creditCardDao: CreditCardDao abstract val transactionDao: TransactionDao + abstract val emiDao: EMIDao companion object { const val DATABASE_NAME = "credit_db" 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 new file mode 100644 index 0000000..e3366fa --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/data/source/dao/EMIDao.kt @@ -0,0 +1,23 @@ +package com.suyash.creditmanager.data.source.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Query +import androidx.room.Upsert +import com.suyash.creditmanager.domain.model.EMI +import kotlinx.coroutines.flow.Flow + +@Dao +interface EMIDao { + @Upsert + suspend fun upsertEMI(emi: EMI) + + @Delete + suspend fun deleteEMI(emi: EMI) + + @Query("SELECT * FROM emis") + fun getEMIs(): Flow> + + @Query("SELECT * FROM emis WHERE id = :id") + suspend fun getEMI(id: Int): EMI? +} \ No newline at end of file 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 9e4f664..5ec4961 100644 --- a/app/src/main/java/com/suyash/creditmanager/di/AppModule.kt +++ b/app/src/main/java/com/suyash/creditmanager/di/AppModule.kt @@ -7,18 +7,25 @@ import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import androidx.room.Room import com.suyash.creditmanager.data.repository.CreditCardRepositoryImpl +import com.suyash.creditmanager.data.repository.EMIRepositoryImpl import com.suyash.creditmanager.data.repository.TransactionRepositoryImpl import com.suyash.creditmanager.data.settings.AppSettings import com.suyash.creditmanager.data.settings.AppSettingsSerializer import com.suyash.creditmanager.data.source.CreditDatabase import com.suyash.creditmanager.domain.repository.CreditCardRepository +import com.suyash.creditmanager.domain.repository.EMIRepository import com.suyash.creditmanager.domain.repository.TransactionRepository import com.suyash.creditmanager.domain.use_case.credit_card.AddCreditCard import com.suyash.creditmanager.domain.use_case.CreditCardUseCases +import com.suyash.creditmanager.domain.use_case.EMIUseCases import com.suyash.creditmanager.domain.use_case.TransactionUseCase import com.suyash.creditmanager.domain.use_case.credit_card.DeleteCreditCard import com.suyash.creditmanager.domain.use_case.credit_card.GetCreditCard import com.suyash.creditmanager.domain.use_case.credit_card.GetCreditCards +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.transaction.AddTransaction import com.suyash.creditmanager.domain.use_case.transaction.DeleteTransaction import com.suyash.creditmanager.domain.use_case.transaction.GetTransaction @@ -48,6 +55,7 @@ object AppModule { @Provides @Singleton fun provideCreditCardRepository(db: CreditDatabase): CreditCardRepository { + db.openHelper.writableDatabase return CreditCardRepositoryImpl(db.creditCardDao) } @@ -57,6 +65,12 @@ object AppModule { return TransactionRepositoryImpl(db.transactionDao) } + @Provides + @Singleton + fun provideEMIRepository(db: CreditDatabase): EMIRepository { + return EMIRepositoryImpl(db.emiDao) + } + @Provides @Singleton fun provideCreditCardUseCases(repository: CreditCardRepository): CreditCardUseCases { @@ -79,6 +93,17 @@ object AppModule { ) } + @Provides + @Singleton + fun provideEMIUseCases(repository: EMIRepository): EMIUseCases { + return EMIUseCases( + getEMIs = GetEMIs(repository), + getEMI = GetEMI(repository), + upsertEMI = AddEMI(repository), + deleteEMI = DeleteEMI(repository) + ) + } + @Singleton @Provides fun provideProtoDataStore(@ApplicationContext appContext: Context): DataStore { 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 new file mode 100644 index 0000000..37b5947 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/model/EMI.kt @@ -0,0 +1,23 @@ +package com.suyash.creditmanager.domain.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import java.time.LocalDate + +@Entity( + tableName = "emis" +) +@TypeConverters(Converters::class) +data class EMI( + val name: String, + val amount: Float, + val rate: Float, + val months: Int, + val card: Int?, + val date: LocalDate, + @PrimaryKey(autoGenerate = true) + val id: Int = 0 +) + +class InvalidEMIException(message: String): Exception(message) 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 new file mode 100644 index 0000000..699c00a --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/repository/EMIRepository.kt @@ -0,0 +1,15 @@ +package com.suyash.creditmanager.domain.repository + +import com.suyash.creditmanager.domain.model.EMI +import kotlinx.coroutines.flow.Flow + +interface EMIRepository { + + suspend fun upsertEMI(emi: EMI) + + suspend fun deleteEMI(emi: EMI) + + fun getEMIs(): Flow> + + suspend fun getEMI(id: Int): EMI? +} \ 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 new file mode 100644 index 0000000..6afa62c --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/EMIUseCases.kt @@ -0,0 +1,13 @@ +package com.suyash.creditmanager.domain.use_case + +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 + +data class EMIUseCases( + val getEMIs: GetEMIs, + val getEMI: GetEMI, + 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/credit_card/GetCreditCards.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/GetCreditCards.kt index 7061554..ec50eb5 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/GetCreditCards.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/credit_card/GetCreditCards.kt @@ -2,7 +2,7 @@ package com.suyash.creditmanager.domain.use_case.credit_card import com.suyash.creditmanager.domain.model.CreditCard import com.suyash.creditmanager.domain.repository.CreditCardRepository -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder import com.suyash.creditmanager.domain.util.OrderType import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -10,25 +10,25 @@ import kotlinx.coroutines.flow.map class GetCreditCards( private val repository: CreditCardRepository ) { - operator fun invoke(creditCardOrder: CreditCardsOrder = CreditCardsOrder.Name(OrderType.Ascending)): Flow> { + operator fun invoke(creditCardOrder: CreditCardOrder = CreditCardOrder.Name(OrderType.Ascending)): Flow> { return repository.getCreditCards().map { creditCards -> when(creditCardOrder.orderType) { is OrderType.Ascending -> { when(creditCardOrder) { - is CreditCardsOrder.Name -> creditCards.sortedBy { it.cardName.lowercase() } - is CreditCardsOrder.Expiry -> creditCards.sortedBy { it.expiryDate } - is CreditCardsOrder.Limit -> creditCards.sortedBy { it.limit } - is CreditCardsOrder.DueDate -> creditCards.sortedBy { it.dueDate } - is CreditCardsOrder.BillDate -> creditCards.sortedBy { it.billDate } + is CreditCardOrder.Name -> creditCards.sortedBy { it.cardName.lowercase() } + is CreditCardOrder.Expiry -> creditCards.sortedBy { it.expiryDate } + is CreditCardOrder.Limit -> creditCards.sortedBy { it.limit } + is CreditCardOrder.DueDate -> creditCards.sortedBy { it.dueDate } + is CreditCardOrder.BillDate -> creditCards.sortedBy { it.billDate } } } is OrderType.Descending -> { when(creditCardOrder) { - is CreditCardsOrder.Name -> creditCards.sortedByDescending { it.cardName.lowercase() } - is CreditCardsOrder.Expiry -> creditCards.sortedByDescending { it.expiryDate } - is CreditCardsOrder.Limit -> creditCards.sortedByDescending { it.limit } - is CreditCardsOrder.DueDate -> creditCards.sortedByDescending { it.dueDate } - is CreditCardsOrder.BillDate -> creditCards.sortedByDescending { it.billDate } + is CreditCardOrder.Name -> creditCards.sortedByDescending { it.cardName.lowercase() } + is CreditCardOrder.Expiry -> creditCards.sortedByDescending { it.expiryDate } + is CreditCardOrder.Limit -> creditCards.sortedByDescending { it.limit } + is CreditCardOrder.DueDate -> creditCards.sortedByDescending { it.dueDate } + is CreditCardOrder.BillDate -> creditCards.sortedByDescending { it.billDate } } } } diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/AddEMI.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/AddEMI.kt new file mode 100644 index 0000000..588915d --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/AddEMI.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 + +class AddEMI(private val repository: EMIRepository) { + + suspend operator fun invoke(emi: EMI) { + repository.upsertEMI(emi) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/DeleteEMI.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/DeleteEMI.kt new file mode 100644 index 0000000..d2d1d94 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/DeleteEMI.kt @@ -0,0 +1,10 @@ +package com.suyash.creditmanager.domain.use_case.emi + +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.repository.EMIRepository + +class DeleteEMI(private val repository: EMIRepository) { + suspend operator fun invoke(emi: EMI) { + return repository.deleteEMI(emi) + } +} \ 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 new file mode 100644 index 0000000..1e201d8 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMI.kt @@ -0,0 +1,10 @@ +package com.suyash.creditmanager.domain.use_case.emi + +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.repository.EMIRepository + +class GetEMI(private val repository: EMIRepository) { + suspend operator fun invoke(id: Int): EMI? { + return repository.getEMI(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIs.kt b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIs.kt new file mode 100644 index 0000000..c6ad00b --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/use_case/emi/GetEMIs.kt @@ -0,0 +1,35 @@ +package com.suyash.creditmanager.domain.use_case.emi + +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.repository.EMIRepository +import com.suyash.creditmanager.domain.util.EMIOrder +import com.suyash.creditmanager.domain.util.OrderType +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class GetEMIs(private val repository: EMIRepository) { + operator fun invoke(emiOrder: EMIOrder = EMIOrder.Date(OrderType.Descending)): Flow> { + return repository.getEMIs().map { emis -> + when(emiOrder.orderType) { + is OrderType.Ascending -> { + when(emiOrder) { + is EMIOrder.Name -> emis.sortedBy { it.name.lowercase() } + is EMIOrder.Amount -> emis.sortedBy { it.amount } + is EMIOrder.Rate -> emis.sortedBy { it.rate } + is EMIOrder.Months -> emis.sortedBy { it.months } + is EMIOrder.Date -> emis.sortedBy { it.date } + } + } + is OrderType.Descending -> { + when(emiOrder) { + is EMIOrder.Name -> emis.sortedByDescending { it.name.lowercase() } + is EMIOrder.Amount -> emis.sortedByDescending { it.amount } + is EMIOrder.Rate -> emis.sortedByDescending { it.rate } + is EMIOrder.Months -> emis.sortedByDescending { it.months } + is EMIOrder.Date -> emis.sortedByDescending { it.date } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/domain/util/CreditCardsOrder.kt b/app/src/main/java/com/suyash/creditmanager/domain/util/CreditCardOrder.kt similarity index 66% rename from app/src/main/java/com/suyash/creditmanager/domain/util/CreditCardsOrder.kt rename to app/src/main/java/com/suyash/creditmanager/domain/util/CreditCardOrder.kt index 251442f..409ed12 100644 --- a/app/src/main/java/com/suyash/creditmanager/domain/util/CreditCardsOrder.kt +++ b/app/src/main/java/com/suyash/creditmanager/domain/util/CreditCardOrder.kt @@ -1,11 +1,11 @@ package com.suyash.creditmanager.domain.util -sealed class CreditCardsOrder(val orderType: OrderType) { - class Name(orderType: OrderType): CreditCardsOrder(orderType) - class Expiry(orderType: OrderType): CreditCardsOrder(orderType) - class Limit(orderType: OrderType): CreditCardsOrder(orderType) - class DueDate(orderType: OrderType): CreditCardsOrder(orderType) - class BillDate(orderType: OrderType): CreditCardsOrder(orderType) +sealed class CreditCardOrder(val orderType: OrderType) { + class Name(orderType: OrderType): CreditCardOrder(orderType) + class Expiry(orderType: OrderType): CreditCardOrder(orderType) + class Limit(orderType: OrderType): CreditCardOrder(orderType) + class DueDate(orderType: OrderType): CreditCardOrder(orderType) + class BillDate(orderType: OrderType): CreditCardOrder(orderType) companion object { val displayNameMap: Map = mapOf( @@ -16,7 +16,7 @@ sealed class CreditCardsOrder(val orderType: OrderType) { "Bill Date" to BillDate::class.simpleName ) - val sorting: Map> = mapOf( + val sorting: Map> = mapOf( Name::class.simpleName to Pair(Name(OrderType.Ascending), Name(OrderType.Descending)), Expiry::class.simpleName to Pair(Expiry(OrderType.Ascending), Expiry(OrderType.Descending)), Limit::class.simpleName to Pair(Limit(OrderType.Ascending), Limit(OrderType.Descending)), diff --git a/app/src/main/java/com/suyash/creditmanager/domain/util/EMIOrder.kt b/app/src/main/java/com/suyash/creditmanager/domain/util/EMIOrder.kt new file mode 100644 index 0000000..66ee30c --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/domain/util/EMIOrder.kt @@ -0,0 +1,9 @@ +package com.suyash.creditmanager.domain.util + +sealed class EMIOrder(val orderType: OrderType) { + class Name(orderType: OrderType): EMIOrder(orderType) + class Amount(orderType: OrderType): EMIOrder(orderType) + class Rate(orderType: OrderType): EMIOrder(orderType) + class Months(orderType: OrderType): EMIOrder(orderType) + class Date(orderType: OrderType): EMIOrder(orderType) +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCScreen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCScreen.kt index f3c736d..672c749 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCScreen.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCScreen.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.suyash.creditmanager.domain.util.CardType -import com.suyash.creditmanager.presentation.util.CCDateMask +import com.suyash.creditmanager.presentation.util.CMDateMask import kotlinx.coroutines.flow.collectLatest @Composable @@ -167,7 +167,7 @@ fun AddEditCCScreen( onValueChange = { newText -> viewModel.onEvent(AddEditCCEvent.EnteredExpiry(newText)) }, - visualTransformation = CCDateMask(), + visualTransformation = CMDateMask(), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), label = { Text("Expiry Date") } ) diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCViewModel.kt index 928ef7d..5cab24b 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCViewModel.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_cc/AddEditCCViewModel.kt @@ -137,7 +137,7 @@ class AddEditCCViewModel @Inject constructor( } catch (e: InvalidCreditCardException) { _eventFlow.emit( UiEvent.ShowSnackbar( - message = e.message ?: "Couldn't save note" + message = e.message ?: "Couldn't save credit card" ) ) } diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIEvent.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIEvent.kt new file mode 100644 index 0000000..02e1b63 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIEvent.kt @@ -0,0 +1,15 @@ +package com.suyash.creditmanager.presentation.add_edit_emis + +import com.suyash.creditmanager.domain.model.CreditCard +import java.time.LocalDate + +sealed class AddEditEMIEvent { + data class SelectedCard(val value: CreditCard): AddEditEMIEvent() + data class EnteredName(val value: String): AddEditEMIEvent() + data class EnteredAmount(val value: String): AddEditEMIEvent() + data class EnteredRate(val value: String): AddEditEMIEvent() + data class EnteredMonths(val value: String): AddEditEMIEvent() + data class EnteredStartDate(val value: LocalDate): AddEditEMIEvent() + data object UpsertEMI: AddEditEMIEvent() + data object BackPressed: AddEditEMIEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIScreen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIScreen.kt new file mode 100644 index 0000000..7b7f05e --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIScreen.kt @@ -0,0 +1,263 @@ +package com.suyash.creditmanager.presentation.add_edit_emis + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +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.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Done +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import kotlinx.coroutines.flow.collectLatest +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.util.concurrent.TimeUnit + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun AddEditEMIScreen( + navController: NavController, + viewModel: AddEditEMIViewModel = hiltViewModel() +) { + val snackbarHostState = remember { SnackbarHostState() } + + var openDatePickerDialog by rememberSaveable { + mutableStateOf(false) + } + + LaunchedEffect(key1 = true) { + viewModel.eventFlow.collectLatest { event -> + when(event) { + is AddEditEMIViewModel.UiEvent.ShowSnackbar -> { + snackbarHostState.showSnackbar( + message = event.message + ) + } + is AddEditEMIViewModel.UiEvent.NavigateUp -> { + navController.navigateUp() + } + } + } + } + + Scaffold ( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + topBar = { + TopAppBar( + title = { + Text(text = if (viewModel.currentEMIId.value == 0) "Add EMI" else "Edit EMI") + }, + navigationIcon = { + IconButton(onClick = { + viewModel.onEvent(AddEditEMIEvent.BackPressed) + }) { + Icon( + Icons.Filled.ArrowBack, + contentDescription = "Go Back" + ) + } + } + ) + }, + floatingActionButton = { + FloatingActionButton( + onClick = { + viewModel.onEvent(AddEditEMIEvent.UpsertEMI) + } + ) { + Icon(Icons.Filled.Done, "Save EMI") + } + } + ) { contentPadding -> + Column( + modifier = Modifier + .fillMaxWidth() + .padding(contentPadding) + .padding(16.dp) + ) { + var ccDropdownExpanded by remember { mutableStateOf(false) } + + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = viewModel.name.value, + onValueChange = { newText -> + viewModel.onEvent(AddEditEMIEvent.EnteredName(newText)) + }, + label = { Text("Name") } + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = viewModel.emiAmount.value, + onValueChange = { newText -> + viewModel.onEvent(AddEditEMIEvent.EnteredAmount(newText)) + }, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + label = { Text("Amount") } + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = viewModel.interestRate.value, + onValueChange = { newText -> + viewModel.onEvent(AddEditEMIEvent.EnteredRate(newText)) + }, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + label = { Text("Rate") } + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = viewModel.months.value, + onValueChange = { newText -> + viewModel.onEvent(AddEditEMIEvent.EnteredMonths(newText)) + }, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + label = { Text("Months") } + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .clickable { + openDatePickerDialog = true + }, + value = viewModel.emiDate.value.format(viewModel.dateFormatter.value), + readOnly = true, + enabled = false, + onValueChange = { }, + label = { Text("Start Date") }, + colors = OutlinedTextFieldDefaults.colors( + disabledTextColor = MaterialTheme.colorScheme.onSurface, + disabledContainerColor = Color.Transparent, + disabledBorderColor = MaterialTheme.colorScheme.outline, + disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledTrailingIconColor = MaterialTheme.colorScheme.onSurface, + disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledSupportingTextColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledPrefixColor = MaterialTheme.colorScheme.onSurfaceVariant, + disabledSuffixColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + ExposedDropdownMenuBox( + modifier = Modifier.fillMaxWidth(), + expanded = ccDropdownExpanded, + onExpandedChange = { + ccDropdownExpanded = !ccDropdownExpanded + }) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .menuAnchor(), + readOnly = true, + value = viewModel.getCCDisplay(), + onValueChange = { }, + label = { Text("Credit Card") }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon( + expanded = ccDropdownExpanded + ) + } + ) + ExposedDropdownMenu( + modifier = Modifier.fillMaxWidth(), + expanded = ccDropdownExpanded, + onDismissRequest = { + ccDropdownExpanded = false + } + ) { + viewModel.creditCards.value.forEach { + DropdownMenuItem( + text = { Text(text = "${it.cardName} (${it.last4Digits})") }, + onClick = { + viewModel.onEvent(AddEditEMIEvent.SelectedCard(it)) + ccDropdownExpanded = false + } + ) + } + } + } + if(openDatePickerDialog) { + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = TimeUnit.DAYS.toMillis(viewModel.emiDate.value.toEpochDay()) + ) + val confirmEnabled = remember { + derivedStateOf { datePickerState.selectedDateMillis != null } + } + + DatePickerDialog( + onDismissRequest = { + openDatePickerDialog = false + }, + confirmButton = { + TextButton( + onClick = { + openDatePickerDialog = false + viewModel.onEvent(AddEditEMIEvent.EnteredStartDate( + Instant.ofEpochMilli( + datePickerState.selectedDateMillis?: + TimeUnit.DAYS.toMillis(LocalDate.now().toEpochDay()) + ).atZone(ZoneId.systemDefault()).toLocalDate() + )) + }, + enabled = confirmEnabled.value + ) { + Text("OK") + } + }, + dismissButton = { + TextButton( + onClick = { + openDatePickerDialog = false + } + ) { + Text("Cancel") + } + } + ) { + DatePicker(state = datePickerState) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIViewModel.kt new file mode 100644 index 0000000..2aaacb7 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_emis/AddEditEMIViewModel.kt @@ -0,0 +1,164 @@ +package com.suyash.creditmanager.presentation.add_edit_emis + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +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.CreditCardOrder +import com.suyash.creditmanager.domain.util.OrderType +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 java.time.LocalDate +import java.time.format.DateTimeFormatter +import javax.inject.Inject + +@HiltViewModel +class AddEditEMIViewModel @Inject constructor( + private val creditCardUseCases: CreditCardUseCases, + private val emiUseCases: EMIUseCases, + savedStateHandle: SavedStateHandle +): ViewModel() { + private var getCreditCardsJob: Job? = null + + private val _dateFormatter = mutableStateOf(DateTimeFormatter.ofPattern("dd/MM/yyyy")) + val dateFormatter: State = _dateFormatter + + private val _creditCards = mutableStateOf(emptyList()) + val creditCards: State> = _creditCards + + private val _selectedCreditCard = mutableIntStateOf(-1) + private val selectedCreditCard: State = _selectedCreditCard + + private val _name = mutableStateOf("") + val name: State = _name + + private val _emiAmount = mutableStateOf("") + val emiAmount: State = _emiAmount + + private val _interestRate = mutableStateOf("") + val interestRate: State = _interestRate + + private val _months = mutableStateOf("") + val months: State = _months + + private val _emiDate = mutableStateOf(LocalDate.now()) + val emiDate: State = _emiDate + + private val _currentEMIId = mutableIntStateOf(-1) + val currentEMIId: State = _currentEMIId + + private val _eventFlow = MutableSharedFlow() + val eventFlow = _eventFlow.asSharedFlow() + + init { + getCreditCards(CreditCardOrder.Name(OrderType.Ascending)) + savedStateHandle.get("emiId")?.let { emiId -> + if(emiId != -1) { + viewModelScope.launch { + emiUseCases.getEMI(emiId)?.also { + _currentEMIId.intValue = it.id + _emiAmount.value = it.name + _emiAmount.value = it.amount.toString() + _interestRate.value = it.rate.toString() + _months.value = it.months.toString() + _emiDate.value = it.date + } + } + } + } + } + + fun onEvent(event: AddEditEMIEvent) { + when (event) { + is AddEditEMIEvent.EnteredName -> { + _name.value = event.value + } + is AddEditEMIEvent.EnteredAmount -> { + if(event.value.isEmpty()) { + _emiAmount.value = event.value + } + val parts = event.value.split(".") + if(event.value.toFloatOrNull() != null + && event.value.toFloat() > 0 + && parts.size <= 2 + && parts.getOrElse(1) { "0" }.length <= 2) { + _emiAmount.value = event.value + } + } + is AddEditEMIEvent.EnteredRate -> { + if(event.value.isEmpty()) { + _interestRate.value = event.value + } + val parts = event.value.split(".") + if(event.value.toFloatOrNull() != null + && event.value.toFloat() > 0 + && parts.size <= 2 + && parts.getOrElse(1) { "0" }.length <= 2) { + _interestRate.value = event.value + } + } + is AddEditEMIEvent.EnteredMonths -> { + if(event.value.isEmpty()) { + _months.value = event.value + } + if(event.value.toIntOrNull() != null && event.value.toInt() > 0) { + _months.value = event.value + } + } + is AddEditEMIEvent.EnteredStartDate -> { + _emiDate.value = event.value + } + is AddEditEMIEvent.SelectedCard -> { + _selectedCreditCard.intValue = event.value.id + } + is AddEditEMIEvent.UpsertEMI -> { + viewModelScope.launch { + emiUseCases.upsertEMI( + EMI( + name = name.value, + amount = emiAmount.value.toFloatOrNull()?:0.0F, + rate = interestRate.value.toFloatOrNull()?:0.0F, + months = months.value.toIntOrNull()?:0, + date = emiDate.value, + card = if(_selectedCreditCard.intValue != -1) _selectedCreditCard.intValue else null + ) + ) + _eventFlow.emit(UiEvent.NavigateUp) + } + } + is AddEditEMIEvent.BackPressed -> { + viewModelScope.launch { + _eventFlow.emit(UiEvent.NavigateUp) + } + } + } + } + + private fun getCreditCards(creditCardsOrder: CreditCardOrder) { + getCreditCardsJob?.cancel() + getCreditCardsJob = creditCardUseCases.getCreditCards(creditCardsOrder).onEach { creditCards -> + _creditCards.value = creditCards + }.launchIn(viewModelScope) + } + + fun getCCDisplay(): String { + val cc: CreditCard = creditCards.value.find { it.id == selectedCreditCard.value } ?: return "" + return "${cc.cardName} (${cc.last4Digits})" + } + + 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/add_edit_txn/AddEditTxnScreen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnScreen.kt index 14d1200..89ea5f0 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnScreen.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnScreen.kt @@ -87,7 +87,9 @@ fun AddEditTxnScreen( Text(text = if (viewModel.currentTxnId.value == 0) "Add Transaction" else "Edit Transaction") }, navigationIcon = { - IconButton(onClick = {viewModel.onEvent(AddEditTxnEvent.BackPressed)}) { + IconButton(onClick = { + viewModel.onEvent(AddEditTxnEvent.BackPressed) + }) { Icon( Icons.Filled.ArrowBack, contentDescription = "Go Back" diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnViewModel.kt index 7d0752f..170fcad 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnViewModel.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/add_edit_txn/AddEditTxnViewModel.kt @@ -10,7 +10,7 @@ import com.suyash.creditmanager.domain.model.CreditCard import com.suyash.creditmanager.domain.model.Transaction import com.suyash.creditmanager.domain.use_case.CreditCardUseCases import com.suyash.creditmanager.domain.use_case.TransactionUseCase -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder import com.suyash.creditmanager.domain.util.OrderType import com.suyash.creditmanager.domain.util.TransactionType import dagger.hilt.android.lifecycle.HiltViewModel @@ -39,7 +39,7 @@ class AddEditTxnViewModel @Inject constructor( val dateFormatter: State = _dateFormatter private val _selectedCreditCard = mutableIntStateOf(-1) - val selectedCreditCard: State = _selectedCreditCard + private val selectedCreditCard: State = _selectedCreditCard private val _txnType = mutableStateOf(TransactionType.DEBIT) val txnType: State = _txnType @@ -57,7 +57,7 @@ class AddEditTxnViewModel @Inject constructor( val eventFlow = _eventFlow.asSharedFlow() init { - getCreditCards(CreditCardsOrder.Name(OrderType.Ascending)) + getCreditCards(CreditCardOrder.Name(OrderType.Ascending)) savedStateHandle.get("txnId")?.let { txnId -> if(txnId != -1) { viewModelScope.launch { @@ -76,7 +76,16 @@ class AddEditTxnViewModel @Inject constructor( fun onEvent(event: AddEditTxnEvent) { when (event) { is AddEditTxnEvent.EnteredAmount -> { - _txnAmount.value = event.value + if(event.value.isEmpty()) { + _txnAmount.value = event.value + } + val parts = event.value.split(".") + if(event.value.toFloatOrNull() != null + && event.value.toFloat() > 0 + && parts.size <= 2 + && parts.getOrElse(1) { "0" }.length <= 2) { + _txnAmount.value = event.value + } } is AddEditTxnEvent.EnteredDate -> { _txnDate.value = event.value @@ -92,7 +101,7 @@ class AddEditTxnViewModel @Inject constructor( transactionUseCase.upsertTransaction( Transaction( type = txnType.value, - amount = txnAmount.value.toFloatOrNull() ?: 0.0F, + amount = txnAmount.value.toFloatOrNull()?:0.0F, card = selectedCreditCard.value, date = txnDate.value, id = currentTxnId.value @@ -109,7 +118,7 @@ class AddEditTxnViewModel @Inject constructor( } } - private fun getCreditCards(creditCardsOrder: CreditCardsOrder) { + private fun getCreditCards(creditCardsOrder: CreditCardOrder) { getCreditCardsJob?.cancel() getCreditCardsJob = creditCardUseCases.getCreditCards(creditCardsOrder).onEach { creditCards -> _creditCards.value = creditCards diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsEvent.kt b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsEvent.kt index b821765..b3bbdce 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsEvent.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsEvent.kt @@ -1,10 +1,10 @@ package com.suyash.creditmanager.presentation.credit_cards import com.suyash.creditmanager.domain.model.CreditCard -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder sealed class CreditCardsEvent { - data class Order(val creditCardsOrder: CreditCardsOrder): CreditCardsEvent() + data class Order(val creditCardsOrder: CreditCardOrder): CreditCardsEvent() data class ToggleBottomSheet(val creditCard: CreditCard): CreditCardsEvent() data class DeleteCreditCard(val creditCard: CreditCard?): CreditCardsEvent() } \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsScreen.kt b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsScreen.kt index 35fc048..f8b5710 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsScreen.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsScreen.kt @@ -38,7 +38,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder import com.suyash.creditmanager.domain.util.OrderType import com.suyash.creditmanager.presentation.credit_cards.component.CreditCardItem import com.suyash.creditmanager.presentation.util.Screen @@ -189,23 +189,23 @@ fun CreditCardsScreen( Column( modifier = Modifier.padding(bottom = 16.dp) ) { - CreditCardsOrder.displayNameMap.forEach { + CreditCardOrder.displayNameMap.forEach { Row( modifier = Modifier .clickable { isSortBottomSheetOpen = false if (viewModel.state.value.creditCardsOrder::class.simpleName == it.value) { if(viewModel.state.value.creditCardsOrder.orderType == OrderType.Ascending) { - CreditCardsOrder.sorting[it.value]?.let { sort -> + CreditCardOrder.sorting[it.value]?.let { sort -> viewModel.onEvent(CreditCardsEvent.Order(sort.second)) } } else if(viewModel.state.value.creditCardsOrder.orderType == OrderType.Descending) { - CreditCardsOrder.sorting[it.value]?.let { sort -> + CreditCardOrder.sorting[it.value]?.let { sort -> viewModel.onEvent(CreditCardsEvent.Order(sort.first)) } } } else { - CreditCardsOrder.sorting[it.value]?.let { sort -> + CreditCardOrder.sorting[it.value]?.let { sort -> viewModel.onEvent(CreditCardsEvent.Order(sort.first)) } } diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsState.kt b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsState.kt index 3255612..7dd234c 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsState.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsState.kt @@ -1,12 +1,12 @@ package com.suyash.creditmanager.presentation.credit_cards import com.suyash.creditmanager.domain.model.CreditCard -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder import com.suyash.creditmanager.domain.util.OrderType data class CreditCardsState( val creditCards: List = emptyList(), - val creditCardsOrder: CreditCardsOrder = CreditCardsOrder.Name(OrderType.Ascending), + val creditCardsOrder: CreditCardOrder = CreditCardOrder.Name(OrderType.Ascending), val selectedCreditCard: CreditCard? = null, val countryCode: String = "IN", val isBottomSheetVisible: Boolean = false diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsViewModel.kt index 6213de5..11f4dbc 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsViewModel.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/CreditCardsViewModel.kt @@ -7,7 +7,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.suyash.creditmanager.data.settings.AppSettings import com.suyash.creditmanager.domain.use_case.CreditCardUseCases -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder import com.suyash.creditmanager.domain.util.OrderType import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job @@ -28,7 +28,7 @@ class CreditCardsViewModel @Inject constructor( private var getCreditCardsJob: Job? = null init { - getCreditCards(CreditCardsOrder.Name(OrderType.Ascending)) + getCreditCards(CreditCardOrder.Name(OrderType.Ascending)) viewModelScope.launch { dataStore.data.collect { _state.value = state.value.copy( @@ -63,7 +63,7 @@ class CreditCardsViewModel @Inject constructor( } } - private fun getCreditCards(creditCardsOrder: CreditCardsOrder) { + private fun getCreditCards(creditCardsOrder: CreditCardOrder) { getCreditCardsJob?.cancel() getCreditCardsJob = creditCardUseCases.getCreditCards(creditCardsOrder).onEach { creditCards -> _state.value = state.value.copy( diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/component/CreditCardItem.kt b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/component/CreditCardItem.kt index dc29f9e..344fc94 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/component/CreditCardItem.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/credit_cards/component/CreditCardItem.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.suyash.creditmanager.domain.model.CreditCard import com.suyash.creditmanager.domain.util.CardType -import com.suyash.creditmanager.presentation.util.CCUtils +import com.suyash.creditmanager.presentation.util.CMUtils @Composable fun CreditCardItem( @@ -54,7 +54,7 @@ fun CreditCardItem( color = MaterialTheme.colorScheme.primary ) Text( - text = CCUtils.currencyMask(creditCard.limit.toFloat(), countryCode), + text = CMUtils.currencyMask(creditCard.limit.toFloat(), countryCode), style = MaterialTheme.typography.bodySmall ) } 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 new file mode 100644 index 0000000..9ae9820 --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsScreen.kt @@ -0,0 +1,57 @@ +package com.suyash.creditmanager.presentation.emis + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.suyash.creditmanager.presentation.emis.component.EMIItem +import com.suyash.creditmanager.presentation.util.Screen + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun EMIsScreen( + navController: NavController, + viewModel: EMIsViewModel = hiltViewModel() +) { + + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = "EMIs") } + ) + }, + floatingActionButton = { + FloatingActionButton( + onClick = { navController.navigate(Screen.AddEditEMIScreen.route) } + ) { + Icon(Icons.Filled.Add, "Add EMI") + } + } + ) { + paddingValues -> + LazyColumn( + modifier = Modifier.padding(paddingValues) + ) { + items(viewModel.state.value.emis) { emi -> + EMIItem( + emi = emi, + countryCode = viewModel.state.value.countryCode, + dateFormat = viewModel.state.value.dateFormat, + modifier = Modifier.fillMaxWidth() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsState.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsState.kt new file mode 100644 index 0000000..16988da --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsState.kt @@ -0,0 +1,14 @@ +package com.suyash.creditmanager.presentation.emis + +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.util.DateFormat +import com.suyash.creditmanager.domain.util.EMIOrder +import com.suyash.creditmanager.domain.util.OrderType + +data class EMIsState( + val emis: List = emptyList(), + val emiOrder: EMIOrder = EMIOrder.Name(OrderType.Ascending), + val countryCode: String = "IN", + val dateFormat: DateFormat = DateFormat.DDMMYYYY, + val selectedEMI: EMI? = null +) diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsViewModel.kt new file mode 100644 index 0000000..b9df2cc --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emis/EMIsViewModel.kt @@ -0,0 +1,50 @@ +package com.suyash.creditmanager.presentation.emis + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.datastore.core.DataStore +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.suyash.creditmanager.data.settings.AppSettings +import com.suyash.creditmanager.domain.use_case.EMIUseCases +import com.suyash.creditmanager.domain.util.EMIOrder +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class EMIsViewModel @Inject constructor( + private val emiUseCases: EMIUseCases, + private val dataStore: DataStore +): ViewModel() { + + private val _state = mutableStateOf(EMIsState()) + val state: State = _state + + private var getEMIsJob: Job? = null + + init { + getEMIs(state.value.emiOrder) + viewModelScope.launch { + dataStore.data.collect { + _state.value = state.value.copy( + countryCode = it.countryCode, + dateFormat = it.dateFormat + ) + } + } + } + + private fun getEMIs(emiOrder: EMIOrder) { + getEMIsJob?.cancel() + getEMIsJob = emiUseCases.getEMIs(emiOrder).onEach { + _state.value = _state.value.copy( + emis = it, + emiOrder = emiOrder + ) + }.launchIn(viewModelScope) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..9205c3b --- /dev/null +++ b/app/src/main/java/com/suyash/creditmanager/presentation/emis/component/EMIItem.kt @@ -0,0 +1,71 @@ +package com.suyash.creditmanager.presentation.emis.component + +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.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.suyash.creditmanager.domain.model.EMI +import com.suyash.creditmanager.domain.util.DateFormat +import com.suyash.creditmanager.presentation.util.CMUtils + +@Composable +fun EMIItem( + emi: EMI, + countryCode: String, + dateFormat: DateFormat, + modifier: Modifier +) { + Column( + modifier = modifier + .padding(16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = emi.name, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = CMUtils.currencyMask(emi.amount, countryCode), + style = MaterialTheme.typography.bodySmall + ) + } + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = CMUtils.formatDate(emi.date, dateFormat), + style = MaterialTheme.typography.bodySmall, + ) + Text( + text = CMUtils.formatDate(emi.date.plusMonths(emi.months.toLong()), dateFormat), + style = MaterialTheme.typography.bodySmall, + ) + } + } + } + } +} \ No newline at end of file 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 e133507..0e3fa55 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 @@ -18,8 +18,10 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument 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.emis.EMIsScreen import com.suyash.creditmanager.presentation.settings.SettingsScreen import com.suyash.creditmanager.presentation.transactions.TransactionsScreen import com.suyash.creditmanager.presentation.util.Screen @@ -74,6 +76,9 @@ fun CreditManager() { composable(route = Screen.TransactionsScreen.route) { TransactionsScreen(navController = navController) } + composable(route = Screen.EMIsScreen.route) { + EMIsScreen(navController = navController) + } composable(route = Screen.SettingsScreen.route) { SettingsScreen() } @@ -99,6 +104,17 @@ fun CreditManager() { ) { AddEditTxnScreen(navController = navController) } + composable( + route = Screen.AddEditEMIScreen.route + "?emiId={emiId}", + arguments = listOf( + navArgument(name = "emiId") { + type = NavType.IntType + defaultValue = -1 + } + ) + ) { + AddEditEMIScreen(navController = navController) + } } } } diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/transactions/TransactionsViewModel.kt b/app/src/main/java/com/suyash/creditmanager/presentation/transactions/TransactionsViewModel.kt index f7bfc59..5cf460e 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/transactions/TransactionsViewModel.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/transactions/TransactionsViewModel.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope import com.suyash.creditmanager.data.settings.AppSettings import com.suyash.creditmanager.domain.use_case.CreditCardUseCases import com.suyash.creditmanager.domain.use_case.TransactionUseCase -import com.suyash.creditmanager.domain.util.CreditCardsOrder +import com.suyash.creditmanager.domain.util.CreditCardOrder import com.suyash.creditmanager.domain.util.OrderType import com.suyash.creditmanager.domain.util.TransactionOrder import dagger.hilt.android.lifecycle.HiltViewModel @@ -32,7 +32,7 @@ class TransactionsViewModel @Inject constructor( private var getTransactionsJob: Job? = null init { - getCreditCards(CreditCardsOrder.Name(OrderType.Ascending)) + getCreditCards(CreditCardOrder.Name(OrderType.Ascending)) getTransactions(TransactionOrder.Date(OrderType.Descending)) viewModelScope.launch { dataStore.data.collect { @@ -69,7 +69,7 @@ class TransactionsViewModel @Inject constructor( } } - private fun getCreditCards(creditCardsOrder: CreditCardsOrder) { + private fun getCreditCards(creditCardsOrder: CreditCardOrder) { getCreditCardsJob?.cancel() getCreditCardsJob = creditCardUseCases.getCreditCards(creditCardsOrder).onEach { creditCards -> _state.value = state.value.copy( diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/transactions/component/TransactionItem.kt b/app/src/main/java/com/suyash/creditmanager/presentation/transactions/component/TransactionItem.kt index da63328..8817167 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/transactions/component/TransactionItem.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/transactions/component/TransactionItem.kt @@ -1,6 +1,5 @@ package com.suyash.creditmanager.presentation.transactions.component -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -17,10 +16,8 @@ import androidx.compose.ui.unit.dp import com.suyash.creditmanager.domain.model.CreditCard import com.suyash.creditmanager.domain.model.Transaction import com.suyash.creditmanager.domain.util.TransactionType -import com.suyash.creditmanager.presentation.util.CCUtils -import com.suyash.creditmanager.ui.theme.CreditBackground +import com.suyash.creditmanager.presentation.util.CMUtils import com.suyash.creditmanager.ui.theme.CreditForeground -import com.suyash.creditmanager.ui.theme.DebitBackground import com.suyash.creditmanager.ui.theme.DebitForeground @Composable @@ -32,7 +29,6 @@ fun TransactionItem( ) { Column( modifier = modifier - .background(color = if(transaction.type == TransactionType.DEBIT) DebitBackground else CreditBackground) .padding(16.dp) ) { Row( @@ -48,7 +44,7 @@ fun TransactionItem( verticalAlignment = Alignment.CenterVertically ) { Text( - text = CCUtils.currencyMask(transaction.amount, countryCode), + text = CMUtils.currencyMask(transaction.amount, countryCode), style = MaterialTheme.typography.bodyLarge, color = if(transaction.type == TransactionType.DEBIT) DebitForeground else CreditForeground ) diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/util/CCDateMask.kt b/app/src/main/java/com/suyash/creditmanager/presentation/util/CMDateMask.kt similarity index 91% rename from app/src/main/java/com/suyash/creditmanager/presentation/util/CCDateMask.kt rename to app/src/main/java/com/suyash/creditmanager/presentation/util/CMDateMask.kt index 94e51c5..0508d7c 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/util/CCDateMask.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/util/CMDateMask.kt @@ -5,10 +5,10 @@ import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation -class CCDateMask: VisualTransformation { +class CMDateMask: VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { val trimmed = if (text.text.length > 4) text.text.substring(0..3) else text.text - val out = CCUtils.expiryDateMask(trimmed) + val out = CMUtils.expiryDateMask(trimmed) val numberOffsetTranslator = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int = diff --git a/app/src/main/java/com/suyash/creditmanager/presentation/util/CCUtils.kt b/app/src/main/java/com/suyash/creditmanager/presentation/util/CMUtils.kt similarity index 64% rename from app/src/main/java/com/suyash/creditmanager/presentation/util/CCUtils.kt rename to app/src/main/java/com/suyash/creditmanager/presentation/util/CMUtils.kt index 880359c..2530bca 100644 --- a/app/src/main/java/com/suyash/creditmanager/presentation/util/CCUtils.kt +++ b/app/src/main/java/com/suyash/creditmanager/presentation/util/CMUtils.kt @@ -1,9 +1,12 @@ package com.suyash.creditmanager.presentation.util +import com.suyash.creditmanager.domain.util.DateFormat import java.text.NumberFormat +import java.time.LocalDate +import java.time.format.DateTimeFormatter import java.util.Locale -class CCUtils { +class CMUtils { companion object { fun expiryDateMask(expiryDate: String): String { var out = "" @@ -17,5 +20,9 @@ class CCUtils { fun currencyMask(amount: Float, countryCode: String): String { return NumberFormat.getCurrencyInstance(Locale("", countryCode)).format(amount) } + + fun formatDate(dateTime: LocalDate, dateFormat: DateFormat): String { + return dateTime.format(DateTimeFormatter.ofPattern(dateFormat.format)) + } } } \ No newline at end of file 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 5bbd8a8..5acc2a0 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,10 +3,12 @@ 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.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.Payments import androidx.compose.material.icons.outlined.Receipt import androidx.compose.material.icons.outlined.Settings import androidx.compose.ui.graphics.vector.ImageVector @@ -29,6 +31,12 @@ sealed class Screen( Icons.Filled.Receipt, Icons.Outlined.Receipt ) + data object EMIsScreen: Screen( + "emis_screen", + "EMIs", + Icons.Filled.Payments, + Icons.Outlined.Payments + ) data object SettingsScreen: Screen( "settings_screen", "Settings", @@ -37,14 +45,20 @@ sealed class Screen( ) data object AddEditCCScreen: Screen( "add_edit_cc_screen", - "Add Card", + "Add/Edit Card", Icons.Filled.Add, Icons.Outlined.Add ) data object AddEditTxnScreen: Screen( "add_edit_txn_screen", - "Add Transaction", + "Add/Edit Transaction", + Icons.Filled.Add, + Icons.Outlined.Add + ) + data object AddEditEMIScreen: Screen( + "add_edit_emi_screen", + "Add/Edit EMI", Icons.Filled.Add, Icons.Outlined.Add ) @@ -53,6 +67,7 @@ sealed class Screen( val bottomBarScreens: List = listOf( CreditCardsScreen, TransactionsScreen, + EMIsScreen, SettingsScreen ) } diff --git a/build.gradle.kts b/build.gradle.kts index 32c637d..cea5870 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,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.2.1" apply false + id("com.android.application") version "8.2.2" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("com.google.gms.google-services") version "4.4.0" apply false id("com.google.dagger.hilt.android") version "2.48" apply false