Updated on 2026-08-14

This commit is contained in:
Tangem 2022-07-06 18:07:27 +04:00
parent 7a202ea6dc
commit a0e31b3412
18 changed files with 448 additions and 53 deletions

View file

@ -6,6 +6,8 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.ui.SimpleAlertDialog
import com.tangem.tap.common.ui.SimpleCancelableAlertDialog
import com.tangem.tap.features.details.redux.DetailsDialog
import com.tangem.tap.features.details.redux.PrivacySetting
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
@ -131,6 +133,18 @@ class DialogManager : StoreSubscriber<GlobalState> {
primaryButtonRes = state.dialog.primaryButtonRes,
primaryButtonAction = state.dialog.onOk
)
is DetailsDialog.ConfirmDisablingSaving -> {
val messageRes = when (state.dialog.setting) {
PrivacySetting.SAVE_CARDS -> R.string.security_and_privacy_save_card_disable_warning
PrivacySetting.SAVE_ACCESS_CODE -> R.string.security_and_privacy_save_password_disable_warning
}
SimpleCancelableAlertDialog.create(
titleRes = R.string.common_warning,
messageRes = messageRes,
context = context,
primaryButtonAction = state.dialog.onOk
)
}
else -> null
}
dialog?.show()

View file

@ -10,6 +10,8 @@ import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.details.ui.DetailsConfirmFragment
import com.tangem.tap.features.details.ui.DetailsFragment
import com.tangem.tap.features.details.ui.DetailsSecurityFragment
import com.tangem.tap.features.details.ui.securityprivacy.ChangeAccessCodeFragment
import com.tangem.tap.features.details.ui.securityprivacy.SecurityAndPrivacyFragment
import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
@ -90,6 +92,8 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.Details -> DetailsFragment()
AppScreen.DetailsConfirm -> DetailsConfirmFragment()
AppScreen.DetailsSecurity -> DetailsSecurityFragment()
AppScreen.SecurityAndPrivacy -> SecurityAndPrivacyFragment()
AppScreen.ChangeAccessCode -> ChangeAccessCodeFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.AddTokens -> AddTokensFragment()
AppScreen.AddCustomToken -> AddCustomTokenFragment()

View file

@ -16,7 +16,7 @@ enum class AppScreen {
OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
Wallet, WalletDetails,
Send,
Details, DetailsConfirm, DetailsSecurity,
Details, DetailsConfirm, DetailsSecurity, SecurityAndPrivacy, ChangeAccessCode,
AddTokens, AddCustomToken,
WalletConnectSessions,
QrScan

View file

@ -59,5 +59,13 @@ sealed class DetailsAction : Action {
}
}
sealed class ManagePrivacy : DetailsAction() {
data class SwitchPrivacySetting(val allow: Boolean, val setting: PrivacySetting) :
ManagePrivacy()
data class ConfirmSwitchingSetting(val allow: Boolean, val setting: PrivacySetting) :
ManagePrivacy()
}
data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction()
}

View file

@ -6,6 +6,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@ -28,24 +29,28 @@ import org.rekotlin.Middleware
class DetailsMiddleware {
private val eraseWalletMiddleware = EraseWalletMiddleware()
private val manageSecurityMiddleware = ManageSecurityMiddleware()
private val managePrivacyMiddleware = ManagePrivacyMiddleware()
val detailsMiddleware: Middleware<AppState> = { _, _ ->
{ next ->
{ action ->
when (action) {
is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
is DetailsAction.ManagePrivacy -> managePrivacyMiddleware.handle(action)
is DetailsAction.ShowDisclaimer -> {
store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
}
is DetailsAction.ReCreateTwinsWallet -> {
val wallet = store.state.walletState.walletManagers.map { it.wallet }.firstOrNull()
val wallet =
store.state.walletState.walletManagers.map { it.wallet }.firstOrNull()
if (wallet == null) {
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
} else {
if (wallet.hasSendableAmountsOrPendingTransactions()) {
val walletIsNotEmpty = store.state.globalState.resources.strings.walletIsNotEmpty
val walletIsNotEmpty =
store.state.globalState.resources.strings.walletIsNotEmpty
store.dispatchNotification(walletIsNotEmpty)
} else {
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
@ -56,7 +61,12 @@ class DetailsMiddleware {
is DetailsAction.CreateBackup -> {
store.state.detailsState.scanResponse?.let {
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false))
store.dispatch(
GlobalAction.Onboarding.Start(
it,
fromHomeScreen = false
)
)
}
}
}
@ -121,13 +131,22 @@ class DetailsMiddleware {
val simulatedResponse = CheckUserCodesResponse(
action.card.isAccessCodeSet, action.card.isPasscodeSet ?: false
)
store.dispatch(DetailsAction.ManageSecurity.SetCurrentOption(simulatedResponse))
store.dispatch(
DetailsAction.ManageSecurity.SetCurrentOption(
simulatedResponse
)
)
store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
} else {
scope.launch {
when (val response = tangemSdkManager.checkUserCodes(action.card.cardId)) {
when (val response =
tangemSdkManager.checkUserCodes(action.card.cardId)) {
is CompletionResult.Success -> {
store.dispatchOnMain(DetailsAction.ManageSecurity.SetCurrentOption(response.data))
store.dispatchOnMain(
DetailsAction.ManageSecurity.SetCurrentOption(
response.data
)
)
store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity)
}
is CompletionResult.Failure -> {
@ -152,7 +171,8 @@ class DetailsMiddleware {
}
is DetailsAction.ManageSecurity.SaveChanges -> {
val cardId = store.state.detailsState.scanResponse?.card?.cardId
val selectedOption = store.state.detailsState.securityScreenState?.selectedOption
val selectedOption =
store.state.detailsState.securityScreenState?.selectedOption
scope.launch {
val result = when (selectedOption) {
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
@ -196,4 +216,26 @@ class DetailsMiddleware {
}
}
}
}
class ManagePrivacyMiddleware {
fun handle(action: DetailsAction.ManagePrivacy) {
when (action) {
is DetailsAction.ManagePrivacy.ConfirmSwitchingSetting -> {
// TODO()
}
is DetailsAction.ManagePrivacy.SwitchPrivacySetting -> {
if (action.allow) {
store.dispatch(
DetailsAction.ManagePrivacy.ConfirmSwitchingSetting(
action.allow,
action.setting
)
)
} else {
store.dispatchDialogShow(DetailsDialog.ConfirmDisablingSaving(action.setting))
}
}
}
}
}
}

View file

@ -9,8 +9,8 @@ import com.tangem.tap.domain.extensions.isWalletDataSupported
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
import com.tangem.tap.store
import java.util.EnumSet
import org.rekotlin.Action
import java.util.*
class DetailsReducer {
companion object {
@ -32,6 +32,9 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
is DetailsAction.ManageSecurity -> {
handleSecurityAction(action, detailsState)
}
is DetailsAction.ManagePrivacy -> {
handlePrivacyAction(action, detailsState)
}
is DetailsAction.ChangeAppCurrency ->
detailsState.copy(appCurrency = action.fiatCurrency)
@ -154,6 +157,20 @@ private fun handleSecurityAction(
}
}
private fun handlePrivacyAction(
action: DetailsAction.ManagePrivacy, state: DetailsState,
): DetailsState {
return when (action) {
is DetailsAction.ManagePrivacy.ConfirmSwitchingSetting -> {
when (action.setting) {
PrivacySetting.SAVE_CARDS -> state.copy(saveCards = action.allow)
PrivacySetting.SAVE_ACCESS_CODE -> state.copy(savePasswords = action.allow)
}
}
is DetailsAction.ManagePrivacy.SwitchPrivacySetting -> state
}
}
private fun prepareAllowedSecurityOptions(
card: Card?, currentSecurityOption: SecurityOption?,
): EnumSet<SecurityOption> {

View file

@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.store
import org.rekotlin.StateType
@ -20,7 +21,9 @@ data class DetailsState(
val securityScreenState: SecurityScreenState? = null,
val cardTermsOfUseUrl: Uri? = null,
val createBackupAllowed: Boolean = false,
val appCurrency: FiatCurrency = FiatCurrency.Default
val appCurrency: FiatCurrency = FiatCurrency.Default,
val saveCards: Boolean = true,
val savePasswords: Boolean = true
) : StateType {
// if you do not delegate - the application crashes on startup,
@ -48,4 +51,16 @@ data class SecurityScreenState(
val buttonProceed: Button = Button(true),
)
enum class SecurityOption { LongTap, PassCode, AccessCode }
enum class SecurityOption { LongTap, PassCode, AccessCode }
sealed interface DetailsDialog : StateDialog {
data class ConfirmDisablingSaving(val setting: PrivacySetting) : DetailsDialog {
val onOk: () -> Unit =
{ store.dispatch(DetailsAction.ManagePrivacy.ConfirmSwitchingSetting(false, setting)) }
}
}
enum class PrivacySetting {
SAVE_CARDS, SAVE_ACCESS_CODE
}

View file

@ -15,7 +15,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
@ -132,17 +131,10 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
}
llManageSecurity.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.CheckCurrentSecurityOption(state.scanResponse!!.card))
tvSecurityAndPrivacy.setOnClickListener {
store.dispatch(NavigationAction.NavigateTo(AppScreen.SecurityAndPrivacy))
}
val currentSecurity = when (state.securityScreenState?.currentOption) {
SecurityOption.LongTap -> R.string.details_manage_security_long_tap
SecurityOption.PassCode -> R.string.details_manage_security_passcode
SecurityOption.AccessCode -> R.string.details_manage_security_access_code
null -> null
}
currentSecurity?.let { tvSecurity.text = getString(it) }
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tap.features.details.ui.securityprivacy
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
class ChangeAccessCodeFragment : BaseStoreFragment(R.layout.view_compose_fragment),
StoreSubscriber<DetailsState> {
override fun newState(state: DetailsState) {
}
override fun subscribeToStore() {
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.detailsState == newState.detailsState
}.select { it.detailsState }
}
storeSubscribersList.add(this)
}
}

View file

@ -0,0 +1,86 @@
package com.tangem.tap.features.details.ui.securityprivacy
import android.os.Bundle
import android.view.View
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.PrivacySetting
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentDetailsSecurityPrivacyBinding
import org.rekotlin.StoreSubscriber
class SecurityAndPrivacyFragment : BaseStoreFragment(R.layout.fragment_details_security_privacy),
StoreSubscriber<DetailsState> {
private val binding: FragmentDetailsSecurityPrivacyBinding
by viewBinding(FragmentDetailsSecurityPrivacyBinding::bind)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
tvAccessCode.setOnClickListener {
store.dispatch(NavigationAction.NavigateTo(AppScreen.ChangeAccessCode))
}
switchSaveCards.setOnCheckedChangeListener { _, isChecked ->
store.dispatch(
DetailsAction.ManagePrivacy.SwitchPrivacySetting(
isChecked,
PrivacySetting.SAVE_CARDS
)
)
}
switchSavePasswords.setOnCheckedChangeListener { _, isChecked ->
store.dispatch(
DetailsAction.ManagePrivacy.SwitchPrivacySetting(
isChecked,
PrivacySetting.SAVE_ACCESS_CODE
)
)
}
}
}
override fun newState(state: DetailsState) {
with(binding) {
llAccessManagement.setOnClickListener {
store.dispatch(
DetailsAction.ManageSecurity.CheckCurrentSecurityOption(
state.scanResponse!!.card
)
)
}
val currentSecurity = when (state.securityScreenState?.currentOption) {
SecurityOption.LongTap -> R.string.details_manage_security_long_tap
SecurityOption.PassCode -> R.string.details_manage_security_passcode
SecurityOption.AccessCode -> R.string.details_manage_security_access_code
null -> null
}
currentSecurity?.let { tvAccessManagement.text = getString(it) }
switchSaveCards.isChecked = state.saveCards
switchSavePasswords.isChecked = state.savePasswords
}
}
override fun subscribeToStore() {
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.detailsState == newState.detailsState
}.select { it.detailsState }
}
storeSubscribersList.add(this)
}
}