diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index dd24af74c5..afe0c1863e 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -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 { 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() diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 89d9327ed6..26ec96a604 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -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() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 1fc6832148..76104a269a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index c37a625c54..44c78b5f4f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index efed01f9c3..93511b1bf6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -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 = { _, _ -> { 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 { } } } -} \ No newline at end of file + + 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)) + } + } + } + } + } +} diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 952e2bb317..963080bc82 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index ee6fd2bb7e..00b73ae10b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -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 } \ No newline at end of file +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 +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 2789b0069d..4e751b5ce1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -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 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) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securityprivacy/ChangeAccessCodeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securityprivacy/ChangeAccessCodeFragment.kt new file mode 100644 index 0000000000..2149080c0f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securityprivacy/ChangeAccessCodeFragment.kt @@ -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 { + + + 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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securityprivacy/SecurityAndPrivacyFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securityprivacy/SecurityAndPrivacyFragment.kt new file mode 100644 index 0000000000..d859f6cfff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securityprivacy/SecurityAndPrivacyFragment.kt @@ -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 { + + 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) + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 58ac038846..2a09aecc77 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -133,39 +133,20 @@ app:layout_constraintTop_toBottomOf="@id/tv_issuer" tools:text="48 hashes" /> - - - - - - + android:text="@string/security_and_privacy_title" + android:textColor="@color/darkGray6" + android:textSize="16sp" + app:drawableEndCompat="@drawable/ic_angle_bracket_right" + app:drawableTint="@color/darkGray1" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv_signed_hashes_title"/> + app:layout_constraintTop_toBottomOf="@id/tv_security_and_privacy" /> + app:title="@string/security_and_privacy_access_method_title" /> diff --git a/app/src/main/res/layout/fragment_details_security_privacy.xml b/app/src/main/res/layout/fragment_details_security_privacy.xml new file mode 100644 index 0000000000..9233e2a675 --- /dev/null +++ b/app/src/main/res/layout/fragment_details_security_privacy.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 4eae54e057..97ba0b8f36 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -60,4 +60,13 @@ %s network not found. Please, add it first and try again. %s network + + Security & Privacy + Security + Privacy + Change access code + Change access method + Change Access Method + Save cards + Save passwords \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 158ab46c65..5f6407ce70 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -60,4 +60,13 @@ %s network not found. Please, add it first and try again. %s network + + Security & Privacy + Security + Privacy + Change access code + Change access method + Change Access Method + Save cards + Save passwords \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 158ab46c65..5f6407ce70 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -60,4 +60,13 @@ %s network not found. Please, add it first and try again. %s network + + Security & Privacy + Security + Privacy + Change access code + Change access method + Change Access Method + Save cards + Save passwords \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 0370067579..051e40e9cc 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -68,4 +68,13 @@ Бэкап кошелька не был произведен Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру + + Безопасность и приватность + Безопасность + Приватность + Изменить код доступа + Изменить защиту доступа + Изменить защиту доступа + Сохранять карты + Сохранять пароли diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 25bf984bbe..7bffbf8513 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -70,4 +70,14 @@ To protect your assets, we advise you to carry out this procedure Remove token + Security & Privacy + Security + Privacy + Change access code + Change access method + Change Access Method + Save cards + Save passwords + При отключении данной опции все сохраненные карты и пароли будут сброшены, все данные удалены. Для входа в приложение понадобится прикладывать карту. + Все сохраненные пароли от карт будут удалены. Для проведения операций с картой вам необходимо будет вводить пароль.