diff --git a/app/build.gradle b/app/build.gradle index c835fe2f3..70779c97e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,15 +3,16 @@ plugins { id 'kotlin-kapt' id 'org.jetbrains.kotlin.android' id 'com.google.devtools.ksp' version "1.8.21-1.0.11" + id 'kotlin-parcelize' } android { - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "dev.arkbuilders.rate" minSdk 23 - targetSdk 33 + targetSdk 34 versionCode 1 versionName "1.0" setProperty("archivesBaseName", "ark-rate") @@ -97,9 +98,12 @@ dependencies { implementation "androidx.room:room-ktx:2.5.1" kapt "androidx.room:room-compiler:2.5.1" + implementation "androidx.datastore:datastore-preferences:1.0.0" + implementation 'com.jakewharton.timber:timber:5.0.1' implementation "androidx.work:work-runtime-ktx:2.8.1" + implementation 'io.github.oleksandrbalan:tagcloud:1.0.1' implementation 'io.github.raamcosta.compose-destinations:core:1.7.41-beta' ksp 'io.github.raamcosta.compose-destinations:ksp:1.7.41-beta' diff --git a/app/schemas/dev.arkbuilders.rate.data.db.Database/4.json b/app/schemas/dev.arkbuilders.rate.data.db.Database/4.json new file mode 100644 index 000000000..30794fdba --- /dev/null +++ b/app/schemas/dev.arkbuilders.rate.data.db.Database/4.json @@ -0,0 +1,174 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "8d453c8a7668458d4c2ef645d290b752", + "entities": [ + { + "tableName": "RoomCurrencyAmount", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `code` TEXT NOT NULL, `amount` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomCurrencyRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `currencyType` TEXT NOT NULL, `rate` REAL NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomFetchTimestamp", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`currencyType` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`currencyType`))", + "fields": [ + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "currencyType" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomPairAlertCondition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `numeratorCode` TEXT NOT NULL, `denominatorCode` TEXT NOT NULL, `ratio` REAL NOT NULL, `moreNotLess` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "numeratorCode", + "columnName": "numeratorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "denominatorCode", + "columnName": "denominatorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratio", + "columnName": "ratio", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "moreNotLess", + "columnName": "moreNotLess", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `usedCount` INTEGER NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "usedCount", + "columnName": "usedCount", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "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, '8d453c8a7668458d4c2ef645d290b752')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/dev.arkbuilders.rate.data.db.Database/5.json b/app/schemas/dev.arkbuilders.rate.data.db.Database/5.json new file mode 100644 index 000000000..0a25762e7 --- /dev/null +++ b/app/schemas/dev.arkbuilders.rate.data.db.Database/5.json @@ -0,0 +1,180 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "dad9301e29863b229a8b4484aeacf421", + "entities": [ + { + "tableName": "RoomCurrencyAmount", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `code` TEXT NOT NULL, `amount` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomCurrencyRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `currencyType` TEXT NOT NULL, `rate` REAL NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomFetchTimestamp", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`currencyType` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`currencyType`))", + "fields": [ + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "currencyType" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomPairAlertCondition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `numeratorCode` TEXT NOT NULL, `denominatorCode` TEXT NOT NULL, `ratio` REAL NOT NULL, `moreNotLess` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "numeratorCode", + "columnName": "numeratorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "denominatorCode", + "columnName": "denominatorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratio", + "columnName": "ratio", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "moreNotLess", + "columnName": "moreNotLess", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `usedCount` INTEGER NOT NULL, `usedTime` INTEGER NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "usedCount", + "columnName": "usedCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usedTime", + "columnName": "usedTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "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, 'dad9301e29863b229a8b4484aeacf421')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/dev.arkbuilders.rate.data.db.Database/6.json b/app/schemas/dev.arkbuilders.rate.data.db.Database/6.json new file mode 100644 index 000000000..ce590daa7 --- /dev/null +++ b/app/schemas/dev.arkbuilders.rate.data.db.Database/6.json @@ -0,0 +1,206 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "afc6d897c7c9960ca3c832f89c12f702", + "entities": [ + { + "tableName": "RoomCurrencyAmount", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `code` TEXT NOT NULL, `amount` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomCurrencyRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `currencyType` TEXT NOT NULL, `rate` REAL NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomFetchTimestamp", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`currencyType` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`currencyType`))", + "fields": [ + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "currencyType" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomPairAlertCondition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `numeratorCode` TEXT NOT NULL, `denominatorCode` TEXT NOT NULL, `ratio` REAL NOT NULL, `moreNotLess` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "numeratorCode", + "columnName": "numeratorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "denominatorCode", + "columnName": "denominatorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratio", + "columnName": "ratio", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "moreNotLess", + "columnName": "moreNotLess", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `usedCount` INTEGER NOT NULL, `usedTime` INTEGER NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "usedCount", + "columnName": "usedCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usedTime", + "columnName": "usedTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickConvertToCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `code` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "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, 'afc6d897c7c9960ca3c832f89c12f702')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/dev.arkbuilders.rate.data.db.Database/7.json b/app/schemas/dev.arkbuilders.rate.data.db.Database/7.json new file mode 100644 index 000000000..e5e8aa127 --- /dev/null +++ b/app/schemas/dev.arkbuilders.rate.data.db.Database/7.json @@ -0,0 +1,200 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "389590c7e395a4c9f732029d3d7fad49", + "entities": [ + { + "tableName": "RoomCurrencyAmount", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `code` TEXT NOT NULL, `amount` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomCurrencyRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `currencyType` TEXT NOT NULL, `rate` REAL NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomFetchTimestamp", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`currencyType` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`currencyType`))", + "fields": [ + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "currencyType" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomPairAlertCondition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `numeratorCode` TEXT NOT NULL, `denominatorCode` TEXT NOT NULL, `ratio` REAL NOT NULL, `moreNotLess` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "numeratorCode", + "columnName": "numeratorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "denominatorCode", + "columnName": "denominatorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratio", + "columnName": "ratio", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "moreNotLess", + "columnName": "moreNotLess", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `usedCount` INTEGER NOT NULL, `usedTime` INTEGER NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "usedCount", + "columnName": "usedCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usedTime", + "columnName": "usedTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickConvertToCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "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, '389590c7e395a4c9f732029d3d7fad49')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/dev.arkbuilders.rate.data.db.Database/8.json b/app/schemas/dev.arkbuilders.rate.data.db.Database/8.json new file mode 100644 index 000000000..747863184 --- /dev/null +++ b/app/schemas/dev.arkbuilders.rate.data.db.Database/8.json @@ -0,0 +1,200 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "546a9354afc631d3fbcb8ae3d097db8e", + "entities": [ + { + "tableName": "RoomCurrencyAmount", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `code` TEXT NOT NULL, `amount` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomCurrencyRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `currencyType` TEXT NOT NULL, `rate` REAL NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomFetchTimestamp", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`currencyType` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`currencyType`))", + "fields": [ + { + "fieldPath": "currencyType", + "columnName": "currencyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "currencyType" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomPairAlertCondition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `numeratorCode` TEXT NOT NULL, `denominatorCode` TEXT NOT NULL, `ratio` REAL NOT NULL, `moreNotLess` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "numeratorCode", + "columnName": "numeratorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "denominatorCode", + "columnName": "denominatorCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratio", + "columnName": "ratio", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "moreNotLess", + "columnName": "moreNotLess", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `usedCount` INTEGER NOT NULL, `usedTime` INTEGER NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "usedCount", + "columnName": "usedCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usedTime", + "columnName": "usedTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RoomQuickBaseCurrency", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "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, '546a9354afc631d3fbcb8ae3d097db8e')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/Alias.kt b/app/src/main/java/dev/arkbuilders/rate/data/Alias.kt deleted file mode 100644 index b0fd7c623..000000000 --- a/app/src/main/java/dev/arkbuilders/rate/data/Alias.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.arkbuilders.rate.data - -typealias CurrencyCode = String \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyAmount.kt b/app/src/main/java/dev/arkbuilders/rate/data/CurrencyAmount.kt deleted file mode 100644 index e0ba72e4e..000000000 --- a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyAmount.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.arkbuilders.rate.data - -data class CurrencyAmount( - val id: Long = 0, - val code: CurrencyCode, - var amount: Double -) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyName.kt b/app/src/main/java/dev/arkbuilders/rate/data/CurrencyName.kt deleted file mode 100644 index 6eae5b173..000000000 --- a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyName.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.arkbuilders.rate.data - -data class CurrencyName( - val code: CurrencyCode, - val name: String -) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/GeneralCurrencyRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/GeneralCurrencyRepo.kt index 7e982014a..cb480bffb 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/GeneralCurrencyRepo.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/GeneralCurrencyRepo.kt @@ -2,6 +2,9 @@ package dev.arkbuilders.rate.data import dev.arkbuilders.rate.data.crypto.CryptoCurrencyRepo import dev.arkbuilders.rate.data.fiat.FiatCurrencyRepo +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyName +import dev.arkbuilders.rate.data.model.CurrencyRate import javax.inject.Inject import javax.inject.Singleton @@ -24,4 +27,12 @@ class GeneralCurrencyRepo @Inject constructor( currencyRepos.fold(emptyList()) { currencyName, repo -> currencyName + repo.getCurrencyName() } + + suspend fun currencyNameByCode(code: CurrencyCode): CurrencyName { + return fiatRepo.getCurrencyRate().find { it.code == code }?.let { + fiatRepo.currencyNameByCode(code) + } ?: let { + cryptoRepo.currencyNameByCode(code) + } + } } \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/assets/AssetsRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/assets/AssetsRepo.kt index 6d55be571..422db3104 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/assets/AssetsRepo.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/assets/AssetsRepo.kt @@ -3,9 +3,9 @@ package dev.arkbuilders.rate.data.assets import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext -import dev.arkbuilders.rate.data.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyAmount import dev.arkbuilders.rate.data.db.AssetsLocalDataSource -import dev.arkbuilders.rate.data.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyCode import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/dev/arkbuilders/rate/data/crypto/CryptoCurrencyRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/crypto/CryptoCurrencyRepo.kt index a84d31f9c..31f42db1d 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/crypto/CryptoCurrencyRepo.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/crypto/CryptoCurrencyRepo.kt @@ -1,9 +1,10 @@ package dev.arkbuilders.rate.data.crypto -import dev.arkbuilders.rate.data.CurrencyName -import dev.arkbuilders.rate.data.CurrencyRate -import dev.arkbuilders.rate.data.CurrencyRepo -import dev.arkbuilders.rate.data.CurrencyType +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyName +import dev.arkbuilders.rate.data.model.CurrencyRate +import dev.arkbuilders.rate.data.model.CurrencyRepo +import dev.arkbuilders.rate.data.model.CurrencyType import dev.arkbuilders.rate.data.network.NetworkStatus import dev.arkbuilders.rate.data.db.CurrencyRateLocalDataSource import dev.arkbuilders.rate.data.db.FetchTimestampDataSource @@ -21,10 +22,13 @@ class CryptoCurrencyRepo @Inject constructor( override suspend fun fetchRemote(): List = cryptoAPI.getCryptoRates() - .map { CurrencyRate(it.symbol.uppercase(), it.current_price) } + .map { CurrencyRate(type, it.symbol.uppercase(), it.current_price) } override suspend fun getCurrencyName(): List = getCurrencyRate().map { CurrencyName(it.code, name = "") } + + override suspend fun currencyNameByCode(code: CurrencyCode) = + CurrencyName(code, name = "") } \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/AssetsDao.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/AssetsDao.kt index 8e176fab6..d44a72aa9 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/db/AssetsDao.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/AssetsDao.kt @@ -8,8 +8,8 @@ import androidx.room.PrimaryKey import androidx.room.Query import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import dev.arkbuilders.rate.data.CurrencyAmount -import dev.arkbuilders.rate.data.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyCode import javax.inject.Inject @Entity diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/CurrencyRateDao.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/CurrencyRateDao.kt index a0224e7fc..594ba65fc 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/db/CurrencyRateDao.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/CurrencyRateDao.kt @@ -6,9 +6,9 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.PrimaryKey import androidx.room.Query -import dev.arkbuilders.rate.data.CurrencyRate -import dev.arkbuilders.rate.data.CurrencyType -import dev.arkbuilders.rate.data.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyRate +import dev.arkbuilders.rate.data.model.CurrencyType +import dev.arkbuilders.rate.data.model.CurrencyCode import javax.inject.Inject @Entity @@ -38,6 +38,8 @@ class CurrencyRateLocalDataSource @Inject constructor(val dao: CurrencyRateDao) dao.getByType(currencyType.name).map { it.toCurrencyRate() } } -private fun RoomCurrencyRate.toCurrencyRate() = CurrencyRate(code, rate) +private fun RoomCurrencyRate.toCurrencyRate() = + CurrencyRate(CurrencyType.valueOf(currencyType), code, rate) + private fun CurrencyRate.toRoom(currencyType: CurrencyType) = RoomCurrencyRate(code, currencyType.name, rate) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/Database.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/Database.kt index e02aa8964..fc9624266 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/db/Database.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/Database.kt @@ -2,24 +2,29 @@ package dev.arkbuilders.rate.data.db import androidx.room.AutoMigration import androidx.room.RoomDatabase -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase @androidx.room.Database( entities = [ RoomCurrencyAmount::class, RoomCurrencyRate::class, RoomFetchTimestamp::class, - RoomPairAlertCondition::class + RoomPairAlertCondition::class, + RoomQuickCurrency::class, + RoomQuickBaseCurrency::class ], - version = 3, + version = 8, exportSchema = true, + autoMigrations = [ + AutoMigration(from = 3, to = 8) + ] ) abstract class Database : RoomDatabase() { abstract fun assetsDao(): AssetsDao abstract fun rateDao(): CurrencyRateDao abstract fun fetchTimestampDao(): FetchTimestampDao abstract fun pairAlertDao(): PairAlertConditionDao + abstract fun quickDao(): QuickCurrencyDao + abstract fun quickBaseCurrencyDao(): QuickBaseCurrencyDao companion object { const val DB_NAME = "arkrate.db" diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/FetchTimestampDao.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/FetchTimestampDao.kt index f28c16080..3e9bc46c9 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/db/FetchTimestampDao.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/FetchTimestampDao.kt @@ -6,7 +6,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.PrimaryKey import androidx.room.Query -import dev.arkbuilders.rate.data.CurrencyType +import dev.arkbuilders.rate.data.model.CurrencyType import javax.inject.Inject @Entity diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/PairAlertConditionRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/PairAlertConditionRepo.kt index e27e642be..778bedec8 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/db/PairAlertConditionRepo.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/PairAlertConditionRepo.kt @@ -6,7 +6,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.PrimaryKey import androidx.room.Query -import dev.arkbuilders.rate.data.PairAlertCondition +import dev.arkbuilders.rate.data.model.PairAlertCondition import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/QuickBaseCurrency.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/QuickBaseCurrency.kt new file mode 100644 index 000000000..169253aed --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/QuickBaseCurrency.kt @@ -0,0 +1,53 @@ +package dev.arkbuilders.rate.data.db + +import androidx.room.Dao +import androidx.room.Entity +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.PrimaryKey +import androidx.room.Query +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.QuickBaseCurrency +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Entity +data class RoomQuickBaseCurrency( + @PrimaryKey + val code: CurrencyCode +) + +@Dao +interface QuickBaseCurrencyDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(quickCurrency: RoomQuickBaseCurrency) + + @Query("SELECT * FROM RoomQuickBaseCurrency") + suspend fun getAll(): List + + @Query("SELECT * FROM RoomQuickBaseCurrency") + fun allFlow(): Flow> + + @Query("DELETE FROM RoomQuickBaseCurrency where code = :code") + suspend fun delete(code: CurrencyCode) +} + +@Singleton +class QuickBaseCurrencyRepo @Inject constructor( + private val dao: QuickBaseCurrencyDao +) { + suspend fun insert(code: CurrencyCode) = + dao.insert(RoomQuickBaseCurrency(code = code)) + + suspend fun getAll() = dao.getAll().map { it.toQuickCurrency() } + + fun allFlow() = + dao.allFlow().map { list -> list.map { it.toQuickCurrency() } } + + suspend fun delete(code: CurrencyCode) = dao.delete(code) +} + +private fun RoomQuickBaseCurrency.toQuickCurrency() = + QuickBaseCurrency(code) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/db/QuickCurrencyRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/db/QuickCurrencyRepo.kt new file mode 100644 index 000000000..f90334247 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/db/QuickCurrencyRepo.kt @@ -0,0 +1,59 @@ +package dev.arkbuilders.rate.data.db + +import androidx.room.Dao +import androidx.room.Entity +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.PrimaryKey +import androidx.room.Query +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.QuickCurrency +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Entity +data class RoomQuickCurrency( + @PrimaryKey + val code: CurrencyCode, + val usedCount: Int, + val usedTime: Long +) + +@Dao +interface QuickCurrencyDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(quickCurrency: RoomQuickCurrency) + + @Query("SELECT * FROM RoomQuickCurrency") + suspend fun getAll(): List + + @Query("SELECT * FROM RoomQuickCurrency") + fun allFlow(): Flow> + + @Query("SELECT * FROM RoomQuickCurrency where code = :code") + suspend fun getByCode(code: CurrencyCode): RoomQuickCurrency? + + @Query("DELETE FROM RoomQuickCurrency where code = :code") + suspend fun delete(code: CurrencyCode) +} + +@Singleton +class QuickCurrencyRepo @Inject constructor(val dao: QuickCurrencyDao) { + + suspend fun insert(quickCurrency: QuickCurrency) = + dao.insert(quickCurrency.toRoom()) + + suspend fun getAll() = dao.getAll().map { it.toQuickCurrency() } + + suspend fun getByCode(code: CurrencyCode) = dao.getByCode(code)?.toQuickCurrency() + + fun allFlow() = + dao.allFlow().map { list -> list.map { it.toQuickCurrency() } } + + suspend fun delete(code: CurrencyCode) = dao.delete(code) +} + +private fun QuickCurrency.toRoom() = RoomQuickCurrency(code, usedCount, usedTime) +private fun RoomQuickCurrency.toQuickCurrency() = QuickCurrency(code, usedCount, usedTime) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/fiat/FiatCurrencyRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/fiat/FiatCurrencyRepo.kt index f8d4b8d40..f82168208 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/fiat/FiatCurrencyRepo.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/fiat/FiatCurrencyRepo.kt @@ -1,9 +1,11 @@ package dev.arkbuilders.rate.data.fiat -import dev.arkbuilders.rate.data.CurrencyName -import dev.arkbuilders.rate.data.CurrencyRate -import dev.arkbuilders.rate.data.CurrencyRepo -import dev.arkbuilders.rate.data.CurrencyType +import android.content.Context +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyName +import dev.arkbuilders.rate.data.model.CurrencyRate +import dev.arkbuilders.rate.data.model.CurrencyRepo +import dev.arkbuilders.rate.data.model.CurrencyType import dev.arkbuilders.rate.data.network.NetworkStatus import dev.arkbuilders.rate.data.db.CurrencyRateLocalDataSource import dev.arkbuilders.rate.data.db.FetchTimestampDataSource @@ -12,6 +14,7 @@ import javax.inject.Singleton @Singleton class FiatCurrencyRepo @Inject constructor( + private val ctx: Context, private val fiatAPI: FiatAPI, private val local: CurrencyRateLocalDataSource, private val networkStatus: NetworkStatus, @@ -21,13 +24,16 @@ class FiatCurrencyRepo @Inject constructor( override suspend fun fetchRemote(): List = fiatAPI.get().rates.map { (code, rate) -> - CurrencyRate(code, 1.0 / rate) + CurrencyRate(type, code, 1.0 / rate) } override suspend fun getCurrencyName(): List = getCurrencyRate().map { CurrencyName(it.code, fiatCodeToCurrency[it.code]!!) } + + override suspend fun currencyNameByCode(code: CurrencyCode) = + CurrencyName(code, fiatCodeToCurrency[code]!!) } private val fiatCodeToCurrency = mutableMapOf( diff --git a/app/src/main/java/dev/arkbuilders/rate/data/model/Alias.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/Alias.kt new file mode 100644 index 000000000..efb33a39c --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/Alias.kt @@ -0,0 +1,3 @@ +package dev.arkbuilders.rate.data.model + +typealias CurrencyCode = String \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyAmount.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyAmount.kt new file mode 100644 index 000000000..87eaf7cf0 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyAmount.kt @@ -0,0 +1,11 @@ +package dev.arkbuilders.rate.data.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class CurrencyAmount( + val id: Long = 0, + val code: CurrencyCode, + var amount: Double +): Parcelable \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyName.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyName.kt new file mode 100644 index 000000000..7f12cf291 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyName.kt @@ -0,0 +1,6 @@ +package dev.arkbuilders.rate.data.model + +data class CurrencyName( + val code: CurrencyCode, + val name: String, +) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyRate.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyRate.kt similarity index 52% rename from app/src/main/java/dev/arkbuilders/rate/data/CurrencyRate.kt rename to app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyRate.kt index 72b9ab54d..e3db1d272 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyRate.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyRate.kt @@ -1,6 +1,7 @@ -package dev.arkbuilders.rate.data +package dev.arkbuilders.rate.data.model data class CurrencyRate( + val type: CurrencyType, val code: CurrencyCode, val rate: Double ) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyRepo.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyRepo.kt similarity index 95% rename from app/src/main/java/dev/arkbuilders/rate/data/CurrencyRepo.kt rename to app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyRepo.kt index 19b6048c6..b3654e2d0 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyRepo.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyRepo.kt @@ -1,4 +1,4 @@ -package dev.arkbuilders.rate.data +package dev.arkbuilders.rate.data.model import android.util.Log import kotlinx.coroutines.Dispatchers @@ -64,5 +64,7 @@ abstract class CurrencyRepo( abstract suspend fun getCurrencyName(): List + abstract suspend fun currencyNameByCode(code: CurrencyCode): CurrencyName + private val dayInMillis = TimeUnit.DAYS.toMillis(1) } \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyType.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyType.kt similarity index 52% rename from app/src/main/java/dev/arkbuilders/rate/data/CurrencyType.kt rename to app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyType.kt index f3c898c60..dd1b1c496 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/CurrencyType.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/CurrencyType.kt @@ -1,4 +1,4 @@ -package dev.arkbuilders.rate.data +package dev.arkbuilders.rate.data.model enum class CurrencyType { FIAT, CRYPTO diff --git a/app/src/main/java/dev/arkbuilders/rate/data/PairAlertCondition.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/PairAlertCondition.kt similarity index 94% rename from app/src/main/java/dev/arkbuilders/rate/data/PairAlertCondition.kt rename to app/src/main/java/dev/arkbuilders/rate/data/model/PairAlertCondition.kt index c5ab25c4d..718a29441 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/PairAlertCondition.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/PairAlertCondition.kt @@ -1,4 +1,4 @@ -package dev.arkbuilders.rate.data +package dev.arkbuilders.rate.data.model data class PairAlertCondition( val id: Long, diff --git a/app/src/main/java/dev/arkbuilders/rate/data/model/QuickCurrency.kt b/app/src/main/java/dev/arkbuilders/rate/data/model/QuickCurrency.kt new file mode 100644 index 000000000..f7fc8ed1c --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/model/QuickCurrency.kt @@ -0,0 +1,11 @@ +package dev.arkbuilders.rate.data.model + +class QuickCurrency( + val code: CurrencyCode, + val usedCount: Int, + val usedTime: Long +) + +class QuickBaseCurrency( + val code: CurrencyCode +) \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/preferences/Preferences.kt b/app/src/main/java/dev/arkbuilders/rate/data/preferences/Preferences.kt new file mode 100644 index 000000000..a79151b55 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/data/preferences/Preferences.kt @@ -0,0 +1,83 @@ +package dev.arkbuilders.rate.data.preferences + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +enum class QuickScreenShowAs { + TAG_CLOUD, LIST, GRID +} + +enum class QuickScreenSortedBy { + USED_COUNT, USED_TIME +} + +sealed class PreferenceKey(val defaultValue: T) { + object QuickScreenShowAsKey : PreferenceKey(0) + object QuickScreenSortedByKey : PreferenceKey(0) + object FiatFiatRateRound : PreferenceKey(2) + object CryptoCryptoRateRound : PreferenceKey(2) + object FiatCryptoRateRound : PreferenceKey(2) + object CrashReport : PreferenceKey(true) +} + +@Singleton +class Preferences @Inject constructor(val context: Context) { + private val SHARED_PREFERENCES_KEY = "user_preferences" + + private val Context.preferencesDatastore by preferencesDataStore( + SHARED_PREFERENCES_KEY + ) + + private val dataStore = context.preferencesDatastore + + suspend fun get(key: PreferenceKey): T { + val prefKey = resolveKey(key) + return dataStore.data.first()[prefKey] ?: key.defaultValue + } + + suspend fun set(key: PreferenceKey, value: T) { + dataStore.edit { pref -> + val prefKey = resolveKey(key) + pref[prefKey] = value + } + } + + suspend fun flow(key: PreferenceKey) = + dataStore.data.map { pref -> + val prefKey = resolveKey(key) + pref[prefKey] ?: key.defaultValue + } + + private fun resolveKey(key: PreferenceKey): Preferences.Key { + val result = when (key) { + PreferenceKey.QuickScreenSortedByKey -> + intPreferencesKey("quick_screen_sorted_by") + + PreferenceKey.QuickScreenShowAsKey -> + intPreferencesKey("quick_screen_show_as") + + PreferenceKey.FiatFiatRateRound -> + intPreferencesKey("round_fiat_fiat") + + PreferenceKey.CryptoCryptoRateRound -> + intPreferencesKey("round_crypto_crypto") + + PreferenceKey.FiatCryptoRateRound -> + intPreferencesKey("round_crypto_fiat") + + PreferenceKey.CrashReport -> booleanPreferencesKey("crash_report") + + } + + return result as Preferences.Key + } + +} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/data/worker/CurrencyMonitorWorker.kt b/app/src/main/java/dev/arkbuilders/rate/data/worker/CurrencyMonitorWorker.kt index 0d19c7065..c38c8c839 100644 --- a/app/src/main/java/dev/arkbuilders/rate/data/worker/CurrencyMonitorWorker.kt +++ b/app/src/main/java/dev/arkbuilders/rate/data/worker/CurrencyMonitorWorker.kt @@ -3,11 +3,11 @@ package dev.arkbuilders.rate.data.worker import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import dev.arkbuilders.rate.data.CurrencyRate +import dev.arkbuilders.rate.data.model.CurrencyRate import dev.arkbuilders.rate.data.GeneralCurrencyRepo import dev.arkbuilders.rate.data.db.PairAlertConditionRepo import dev.arkbuilders.rate.di.DIManager -import dev.arkbuilders.rate.data.PairAlertCondition +import dev.arkbuilders.rate.data.model.PairAlertCondition import dev.arkbuilders.rate.presentation.utils.NotificationUtils import javax.inject.Inject diff --git a/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt b/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt index bb6dad745..954902c94 100644 --- a/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt +++ b/app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt @@ -6,13 +6,15 @@ import dagger.BindsInstance import dagger.Component import dev.arkbuilders.rate.data.GeneralCurrencyRepo import dev.arkbuilders.rate.data.assets.AssetsRepo +import dev.arkbuilders.rate.data.preferences.Preferences import dev.arkbuilders.rate.data.worker.CurrencyMonitorWorker import dev.arkbuilders.rate.di.module.ApiModule import dev.arkbuilders.rate.di.module.DBModule import dev.arkbuilders.rate.presentation.summary.SummaryViewModelFactory import dev.arkbuilders.rate.presentation.addcurrency.AddCurrencyViewModelFactory import dev.arkbuilders.rate.presentation.assets.AssetsViewModelFactory -import dev.arkbuilders.rate.presentation.shared.SharedViewModel +import dev.arkbuilders.rate.presentation.quick.QuickViewModelFactory +import dev.arkbuilders.rate.presentation.settings.SettingsViewModelFactory import dev.arkbuilders.rate.presentation.shared.SharedViewModelFactory import javax.inject.Singleton @@ -24,10 +26,14 @@ import javax.inject.Singleton ] ) interface AppComponent { - fun summaryViewModelFactory(): SummaryViewModelFactory + fun summaryViewModelFactory(): SummaryViewModelFactory.Factory fun assetsVMFactory(): AssetsViewModelFactory fun addCurrencyVMFactory(): AddCurrencyViewModelFactory + fun quickVMFactory(): QuickViewModelFactory.Factory fun sharedVMFactory(): SharedViewModelFactory + fun settingsVMFactory(): SettingsViewModelFactory + + fun prefs(): Preferences fun generalCurrencyRepo(): GeneralCurrencyRepo fun assetsRepo(): AssetsRepo diff --git a/app/src/main/java/dev/arkbuilders/rate/di/module/DBModule.kt b/app/src/main/java/dev/arkbuilders/rate/di/module/DBModule.kt index cad368447..f6d8205ac 100644 --- a/app/src/main/java/dev/arkbuilders/rate/di/module/DBModule.kt +++ b/app/src/main/java/dev/arkbuilders/rate/di/module/DBModule.kt @@ -22,6 +22,9 @@ class DBModule { @Provides fun assetsDao(db: Database) = db.assetsDao() + @Provides + fun quickDao(db: Database) = db.quickDao() + @Provides fun rateDao(db: Database) = db.rateDao() @@ -30,4 +33,7 @@ class DBModule { @Provides fun fetchTimestampDao(db: Database) = db.fetchTimestampDao() + + @Provides + fun quickBaseDao(db: Database) = db.quickBaseCurrencyDao() } \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt index 1615eb770..a1b7a6bf4 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/App.kt @@ -16,21 +16,28 @@ import org.acra.ktx.initAcra import org.acra.sender.HttpSender import dev.arkbuilders.rate.BuildConfig import dev.arkbuilders.rate.R +import dev.arkbuilders.rate.data.preferences.PreferenceKey import dev.arkbuilders.rate.data.worker.CurrencyMonitorWorker import dev.arkbuilders.rate.di.DIManager +import timber.log.Timber import java.util.concurrent.TimeUnit +import java.util.prefs.Preferences class App : Application() { override fun onCreate() { super.onCreate() - initAcra() + Timber.plant(Timber.DebugTree()) DIManager.init(this) + initAcra() initWorker() } private fun initAcra() = CoroutineScope(Dispatchers.IO).launch { + if (!DIManager.component.prefs().get(PreferenceKey.CrashReport)) + return@launch + initAcra { buildConfigClass = BuildConfig::class.java reportFormat = StringFormat.JSON @@ -48,6 +55,7 @@ class App : Application() { } } + private fun initWorker() { val workManager = WorkManager.getInstance(this) diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt index fbc44bdbb..14ed1063f 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/MainActivity.kt @@ -6,8 +6,11 @@ import androidx.activity.compose.setContent import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import androidx.core.view.WindowCompat +import androidx.lifecycle.lifecycleScope +import dev.arkbuilders.rate.di.DIManager import dev.arkbuilders.rate.di.NavDepContainer import dev.arkbuilders.rate.presentation.theme.ARKRateTheme +import kotlinx.coroutines.launch val LocalDependencyContainer = staticCompositionLocalOf { error("No dependency container provided!") diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/MainScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/MainScreen.kt index 4291723a8..9059f2b71 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/MainScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/MainScreen.kt @@ -10,11 +10,16 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.navigation.compose.currentBackStackEntryAsState import com.ramcosta.composedestinations.DestinationsNavHost -import com.ramcosta.composedestinations.navigation.navigate import com.ramcosta.composedestinations.rememberNavHostEngine +import com.ramcosta.composedestinations.utils.startDestination import dev.arkbuilders.rate.presentation.destinations.AddCurrencyScreenDestination +import dev.arkbuilders.rate.presentation.destinations.AssetsScreenDestination +import dev.arkbuilders.rate.presentation.destinations.QuickScreenDestination +import dev.arkbuilders.rate.presentation.destinations.SummaryScreenDestination +import dev.arkbuilders.rate.presentation.shared.SharedViewModel import dev.arkbuilders.rate.presentation.ui.AnimatedRateBottomNavigation import dev.arkbuilders.rate.presentation.ui.RateScaffold +import dev.arkbuilders.rate.presentation.utils.activityViewModel import dev.arkbuilders.rate.presentation.utils.keyboardAsState @@ -26,12 +31,18 @@ fun MainScreen() { val isKeyboardOpen by keyboardAsState() val bottomBarVisible = rememberSaveable { mutableStateOf(false) } + val navBackStackEntry by navController.currentBackStackEntryAsState() when (navBackStackEntry?.destination?.route) { AddCurrencyScreenDestination.route -> { bottomBarVisible.value = false } + + SummaryScreenDestination.route -> { + bottomBarVisible.value = false + } + else -> { bottomBarVisible.value = true } @@ -41,7 +52,9 @@ fun MainScreen() { bottomBarVisible.value = false RateScaffold( - modifier = Modifier.systemBarsPadding().imePadding(), + modifier = Modifier + .systemBarsPadding() + .imePadding(), navController = navController, bottomBar = { destination -> AnimatedRateBottomNavigation( @@ -59,7 +72,8 @@ fun MainScreen() { engine = engine, navController = navController, navGraph = NavGraphs.root, - modifier = Modifier.padding(it) + modifier = Modifier.padding(it), + startRoute = QuickScreenDestination.startDestination ) } } diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyScreen.kt index dd290fe08..86fad46c2 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Divider import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text @@ -12,11 +13,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.ramcosta.composedestinations.annotation.Destination @@ -24,6 +23,7 @@ import dev.arkbuilders.rate.R import dev.arkbuilders.rate.di.DIManager import dev.arkbuilders.rate.presentation.destinations.AssetsScreenDestination import dev.arkbuilders.rate.presentation.destinations.PairAlertConditionScreenDestination +import dev.arkbuilders.rate.presentation.destinations.QuickScreenDestination import dev.arkbuilders.rate.presentation.shared.SharedViewModel import dev.arkbuilders.rate.presentation.utils.activityViewModel import dev.arkbuilders.rate.presentation.utils.collectInLaunchedEffectWithLifecycle @@ -35,15 +35,18 @@ fun AddCurrencyScreen( sharedViewModel: SharedViewModel = activityViewModel(), fromScreen: String, numeratorNotDenominator: Boolean? = null, - pairAlertConditionId: Long? = null + pairAlertConditionId: Long? = null, + quickBase: Boolean? = null ) { val viewModel: AddCurrencyViewModel = viewModel(factory = DIManager.component.addCurrencyVMFactory()) val ctx = LocalContext.current var filter by remember { mutableStateOf("") } - val filteredCurrencyNameList = viewModel.currencyNameList?.filter { (code, _) -> - code.startsWith(filter.uppercase()) - } ?: emptyList() + val filteredCurrencyNameList = + viewModel.currencyNameList?.filter { (code, name) -> + code.startsWith(filter, ignoreCase = true) || + name.contains(filter, ignoreCase = true) + } ?: emptyList() viewModel.eventsFlow.collectInLaunchedEffectWithLifecycle { event -> @@ -71,11 +74,14 @@ fun AddCurrencyScreen( .padding(start = 20.dp, end = 20.dp, top = 16.dp), value = filter, onValueChange = { filter = it }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + singleLine = true, label = { Text("Search") } ) } + LazyColumn { items(filteredCurrencyNameList.sortedBy { it.code }) { currencyName -> CurrencyItem( @@ -86,6 +92,7 @@ fun AddCurrencyScreen( AssetsScreenDestination.route -> viewModel.addCurrency( currencyName.code ) + PairAlertConditionScreenDestination.route -> { sharedViewModel.onAlertConditionCodePicked( currencyName.code, @@ -94,6 +101,18 @@ fun AddCurrencyScreen( ) navController.popBackStack() } + + QuickScreenDestination.route -> { + if (quickBase!!) { + sharedViewModel.onQuickBaseCurrencyPicked( + currencyName.code + ) + } else + sharedViewModel.onQuickCurrencyPicked( + currencyName.code + ) + navController.popBackStack() + } } } ) diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyViewModel.kt index 9f6f4d0ce..136dd2728 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/addcurrency/AddCurrencyViewModel.kt @@ -7,11 +7,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch -import dev.arkbuilders.rate.data.CurrencyAmount -import dev.arkbuilders.rate.data.CurrencyName +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyName import dev.arkbuilders.rate.data.GeneralCurrencyRepo import dev.arkbuilders.rate.data.assets.AssetsRepo -import dev.arkbuilders.rate.data.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyCode import kotlinx.coroutines.flow.MutableSharedFlow import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsScreen.kt index 2078b9205..46702b4d1 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsScreen.kt @@ -1,6 +1,7 @@ package dev.arkbuilders.rate.presentation.assets import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -9,9 +10,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.material.icons.Icons @@ -25,27 +28,29 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import dev.arkbuilders.rate.data.CurrencyAmount +import dev.arkbuilders.rate.R +import dev.arkbuilders.rate.data.model.CurrencyAmount import dev.arkbuilders.rate.di.DIManager import dev.arkbuilders.rate.presentation.destinations.AddCurrencyScreenDestination import dev.arkbuilders.rate.presentation.destinations.AssetsScreenDestination +import dev.arkbuilders.rate.presentation.destinations.SummaryScreenDestination import dev.arkbuilders.rate.utils.removeFractionalPartIfEmpty -@RootNavGraph(start = true) @Destination @Composable fun AssetsScreen(navigator: DestinationsNavigator) { val viewModel: AssetsViewModel = viewModel(factory = DIManager.component.assetsVMFactory()) - Box(Modifier.fillMaxSize()) { - LazyColumn(modifier = Modifier.fillMaxSize()) { + Column(Modifier.fillMaxSize()) { + LazyColumn(modifier = Modifier.weight(1f)) { items( viewModel.currencyAmountList, key = { it.code } @@ -58,21 +63,22 @@ fun AssetsScreen(navigator: DestinationsNavigator) { } ItemAdd(viewModel, navigator) } - if (viewModel.initialized && viewModel.currencyAmountList.isEmpty()) { - FloatingActionButton( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(10.dp), - onClick = { - navigator.navigate( - AddCurrencyScreenDestination( - fromScreen = AssetsScreenDestination.route - ) - ) - }, - ) { - Icon(Icons.Filled.Add, contentDescription = "Add") + Button( + modifier = Modifier + .padding(8.dp) + .align(Alignment.CenterHorizontally), + onClick = { + navigator.navigate( + SummaryScreenDestination() + ) } + ) { + Icon(painterResource(R.drawable.ic_list_alt), contentDescription = "") + Text( + modifier = Modifier.padding(start = 4.dp), + text = "Summary", + style = MaterialTheme.typography.h6 + ) } } } @@ -130,7 +136,7 @@ private fun LazyListScope.ItemAdd( viewModel: AssetsViewModel, navigator: DestinationsNavigator, ) { - if (viewModel.initialized && viewModel.currencyAmountList.isNotEmpty()) { + if (viewModel.initialized) { item { Box( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsViewModel.kt index da93f89c6..2458adca2 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/assets/AssetsViewModel.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch -import dev.arkbuilders.rate.data.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyAmount import dev.arkbuilders.rate.data.assets.AssetsRepo import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt index fac13b45d..5e47a3e08 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/pairalert/PairAlertConditionScreen.kt @@ -37,7 +37,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import dev.arkbuilders.rate.data.PairAlertCondition +import dev.arkbuilders.rate.data.model.PairAlertCondition import dev.arkbuilders.rate.presentation.destinations.AddCurrencyScreenDestination import dev.arkbuilders.rate.presentation.destinations.PairAlertConditionScreenDestination import dev.arkbuilders.rate.presentation.shared.SharedViewModel diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt new file mode 100644 index 000000000..c4df85ea3 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreen.kt @@ -0,0 +1,403 @@ +@file:OptIn( + ExperimentalComposeUiApi::class, + ExperimentalFoundationApi::class +) + +package dev.arkbuilders.rate.presentation.quick + +import androidx.annotation.StringRes +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedButton +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.RadioButton +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import dev.arkbuilders.rate.R +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.preferences.PreferenceKey +import dev.arkbuilders.rate.data.preferences.QuickScreenShowAs +import dev.arkbuilders.rate.data.preferences.QuickScreenSortedBy +import dev.arkbuilders.rate.di.DIManager +import dev.arkbuilders.rate.presentation.destinations.AddCurrencyScreenDestination +import dev.arkbuilders.rate.presentation.destinations.QuickScreenDestination +import dev.arkbuilders.rate.presentation.destinations.SummaryScreenDestination +import dev.arkbuilders.rate.presentation.shared.SharedViewModel +import dev.arkbuilders.rate.presentation.utils.activityViewModel +import dev.arkbuilders.rate.presentation.utils.collectInLaunchedEffectWithLifecycle +import dev.arkbuilders.rate.utils.removeFractionalPartIfEmpty +import eu.wewox.tagcloud.TagCloud +import eu.wewox.tagcloud.TagCloudItemScope +import eu.wewox.tagcloud.rememberTagCloudState +import kotlinx.coroutines.launch + +@RootNavGraph(start = true) +@Destination +@Composable +fun QuickScreen( + navigator: DestinationsNavigator, + sharedViewModel: SharedViewModel = activityViewModel(), +) { + val viewModel: QuickViewModel = viewModel( + factory = DIManager.component.quickVMFactory().create(sharedViewModel) + ) + + viewModel.navigateToSummary.collectInLaunchedEffectWithLifecycle { amount -> + navigator.navigate(SummaryScreenDestination(amount)) + } + + SortDialog(viewModel) + + if (viewModel.selectedCurrency == null) { + SelectQuickCurrency(navigator, viewModel) + } else { + QuickScreenInputAmount(navigator, viewModel, viewModel.selectedCurrency!!) + } +} + +@Composable +private fun SelectQuickCurrency( + navigator: DestinationsNavigator, + viewModel: QuickViewModel +) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box(Modifier.fillMaxWidth()) { + ShowAsToggle(Modifier.align(Alignment.Center), viewModel) + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = { viewModel.sortDialogVisible = true } + ) { + Icon( + painterResource(R.drawable.ic_sort), + tint = MaterialTheme.colors.secondary, + contentDescription = "" + ) + } + } + when (viewModel.showAs) { + QuickScreenShowAs.TAG_CLOUD -> { + val tagCloudState = rememberTagCloudState() + viewModel.currencyAttractionList.let { list -> + if (list.isNotEmpty()) { + TagCloud( + state = tagCloudState, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(64.dp) + ) { + items(list) { + CloudQuickItem(viewModel, it) + } + } + } else { + Box(modifier = Modifier.weight(1f)) + } + } + } + + QuickScreenShowAs.LIST -> { + LazyColumn( + modifier = Modifier.weight(1f) + ) { + items( + viewModel.currencyAttractionList + .sortedByDescending { it.attractionRatio }, + key = { it.code } + ) { + ListQuickItem( + viewModel, + it + ) + } + } + } + + QuickScreenShowAs.GRID -> { + LazyVerticalStaggeredGrid( + modifier = Modifier + .wrapContentSize() + .weight(1f), + columns = StaggeredGridCells.Fixed(3), + horizontalArrangement = Arrangement.Center + ) { + items( + viewModel.currencyAttractionList, + key = { it.code } + ) { + GridQuickItem(viewModel, it) + } + } + } + } + Button( + modifier = Modifier + .padding(10.dp) + .wrapContentSize(), + colors = ButtonDefaults + .buttonColors(backgroundColor = MaterialTheme.colors.secondary), + onClick = { + navigator.navigate( + AddCurrencyScreenDestination( + fromScreen = QuickScreenDestination.route, + quickBase = false + ) + ) + } + ) { + val iconWidth = remember { 32.dp } + Row { + Icon( + Icons.Filled.Search, + contentDescription = "", + modifier = Modifier.width(iconWidth) + ) + Text(text = "Search") + } + } + } +} + +private const val minSize = 70 +private const val maxSize = 140 +private const val minFontSize = 12 +private const val maxFontSize = 20 +private const val sizeDiff = maxSize - minSize +private const val fontSizeDiff = maxFontSize - minFontSize + +@Composable +private fun TagCloudItemScope.CloudQuickItem( + viewModel: QuickViewModel, + attraction: CurrencyAttraction +) { + val height = (minSize + sizeDiff * attraction.attractionRatio).dp + val fontSize = (minFontSize + fontSizeDiff * attraction.attractionRatio).sp + + Box( + modifier = Modifier + .tagCloudItemFade() + .tagCloudItemScaleDown() + .width(height) + .height(height) + .padding(10.dp) + .clip(RoundedCornerShape(10)) + .background(Color.LightGray) + .clickable { viewModel.onCurrencySelected(attraction.code) }, + contentAlignment = Alignment.Center + ) { + Text(text = attraction.code, fontSize = fontSize) + } +} + +@Composable +private fun GridQuickItem( + viewModel: QuickViewModel, + attraction: CurrencyAttraction +) { + val height = (minSize + sizeDiff * attraction.attractionRatio).dp + val fontSize = (minFontSize + fontSizeDiff * attraction.attractionRatio).sp + + Box( + modifier = Modifier + .fillMaxWidth() + .height(height) + .padding(10.dp) + .clip(RoundedCornerShape(10)) + .background(Color.LightGray) + .clickable { + viewModel.onCurrencySelected(attraction.code) + }, + contentAlignment = Alignment.Center + ) { + Text(text = attraction.code, fontSize = fontSize) + } +} + +@Composable +private fun ListQuickItem( + viewModel: QuickViewModel, + attraction: CurrencyAttraction +) { + Column( + Modifier + .fillMaxWidth() + .clickable(onClick = { viewModel.onCurrencySelected(attraction.code) }) + .padding(horizontal = 20.dp) + ) { + Spacer(modifier = Modifier.height(2.dp)) + Box(Modifier.fillMaxWidth()) { + Text( + modifier = Modifier.align(Alignment.CenterStart), + text = attraction.code, + fontSize = 20.sp + ) + Text( + modifier = Modifier.align(Alignment.CenterEnd), + text = attraction.name.name, + ) + } + Divider() + } +} + +private val showAsStates = listOf( + QuickScreenShowAs.TAG_CLOUD to R.string.show_as_tag_cloud, + QuickScreenShowAs.GRID to R.string.show_as_grid, + QuickScreenShowAs.LIST to R.string.show_as_list, +) + +@Composable +private fun ShowAsToggle( + modifier: Modifier, + viewModel: QuickViewModel, +) { + Surface( + shape = RoundedCornerShape(24.dp), + elevation = 4.dp, + modifier = modifier + .padding(vertical = 10.dp) + .wrapContentSize() + ) { + Row( + modifier = Modifier + .clip(shape = RoundedCornerShape(24.dp)) + .background(Color.LightGray) + ) { + showAsStates.forEach { (showAs, id) -> + Text( + text = stringResource(id), + color = Color.White, + modifier = Modifier + .clip(shape = RoundedCornerShape(24.dp)) + .clickable { + viewModel.apply { + viewModel.showAs = showAs + viewModelScope.launch { + prefs.set( + PreferenceKey.QuickScreenShowAsKey, + showAs.ordinal + ) + } + } + } + .background( + if (showAs == viewModel.showAs) { + MaterialTheme.colors.secondary + } else { + Color.LightGray + } + ) + .padding( + vertical = 12.dp, + horizontal = 16.dp, + ), + ) + } + } + } +} + +@Composable +private fun SortDialog( + viewModel: QuickViewModel, +) { + if (!viewModel.sortDialogVisible) + return + + Dialog(onDismissRequest = { viewModel.sortDialogVisible = false }) { + Surface( + shape = RoundedCornerShape(10.dp), + color = Color.White + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Sort by", style = MaterialTheme.typography.h6) + SortItem( + R.string.sorted_by_used_count, + selected = viewModel.sortedBy == QuickScreenSortedBy.USED_COUNT + ) { + viewModel.onSortedByPick(QuickScreenSortedBy.USED_COUNT) + viewModel.sortDialogVisible = false + } + SortItem( + R.string.sorted_by_used_time, + selected = viewModel.sortedBy == QuickScreenSortedBy.USED_TIME + ) { + viewModel.onSortedByPick(QuickScreenSortedBy.USED_TIME) + viewModel.sortDialogVisible = false + } + } + } + } +} + +@Composable +private fun SortItem( + @StringRes + item: Int, + selected: Boolean, + onClick: () -> Unit +) { + Row( + modifier = Modifier.clickable { if (!selected) onClick() }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(id = item), + style = MaterialTheme.typography.body2 + ) + RadioButton(selected = selected, onClick = { if (!selected) onClick() }) + } +} diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreenInputAmount.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreenInputAmount.kt new file mode 100644 index 000000000..ea3824416 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickScreenInputAmount.kt @@ -0,0 +1,173 @@ +@file:OptIn(ExperimentalComposeUiApi::class) + +package dev.arkbuilders.rate.presentation.quick + +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import dev.arkbuilders.rate.R +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.presentation.destinations.AddCurrencyScreenDestination +import dev.arkbuilders.rate.presentation.destinations.QuickScreenDestination +import dev.arkbuilders.rate.utils.removeFractionalPartIfEmpty + +@Composable +fun QuickScreenInputAmount( + navigator: DestinationsNavigator, + viewModel: QuickViewModel, + amount: CurrencyAmount, +) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + val ctx = LocalContext.current + + var amountInput by remember { + mutableStateOf( + if (amount.amount == 0.0) "" + else amount.amount.removeFractionalPartIfEmpty() + ) + } + BackHandler { + viewModel.selectedCurrency = null + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedTextField( + modifier = Modifier + .padding(top = 8.dp) + .focusRequester(focusRequester) + .onFocusChanged { + if (it.hasFocus) { + keyboardController?.show() + } + }, + value = amountInput, + onValueChange = { newInput -> + amountInput = viewModel.onAmountChanged( + amountInput, + newInput + ) + }, + label = { Text(viewModel.selectedCurrency!!.code) }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done + ), + singleLine = true, + maxLines = 1, + ) + Column( + modifier = Modifier + .weight(1f) + .padding(top = 6.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Convert ${amount.code} to:", + style = MaterialTheme.typography.h5 + ) + viewModel.quickBaseCurrency.forEach { + QuickBaseCurrencyItem(it.code, viewModel) + } + IconButton( + onClick = { + navigator.navigate( + AddCurrencyScreenDestination( + fromScreen = QuickScreenDestination.route, + quickBase = true + ) + ) + } + ) { + Icon( + modifier = Modifier.size(32.dp), + painter = painterResource(R.drawable.ic_add), + tint = MaterialTheme.colors.secondary, + contentDescription = "" + ) + } + } + Button( + modifier = Modifier.padding(12.dp), + onClick = { + if (viewModel.quickBaseCurrency.isEmpty()) { + Toast.makeText( + ctx, + "Add at least one currency to convert into", + Toast.LENGTH_SHORT + ).show() + } else + viewModel.onExchange() + } + ) { + Text(text = "Convert", fontSize = 20.sp) + } + } +} + +@Composable +private fun QuickBaseCurrencyItem( + code: CurrencyCode, + viewModel: QuickViewModel +) { + Row( + Modifier.padding(top = 5.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = code, style = MaterialTheme.typography.h6) + IconButton(onClick = { viewModel.onRemoveBaseCurrency(code) }) { + Icon( + painterResource(R.drawable.ic_delete_outline), + tint = MaterialTheme.colors.secondary, + contentDescription = "" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickViewModel.kt new file mode 100644 index 000000000..925d1509c --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/quick/QuickViewModel.kt @@ -0,0 +1,221 @@ +package dev.arkbuilders.rate.presentation.quick + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.CurrencyName +import dev.arkbuilders.rate.data.GeneralCurrencyRepo +import dev.arkbuilders.rate.data.model.QuickCurrency +import dev.arkbuilders.rate.data.assets.AssetsRepo +import dev.arkbuilders.rate.data.db.QuickBaseCurrencyRepo +import dev.arkbuilders.rate.data.db.QuickCurrencyRepo +import dev.arkbuilders.rate.data.model.QuickBaseCurrency +import dev.arkbuilders.rate.data.preferences.PreferenceKey +import dev.arkbuilders.rate.data.preferences.Preferences +import dev.arkbuilders.rate.data.preferences.QuickScreenShowAs +import dev.arkbuilders.rate.data.preferences.QuickScreenSortedBy +import dev.arkbuilders.rate.presentation.shared.SharedViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import java.util.Calendar + +class CurrencyAttraction( + val code: CurrencyCode, + val name: CurrencyName, + val attractionRatio: Double +) + +class QuickViewModel( + val sharedViewModel: SharedViewModel, + val currencyRepo: GeneralCurrencyRepo, + val assetsRepo: AssetsRepo, + val quickCurrencyRepo: QuickCurrencyRepo, + val quickBaseCurrencyRepo: QuickBaseCurrencyRepo, + val prefs: Preferences +) : ViewModel() { + val currencyAttractionList = mutableStateListOf() + val quickBaseCurrency = mutableStateListOf() + var selectedCurrency: CurrencyAmount? by mutableStateOf(null) + val navigateToSummary = MutableSharedFlow() + var showAs by mutableStateOf(QuickScreenShowAs.TAG_CLOUD) + var sortedBy by mutableStateOf(QuickScreenSortedBy.USED_COUNT) + var sortDialogVisible by mutableStateOf(false) + lateinit var quickCurrencies: List + + init { + viewModelScope.launch { + showAs = QuickScreenShowAs + .values()[prefs.get(PreferenceKey.QuickScreenShowAsKey)] + + sortedBy = QuickScreenSortedBy + .values()[prefs.get(PreferenceKey.QuickScreenSortedByKey)] + + quickCurrencyRepo.allFlow().onEach { currencies -> + quickCurrencies = currencies + calculateAttraction() + }.launchIn(viewModelScope) + + quickBaseCurrencyRepo.allFlow().onEach { + quickBaseCurrency.clear() + quickBaseCurrency.addAll(it) + }.launchIn(viewModelScope) + + sharedViewModel.quickCurrencyPickedFlow.onEach { code -> + selectedCurrency = CurrencyAmount(code = code, amount = 0.0) + }.launchIn(viewModelScope) + } + } + + fun onCurrencySelected(code: CurrencyCode) { + selectedCurrency = CurrencyAmount(code = code, amount = 0.0) + } + + fun onExchange() = viewModelScope.launch { + var usedCount = + quickCurrencyRepo.getByCode(selectedCurrency!!.code)?.usedCount + ?: 0 + quickCurrencyRepo.insert( + QuickCurrency( + selectedCurrency!!.code, + ++usedCount, + Calendar.getInstance().timeInMillis + ) + ) + navigateToSummary.emit(selectedCurrency!!) + } + + fun onAmountChanged( + oldInput: String, + newInput: String + ): String { + val containsDigitsAndDot = Regex("[0-9]*\\.?[0-9]*") + if (!containsDigitsAndDot.matches(newInput)) + return oldInput + + val containsDigit = Regex(".*[0-9].*") + if (!containsDigit.matches(newInput)) { + selectedCurrency = selectedCurrency!!.copy(amount = 0.0) + return newInput + } + + selectedCurrency = selectedCurrency!!.copy(amount = newInput.toDouble()) + + val leadingZeros = "^0+(?=\\d)".toRegex() + + return newInput.replace(leadingZeros, "") + } + + fun onRemoveBaseCurrency(code: CurrencyCode) = viewModelScope.launch { + quickBaseCurrencyRepo.delete(code) + } + + private suspend fun calculateAttraction() { + var attraction = when (sortedBy) { + QuickScreenSortedBy.USED_COUNT -> calcAttractionUsedCount() + QuickScreenSortedBy.USED_TIME -> calcAttractionUsedTime() + } + + // most attractive in center + val attraction1 = attraction.filterIndexed { index, _ -> index % 2 == 0 } + val attraction2Reversed = attraction + .filterIndexed { index, _ -> index % 2 == 1 } + .reversed() + + attraction = attraction1 + attraction2Reversed + + currencyAttractionList.clear() + currencyAttractionList.addAll(attraction) + } + + private suspend fun calcAttractionUsedCount(): List { + val max = quickCurrencies.maxOfOrNull { it.usedCount }?.toDouble() + + val attraction = quickCurrencies.map { + CurrencyAttraction( + it.code, + name = currencyRepo.currencyNameByCode(it.code), + attractionRatio = it.usedCount / max!! + ) + }.sortedBy { it.attractionRatio } + + return attraction + } + + private suspend fun calcAttractionUsedTime(): List { + if (quickCurrencies.isEmpty()) { + return emptyList() + } + + if (quickCurrencies.size == 1) { + val quick = quickCurrencies.first() + return listOf( + CurrencyAttraction( + quick.code, + currencyRepo.currencyNameByCode(quick.code), + attractionRatio = 1.0 + ) + ) + } + + val min = quickCurrencies.minOfOrNull { it.usedTime }!! + + val usedTimesReducedByMin = quickCurrencies.map { it to it.usedTime - min } + val max = usedTimesReducedByMin.maxOfOrNull { (_, time) -> time }!! + + val attraction = usedTimesReducedByMin.map { (quick, time) -> + CurrencyAttraction( + quick.code, + name = currencyRepo.currencyNameByCode(quick.code), + attractionRatio = time.toDouble() / max + ) + }.sortedBy { it.attractionRatio } + + return attraction + } + + fun onSortedByPick( + sortedBy: QuickScreenSortedBy + ) = viewModelScope.launch { + prefs.set(PreferenceKey.QuickScreenSortedByKey, sortedBy.ordinal) + this@QuickViewModel.sortedBy = sortedBy + calculateAttraction() + } +} + +class QuickViewModelFactory @AssistedInject constructor( + @Assisted private val sharedViewModel: SharedViewModel, + private val assetsRepo: AssetsRepo, + private val quickCurrencyRepo: QuickCurrencyRepo, + private val currencyRepo: GeneralCurrencyRepo, + private val quickBaseCurrencyRepo: QuickBaseCurrencyRepo, + private val prefs: Preferences, +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return QuickViewModel( + sharedViewModel, + currencyRepo, + assetsRepo, + quickCurrencyRepo, + quickBaseCurrencyRepo, + prefs + ) as T + } + + @AssistedFactory + interface Factory { + fun create( + @Assisted sharedViewModel: SharedViewModel, + ): QuickViewModelFactory + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/settings/SettingsScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/settings/SettingsScreen.kt new file mode 100644 index 000000000..3d4ce44f3 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/settings/SettingsScreen.kt @@ -0,0 +1,318 @@ +@file:OptIn(ExperimentalMaterialApi::class) + +package dev.arkbuilders.rate.presentation.settings + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Switch +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.lifecycle.viewmodel.compose.viewModel +import com.ramcosta.composedestinations.annotation.Destination +import dev.arkbuilders.rate.R +import dev.arkbuilders.rate.data.preferences.PreferenceKey +import dev.arkbuilders.rate.di.DIManager + +@Destination +@Composable +fun SettingsScreen() { + val vm: SettingsViewModel = + viewModel(factory = DIManager.component.settingsVMFactory()) + + if (vm.initialized) { + Settings(vm) + } else { + Box(modifier = Modifier.fillMaxSize()) { + CircularProgressIndicator() + } + } +} + +@Composable +private fun Settings(vm: SettingsViewModel) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + SettingsGroup(name = R.string.currencies) { + SettingsNumberComp( + name = R.string.fiat_fiat_rate_round, + state = vm.currencyRoundPrefs[PreferenceKey.FiatFiatRateRound]!!, + onSave = { state, value -> + vm.onRoundSave( + PreferenceKey.FiatFiatRateRound, + state, + value + ) + }, + inputFilter = { input -> input.filter { it.isDigit() } }, + onCheck = { true } + ) + SettingsNumberComp( + name = R.string.crypto_crypto_rate_round, + state = vm.currencyRoundPrefs[PreferenceKey.CryptoCryptoRateRound]!!, + onSave = { state, value -> + vm.onRoundSave( + PreferenceKey.CryptoCryptoRateRound, + state, + value + ) + }, + inputFilter = { input -> input.filter { it.isDigit() } }, + onCheck = { true } + ) + SettingsNumberComp( + name = R.string.fiat_crypto_rate_round, + state = vm.currencyRoundPrefs[PreferenceKey.FiatCryptoRateRound]!!, + onSave = { state, value -> + vm.onRoundSave( + PreferenceKey.FiatCryptoRateRound, + state, + value + ) + }, + inputFilter = { input -> input.filter { it.isDigit() } }, + onCheck = { true } + ) + } + SettingsGroup(name = R.string.privacy) { + SettingsSwitchComp( + name = R.string.crash_reports, + state = vm.boolPrefs[PreferenceKey.CrashReport]!! + ) { state -> + vm.onToggle( + PreferenceKey.CrashReport, + state + ) + } + } + } +} + +@Composable +fun SettingsGroup( + @StringRes name: Int, + content: @Composable ColumnScope.() -> Unit +) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + Text(stringResource(id = name), style = MaterialTheme.typography.h5) + Spacer(modifier = Modifier.height(8.dp)) + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(4), + ) { + Column { + content() + } + } + } +} + +@Composable +private fun SettingsSwitchComp( + @DrawableRes icon: Int? = null, + @StringRes iconDesc: Int? = null, + @StringRes name: Int, + state: MutableState, + onClick: (state: MutableState) -> Unit +) { + Surface( + color = Color.Transparent, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + onClick = { + onClick(state) + }, + ) { + Column { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + modifier = Modifier.weight(1f), + verticalAlignment = Alignment.CenterVertically + ) { + icon?.let { + Icon( + painterResource(id = icon), + contentDescription = iconDesc + ?.let { stringResource(id = iconDesc) }, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + } + Text( + text = stringResource(id = name), + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Start, + ) + } + Spacer(modifier = Modifier.width(10.dp)) + Switch( + checked = state.value, + onCheckedChange = { onClick(state) } + ) + } + Divider() + } + } +} + +@Composable +fun SettingsNumberComp( + @DrawableRes icon: Int? = null, + @StringRes iconDesc: Int? = null, + @StringRes name: Int, + state: MutableState, + onSave: (MutableState, String) -> Unit, + inputFilter: (String) -> String, // input filter for the preference + onCheck: (String) -> Boolean +) { + + var isDialogShown by remember { + mutableStateOf(false) + } + + if (isDialogShown) { + Dialog(onDismissRequest = { isDialogShown = isDialogShown.not() }) { + TextEditNumberDialog(name, state, inputFilter, onSave, onCheck) { + isDialogShown = isDialogShown.not() + } + } + } + + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + onClick = { + isDialogShown = isDialogShown.not() + }, + ) { + Column { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + icon?.let { + Icon( + painterResource(id = icon), + contentDescription = iconDesc?.let { stringResource(id = iconDesc) }, + modifier = Modifier.size(24.dp) + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier.padding(8.dp)) { + Text( + text = stringResource(id = name), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Start, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = state.value, + style = MaterialTheme.typography.body2, + textAlign = TextAlign.Start, + ) + } + } + Divider() + } + } +} + +@Composable +private fun TextEditNumberDialog( + @StringRes name: Int, + state: MutableState, + inputFilter: (String) -> String, // filters out not needed letters + onSave: (MutableState, String) -> Unit, + onCheck: (String) -> Boolean, + onDismiss: () -> Unit +) { + + var currentInput by remember { + mutableStateOf(state.value) + } + + var isValid by remember { + mutableStateOf(onCheck(state.value)) + } + + Surface( + shape = RoundedCornerShape(10.dp), + color = Color.White + ) { + + Column( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(16.dp) + ) { + Text(stringResource(id = name)) + Spacer(modifier = Modifier.height(8.dp)) + TextField(currentInput, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + onValueChange = { + // filters the input and removes redundant numbers + val filteredText = inputFilter(it) + isValid = onCheck(filteredText) + currentInput = filteredText + }) + Row { + Spacer(modifier = Modifier.weight(1f)) + Button(onClick = { + onSave(state, currentInput) + onDismiss() + }, enabled = isValid) { + Text("Save") + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/settings/SettingsViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/settings/SettingsViewModel.kt new file mode 100644 index 000000000..5a13dbfa7 --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/settings/SettingsViewModel.kt @@ -0,0 +1,71 @@ +package dev.arkbuilders.rate.presentation.settings + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import dev.arkbuilders.rate.data.preferences.PreferenceKey +import dev.arkbuilders.rate.data.preferences.Preferences +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +class SettingsViewModel( + private val prefs: Preferences +) : ViewModel() { + + val currencyRoundPrefs = mutableMapOf, MutableState>() + val boolPrefs = mutableMapOf, MutableState>() + + var initialized by mutableStateOf(false) + + init { + viewModelScope.launch { + listOf( + PreferenceKey.CrashReport + ).forEach { + boolPrefs[it] = mutableStateOf(prefs.get(it)) + } + listOf( + PreferenceKey.FiatFiatRateRound, + PreferenceKey.CryptoCryptoRateRound, + PreferenceKey.FiatCryptoRateRound + ).forEach { + currencyRoundPrefs[it] = mutableStateOf(prefs.get(it).toString()) + } + } + + initialized = true + } + + fun onToggle( + key: PreferenceKey, + state: MutableState + ) = viewModelScope.launch { + val newValue = state.value.not() + state.value = newValue + prefs.set(key, newValue) + } + + fun onRoundSave( + key: PreferenceKey, + state: MutableState, + _value: String + ) = viewModelScope.launch { + val value = _value.ifEmpty { "0" } + state.value = value + prefs.set(key, value.toInt()) + } +} + +@Singleton +class SettingsViewModelFactory @Inject constructor( + private val prefs: Preferences +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return SettingsViewModel(prefs) as T + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/shared/SharedViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/shared/SharedViewModel.kt index d3f5a8133..fec951b4c 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/shared/SharedViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/shared/SharedViewModel.kt @@ -9,18 +9,22 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch import dev.arkbuilders.rate.data.db.PairAlertConditionRepo -import dev.arkbuilders.rate.data.CurrencyCode -import dev.arkbuilders.rate.data.PairAlertCondition +import dev.arkbuilders.rate.data.db.QuickBaseCurrencyRepo +import dev.arkbuilders.rate.data.model.CurrencyCode +import dev.arkbuilders.rate.data.model.PairAlertCondition +import kotlinx.coroutines.flow.MutableSharedFlow import javax.inject.Inject import javax.inject.Singleton class SharedViewModel( - private val alertConditionRepo: PairAlertConditionRepo + private val alertConditionRepo: PairAlertConditionRepo, + private val quickBaseCurrencyRepo: QuickBaseCurrencyRepo ) : ViewModel() { - var pairAlertConditions = mutableStateListOf() var newCondition by mutableStateOf(PairAlertCondition.defaultInstance()) + var quickCurrencyPickedFlow = MutableSharedFlow() + init { viewModelScope.launch { pairAlertConditions.addAll(alertConditionRepo.getAll()) @@ -115,13 +119,29 @@ class SharedViewModel( newCondition = PairAlertCondition.defaultInstance() } } + + fun onQuickBaseCurrencyPicked(code: CurrencyCode) { + viewModelScope.launch { + quickBaseCurrencyRepo.insert(code) + } + } + + fun onQuickCurrencyPicked(code: CurrencyCode) { + viewModelScope.launch { + quickCurrencyPickedFlow.emit(code) + } + } } @Singleton class SharedViewModelFactory @Inject constructor( - private val alertConditionRepo: PairAlertConditionRepo + private val alertConditionRepo: PairAlertConditionRepo, + private val quickBaseCurrencyRepo: QuickBaseCurrencyRepo ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return SharedViewModel(alertConditionRepo) as T + return SharedViewModel( + alertConditionRepo, + quickBaseCurrencyRepo + ) as T } } \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryScreen.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryScreen.kt index ca143dfea..d71a881da 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryScreen.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryScreen.kt @@ -14,6 +14,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyCode import dev.arkbuilders.rate.di.DIManager import java.math.RoundingMode import java.text.DecimalFormat @@ -24,11 +26,26 @@ private val format = DecimalFormat("0.######").apply { @Destination @Composable -fun SummaryScreen() { +fun SummaryScreen( + amount: CurrencyAmount? = null +) { val viewModel: SummaryViewModel = - viewModel(factory = DIManager.component.summaryViewModelFactory()) + viewModel( + factory = DIManager.component.summaryViewModelFactory().create(amount) + ) Box(Modifier.fillMaxSize()) { LazyColumn(modifier = Modifier.align(Alignment.Center)) { + amount?.let { + item { + Box( + modifier = Modifier + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Text(text = "Quick ${amount.code}", fontSize = 20.sp) + } + } + } item { TotalCard(viewModel) } @@ -111,7 +128,7 @@ private fun ExchangeCard(viewModel: SummaryViewModel) { modifier = Modifier.align(Alignment.CenterStart) ) Text( - format.format(it.value), + it.value, modifier = Modifier.align(Alignment.CenterEnd) ) Divider() diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryViewModel.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryViewModel.kt index 35a4ab8cc..88104d3fa 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/summary/SummaryViewModel.kt @@ -3,31 +3,62 @@ package dev.arkbuilders.rate.presentation.summary import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dev.arkbuilders.rate.data.model.CurrencyAmount +import dev.arkbuilders.rate.data.model.CurrencyType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import dev.arkbuilders.rate.data.GeneralCurrencyRepo import dev.arkbuilders.rate.data.assets.AssetsRepo -import javax.inject.Inject -import javax.inject.Singleton +import dev.arkbuilders.rate.data.db.QuickBaseCurrencyRepo +import dev.arkbuilders.rate.data.preferences.PreferenceKey +import dev.arkbuilders.rate.data.preferences.Preferences +import java.text.DecimalFormat class SummaryViewModel( + private val selectedAmount: CurrencyAmount?, private val assetsRepo: AssetsRepo, - private val currencyRepo: GeneralCurrencyRepo + private val currencyRepo: GeneralCurrencyRepo, + private val quickBaseCurrencyRepo: QuickBaseCurrencyRepo, + private val prefs: Preferences, ) : ViewModel() { var total = MutableStateFlow?>(null) - var exchange = MutableStateFlow?>(null) + var exchange = MutableStateFlow?>(null) + private lateinit var fiatFiatFormat: DecimalFormat + private lateinit var cryptoCryptoFormat: DecimalFormat + private lateinit var fiatCryptoFormat: DecimalFormat init { viewModelScope.launch { - calculateTotal() - calculateExchange() + prefs.get(PreferenceKey.FiatFiatRateRound).let { + fiatFiatFormat = createFormat(it) + } + prefs.get(PreferenceKey.CryptoCryptoRateRound).let { + cryptoCryptoFormat = createFormat(it) + } + prefs.get(PreferenceKey.FiatCryptoRateRound).let { + fiatCryptoFormat = createFormat(it) + } + if (selectedAmount == null) { + viewModelScope.launch { + calculateTotal() + calculateExchange() + } + assetsRepo.allCurrencyAmountFlow().onEach { + calculateTotal() + calculateExchange() + }.launchIn(viewModelScope) + } else { + viewModelScope.launch { + calculateSelectedTotal() + calculateSelectedExchange() + } + } } - assetsRepo.allCurrencyAmountFlow().onEach { - calculateTotal() - calculateExchange() - }.launchIn(viewModelScope) } private suspend fun calculateTotal() { @@ -50,30 +81,125 @@ class SummaryViewModel( } private suspend fun calculateExchange() { - val count = assetsRepo.allCurrencyAmount().map { + val count = assetsRepo.allCurrencyAmount().associate { it.code to it.amount - }.toMap() - val rates = currencyRepo.getCurrencyRate().associate { it.code to it.rate } - val result = mutableMapOf() + } + val rates = currencyRepo.getCurrencyRate().associateBy { it.code } + val result = mutableMapOf() count.forEach { i -> count.forEach { j -> if (j.key != i.key) { - result["${i.key}/${j.key}"] = rates[i.key]!! / rates[j.key]!! + val rate1 = rates[i.key]!! + val rate2 = rates[j.key]!! + val exchangeRate = rate1.rate / rate2.rate + result["${i.key}/${j.key}"] = + pickFormatter(rate1.type, rate2.type) + .format(exchangeRate) } } } exchange.emit(result) } + + private suspend fun calculateSelectedTotal() { + val baseList = quickBaseCurrencyRepo.getAll().map { it.code } + val rates = currencyRepo + .getCurrencyRate() + .associate { it.code to it.rate } + val result = mutableMapOf() + val USD = selectedAmount!!.amount * rates[selectedAmount.code]!! + result[selectedAmount.code] = selectedAmount.amount + + baseList.forEach { + result[it] = USD / rates[it]!! + } + + + total.emit(result) + } + + private suspend fun calculateSelectedExchange() { + val baseList = + quickBaseCurrencyRepo.getAll().map { it.code }.toMutableList() + val rates = currencyRepo + .getCurrencyRate() + .associateBy { it.code } + val result = mutableMapOf() + + baseList.find { + it == selectedAmount!!.code + }?.let { duplicate -> + baseList.remove(duplicate) + } + + baseList.add(0, selectedAmount!!.code) + + baseList.forEach { iter1 -> + baseList.forEach { iter2 -> + if (iter1 == iter2) { + return@forEach + } + + if (iter1 == selectedAmount.code || + iter2 == selectedAmount.code + ) { + val rate1 = rates[iter1]!! + val rate2 = rates[iter2]!! + + val exchangeRate = rate1.rate / rate2.rate + result["${iter1}/${iter2}"] = + pickFormatter(rate1.type, rate2.type).format(exchangeRate) + } + } + } + + exchange.emit(result) + } + + private fun createFormat(n: Int): DecimalFormat { + return if (n == 0) + DecimalFormat("0") + else + DecimalFormat("0." + "#".repeat(n)) + } + + private fun pickFormatter( + type1: CurrencyType, + type2: CurrencyType + ): DecimalFormat { + if (type1 == CurrencyType.FIAT && type2 == CurrencyType.FIAT) + return fiatFiatFormat + + if (type1 == CurrencyType.CRYPTO && type2 == CurrencyType.CRYPTO) + return cryptoCryptoFormat + + return fiatCryptoFormat + } } -@Singleton -class SummaryViewModelFactory @Inject constructor( +class SummaryViewModelFactory @AssistedInject constructor( + @Assisted private val amount: CurrencyAmount?, private val assetsRepo: AssetsRepo, - private val currencyRepo: GeneralCurrencyRepo + private val currencyRepo: GeneralCurrencyRepo, + private val quickBaseCurrencyRepo: QuickBaseCurrencyRepo, + private val prefs: Preferences ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return SummaryViewModel(assetsRepo, currencyRepo) as T + return SummaryViewModel( + amount, + assetsRepo, + currencyRepo, + quickBaseCurrencyRepo, + prefs + ) as T + } + + @AssistedFactory + interface Factory { + fun create( + @Assisted amount: CurrencyAmount?, + ): SummaryViewModelFactory } } \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/ui/BottomNavigation.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/ui/BottomNavigation.kt index 8f0a46b1b..2048f84ce 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/ui/BottomNavigation.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/ui/BottomNavigation.kt @@ -1,77 +1,92 @@ +@file:OptIn(ExperimentalAnimationApi::class) + package dev.arkbuilders.rate.presentation.ui +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.with import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.sp -import androidx.navigation.compose.currentBackStackEntryAsState -import com.ramcosta.composedestinations.spec.Direction -import com.ramcosta.composedestinations.spec.DirectionDestinationSpec import dev.arkbuilders.rate.R import dev.arkbuilders.rate.presentation.destinations.AssetsScreenDestination import dev.arkbuilders.rate.presentation.destinations.Destination import dev.arkbuilders.rate.presentation.destinations.PairAlertConditionScreenDestination +import dev.arkbuilders.rate.presentation.destinations.QuickScreenDestination +import dev.arkbuilders.rate.presentation.destinations.SettingsScreenDestination import dev.arkbuilders.rate.presentation.destinations.SummaryScreenDestination sealed class BottomNavItem( var title: String, var icon: Int, - var direction: DirectionDestinationSpec + var route: String, ) { object Assets : BottomNavItem( "Assets", R.drawable.ic_list, - AssetsScreenDestination - ) - - object Summary : BottomNavItem( - "Summary", - R.drawable.ic_list_alt, - SummaryScreenDestination + AssetsScreenDestination.route ) object PairAlert : BottomNavItem( - "Notifications", + "Alerts", R.drawable.ic_notifications, - PairAlertConditionScreenDestination + PairAlertConditionScreenDestination.route ) + + object Quick : BottomNavItem( + "Quick", + R.drawable.currency_exchange, + QuickScreenDestination.route + ) + + object Settings : BottomNavItem( + "Settings", + R.drawable.ic_settings, + SettingsScreenDestination.route + ) + } @Composable fun AnimatedRateBottomNavigation( currentDestination: Destination, - onBottomBarItemClick: (Direction) -> Unit, + onBottomBarItemClick: (String) -> Unit, bottomBarVisible: State ) { - AnimatedVisibility( - visible = bottomBarVisible.value, - enter = fadeIn(), - exit = fadeOut(), - content = { RateBottomNavigation(currentDestination, onBottomBarItemClick) } - ) + AnimatedContent( + targetState = bottomBarVisible.value, + transitionSpec = { + slideInVertically { height -> height } with + slideOutVertically { height -> height } + } + ) { expanded -> + if (expanded) + RateBottomNavigation(currentDestination, onBottomBarItemClick) + } } @Composable fun RateBottomNavigation( currentDestination: Destination, - onBottomBarItemClick: (Direction) -> Unit + onBottomBarItemClick: (String) -> Unit ) { val items = listOf( + BottomNavItem.Quick, BottomNavItem.Assets, - BottomNavItem.Summary, BottomNavItem.PairAlert, + BottomNavItem.Settings ) BottomNavigation( @@ -95,8 +110,8 @@ fun RateBottomNavigation( selectedContentColor = Color.Black, unselectedContentColor = Color.Black.copy(0.4f), alwaysShowLabel = true, - selected = currentDestination == item.direction, - onClick = { onBottomBarItemClick(item.direction) } + selected = item.route.contains(currentDestination.baseRoute), + onClick = { onBottomBarItemClick(item.route) } ) } } diff --git a/app/src/main/java/dev/arkbuilders/rate/presentation/utils/NotificationUtils.kt b/app/src/main/java/dev/arkbuilders/rate/presentation/utils/NotificationUtils.kt index c0b80b3e7..5c38e40fe 100644 --- a/app/src/main/java/dev/arkbuilders/rate/presentation/utils/NotificationUtils.kt +++ b/app/src/main/java/dev/arkbuilders/rate/presentation/utils/NotificationUtils.kt @@ -10,7 +10,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import dev.arkbuilders.rate.R -import dev.arkbuilders.rate.data.PairAlertCondition +import dev.arkbuilders.rate.data.model.PairAlertCondition import dev.arkbuilders.rate.presentation.MainActivity object NotificationUtils { diff --git a/app/src/main/res/drawable/currency_exchange.xml b/app/src/main/res/drawable/currency_exchange.xml new file mode 100644 index 000000000..d2a8e61db --- /dev/null +++ b/app/src/main/res/drawable/currency_exchange.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_outline.xml b/app/src/main/res/drawable/ic_delete_outline.xml new file mode 100644 index 000000000..ef3664912 --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_outline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 000000000..298a5a1ff --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml new file mode 100644 index 000000000..ed8927fc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46d3340b7..1a1b46d3c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,4 +6,17 @@ Sorry, the application crashed. Please send a report to the developers. Crash You can add a comment here: + Show quick currencies as tag cloud + Appearance + Privacy + Crash reports + Currencies + Fiat/fiat rate round + Crypto/crypto rate round + Fiat/crypto rate round + 3D + Grid + List + Used count + Used time