Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-21 13:51:18 +03:00
parent 64af848bd9
commit cc468b2d0d
17 changed files with 579 additions and 94 deletions

View file

@ -2,6 +2,7 @@
<dictionary name="romanpotapov">
<words>
<w>blockchain</w>
<w>passcode</w>
<w>tangem</w>
</words>
</dictionary>

View file

@ -1,3 +1,3 @@
package com.tangem.tap.common.entities
abstract class Button(val enabled: Boolean)
open class Button(val enabled: Boolean)

View file

@ -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()
}
}

View file

@ -9,4 +9,4 @@ data class NavigationState(
val activity: WeakReference<FragmentActivity>? = null
) : StateType
enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm }
enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity }

View file

@ -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<ScanNoteResponse> {
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask())
suspend fun createWallet(cardId: String?): CompletionResult<ScanNoteResponse> {
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId)
}
suspend fun eraseWallet(): CompletionResult<PurgeWalletResponse> {
return runTaskAsyncReturnOnMain(PurgeWalletCommand())
suspend fun eraseWallet(cardId: String?): CompletionResult<PurgeWalletResponse> {
return runTaskAsyncReturnOnMain(PurgeWalletCommand(), cardId)
}
suspend fun setPasscode(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId)
}
suspend fun setAccessCode(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId)
}
suspend fun setLongTap(cardId: String?): CompletionResult<SetPinResponse> {
return runTaskAsyncReturnOnMain(SetPinCommand(
pinType = PinType.Pin1,
newPin1 = tangemSdk.config.defaultPin1.calculateSha256(),
newPin2 = tangemSdk.config.defaultPin2.calculateSha256()
), cardId)
}
private suspend fun <T : CommandResponse> runTaskAsync(

View file

@ -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
}
}
}
}

View file

@ -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<AppState> = { 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)
}
}
}
}
}
}
}
}

View file

@ -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

View file

@ -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<SecurityOption> = 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,

View file

@ -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) }
}
}

View file

@ -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<Det
store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency)
}
tv_security_title.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
}
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 { tv_security.text = getString(it) }
if (state.appCurrencyState.showAppCurrencyDialog &&
!state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) {
currencySelectionDialog.show(

View file

@ -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<DetailsState> {
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
}
}

View file

@ -63,7 +63,9 @@ val walletMiddleware: Middleware<AppState> = { 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)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20.7825,6.9675L17.0325,3.2175C16.9624,3.148 16.8793,3.093 16.7879,3.0557C16.6966,3.0184 16.5987,2.9994 16.5,3H4.5C4.1022,3 3.7206,3.1581 3.4393,3.4393C3.158,3.7207 3,4.1022 3,4.5V19.5C3,19.8978 3.158,20.2794 3.4393,20.5607C3.7206,20.842 4.1022,21 4.5,21H19.5C19.8978,21 20.2794,20.842 20.5607,20.5607C20.842,20.2794 21,19.8978 21,19.5V7.5C21.0006,7.4013 20.9817,7.3035 20.9443,7.2121C20.907,7.1207 20.852,7.0376 20.7825,6.9675ZM9,4.5H15V7.5H9V4.5ZM15,19.5H9V13.5H15V19.5ZM16.5,19.5V13.5C16.5,13.1022 16.342,12.7207 16.0607,12.4394C15.7794,12.158 15.3978,12 15,12H9C8.6022,12 8.2206,12.158 7.9393,12.4394C7.658,12.7207 7.5,13.1022 7.5,13.5V19.5H4.5V4.5H7.5V7.5C7.5,7.8978 7.658,8.2794 7.9393,8.5607C8.2206,8.842 8.6022,9 9,9H15C15.3978,9 15.7794,8.842 16.0607,8.5607C16.342,8.2794 16.5,7.8978 16.5,7.5V4.8075L19.5,7.8075V19.5H16.5Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -68,34 +68,27 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Space
android:id="@+id/space"
android:layout_width="93dp"
android:layout_height="48dp"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_confirm"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1" />
app:layout_constraintGuide_percent="0.37" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_confirm"
style="@style/TapBlackButton"
android:layout_width="200dp"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="7dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="30dp"
tools:text="@string/wallet_button_send"
tools:icon="@drawable/ic_send"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/space"
app:layout_constraintVertical_bias="1" />
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,197 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_details_confirm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLightGray"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:fitsSystemWindows="true"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/details_manage_security"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_details_confirm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="33dp"
android:layout_marginTop="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:id="@+id/tv_long_tap_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
android:text="@string/details_manage_security_long_tap"
android:textSize="16sp"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radiobutton_long_tap"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tv_long_tap_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="8dp"
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:textSize="13sp"
/>
<View
android:id="@+id/v_long_tap"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/tv_long_tap_title"
app:layout_constraintBottom_toBottomOf="@id/tv_long_tap_description"/>
<TextView
android:id="@+id/tv_passcode_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/v_long_tap"
android:text="@string/details_manage_security_passcode"
android:textSize="16sp"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radiobutton_passcode"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/v_passcode"/>
<TextView
android:id="@+id/tv_passcode_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="8dp"
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:textSize="13sp"
/>
<View
android:id="@+id/v_passcode"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/tv_passcode_title"
app:layout_constraintBottom_toBottomOf="@id/tv_passcode_description"/>
<TextView
android:id="@+id/tv_access_code_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/v_passcode"
android:text="@string/details_manage_security_access_code"
android:textSize="16sp"
/>
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radiobutton_access_code"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/v_access_code"/>
<TextView
android:id="@+id/tv_access_code_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingBottom="8dp"
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:textSize="13sp"
/>
<View
android:id="@+id/v_access_code"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/tv_access_code_title"
app:layout_constraintBottom_toBottomOf="@id/tv_access_code_description"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_percent="0.37" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_save_changes"
style="@style/TapBlackButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="7dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="30dp"
android:text="@string/details_save_changes"
app:icon="@drawable/ic_save"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -86,6 +86,7 @@
<string name="details_toolbar_title">Details</string>
<string name="details_notification_erase_wallet_not_allowed">Card settings prohibits from erasing wallet</string>
<string name="details_notification_erase_wallet_not_possible">You balance on this wallet is not zero, or you have unconfirmed transactions</string>
<string name="details_notification_security_option_already_active">This is the currently active option</string>
<string name="details_card_id">Card ID</string>
<string name="details_issuer">Issuer</string>
<string name="details_signed_hashes">Signed</string>
@ -95,6 +96,13 @@
<string name="details_validate_card">Validate card</string>
<string name="details_manage_security">Manage security</string>
<string name="details_erase_wallet">Erase wallet</string>
<string name="details_save_changes">Save changes</string>
<string name="details_manage_security_long_tap">Long Tap</string>
<string name="details_manage_security_passcode">Passcode</string>
<string name="details_manage_security_access_code">Access Code</string>
<string name="details_erase_wallet_warning">This action is irreversible. If, after deleting the wallet, someone sends funds to it, then you will not be able to withdraw them.</string>
<string name="details_manage_security_warning">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.
</string>
</resources>