From cc468b2d0d7ad09ca78b34c3218fb22f9090a953 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 21 Sep 2020 13:51:18 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .idea/dictionaries/romanpotapov.xml | 1 + .../com/tangem/tap/common/entities/Button.kt | 2 +- .../tap/common/extensions/Navigation.kt | 2 + .../redux/navigation/NavigationState.kt | 2 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 29 ++- .../features/details/redux/DetailsAction.kt | 14 ++ .../details/redux/DetailsMiddleware.kt | 165 ++++++++++----- .../features/details/redux/DetailsReducer.kt | 93 +++++++-- .../features/details/redux/DetailsState.kt | 11 + .../details/ui/DetailsConfirmFragment.kt | 15 +- .../features/details/ui/DetailsFragment.kt | 13 ++ .../details/ui/DetailsSecurityFragment.kt | 83 ++++++++ .../features/wallet/redux/WalletMiddleware.kt | 4 +- app/src/main/res/drawable/ic_save.xml | 9 + .../res/layout/fragment_details_confirm.xml | 25 +-- .../res/layout/fragment_details_security.xml | 197 ++++++++++++++++++ app/src/main/res/values/strings.xml | 8 + 17 files changed, 579 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt create mode 100644 app/src/main/res/drawable/ic_save.xml create mode 100644 app/src/main/res/layout/fragment_details_security.xml diff --git a/.idea/dictionaries/romanpotapov.xml b/.idea/dictionaries/romanpotapov.xml index 22eae8bc66..9973a9778a 100644 --- a/.idea/dictionaries/romanpotapov.xml +++ b/.idea/dictionaries/romanpotapov.xml @@ -2,6 +2,7 @@ blockchain + passcode tangem diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt index 14383c3c99..986a170564 100644 --- a/app/src/main/java/com/tangem/tap/common/entities/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt @@ -1,3 +1,3 @@ package com.tangem.tap.common.entities -abstract class Button(val enabled: Boolean) \ No newline at end of file +open class Button(val enabled: Boolean) \ No newline at end of file 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 bd8de9b6be..03246d0461 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 @@ -6,6 +6,7 @@ import androidx.fragment.app.FragmentManager import com.tangem.tap.common.redux.navigation.AppScreen 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.home.HomeFragment import com.tangem.tap.features.send.ui.SendFragment import com.tangem.tap.features.wallet.ui.WalletFragment @@ -40,5 +41,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() AppScreen.DetailsConfirm -> DetailsConfirmFragment() + AppScreen.DetailsSecurity -> DetailsSecurityFragment() } } \ No newline at end of file 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 60dba22fbc..3a062653d8 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 @@ -9,4 +9,4 @@ data class NavigationState( val activity: WeakReference? = null ) : StateType -enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm } \ No newline at end of file +enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 3c9b613d01..38dcfe6358 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -2,11 +2,10 @@ package com.tangem.tap.domain import androidx.activity.ComponentActivity import com.tangem.* -import com.tangem.commands.CommandResponse -import com.tangem.commands.PurgeWalletCommand -import com.tangem.commands.PurgeWalletResponse +import com.tangem.commands.* import com.tangem.common.CompletionResult import com.tangem.common.extensions.CardType +import com.tangem.common.extensions.calculateSha256 import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse @@ -26,12 +25,28 @@ class TangemSdkManager(val activity: ComponentActivity) { return runTaskAsyncReturnOnMain(ScanNoteTask()) } - suspend fun createWallet(): CompletionResult { - return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask()) + suspend fun createWallet(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId) } - suspend fun eraseWallet(): CompletionResult { - return runTaskAsyncReturnOnMain(PurgeWalletCommand()) + suspend fun eraseWallet(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(PurgeWalletCommand(), cardId) + } + + suspend fun setPasscode(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId) + } + + suspend fun setAccessCode(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId) + } + + suspend fun setLongTap(cardId: String?): CompletionResult { + return runTaskAsyncReturnOnMain(SetPinCommand( + pinType = PinType.Pin1, + newPin1 = tangemSdk.config.defaultPin1.calculateSha256(), + newPin2 = tangemSdk.config.defaultPin2.calculateSha256() + ), cardId) } private suspend fun runTaskAsync( 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 bddb0b3d6c..1b3d174176 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 @@ -41,4 +41,18 @@ sealed class DetailsAction : Action { data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName): AppCurrencyAction() } + sealed class ManageSecurity : DetailsAction() { + object OpenSecurity : ManageSecurity() + data class SelectOption(val option: SecurityOption) : ManageSecurity() + object SaveChanges : ManageSecurity() { + object Success : ManageSecurity() + object Failure : ManageSecurity() + } + data class ConfirmSelection(val option: SecurityOption) : ManageSecurity() { + object AlreadySet: ManageSecurity(), NotificationAction { + override val messageResource = R.string.details_notification_security_option_already_active + } + } + } + } \ 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 50a0d1d452..1376dd5398 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 @@ -18,64 +18,127 @@ import kotlinx.coroutines.withContext import org.rekotlin.Middleware class DetailsMiddleware { + private val eraseWalletMiddleware = EraseWalletMiddleware() + private val appCurrencyMiddleware = AppCurrencyMiddleware() + private val manageSecurityMiddleware = ManageSecurityMiddleware() val detailsMiddleware: Middleware = { dispatch, state -> { next -> { action -> when (action) { - is DetailsAction.PrepareScreen -> { - scope.launch { - val loadedCurrencies = preferencesStorage.getFiatCurrencies() - if (loadedCurrencies.isNullOrEmpty()) { - val response = CoinMarketCapService().getFiatCurrencies() - withContext(Dispatchers.Main) { - when (response) { - is Result.Success -> { - preferencesStorage.saveFiatCurrencies(response.data) - store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(response.data)) - } - } - } - } else { - withContext(Dispatchers.Main) { - store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(loadedCurrencies)) - } - } - } - - } - is DetailsAction.EraseWallet.Proceed -> { - when (store.state.detailsState.eraseWalletState) { - EraseWalletState.Allowed -> - store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) - EraseWalletState.NotAllowedByCard -> - store.dispatch(DetailsAction.EraseWallet.Proceed.NotAllowedByCard) - EraseWalletState.NotEmpty -> - store.dispatch(DetailsAction.EraseWallet.Proceed.NotEmpty) - } - } - is DetailsAction.EraseWallet.Cancel -> { - store.dispatch(NavigationAction.PopBackTo()) - } - is DetailsAction.EraseWallet.Confirm -> { - scope.launch { - val result = tangemSdkManager.eraseWallet() - withContext(Dispatchers.Main) { - when (result) { - is CompletionResult.Success -> { - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - } - } - } - } - } - is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { - preferencesStorage.saveAppCurrency(action.fiatCurrencyName) - store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName)) - store.dispatch(WalletAction.LoadFiatRate) - } + is DetailsAction.PrepareScreen -> prepareData() + is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action) + is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) + is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) } next(action) } } } + + private fun prepareData() { + scope.launch { + val loadedCurrencies = preferencesStorage.getFiatCurrencies() + if (loadedCurrencies.isNullOrEmpty()) { + val response = CoinMarketCapService().getFiatCurrencies() + withContext(Dispatchers.Main) { + when (response) { + is Result.Success -> { + preferencesStorage.saveFiatCurrencies(response.data) + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(response.data)) + } + } + } + } else { + withContext(Dispatchers.Main) { + store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(loadedCurrencies)) + } + } + } + } + + class EraseWalletMiddleware() { + fun handle(action: DetailsAction.EraseWallet) { + when (action) { + is DetailsAction.EraseWallet.Proceed -> { + when (store.state.detailsState.eraseWalletState) { + EraseWalletState.Allowed -> + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + EraseWalletState.NotAllowedByCard -> + store.dispatch(DetailsAction.EraseWallet.Proceed.NotAllowedByCard) + EraseWalletState.NotEmpty -> + store.dispatch(DetailsAction.EraseWallet.Proceed.NotEmpty) + } + } + is DetailsAction.EraseWallet.Cancel -> { + store.dispatch(NavigationAction.PopBackTo()) + } + is DetailsAction.EraseWallet.Confirm -> { + scope.launch { + val result = tangemSdkManager.eraseWallet( + store.state.detailsState.card?.cardId + ) + withContext(Dispatchers.Main) { + when (result) { + is CompletionResult.Success -> { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + } + } + } + } + } + } + + class AppCurrencyMiddleware { + fun handle(action: DetailsAction.AppCurrencyAction) { + when (action) { + is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { + preferencesStorage.saveAppCurrency(action.fiatCurrencyName) + store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName)) + store.dispatch(WalletAction.LoadFiatRate) + } + } + } + } + + class ManageSecurityMiddleware { + fun handle(action: DetailsAction.ManageSecurity) { + when (action) { + is DetailsAction.ManageSecurity.OpenSecurity -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity)) + } + is DetailsAction.ManageSecurity.ConfirmSelection -> { + if (action.option != store.state.detailsState.securityScreenState?.currentOption) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + } else { + store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection.AlreadySet) + } + } + is DetailsAction.ManageSecurity.SaveChanges -> { + val cardId = store.state.detailsState.card?.cardId + scope.launch { + val result = when (store.state.detailsState.securityScreenState?.selectedOption) { + SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) + SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) + SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) + else -> null + } + withContext(Dispatchers.Main) { + when (result) { + is CompletionResult.Success -> { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) + } + is CompletionResult.Failure, null -> + store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) + } + } + + } + + } + } + } + } } 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 9cc2fbb831..0f9cbff975 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 @@ -5,6 +5,7 @@ import com.tangem.commands.Settings import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState import org.rekotlin.Action +import java.util.* class DetailsReducer { companion object { @@ -13,28 +14,45 @@ class DetailsReducer { } private fun internalReduce(action: Action, state: AppState): DetailsState { - if (action !is DetailsAction) return state.detailsState - var detailsState = state.detailsState - when (action) { + val detailsState = state.detailsState + return when (action) { is DetailsAction.PrepareScreen -> { - detailsState = DetailsState( - card = action.card, wallet = action.wallet, - cardInfo = action.card.toCardInfo(), - appCurrencyState = AppCurrencyState( - action.fiatCurrencyName - ) - ) + handlePrepareScreen(action, detailsState) } is DetailsAction.EraseWallet -> { - detailsState = handleEraseWallet(action, detailsState) + handleEraseWallet(action, detailsState) } is DetailsAction.AppCurrencyAction -> { - detailsState = handleAppCurrencyAction(action, detailsState) + handleAppCurrencyAction(action, detailsState) + } + is DetailsAction.ManageSecurity -> { + handleSecurityAction(action, detailsState) } } - return detailsState +} + +private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState { + val securityOption = when { + action.card.isPin1Default == false -> { + SecurityOption.AccessCode + } + action.card.isPin2Default == false -> { + SecurityOption.PassCode + } + else -> { + SecurityOption.LongTap + } + } + return DetailsState( + card = action.card, wallet = action.wallet, + cardInfo = action.card.toCardInfo(), + appCurrencyState = AppCurrencyState( + action.fiatCurrencyName + ), + securityScreenState = SecurityScreenState(currentOption = securityOption) + ) } private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState { @@ -88,6 +106,55 @@ private fun handleAppCurrencyAction( } } +private fun handleSecurityAction( + action: DetailsAction.ManageSecurity, state: DetailsState +): DetailsState { + return when (action) { + is DetailsAction.ManageSecurity.OpenSecurity -> { + val prohibitDefaultPin = state.card?.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true + val allowSetPin1 = state.card?.settingsMask?.contains(Settings.AllowSetPIN1) != false + val allowSetPin2 = state.card?.settingsMask?.contains(Settings.AllowSetPIN2) != false + val isDefaultPin1 = state.card?.isPin1Default != false + val isDefaultPin2 = state.card?.isPin2Default != false + + val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java) + if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) { + allowedSecurityOptions.add(SecurityOption.LongTap) + } + if (allowSetPin1 && (isDefaultPin2 || !prohibitDefaultPin)) { + allowedSecurityOptions.add(SecurityOption.AccessCode) + } + if (allowSetPin2 && (isDefaultPin1 || !prohibitDefaultPin)) { + allowedSecurityOptions.add(SecurityOption.AccessCode) + } + + state.copy(securityScreenState = state.securityScreenState?.copy( + allowedOptions = allowedSecurityOptions + )) + } + is DetailsAction.ManageSecurity.SelectOption -> { + state.copy(securityScreenState = state.securityScreenState?.copy( + selectedOption = action.option + )) + } + is DetailsAction.ManageSecurity.ConfirmSelection -> { + val confirmScreenState = when (action.option) { + SecurityOption.LongTap -> ConfirmScreenState.LongTap + SecurityOption.PassCode -> ConfirmScreenState.PassCode + SecurityOption.AccessCode -> ConfirmScreenState.AccessCode + } + state.copy(confirmScreenState = confirmScreenState) + } + is DetailsAction.ManageSecurity.SaveChanges.Success -> { + state.copy(securityScreenState = state.securityScreenState?.copy( + currentOption = state.securityScreenState.selectedOption + )) + } + + else -> state + } +} + private fun Card.toCardInfo(): CardInfo? { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.cardData?.issuerName ?: return null 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 304e7902c8..7c152c57ee 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 @@ -2,10 +2,12 @@ package com.tangem.tap.features.details.redux import com.tangem.blockchain.common.Wallet import com.tangem.commands.Card +import com.tangem.tap.common.entities.Button import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.network.coinmarketcap.FiatCurrency import org.rekotlin.StateType +import java.util.* data class DetailsState( val card: Card? = null, @@ -14,6 +16,7 @@ data class DetailsState( val appCurrencyState: AppCurrencyState = AppCurrencyState(), val eraseWalletState: EraseWalletState? = null, val confirmScreenState: ConfirmScreenState? = null, + val securityScreenState: SecurityScreenState? = null, ) : StateType data class CardInfo( @@ -24,6 +27,14 @@ data class CardInfo( enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty } enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode } +data class SecurityScreenState( + val currentOption: SecurityOption = SecurityOption.LongTap, + val selectedOption: SecurityOption = currentOption, + val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java), + val buttonProceed: Button = Button(true) +) + +enum class SecurityOption { LongTap, PassCode, AccessCode } data class AppCurrencyState( val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, val showAppCurrencyDialog: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt index a13937adbf..d75799b5c1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt @@ -59,16 +59,23 @@ class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm), when (state.confirmScreenState) { ConfirmScreenState.EraseWallet -> { toolbar.title = getString(R.string.details_erase_wallet) + tv_warning_description.text = getString(R.string.details_erase_wallet_warning) btn_confirm.text = getString(R.string.details_erase_wallet) btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds( null, null, getDrawable(R.drawable.ic_send), null ) btn_confirm.setOnClickListener { store.dispatch(DetailsAction.EraseWallet.Confirm) } } - ConfirmScreenState.LongTap -> TODO() - ConfirmScreenState.AccessCode -> TODO() - ConfirmScreenState.PassCode -> TODO() - null -> TODO() + ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode, + ConfirmScreenState.PassCode -> { + toolbar.title = getString(R.string.details_manage_security) + tv_warning_description.text = getString(R.string.details_manage_security_warning) + btn_confirm.text = getString(R.string.details_save_changes) + btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds( + null, null, getDrawable(R.drawable.ic_save), null + ) + btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) } + } } 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 eeca408c37..f4bdf9519a 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 @@ -8,6 +8,7 @@ import androidx.transition.TransitionInflater import com.tangem.tap.common.redux.navigation.NavigationAction 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.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_details.* @@ -74,6 +75,18 @@ 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 { tv_security.text = getString(it) } + if (state.appCurrencyState.showAppCurrencyDialog && !state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) { currencySelectionDialog.show( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt new file mode 100644 index 0000000000..60050df949 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt @@ -0,0 +1,83 @@ +package com.tangem.tap.features.details.ui + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.redux.navigation.NavigationAction +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.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_details_confirm.toolbar +import kotlinx.android.synthetic.main.fragment_details_security.* +import org.rekotlin.StoreSubscriber + +class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security), + StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + 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 onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + setOnClickListeners() + } + + private fun setOnClickListeners() { + v_long_tap.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.LongTap)) + } + v_passcode.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.PassCode)) + } + v_access_code.setOnClickListener { + store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.AccessCode)) + } + btn_save_changes.setOnClickListener { + store.state.detailsState.securityScreenState?.selectedOption?.let { + store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection(it)) + } + } + } + + override fun newState(state: DetailsState) { + if (activity == null) return + selectSecurityOption(state.securityScreenState?.selectedOption) + } + + private fun selectSecurityOption(securityOption: SecurityOption?) { + radiobutton_long_tap.isChecked = securityOption == SecurityOption.LongTap + radiobutton_passcode.isChecked = securityOption == SecurityOption.PassCode + radiobutton_access_code.isChecked = securityOption == SecurityOption.AccessCode + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 6c9f2b85bd..f6354b7291 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -63,7 +63,9 @@ val walletMiddleware: Middleware = { dispatch, state -> } is WalletAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createWallet() + val result = tangemSdkManager.createWallet( + store.state.globalState.scanNoteResponse?.card?.cardId + ) when (result) { is CompletionResult.Success -> { store.state.globalState.tapWalletManager.onCardScanned(result.data) diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 0000000000..756c7557dc --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_details_confirm.xml b/app/src/main/res/layout/fragment_details_confirm.xml index 528dc0aa4c..8aaec159c0 100644 --- a/app/src/main/res/layout/fragment_details_confirm.xml +++ b/app/src/main/res/layout/fragment_details_confirm.xml @@ -68,34 +68,27 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> - + app:layout_constraintGuide_percent="0.37" /> + app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/fragment_details_security.xml b/app/src/main/res/layout/fragment_details_security.xml new file mode 100644 index 0000000000..e645ea6b3b --- /dev/null +++ b/app/src/main/res/layout/fragment_details_security.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 22a73a85d7..5ebb9b3051 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,6 +86,7 @@ Details Card settings prohibits from erasing wallet You balance on this wallet is not zero, or you have unconfirmed transactions + This is the currently active option Card ID Issuer Signed @@ -95,6 +96,13 @@ Validate card Manage security Erase wallet + Save changes + Long Tap + Passcode + Access Code + This action is irreversible. If, after deleting the wallet, someone sends funds to it, then you will not be able to withdraw them. + If you forget the code, you will lose the ability to use the card. There is no way to recover or change your code if you lose it. + \ No newline at end of file From d96086f4485e58650ffc8b7f75e6b8c6f73f591d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Sep 2020 10:52:52 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../tap/features/details/redux/DetailsMiddleware.kt | 13 ++++++++++--- .../main/res/layout/fragment_details_security.xml | 6 +++--- app/src/main/res/values/strings.xml | 3 +++ 3 files changed, 16 insertions(+), 6 deletions(-) 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 1376dd5398..b70f14c6d5 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 @@ -110,15 +110,20 @@ class DetailsMiddleware { } is DetailsAction.ManageSecurity.ConfirmSelection -> { if (action.option != store.state.detailsState.securityScreenState?.currentOption) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + if (action.option == SecurityOption.LongTap) { + store.dispatch(DetailsAction.ManageSecurity.SaveChanges) + } else { + store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm)) + } } else { store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection.AlreadySet) } } is DetailsAction.ManageSecurity.SaveChanges -> { val cardId = store.state.detailsState.card?.cardId + val selectedOption = store.state.detailsState.securityScreenState?.selectedOption scope.launch { - val result = when (store.state.detailsState.securityScreenState?.selectedOption) { + val result = when (selectedOption) { SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) @@ -127,7 +132,9 @@ class DetailsMiddleware { withContext(Dispatchers.Main) { when (result) { is CompletionResult.Success -> { - store.dispatch(NavigationAction.PopBackTo()) + if (selectedOption != SecurityOption.LongTap) { + store.dispatch(NavigationAction.PopBackTo()) + } store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) } is CompletionResult.Failure, null -> diff --git a/app/src/main/res/layout/fragment_details_security.xml b/app/src/main/res/layout/fragment_details_security.xml index e645ea6b3b..11d1ea1b4c 100644 --- a/app/src/main/res/layout/fragment_details_security.xml +++ b/app/src/main/res/layout/fragment_details_security.xml @@ -64,7 +64,7 @@ android:paddingEnd="60dp" android:textColor="@color/darkGray1" app:layout_constraintTop_toBottomOf="@id/tv_long_tap_title" - android:text="Caution: When having the switch on, all data will be encrypted and kept from being stolen." + android:text="@string/details_manage_security_long_tap_description" android:textSize="13sp" /> @@ -109,7 +109,7 @@ android:paddingEnd="60dp" android:textColor="@color/darkGray1" app:layout_constraintTop_toBottomOf="@id/tv_passcode_title" - android:text="Caution: When having the switch on, all data will be encrypted and kept from being stolen." + android:text="@string/details_manage_security_passcode_description" android:textSize="13sp" /> @@ -155,7 +155,7 @@ android:paddingEnd="60dp" android:textColor="@color/darkGray1" app:layout_constraintTop_toBottomOf="@id/tv_access_code_title" - android:text="Caution: When having the switch on, all data will be encrypted and kept from being stolen." + android:text="@string/details_manage_security_access_code_description" android:textSize="13sp" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5ebb9b3051..b75584ec73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,6 +100,9 @@ Long Tap Passcode Access Code + This mechanism protects against proximity attack on a card. It will enforce a delay between reception and execution of a command. After the first signed transaction this phone will be linked with the card and transactions will be signed immediately. + Before execution of any command entailing a change of the card state you will have to enter the passcode. + You will have to submit the correct access code before scanning the card. This action is irreversible. If, after deleting the wallet, someone sends funds to it, then you will not be able to withdraw them. If you forget the code, you will lose the ability to use the card. There is no way to recover or change your code if you lose it.