Updated on 2026-08-14
This commit is contained in:
parent
3fc34fac64
commit
0dd4d45b62
11 changed files with 141 additions and 67 deletions
|
|
@ -61,6 +61,9 @@ class TangemSdkManager(
|
|||
val biometricManager: BiometricManager
|
||||
get() = tangemSdk.biometricManager
|
||||
|
||||
val userCodeRequestPolicy: UserCodeRequestPolicy
|
||||
get() = tangemSdk.config.userCodeRequestPolicy
|
||||
|
||||
suspend fun scanProduct(
|
||||
cardId: String? = null,
|
||||
messageRes: Int? = null,
|
||||
|
|
@ -138,7 +141,9 @@ class TangemSdkManager(
|
|||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
): CompletionResult<CardDTO> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ResetToFactorySettingsTask(allowsRequestAccessCodeFromRepository),
|
||||
runnable = ResetToFactorySettingsTask(
|
||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||
),
|
||||
cardId = cardId,
|
||||
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
|
||||
)
|
||||
|
|
@ -243,17 +248,8 @@ class TangemSdkManager(
|
|||
return resources.getString(stringResId, *formatArgs)
|
||||
}
|
||||
|
||||
fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) {
|
||||
tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) {
|
||||
UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
|
||||
} else {
|
||||
UserCodeRequestPolicy.Default
|
||||
}
|
||||
}
|
||||
|
||||
fun useBiometricsForAccessCode(): Boolean {
|
||||
val policy = tangemSdk.config.userCodeRequestPolicy
|
||||
return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode
|
||||
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
|
||||
tangemSdk.config.userCodeRequestPolicy = policy
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ sealed class DetailsAction : Action {
|
|||
sealed class ResetToFactory : DetailsAction() {
|
||||
object Start : ResetToFactory()
|
||||
object Proceed : ResetToFactory()
|
||||
data class Confirm(val confirmed: Boolean) : ResetToFactory()
|
||||
data class AcceptCondition1(val accepted: Boolean) : ResetToFactory()
|
||||
data class AcceptCondition2(val accepted: Boolean) : ResetToFactory()
|
||||
object Failure : ResetToFactory()
|
||||
object Success : ResetToFactory()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
|
@ -103,6 +101,27 @@ class DetailsMiddleware {
|
|||
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)) }
|
||||
|
|
@ -126,6 +145,9 @@ class DetailsMiddleware {
|
|||
Analytics.send(Settings.CardSettings.FactoryResetFinished(error))
|
||||
}
|
||||
}
|
||||
.doOnResult {
|
||||
doAfterErase()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ private fun handlePrepareCardSettingsScreen(
|
|||
manageSecurityState = prepareSecurityOptions(card, cardTypesResolver),
|
||||
card = card,
|
||||
resetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver),
|
||||
resetButtonEnabled = false,
|
||||
condition1Checked = false,
|
||||
condition2Checked = false,
|
||||
accessCodeRecovery = if (cardTypesResolver.isWallet2()) {
|
||||
val enabled = card.userSettings?.isUserCodeRecoveryAllowed ?: false
|
||||
AccessCodeRecoveryState(
|
||||
|
|
@ -145,8 +148,25 @@ private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: Card
|
|||
|
||||
private fun handleEraseWallet(action: DetailsAction.ResetToFactory, state: DetailsState): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.ResetToFactory.Confirm ->
|
||||
state.copy(cardSettingsState = state.cardSettingsState?.copy(resetConfirmed = action.confirmed))
|
||||
is DetailsAction.ResetToFactory.AcceptCondition1 -> {
|
||||
val warning1Checked = action.accepted
|
||||
state.copy(
|
||||
cardSettingsState = state.cardSettingsState?.copy(
|
||||
condition1Checked = action.accepted,
|
||||
resetButtonEnabled = warning1Checked && state.cardSettingsState.condition2Checked,
|
||||
),
|
||||
)
|
||||
}
|
||||
is DetailsAction.ResetToFactory.AcceptCondition2 -> {
|
||||
val warning2Checked = action.accepted
|
||||
state.copy(
|
||||
cardSettingsState = state.cardSettingsState?.copy(
|
||||
condition2Checked = action.accepted,
|
||||
resetButtonEnabled = warning2Checked && state.cardSettingsState.condition1Checked,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ data class CardSettingsState(
|
|||
val card: CardDTO,
|
||||
val manageSecurityState: ManageSecurityState?,
|
||||
val resetCardAllowed: Boolean,
|
||||
val resetConfirmed: Boolean = false,
|
||||
val resetButtonEnabled: Boolean,
|
||||
val condition1Checked: Boolean,
|
||||
val condition2Checked: Boolean,
|
||||
val accessCodeRecovery: AccessCodeRecoveryState? = null,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -33,6 +35,7 @@ internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Uni
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
@Composable
|
||||
private fun ResetCardView(state: ResetCardScreenState) {
|
||||
|
|
@ -76,54 +79,78 @@ private fun ResetCardView(state: ResetCardScreenState) {
|
|||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(28.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = { state.onAcceptWarningToggleClick(!state.accepted) },
|
||||
)
|
||||
.padding(top = 16.dp, bottom = 16.dp),
|
||||
) {
|
||||
IconToggleButton(
|
||||
checked = state.accepted,
|
||||
onCheckedChange = state.onAcceptWarningToggleClick,
|
||||
modifier = Modifier.padding(start = 20.dp, end = 20.dp),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (state.accepted) {
|
||||
R.drawable.ic_accepted
|
||||
} else {
|
||||
R.drawable.ic_unticked
|
||||
},
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = if (state.accepted) {
|
||||
TangemTheme.colors.icon.accent
|
||||
} else {
|
||||
TangemTheme.colors.icon.secondary
|
||||
},
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.reset_card_to_factory_warning_message),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier.padding(end = 20.dp),
|
||||
)
|
||||
}
|
||||
|
||||
ConditionCheckBox(
|
||||
checkedState = state.acceptCondition1Checked,
|
||||
onCheckedChange = state.onAcceptCondition1ToggleClick,
|
||||
description = TextReference.Res(R.string.reset_card_to_factory_condition_1),
|
||||
)
|
||||
|
||||
ConditionCheckBox(
|
||||
checkedState = state.acceptCondition2Checked,
|
||||
onCheckedChange = state.onAcceptCondition2ToggleClick,
|
||||
description = TextReference.Res(R.string.reset_card_to_factory_condition_2),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
Box(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
|
||||
) {
|
||||
DetailsMainButton(
|
||||
title = stringResource(id = R.string.reset_card_to_factory_button_title),
|
||||
onClick = state.onResetButtonClick,
|
||||
enabled = state.resetButtonEnabled,
|
||||
AnimatedContent(
|
||||
targetState = state.resetButtonEnabled,
|
||||
label = "Update checked state",
|
||||
) { buttonEnabled ->
|
||||
DetailsMainButton(
|
||||
title = stringResource(id = R.string.reset_card_to_factory_button_title),
|
||||
onClick = state.onResetButtonClick,
|
||||
enabled = buttonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun ConditionCheckBox(checkedState: Boolean, onCheckedChange: (Boolean) -> Unit, description: TextReference) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = { onCheckedChange.invoke(!checkedState) },
|
||||
)
|
||||
.padding(top = TangemTheme.dimens.size16, bottom = TangemTheme.dimens.size16),
|
||||
) {
|
||||
IconToggleButton(
|
||||
checked = checkedState,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.size20, end = TangemTheme.dimens.size20),
|
||||
) {
|
||||
AnimatedContent(targetState = checkedState, label = "Update checked state") { checked ->
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (checked) {
|
||||
R.drawable.ic_accepted
|
||||
} else {
|
||||
R.drawable.ic_unticked
|
||||
},
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = if (checked) {
|
||||
TangemTheme.colors.icon.accent
|
||||
} else {
|
||||
TangemTheme.colors.icon.secondary
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = description.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier.padding(end = TangemTheme.dimens.size20),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +165,8 @@ private fun ResetCardScreenSample(modifier: Modifier = Modifier) {
|
|||
state = ResetCardScreenState(
|
||||
accepted = true,
|
||||
descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message),
|
||||
onAcceptWarningToggleClick = {},
|
||||
onAcceptCondition1ToggleClick = {},
|
||||
onAcceptCondition2ToggleClick = {},
|
||||
onResetButtonClick = {},
|
||||
),
|
||||
onBackClick = {},
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
|||
internal data class ResetCardScreenState(
|
||||
val accepted: Boolean = false,
|
||||
val descriptionText: TextReference,
|
||||
val onAcceptWarningToggleClick: (Boolean) -> Unit,
|
||||
val acceptCondition1Checked: Boolean = false,
|
||||
val acceptCondition2Checked: Boolean = false,
|
||||
val onAcceptCondition1ToggleClick: (Boolean) -> Unit,
|
||||
val onAcceptCondition2ToggleClick: (Boolean) -> Unit,
|
||||
val onResetButtonClick: () -> Unit,
|
||||
) {
|
||||
val resetButtonEnabled: Boolean
|
||||
|
|
|
|||
|
|
@ -15,9 +15,12 @@ internal class ResetCardViewModel(private val store: Store<AppState>) {
|
|||
?: TextReference.Str(value = "")
|
||||
|
||||
return ResetCardScreenState(
|
||||
accepted = state?.resetConfirmed ?: false,
|
||||
accepted = state?.resetButtonEnabled ?: false,
|
||||
descriptionText = descriptionText,
|
||||
onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) },
|
||||
acceptCondition1Checked = state?.condition1Checked ?: false,
|
||||
acceptCondition2Checked = state?.condition2Checked ?: false,
|
||||
onAcceptCondition1ToggleClick = { store.dispatch(DetailsAction.ResetToFactory.AcceptCondition1(it)) },
|
||||
onAcceptCondition2ToggleClick = { store.dispatch(DetailsAction.ResetToFactory.AcceptCondition2(it)) },
|
||||
onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,7 +351,6 @@
|
|||
</plurals>
|
||||
<string name="registration_task_alert_message">Пожалуйста, удерживайте карту до завершения операции</string>
|
||||
<string name="reset_card_to_factory_button_title">Сбросить карту</string>
|
||||
<string name="reset_card_to_factory_warning_message">Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек.</string>
|
||||
<string name="russian_bank_card_warning_subtitle">У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ?</string>
|
||||
|
|
|
|||
|
|
@ -277,7 +277,6 @@
|
|||
</plurals>
|
||||
<string name="registration_task_alert_message">請持有卡片直至操作完成</string>
|
||||
<string name="reset_card_to_factory_button_title">重置卡片</string>
|
||||
<string name="reset_card_to_factory_warning_message">我了解執行此操作後,我將無法再訪問當前錢包</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包。您將無法恢復當前錢包或使用卡恢復訪問密碼</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包並將其從應用程序中刪除。您將無法恢復當前錢包</string>
|
||||
<string name="russian_bank_card_warning_title">目前不接受俄羅斯銀行卡</string>
|
||||
|
|
|
|||
|
|
@ -371,7 +371,8 @@
|
|||
</plurals>
|
||||
<string name="registration_task_alert_message">Please hold the card until the operation complete</string>
|
||||
<string name="reset_card_to_factory_button_title">Reset the Card</string>
|
||||
<string name="reset_card_to_factory_warning_message">I understand that after performing this action, I will no longer have access to the current wallet</string>
|
||||
<string name="reset_card_to_factory_condition_1">I realize that I\'ll lose access to my funds on this card after this action.</string>
|
||||
<string name="reset_card_to_factory_condition_2">I realize that I can\'t use this card to recover my access code on the other cards.</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet.</string>
|
||||
<string name="russian_bank_card_warning_subtitle">Do you have a bank card from another country and a residence permit or registration outside the Russian Federation?</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue