Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-25 13:04:58 +03:00
parent 78a69e656b
commit 2596a73c7b
13 changed files with 591 additions and 0 deletions

View file

@ -8,6 +8,7 @@ plugins {
alias(deps.plugins.hilt.android) apply false
alias(deps.plugins.google.services) apply false
alias(deps.plugins.firebase.crashlytics) apply false
alias(deps.plugins.room) apply false
}
val clean by tasks.registering {

View file

@ -3,11 +3,16 @@ plugins {
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
alias(deps.plugins.room)
id("configuration")
}
android {
namespace = "com.tangem.datasource"
room {
schemaDirectory("$projectDir/schemas")
}
}
dependencies {
@ -61,4 +66,7 @@ dependencies {
/** Local storages */
implementation(deps.androidx.datastore)
implementation(deps.room.runtime)
implementation(deps.room.ktx)
kapt(deps.room.compiler)
}

View file

@ -0,0 +1,304 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "a8a710af25033ee27e5043d001385234",
"entities": [
{
"tableName": "UserWalletEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `artworkUrl` TEXT NOT NULL, `isMultiCurrency` INTEGER NOT NULL, `hasBackupError` INTEGER NOT NULL, `cardsInWallet` TEXT NOT NULL, `ordinalNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "artworkUrl",
"columnName": "artworkUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isMultiCurrency",
"columnName": "isMultiCurrency",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hasBackupError",
"columnName": "hasBackupError",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "cardsInWallet",
"columnName": "cardsInWallet",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ordinalNumber",
"columnName": "ordinalNumber",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_UserWalletEntity_id",
"unique": true,
"columnNames": [
"id"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_UserWalletEntity_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "index_UserWalletEntity_ordinalNumber",
"unique": true,
"columnNames": [
"ordinalNumber"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_UserWalletEntity_ordinalNumber` ON `${TABLE_NAME}` (`ordinalNumber`)"
}
],
"foreignKeys": []
},
{
"tableName": "CryptoCurrenciesAccountEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `userWalletId` TEXT NOT NULL, `title` TEXT NOT NULL, `currenciesCount` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `ordinalNumber` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`userWalletId`) REFERENCES `UserWalletEntity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userWalletId",
"columnName": "userWalletId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currenciesCount",
"columnName": "currenciesCount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isArchived",
"columnName": "isArchived",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ordinalNumber",
"columnName": "ordinalNumber",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_CryptoCurrenciesAccountEntity_id",
"unique": false,
"columnNames": [
"id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrenciesAccountEntity_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "index_CryptoCurrenciesAccountEntity_userWalletId",
"unique": false,
"columnNames": [
"userWalletId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrenciesAccountEntity_userWalletId` ON `${TABLE_NAME}` (`userWalletId`)"
}
],
"foreignKeys": [
{
"table": "UserWalletEntity",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"userWalletId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "CryptoCurrencyEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `currencyBackendId` TEXT, `networkId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `userWalletId` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `decimals` INTEGER NOT NULL, `contractAddress` TEXT, `derivationPath` TEXT, FOREIGN KEY(`accountId`) REFERENCES `CryptoCurrenciesAccountEntity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userWalletId`) REFERENCES `UserWalletEntity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "currencyBackendId",
"columnName": "currencyBackendId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "networkId",
"columnName": "networkId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "accountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userWalletId",
"columnName": "userWalletId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "symbol",
"columnName": "symbol",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "decimals",
"columnName": "decimals",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "contractAddress",
"columnName": "contractAddress",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "derivationPath",
"columnName": "derivationPath",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_CryptoCurrencyEntity_currencyBackendId",
"unique": false,
"columnNames": [
"currencyBackendId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_currencyBackendId` ON `${TABLE_NAME}` (`currencyBackendId`)"
},
{
"name": "index_CryptoCurrencyEntity_networkId",
"unique": false,
"columnNames": [
"networkId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_networkId` ON `${TABLE_NAME}` (`networkId`)"
},
{
"name": "index_CryptoCurrencyEntity_accountId",
"unique": false,
"columnNames": [
"accountId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_accountId` ON `${TABLE_NAME}` (`accountId`)"
},
{
"name": "index_CryptoCurrencyEntity_userWalletId",
"unique": false,
"columnNames": [
"userWalletId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_CryptoCurrencyEntity_userWalletId` ON `${TABLE_NAME}` (`userWalletId`)"
}
],
"foreignKeys": [
{
"table": "CryptoCurrenciesAccountEntity",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"accountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "UserWalletEntity",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"userWalletId"
],
"referencedColumns": [
"id"
]
}
]
}
],
"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, 'a8a710af25033ee27e5043d001385234')"
]
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.datasource.local.db
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.tangem.datasource.local.db.dao.CryptoCurrenciesAccountDao
import com.tangem.datasource.local.db.dao.CryptoCurrencyDao
import com.tangem.datasource.local.db.dao.UserWalletDao
import com.tangem.datasource.local.db.entity.CryptoCurrenciesAccountEntity
import com.tangem.datasource.local.db.entity.CryptoCurrencyEntity
import com.tangem.datasource.local.db.entity.UserWalletEntity
import com.tangem.datasource.local.db.utils.Converters
@Database(
entities = [
UserWalletEntity::class,
CryptoCurrenciesAccountEntity::class,
CryptoCurrencyEntity::class,
],
version = 1,
)
@TypeConverters(Converters::class)
abstract class TangemDatabase : RoomDatabase() {
abstract fun userWalletDao(): UserWalletDao
abstract fun cryptoCurrencyDao(): CryptoCurrencyDao
abstract fun cryptoCurrenciesAccountDao(): CryptoCurrenciesAccountDao
}

View file

@ -0,0 +1,25 @@
package com.tangem.datasource.local.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.tangem.datasource.local.db.entity.CryptoCurrenciesAccountEntity
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@Dao
interface CryptoCurrenciesAccountDao {
@Insert
suspend fun insert(account: CryptoCurrenciesAccountEntity)
@Update
suspend fun update(account: CryptoCurrenciesAccountEntity)
@Query("SELECT * FROM CryptoCurrenciesAccountEntity WHERE userWalletId = :userWalletId")
fun observeByUserWalletId(userWalletId: UserWalletId): Flow<List<CryptoCurrenciesAccountEntity>>
@Query("SELECT * FROM CryptoCurrenciesAccountEntity WHERE userWalletId = :userWalletId")
suspend fun selectByUserWalletId(userWalletId: UserWalletId): List<CryptoCurrenciesAccountEntity>
}

View file

@ -0,0 +1,39 @@
package com.tangem.datasource.local.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.tangem.datasource.local.db.entity.CryptoCurrencyEntity
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@Dao
interface CryptoCurrencyDao {
@Insert
suspend fun insert(currencies: List<CryptoCurrencyEntity>)
@Query(
"""
SELECT * FROM CryptoCurrencyEntity
WHERE userWalletId = :userWalletId AND accountId = :accountId
""",
)
suspend fun selectByAccountId(userWalletId: UserWalletId, accountId: Int): List<CryptoCurrencyEntity>
@Query(
"""
SELECT * FROM CryptoCurrencyEntity
WHERE userWalletId = :userWalletId AND accountId = :accountId
""",
)
fun observeByAccountId(userWalletId: UserWalletId, accountId: Int): Flow<List<CryptoCurrencyEntity>>
@Query(
"""
SELECT COUNT(*) FROM CryptoCurrencyEntity
WHERE userWalletId = :userWalletId
""",
)
suspend fun countByUserWalletId(userWalletId: UserWalletId): Int
}

View file

@ -0,0 +1,30 @@
package com.tangem.datasource.local.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.tangem.datasource.local.db.entity.UserWalletEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface UserWalletDao {
@Insert
suspend fun insert(vararg wallets: UserWalletEntity)
@Update
suspend fun update(wallet: UserWalletEntity)
@Query("SELECT * FROM UserWalletEntity")
suspend fun selectAll(): List<UserWalletEntity>
@Query("SELECT * FROM UserWalletEntity")
fun observeAll(): Flow<List<UserWalletEntity>>
@Query("SELECT * FROM UserWalletEntity WHERE id = :id")
suspend fun selectById(id: String): UserWalletEntity?
@Query("SELECT * FROM UserWalletEntity WHERE id = :id")
fun observeById(id: String): Flow<UserWalletEntity>
}

View file

@ -0,0 +1,28 @@
package com.tangem.datasource.local.db.di
import android.content.Context
import androidx.room.Room
import com.tangem.datasource.local.db.TangemDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object TangemDatabaseModule {
private const val DATABASE_NAME = "tangem_database.db"
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): TangemDatabase {
return Room.databaseBuilder(
context,
TangemDatabase::class.java,
DATABASE_NAME,
).build()
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.datasource.local.db.entity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = UserWalletEntity::class,
parentColumns = ["id"],
childColumns = ["userWalletId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index(value = ["id"]),
Index(value = ["userWalletId"]),
],
)
data class CryptoCurrenciesAccountEntity(
@PrimaryKey
val id: Int,
val userWalletId: String,
val title: String,
val currenciesCount: Int,
val isArchived: Boolean,
val ordinalNumber: Int,
)

View file

@ -0,0 +1,42 @@
package com.tangem.datasource.local.db.entity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = CryptoCurrenciesAccountEntity::class,
parentColumns = ["id"],
childColumns = ["accountId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = UserWalletEntity::class,
parentColumns = ["id"],
childColumns = ["userWalletId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index(value = ["currencyBackendId"]),
Index(value = ["networkId"]),
Index(value = ["accountId"]),
Index(value = ["userWalletId"]),
],
)
data class CryptoCurrencyEntity(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val currencyBackendId: String?,
val networkId: String,
val accountId: Int,
val userWalletId: String,
val name: String,
val symbol: String,
val decimals: Int,
val contractAddress: String?,
val derivationPath: String?,
)

View file

@ -0,0 +1,22 @@
package com.tangem.datasource.local.db.entity
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
indices = [
Index(value = ["id"], unique = true),
Index(value = ["ordinalNumber"], unique = true),
],
)
data class UserWalletEntity(
@PrimaryKey
val id: String,
val name: String,
val artworkUrl: String,
val isMultiCurrency: Boolean,
val hasBackupError: Boolean,
val cardsInWallet: Set<String>,
val ordinalNumber: Int,
)

View file

@ -0,0 +1,27 @@
package com.tangem.datasource.local.db.utils
import androidx.room.TypeConverter
import com.tangem.domain.wallets.models.UserWalletId
internal class Converters {
@TypeConverter
fun setFromString(value: String): Set<String> {
return value.split(",").toSet()
}
@TypeConverter
fun setToString(set: Set<Any>): String {
return set.joinToString(",")
}
@TypeConverter
fun userWalletIdFromString(value: String): UserWalletId {
return UserWalletId(value)
}
@TypeConverter
fun userWalletIdToString(userWalletId: UserWalletId): String {
return userWalletId.stringValue
}
}

View file

@ -82,6 +82,7 @@ spr-client = "3.6.2"
web3j = "4.10.1"
leakcanary = "2.13"
decompose = "2.2.2"
room = "2.6.1"
# endregion Other libraries
# region Tangem
@ -120,6 +121,7 @@ firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
room = { id = "androidx.room", version.ref = "room" }
[libraries]
# region Classpath
@ -253,4 +255,7 @@ web3j-core = { module = "org.web3j:core", version.ref = "web3j" }
leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-ext-compose = { module = "com.arkivanov.decompose:extensions-compose-jetpack", version.ref = "decompose" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
# endregion Other