Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-22 12:57:00 +00:00
commit 470e62b96c
8 changed files with 105 additions and 82 deletions

View file

@ -4,7 +4,7 @@ import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.core.analytics.models.Basic
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.feedback.ScanFailsEmail
import com.tangem.tap.common.redux.global.GlobalAction
@ -20,7 +20,7 @@ object ScanFailsDialog {
setTitle(context.getString(R.string.common_warning))
setMessage(R.string.alert_troubleshooting_scan_card_title)
setPositiveButton(R.string.alert_button_request_support) { _, _ ->
Analytics.send(IntroductionProcess.ButtonRequestSupport())
Analytics.send(Basic.ButtonSupport())
store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
}
setNeutralButton(R.string.common_cancel) { _, _ -> }

View file

@ -122,34 +122,38 @@ internal class GeneralUserWalletsListManager(
appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
.distinctUntilChanged()
.onEach { shouldSaveUserWallets ->
val manager = if (shouldSaveUserWallets) {
biometricUserWalletsListManager.copyFrom(runtimeUserWalletsListManager)
val possibleManager = if (shouldSaveUserWallets) {
biometricUserWalletsListManager
} else {
runtimeUserWalletsListManager.copyFrom(biometricUserWalletsListManager)
runtimeUserWalletsListManager
}
Timber.d("Switch to ${manager::class.java.simpleName}")
if (possibleManager == implementation.value) {
error("${possibleManager::class.simpleName}} is already selected")
}
implementation.value = manager
Timber.d("Switch to ${possibleManager::class.simpleName}")
clearOldManager(manager)
val previousManager = implementation.value
implementation.value = copySelectedUserWallet(
sourceManager = previousManager,
destinationManager = possibleManager,
)
previousManager.clear()
}
.flowOn(dispatchers.io)
.launchIn(applicationScope)
}
/** Copy data from [old] manager */
private suspend fun UserWalletsListManager.copyFrom(old: UserWalletsListManager): UserWalletsListManager {
old.selectedUserWalletSync?.let { this.save(it) }
return this
}
private suspend fun clearOldManager(current: UserWalletsListManager) {
if (current == biometricUserWalletsListManager) {
runtimeUserWalletsListManager.clear()
} else {
biometricUserWalletsListManager.clear()
private suspend fun copySelectedUserWallet(
sourceManager: UserWalletsListManager,
destinationManager: UserWalletsListManager,
): UserWalletsListManager {
sourceManager.selectedUserWalletSync?.let { selectedWallet ->
destinationManager.save(selectedWallet, canOverride = true)
}
return destinationManager
}
}

View file

@ -87,7 +87,13 @@ object OnboardingHelper {
backupCardsIds = backupCardsIds?.toSet(),
),
)
store.dispatchOnMain(SaveWalletAction.Save)
val toggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (toggles.isGeneralManagerEnabled) {
store.dispatchOnMain(SaveWalletAction.AllowToUseBiometrics)
} else {
store.dispatchOnMain(SaveWalletAction.Save)
}
}
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
// then open save wallet screen

View file

@ -12,6 +12,7 @@ import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.isLockable
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
@ -23,11 +24,16 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
internal class SaveWalletMiddleware {
private val saveWalletJobHolder = JobHolder()
val middleware: Middleware<AppState> = { _, stateProvider ->
{ next ->
{ action ->
@ -99,9 +105,7 @@ internal class SaveWalletMiddleware {
?: return@launch
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (featureToggles.isGeneralManagerEnabled) {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
} else {
if (!featureToggles.isGeneralManagerEnabled) {
provideLockableUserWalletsListManagerIfNot()
}
@ -143,7 +147,7 @@ internal class SaveWalletMiddleware {
},
)
}
}
}.saveIn(saveWalletJobHolder)
}
private fun allowToUseBiometrics(state: SaveWalletState) {
@ -152,10 +156,6 @@ internal class SaveWalletMiddleware {
return
}
val scanResponse = state.backupInfo?.scanResponse
?: store.state.globalState.scanResponse
?: return
if (state.backupInfo != null) {
// TODO: Remove after onboarding refactoring
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On))
@ -164,31 +164,49 @@ internal class SaveWalletMiddleware {
}
scope.launch {
val userWallet = userWalletsListManager.selectedUserWalletSync
?: UserWalletBuilder(scanResponse)
.backupCardsIds(state.backupInfo?.backupCardsIds)
.build()
?: return@launch
val backupInfo = state.backupInfo
val userWalletFromBackup = backupInfo?.scanResponse
?.let(::UserWalletBuilder)
?.backupCardsIds(backupInfo.backupCardsIds)
?.build()
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
if (userWalletFromBackup != null) {
userWalletsListManager.save(userWalletFromBackup, canOverride = true)
.flatMap { saveAccessCodeIfNeeded(backupInfo.accessCode, userWalletFromBackup.cardsInWallet) }
.doOnFailure {
Timber.e(it, "Unable to save user wallet")
saveAccessCodeIfNeeded(accessCode = state.backupInfo?.accessCode, cardsInWallet = userWallet.cardsInWallet)
.flatMap {
userWalletsListManager.save(userWallet, canOverride = true)
}
.doOnFailure { error ->
store.dispatchWithMain(SaveWalletAction.Save.Error(error))
}
.doOnSuccess {
preferencesStorage.shouldSaveAccessCodes = true
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
store.dispatchWithMain(SaveWalletAction.Save.Error(it))
}
.doOnSuccess {
handleSavingSuccess(userWalletFromBackup)
}
} else {
/*
store.dispatchOnMain(SaveWalletAction.Save.Success)
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
* because it will be automatically saved on UserWalletsListManager switch
* */
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
val error = IllegalStateException("No selected user wallet")
Timber.e(error, "Unable to save user wallet")
store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error)))
return@launch
}
}
handleSavingSuccess(selectedUserWallet)
}
}.saveIn(saveWalletJobHolder)
}
private suspend fun handleSavingSuccess(userWallet: UserWallet) {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
preferencesStorage.shouldSaveAccessCodes = true
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
store.dispatchWithMain(SaveWalletAction.Save.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
}
private suspend fun provideLockableUserWalletsListManagerIfNot() {
@ -223,15 +241,15 @@ internal class SaveWalletMiddleware {
cardsInWallet: Set<String>,
): CompletionResult<Unit> {
return when {
accessCode != null -> {
accessCode.isNullOrBlank() -> {
CompletionResult.Success(Unit)
}
else -> {
tangemSdkManager.saveAccessCode(
accessCode = accessCode,
cardsIds = cardsInWallet,
)
}
else -> {
CompletionResult.Success(Unit)
}
}
}
}

View file

@ -34,7 +34,9 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
*/
@Composable
fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) {
Row(
var rootWidth by remember { mutableIntStateOf(value = 0) }
Column(
modifier = modifier
.background(
color = TangemTheme.colors.background.primary,
@ -42,27 +44,13 @@ fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier
)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size72)
.padding(all = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
.padding(all = TangemTheme.dimens.spacing12)
.onSizeChanged { rootWidth = it.width },
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
horizontalAlignment = Alignment.Start,
) {
var rootWidth by remember { mutableIntStateOf(value = 0) }
Column(
modifier = Modifier.onSizeChanged { rootWidth = it.width },
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
horizontalAlignment = Alignment.Start,
) {
Title(currencyName = state.currencySymbol)
Content(state = state, rootWidth = rootWidth)
}
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_chevron_right_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
Title(currencyName = state.currencySymbol)
Content(state = state, rootWidth = rootWidth)
}
}

View file

@ -1,16 +1,18 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
/**
[REDACTED_AUTHOR]
*/
internal class ReinitializeWalletTransformer(
private val userWallet: UserWallet,
private val prevWalletId: UserWalletId,
private val newUserWallet: UserWallet,
private val clickIntents: WalletClickIntents,
) : WalletScreenStateTransformer {
@ -18,9 +20,10 @@ internal class ReinitializeWalletTransformer(
override fun transform(prevState: WalletScreenState): WalletScreenState {
return prevState.copy(
wallets = persistentListOf(
walletLoadingStateFactory.create(userWallet),
),
wallets = prevState.wallets
.filterNot { it.walletCardState.id == prevWalletId }
.plus(element = walletLoadingStateFactory.create(newUserWallet))
.toImmutableList(),
)
}
}

View file

@ -223,7 +223,11 @@ internal class WalletViewModel @Inject constructor(
)
stateHolder.update(
ReinitializeWalletTransformer(userWallet = action.selectedWallet, clickIntents = clickIntents),
ReinitializeWalletTransformer(
prevWalletId = action.prevWalletId,
newUserWallet = action.selectedWallet,
clickIntents = clickIntents,
),
)
}

View file

@ -87,7 +87,7 @@ web3j = "4.10.1"
# region Tangem
tangemBlockchainSdk = "release-app_5.8-536"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.8-336"
tangemCardSdk = "release-app_5.8-338"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
# endregion Tangem