Updated on 2026-08-14
This commit is contained in:
commit
ab914dd37a
46 changed files with 740 additions and 1006 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<AppState> = { _, 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<AppRoute.Wallet>() }
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<SecurityOption> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<SecurityOption> = EnumSet.allOf(SecurityOption::class.java),
|
||||
val buttonProceed: Button = Button(true),
|
||||
)
|
||||
|
||||
data class AppSettingsState(
|
||||
val saveWallets: Boolean = false,
|
||||
val saveAccessCodes: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<CardInfo>? = 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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<DetailsState> {
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
var screenState: MutableState<CardSettingsScreenState> =
|
||||
mutableStateOf(updateState(store.state.detailsState.cardSettingsState))
|
||||
private val userWalletId = savedStateHandle.get<Bundle>(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<CardSettingsScreenState> = 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<CardInfo> = 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DetailsState> {
|
||||
class AccessCodeRecoveryFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel = AccessCodeRecoveryViewModel(store)
|
||||
|
||||
private var screenState: MutableState<AccessCodeRecoveryScreenState> =
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<AppState>) {
|
||||
@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<Bundle>(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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SecurityOption> = 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<SecurityOption> {
|
||||
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
|
||||
}
|
||||
|
|
@ -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<AppState>,
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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<DetailsState> {
|
||||
internal class ResetCardFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel: ResetCardViewModel by viewModels()
|
||||
|
||||
private var screenState = MutableStateFlow<ResetCardScreenState>(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<DetailsSta
|
|||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
|
|
|
|||
|
|
@ -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<WarningsToReset>,
|
||||
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<WarningsToReset>,
|
||||
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 = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Bundle>(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<ResetCardScreenState> = 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard.featuretoggles
|
||||
|
||||
interface ResetCardFeatureToggles {
|
||||
|
||||
val isFullResetEnabled: Boolean
|
||||
}
|
||||
|
|
@ -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<DetailsState> {
|
||||
internal class SecurityModeFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel = SecurityModeViewModel(store)
|
||||
|
||||
private var screenState: MutableState<SecurityModeScreenState> =
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppState>) {
|
||||
@HiltViewModel
|
||||
internal class SecurityModeViewModel @Inject constructor(
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val userWalletId = savedStateHandle.get<Bundle>(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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ internal class WelcomeViewModel @Inject constructor(
|
|||
DefaultLifecycleObserver {
|
||||
|
||||
private val initialIntent: SerializableIntent? = savedStateHandle.get<Bundle>(AppRoute.Welcome.INITIAL_INTENT_KEY)
|
||||
?.let { it.unbundle(SerializableIntent.serializer()) }
|
||||
?.unbundle(SerializableIntent.serializer())
|
||||
|
||||
private val stateInternal = MutableStateFlow(WelcomeScreenState())
|
||||
val state: StateFlow<WelcomeScreenState> = stateInternal
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ interface UserWalletsListManager {
|
|||
val userWallets: Flow<List<UserWallet>>
|
||||
|
||||
/** [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<UserWallet>
|
||||
|
||||
/** [List] with all saved [UserWallet]s updates */
|
||||
val userWalletsSync: List<UserWallet>
|
||||
|
||||
/** 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] */
|
||||
|
|
|
|||
|
|
@ -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<GetUserWalletError, UserWallet> {
|
||||
return either {
|
||||
ensureNotNull(
|
||||
|
|
|
|||
|
|
@ -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<GetUserWalletError, Flow<UserWallet>> {
|
||||
return either {
|
||||
userWalletsListManager.selectedUserWallet
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.features.referral.domain)
|
||||
|
||||
/** Other libraries */
|
||||
|
|
|
|||
|
|
@ -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<Bundle>(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) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ dependencies {
|
|||
implementation(projects.common.routing)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<DialogConfig>,
|
||||
): PersistentList<WalletSettingsItemUM> = itemsBuilder.buildItems(
|
||||
walletName = userWallet.name,
|
||||
userWalletId = userWallet.walletId,
|
||||
userWalletName = userWallet.name,
|
||||
isReferralAvailable = userWallet.cardTypesResolver.isTangemWallet(),
|
||||
renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) },
|
||||
forgetWallet = { forgetWallet(userWallet.walletId) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<WalletSettingsItemUM> = 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,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue