diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index bd50b28faf..58d13616e4 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull // FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented @@ -15,6 +16,9 @@ internal class RuntimeUserWalletsStore( override val selectedUserWalletOrNull: UserWallet? get() = userWalletsListManager.selectedUserWalletSync + override val userWallets: Flow> + get() = userWalletsListManager.userWallets + override suspend fun getSyncOrNull(key: UserWalletId): UserWallet? { return userWalletsListManager .userWallets diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 8d567fc5a5..b127b60b18 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -7,7 +7,6 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.Basic - import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/UserTokensStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/UserTokensStoreModule.kt index 757c01cef5..b2997661fb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/UserTokensStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/UserTokensStoreModule.kt @@ -1,11 +1,11 @@ package com.tangem.datasource.di -import com.squareup.moshi.Moshi -import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.datasource.files.FileReader -import com.tangem.datasource.local.datastore.FileDataStore -import com.tangem.datasource.local.token.DefaultUserTokensStore +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.token.AppPreferencesUserTokensStore import com.tangem.datasource.local.token.UserTokensStore +import com.tangem.datasource.local.token.UserTokensStoreMigrationRunner +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -18,9 +18,17 @@ internal object UserTokensStoreModule { @Provides @Singleton - fun provideUserTokensStore(fileReader: FileReader, @NetworkMoshi moshi: Moshi): UserTokensStore { - return DefaultUserTokensStore( - dataStore = FileDataStore(fileReader, moshi.adapter(UserTokensResponse::class.java)), + fun provideUserTokensStore( + appPreferencesStore: AppPreferencesStore, + userTokensStoreMigrationRunner: UserTokensStoreMigrationRunner, + userWalletsStore: UserWalletsStore, + dispatchers: CoroutineDispatcherProvider, + ): UserTokensStore { + return AppPreferencesUserTokensStore( + appPreferencesStore = appPreferencesStore, + userTokensStoreMigrationRunner = userTokensStoreMigrationRunner, + userWalletsStore = userWalletsStore, + dispatchers = dispatchers, ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 42df77c07d..47ad806d82 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -108,6 +108,8 @@ object PreferencesKeys { fun getPermissionDaysCount(permission: String) = longPreferencesKey("pushPermissionDaysCount_$permission") // endregion + + fun getUserTokensKey(userWalletId: String) = stringPreferencesKey(name = "user_tokens_$userWalletId") } /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore */ diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/AppPreferencesUserTokensStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/AppPreferencesUserTokensStore.kt new file mode 100644 index 0000000000..80d70825bf --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/AppPreferencesUserTokensStore.kt @@ -0,0 +1,63 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObject +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.* + +/** + * Implementation of [UserTokensStore] that based on [appPreferencesStore] + * + * @property appPreferencesStore application preference store + * +[REDACTED_AUTHOR] + */ +internal class AppPreferencesUserTokensStore( + private val appPreferencesStore: AppPreferencesStore, + private val userTokensStoreMigrationRunner: UserTokensStoreMigrationRunner, + private val userWalletsStore: UserWalletsStore, + private val dispatchers: CoroutineDispatcherProvider, +) : UserTokensStore { + + init { + runUserTokensMigrations() + } + + override fun get(key: UserWalletId): Flow { + return appPreferencesStore + .getObject(PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue)) + .filterNotNull() + } + + override suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse? { + return appPreferencesStore.getObjectSyncOrNull( + key = PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue), + ) + } + + override suspend fun store(key: UserWalletId, value: UserTokensResponse) { + appPreferencesStore.storeObject( + key = PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue), + value = value, + ) + } + + // TODO: delete in 5.15 (Mobile Sprint 161) [REDACTED_JIRA] + private fun runUserTokensMigrations() { + userWalletsStore.userWallets + .filter { it.isNotEmpty() } + .take(1) + .onEach { userWallets -> + userTokensStoreMigrationRunner.run(ids = userWallets.map { it.walletId.stringValue }) + } + .flowOn(dispatchers.io) + .launchIn(CoroutineScope(dispatchers.io)) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStore.kt index 08fcb31438..f3f12f026b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStore.kt @@ -4,11 +4,43 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +@Deprecated( + message = "Use AppPreferencesStore", + replaceWith = ReplaceWith( + expression = "AppPreferencesStore", + imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"), + ), + level = DeprecationLevel.WARNING, +) interface UserTokensStore { + @Deprecated( + message = "Use getObject", + replaceWith = ReplaceWith( + expression = "appPreferencesStore.getObject(userWalletId)", + imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"), + ), + level = DeprecationLevel.WARNING, + ) fun get(key: UserWalletId): Flow + @Deprecated( + message = "Use getObjectSyncOrNull", + replaceWith = ReplaceWith( + expression = "appPreferencesStore.getObjectSyncOrNull(userWalletId)", + imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"), + ), + level = DeprecationLevel.WARNING, + ) suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse? + @Deprecated( + message = "Use storeObject", + replaceWith = ReplaceWith( + expression = "appPreferencesStore.storeObject(userWalletId, response)", + imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"), + ), + level = DeprecationLevel.WARNING, + ) suspend fun store(key: UserWalletId, value: UserTokensResponse) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStoreMigration.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStoreMigration.kt new file mode 100644 index 0000000000..dae09c0616 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStoreMigration.kt @@ -0,0 +1,56 @@ +package com.tangem.datasource.local.token + +import androidx.datastore.core.DataMigration +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.files.FileReader +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject + +/** + * Migration of saving [UserTokensResponse] from file to [AppPreferencesStore] + * + * @param userWalletId user wallet id + * @param moshi moshi + * @property fileReader file reader + * +[REDACTED_AUTHOR] + */ +internal class UserTokensStoreMigration( + userWalletId: String, + moshi: Moshi, + private val fileReader: FileReader, +) : DataMigration { + + private val legacyFileName = "user_tokens_$userWalletId" + private val keyName = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId) + + @OptIn(ExperimentalStdlibApi::class) + private val adapter = moshi.adapter() + + override suspend fun shouldMigrate(currentData: AppPreferencesStore): Boolean = true + + override suspend fun migrate(currentData: AppPreferencesStore): AppPreferencesStore { + val currentKey = currentData.getObjectSyncOrNull(key = keyName) + + if (currentKey != null) return currentData + + val value = runCatching { + val json = fileReader.readFile(legacyFileName) + adapter.fromJson(json) + }.getOrNull() + + if (value != null) { + currentData.storeObject(key = keyName, value = value) + } + + return currentData + } + + override suspend fun cleanUp() { + fileReader.removeFile(legacyFileName) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStoreMigrationRunner.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStoreMigrationRunner.kt new file mode 100644 index 0000000000..eafb84aa79 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/UserTokensStoreMigrationRunner.kt @@ -0,0 +1,50 @@ +package com.tangem.datasource.local.token + +import com.squareup.moshi.Moshi +import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.files.FileReader +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Runner that launch migrations of saving user tokens store + * + * @property appPreferencesStore application preference store + * @property fileReader file reader + * @property moshi moshi + * @property dispatchers dispatchers + * +[REDACTED_AUTHOR] + */ +@Singleton +class UserTokensStoreMigrationRunner @Inject constructor( + private val appPreferencesStore: AppPreferencesStore, + private val fileReader: FileReader, + @NetworkMoshi private val moshi: Moshi, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend fun run(ids: List) { + ids.forEach { id -> + coroutineScope { run(id) } + } + } + + private suspend fun run(id: String) { + withContext(dispatchers.io) { + val migration = UserTokensStoreMigration( + userWalletId = id, + moshi = moshi, + fileReader = fileReader, + ) + + migration.migrate(appPreferencesStore) + + migration.cleanUp() + } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt index bfee20e460..6aa2933ba2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt @@ -2,11 +2,14 @@ package com.tangem.datasource.local.userwallet import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow interface UserWalletsStore { val selectedUserWalletOrNull: UserWallet? + val userWallets: Flow> + suspend fun getSyncOrNull(key: UserWalletId): UserWallet? suspend fun getAllSyncOrNull(): List?