Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-14 18:49:07 +05:00
parent 361b6c05b6
commit 4c25513253
9 changed files with 136 additions and 3 deletions

View file

@ -0,0 +1,23 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.datasource.local.accounts.DefaultAccountTokenMigrationStore
import com.tangem.datasource.local.datastore.RuntimeStateStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object AccountsMigrationStoreModule {
@Provides
@Singleton
fun provideAccountTokenMigrationStore(): AccountTokenMigrationStore {
return DefaultAccountTokenMigrationStore(
runtimeStateStore = RuntimeStateStore(emptyMap()),
)
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.local.accounts
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
interface AccountTokenMigrationStore {
fun get(userWalletId: UserWalletId): Flow<Pair<String, String>?>
suspend fun store(userWalletId: UserWalletId, value: Pair<String, String>)
suspend fun remove(userWalletId: UserWalletId)
}

View file

@ -0,0 +1,23 @@
package com.tangem.datasource.local.accounts
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class DefaultAccountTokenMigrationStore(
private val runtimeStateStore: RuntimeStateStore<Map<UserWalletId, Pair<String, String>>>,
) : AccountTokenMigrationStore {
override fun get(userWalletId: UserWalletId): Flow<Pair<String, String>?> {
return runtimeStateStore.get().map { it[userWalletId] }
}
override suspend fun store(userWalletId: UserWalletId, value: Pair<String, String>) {
runtimeStateStore.update { it + (userWalletId to value) }
}
override suspend fun remove(userWalletId: UserWalletId) {
runtimeStateStore.update { it - userWalletId }
}
}

View file

@ -269,7 +269,7 @@
<string name="common_free">無料</string>
<string name="common_from">送信元</string>
<string name="common_generate_addresses">アドレスを同期する</string>
<string name="common_get_started">もっと詳しく</string>
<string name="common_get_started">はじめる</string>
<string name="common_get_token">トークンを取得</string>
<string name="common_go_to_provider">プロバイダーへ移動</string>
<string name="common_go_to_token">トークンへ移動</string>

View file

@ -13,6 +13,7 @@ import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
@ -75,9 +76,11 @@ internal object AccountDataModule {
fun provideMainAccountTokensMigration(
accountsResponseStoreFactory: AccountsResponseStoreFactory,
userTokensSaver: UserTokensSaver,
accountTokenMigrationStore: AccountTokenMigrationStore,
): MainAccountTokensMigration {
return DefaultMainAccountTokensMigration(
accountsResponseStoreFactory = accountsResponseStoreFactory,
accountTokenMigrationStore = accountTokenMigrationStore,
userTokensSaver = userTokensSaver,
)
}

View file

@ -14,6 +14,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.datasource.utils.getSyncOrNull
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.models.account.DerivationIndex
@ -32,6 +33,7 @@ import timber.log.Timber
*/
internal class DefaultMainAccountTokensMigration(
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
private val accountTokenMigrationStore: AccountTokenMigrationStore,
private val userTokensSaver: UserTokensSaver,
) : MainAccountTokensMigration {
@ -80,6 +82,12 @@ internal class DefaultMainAccountTokensMigration(
store.updateData { updatedResponse }
val mainAccountName = mainAccount.name
val selectedAccountName = selectedAccount.name
if (mainAccountName != null && selectedAccountName != null) {
accountTokenMigrationStore.store(userWalletId, mainAccountName to selectedAccountName)
}
val userTokensResponse = updatedResponse.toUserTokensResponse()
userTokensSaver.pushWithRetryer(
userWalletId = userWalletId,

View file

@ -11,6 +11,7 @@ import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId
@ -29,11 +30,12 @@ class DefaultMainAccountTokensMigrationTest {
private val accountsResponseStoreFactory = mockk<AccountsResponseStoreFactory>()
private val accountsResponseStore = mockk<AccountsResponseStore>()
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
private val accountTokenMigrationStore = mockk<AccountTokenMigrationStore>(relaxUnitFun = true)
private val userTokensSaver = mockk<UserTokensSaver>(relaxUnitFun = true)
private val migration = DefaultMainAccountTokensMigration(
accountsResponseStoreFactory = accountsResponseStoreFactory,
accountTokenMigrationStore = accountTokenMigrationStore,
userTokensSaver = userTokensSaver,
)

View file

@ -29,6 +29,7 @@ dependencies {
implementation(projects.core.navigation)
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.datasource)
implementation(projects.common.routing)
implementation(projects.common.ui)

View file

@ -9,33 +9,52 @@ import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableDialogComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.utils.requestPermission
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.feature.walletsettings.component.NetworksAvailableForNotificationsComponent
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.entity.DialogConfig
import com.tangem.feature.walletsettings.entity.NetworksAvailableForNotificationBSConfig
import com.tangem.feature.walletsettings.impl.R
import com.tangem.feature.walletsettings.model.WalletSettingsModel
import com.tangem.feature.walletsettings.ui.WalletSettingsScreen
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
internal class DefaultWalletSettingsComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted params: WalletSettingsComponent.Params,
@Assisted private val params: WalletSettingsComponent.Params,
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
private val networksAvailableForNotificationsComponent: NetworksAvailableForNotificationsComponent.Factory,
private val accountTokenMigrationStore: AccountTokenMigrationStore,
private val accountsFeatureToggles: AccountsFeatureToggles,
) : WalletSettingsComponent, AppComponentContext by context {
private val model: WalletSettingsModel = getOrCreateModel(params)
private val accountMigrationJobHolder = JobHolder()
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = null,
@ -52,6 +71,17 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
childFactory = ::dialogChild,
)
init {
lifecycle.subscribe(
onResume = {
if (accountsFeatureToggles.isFeatureEnabled) {
showMigrationAlertIfNeeded()
}
},
onPause = { accountMigrationJobHolder.cancel() },
)
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
@ -106,6 +136,36 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
),
)
private fun showMigrationAlertIfNeeded() {
accountTokenMigrationStore.get(params.userWalletId)
.distinctUntilChanged()
.filterNotNull()
.onEach { (fromAccount, toAccount) ->
messageSender.send(
DialogMessage(
title = resourceReference(R.string.accounts_migration_alert_title),
message = resourceReference(
R.string.accounts_migration_alert_message,
wrappedList(fromAccount, toAccount),
),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_got_it),
onClick = { /* no-op */ },
)
},
onDismissRequest = {
componentScope.launch {
accountTokenMigrationStore.remove(params.userWalletId)
}
},
),
)
}
.launchIn(componentScope)
.saveIn(accountMigrationJobHolder)
}
@AssistedFactory
interface Factory : WalletSettingsComponent.Factory {
override fun create(