diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index ba7a0ac095..e7fba06fb7 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -16,7 +16,6 @@ import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.features.details.redux.SecurityOption import org.rekotlin.Action sealed class GlobalAction : Action { @@ -75,7 +74,6 @@ sealed class GlobalAction : Action { ) : GlobalAction() data class HideWarningMessage(val warning: WarningMessage) : GlobalAction() - data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction() data class SetConfigManager(val configManager: ConfigManager) : GlobalAction() data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 7dc282cacd..c8576de418 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -4,7 +4,6 @@ import com.tangem.domain.demo.DemoConfig import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store @@ -21,7 +20,6 @@ object DemoHelper { private val disabledActionFeatures = listOf( WalletConnectAction.StartWalletConnect::class.java, BackupAction.StartBackup::class.java, - DetailsAction.ResetToFactory.Start::class.java, ) fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index b48655b7c7..10bb1b383d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -14,26 +14,6 @@ sealed class DetailsAction : Action { val shouldSaveUserWallets: Boolean, ) : DetailsAction() - data object ReCreateTwinsWallet : DetailsAction() - - sealed class ResetToFactory : DetailsAction() { - data object Start : ResetToFactory() - data object Proceed : ResetToFactory() - data class AcceptCondition1(val accepted: Boolean) : ResetToFactory() - data class AcceptCondition2(val accepted: Boolean) : ResetToFactory() - data object Failure : ResetToFactory() - data object Success : ResetToFactory() - - data class ShowDialog(val dialog: CardSettingsState.Dialog) : ResetToFactory() - - data object DismissDialog : ResetToFactory() - } - - data object ScanCard : DetailsAction() - - data class PrepareCardSettingsData(val scanResponse: ScanResponse) : DetailsAction() - - data object ResetCardSettingsData : DetailsAction() data object ScanAndSaveUserWallet : DetailsAction() { data object Success : DetailsAction() @@ -43,26 +23,6 @@ sealed class DetailsAction : Action { data object DismissError : DetailsAction() - sealed class AccessCodeRecovery : DetailsAction() { - object Open : AccessCodeRecovery() - data class SaveChanges(val enabled: Boolean) : AccessCodeRecovery() { - data class Success(val enabled: Boolean) : AccessCodeRecovery() - } - - data class SelectOption(val enabled: Boolean) : AccessCodeRecovery() - } - - sealed class ManageSecurity : DetailsAction() { - data object OpenSecurity : ManageSecurity() - data class SelectOption(val option: SecurityOption) : ManageSecurity() - data object SaveChanges : ManageSecurity() { - data object Success : ManageSecurity() - data object Failure : ManageSecurity() - } - - data object ChangeAccessCode : ManageSecurity() - } - sealed class AppSettings : DetailsAction() { data class SwitchPrivacySetting( val enable: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index c9dc2ceead..689263efaf 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -1,40 +1,36 @@ package com.tangem.tap.features.details.redux import androidx.lifecycle.LifecycleCoroutineScope -import com.tangem.common.* +import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError -import com.tangem.common.core.UserCodeRequestPolicy +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.Analytics - import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.apptheme.model.AppThemeMode -import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletBuilder -import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.legacy.UserWalletsListError -import com.tangem.domain.wallets.legacy.asLockable 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.Settings -import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.common.extensions.dispatchWithMain +import com.tangem.tap.common.extensions.inject +import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.demo.DemoHelper -import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn -import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged @@ -42,17 +38,13 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam class DetailsMiddleware { - private val eraseWalletMiddleware = EraseWalletMiddleware() - private val manageSecurityMiddleware = ManageSecurityMiddleware() private val appSettingsMiddleware = AppSettingsMiddleware() - private val accessCodeRecoveryMiddleware = AccessCodeRecoveryMiddleware() val detailsMiddleware: Middleware = { _, stateProvider -> { next -> { action -> @@ -69,153 +61,11 @@ class DetailsMiddleware { private fun handleAction(state: DetailsState, action: Action) { when (action) { - is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action) - is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action, state) is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action) - is DetailsAction.ReCreateTwinsWallet -> { - store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) - - store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) } - } - is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action) - is DetailsAction.ScanCard -> scanCard(state) is DetailsAction.ScanAndSaveUserWallet -> scanAndSaveUserWallet() } } - class EraseWalletMiddleware { - @Suppress("CyclomaticComplexMethod") - fun handle(action: DetailsAction.ResetToFactory) { - val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - - when (action) { - is DetailsAction.ResetToFactory.Start -> { - val card = store.state.detailsState.cardSettingsState?.card ?: return - if (card.isTangemTwins) { - store.dispatch(DetailsAction.ReCreateTwinsWallet) - return - } else { - store.dispatchNavigationAction { push(AppRoute.ResetToFactory) } - } - } - is DetailsAction.ResetToFactory.Proceed -> { - val card = store.state.detailsState.cardSettingsState?.card ?: return - scope.launch { - val userWalletId = UserWalletIdBuilder.card(card).build() - - // we must require a password regardless of biometric settings - val policy = tangemSdkManager.userCodeRequestPolicy - val doBeforeErase = { - val type = if (card.isAccessCodeSet) { - UserCodeType.AccessCode - } else if (card.isPasscodeSet == true) { - UserCodeType.Passcode - } else { - null - } - - type?.let { - tangemSdkManager.setUserCodeRequestPolicy(UserCodeRequestPolicy.Always(type)) - } - } - - val doAfterErase = { - tangemSdkManager.setUserCodeRequestPolicy(policy) - } - - doBeforeErase() - tangemSdkManager.resetToFactorySettings(card.cardId, true) - .flatMap { - userWalletsListManager.delete(listOfNotNull(userWalletId)) - } - .flatMap { - tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) - } - .doOnSuccess { - Analytics.send(Settings.CardSettings.FactoryResetFinished()) - - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - store.dispatchNavigationAction { popTo() } - store.onUserWalletSelected(selectedUserWallet) - } else { - val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } - .fold(onSuccess = { true }, onFailure = { false }) - if (isLocked && userWalletsListManager.hasUserWallets) { - store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } - } else { - store.dispatchNavigationAction { replaceAll(AppRoute.Home) } - } - } - } - .doOnFailure { error -> - if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) { - Analytics.send(Settings.CardSettings.FactoryResetFinished(error = error)) - } - } - .doOnResult { - doAfterErase() - } - } - } - else -> Unit - } - } - } - - class ManageSecurityMiddleware { - @Suppress("ComplexMethod") - fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) { - when (action) { - is DetailsAction.ManageSecurity.OpenSecurity -> { - store.dispatchNavigationAction { push(AppRoute.DetailsSecurity) } - } - is DetailsAction.ManageSecurity.SaveChanges -> { - val cardSettingsState = detailsState.cardSettingsState - val cardId = cardSettingsState?.card?.cardId - val selectedOption = cardSettingsState?.manageSecurityState?.selectedOption - scope.launch { - val result = when (selectedOption) { - SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) - SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) - SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) - else -> return@launch - } - withContext(Dispatchers.Main) { - val paramValue = AnalyticsParam.SecurityMode.from(selectedOption) - when (result) { - is CompletionResult.Success -> { - Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue)) - store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption)) - store.dispatchNavigationAction(AppRouter::pop) - store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) - } - is CompletionResult.Failure -> { - val error = result.error - if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) { - Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error)) - } - store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) - } - else -> Unit - } - } - } - } - is DetailsAction.ManageSecurity.ChangeAccessCode -> { - val card = store.state.detailsState.cardSettingsState?.card ?: return - scope.launch { - when (tangemSdkManager.setAccessCode(card.cardId)) { - is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged()) - is CompletionResult.Failure -> {} - } - } - } - else -> Unit - } - } - } - class AppSettingsMiddleware { private val checkBiometricsStatusJobHolder = JobHolder() @@ -431,63 +281,6 @@ class DetailsMiddleware { } } - class AccessCodeRecoveryMiddleware { - fun handle(state: DetailsState, action: DetailsAction.AccessCodeRecovery) { - when (action) { - is DetailsAction.AccessCodeRecovery.Open -> { - Analytics.send(Settings.CardSettings.AccessCodeRecoveryButton()) - store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) } - } - is DetailsAction.AccessCodeRecovery.SaveChanges -> { - scope.launch { - tangemSdkManager - .setAccessCodeRecoveryEnabled(state.cardSettingsState?.card?.cardId, action.enabled) - .doOnSuccess { - Analytics.send( - Settings.CardSettings.AccessCodeRecoveryChanged( - AnalyticsParam.AccessCodeRecoveryStatus.from(action.enabled), - ), - ) - store.dispatchNavigationAction(AppRouter::pop) - store.dispatchOnMain( - DetailsAction.AccessCodeRecovery.SaveChanges.Success(action.enabled), - ) - } - } - } - is DetailsAction.AccessCodeRecovery.SelectOption -> Unit - is DetailsAction.AccessCodeRecovery.SaveChanges.Success -> Unit - } - } - } - - private fun scanCard(state: DetailsState) = scope.launch { - store.inject(DaggerGraphState::scanCardProcessor) - .scan(allowsRequestAccessCodeFromRepository = true) - .doOnSuccess { scanResponse -> - // if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards - store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) - val currentUserWalletId = state.scanResponse - ?.let { UserWalletIdBuilder.scanResponse(it).build() } - val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse) - .build() - val isSameWallet = currentUserWalletId == scannedUserWalletId - - if (isSameWallet) { - store.dispatchOnMain( - DetailsAction.PrepareCardSettingsData(scanResponse = scanResponse), - ) - } else { - store.dispatchDialogShow( - AppDialog.SimpleOkDialogRes( - headerId = R.string.common_warning, - messageId = R.string.error_wrong_wallet_tapped, - ), - ) - } - } - } - private fun scanAndSaveUserWallet() = scope.launch(Dispatchers.IO) { val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 7a87450f97..dbb450b426 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -1,21 +1,15 @@ package com.tangem.tap.features.details.redux -import com.tangem.common.routing.AppRoute import com.tangem.domain.apptheme.model.AppThemeMode -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.runBlocking import org.rekotlin.Action -import java.util.EnumSet object DetailsReducer { fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state) @@ -29,16 +23,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { is DetailsAction.PrepareScreen -> { handlePrepareScreen(action, state) } - is DetailsAction.PrepareCardSettingsData -> { - handlePrepareCardSettingsScreen(scanResponse = action.scanResponse, state = detailsState) - } - is DetailsAction.ResetCardSettingsData -> detailsState.copy(cardSettingsState = null) - is DetailsAction.ResetToFactory -> { - handleEraseWallet(action, detailsState) - } - is DetailsAction.ManageSecurity -> { - handleSecurityAction(action, detailsState) - } is DetailsAction.AppSettings -> { handlePrivacyAction(action, detailsState) } @@ -47,7 +31,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { selectedAppCurrency = action.currency, ), ) - is DetailsAction.AccessCodeRecovery -> handleAccessCodeRecoveryAction(action, detailsState) is DetailsAction.ScanAndSaveUserWallet -> detailsState.copy( isScanningInProgress = true, ) @@ -61,21 +44,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { is DetailsAction.DismissError -> detailsState.copy( error = null, ) - else -> detailsState } } private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState { - val router = store.inject(DaggerGraphState::appRouter) - return DetailsState( scanResponse = action.scanResponse, - // If the current screen is ResetToFactory, we must save the ResetToFactory's state - cardSettingsState = if (router.stack.lastOrNull() is AppRoute.ResetToFactory) { - state.detailsState.cardSettingsState - } else { - null - }, createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appSettingsState = AppSettingsState( isBiometricsAvailable = runBlocking { @@ -98,139 +72,6 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS ) } -private fun handlePrepareCardSettingsScreen(scanResponse: ScanResponse, state: DetailsState): DetailsState { - val cardTypesResolver = scanResponse.cardTypesResolver - val card = scanResponse.card - val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2() - val isShowPasswordResetRadioButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active - - val cardSettingsState = CardSettingsState( - cardInfo = card.toCardInfo(cardTypesResolver), - scanResponse = scanResponse, - manageSecurityState = prepareSecurityOptions(card, cardTypesResolver), - card = scanResponse.card, - resetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver), - resetButtonEnabled = false, - condition1Checked = false, - condition2Checked = false, - accessCodeRecovery = if (cardTypesResolver.isWallet2()) { - val enabled = card.userSettings?.isUserCodeRecoveryAllowed ?: false - AccessCodeRecoveryState( - enabledOnCard = enabled, - enabledSelection = enabled, - ) - } else { - null - }, - isShowPasswordResetRadioButton = isShowPasswordResetRadioButton, - dialog = null, - ) - - return state.copy(cardSettingsState = cardSettingsState) -} - -private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesResolver): ManageSecurityState { - val securityOption = when { - card.isAccessCodeSet -> { - SecurityOption.AccessCode - } - - card.isPasscodeSet == true -> { - SecurityOption.PassCode - } - - else -> { - SecurityOption.LongTap - } - } - val allowedSecurityOptions = when { - cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() -> { - EnumSet.of(SecurityOption.LongTap) - } - card.settings.isBackupAllowed -> { - EnumSet.of(securityOption) - } - else -> - prepareAllowedSecurityOptions(cardTypesResolver = cardTypesResolver, currentSecurityOption = securityOption) - } - return ManageSecurityState( - currentOption = securityOption, - allowedOptions = allowedSecurityOptions, - selectedOption = securityOption, - ) -} - -private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean { - val hasPermanentWallet = card.wallets.any { it.settings.isPermanent } - val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin() - return !isNotAllowed -} - -private fun handleEraseWallet(action: DetailsAction.ResetToFactory, state: DetailsState): DetailsState { - val cardSettingsState = state.cardSettingsState - return when (action) { - is DetailsAction.ResetToFactory.AcceptCondition1 -> { - val warning1Checked = action.accepted - val resetButtonEnabled = if (cardSettingsState?.isShowPasswordResetRadioButton == true) { - warning1Checked && cardSettingsState.condition2Checked - } else { - warning1Checked - } - state.copy( - cardSettingsState = cardSettingsState?.copy( - condition1Checked = action.accepted, - resetButtonEnabled = resetButtonEnabled, - ), - ) - } - is DetailsAction.ResetToFactory.AcceptCondition2 -> { - val warning2Checked = action.accepted - state.copy( - cardSettingsState = cardSettingsState?.copy( - condition2Checked = action.accepted, - resetButtonEnabled = warning2Checked && cardSettingsState.condition1Checked, - ), - ) - } - is DetailsAction.ResetToFactory.ShowDialog -> { - state.copy(cardSettingsState = cardSettingsState?.copy(dialog = action.dialog)) - } - is DetailsAction.ResetToFactory.DismissDialog -> { - state.copy(cardSettingsState = cardSettingsState?.copy(dialog = null)) - } - else -> state - } -} - -private fun handleSecurityAction(action: DetailsAction.ManageSecurity, state: DetailsState): DetailsState { - return when (action) { - is DetailsAction.ManageSecurity.SelectOption -> { - val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy( - selectedOption = action.option, - ) - state.copy(cardSettingsState = state.cardSettingsState?.copy(manageSecurityState = manageSecurityState)) - } - is DetailsAction.ManageSecurity.SaveChanges.Success -> { - // Setting options to show only LongTap from now on for non-twins - val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy( - currentOption = state.cardSettingsState.manageSecurityState.selectedOption, - allowedOptions = state.scanResponse?.cardTypesResolver?.let { - prepareAllowedSecurityOptions( - cardTypesResolver = it, - currentSecurityOption = state.cardSettingsState.manageSecurityState.selectedOption, - ) - } ?: EnumSet.of(SecurityOption.LongTap), - ) - state.copy( - cardSettingsState = state.cardSettingsState?.copy( - manageSecurityState = manageSecurityState, - ), - ) - } - else -> state - } -} - private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: DetailsState): DetailsState { return when (action) { is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy( @@ -290,65 +131,4 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail is DetailsAction.AppSettings.CheckBiometricsStatus, -> state } -} - -private fun handleAccessCodeRecoveryAction( - action: DetailsAction.AccessCodeRecovery, - state: DetailsState, -): DetailsState { - return when (action) { - DetailsAction.AccessCodeRecovery.Open -> { - val accessCodeRecovery = state.cardSettingsState?.accessCodeRecovery?.copy( - enabledSelection = state.cardSettingsState.accessCodeRecovery.enabledOnCard, - ) - state.copy(cardSettingsState = state.cardSettingsState?.copy(accessCodeRecovery = accessCodeRecovery)) - } - is DetailsAction.AccessCodeRecovery.SaveChanges -> state - is DetailsAction.AccessCodeRecovery.SelectOption -> { - val accessCodeRecovery = state.cardSettingsState?.accessCodeRecovery?.copy( - enabledSelection = action.enabled, - ) - state.copy(cardSettingsState = state.cardSettingsState?.copy(accessCodeRecovery = accessCodeRecovery)) - } - is DetailsAction.AccessCodeRecovery.SaveChanges.Success -> { - val accessCodeRecovery = state.cardSettingsState?.accessCodeRecovery?.copy( - enabledOnCard = action.enabled, - enabledSelection = action.enabled, - ) - state.copy(cardSettingsState = state.cardSettingsState?.copy(accessCodeRecovery = accessCodeRecovery)) - } - } -} - -private fun prepareAllowedSecurityOptions( - cardTypesResolver: CardTypesResolver, - currentSecurityOption: SecurityOption?, -): EnumSet { - val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap) - - if (cardTypesResolver.isTangemTwins()) { - allowedSecurityOptions.add(SecurityOption.PassCode) - } - if (currentSecurityOption == SecurityOption.AccessCode) { - allowedSecurityOptions.add(SecurityOption.AccessCode) - } - if (currentSecurityOption == SecurityOption.PassCode) { - allowedSecurityOptions.add(SecurityOption.PassCode) - } - return allowedSecurityOptions -} - -@Suppress("MagicNumber") -private fun CardDTO.toCardInfo(cardTypesResolver: CardTypesResolver): CardInfo { - val cardId = this.cardId.chunked(4).joinToString(separator = " ") - val issuer = this.issuer.name - val signedHashes = this.signedHashesCount() - - return CardInfo( - cardId = cardId, - issuer = issuer, - signedHashes = signedHashes, - isTwin = cardTypesResolver.isTangemTwins(), - hasBackup = backupStatus?.isActive == true, - ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 8e7e583ecf..c35d0c8d83 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -3,15 +3,11 @@ package com.tangem.tap.features.details.redux import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.entities.Button import org.rekotlin.StateType -import java.util.EnumSet data class DetailsState( val scanResponse: ScanResponse? = null, - val cardSettingsState: CardSettingsState? = null, val privacyPolicyUrl: String? = null, val createBackupAllowed: Boolean = false, val isScanningInProgress: Boolean = false, @@ -19,52 +15,13 @@ data class DetailsState( val appSettingsState: AppSettingsState = AppSettingsState(), ) : StateType -data class CardInfo( - val cardId: String, - val issuer: String, - val signedHashes: Int, - val isTwin: Boolean, - val hasBackup: Boolean, -) - -/** - * @property enabledOnCard whether access code recovery is enabled on card - * @property enabledSelection current selected option in app (not saved on card yet) - */ -data class AccessCodeRecoveryState( - val enabledOnCard: Boolean, - val enabledSelection: Boolean, -) - -data class CardSettingsState( - val cardInfo: CardInfo, - val card: CardDTO, - val scanResponse: ScanResponse, - val manageSecurityState: ManageSecurityState?, - val resetCardAllowed: Boolean, - val resetButtonEnabled: Boolean, - val condition1Checked: Boolean, - val condition2Checked: Boolean, - val accessCodeRecovery: AccessCodeRecoveryState? = null, - val isShowPasswordResetRadioButton: Boolean, - val dialog: Dialog?, -) { - - sealed interface Dialog { - data object StartResetDialog : Dialog - data object ContinueResetDialog : Dialog - data object InterruptedResetDialog : Dialog - data object CompletedResetDialog : Dialog - } +sealed class ResetCardDialog { + data object StartResetDialog : ResetCardDialog() + data object ContinueResetDialog : ResetCardDialog() + data object InterruptedResetDialog : ResetCardDialog() + data object CompletedResetDialog : ResetCardDialog() } -data class ManageSecurityState( - val currentOption: SecurityOption = SecurityOption.LongTap, - val selectedOption: SecurityOption = currentOption, - val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java), - val buttonProceed: Button = Button(true), -) - data class AppSettingsState( val saveWallets: Boolean = false, val saveAccessCodes: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 2d6e9d554c..9036d7d1fd 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -7,12 +7,10 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter - import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -37,7 +35,6 @@ internal class AppSettingsFragment : ComposeFragment() { modifier = modifier, state = state, onBackClick = { - store.dispatch(DetailsAction.ResetCardSettingsData) store.dispatchNavigationAction(AppRouter::pop) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt index 6fc835cc9c..6054581056 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -1,15 +1,14 @@ package com.tangem.tap.features.details.ui.cardsettings import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter - import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -24,13 +23,12 @@ internal class CardSettingsFragment : ComposeFragment() { @Composable override fun ScreenContent(modifier: Modifier) { - LocalLifecycleOwner.current.lifecycle.addObserver(viewModel) + val state by viewModel.screenState.collectAsStateWithLifecycle() CardSettingsScreen( modifier = modifier, - state = viewModel.screenState.value, + state = state, onBackClick = { - store.dispatch(DetailsAction.ResetCardSettingsData) store.dispatchNavigationAction(AppRouter::pop) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index 4cf5e68186..46425706b4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -4,16 +4,12 @@ import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.stringResource -import com.tangem.tap.features.details.redux.AccessCodeRecoveryState import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.ui.securitymode.toTitleRes -import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText import com.tangem.wallet.R -import com.tangem.tap.features.details.redux.CardInfo as ReduxCardInfo internal data class CardSettingsScreenState( val cardDetails: List? = null, - val accessCodeRecoveryState: AccessCodeRecoveryState? = null, val onScanCardClick: () -> Unit, val onElementClick: (CardInfo) -> Unit, ) @@ -44,7 +40,7 @@ internal sealed class CardInfo( clickable = clickable, ) - object ChangeAccessCode : CardInfo( + data object ChangeAccessCode : CardInfo( titleRes = TextReference.Res(R.string.card_settings_change_access_code), subtitle = TextReference.Res(R.string.card_settings_change_access_code_footer), clickable = true, @@ -60,9 +56,9 @@ internal sealed class CardInfo( clickable = true, ) - class ResetToFactorySettings(cardInfo: ReduxCardInfo) : CardInfo( + class ResetToFactorySettings(description: TextReference) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), - subtitle = cardInfo.toResetCardDescriptionText(), + subtitle = description, clickable = true, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 64c00b50f4..94be2ca2fd 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -1,105 +1,124 @@ package com.tangem.tap.features.details.ui.cardsettings -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner +import android.os.Bundle +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import arrow.core.Either +import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse +import com.tangem.common.CompletionResult +import com.tangem.common.doOnSuccess +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.bundle.unbundle import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.common.getTwinCardIdForUser -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.card.ScanCardProcessor +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.wallets.builder.UserWalletIdBuilder +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings -import com.tangem.tap.features.details.redux.CardSettingsState -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.DetailsState +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.domain.extensions.signedHashesCount +import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.tap.features.details.ui.common.utils.* import com.tangem.tap.store +import com.tangem.wallet.R import dagger.hilt.android.lifecycle.HiltViewModel -import org.rekotlin.StoreSubscriber +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @HiltViewModel internal class CardSettingsViewModel @Inject constructor( - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, -) : - ViewModel(), DefaultLifecycleObserver, StoreSubscriber { + private val scanCardProcessor: ScanCardProcessor, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val tangemSdkManager: TangemSdkManager, + savedStateHandle: SavedStateHandle, +) : ViewModel() { - var screenState: MutableState = - mutableStateOf(updateState(store.state.detailsState.cardSettingsState)) + private val userWalletId = savedStateHandle.get(AppRoute.CardSettings.USER_WALLET_ID_KEY) + ?.unbundle(UserWalletId.serializer()) + ?: error("User wallet ID is required for CardSettingsViewModel") - override fun onStart(owner: LifecycleOwner) { - when (val selectedWalletEither = getSelectedWalletSyncUseCase()) { - is Either.Left -> { - Timber.e(selectedWalletEither.value.toString()) + val screenState: MutableStateFlow = MutableStateFlow(getInitialState()) + + private fun getInitialState() = CardSettingsScreenState( + cardDetails = null, + onElementClick = ::handleClickingItem, + onScanCardClick = ::scanCard, + ) + + private fun scanCard() = viewModelScope.launch { + scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) + .doOnSuccess { scanResponse -> + val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + val isCorrectUserWalletScanned = scannedUserWalletId == userWalletId + + if (isCorrectUserWalletScanned) { + val userWallet = getUserWallet() + + updateCardDetails(userWallet) + } else { + store.dispatchDialogShow( + AppDialog.SimpleOkDialogRes( + headerId = R.string.common_warning, + messageId = R.string.error_wrong_wallet_tapped, + ), + ) + } + } + } + + private fun updateCardDetails(userWallet: UserWallet) { + val card = userWallet.scanResponse.card + val cardTypesResolver = userWallet.scanResponse.cardTypesResolver + val cardId = cardTypesResolver.getCardId() + + val currentSecurityOption = getCurrentSecurityOption(card) + val allowedSecurityOptions = getAllowedSecurityOptions( + card = card, + cardTypesResolver = cardTypesResolver, + currentSecurityOption = currentSecurityOption, + ) + val isResetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver) + + val cardDetails = buildList { + CardInfo.CardId(cardId).let(::add) + CardInfo.Issuer(card.issuer.name).let(::add) + + if (!cardTypesResolver.isTangemTwins()) { + CardInfo.SignedHashes(card.signedHashesCount().toString()).let(::add) + } + + CardInfo.SecurityMode( + currentSecurityOption, + clickable = allowedSecurityOptions.size > 1, + ).let(::add) + + if (card.backupStatus?.isActive == true && card.isAccessCodeSet) { + CardInfo.ChangeAccessCode.let(::add) + } + + if (isAccessCodeRecoveryAllowed(cardTypesResolver)) { + CardInfo.AccessCodeRecovery(isAccessCodeRecoveryEnabled(cardTypesResolver, card)).let(::add) + } + + if (isResetCardAllowed) { + CardInfo.ResetToFactorySettings( + description = getResetToFactoryDescription(card, cardTypesResolver), + ).let(::add) } - is Either.Right -> Unit } - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.detailsState == newState.detailsState - }.select { it.detailsState } - } - } - - override fun onStop(owner: LifecycleOwner) { - store.unsubscribe(this) - } - - override fun newState(state: DetailsState) { - screenState.value = updateState(state.cardSettingsState) - } - - private fun updateState(state: CardSettingsState?): CardSettingsScreenState { - return if (state?.manageSecurityState == null) { - CardSettingsScreenState( - cardDetails = null, - accessCodeRecoveryState = null, - onElementClick = {}, - onScanCardClick = { - store.dispatch(DetailsAction.ScanCard) - }, - ) - } else { - val cardId = if (state.card.isTangemTwins) { - state.card.getTwinCardIdForUser() - } else { - state.cardInfo.cardId - } - val cardDetails: MutableList = mutableListOf( - CardInfo.CardId(cardId), - CardInfo.Issuer(state.cardInfo.issuer), - ) - - if (!state.card.isTangemTwins) { - cardDetails.add(CardInfo.SignedHashes(state.cardInfo.signedHashes.toString())) - } - cardDetails.add( - CardInfo.SecurityMode( - securityOption = state.manageSecurityState.currentOption, - clickable = state.manageSecurityState.allowedOptions.size > 1, - ), - ) - if (state.card.backupStatus?.isActive == true && state.card.isAccessCodeSet) { - cardDetails.add(CardInfo.ChangeAccessCode) - } - if (state.accessCodeRecovery != null) { - cardDetails.add(CardInfo.AccessCodeRecovery(state.accessCodeRecovery.enabledOnCard)) - } - if (state.resetCardAllowed) { - cardDetails.add(CardInfo.ResetToFactorySettings(state.cardInfo)) - } - CardSettingsScreenState( - cardDetails = cardDetails, - accessCodeRecoveryState = state.accessCodeRecovery, - onScanCardClick = { }, - onElementClick = { - handleClickingItem(it) - }, - ) + screenState.update { state -> + state.copy(cardDetails = cardDetails) } } @@ -107,20 +126,43 @@ internal class CardSettingsViewModel @Inject constructor( when (item) { is CardInfo.ChangeAccessCode -> { Analytics.send(Settings.CardSettings.ButtonChangeUserCode(AnalyticsParam.UserCode.AccessCode)) - store.dispatch(DetailsAction.ManageSecurity.ChangeAccessCode) + changeAccessCode() } is CardInfo.ResetToFactorySettings -> { Analytics.send(Settings.CardSettings.ButtonFactoryReset()) - store.dispatch(DetailsAction.ResetToFactory.Start) + store.dispatchNavigationAction { AppRoute.ResetToFactory(userWalletId) } } is CardInfo.SecurityMode -> { Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode()) - store.dispatch(DetailsAction.ManageSecurity.OpenSecurity) + store.dispatchNavigationAction { AppRoute.DetailsSecurity(userWalletId) } } is CardInfo.AccessCodeRecovery -> { - store.dispatch(DetailsAction.AccessCodeRecovery.Open) + store.dispatchNavigationAction { AppRoute.AccessCodeRecovery(userWalletId) } } else -> {} } } + + private fun changeAccessCode() = viewModelScope.launch { + val card = getUserWallet().scanResponse.card + + when (val result = tangemSdkManager.setAccessCode(card.cardId)) { + is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged()) + is CompletionResult.Failure -> { + Timber.e("Failed to change access code: ${result.error}") + } + } + } + + private fun getUserWallet(): UserWallet { + return getUserWalletUseCase(userWalletId).getOrElse { + error("Failed to get user wallet $userWalletId: $it") + } + } + + private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean { + val hasPermanentWallet = card.wallets.any { it.settings.isPermanent } + val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin() + return !isNotAllowed + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt index 3beea0503a..bcfe7cd28d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt @@ -1,55 +1,33 @@ package com.tangem.tap.features.details.ui.cardsettings.coderecovery import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint -import org.rekotlin.StoreSubscriber import javax.inject.Inject @AndroidEntryPoint -class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber { +class AccessCodeRecoveryFragment : ComposeFragment() { - private val viewModel = AccessCodeRecoveryViewModel(store) - - private var screenState: MutableState = - mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.accessCodeRecovery)) + private val viewModel: AccessCodeRecoveryViewModel by viewModels() @Inject override lateinit var uiDependencies: UiDependencies @Composable override fun ScreenContent(modifier: Modifier) { + val state by viewModel.screenState.collectAsStateWithLifecycle() + AccessCodeRecoveryScreen( - state = screenState.value, + state = state, onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, ) } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.detailsState == newState.detailsState - }.select { it.detailsState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun newState(state: DetailsState) { - if (activity == null || view == null) return - screenState.value = - viewModel.updateState(store.state.detailsState.cardSettingsState?.accessCodeRecovery) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt index 12edf5a47e..d8107e0c4d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt @@ -1,10 +1,6 @@ package com.tangem.tap.features.details.ui.cardsettings.coderecovery -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable @@ -57,7 +53,7 @@ fun AccessCodeRecoveryOptions(state: AccessCodeRecoveryScreenState) { DetailsMainButton( title = stringResource(id = R.string.common_save_changes), enabled = state.isSaveChangesEnabled, - onClick = { state.onSaveChangesClick(state.enabledSelection) }, + onClick = { state.onSaveChangesClick() }, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing20), ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt index f1a825067a..b4b6c68535 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt @@ -11,6 +11,6 @@ data class AccessCodeRecoveryScreenState( val enabledOnCard: Boolean, val enabledSelection: Boolean, val isSaveChangesEnabled: Boolean, - val onSaveChangesClick: (Boolean) -> Unit, + val onSaveChangesClick: () -> Unit, val onOptionClick: (Boolean) -> Unit, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt index a2948075f8..8404af2beb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt @@ -1,30 +1,91 @@ package com.tangem.tap.features.details.ui.cardsettings.coderecovery -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.details.redux.AccessCodeRecoveryState -import com.tangem.tap.features.details.redux.DetailsAction -import org.rekotlin.Store +import android.os.Bundle +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse +import com.tangem.common.doOnSuccess +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter +import com.tangem.common.routing.bundle.unbundle +import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Settings +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled +import com.tangem.tap.store +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject -class AccessCodeRecoveryViewModel(val store: Store) { +@HiltViewModel +internal class AccessCodeRecoveryViewModel @Inject constructor( + private val getUserWalletUseCase: GetUserWalletUseCase, + private val tangemSdkManager: TangemSdkManager, + savedStateHandle: SavedStateHandle, +) : ViewModel() { - fun updateState(state: AccessCodeRecoveryState?): AccessCodeRecoveryScreenState { - // We shouldn't get to this screen here when this state is null - return if (state == null) { - AccessCodeRecoveryScreenState( - enabledOnCard = false, - enabledSelection = false, - isSaveChangesEnabled = false, - onSaveChangesClick = {}, - onOptionClick = {}, - ) - } else { - AccessCodeRecoveryScreenState( - enabledOnCard = state.enabledOnCard, - enabledSelection = state.enabledSelection, - isSaveChangesEnabled = state.enabledOnCard != state.enabledSelection, - onSaveChangesClick = { store.dispatch(DetailsAction.AccessCodeRecovery.SaveChanges(it)) }, - onOptionClick = { store.dispatch(DetailsAction.AccessCodeRecovery.SelectOption(it)) }, + private val userWalletId = savedStateHandle.get(AppRoute.AccessCodeRecovery.USER_WALLET_ID_KEY) + ?.unbundle(UserWalletId.serializer()) + ?: error("UserWalletId is required for AccessCodeRecoveryViewModel") + + val screenState = MutableStateFlow( + value = getInitialState(), + ) + + private fun getInitialState(): AccessCodeRecoveryScreenState { + val userWallet = getUserWallet() + + val isEnabled = isAccessCodeRecoveryEnabled( + typeResolver = userWallet.scanResponse.cardTypesResolver, + card = userWallet.scanResponse.card, + ) + + return AccessCodeRecoveryScreenState( + enabledOnCard = isEnabled, + enabledSelection = isEnabled, + isSaveChangesEnabled = false, + onSaveChangesClick = ::saveChanges, + onOptionClick = ::selectOption, + ) + } + + private fun saveChanges() = viewModelScope.launch { + val userWallet = getUserWallet() + val isEnabled = screenState.value.enabledSelection + + tangemSdkManager + .setAccessCodeRecoveryEnabled(userWallet.cardId, isEnabled) + .doOnSuccess { + Analytics.send( + Settings.CardSettings.AccessCodeRecoveryChanged( + AnalyticsParam.AccessCodeRecoveryStatus.from(isEnabled), + ), + ) + + store.dispatchNavigationAction(AppRouter::pop) + } + } + + private fun selectOption(isEnabled: Boolean) { + screenState.update { + it.copy( + isSaveChangesEnabled = isEnabled != it.enabledOnCard, ) } } + + private fun getUserWallet(): UserWallet { + return getUserWalletUseCase(userWalletId).getOrElse { + error("Unable to get user wallet $userWalletId: $it") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt new file mode 100644 index 0000000000..c08b50c4e5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.features.details.ui.common.utils + +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.models.scan.CardDTO + +internal fun isAccessCodeRecoveryAllowed(typeResolver: CardTypesResolver): Boolean = typeResolver.isWallet2() + +internal fun isAccessCodeRecoveryEnabled(typeResolver: CardTypesResolver, card: CardDTO): Boolean = + if (typeResolver.isWallet2()) { + card.userSettings?.isUserCodeRecoveryAllowed ?: false + } else { + false + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt new file mode 100644 index 0000000000..b576e8193e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt @@ -0,0 +1,14 @@ +package com.tangem.tap.features.details.ui.common.utils + +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.models.scan.CardDTO +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.wallet.R + +internal fun getResetToFactoryDescription(card: CardDTO, typesResolver: CardTypesResolver): TextReference { + return if (card.backupStatus?.isActive != true || typesResolver.isTangemTwins()) { + TextReference.Res(R.string.reset_card_without_backup_to_factory_message) + } else { + TextReference.Res(R.string.reset_card_with_backup_to_factory_message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt new file mode 100644 index 0000000000..0a62abe82e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.details.ui.common.utils + +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.models.scan.CardDTO +import com.tangem.tap.features.details.redux.SecurityOption +import java.util.EnumSet + +internal fun getCurrentSecurityOption(card: CardDTO): SecurityOption = when { + card.isAccessCodeSet -> SecurityOption.AccessCode + card.isPasscodeSet == true -> SecurityOption.PassCode + else -> SecurityOption.LongTap +} + +internal fun getAllowedSecurityOptions( + card: CardDTO, + cardTypesResolver: CardTypesResolver, + currentSecurityOption: SecurityOption, +): EnumSet = when { + cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() -> EnumSet.of(SecurityOption.LongTap) + card.settings.isBackupAllowed -> EnumSet.of(currentSecurityOption) + else -> prepareAllowedSecurityOptions( + cardTypesResolver = cardTypesResolver, + currentSecurityOption = currentSecurityOption, + ) +} + +private fun prepareAllowedSecurityOptions( + cardTypesResolver: CardTypesResolver, + currentSecurityOption: SecurityOption?, +): EnumSet { + val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap) + + if (cardTypesResolver.isTangemTwins()) { + allowedSecurityOptions.add(SecurityOption.PassCode) + } + if (currentSecurityOption == SecurityOption.AccessCode) { + allowedSecurityOptions.add(SecurityOption.AccessCode) + } + if (currentSecurityOption == SecurityOption.PassCode) { + allowedSecurityOptions.add(SecurityOption.PassCode) + } + + return allowedSecurityOptions +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 4cc6783772..d2c84a43db 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.onEach import org.rekotlin.Store import timber.log.Timber -// TODO: change to Android ViewModel [REDACTED_JIRA] internal class DetailsViewModel( private val store: Store, private val walletsRepository: WalletsRepository, @@ -90,10 +89,6 @@ internal class DetailsViewModel( SettingsItem.AppSettings(::navigateToAppSettings) .let(::add) - // removed chat in task [REDACTED_TASK_KEY] - // SettingsItem.Chat(::navigateToChat) - // .let(::add) - SettingsItem.SendFeedback(::sendFeedback) .let(::add) @@ -137,7 +132,10 @@ internal class DetailsViewModel( } private fun navigateToReferralProgram() { - store.dispatchNavigationAction { push(AppRoute.ReferralProgram) } + val userWallet = userWalletsListManager.selectedUserWalletSync + ?: error("Selected wallet must be not null") + + store.dispatchNavigationAction { push(AppRoute.ReferralProgram(userWallet.walletId)) } } private fun sendFeedback() { @@ -157,8 +155,10 @@ internal class DetailsViewModel( } private fun navigateToCardSettings() { + val userWalletId = userWalletsListManager.selectedUserWalletSync?.walletId + ?: error("UserWalletId must be not null") Analytics.send(Settings.ButtonCardSettings()) - store.dispatchNavigationAction { push(AppRoute.CardSettings) } + store.dispatchNavigationAction { push(AppRoute.CardSettings(userWalletId)) } } private fun linkMoreCards() { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt index a17e32ffd9..041a91ee08 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -8,26 +9,21 @@ import com.tangem.common.routing.AppRouter import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.MutableStateFlow -import org.rekotlin.StoreSubscriber import javax.inject.Inject @AndroidEntryPoint -internal class ResetCardFragment : ComposeFragment(), StoreSubscriber { +internal class ResetCardFragment : ComposeFragment() { @Inject override lateinit var uiDependencies: UiDependencies private val viewModel: ResetCardViewModel by viewModels() - private var screenState = MutableStateFlow(ResetCardScreenState.InitialState) - @Composable override fun ScreenContent(modifier: Modifier) { - val state = screenState.collectAsStateWithLifecycle().value + val state by viewModel.screenState.collectAsStateWithLifecycle() ResetCardScreen( state = state, @@ -35,23 +31,4 @@ internal class ResetCardFragment : ComposeFragment(), StoreSubscriber - state.skipRepeats { oldState, newState -> - oldState.detailsState == newState.detailsState - }.select { it.detailsState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun newState(state: DetailsState) { - if (activity == null || view == null) return - screenState.value = viewModel.updateState(state.cardSettingsState) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 85bec44728..1b52a50fd1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -20,24 +20,19 @@ import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.details.ui.common.DetailsMainButton import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R -import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState.ResetCardScreenContent.Dialog as ResetCardDialog +import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState.Dialog as ResetCardDialog @Composable internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) { SettingsScreensScaffold( modifier = modifier, content = { - when (state) { - is ResetCardScreenState.ResetCardScreenContent -> ResetCardView(state = state) - ResetCardScreenState.InitialState -> { - // do nothing for now, just white screen - } - } + ResetCardView(state = state) }, onBackClick = onBackClick, ) - when (val dialog = (state as? ResetCardScreenState.ResetCardScreenContent)?.dialog) { + when (val dialog = state.dialog) { is ResetCardDialog.StartReset, is ResetCardDialog.ContinueReset, is ResetCardDialog.InterruptedReset, @@ -48,7 +43,7 @@ internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Uni } @Composable -private fun ResetCardView(state: ResetCardScreenState.ResetCardScreenContent) { +private fun ResetCardView(state: ResetCardScreenState) { val scrollState = rememberScrollState() Column( @@ -110,7 +105,7 @@ private fun Description(text: TextReference) { } @Composable -private fun Conditions(state: ResetCardScreenState.ResetCardScreenContent) { +private fun Conditions(state: ResetCardScreenState) { state.warningsToShow.forEach { when (it) { ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS -> { @@ -120,7 +115,6 @@ private fun Conditions(state: ResetCardScreenState.ResetCardScreenContent) { description = TextReference.Res(R.string.reset_card_to_factory_condition_1), ) } - ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE -> { ConditionCheckBox( checkedState = state.acceptCondition2Checked, @@ -192,7 +186,7 @@ private fun ResetButton(enabled: Boolean, onResetButtonClick: () -> Unit) { } @Composable -private fun CommonResetDialog(dialog: ResetCardScreenState.ResetCardScreenContent.Dialog) { +private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) { BasicDialog( title = stringResource(dialog.titleResId), message = stringResource(dialog.messageResId), @@ -231,10 +225,13 @@ private fun ResetCardScreenSample(modifier: Modifier = Modifier) { .background(TangemTheme.colors.background.secondary), ) { ResetCardScreen( - state = ResetCardScreenState.ResetCardScreenContent( - accepted = true, + state = ResetCardScreenState( + resetButtonEnabled = true, + showResetPasswordButton = false, warningsToShow = listOf(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS), descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message), + acceptCondition1Checked = false, + acceptCondition2Checked = false, onAcceptCondition1ToggleClick = {}, onAcceptCondition2ToggleClick = {}, onResetButtonClick = {}, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index 0bcff503bc..27dacf387f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -4,63 +4,57 @@ import androidx.annotation.StringRes import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R -internal sealed class ResetCardScreenState { +internal data class ResetCardScreenState( + val resetButtonEnabled: Boolean, + val descriptionText: TextReference, + val warningsToShow: List, + val showResetPasswordButton: Boolean, + val acceptCondition1Checked: Boolean, + val acceptCondition2Checked: Boolean, + val onAcceptCondition1ToggleClick: (Boolean) -> Unit, + val onAcceptCondition2ToggleClick: (Boolean) -> Unit, + val onResetButtonClick: () -> Unit, + val dialog: Dialog?, +) { - object InitialState : ResetCardScreenState() + sealed class Dialog( + @StringRes val titleResId: Int, + @StringRes val messageResId: Int, + ) { - data class ResetCardScreenContent( - val accepted: Boolean = false, - val descriptionText: TextReference, - val warningsToShow: List, - val acceptCondition1Checked: Boolean = false, - val acceptCondition2Checked: Boolean = false, - val onAcceptCondition1ToggleClick: (Boolean) -> Unit, - val onAcceptCondition2ToggleClick: (Boolean) -> Unit, - val onResetButtonClick: () -> Unit, - val dialog: Dialog? = null, - ) : ResetCardScreenState() { - val resetButtonEnabled: Boolean - get() = accepted + abstract val onConfirmClick: () -> Unit + abstract val onDismiss: () -> Unit - sealed class Dialog( - @StringRes val titleResId: Int, - @StringRes val messageResId: Int, + data class StartReset( + override val onConfirmClick: () -> Unit, + override val onDismiss: () -> Unit, + ) : Dialog( + titleResId = R.string.common_attention, + messageResId = R.string.card_settings_action_sheet_title, + ) + + data class ContinueReset( + override val onConfirmClick: () -> Unit, + override val onDismiss: () -> Unit, + ) : Dialog( + titleResId = R.string.card_settings_continue_reset_alert_title, + messageResId = R.string.card_settings_continue_reset_alert_message, + ) + + data class InterruptedReset( + override val onConfirmClick: () -> Unit, + override val onDismiss: () -> Unit, + ) : Dialog( + titleResId = R.string.card_settings_interrupted_reset_alert_title, + messageResId = R.string.card_settings_interrupted_reset_alert_message, + ) + + data class CompletedReset(override val onConfirmClick: () -> Unit) : Dialog( + titleResId = R.string.card_settings_completed_reset_alert_title, + messageResId = R.string.card_settings_completed_reset_alert_message, ) { - abstract val onConfirmClick: () -> Unit - abstract val onDismiss: () -> Unit - - data class StartReset( - override val onConfirmClick: () -> Unit, - override val onDismiss: () -> Unit, - ) : Dialog( - titleResId = R.string.common_attention, - messageResId = R.string.card_settings_action_sheet_title, - ) - - data class ContinueReset( - override val onConfirmClick: () -> Unit, - override val onDismiss: () -> Unit, - ) : Dialog( - titleResId = R.string.card_settings_continue_reset_alert_title, - messageResId = R.string.card_settings_continue_reset_alert_message, - ) - - data class InterruptedReset( - override val onConfirmClick: () -> Unit, - override val onDismiss: () -> Unit, - ) : Dialog( - titleResId = R.string.card_settings_interrupted_reset_alert_title, - messageResId = R.string.card_settings_interrupted_reset_alert_message, - ) - - data class CompletedReset(override val onConfirmClick: () -> Unit) : Dialog( - titleResId = R.string.card_settings_completed_reset_alert_title, - messageResId = R.string.card_settings_completed_reset_alert_message, - ) { - - override val onDismiss: () -> Unit = {} - } + override val onDismiss: () -> Unit = {} } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 491f7dfc81..d578f0875f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -1,8 +1,12 @@ package com.tangem.tap.features.details.ui.resetcard +import android.os.Bundle +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.bundle.unbundle import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.card.DeleteSavedAccessCodesUseCase @@ -10,96 +14,132 @@ import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase -import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.tap.common.analytics.events.Settings +import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.onUserWalletSelected -import com.tangem.tap.features.details.redux.CardSettingsState -import com.tangem.tap.features.details.redux.DetailsAction.ResetToFactory -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.tap.features.details.ui.resetcard.featuretoggles.ResetCardFeatureToggles -import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText +import com.tangem.tap.features.details.redux.ResetCardDialog +import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription import com.tangem.tap.store import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject -import com.tangem.tap.features.details.redux.CardSettingsState.Dialog as CardSettingsDialog @Suppress("LongParameterList") @HiltViewModel internal class ResetCardViewModel @Inject constructor( - private val resetCardFeatureToggles: ResetCardFeatureToggles, + private val getUserWalletUseCase: GetUserWalletUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val resetCardUseCase: ResetCardUseCase, private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val deleteWalletUseCase: DeleteWalletUseCase, private val userWalletsListManager: UserWalletsListManager, private val analyticsEventHandler: AnalyticsEventHandler, + savedStateHandle: SavedStateHandle, ) : ViewModel() { - private val firstCardScanResponse = store.state.detailsState.cardSettingsState?.scanResponse - ?: error("ScanResponse can't be null") - - private val currentUserWalletId = createUserWalletId(firstCardScanResponse) + private val userWalletId = savedStateHandle.get(AppRoute.ResetToFactory.USER_WALLET_ID) + ?.unbundle(UserWalletId.serializer()) + ?: error("User wallet ID must be provided for ResetCardViewModel") // TODO: move logic to separate domain entity private var resetBackupCardCount = 0 - fun updateState(state: CardSettingsState?): ResetCardScreenState.ResetCardScreenContent { - val descriptionText = state?.cardInfo - ?.toResetCardDescriptionText() - ?: TextReference.Str(value = "") + val screenState: MutableStateFlow = MutableStateFlow( + value = getInitialState(), + ) + + private fun getInitialState(): ResetCardScreenState { + val userWallet = getUserWalletUseCase(userWalletId).getOrElse { + error("Failed to get user wallet $userWalletId: $it") + } + val card = userWallet.scanResponse.card + val cardTypesResolver = userWallet.scanResponse.cardTypesResolver + val descriptionText = getResetToFactoryDescription(card, cardTypesResolver) + val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2() + val showResetPasswordButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active val warningsToShow = buildList { add(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS) - if (state?.isShowPasswordResetRadioButton == true) { + if (showResetPasswordButton) { add(ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE) } } - return ResetCardScreenState.ResetCardScreenContent( - accepted = state?.resetButtonEnabled ?: false, + return ResetCardScreenState( + resetButtonEnabled = false, descriptionText = descriptionText, warningsToShow = warningsToShow, - acceptCondition1Checked = state?.condition1Checked ?: false, - acceptCondition2Checked = state?.condition2Checked ?: false, - onAcceptCondition1ToggleClick = { store.dispatch(ResetToFactory.AcceptCondition1(it)) }, - onAcceptCondition2ToggleClick = { store.dispatch(ResetToFactory.AcceptCondition2(it)) }, - onResetButtonClick = { showDialog(CardSettingsDialog.StartResetDialog) }, - dialog = state?.dialog?.let(::createDialog), + showResetPasswordButton = showResetPasswordButton, + acceptCondition1Checked = false, + acceptCondition2Checked = false, + onAcceptCondition1ToggleClick = ::toggleFirstCondition, + onAcceptCondition2ToggleClick = ::toggleSecondCondition, + onResetButtonClick = { showDialog(ResetCardDialog.StartResetDialog) }, + dialog = null, ) } - private fun createDialog(dialog: CardSettingsDialog): ResetCardScreenState.ResetCardScreenContent.Dialog { + private fun toggleFirstCondition(isAccepted: Boolean) { + screenState.update { prevState -> + val resetButtonEnabled = if (prevState.showResetPasswordButton) { + isAccepted && prevState.acceptCondition2Checked + } else { + isAccepted + } + + prevState.copy( + acceptCondition1Checked = isAccepted, + resetButtonEnabled = resetButtonEnabled, + ) + } + } + + private fun toggleSecondCondition(isAccepted: Boolean) { + screenState.update { prevState -> + val resetButtonEnabled = prevState.acceptCondition1Checked && isAccepted + + prevState.copy( + acceptCondition2Checked = isAccepted, + resetButtonEnabled = resetButtonEnabled, + ) + } + } + + private fun createDialog(dialog: ResetCardDialog): ResetCardScreenState.Dialog { return when (dialog) { - CardSettingsDialog.StartResetDialog -> { - ResetCardScreenState.ResetCardScreenContent.Dialog.StartReset( + ResetCardDialog.StartResetDialog -> { + ResetCardScreenState.Dialog.StartReset( onConfirmClick = ::onStartResetClick, onDismiss = ::dismissDialog, ) } - CardSettingsDialog.ContinueResetDialog -> { - ResetCardScreenState.ResetCardScreenContent.Dialog.ContinueReset( + ResetCardDialog.ContinueResetDialog -> { + ResetCardScreenState.Dialog.ContinueReset( onConfirmClick = ::onContinueResetClick, onDismiss = ::onContinueResetDialogDismiss, ) } - CardSettingsDialog.InterruptedResetDialog -> { - ResetCardScreenState.ResetCardScreenContent.Dialog.InterruptedReset( + ResetCardDialog.InterruptedResetDialog -> { + ResetCardScreenState.Dialog.InterruptedReset( onConfirmClick = ::onContinueResetClick, onDismiss = ::onInterruptedResetDialogDismiss, ) } - CardSettingsDialog.CompletedResetDialog -> { - ResetCardScreenState.ResetCardScreenContent.Dialog.CompletedReset( + ResetCardDialog.CompletedResetDialog -> { + ResetCardScreenState.Dialog.CompletedReset( onConfirmClick = ::dismissAndFinishFullReset, ) } @@ -109,21 +149,26 @@ internal class ResetCardViewModel @Inject constructor( private fun onStartResetClick() { dismissDialog() - if (resetCardFeatureToggles.isFullResetEnabled) { - makeFullReset() - } else { - store.dispatch(ResetToFactory.Proceed) - } + makeFullReset() } private fun makeFullReset() { viewModelScope.launch { - resetCardUseCase(card = firstCardScanResponse.card).onRight { - deleteSavedAccessCodesUseCase(firstCardScanResponse.card.cardId) - deleteWalletUseCase(currentUserWalletId) + val userWallet = getUserWallet() + val scanResponse = userWallet.scanResponse + + resetCardUseCase(card = scanResponse.card).onRight { + deleteSavedAccessCodesUseCase(scanResponse.card.cardId) + val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse { + Timber.e("Unable to delete user wallet: $it") + return@launch + } + + if (hasUserWallets) { + val newSelectedWallet = getSelectedWalletSyncUseCase().getOrElse { + error("Failed to get selected wallet: $it") + } - val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull() - if (newSelectedWallet != null) { store.onUserWalletSelected(newSelectedWallet) } @@ -138,10 +183,11 @@ internal class ResetCardViewModel @Inject constructor( dismissDialog() viewModelScope.launch { + val userWallet = getUserWallet() resetCardUseCase( cardNumber = resetBackupCardCount + 1, - card = firstCardScanResponse.card, - userWalletId = currentUserWalletId, + card = userWallet.scanResponse.card, + userWalletId = userWalletId, ) .onRight { resetBackupCardCount++ @@ -150,14 +196,14 @@ internal class ResetCardViewModel @Inject constructor( checkRemainingBackupCards() } - .onLeft { showDialog(CardSettingsDialog.InterruptedResetDialog) } + .onLeft { showDialog(ResetCardDialog.InterruptedResetDialog) } } } private fun onContinueResetDialogDismiss() { dismissDialog() - showDialog(CardSettingsDialog.InterruptedResetDialog) + showDialog(ResetCardDialog.InterruptedResetDialog) } private fun onInterruptedResetDialogDismiss() { @@ -167,20 +213,26 @@ internal class ResetCardViewModel @Inject constructor( } private fun checkRemainingBackupCards() { - val backupCardsCount = firstCardScanResponse.getBackupCardsCount() + val backupCardsCount = getUserWallet().scanResponse.getBackupCardsCount() when { - backupCardsCount > resetBackupCardCount -> showDialog(CardSettingsDialog.ContinueResetDialog) + backupCardsCount > resetBackupCardCount -> showDialog(ResetCardDialog.ContinueResetDialog) backupCardsCount == resetBackupCardCount -> { analyticsEventHandler.send( event = Settings.CardSettings.FactoryResetFinished(cardsCount = resetBackupCardCount + 1), ) - showDialog(CardSettingsDialog.CompletedResetDialog) + showDialog(ResetCardDialog.CompletedResetDialog) } else -> finishFullReset() } } + private fun getUserWallet(): UserWallet { + return getUserWalletUseCase(userWalletId).getOrElse { + error("Failed to get user wallet $userWalletId: $it") + } + } + private fun dismissAndFinishFullReset() { dismissDialog() @@ -202,12 +254,12 @@ internal class ResetCardViewModel @Inject constructor( } } - private fun showDialog(dialog: CardSettingsDialog) { - store.dispatch(ResetToFactory.ShowDialog(dialog)) + private fun showDialog(dialog: ResetCardDialog) { + screenState.update { it.copy(dialog = createDialog(dialog)) } } private fun dismissDialog() { - store.dispatch(ResetToFactory.DismissDialog) + screenState.update { it.copy(dialog = null) } } private fun ScanResponse.getBackupCardsCount(): Int { @@ -221,9 +273,4 @@ internal class ResetCardViewModel @Inject constructor( -> 0 } } - - private fun createUserWalletId(scanResponse: ScanResponse): UserWalletId { - return UserWalletIdBuilder.scanResponse(scanResponse).build() - ?: error("UserWalletId can't be null") - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt deleted file mode 100644 index 2de1e70012..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/di/ResetCardModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.features.details.ui.resetcard.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.tap.features.details.ui.resetcard.featuretoggles.DefaultResetCardFeatureToggles -import com.tangem.tap.features.details.ui.resetcard.featuretoggles.ResetCardFeatureToggles -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 ResetCardModule { - - @Provides - @Singleton - fun provideResetCardFeatureToggles(featureTogglesManager: FeatureTogglesManager): ResetCardFeatureToggles { - return DefaultResetCardFeatureToggles(featureTogglesManager) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt deleted file mode 100644 index a5b76a410d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/DefaultResetCardFeatureToggles.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.tap.features.details.ui.resetcard.featuretoggles - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager - -internal class DefaultResetCardFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) : ResetCardFeatureToggles { - - override val isFullResetEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "FULL_RESET_ENABLED") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt deleted file mode 100644 index d290d4d1d9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/featuretoggles/ResetCardFeatureToggles.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.tap.features.details.ui.resetcard.featuretoggles - -interface ResetCardFeatureToggles { - - val isFullResetEnabled: Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt index f61e54fd78..1c5fe7f90b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt @@ -1,55 +1,34 @@ package com.tangem.tap.features.details.ui.securitymode import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store import dagger.hilt.android.AndroidEntryPoint -import org.rekotlin.StoreSubscriber import javax.inject.Inject @AndroidEntryPoint -internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber { +internal class SecurityModeFragment : ComposeFragment() { @Inject override lateinit var uiDependencies: UiDependencies - private val viewModel = SecurityModeViewModel(store) - - private var screenState: MutableState = - mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.manageSecurityState)) + private val viewModel: SecurityModeViewModel by viewModels() @Composable override fun ScreenContent(modifier: Modifier) { + val state by viewModel.screenState.collectAsStateWithLifecycle() + SecurityModeScreen( modifier = modifier, - state = screenState.value, + state = state, onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, ) } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.detailsState == newState.detailsState - }.select { it.detailsState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun newState(state: DetailsState) { - if (activity == null || view == null) return - screenState.value = viewModel.updateState(state.cardSettingsState?.manageSecurityState) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt index 7aa863a7df..5f0d54717e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt @@ -1,29 +1,109 @@ package com.tangem.tap.features.details.ui.securitymode -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.ManageSecurityState +import android.os.Bundle +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter +import com.tangem.common.routing.bundle.unbundle +import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Settings +import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.features.details.redux.SecurityOption -import org.rekotlin.Store +import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions +import com.tangem.tap.features.details.ui.common.utils.getCurrentSecurityOption +import com.tangem.tap.store +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject -internal class SecurityModeViewModel(val store: Store) { +@HiltViewModel +internal class SecurityModeViewModel @Inject constructor( + private val getUserWalletUseCase: GetUserWalletUseCase, + private val tangemSdkManager: TangemSdkManager, + savedStateHandle: SavedStateHandle, +) : ViewModel() { + + private val userWalletId = savedStateHandle.get(AppRoute.DetailsSecurity.USER_WALLET_ID_KEY) + ?.unbundle(UserWalletId.serializer()) + ?: error("UserWalletId is required for SecurityModeViewModel") + + val screenState = MutableStateFlow( + value = getInitialState(), + ) + + private fun getInitialState(): SecurityModeScreenState { + val userWallet = getUserWallet() + val scanResponse = userWallet.scanResponse + val card = scanResponse.card + val cardTypesResolver = scanResponse.cardTypesResolver + + val currentSecurityOption = getCurrentSecurityOption(card) + val allowedSecurityOptions = getAllowedSecurityOptions(card, cardTypesResolver, currentSecurityOption) - fun updateState(state: ManageSecurityState?): SecurityModeScreenState { - if (state == null) { - return SecurityModeScreenState( - availableOptions = emptyList(), - selectedSecurityMode = SecurityOption.LongTap, - isSaveChangesEnabled = false, - onNewModeSelected = {}, - onSaveChangesClicked = {}, - ) - } return SecurityModeScreenState( - availableOptions = state.allowedOptions.toList(), - selectedSecurityMode = state.selectedOption, - isSaveChangesEnabled = state.selectedOption != state.currentOption, - onNewModeSelected = { store.dispatch(DetailsAction.ManageSecurity.SelectOption(it)) }, - onSaveChangesClicked = { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }, + availableOptions = allowedSecurityOptions.toList(), + selectedSecurityMode = currentSecurityOption, + isSaveChangesEnabled = false, + onNewModeSelected = ::selectOption, + onSaveChangesClicked = ::saveChanges, ) } + + private fun selectOption(securityOption: SecurityOption) { + screenState.update { state -> + state.copy( + selectedSecurityMode = securityOption, + isSaveChangesEnabled = securityOption != getCurrentSecurityOption(getUserWallet().scanResponse.card), + ) + } + } + + private fun saveChanges() { + val userWallet = getUserWallet() + val cardId = userWallet.cardId + val selectedOption = screenState.value.selectedSecurityMode + + viewModelScope.launch { + val result = when (selectedOption) { + SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) + SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) + SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) + } + + val paramValue = AnalyticsParam.SecurityMode.from(selectedOption) + when (result) { + is CompletionResult.Success -> { + Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue)) + + store.dispatchNavigationAction(AppRouter::pop) + } + is CompletionResult.Failure -> { + val error = result.error + if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) { + Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error)) + } + } + else -> Unit + } + } + } + + private fun getUserWallet(): UserWallet { + return getUserWalletUseCase(userWalletId).getOrElse { + error("Unable to get user wallet $userWalletId: $it") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt b/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt deleted file mode 100644 index 459e5ba49a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/utils/Mappers.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.tap.features.details.ui.utils - -import com.tangem.tap.features.details.redux.CardInfo -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.wallet.R - -internal fun CardInfo.toResetCardDescriptionText(): TextReference { - return if (!this.hasBackup || this.isTwin) { - TextReference.Res(R.string.reset_card_without_backup_to_factory_message) - } else { - TextReference.Res(R.string.reset_card_with_backup_to_factory_message) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index bb885005f9..0ecd71bd5d 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -30,7 +30,7 @@ internal class WelcomeViewModel @Inject constructor( DefaultLifecycleObserver { private val initialIntent: SerializableIntent? = savedStateHandle.get(AppRoute.Welcome.INITIAL_INTENT_KEY) - ?.let { it.unbundle(SerializableIntent.serializer()) } + ?.unbundle(SerializableIntent.serializer()) private val stateInternal = MutableStateFlow(WelcomeScreenState()) val state: StateFlow = stateInternal diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 8bd5b8a2c1..8a2676a038 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -117,19 +117,55 @@ sealed class AppRoute(val path: String) : Route { ) : AppRoute(path = "/details/${userWalletId.stringValue}") @Serializable - data object DetailsSecurity : AppRoute(path = "/details/security") + data class DetailsSecurity( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/details/security"), RouteBundleParams { + + override fun getBundle(): Bundle = bundle(serializer()) + + companion object { + const val USER_WALLET_ID_KEY = "userWalletId" + } + } @Serializable - data object CardSettings : AppRoute(path = "/card_settings") + data class CardSettings( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/card_settings/${userWalletId.stringValue}"), RouteBundleParams { + + override fun getBundle(): Bundle = bundle(serializer()) + + companion object { + const val USER_WALLET_ID_KEY = "userWalletId" + } + } @Serializable data object AppSettings : AppRoute(path = "/app_settings") @Serializable - data object ResetToFactory : AppRoute(path = "/reset_to_factory") + data class ResetToFactory( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/reset_to_factory/${userWalletId.stringValue}"), RouteBundleParams { + + override fun getBundle(): Bundle = bundle(serializer()) + + companion object { + const val USER_WALLET_ID = "userWalletId" + } + } @Serializable - data object AccessCodeRecovery : AppRoute(path = "/access_code_recovery") + data class AccessCodeRecovery( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/access_code_recovery/${userWalletId.stringValue}"), RouteBundleParams { + + override fun getBundle(): Bundle = bundle(serializer()) + + companion object { + const val USER_WALLET_ID_KEY = "userWalletId" + } + } @Serializable data object ManageTokens : AppRoute(path = "/manage_tokens") @@ -155,7 +191,16 @@ sealed class AppRoute(val path: String) : Route { } @Serializable - data object ReferralProgram : AppRoute(path = "/referral_program") + data class ReferralProgram( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/referral_program"), RouteBundleParams { + + override fun getBundle(): Bundle = bundle(serializer()) + + companion object { + const val USER_WALLET_ID_KEY = "userWalletId" + } + } @Serializable data class Swap( diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 8c540a7cbd..7def5136f7 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -27,10 +27,6 @@ "name": "STAKING_ENABLED", "version": "undefined" }, - { - "name": "FULL_RESET_ENABLED", - "version": "5.12.0" - }, { "name": "DETAILS_REDESIGN_ENABLED", "version": "5.13.0" diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt index 82208040e8..6a4afdcfd9 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt @@ -143,7 +143,13 @@ internal class TangemCardTypesResolver( override fun getRemainingSignatures(): Int? = card.wallets.firstOrNull()?.remainingSignatures - override fun getCardId(): String = card.cardId + override fun getCardId(): String { + return if (isTangemTwins()) { + card.getTwinCardIdForUser() + } else { + card.cardId + } + } override fun isTestCard(): Boolean = card.isTestCard diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExtensions.kt b/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExtensions.kt index 61ec0cc72e..1c3519f606 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExtensions.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExtensions.kt @@ -13,6 +13,7 @@ import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.configs.CardConfig import com.tangem.domain.common.configs.Wallet2CardConfig import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.wallets.models.UserWallet val ScanResponse.cardTypesResolver: CardTypesResolver get() = TangemCardTypesResolver( @@ -27,6 +28,9 @@ val ScanResponse.derivationStyleProvider: DerivationStyleProvider card, ) +val UserWallet.cardTypesResolver: CardTypesResolver + get() = scanResponse.cardTypesResolver + fun ScanResponse.twinsIsTwinned(): Boolean = card.isTangemTwins && walletData != null && secondTwinPublicKey != null fun ScanResponse.supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed fun ScanResponse.supportsBackup(): Boolean = card.settings.isBackupAllowed diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 9b63847e73..bd117a8c8e 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -16,12 +16,14 @@ interface UserWalletsListManager { val userWallets: Flow> /** [Flow] with selected [UserWallet] updates */ + @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") val selectedUserWallet: Flow /** [List] with all saved [UserWallet]s updates */ val userWalletsSync: List /** Selected [UserWallet] */ + @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") val selectedUserWalletSync: UserWallet? /** Indicates that the [UserWalletsListManager] contains at least one saved [UserWallet] */ diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt index e9f51327a6..ebcd1378d7 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletSyncUseCase.kt @@ -17,6 +17,7 @@ import com.tangem.domain.wallets.models.UserWallet */ class GetSelectedWalletSyncUseCase(private val userWalletsListManager: UserWalletsListManager) { + @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") operator fun invoke(): Either { return either { ensureNotNull( diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt index bda3c953ee..4751238a0f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSelectedWalletUseCase.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.Flow */ class GetSelectedWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { + @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") operator fun invoke(): Either> { return either { userWalletsListManager.selectedUserWallet diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt index 8024d1cc67..2bf7174f00 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt @@ -1,12 +1,13 @@ package com.tangem.feature.referral.domain +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.referral.domain.models.ReferralData interface ReferralInteractor { val isDemoMode: Boolean - suspend fun getReferralStatus(): ReferralData + suspend fun getReferralStatus(userWalletId: UserWalletId): ReferralData - suspend fun startReferral(): ReferralData + suspend fun startReferral(userWalletId: UserWalletId): ReferralData } \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 154764b162..36782f4e1d 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -3,7 +3,8 @@ package com.tangem.feature.referral.domain import arrow.core.getOrElse import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData import com.tangem.lib.crypto.UserWalletManager @@ -14,7 +15,7 @@ internal class ReferralInteractorImpl( private val repository: ReferralRepository, private val userWalletManager: UserWalletManager, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, - private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + private val getUserWalletUseCase: GetUserWalletUseCase, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, ) : ReferralInteractor { @@ -22,20 +23,20 @@ internal class ReferralInteractorImpl( override val isDemoMode: Boolean get() = repository.isDemoMode - override suspend fun getReferralStatus(): ReferralData { - val referralData = repository.getReferralData(userWalletManager.getWalletId()) + override suspend fun getReferralStatus(userWalletId: UserWalletId): ReferralData { + val referralData = repository.getReferralData(userWalletId.stringValue) saveReferralTokens(referralData.tokens) return referralData } - override suspend fun startReferral(): ReferralData { + override suspend fun startReferral(userWalletId: UserWalletId): ReferralData { if (tokensForReferral.isEmpty()) error("Tokens for ref is empty") val tokenData = tokensForReferral.first() - val userWallet = getSelectedWalletSyncUseCase().getOrElse { - error("Failed to get selected wallet: $it") + val userWallet = getUserWalletUseCase(userWalletId).getOrElse { + error("Failed to get user wallet $userWalletId: $it") } val cryptoCurrency = repository.getCryptoCurrency(userWalletId = userWallet.walletId, tokenData = tokenData) diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt index 0cd466fed7..172c41b27d 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt @@ -2,7 +2,7 @@ package com.tangem.feature.referral.domain.di import com.tangem.domain.card.DerivePublicKeysUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase -import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.ReferralInteractorImpl import com.tangem.feature.referral.domain.ReferralRepository @@ -23,14 +23,14 @@ class ReferralDomainModule { referralRepository: ReferralRepository, userWalletManager: UserWalletManager, derivePublicKeysUseCase: DerivePublicKeysUseCase, - getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + getUserWalletUseCase: GetUserWalletUseCase, addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, ): ReferralInteractor { return ReferralInteractorImpl( repository = referralRepository, userWalletManager = userWalletManager, derivePublicKeysUseCase = derivePublicKeysUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + getUserWalletUseCase = getUserWalletUseCase, addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase, ) } diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 8a99c77aa6..ef6062abd5 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(deps.compose.ui.tooling) /** Domain */ + implementation(projects.domain.wallets.models) implementation(projects.features.referral.domain) /** Other libraries */ diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index 82c0d08bb8..6565947793 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -1,11 +1,16 @@ package com.tangem.feature.referral.viewmodels +import android.os.Bundle import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.bundle.unbundle import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.referral.analytics.ReferralEvents import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.models.DiscountType @@ -29,15 +34,20 @@ internal class ReferralViewModel @Inject constructor( private val referralInteractor: ReferralInteractor, private val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, + savedStateHandle: SavedStateHandle, ) : ViewModel() { - var uiState: ReferralStateHolder by mutableStateOf(createInitiallyUiState()) - private set + private val userWalletId = savedStateHandle.get(AppRoute.ReferralProgram.USER_WALLET_ID_KEY) + ?.unbundle(UserWalletId.serializer()) + ?: error("User wallet ID is required for Referral screen") private var referralRouter: ReferralRouter by Delegates.notNull() private var lastReferralData: ReferralData? = null + var uiState: ReferralStateHolder by mutableStateOf(createInitiallyUiState()) + private set + init { loadReferralData() } @@ -66,7 +76,7 @@ internal class ReferralViewModel @Inject constructor( uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) viewModelScope.launch(dispatchers.main) { runCatching(dispatchers.io) { - referralInteractor.getReferralStatus().apply { + referralInteractor.getReferralStatus(userWalletId).apply { lastReferralData = this } } @@ -82,7 +92,7 @@ internal class ReferralViewModel @Inject constructor( analyticsEventHandler.send(ReferralEvents.ClickParticipate) uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) viewModelScope.launch(dispatchers.main) { - runCatching(dispatchers.io) { referralInteractor.startReferral() } + runCatching(dispatchers.io) { referralInteractor.startReferral(userWalletId) } .onSuccess(::showContent) .onFailure { throwable -> if (throwable is UserCancelledException) { diff --git a/features/wallet-settings/impl/build.gradle.kts b/features/wallet-settings/impl/build.gradle.kts index a94b469537..8c61fc867a 100644 --- a/features/wallet-settings/impl/build.gradle.kts +++ b/features/wallet-settings/impl/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { implementation(projects.common.routing) /* Project - Domain */ + implementation(projects.domain.legacy) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt index 1ba3218c38..7649ef9bd7 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt @@ -3,6 +3,7 @@ package com.tangem.feature.walletsettings.component.preview import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.decompose.navigation.DummyRouter +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.entity.WalletSettingsUM import com.tangem.feature.walletsettings.ui.WalletSettingsScreen @@ -15,7 +16,9 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent { items = ItemsBuilder( router = DummyRouter(), ).buildItems( - walletName = "My wallet", + userWalletId = UserWalletId("011"), + userWalletName = "My Wallet", + isReferralAvailable = true, renameWallet = {}, forgetWallet = {}, ), diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 188a3ba043..e7d755f454 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -11,6 +11,7 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase @@ -75,7 +76,9 @@ internal class WalletSettingsModel @Inject constructor( userWallet: UserWallet, dialogNavigation: SlotNavigation, ): PersistentList = itemsBuilder.buildItems( - walletName = userWallet.name, + userWalletId = userWallet.walletId, + userWalletName = userWallet.name, + isReferralAvailable = userWallet.cardTypesResolver.isTangemWallet(), renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) }, forgetWallet = { forgetWallet(userWallet.walletId) }, ) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt index 52f95c08b8..deb049021a 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt @@ -6,9 +6,12 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM import com.tangem.feature.walletsettings.impl.R +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @ComponentScoped @@ -16,9 +19,15 @@ internal class ItemsBuilder @Inject constructor( private val router: Router, ) { - fun buildItems(walletName: String, forgetWallet: () -> Unit, renameWallet: () -> Unit) = persistentListOf( - buildNameItem(walletName, renameWallet), - buildCardItem(), + fun buildItems( + userWalletId: UserWalletId, + userWalletName: String, + isReferralAvailable: Boolean, + forgetWallet: () -> Unit, + renameWallet: () -> Unit, + ): PersistentList = persistentListOf( + buildNameItem(userWalletName, renameWallet), + buildCardItem(userWalletId, isReferralAvailable), buildForgetItem(forgetWallet), ) @@ -29,37 +38,37 @@ internal class ItemsBuilder @Inject constructor( onClick = renameWallet, ) - private fun buildCardItem() = WalletSettingsItemUM.WithItems( - id = "card", - blocks = buildCardBlocks(), - description = resourceReference(R.string.settings_card_settings_footer), - ) + private fun buildCardItem(userWalletId: UserWalletId, isReferralAvailable: Boolean) = + WalletSettingsItemUM.WithItems( + id = "card", + description = resourceReference(R.string.settings_card_settings_footer), + blocks = buildList { + BlockUM( + text = resourceReference(R.string.card_settings_title), + iconRes = R.drawable.ic_card_settings_24, + onClick = { router.push(AppRoute.CardSettings(userWalletId)) }, + ).let(::add) - private fun buildCardBlocks() = persistentListOf( - BlockUM( - text = resourceReference(R.string.card_settings_title), - iconRes = R.drawable.ic_card_settings_24, - onClick = { router.push(AppRoute.CardSettings) }, - ), - BlockUM( - text = resourceReference(R.string.referral_title), - iconRes = R.drawable.ic_add_friends_24, - onClick = { router.push(AppRoute.ReferralProgram) }, - ), - ) + if (isReferralAvailable) { + BlockUM( + text = resourceReference(R.string.referral_title), + iconRes = R.drawable.ic_add_friends_24, + onClick = { router.push(AppRoute.ReferralProgram(userWalletId)) }, + ).let(::add) + } + }.toImmutableList(), + ) private fun buildForgetItem(forgetWallet: () -> Unit) = WalletSettingsItemUM.WithItems( id = "forget", - blocks = buildForgetBlocks(forgetWallet), description = resourceReference(R.string.settings_forget_wallet_footer), - ) - - private fun buildForgetBlocks(forgetWallet: () -> Unit) = persistentListOf( - BlockUM( - text = resourceReference(R.string.settings_forget_wallet), - iconRes = R.drawable.ic_card_foget_24, - onClick = forgetWallet, - accentType = BlockUM.AccentType.WARNING, + blocks = persistentListOf( + BlockUM( + text = resourceReference(R.string.settings_forget_wallet), + iconRes = R.drawable.ic_card_foget_24, + onClick = forgetWallet, + accentType = BlockUM.AccentType.WARNING, + ), ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index e13c2f56af..9c94f8bcdf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -176,6 +176,8 @@ internal class WalletViewModel @Inject constructor( } } + // It's okay here because we need to be able to observe the selected wallet changes + @Suppress("DEPRECATION") private fun subscribeOnSelectedWalletFlow() { getSelectedWalletUseCase().onRight { it