Updated on 2026-08-14
This commit is contained in:
commit
2126d9803e
106 changed files with 3186 additions and 516 deletions
|
|
@ -72,13 +72,14 @@ dependencies {
|
|||
implementation 'androidx.fragment:fragment-ktx:1.3.6'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation 'com.google.android.play:core:1.10.2'
|
||||
implementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.tangem:blockchain:develop-48'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-95'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-95'
|
||||
implementation 'com.tangem:blockchain:develop-51'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-106'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-106'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import android.view.View
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tap.common.DialogManager
|
||||
import com.tangem.tap.common.IntentHandler
|
||||
|
|
@ -32,6 +33,7 @@ import kotlin.coroutines.CoroutineContext
|
|||
|
||||
lateinit var tangemSdk: TangemSdk
|
||||
lateinit var tangemSdkManager: TangemSdkManager
|
||||
lateinit var backupService: BackupService
|
||||
var notificationsHandler: NotificationsHandler? = null
|
||||
|
||||
private val coroutineContext: CoroutineContext
|
||||
|
|
@ -67,6 +69,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
|
||||
tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
|
||||
tangemSdkManager = TangemSdkManager(tangemSdk, this)
|
||||
backupService = BackupService.init(tangemSdk, this)
|
||||
|
||||
store.dispatch(GlobalAction.SetResources(getAndroidResources()))
|
||||
store.dispatch(WalletConnectAction.RestoreSessions)
|
||||
|
|
@ -93,7 +96,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isScannedBefore = store.state.globalState.scanResponse != null
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingManager != null
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
|||
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -74,6 +79,10 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
TransactionDialog.create(state.dialog.dialogData, context)
|
||||
is WalletConnectDialog.PersonalSign ->
|
||||
PersonalSignDialog.create(state.dialog.data, context)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
|
||||
else -> null
|
||||
}
|
||||
dialog?.show()
|
||||
|
|
|
|||
|
|
@ -72,9 +72,39 @@ object TangemSdk {
|
|||
is TangemSdkError.WalletCannotBeCreated -> TangemSdkError.WalletCannotBeCreated()
|
||||
is TangemSdkError.UnsupportedCurve -> TangemSdkError.UnsupportedCurve()
|
||||
is TangemSdkError.UnsupportedWalletConfig -> TangemSdkError.UnsupportedWalletConfig()
|
||||
is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError()
|
||||
is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError(error.customMessage)
|
||||
is TangemSdkError.NetworkError -> TangemSdkError.NetworkError(error.customMessage)
|
||||
is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.throwable)
|
||||
is TangemSdkError.TooMuchBackupCards -> TangemSdkError.TooMuchBackupCards()
|
||||
is TangemSdkError.BackupCardRequired -> TangemSdkError.BackupCardRequired()
|
||||
is TangemSdkError.CertificateSignatureRequired -> TangemSdkError.CertificateSignatureRequired()
|
||||
is TangemSdkError.AccessCodeOrPasscodeRequired -> TangemSdkError.AccessCodeOrPasscodeRequired()
|
||||
is TangemSdkError.ResetPinNoCardsToReset -> TangemSdkError.ResetPinNoCardsToReset()
|
||||
is TangemSdkError.ResetPinWrongCard -> TangemSdkError.ResetPinWrongCard()
|
||||
is TangemSdkError.BackupFailedCardNotLinked -> TangemSdkError.BackupFailedCardNotLinked()
|
||||
is TangemSdkError.BackupNotAllowed -> TangemSdkError.BackupNotAllowed()
|
||||
is TangemSdkError.BackupCardAlreadyAdded -> TangemSdkError.BackupCardAlreadyAdded()
|
||||
is TangemSdkError.MissingPrimaryCard -> TangemSdkError.MissingPrimaryCard()
|
||||
is TangemSdkError.MissingPrimaryAttestSignature -> TangemSdkError.MissingPrimaryAttestSignature()
|
||||
is TangemSdkError.NoBackupDataForCard -> TangemSdkError.NoBackupDataForCard()
|
||||
is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets()
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets()
|
||||
is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup()
|
||||
is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets()
|
||||
is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState()
|
||||
is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex()
|
||||
is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards()
|
||||
is TangemSdkError.BackupFailedWrongIssuer -> TangemSdkError.BackupFailedWrongIssuer()
|
||||
is TangemSdkError.BackupFailedHDWalletSettings -> TangemSdkError.BackupFailedHDWalletSettings()
|
||||
is TangemSdkError.BackupFailedNotEnoughCurves -> TangemSdkError.BackupFailedNotEnoughCurves()
|
||||
is TangemSdkError.BackupFailedNotEnoughWallets -> TangemSdkError.BackupFailedNotEnoughWallets()
|
||||
is TangemSdkError.FileSettingsUnsupported -> TangemSdkError.FileSettingsUnsupported()
|
||||
is TangemSdkError.FilesIsEmpty -> TangemSdkError.FilesIsEmpty()
|
||||
is TangemSdkError.FilesDisabled -> TangemSdkError.FilesDisabled()
|
||||
is TangemSdkError.HDWalletDisabled -> TangemSdkError.HDWalletDisabled()
|
||||
is TangemSdkError.WrongInteractionMode -> TangemSdkError.WrongInteractionMode()
|
||||
is TangemSdkError.IssuerSignatureLoadingFailed -> TangemSdkError.IssuerSignatureLoadingFailed()
|
||||
is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
data class ByteArrayKey(val bytes: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || other is ByteArrayKey && this.bytes.contentEquals(other.bytes)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = bytes.contentHashCode()
|
||||
}
|
||||
|
||||
fun ByteArray.toMapKey(): ByteArrayKey = ByteArrayKey(this)
|
||||
|
||||
fun ByteArrayKey.bytes(): ByteArray = this.bytes
|
||||
|
|
@ -17,7 +17,7 @@ import com.tangem.tap.features.home.HomeFragment
|
|||
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
|
|
@ -68,7 +68,6 @@ fun FragmentActivity.addOnBackPressedDispatcher(
|
|||
|
||||
private fun fragmentFactory(screen: AppScreen): Fragment {
|
||||
return when (screen) {
|
||||
// AppScreen.Home -> TestLeapfrogFragment()
|
||||
AppScreen.Home -> HomeFragment()
|
||||
AppScreen.OnboardingNote -> OnboardingNoteFragment()
|
||||
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
|
|||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
|
||||
|
|
@ -60,7 +61,8 @@ data class AppState(
|
|||
DetailsMiddleware().detailsMiddleware,
|
||||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
TokensMiddleware().tokensMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ sealed class GlobalAction : Action {
|
|||
object HideDialog : GlobalAction()
|
||||
|
||||
sealed class Onboarding {
|
||||
data class Start(val scanResponse: ScanResponse) : GlobalAction()
|
||||
data class Start(val scanResponse: ScanResponse?, val fromHomeScreen: Boolean = true) : GlobalAction()
|
||||
object Stop : GlobalAction()
|
||||
}
|
||||
|
||||
data class ScanCard(
|
||||
val shouldDeriveWC: Boolean,
|
||||
val onSuccess: ((ScanResponse) -> Unit)? = null,
|
||||
val onFailure: ((TangemError) -> Unit)? = null,
|
||||
val messageResId: Int? = null,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -13,10 +14,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
|||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.moonpay.MoonpayService
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
|
@ -91,7 +88,12 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
}
|
||||
is GlobalAction.ScanCard -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId)
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
FirebaseAnalyticsHandler,
|
||||
currenciesRepository,
|
||||
action.shouldDeriveWC,
|
||||
action.messageResId
|
||||
)
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
withMainContext {
|
||||
when (result) {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState.copy(resources = action.resources)
|
||||
}
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage
|
||||
val onboardingState = OnboardingManager(action.scanResponse, usedCardsPrefStorage)
|
||||
globalState.copy(onboardingManager = onboardingState)
|
||||
val onboardingManager = if (action.scanResponse != null) {
|
||||
val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage
|
||||
OnboardingManager(action.scanResponse, usedCardsPrefStorage)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
globalState.copy(onboardingState = OnboardingState(true, onboardingManager))
|
||||
}
|
||||
is GlobalAction.Onboarding.Stop -> {
|
||||
globalState.copy(onboardingManager = null)
|
||||
globalState.copy(onboardingState = OnboardingState(false))
|
||||
}
|
||||
is GlobalAction.ScanFailsCounter.Increment -> {
|
||||
globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import org.rekotlin.StateType
|
|||
|
||||
data class GlobalState(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val onboardingManager: OnboardingManager? = null,
|
||||
val onboardingState: OnboardingState = OnboardingState(),
|
||||
val cardVerifiedOnline: Boolean = false,
|
||||
val tapWalletManager: TapWalletManager = TapWalletManager(),
|
||||
val payIdManager: PayIdManager = PayIdManager(),
|
||||
|
|
@ -32,12 +32,17 @@ data class GlobalState(
|
|||
|
||||
|
||||
data class AndroidResources(
|
||||
val strings: RString = RString()
|
||||
val strings: RString = RString(),
|
||||
) {
|
||||
data class RString(
|
||||
val addressWasCopied: Int = -1,
|
||||
val walletIsNotEmpty: Int = -1
|
||||
val walletIsNotEmpty: Int = -1,
|
||||
)
|
||||
}
|
||||
typealias CryptoCurrencyName = String
|
||||
typealias FiatCurrencyName = String
|
||||
typealias FiatCurrencyName = String
|
||||
|
||||
data class OnboardingState(
|
||||
val onboardingStarted: Boolean = false,
|
||||
val onboardingManager: OnboardingManager? = null,
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import CreateProductWalletAndRescanTask
|
||||
import CreateProductWalletTask
|
||||
import android.content.Context
|
||||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
|
|
@ -12,17 +12,21 @@ import com.tangem.common.card.FirmwareVersion
|
|||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeyList
|
||||
import com.tangem.operations.pins.CheckUserCodesCommand
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -33,18 +37,20 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
|
||||
suspend fun scanProduct(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
shouldDeriveWC: Boolean,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanResponse> {
|
||||
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(ScanProductTask(), null, message)
|
||||
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository, shouldDeriveWC), null, message)
|
||||
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<Card> {
|
||||
return runTaskAsync(
|
||||
CreateProductWalletAndRescanTask(scanResponse.productType),
|
||||
CreateProductWalletTask(scanResponse.productType),
|
||||
scanResponse.card.cardId,
|
||||
Message(context.getString(R.string.initial_message_create_wallet_body))
|
||||
)
|
||||
|
|
@ -66,11 +72,19 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)))
|
||||
}
|
||||
|
||||
suspend fun eraseWallet(card: Card): CompletionResult<SuccessResponse> {
|
||||
suspend fun derivePublicKeys(
|
||||
cardId: String,
|
||||
walletPublicKey: ByteArray,
|
||||
derivationPaths: List<DerivationPath>
|
||||
): CompletionResult<ExtendedPublicKeyList> {
|
||||
return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId)
|
||||
}
|
||||
|
||||
suspend fun resetToFactorySettings(card: Card): CompletionResult<Card> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
PurgeWalletCommand(card.wallets.first().publicKey),
|
||||
ResetToFactorySettingsTask(),
|
||||
card.cardId,
|
||||
initialMessage = Message(context.getString(R.string.initial_message_purge_wallet_body)))
|
||||
initialMessage = Message(context.getString(R.string.details_row_title_reset_factory_settings)))
|
||||
}
|
||||
|
||||
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
|
||||
|
|
@ -104,13 +118,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
suspend fun <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> =
|
||||
withContext(Dispatchers.Main) {
|
||||
suspendCoroutine { continuation ->
|
||||
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
suspendCoroutine { continuation ->
|
||||
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : CommandResponse> runTaskAsyncReturnOnMain(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain
|
|||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.domain.tasks.SignHashTask
|
||||
import com.tangem.tap.domain.tasks.SignHashesTask
|
||||
|
|
@ -15,13 +16,9 @@ class TangemSigner(
|
|||
private val signerCallback: (TangemSignerResponse) -> Unit,
|
||||
) : TransactionSigner {
|
||||
|
||||
override suspend fun sign(
|
||||
hash: ByteArray,
|
||||
cardId: String,
|
||||
walletPublicKey: ByteArray,
|
||||
): CompletionResult<ByteArray> =
|
||||
suspendCoroutine { continuation ->
|
||||
val command = SignHashTask(hash, walletPublicKey)
|
||||
override suspend fun sign(hash: ByteArray, cardId: String, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
|
||||
return suspendCoroutine { continuation ->
|
||||
val command = SignHashTask(hash, publicKey)
|
||||
tangemSdk.startSessionWithRunnable(
|
||||
runnable = command,
|
||||
cardId = cardId,
|
||||
|
|
@ -42,15 +39,11 @@ class TangemSigner(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun sign(
|
||||
hashes: List<ByteArray>,
|
||||
cardId: String,
|
||||
walletPublicKey: ByteArray,
|
||||
): CompletionResult<List<ByteArray>> =
|
||||
suspendCoroutine { continuation ->
|
||||
val task = SignHashesTask(hashes, walletPublicKey)
|
||||
override suspend fun sign(hashes: List<ByteArray>, cardId: String, publicKey: Wallet.PublicKey): CompletionResult<List<ByteArray>> {
|
||||
return suspendCoroutine { continuation ->
|
||||
val task = SignHashesTask(hashes, publicKey)
|
||||
tangemSdk.startSessionWithRunnable(
|
||||
runnable = task,
|
||||
cardId = cardId,
|
||||
|
|
@ -71,6 +64,7 @@ class TangemSigner(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TangemSignerResponse(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
|
|
@ -16,7 +15,6 @@ import com.tangem.tap.domain.configurable.config.ConfigManager
|
|||
import com.tangem.tap.domain.extensions.*
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
|
|
@ -85,12 +83,6 @@ class TapWalletManager {
|
|||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
|
||||
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
|
||||
|
||||
val blockchain = data.getBlockchain()
|
||||
if (blockchain == Blockchain.Ethereum ||
|
||||
blockchain == Blockchain.EthereumTestnet) {
|
||||
store.dispatch(TokensAction.LoadCardTokens)
|
||||
}
|
||||
loadData(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -131,7 +123,7 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
|
||||
}
|
||||
if (data.card.isMultiwalletAllowed) {
|
||||
loadMultiWalletData(data.card, blockchain, primaryWalletManager)
|
||||
loadMultiWalletData(data, blockchain, primaryWalletManager)
|
||||
} else {
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
|
||||
|
|
@ -139,7 +131,7 @@ class TapWalletManager {
|
|||
|
||||
} else {
|
||||
if (data.card.isMultiwalletAllowed) {
|
||||
loadMultiWalletData(data.card, blockchain, null)
|
||||
loadMultiWalletData(data, blockchain, null)
|
||||
}
|
||||
}
|
||||
val moonPayStatus = store.state.globalState.moonpayStatus
|
||||
|
|
@ -149,10 +141,10 @@ class TapWalletManager {
|
|||
}
|
||||
|
||||
private fun loadMultiWalletData(
|
||||
card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
|
||||
scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
|
||||
) {
|
||||
val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList()
|
||||
val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
|
||||
val savedCurrencies = currenciesRepository.loadCardCurrencies(scanResponse.card.cardId)
|
||||
|
||||
if (savedCurrencies == null) {
|
||||
if (primaryBlockchain != null && primaryWalletManager != null) {
|
||||
|
|
@ -169,11 +161,11 @@ class TapWalletManager {
|
|||
CardCurrencies(blockchains = blockchains, tokens = emptyList())
|
||||
))
|
||||
val walletManagers =
|
||||
walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList())
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList()))
|
||||
}
|
||||
store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory))
|
||||
store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse)
|
||||
store.dispatch(WalletAction.MultiWallet.FindTokensInUse)
|
||||
} else {
|
||||
val blockchains = savedCurrencies.blockchains.toList()
|
||||
|
|
@ -182,10 +174,10 @@ class TapWalletManager {
|
|||
primaryWalletManager != null && primaryBlockchain != null
|
||||
) {
|
||||
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
|
||||
walletManagerFactory.makeWalletManagersForApp(card, blockchainsWithoutPrimary)
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary)
|
||||
.plus(primaryWalletManager)
|
||||
} else {
|
||||
walletManagerFactory.makeWalletManagersForApp(card, blockchains)
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
|
||||
}
|
||||
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ object TapWorkarounds {
|
|||
}
|
||||
|
||||
fun Card.isNotSupportedInThatRelease():Boolean {
|
||||
return tangemWalletBatches.contains(batchId)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@Deprecated("Use ScanResponse.isTangemNote")
|
||||
|
|
|
|||
|
|
@ -3,74 +3,70 @@ package com.tangem.tap.domain.extensions
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
card: Card,
|
||||
blockchain: Blockchain,
|
||||
scanResponse: ScanResponse, blockchain: Blockchain
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
|
||||
val supportedCurves = blockchain.getSupportedCurves() ?: return null
|
||||
|
||||
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
|
||||
val wallet = selectWallet(wallets)
|
||||
val publicKey = wallet?.publicKey ?: return null
|
||||
val curveToUse = wallet.curve ?: return null
|
||||
return if (card.isTestCard) {
|
||||
blockchain.getTestnetVersion()?.let {
|
||||
makeWalletManager(card.cardId, publicKey, it, curveToUse)
|
||||
val wallet = selectWallet(wallets) ?: return null
|
||||
|
||||
val environmentBlockchain = if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
|
||||
|
||||
val seedKey = wallet.extendedPublicKey
|
||||
return when {
|
||||
scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
|
||||
makeTwinWalletManager(
|
||||
card.cardId,
|
||||
wallet.publicKey, scanResponse.secondTwinPublicKey.hexToBytes(),
|
||||
environmentBlockchain, wallet.curve
|
||||
)
|
||||
}
|
||||
seedKey != null -> {
|
||||
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
|
||||
val derivedKey = derivedKeys?.firstOrNull { it.derivationPath == blockchain.derivationPath() }
|
||||
?: return null
|
||||
|
||||
makeWalletManager(card.cardId, environmentBlockchain, seedKey, derivedKey)
|
||||
}
|
||||
else -> {
|
||||
makeWalletManager(card.cardId, environmentBlockchain, wallet.publicKey, wallet.curve)
|
||||
}
|
||||
} else {
|
||||
makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse: ScanResponse, blockchains: List<Blockchain>,
|
||||
): List<WalletManager> {
|
||||
val isTestCard = scanResponse.card.isTestCard
|
||||
val filteredBlockchains = blockchains.mapNotNull { if (isTestCard) it.getTestnetVersion() else it }
|
||||
return filteredBlockchains.mapNotNull { makeWalletManagerForApp(scanResponse, it) }
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makePrimaryWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
): WalletManager? {
|
||||
val blockchain = if (scanResponse.card.isTestCard) {
|
||||
scanResponse.getBlockchain().getTestnetVersion() ?: return null
|
||||
} else {
|
||||
scanResponse.getBlockchain()
|
||||
}
|
||||
return makeWalletManagerForApp(scanResponse, blockchain)
|
||||
}
|
||||
|
||||
private fun selectWallet(wallets: List<CardWallet>): CardWallet? {
|
||||
return when (wallets.size) {
|
||||
0 -> null
|
||||
1 -> wallets[0]
|
||||
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagersForApp(
|
||||
card: Card, blockchains: List<Blockchain>,
|
||||
): List<WalletManager> {
|
||||
return if (card.isTestCard) {
|
||||
blockchains.mapNotNull { blockchain ->
|
||||
blockchain.getTestnetVersion()?.let { makeWalletManagerForApp(card, it) }
|
||||
}
|
||||
} else {
|
||||
blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) }
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makePrimaryWalletManager(
|
||||
data: ScanResponse,
|
||||
): WalletManager? {
|
||||
val card = data.card
|
||||
val blockchain = if (card.isTestCard) {
|
||||
data.getBlockchain().getTestnetVersion()
|
||||
} else {
|
||||
data.getBlockchain()
|
||||
}
|
||||
val supportedCurves = blockchain?.getSupportedCurves() ?: return null
|
||||
|
||||
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
|
||||
val wallet = selectWallet(wallets)
|
||||
val publicKey = wallet?.publicKey ?: return null
|
||||
val curveToUse = wallet.curve
|
||||
|
||||
return if (data.isTangemTwins() && data.secondTwinPublicKey != null) {
|
||||
makeMultisigWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(),
|
||||
blockchain = blockchain, curve = curveToUse
|
||||
)
|
||||
} else {
|
||||
makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
|
||||
@Deprecated("Use CreateProductWalletAndRescanTask instead")
|
||||
class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
|
||||
|
|
@ -22,7 +22,7 @@ class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
|
|||
val firmwareVersion = card.firmwareVersion
|
||||
|
||||
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
CreateWalletCommand(card.supportedCurves.first())
|
||||
CreateWalletTask(card.supportedCurves.first())
|
||||
} else {
|
||||
CreateWalletsTask()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import com.tangem.common.core.CardSession
|
|||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
|
||||
@Deprecated("Use CreateProductWalletsResponse instead")
|
||||
@Deprecated("Use CreateProductWalletTask instead")
|
||||
class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnable<Card> {
|
||||
|
||||
private val curves = curves ?: listOf(
|
||||
|
|
@ -30,7 +30,7 @@ class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnab
|
|||
callback: (result: CompletionResult<Card>) -> Unit
|
||||
) {
|
||||
|
||||
CreateWalletCommand(curve).run(session) { result ->
|
||||
CreateWalletTask(curve).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (index == curves.lastIndex) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
|
|
@ -15,17 +16,17 @@ class TangemSignHashResponse(
|
|||
|
||||
class SignHashTask(
|
||||
private val hash: ByteArray,
|
||||
private val walletPublicKey: ByteArray,
|
||||
private val publicKey: Wallet.PublicKey,
|
||||
) : CardSessionRunnable<TangemSignHashResponse> {
|
||||
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashResponse>) {
|
||||
SignHashCommand(hash, walletPublicKey).run(session) { response ->
|
||||
SignHashCommand(hash, publicKey.seedKey, publicKey.derivationPath).run(session) { response ->
|
||||
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(TangemSignHashResponse(
|
||||
response.data.signature,
|
||||
response.data.totalSignedHashes,
|
||||
session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
|
||||
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures
|
||||
)))
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
|
|
@ -15,16 +16,16 @@ class TangemSignHashesResponse(
|
|||
|
||||
class SignHashesTask(
|
||||
private val hashes: Collection<ByteArray>,
|
||||
private val walletPublicKey: ByteArray,
|
||||
private val publicKey: Wallet.PublicKey,
|
||||
) : CardSessionRunnable<TangemSignHashesResponse> {
|
||||
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashesResponse>) {
|
||||
SignHashesCommand(hashes.toTypedArray(), walletPublicKey).run(session) { response ->
|
||||
SignHashesCommand(hashes.toTypedArray(), publicKey.seedKey, publicKey.derivationPath).run(session) { response ->
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(TangemSignHashesResponse(
|
||||
response.data.signatures,
|
||||
response.data.totalSignedHashes,
|
||||
session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
|
||||
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures
|
||||
)))
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeyList
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.ProductType
|
||||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletsTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
|
||||
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
|
||||
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CreateProductWalletAndRescanTask(
|
||||
class CreateProductWalletTask(
|
||||
private val type: ProductType,
|
||||
) : CardSessionRunnable<Card> {
|
||||
|
||||
|
|
@ -28,28 +32,19 @@ class CreateProductWalletAndRescanTask(
|
|||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
CreateProductWalletTask(card, type).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateProductWalletTask(
|
||||
private val card: Card,
|
||||
private val type: ProductType,
|
||||
) : CardSessionRunnable<CreateWalletResponse> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
val commandProcessor = when (type) {
|
||||
ProductType.Note -> CreateWalletTangemNote()
|
||||
ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||
ProductType.Wallet -> CreateWalletTangemWallet()
|
||||
else -> CreateWalletOtherCards()
|
||||
}
|
||||
commandProcessor.proceed(card, session, callback)
|
||||
commandProcessor.proceed(card, session) {
|
||||
when (it) {
|
||||
is CompletionResult.Success -> callback(CompletionResult.Success(session.environment.card!!))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +52,7 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
|
|||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
if (card.supportedCurves.isEmpty()) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
|
|
@ -79,34 +74,63 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
|
|||
} else {
|
||||
intersectCurves[0]
|
||||
}
|
||||
CreateWalletCommand(curve).run(session, callback)
|
||||
|
||||
CreateWalletTask(curve).run(session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletResponse> {
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
//TODO:
|
||||
val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
|
||||
val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList()
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = result.data.createWalletResponses[0]
|
||||
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
.mapNotNull { it.derivationPath() }
|
||||
|
||||
DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths).run(session) {
|
||||
when (it) {
|
||||
is CompletionResult.Success -> {
|
||||
updateDerivedKeys(response.wallet, it.data)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: updating derived wallet public keys - make it better later
|
||||
private fun updateDerivedKeys(wallet: CardWallet, derivedKeys: ExtendedPublicKeyList) {
|
||||
val onboardingManager = store.state.globalState.onboardingState.onboardingManager ?: return
|
||||
onboardingManager.scanResponse = onboardingManager.scanResponse.copy(
|
||||
derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys)
|
||||
)
|
||||
}
|
||||
|
||||
private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletResponse> {
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
val firmwareVersion = card.firmwareVersion
|
||||
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
CreateProductWalletsTask(listOf(card.supportedCurves.first()))
|
||||
CreateWalletsTask(listOf(card.supportedCurves.first()))
|
||||
} else {
|
||||
CreateProductWalletsTask(card.getCurvesForNonCreatedWallets())
|
||||
CreateWalletsTask(card.getCurvesForNonCreatedWallets())
|
||||
}
|
||||
|
||||
task.run(session) { result ->
|
||||
|
|
@ -5,23 +5,23 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CreateProductWalletsResponse(
|
||||
class CreateWalletsResponse(
|
||||
val createWalletResponses: List<CreateWalletResponse>
|
||||
) : CommandResponse
|
||||
|
||||
class CreateProductWalletsTask(
|
||||
class CreateWalletsTask(
|
||||
private val curves: List<EllipticCurve>,
|
||||
) : CardSessionRunnable<CreateProductWalletsResponse> {
|
||||
) : CardSessionRunnable<CreateWalletsResponse> {
|
||||
|
||||
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateProductWalletsResponse>) -> Unit) {
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit) {
|
||||
val curve = curves[createdWalletsResponses.size]
|
||||
createWallet(curve, session, callback)
|
||||
}
|
||||
|
|
@ -29,15 +29,14 @@ class CreateProductWalletsTask(
|
|||
private fun createWallet(
|
||||
curve: EllipticCurve,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletsResponse>) -> Unit
|
||||
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit
|
||||
) {
|
||||
|
||||
CreateWalletCommand(curve).run(session) { result ->
|
||||
CreateWalletTask(curve).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
createdWalletsResponses.add(result.data)
|
||||
if (createdWalletsResponses.size == curves.size) {
|
||||
callback(CompletionResult.Success(CreateProductWalletsResponse(createdWalletsResponses)))
|
||||
callback(CompletionResult.Success(CreateWalletsResponse(createdWalletsResponses)))
|
||||
return@run
|
||||
}
|
||||
createWallet(curves[createdWalletsResponses.size], session, callback)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.tap.domain.tasks.product
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.operations.backup.ResetBackupCommand
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
|
||||
class ResetToFactorySettingsTask() : CardSessionRunnable<Card> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
deleteWallets(session, callback)
|
||||
}
|
||||
|
||||
private fun deleteWallets(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<Card>) -> Unit,
|
||||
) {
|
||||
|
||||
val wallet = session.environment.card?.wallets?.lastOrNull().guard {
|
||||
resetBackup(session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
PurgeWalletCommand(wallet.publicKey).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
deleteWallets(session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetBackup(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<Card>) -> Unit,
|
||||
) {
|
||||
|
||||
val backupStatus = session.environment.card?.backupStatus
|
||||
if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) {
|
||||
callback(CompletionResult.Success(session.environment.card!!))
|
||||
return
|
||||
}
|
||||
|
||||
ResetBackupCommand().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(session.environment.card!!))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,11 +14,16 @@ import com.tangem.common.core.TangemError
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.ProductType
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
|
|
@ -26,6 +31,7 @@ import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
|||
import com.tangem.tap.domain.TapWorkarounds.isExcluded
|
||||
import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
|
||||
data class ScanResponse(
|
||||
|
|
@ -33,6 +39,7 @@ data class ScanResponse(
|
|||
val productType: ProductType,
|
||||
val walletData: WalletData?,
|
||||
val secondTwinPublicKey: String? = null,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf()
|
||||
) : CommandResponse {
|
||||
|
||||
fun getBlockchain(): Blockchain {
|
||||
|
|
@ -60,7 +67,13 @@ data class ScanResponse(
|
|||
fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
|
||||
}
|
||||
|
||||
class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanResponse> {
|
||||
typealias KeyWalletPublicKey = ByteArrayKey
|
||||
|
||||
class ScanProductTask(
|
||||
val card: Card? = null,
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
private val shouldDeriveWC: Boolean
|
||||
) : CardSessionRunnable<ScanResponse> {
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
|
|
@ -80,7 +93,7 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanResponse
|
|||
val commandProcessor = when {
|
||||
TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
|
||||
card.isTangemTwins() -> ScanTwinProcessor()
|
||||
TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor()
|
||||
TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor(currenciesRepository, shouldDeriveWC)
|
||||
else -> ScanOtherCardsProcessor()
|
||||
}
|
||||
commandProcessor.proceed(card, session) { processorResult ->
|
||||
|
|
@ -116,13 +129,60 @@ private class ScanNoteProcessor : ProductCommandProcessor<ScanResponse> {
|
|||
}
|
||||
}
|
||||
|
||||
private class ScanWalletProcessor : ProductCommandProcessor<ScanResponse> {
|
||||
private class ScanWalletProcessor(
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
private val shouldDeriveWC: Boolean
|
||||
) : ProductCommandProcessor<ScanResponse> {
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
|
||||
val derivationPaths = collectDerivationPaths(card)?.distinct()
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
|
||||
if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
|
||||
return
|
||||
}
|
||||
|
||||
DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data)
|
||||
val response = ScanResponse(
|
||||
card,
|
||||
ProductType.Wallet,
|
||||
session.environment.walletData,
|
||||
derivedKeys = derivedKeys
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectDerivationPaths(card: Card): List<DerivationPath>? {
|
||||
val currenciesRepository = currenciesRepository ?: return null
|
||||
val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
|
||||
|
||||
val blockchainsToDerive = if (cardCurrencies == null) {
|
||||
mutableListOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
} else {
|
||||
val tokenBlockchains = cardCurrencies.tokens.map { it.blockchain }
|
||||
(cardCurrencies.blockchains + tokenBlockchains).toMutableList()
|
||||
}
|
||||
|
||||
if (shouldDeriveWC) {
|
||||
blockchainsToDerive.addAll(listOf(Blockchain.Ethereum, Blockchain.Binance, Blockchain.EthereumTestnet))
|
||||
}
|
||||
|
||||
return blockchainsToDerive.toSet()
|
||||
.filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true }
|
||||
.mapNotNull { it.derivationPath() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +234,7 @@ private class ScanOtherCardsProcessor : ProductCommandProcessor<ScanResponse> {
|
|||
return
|
||||
}
|
||||
|
||||
CreateProductWalletsTask(curvesToCreate).run(session) { result ->
|
||||
CreateWalletsTask(curvesToCreate).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult ->
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
return try {
|
||||
tokensAdapter.fromJson(json)!!.map { it.toToken() }
|
||||
tokensAdapter.fromJson(json)!!.map { it.toToken() }.distinct()
|
||||
} catch (exception: Exception) {
|
||||
try {
|
||||
obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() }
|
||||
|
|
@ -86,7 +86,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainsAdapter.fromJson(json) ?: emptyList()
|
||||
blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class TopUpManager {
|
|||
store.dispatch(
|
||||
GlobalAction.UpdateWalletSignedHashes(
|
||||
walletSignedHashes = signResponse.totalSignedHashes,
|
||||
walletPublicKey = walletManager.wallet.publicKey,
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
remainingSignatures = signResponse.remainingSignatures
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
|
||||
|
|
@ -20,15 +20,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
|||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.setWallets(emptyList())
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1)
|
||||
.run(session) { callback(it) }
|
||||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) }
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1)
|
||||
.run(session) { callback(it) }
|
||||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import com.tangem.common.core.CardSession
|
|||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -47,7 +47,7 @@ class CreateSecondTwinWalletTask(
|
|||
|
||||
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
session.setMessage(creatingWalletMessage)
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1).run(session) { result ->
|
||||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.updateWallet(result.data.wallet)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class FinalizeTwinTask(
|
|||
PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success ->
|
||||
ScanProductTask(readResult.data).run(session, callback)
|
||||
ScanProductTask(readResult.data, null, false).run(session, callback)
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.domain.walletconnect
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -97,10 +96,10 @@ class WalletConnectManager {
|
|||
val activeData = sessions[session] ?: return
|
||||
removeSimilarSessions(activeData)
|
||||
|
||||
val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey
|
||||
val accounts = listOf(Blockchain.Ethereum.makeAddresses(key).first().value)
|
||||
val approved = activeData.client.approveSession(
|
||||
accounts = listOf(Blockchain.Ethereum.makeAddresses(
|
||||
activeData.wallet.walletPublicKey.hexToBytes()).first().value
|
||||
),
|
||||
accounts = accounts,
|
||||
chainId = activeData.wallet.chainId
|
||||
)
|
||||
if (approved) {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,12 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionSender
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
|
|
@ -36,9 +34,14 @@ class WalletConnectSdkHelper {
|
|||
type: WcTransactionType,
|
||||
): WcTransactionData? {
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val publicKey = Wallet.PublicKey(
|
||||
session.wallet.walletPublicKey,
|
||||
session.wallet.derivedPublicKey,
|
||||
session.wallet.derivationPath,
|
||||
)
|
||||
val walletManager = factory.makeEthereumWalletManager(
|
||||
session.wallet.cardId,
|
||||
session.wallet.walletPublicKey.hexToBytes(),
|
||||
publicKey,
|
||||
emptyList(),
|
||||
isTestNet = session.wallet.isTestNet
|
||||
) ?: return null
|
||||
|
|
@ -151,7 +154,8 @@ class WalletConnectSdkHelper {
|
|||
|
||||
val command = SignHashCommand(
|
||||
hash = dataToSign.hash,
|
||||
walletPublicKey = data.walletManager.wallet.publicKey
|
||||
walletPublicKey = data.walletManager.wallet.publicKey.seedKey,
|
||||
derivationPath = data.walletManager.wallet.publicKey.derivationPath
|
||||
)
|
||||
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message())
|
||||
return when (result) {
|
||||
|
|
@ -208,13 +212,13 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
|
||||
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? {
|
||||
val publicKey = wallet.walletPublicKey.hexToBytes()
|
||||
val command = SignHashCommand(hashToSign, publicKey)
|
||||
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey
|
||||
val command = SignHashCommand(hashToSign, wallet.walletPublicKey, wallet.derivationPath)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) {
|
||||
is CompletionResult.Success -> {
|
||||
val hash = result.data.signature
|
||||
return EthereumUtils.prepareSignedMessageData(
|
||||
hash, hashToSign, publicKey
|
||||
hash, hashToSign, CryptoUtils.decompressPublicKey(key)
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
|
|||
|
|
@ -25,24 +25,26 @@ sealed class DetailsAction : Action {
|
|||
object ShowDisclaimer : DetailsAction()
|
||||
data class ReCreateTwinsWallet(val number: TwinCardNumber) : DetailsAction()
|
||||
|
||||
sealed class EraseWallet : DetailsAction() {
|
||||
object Check : EraseWallet()
|
||||
object Proceed : EraseWallet() {
|
||||
object NotAllowedByCard : EraseWallet(), NotificationAction {
|
||||
sealed class ResetToFactory : DetailsAction() {
|
||||
object Check : ResetToFactory()
|
||||
object Proceed : ResetToFactory() {
|
||||
object NotAllowedByCard : ResetToFactory(), NotificationAction {
|
||||
override val messageResource = R.string.error_purge_prohibited
|
||||
}
|
||||
|
||||
object NotEmpty : EraseWallet(), NotificationAction {
|
||||
object NotEmpty : ResetToFactory(), NotificationAction {
|
||||
override val messageResource = R.string.details_notification_erase_wallet_not_possible
|
||||
}
|
||||
}
|
||||
|
||||
object Confirm : EraseWallet()
|
||||
object Cancel : EraseWallet()
|
||||
object Failure : EraseWallet()
|
||||
object Success : EraseWallet()
|
||||
object Confirm : ResetToFactory()
|
||||
object Cancel : ResetToFactory()
|
||||
object Failure : ResetToFactory()
|
||||
object Success : ResetToFactory()
|
||||
}
|
||||
|
||||
object CreateBackup : DetailsAction()
|
||||
|
||||
sealed class AppCurrencyAction : DetailsAction() {
|
||||
data class SetCurrencies(val currencies: List<FiatCurrency>) : AppCurrencyAction()
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class DetailsMiddleware {
|
|||
{ action ->
|
||||
when (action) {
|
||||
is DetailsAction.PrepareScreen -> prepareData()
|
||||
is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action)
|
||||
is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
|
||||
is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
|
||||
is DetailsAction.ShowDisclaimer -> {
|
||||
|
|
@ -58,6 +58,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))
|
||||
}
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
|
@ -86,25 +92,25 @@ class DetailsMiddleware {
|
|||
}
|
||||
|
||||
class EraseWalletMiddleware() {
|
||||
fun handle(action: DetailsAction.EraseWallet) {
|
||||
fun handle(action: DetailsAction.ResetToFactory) {
|
||||
when (action) {
|
||||
is DetailsAction.EraseWallet.Proceed -> {
|
||||
is DetailsAction.ResetToFactory.Proceed -> {
|
||||
when (store.state.detailsState.eraseWalletState) {
|
||||
EraseWalletState.Allowed ->
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm))
|
||||
EraseWalletState.NotAllowedByCard ->
|
||||
store.dispatch(DetailsAction.EraseWallet.Proceed.NotAllowedByCard)
|
||||
store.dispatch(DetailsAction.ResetToFactory.Proceed.NotAllowedByCard)
|
||||
EraseWalletState.NotEmpty ->
|
||||
store.dispatch(DetailsAction.EraseWallet.Proceed.NotEmpty)
|
||||
store.dispatch(DetailsAction.ResetToFactory.Proceed.NotEmpty)
|
||||
}
|
||||
}
|
||||
is DetailsAction.EraseWallet.Cancel -> {
|
||||
is DetailsAction.ResetToFactory.Cancel -> {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
is DetailsAction.EraseWallet.Confirm -> {
|
||||
is DetailsAction.ResetToFactory.Confirm -> {
|
||||
val card = store.state.detailsState.scanResponse?.card ?: return
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.eraseWallet(card)
|
||||
val result = tangemSdkManager.resetToFactorySettings(card)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
is DetailsAction.PrepareScreen -> {
|
||||
handlePrepareScreen(action, detailsState)
|
||||
}
|
||||
is DetailsAction.EraseWallet -> {
|
||||
is DetailsAction.ResetToFactory -> {
|
||||
handleEraseWallet(action, detailsState)
|
||||
}
|
||||
is DetailsAction.AppCurrencyAction -> {
|
||||
|
|
@ -39,23 +39,34 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState {
|
||||
private fun handlePrepareScreen(
|
||||
action: DetailsAction.PrepareScreen,
|
||||
state: DetailsState,
|
||||
): DetailsState {
|
||||
val backupIsActive = action.scanResponse.card.backupStatus?.isActive ?: false
|
||||
|
||||
return DetailsState(
|
||||
scanResponse = action.scanResponse,
|
||||
wallets = action.wallets,
|
||||
cardInfo = action.scanResponse.card.toCardInfo(),
|
||||
appCurrencyState = AppCurrencyState(action.fiatCurrencyName),
|
||||
cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card)
|
||||
cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card),
|
||||
createBackupAllowed = action.scanResponse.isTangemWallet() && !backupIsActive,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState {
|
||||
private fun handleEraseWallet(
|
||||
action: DetailsAction.ResetToFactory,
|
||||
state: DetailsState,
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
DetailsAction.EraseWallet.Check -> {
|
||||
DetailsAction.ResetToFactory.Check -> {
|
||||
val card = state.scanResponse?.card
|
||||
val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false
|
||||
val notAllowedByCard = notAllowedByAnyWallet ||
|
||||
(card?.isWalletDataSupported == true && !state.scanResponse.isTangemNote())
|
||||
(card?.isWalletDataSupported == true &&
|
||||
(!state.scanResponse.isTangemNote() && !state.scanResponse.isTangemWallet()))
|
||||
|
||||
val notEmpty = state.wallets.any {
|
||||
it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty()
|
||||
}
|
||||
|
|
@ -66,22 +77,22 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
|
|||
}
|
||||
state.copy(eraseWalletState = eraseWalletState)
|
||||
}
|
||||
DetailsAction.EraseWallet.Proceed -> {
|
||||
DetailsAction.ResetToFactory.Proceed -> {
|
||||
if (state.eraseWalletState == EraseWalletState.Allowed) {
|
||||
state.copy(confirmScreenState = ConfirmScreenState.EraseWallet)
|
||||
} else {
|
||||
state
|
||||
}
|
||||
}
|
||||
DetailsAction.EraseWallet.Cancel -> state.copy(eraseWalletState = null)
|
||||
DetailsAction.EraseWallet.Failure -> state.copy(eraseWalletState = null)
|
||||
DetailsAction.EraseWallet.Success -> state.copy(eraseWalletState = null)
|
||||
DetailsAction.ResetToFactory.Cancel -> state.copy(eraseWalletState = null)
|
||||
DetailsAction.ResetToFactory.Failure -> state.copy(eraseWalletState = null)
|
||||
DetailsAction.ResetToFactory.Success -> state.copy(eraseWalletState = null)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAppCurrencyAction(
|
||||
action: DetailsAction.AppCurrencyAction, state: DetailsState
|
||||
action: DetailsAction.AppCurrencyAction, state: DetailsState,
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.AppCurrencyAction.SetCurrencies -> {
|
||||
|
|
@ -105,7 +116,7 @@ private fun handleAppCurrencyAction(
|
|||
}
|
||||
|
||||
private fun handleSecurityAction(
|
||||
action: DetailsAction.ManageSecurity, state: DetailsState
|
||||
action: DetailsAction.ManageSecurity, state: DetailsState,
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.ManageSecurity.SetCurrentOption -> {
|
||||
|
|
@ -123,18 +134,18 @@ private fun handleSecurityAction(
|
|||
state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption))
|
||||
}
|
||||
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
||||
if (state.scanResponse?.card?.isStart2Coin == true ||
|
||||
state.scanResponse?.isTangemNote() == true
|
||||
) {
|
||||
return state.copy(securityScreenState = state.securityScreenState?.copy(
|
||||
allowedOptions = EnumSet.of(SecurityOption.LongTap),
|
||||
selectedOption = state.securityScreenState.currentOption
|
||||
))
|
||||
val allowedSecurityOptions = when {
|
||||
state.scanResponse?.card?.isStart2Coin == true ||
|
||||
state.scanResponse?.isTangemNote() == true -> {
|
||||
EnumSet.of(SecurityOption.LongTap)
|
||||
}
|
||||
state.scanResponse?.isTangemWallet() == true -> {
|
||||
EnumSet.of(state.securityScreenState?.currentOption)
|
||||
}
|
||||
else -> prepareAllowedSecurityOptions(
|
||||
state.scanResponse?.card, state.securityScreenState?.currentOption
|
||||
)
|
||||
}
|
||||
|
||||
val allowedSecurityOptions = prepareAllowedSecurityOptions(
|
||||
state.scanResponse?.card, state.securityScreenState?.currentOption
|
||||
)
|
||||
state.copy(securityScreenState = state.securityScreenState?.copy(
|
||||
allowedOptions = allowedSecurityOptions,
|
||||
selectedOption = state.securityScreenState.currentOption
|
||||
|
|
@ -171,18 +182,13 @@ private fun handleSecurityAction(
|
|||
}
|
||||
|
||||
private fun prepareAllowedSecurityOptions(
|
||||
card: Card?, currentSecurityOption: SecurityOption?
|
||||
card: Card?, currentSecurityOption: SecurityOption?,
|
||||
): EnumSet<SecurityOption> {
|
||||
val prohibitDefaultPin = card?.settings?.isResettingUserCodesAllowed != true
|
||||
|
||||
val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java)
|
||||
val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap)
|
||||
|
||||
if (card?.isTangemTwin() == true) {
|
||||
allowedSecurityOptions.add(SecurityOption.PassCode)
|
||||
}
|
||||
if ((currentSecurityOption == SecurityOption.LongTap) || !prohibitDefaultPin) {
|
||||
allowedSecurityOptions.add(SecurityOption.LongTap)
|
||||
}
|
||||
if (currentSecurityOption == SecurityOption.AccessCode) {
|
||||
allowedSecurityOptions.add(SecurityOption.AccessCode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ data class DetailsState(
|
|||
val confirmScreenState: ConfirmScreenState? = null,
|
||||
val securityScreenState: SecurityScreenState? = null,
|
||||
val cardTermsOfUseUrl: Uri? = null,
|
||||
val createBackupAllowed: Boolean = false,
|
||||
) : StateType {
|
||||
|
||||
// if you do not delegate - the application crashes on startup,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
|
@ -14,6 +12,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -128,24 +127,32 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
private fun handleScanCard(wcUri: String) {
|
||||
store.dispatch(GlobalAction.ScanCard({ scanResponse ->
|
||||
store.dispatch(GlobalAction.ScanCard(true, { scanResponse ->
|
||||
val card = scanResponse.card
|
||||
if (!card.isMultiwalletAllowed) {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
|
||||
return@ScanCard
|
||||
}
|
||||
|
||||
val walletManager = getWalletManager(card).guard {
|
||||
val walletManager = getWalletManager(scanResponse).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
|
||||
return@ScanCard
|
||||
}
|
||||
|
||||
val key = walletManager.wallet.publicKey
|
||||
val wallet = walletManager.wallet
|
||||
val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
|
||||
null
|
||||
} else {
|
||||
walletManager.wallet.publicKey.blockchainKey
|
||||
}
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(
|
||||
wcUri = wcUri,
|
||||
wallet = WalletForSession(
|
||||
card.cardId, key.toHexString(),
|
||||
isTestNet = card.isTestCard
|
||||
card.cardId,
|
||||
wallet.publicKey.seedKey,
|
||||
derivedKey,
|
||||
wallet.publicKey.derivationPath,
|
||||
card.isTestCard
|
||||
),
|
||||
))
|
||||
}, {
|
||||
|
|
@ -153,26 +160,32 @@ class WalletConnectMiddleware {
|
|||
}, R.string.wallet_connect_scan_card_message))
|
||||
}
|
||||
|
||||
private fun getWalletManager(card: Card): WalletManager? {
|
||||
private fun getWalletManager(scanResponse: ScanResponse): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
|
||||
val wcBlockchain = if (scanResponse.card.isTestCard) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
Blockchain.Ethereum
|
||||
}
|
||||
|
||||
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
|
||||
store.state.walletState.getWalletManager(Blockchain.Ethereum)
|
||||
?: factory.makeWalletManagerForApp(card, Blockchain.Ethereum)
|
||||
store.state.walletState.getWalletManager(wcBlockchain)
|
||||
?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
|
||||
?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
}
|
||||
} else {
|
||||
if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains(
|
||||
Blockchain.Ethereum) == true
|
||||
wcBlockchain) == true
|
||||
) {
|
||||
factory.makeWalletManagerForApp(card, Blockchain.Ethereum)
|
||||
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
|
||||
} else {
|
||||
factory.makeWalletManagerForApp(card,
|
||||
Blockchain.Ethereum)
|
||||
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
|
||||
?.also {
|
||||
currenciesRepository.saveAddedBlockchain(card.cardId, Blockchain.Ethereum)
|
||||
currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
|
||||
|
|
@ -21,9 +23,12 @@ data class WalletConnectSession(
|
|||
val peerMeta: WCPeerMeta,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletForSession(
|
||||
val cardId: String,
|
||||
val walletPublicKey: String,
|
||||
val walletPublicKey: ByteArray,
|
||||
val derivedPublicKey: ByteArray?,
|
||||
val derivationPath: DerivationPath?,
|
||||
val isTestNet: Boolean = false,
|
||||
) {
|
||||
val chainId
|
||||
|
|
|
|||
|
|
@ -58,13 +58,13 @@ class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
|
|||
|
||||
when (state.confirmScreenState) {
|
||||
ConfirmScreenState.EraseWallet -> {
|
||||
toolbar.title = getString(R.string.details_row_title_erase_wallet)
|
||||
tv_warning_description.text = getString(R.string.details_erase_wallet_warning)
|
||||
btn_confirm.text = getString(R.string.details_row_title_erase_wallet)
|
||||
toolbar.title = getString(R.string.details_row_title_reset_factory_settings)
|
||||
tv_warning_description.text = getString(R.string.details_row_title_reset_factory_settings_warning)
|
||||
btn_confirm.text = getString(R.string.details_row_title_reset_factory_settings)
|
||||
btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
null, null, getDrawable(R.drawable.ic_send), null
|
||||
)
|
||||
btn_confirm.setOnClickListener { store.dispatch(DetailsAction.EraseWallet.Confirm) }
|
||||
btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
|
||||
}
|
||||
ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode,
|
||||
ConfirmScreenState.PassCode -> {
|
||||
|
|
|
|||
|
|
@ -89,20 +89,24 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
}
|
||||
|
||||
if (state.isTangemTwins) {
|
||||
tv_erase_wallet.show()
|
||||
tv_erase_wallet.text = getText(R.string.details_row_title_twins_recreate)
|
||||
tv_erase_wallet.setOnClickListener {
|
||||
tv_reset_to_factory.show()
|
||||
tv_reset_to_factory.text = getText(R.string.details_row_title_twins_recreate)
|
||||
tv_reset_to_factory.setOnClickListener {
|
||||
store.dispatch(DetailsAction.ReCreateTwinsWallet(state.twinCardsState.cardNumber!!))
|
||||
}
|
||||
} else {
|
||||
tv_erase_wallet.show()
|
||||
tv_erase_wallet.text = getText(R.string.details_row_title_erase_wallet)
|
||||
tv_erase_wallet.setOnClickListener {
|
||||
store.dispatch(DetailsAction.EraseWallet.Check)
|
||||
store.dispatch(DetailsAction.EraseWallet.Proceed)
|
||||
tv_reset_to_factory.show()
|
||||
tv_reset_to_factory.text = getText(R.string.details_row_title_reset_factory_settings)
|
||||
tv_reset_to_factory.setOnClickListener {
|
||||
store.dispatch(DetailsAction.ResetToFactory.Check)
|
||||
store.dispatch(DetailsAction.ResetToFactory.Proceed)
|
||||
}
|
||||
}
|
||||
|
||||
tv_create_backup.show(state.createBackupAllowed)
|
||||
tv_create_backup.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateBackup)
|
||||
}
|
||||
|
||||
tv_app_currency.text = state.appCurrencyState.fiatCurrencyName
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
|||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -26,8 +27,7 @@ class HomeMiddleware {
|
|||
companion object {
|
||||
val handler = homeMiddleware
|
||||
|
||||
const val CARD_SHOP_URI =
|
||||
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
|
||||
const val CARD_SHOP_URI = "http://cards.tangem.com/"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +39,7 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.GetMoonPayStatus)
|
||||
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
is HomeAction.ShouldScanCardOnResume -> {
|
||||
if (action.shouldScanCard) {
|
||||
|
|
@ -59,7 +60,7 @@ private fun handleReadCard() {
|
|||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
} else {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
store.dispatch(GlobalAction.ScanCard({ scanResponse ->
|
||||
store.dispatch(GlobalAction.ScanCard(false, { scanResponse ->
|
||||
store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state -
|
|||
private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is OnboardingNoteAction) return
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingManager ?: return
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
val scanResponse = onboardingManager.scanResponse
|
||||
val card = onboardingManager.scanResponse.card
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ private val onboardingOtherCardsMiddleware: Middleware<AppState> = { dispatch, s
|
|||
private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is OnboardingOtherCardsAction) return
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingManager ?: return
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
val card = onboardingManager.scanResponse.card
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
val action = action as? TwinCardsAction ?: return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingManager
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
val twinCardsState = store.state.twinCardsState
|
||||
|
||||
fun getScanResponse(): ScanResponse {
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber<OnboardingWalletState> {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingWalletState == newState.onboardingWalletState
|
||||
}.select { it.onboardingWalletState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingWalletState) {
|
||||
if (activity == null) return
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,67 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
object Init : OnboardingWalletAction()
|
||||
object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
object CreateWallet : OnboardingWalletAction()
|
||||
object TopUp : OnboardingWalletAction()
|
||||
object Done : OnboardingWalletAction()
|
||||
object FinishOnboarding : OnboardingWalletAction()
|
||||
|
||||
object ProceedBackup : OnboardingWalletAction()
|
||||
|
||||
sealed class Backup : Action {
|
||||
object LoadArtwork : OnboardingWalletAction()
|
||||
class SetArtworkUrl(val artwork: Artwork) : OnboardingWalletAction()
|
||||
|
||||
object OnBackPressed : OnboardingWalletAction()
|
||||
}
|
||||
|
||||
sealed class BackupAction : Action {
|
||||
|
||||
object DetermineBackupStep : BackupAction()
|
||||
object IntroduceBackup : BackupAction()
|
||||
object StartBackup : BackupAction()
|
||||
object DismissBackup : BackupAction()
|
||||
object ScanPrimaryCard : BackupAction()
|
||||
|
||||
object CheckForUnfinishedBackup : BackupAction()
|
||||
|
||||
object StartAddingBackupCards : BackupAction()
|
||||
object AddBackupCard : BackupAction() {
|
||||
object Success : BackupAction()
|
||||
}
|
||||
|
||||
data class LoadBackupCardArtwork(
|
||||
val cardId: CardId,
|
||||
val cardPublicKey: ByteArray,
|
||||
) : BackupAction() {
|
||||
data class Success(val cardId: CardId, val artwork: Bitmap)
|
||||
}
|
||||
|
||||
object GoToShop : BackupAction()
|
||||
object FinishAddingBackupCards : BackupAction()
|
||||
|
||||
object ShowAccessCodeInfoScreen : BackupAction()
|
||||
object ShowEnterAccessCodeScreen : BackupAction()
|
||||
data class CheckAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SetAccessCodeError(val error: AccessCodeError?) : BackupAction()
|
||||
data class SaveFirstAccessCode(val accessCode: String) : BackupAction()
|
||||
object ShowReenterAccessCodeScreen : BackupAction()
|
||||
data class SaveAccessCodeConfirmation(val accessCodeConfirmation: String) : BackupAction()
|
||||
object OnAccessCodeDialogClosed : BackupAction()
|
||||
|
||||
data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
|
||||
object PrepareToWritePrimaryCard : BackupAction()
|
||||
object WritePrimaryCard: BackupAction()
|
||||
|
||||
object FinishBackup : BackupAction()
|
||||
object DiscardBackup : BackupAction()
|
||||
object DiscardSavedBackup : BackupAction()
|
||||
object ResumeBackup : BackupAction()
|
||||
|
||||
}
|
||||
|
|
@ -1,18 +1,282 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.extensions.hasWallets
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class OnboardingWalletMiddleware {
|
||||
companion object {
|
||||
val handler = onboardingWalletMiddleware
|
||||
|
||||
const val BUY_WALLET_URL = "https://wallet.tangem.com/"
|
||||
}
|
||||
}
|
||||
|
||||
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleWalletAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWalletAction(action: Action) {
|
||||
if (action !is OnboardingWalletAction) return
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
||||
val walletState = store.state.onboardingWalletState
|
||||
|
||||
when (action) {
|
||||
OnboardingWalletAction.Init -> {
|
||||
val action = when {
|
||||
card == null -> {
|
||||
OnboardingWalletAction.ProceedBackup
|
||||
}
|
||||
card.hasWallets() && card.backupStatus == Card.BackupStatus.NoBackup ->
|
||||
OnboardingWalletAction.ProceedBackup
|
||||
card.hasWallets() && card.backupStatus?.isActive == true ->
|
||||
BackupAction.FinishBackup
|
||||
else -> OnboardingWalletAction.GetToCreateWalletStep
|
||||
}
|
||||
store.dispatch(action)
|
||||
}
|
||||
is OnboardingNoteAction.LoadCardArtwork -> {
|
||||
// scope.launch {
|
||||
// val artwork = onboardingManager.loadArtwork()
|
||||
// store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(artwork))
|
||||
// }
|
||||
}
|
||||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
scanResponse ?: return@launch
|
||||
val result = tangemSdkManager.createProductWallet(scanResponse)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
//here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse =
|
||||
globalState.onboardingState.onboardingManager.scanResponse.copy(card = result.data)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingWalletAction.ProceedBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OnboardingWalletAction.FinishOnboarding -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
|
||||
if (scanResponse == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(HomeAction.ReadCard)
|
||||
} else {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||
scope.launch { globalState.tapWalletManager.onCardScanned(updatedScanResponse) }
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
OnboardingWalletAction.ProceedBackup -> {
|
||||
val newAction = when (val backupState = backupService.currentState) {
|
||||
BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard
|
||||
is BackupService.State.FinalizingBackupCard ->
|
||||
BackupAction.PrepareToWriteBackupCard(backupState.index)
|
||||
else -> BackupAction.IntroduceBackup
|
||||
}
|
||||
store.dispatch(newAction)
|
||||
}
|
||||
OnboardingWalletAction.OnBackPressed -> {
|
||||
when (walletState.backupState.backupStep) {
|
||||
BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo())
|
||||
BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard,
|
||||
-> {
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
is BackupStep.WriteBackupCard ->
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateScanResponseAfterBackup(
|
||||
scanResponse: ScanResponse, backupState: BackupState,
|
||||
): ScanResponse {
|
||||
val card = if (backupState.backupCardsNumber > 0) {
|
||||
val cardsCount = backupState.backupCardsNumber
|
||||
scanResponse.card.copy(
|
||||
backupStatus = Card.BackupStatus.Active(cardCount = cardsCount),
|
||||
isAccessCodeSet = true
|
||||
)
|
||||
} else {
|
||||
scanResponse.card
|
||||
}
|
||||
return scanResponse.copy(card = card)
|
||||
}
|
||||
|
||||
class BackupMiddleware {
|
||||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is BackupAction) handleBackupAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackupAction(action: BackupAction) {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
||||
when (action) {
|
||||
is BackupAction.StartBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.ScanPrimaryCard -> {
|
||||
backupService.readPrimaryCard(cardId = card?.cardId) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.StartAddingBackupCards)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.AddBackupCard -> {
|
||||
backupService.addBackupCard { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.GoToShop -> {
|
||||
store.dispatchOpenUrl(BUY_WALLET_URL)
|
||||
}
|
||||
is BackupAction.FinishAddingBackupCards -> {
|
||||
if (backupService.addedBackupCardsCount == 1) {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.AddMoreBackupCards))
|
||||
}
|
||||
if (backupService.addedBackupCardsCount == 2) {
|
||||
store.dispatchOnMain(BackupAction.ShowAccessCodeInfoScreen)
|
||||
}
|
||||
}
|
||||
is BackupAction.CheckAccessCode -> {
|
||||
if (action.accessCode.length < 4) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(
|
||||
AccessCodeError.CodeTooShort
|
||||
))
|
||||
} else {
|
||||
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
|
||||
}
|
||||
}
|
||||
is BackupAction.SaveAccessCodeConfirmation -> {
|
||||
if (action.accessCodeConfirmation == backupState.accessCode) {
|
||||
backupService.setAccessCode(action.accessCodeConfirmation)
|
||||
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(
|
||||
AccessCodeError.CodesDoNotMatch
|
||||
))
|
||||
}
|
||||
}
|
||||
is BackupAction.WritePrimaryCard -> {
|
||||
backupService.proceedBackup { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(
|
||||
BackupAction.PrepareToWriteBackupCard(1)
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.WriteBackupCard -> {
|
||||
backupService.proceedBackup { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (backupService.currentState == BackupService.State.Finished) {
|
||||
store.dispatchOnMain(BackupAction.FinishBackup)
|
||||
} else {
|
||||
store.dispatchOnMain(
|
||||
BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishBackup -> {
|
||||
(listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||
.distinct().filterNotNull()
|
||||
.forEach { cardId ->
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
}
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
backupService.primaryCardId?.let {
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(it)
|
||||
}
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.CheckForUnfinishedBackup -> {
|
||||
if (backupService.hasIncompletedBackup) {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound))
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeBackup -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Start(null, true))
|
||||
store.dispatch(OnboardingWalletAction.ProceedBackup)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
is BackupAction.DismissBackup -> {
|
||||
store.dispatch(BackupAction.FinishBackup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,134 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
class OnboardingWalletReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state)
|
||||
fun reduce(action: Action, state: AppState): OnboardingWalletState =
|
||||
internalReduce(action, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState {
|
||||
var state = appState.onboardingWalletState
|
||||
|
||||
when (action) {
|
||||
val state = appState.onboardingWalletState
|
||||
|
||||
return when (action) {
|
||||
is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState))
|
||||
is GlobalAction.Onboarding.Start -> OnboardingWalletState(
|
||||
backupState = state.backupState.copy(canSkipBackup = action.fromHomeScreen)
|
||||
)
|
||||
is OnboardingWalletAction.GetToCreateWalletStep ->
|
||||
OnboardingWalletState(step = OnboardingWalletStep.CreateWallet)
|
||||
is OnboardingWalletAction.ProceedBackup -> state.copy(step = OnboardingWalletStep.Backup)
|
||||
is OnboardingWalletAction.SetArtworkUrl -> {
|
||||
state.copy(artwork = action.artwork)
|
||||
}
|
||||
is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
|
||||
class BackupReducer {
|
||||
companion object {
|
||||
fun reduce(
|
||||
action: BackupAction, state: BackupState,
|
||||
): BackupState {
|
||||
|
||||
return when (action) {
|
||||
BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
canSkipBackup = state.canSkipBackup
|
||||
)
|
||||
|
||||
BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard)
|
||||
|
||||
BackupAction.StartAddingBackupCards -> {
|
||||
state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
}
|
||||
BackupAction.AddBackupCard.Success -> {
|
||||
state.copy(backupCardsNumber = state.backupCardsNumber + 1)
|
||||
}
|
||||
BackupAction.FinishAddingBackupCards -> {
|
||||
state
|
||||
}
|
||||
BackupAction.ShowAccessCodeInfoScreen -> {
|
||||
state.copy(backupStep = BackupStep.SetAccessCode)
|
||||
}
|
||||
BackupAction.ShowEnterAccessCodeScreen -> {
|
||||
state.copy(backupStep = BackupStep.EnterAccessCode)
|
||||
}
|
||||
is BackupAction.SaveFirstAccessCode -> {
|
||||
state.copy(
|
||||
backupStep = BackupStep.ReenterAccessCode,
|
||||
accessCode = action.accessCode
|
||||
)
|
||||
}
|
||||
is BackupAction.SetAccessCodeError -> {
|
||||
state.copy(
|
||||
accessCodeError = action.error
|
||||
)
|
||||
}
|
||||
is BackupAction.PrepareToWritePrimaryCard -> {
|
||||
if (state.primaryCardId == null) {
|
||||
state.copy(
|
||||
primaryCardId = backupService.primaryCardId,
|
||||
backupCardIds = backupService.backupCardIds,
|
||||
backupCardsNumber = backupService.backupCardIds.size,
|
||||
backupStep = BackupStep.WritePrimaryCard
|
||||
)
|
||||
} else {
|
||||
state.copy(
|
||||
backupStep = BackupStep.WritePrimaryCard
|
||||
)
|
||||
}
|
||||
}
|
||||
is BackupAction.PrepareToWriteBackupCard -> {
|
||||
if (state.primaryCardId == null) {
|
||||
state.copy(
|
||||
primaryCardId = backupService.primaryCardId,
|
||||
backupCardIds = backupService.backupCardIds,
|
||||
backupCardsNumber = backupService.backupCardIds.size,
|
||||
backupStep = BackupStep.WriteBackupCard(action.cardNumber)
|
||||
)
|
||||
} else {
|
||||
state.copy(
|
||||
backupStep = BackupStep.WriteBackupCard(action.cardNumber)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is BackupAction.FinishBackup -> {
|
||||
state.copy(
|
||||
backupStep = BackupStep.Finished
|
||||
)
|
||||
}
|
||||
|
||||
BackupAction.OnAccessCodeDialogClosed -> {
|
||||
state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
}
|
||||
|
||||
BackupAction.WritePrimaryCard -> state
|
||||
is BackupAction.WriteBackupCard -> state
|
||||
is BackupAction.SaveAccessCodeConfirmation -> state
|
||||
BackupAction.ScanPrimaryCard -> state
|
||||
BackupAction.ShowEnterAccessCodeScreen -> state
|
||||
BackupAction.ShowReenterAccessCodeScreen -> state
|
||||
BackupAction.AddBackupCard -> state
|
||||
BackupAction.DismissBackup -> state
|
||||
is BackupAction.LoadBackupCardArtwork -> state
|
||||
is BackupAction.CheckAccessCode -> state
|
||||
BackupAction.GoToShop -> state
|
||||
BackupAction.DetermineBackupStep -> state
|
||||
BackupAction.CheckForUnfinishedBackup -> state
|
||||
BackupAction.DiscardBackup -> state
|
||||
BackupAction.ResumeBackup -> state
|
||||
BackupAction.DiscardSavedBackup -> state
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,81 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingWalletState(
|
||||
val any: String? = null
|
||||
) : StateType
|
||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||
val backupState: BackupState = BackupState(),
|
||||
val artwork: Artwork? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
) : StateType {
|
||||
|
||||
fun getProgressStep(): Int {
|
||||
return when {
|
||||
step == OnboardingWalletStep.CreateWallet -> {
|
||||
1
|
||||
}
|
||||
step == OnboardingWalletStep.Backup -> {
|
||||
when (backupState.backupStep) {
|
||||
BackupStep.InitBackup -> 2
|
||||
BackupStep.ScanOriginCard -> 3
|
||||
|
||||
BackupStep.AddBackupCards -> 4
|
||||
BackupStep.EnterAccessCode -> 4
|
||||
BackupStep.ReenterAccessCode -> 4
|
||||
BackupStep.SetAccessCode -> 4
|
||||
BackupStep.WritePrimaryCard, is BackupStep.WriteBackupCard -> 5
|
||||
BackupStep.Finished -> 6
|
||||
}
|
||||
}
|
||||
step == OnboardingWalletStep.Done -> 6
|
||||
else -> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class OnboardingWalletStep {
|
||||
None, CreateWallet, TopUpWallet, Backup, Done
|
||||
None, CreateWallet, Backup, Done
|
||||
}
|
||||
|
||||
data class BackupState(
|
||||
val primaryCardId: String? = null,
|
||||
val backupCardsNumber: Int = 0,
|
||||
val backupCardIds: List<CardId> = emptyList(),
|
||||
val backupCardsArtworks: Map<CardId, Bitmap> = emptyMap(),
|
||||
val accessCode: String = "",
|
||||
val accessCodeError: AccessCodeError? = null,
|
||||
val backupStep: BackupStep = BackupStep.InitBackup,
|
||||
val maxBackupCards: Int = 2,
|
||||
val canSkipBackup: Boolean = true
|
||||
)
|
||||
|
||||
enum class AccessCodeError {
|
||||
CodeTooShort, CodesDoNotMatch
|
||||
}
|
||||
|
||||
typealias CardId = String
|
||||
|
||||
sealed class BackupStep {
|
||||
object InitBackup : BackupStep()
|
||||
object ScanOriginCard : BackupStep()
|
||||
object AddBackupCards : BackupStep()
|
||||
object SetAccessCode : BackupStep()
|
||||
object EnterAccessCode : BackupStep()
|
||||
object ReenterAccessCode : BackupStep()
|
||||
object WritePrimaryCard : BackupStep()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupStep()
|
||||
object Finished : BackupStep()
|
||||
}
|
||||
|
||||
sealed class BackupDialog: StateDialog {
|
||||
object AddMoreBackupCards : StateDialog
|
||||
object BackupInProgress : StateDialog
|
||||
object UnfinishedBackupFound : StateDialog
|
||||
object ConfirmDiscardingBackup : StateDialog
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.PropertyValuesHolder
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapView
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class BackupCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
val getTopOfAnchorViewForActivateState: () -> Float,
|
||||
) {
|
||||
|
||||
fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
val animator = createAnimator(animate, onEnd)
|
||||
animator.playTogether(
|
||||
createAnimator(BackupCardType.ORIGIN, createWelcomeProperties(BackupCardType.ORIGIN)),
|
||||
createAnimator(BackupCardType.FIRST_BACKUP, createWelcomeProperties(BackupCardType.FIRST_BACKUP)),
|
||||
createAnimator(BackupCardType.SECOND_BACKUP, createWelcomeProperties(BackupCardType.SECOND_BACKUP))
|
||||
)
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toFolded(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
val animator = createAnimator(animate, onEnd)
|
||||
animator.playTogether(
|
||||
createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)),
|
||||
createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)),
|
||||
createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP))
|
||||
)
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
val animator = createAnimator(animate, onEnd)
|
||||
animator.playTogether(
|
||||
createAnimator(BackupCardType.ORIGIN, createFanProperties(BackupCardType.ORIGIN)),
|
||||
createAnimator(BackupCardType.FIRST_BACKUP, createFanProperties(BackupCardType.FIRST_BACKUP)),
|
||||
createAnimator(BackupCardType.SECOND_BACKUP, createFanProperties(BackupCardType.SECOND_BACKUP))
|
||||
)
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
val animator = createAnimator(animate, onEnd)
|
||||
animator.playTogether(
|
||||
createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)),
|
||||
createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)),
|
||||
createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP))
|
||||
)
|
||||
leapfrogWidget.fold {
|
||||
animator.doOnEnd {
|
||||
leapfrogWidget.initViews()
|
||||
leapfrogWidget.unfold()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
//
|
||||
// fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
// val animator = createAnimator(animate, onEnd)
|
||||
// animator.playTogether(
|
||||
// createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)),
|
||||
// createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second))
|
||||
// )
|
||||
// animator.start()
|
||||
// }
|
||||
|
||||
private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet {
|
||||
return AnimatorSet().apply {
|
||||
duration = if (animate) 400 else 0
|
||||
doOnEnd { onEnd() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAnimator(
|
||||
cardType: BackupCardType,
|
||||
properties: CardProperties,
|
||||
): ObjectAnimator {
|
||||
val view = getLeapViewByCardNumber(cardType).view
|
||||
val animator = ObjectAnimator.ofPropertyValuesHolder(view,
|
||||
*properties.createValuesHolders().toTypedArray())
|
||||
view.elevation = properties.elevation
|
||||
return animator
|
||||
}
|
||||
|
||||
private fun createWelcomeProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 440f,
|
||||
yTranslation = 0f,
|
||||
rotation = 75f,
|
||||
elevation = 2f,
|
||||
scale = .9f,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 10f,
|
||||
yTranslation = -100f,
|
||||
rotation = 100f,
|
||||
elevation = 1f,
|
||||
scale = .9f,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = -440f,
|
||||
yTranslation = -100f,
|
||||
rotation = 70f,
|
||||
elevation = 0f,
|
||||
scale = .9f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFanProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 5f,
|
||||
elevation = 2f,
|
||||
scale = 1f,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = -80f,
|
||||
rotation = -5f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 10f,
|
||||
yTranslation = -170f,
|
||||
rotation = -20f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLeapfrogProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 2f,
|
||||
scale = 1f,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActivateProperties(cardType: BackupCardType): CardProperties {
|
||||
val topOfAnchorView = getTopOfAnchorViewForActivateState()
|
||||
val twinProperties = CardProperties.from(getLeapViewByCardNumber(cardType).state)
|
||||
return twinProperties.copy(
|
||||
xTranslation = twinProperties.xTranslation,
|
||||
yTranslation = twinProperties.yTranslation - topOfAnchorView,
|
||||
rotation = 0f,
|
||||
scale = twinProperties.scale - 0.4f,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getLeapViewByCardNumber(cardType: BackupCardType): LeapView {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> leapfrogWidget.getViewByPosition(0)
|
||||
BackupCardType.FIRST_BACKUP -> leapfrogWidget.getViewByPosition(1)
|
||||
BackupCardType.SECOND_BACKUP -> leapfrogWidget.getViewByPosition(2)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOriginCardView(): ImageView {
|
||||
return getLeapViewByCardNumber(BackupCardType.ORIGIN).view as ImageView
|
||||
}
|
||||
|
||||
fun getFirstBackupCardView(): ImageView {
|
||||
return getLeapViewByCardNumber(BackupCardType.FIRST_BACKUP).view as ImageView
|
||||
}
|
||||
|
||||
fun getSecondBackupCardView(): ImageView {
|
||||
return getLeapViewByCardNumber(BackupCardType.SECOND_BACKUP).view as ImageView
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class BackupCardType { ORIGIN, FIRST_BACKUP, SECOND_BACKUP }
|
||||
|
||||
|
||||
private data class CardProperties(
|
||||
val xTranslation: Float = 0f,
|
||||
val yTranslation: Float = 0f,
|
||||
val rotation: Float = 0f,
|
||||
val elevation: Float = 0f,
|
||||
val scale: Float = 1.1f,
|
||||
) {
|
||||
|
||||
fun createValuesHolders(): List<PropertyValuesHolder> {
|
||||
return listOf(
|
||||
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation),
|
||||
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation),
|
||||
PropertyValuesHolder.ofFloat(View.ROTATION, rotation),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, scale),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, scale),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(leapViewState: LeapViewState): CardProperties {
|
||||
val leapViewProperties = leapViewState.properties
|
||||
|
||||
return CardProperties(
|
||||
yTranslation = leapViewProperties.yTranslation,
|
||||
elevation = leapViewProperties.elevationEnd,
|
||||
scale = leapViewProperties.scale,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.item_backup_info_adapter.view.*
|
||||
|
||||
class BackupInfoAdapter : RecyclerView.Adapter<BackupInfoViewHolder>() {
|
||||
|
||||
private val data = backupInfoSnippets
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder =
|
||||
BackupInfoViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_backup_info_adapter, parent, false))
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
override fun onBindViewHolder(holder: BackupInfoViewHolder, position: Int) =
|
||||
holder.itemView.run {
|
||||
tv_header.text = getString(data[position].header)
|
||||
tv_body.text = getString(data[position].body)
|
||||
}
|
||||
}
|
||||
|
||||
private class BackupInfo(
|
||||
@StringRes val header: Int,
|
||||
@StringRes val body: Int,
|
||||
)
|
||||
|
||||
private val backupInfoSnippets = listOf(
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_first,
|
||||
R.string.onboarding_wallet_info_subtitle_first
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_second,
|
||||
R.string.onboarding_wallet_info_subtitle_second
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_third,
|
||||
R.string.onboarding_wallet_info_subtitle_third
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_fourth,
|
||||
R.string.onboarding_wallet_info_subtitle_fourth
|
||||
),
|
||||
|
||||
)
|
||||
|
||||
class BackupInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
|
@ -0,0 +1,385 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_onboarding_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_buttons_add_cards.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_buttons_common.*
|
||||
import kotlinx.android.synthetic.main.view_confetti.*
|
||||
import kotlinx.android.synthetic.main.view_onboarding_progress.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState>, FragmentOnBackPressedHandler {
|
||||
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
private lateinit var cardsWidget: BackupCardsWidget
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val leapfrog = LeapfrogWidget(fl_cards_container)
|
||||
cardsWidget = BackupCardsWidget(leapfrog) { 200f }
|
||||
startPostponedEnterTransition()
|
||||
|
||||
view_pager_backup_info.adapter = BackupInfoAdapter()
|
||||
TabLayoutMediator(tab_layout_backup_info, view_pager_backup_info) { tab, position ->
|
||||
//Some implementation
|
||||
}.attach()
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
|
||||
store.dispatch(OnboardingWalletAction.Init)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
addBackPressHandler(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingWalletState == newState.onboardingWalletState
|
||||
}.select { it.onboardingWalletState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingWalletState) {
|
||||
if (activity == null) return
|
||||
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
|
||||
state.artwork?.artwork?.let { imv_front_card.setImageBitmap(it) }
|
||||
pb_state.max = 6
|
||||
pb_state.progress = state.getProgressStep()
|
||||
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.None -> {
|
||||
}
|
||||
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingWalletStep.Backup -> setBackupState(state.backupState)
|
||||
OnboardingWalletStep.Done -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() {
|
||||
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
|
||||
btn_alternative_action.hide()
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_getting_started)
|
||||
|
||||
tv_header.setText(R.string.onboarding_create_wallet_header)
|
||||
tv_body.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
cardsWidget.toFolded()
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
|
||||
|
||||
private fun setBackupState(state: BackupState) {
|
||||
when (state.backupStep) {
|
||||
BackupStep.InitBackup -> showBackupIntro(state)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard(state)
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
BackupStep.EnterAccessCode -> showEnterAccessCode(state)
|
||||
BackupStep.ReenterAccessCode -> showReenterAccessCode(state)
|
||||
is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state)
|
||||
is BackupStep.WriteBackupCard -> showWriteBackupCard(state)
|
||||
BackupStep.Finished -> showSuccess(state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState) {
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
|
||||
cardsWidget.toWelcome()
|
||||
|
||||
tv_header.hide()
|
||||
tv_body.hide()
|
||||
view_pager_backup_info.show()
|
||||
tab_layout_backup_info.show()
|
||||
|
||||
btn_main_action.text = getText(R.string.onboarding_button_backup_now)
|
||||
btn_alternative_action.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btn_alternative_action.show(state.canSkipBackup)
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
|
||||
btn_alternative_action.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
|
||||
private fun showScanOriginCard(state: BackupState) {
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
|
||||
|
||||
cardsWidget.toFolded()
|
||||
|
||||
tv_header.show()
|
||||
tv_body.show()
|
||||
view_pager_backup_info.hide()
|
||||
tab_layout_backup_info.hide()
|
||||
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
|
||||
tv_header.text = getText(R.string.onboarding_title_scan_origin_card)
|
||||
tv_body.text = getString(
|
||||
R.string.onboarding_subtitle_scan_origin_card,
|
||||
)
|
||||
|
||||
btn_main_action.text = getString(R.string.onboarding_button_scan_origin_card)
|
||||
btn_alternative_action.hide()
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
}
|
||||
|
||||
private fun showAddBackupCards(state: BackupState) {
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
|
||||
accessCodeDialog?.dismiss()
|
||||
accessCodeDialog = null
|
||||
|
||||
layout_buttons_add_cards.show()
|
||||
layout_buttons_common.hide()
|
||||
if (state.backupCardsNumber < state.maxBackupCards) {
|
||||
btn_add_card.text = getText(R.string.onboarding_button_add_backup_card)
|
||||
btn_add_card.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
|
||||
} else {
|
||||
btn_add_card.text = "You hit the maximum"
|
||||
btn_add_card.isEnabled = false
|
||||
}
|
||||
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
cardsWidget.toFan()
|
||||
tv_header.text = getText(R.string.onboarding_title_no_backup_cards)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards)
|
||||
cardsWidget.getFirstBackupCardView().alpha = 0.6f
|
||||
cardsWidget.getSecondBackupCardView().alpha = 0.2f
|
||||
}
|
||||
1 -> {
|
||||
tv_header.text = getText(R.string.onboarding_title_one_backup_card)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_one_backup_card)
|
||||
|
||||
cardsWidget.getFirstBackupCardView().animate().alpha(1f).setDuration(400)
|
||||
cardsWidget.getSecondBackupCardView().alpha = 0.2f
|
||||
}
|
||||
2 -> {
|
||||
tv_header.text = getText(R.string.onboarding_title_two_backup_cards)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_two_backup_cards)
|
||||
cardsWidget.getFirstBackupCardView().alpha = 1f
|
||||
cardsWidget.getSecondBackupCardView().animate().alpha(1f).setDuration(400)
|
||||
}
|
||||
}
|
||||
|
||||
btn_continue.text = getText(R.string.onboarding_button_finalize_backup)
|
||||
btn_continue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
|
||||
btn_continue.isEnabled = state.backupCardsNumber != 0
|
||||
}
|
||||
|
||||
private fun showSetAccessCode() {
|
||||
accessCodeDialog = AccessCodeDialog(requireContext()).apply {
|
||||
dismissWithAnimation = true
|
||||
create()
|
||||
setOnCancelListener {
|
||||
store.dispatch(BackupAction.OnAccessCodeDialogClosed)
|
||||
}
|
||||
show()
|
||||
showInfoScreen()
|
||||
val view =
|
||||
delegate.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
|
||||
(view?.parent as? ViewGroup)?.let { TransitionManager.beginDelayedTransition(it) }
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
view?.let { it.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEnterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showEnterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) }
|
||||
|
||||
}
|
||||
|
||||
private fun showReenterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showReenterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) }
|
||||
|
||||
}
|
||||
|
||||
private fun showWritePrimaryCard(state: BackupState) {
|
||||
accessCodeDialog?.dismiss()
|
||||
|
||||
prepareViewForFinalizeStep()
|
||||
cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2)
|
||||
|
||||
cardsWidget.toLeapfrog() {
|
||||
cardsWidget.getFirstBackupCardView().alpha = 0.6f
|
||||
cardsWidget.getSecondBackupCardView().alpha = 0.2f
|
||||
}
|
||||
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
tv_header.text = getText(R.string.onboarding_title_prepare_origin)
|
||||
tv_body.text = getString(
|
||||
R.string.onboarding_subtitle_scan_primary_card_format,
|
||||
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }
|
||||
)
|
||||
btn_main_action.text = getText(R.string.onboarding_button_backup_origin)
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
|
||||
|
||||
}
|
||||
|
||||
private fun prepareViewForFinalizeStep() {
|
||||
layout_buttons_add_cards.hide()
|
||||
layout_buttons_common.show()
|
||||
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_button_finalize_backup)
|
||||
|
||||
imv_card_background.hide()
|
||||
|
||||
cardsWidget.toLeapfrog()
|
||||
|
||||
// imv_front_card.alpha = 1f
|
||||
// imv_first_backup_card.alpha = 1f
|
||||
// imv_second_backup_card.alpha = 1f
|
||||
|
||||
btn_alternative_action.hide()
|
||||
}
|
||||
|
||||
private fun showWriteBackupCard(state: BackupState) {
|
||||
prepareViewForFinalizeStep()
|
||||
|
||||
cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2)
|
||||
|
||||
val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1
|
||||
when (cardNumber) {
|
||||
1 -> cardsWidget.leapfrogWidget.leap() {
|
||||
cardsWidget.getOriginCardView().alpha = 0.4f
|
||||
cardsWidget.getFirstBackupCardView().alpha = 0.2f
|
||||
cardsWidget.getSecondBackupCardView().alpha = 1f
|
||||
}
|
||||
2 -> cardsWidget.leapfrogWidget.leap() {
|
||||
cardsWidget.getOriginCardView().alpha = 0.4f
|
||||
cardsWidget.getFirstBackupCardView().alpha = 0.2f
|
||||
cardsWidget.getSecondBackupCardView().alpha = 1f
|
||||
}
|
||||
}
|
||||
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
tv_header.text =
|
||||
getString(R.string.onboarding_title_backup_card_format, cardNumber)
|
||||
tv_body.text = getString(
|
||||
R.string.onboarding_subtitle_scan_primary_card_format,
|
||||
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1])
|
||||
)
|
||||
btn_main_action.text = getString(
|
||||
R.string.onboarding_button_backup_card_format,
|
||||
cardNumber
|
||||
)
|
||||
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) }
|
||||
}
|
||||
|
||||
private fun showSuccess(state: BackupState) {
|
||||
tv_header.text = getText(R.string.onboarding_done_header)
|
||||
val text = when (state.backupCardsNumber) {
|
||||
1 -> getText(R.string.onboarding_subtitle_success_backup_one_card)
|
||||
2 -> getText(R.string.onboarding_subtitle_success_backup)
|
||||
else -> getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
|
||||
}
|
||||
|
||||
tv_header.show()
|
||||
tv_body.show()
|
||||
view_pager_backup_info.hide()
|
||||
tab_layout_backup_info.hide()
|
||||
|
||||
tv_body.text = text
|
||||
btn_main_action.text = getText(R.string.common_continue)
|
||||
btn_alternative_action.hide()
|
||||
btn_main_action.setOnClickListener {
|
||||
lav_confetti.cancelAnimation()
|
||||
lav_confetti.hide()
|
||||
store.dispatch(OnboardingWalletAction.FinishOnboarding)
|
||||
}
|
||||
|
||||
cardsWidget.leapfrogWidget.fold {
|
||||
fl_cards_container?.hide()
|
||||
imv_card_background?.hide()
|
||||
lav_confetti?.show()
|
||||
lav_confetti?.playAnimation()
|
||||
imv_success?.alpha = 0f
|
||||
imv_success?.show()
|
||||
|
||||
imv_success?.animate()
|
||||
?.alpha(1f)
|
||||
?.setDuration(400)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.shop_menu -> {
|
||||
store.dispatchOpenUrl(BUY_WALLET_URL)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.shop, menu)
|
||||
|
||||
val backupStep = store.state.onboardingWalletState.backupState.backupStep
|
||||
val shopMenuShouldBeVisible =
|
||||
backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards
|
||||
menu.getItem(0).isVisible = shopMenuShouldBeVisible
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingWalletAction.OnBackPressed)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.AccessCodeError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.layout_backup_access_code.*
|
||||
import kotlinx.android.synthetic.main.layout_backup_access_code_info.*
|
||||
import kotlinx.android.synthetic.main.layout_backup_access_code_submit.*
|
||||
|
||||
class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
|
||||
|
||||
|
||||
init {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
val dialogView = LayoutInflater.from(context).inflate(R.layout.layout_backup_access_code, null)
|
||||
setContentView(dialogView)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
override fun setContentView(view: View) {
|
||||
super.setContentView(view)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
|
||||
fun showInfoScreen() {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title)
|
||||
layout_backup_access_code_info.show()
|
||||
layout_backup_access_code_submit.hide()
|
||||
btn_access_code_create.setOnClickListener {
|
||||
store.dispatch(BackupAction.ShowEnterAccessCodeScreen)
|
||||
}
|
||||
}
|
||||
|
||||
fun showEnterAccessCode() {
|
||||
layout_backup_access_code_info.hide()
|
||||
layout_backup_access_code_submit.show()
|
||||
access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title)
|
||||
btn_access_code_submit.text = context.getText(R.string.common_continue)
|
||||
btn_access_code_submit.setOnClickListener {
|
||||
store.dispatch(BackupAction.CheckAccessCode(et_access_code.text.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
fun showReenterAccessCode() {
|
||||
layout_backup_access_code_info.hide()
|
||||
layout_backup_access_code_submit.show()
|
||||
access_code_title.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
|
||||
et_access_code.setText("")
|
||||
btn_access_code_submit.text = context.getText(R.string.common_submit)
|
||||
btn_access_code_submit.setOnClickListener {
|
||||
store.dispatch(BackupAction.SaveAccessCodeConfirmation(et_access_code.text.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
fun showError(error: AccessCodeError?) {
|
||||
when (error) {
|
||||
AccessCodeError.CodeTooShort ->
|
||||
til_access_code.error = context.getString(R.string.onboarding_access_code_too_short)
|
||||
AccessCodeError.CodesDoNotMatch ->
|
||||
til_access_code.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
|
||||
null -> til_access_code.error = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class AddMoreBackupCardsDialog {
|
||||
companion object {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.alert_title)
|
||||
setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added)
|
||||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ShowAccessCodeInfoScreen)
|
||||
}
|
||||
setNegativeButton(R.string.onboarding_button_add_more_cards) { _, _ ->
|
||||
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class BackupInProgressDialog {
|
||||
companion object {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.alert_title)
|
||||
setMessage(R.string.onboarding_backup_exit_warning)
|
||||
setPositiveButton(R.string.warning_button_ok) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class ConfirmDiscardingBackupDialog {
|
||||
companion object {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.welcome_interrupted_backup_discard_title)
|
||||
setMessage(R.string.welcome_interrupted_backup_discard_message)
|
||||
setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ ->
|
||||
store.dispatch(BackupAction.ResumeBackup)
|
||||
}
|
||||
setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ ->
|
||||
store.dispatch(BackupAction.DiscardSavedBackup)
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
setCancelable(false)
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class UnfinishedBackupFoundDialog {
|
||||
companion object {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.alert_title)
|
||||
setMessage(R.string.welcome_interrupted_backup_alert_message)
|
||||
setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
store.dispatch(BackupAction.ResumeBackup)
|
||||
}
|
||||
setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup))
|
||||
}
|
||||
setCancelable(false)
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.features.onboarding.service
|
||||
|
||||
//class OnboardingWalletService(
|
||||
// override val fromScreen: AppScreen,
|
||||
// override var scanResponse: ScanResponse,
|
||||
// cardInfoStorage: UsedCardsPrefStorage,
|
||||
// walletManagerFactory: WalletManagerFactory,
|
||||
//) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) {
|
||||
//
|
||||
// override fun navigateToScreen(): AppScreen {
|
||||
// return AppScreen.OnboardingWallet
|
||||
// }
|
||||
//}
|
||||
|
|
@ -15,6 +15,7 @@ import com.tangem.tap.common.extensions.*
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.domain.TangemSigner
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
|
||||
|
|
@ -152,21 +153,32 @@ private fun sendTransaction(
|
|||
if (card.isStart2Coin) {
|
||||
tangemSdk.config.linkedTerminal = false
|
||||
}
|
||||
val signer = TangemSigner(
|
||||
tangemSdk = tangemSdk, initialMessage = action.messageForSigner
|
||||
) { signResponse ->
|
||||
|
||||
val signer = TangemSigner(tangemSdk, action.messageForSigner) { signResponse ->
|
||||
store.dispatch(
|
||||
GlobalAction.UpdateWalletSignedHashes(
|
||||
walletSignedHashes = signResponse.totalSignedHashes,
|
||||
walletPublicKey = walletManager.wallet.publicKey,
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
remainingSignatures = signResponse.remainingSignatures
|
||||
)
|
||||
)
|
||||
}
|
||||
val result = (walletManager as TransactionSender).send(txData, signer)
|
||||
|
||||
val sendResult = try {
|
||||
(walletManager as TransactionSender).send(txData, signer)
|
||||
} catch (ex: Exception) {
|
||||
FirebaseCrashlytics.getInstance().recordException(ex)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
|
||||
store.dispatchErrorNotification(TapError.CustomError(ex.localizedMessage ?: "Unknown error"))
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
tangemSdk.config.linkedTerminal = isLinkedTerminal
|
||||
when (result) {
|
||||
when (sendResult) {
|
||||
is SimpleResult.Success -> {
|
||||
FirebaseAnalyticsHandler.triggerEvent(
|
||||
event = AnalyticsEvent.TRANSACTION_IS_SENT,
|
||||
|
|
@ -191,20 +203,18 @@ private fun sendTransaction(
|
|||
}
|
||||
}
|
||||
is SimpleResult.Failure -> {
|
||||
when (result.error) {
|
||||
when (sendResult.error) {
|
||||
is CreateAccountUnderfunded -> {
|
||||
val error = result.error as CreateAccountUnderfunded
|
||||
val error = sendResult.error as CreateAccountUnderfunded
|
||||
val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0"
|
||||
val symbol = error.minReserve.currencySymbol
|
||||
dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol))))
|
||||
}
|
||||
is SendException -> {
|
||||
result.error?.let {
|
||||
FirebaseCrashlytics.getInstance().recordException(it)
|
||||
}
|
||||
sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) }
|
||||
}
|
||||
is Throwable -> {
|
||||
val throwable = result.error as Throwable
|
||||
val throwable = sendResult.error as Throwable
|
||||
val message = throwable.message
|
||||
val infoHolder = store.state.globalState.feedbackManager?.infoHolder
|
||||
when {
|
||||
|
|
@ -228,13 +238,13 @@ private fun sendTransaction(
|
|||
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
|
||||
}
|
||||
else -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
(sendResult.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
|
||||
mapOf(
|
||||
FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
|
||||
to walletManager.wallet.blockchain.currency),
|
||||
to walletManager.wallet.blockchain.currency),
|
||||
card = card,
|
||||
)
|
||||
}
|
||||
|
|
@ -303,7 +313,7 @@ fun createValidateTransactionError(errorList: EnumSet<TransactionError>, walletM
|
|||
|
||||
private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) {
|
||||
val isSendingToPayIdEnabled =
|
||||
appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -285,8 +285,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
override fun onDestroyView() {
|
||||
keyboardObserver.unregisterListener()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
store.dispatch(ReleaseSendState)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,12 @@ import com.tangem.blockchain.common.Token
|
|||
import com.tangem.wallet.R
|
||||
|
||||
sealed class CurrencyListItem {
|
||||
var isAdded: Boolean = false
|
||||
var isLock: Boolean = false
|
||||
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
|
||||
data class TitleListItem(
|
||||
@StringRes val titleResId: Int,
|
||||
var isContentShown: Boolean = true,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class TokensAction : Action {
|
||||
|
||||
object ResetState : TokensAction()
|
||||
|
||||
object LoadCurrencies : TokensAction() {
|
||||
data class Success(val currencies: List<CurrencyListItem>) : TokensAction()
|
||||
}
|
||||
|
||||
object LoadCardTokens : TokensAction() {
|
||||
data class Success(val tokens: List<Token>) : TokensAction()
|
||||
}
|
||||
|
||||
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
|
||||
|
||||
data class SaveChanges(val addedItems: List<CurrencyListItem>) : TokensAction()
|
||||
}
|
||||
|
|
@ -1,9 +1,25 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class TokensMiddleware {
|
||||
|
|
@ -12,22 +28,111 @@ class TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> {
|
||||
val card = state()?.globalState?.scanResponse?.card
|
||||
val isTestcard = card?.isTestCard ?: false
|
||||
val tokens = currenciesRepository.getPopularTokens(isTestcard)
|
||||
val blockchains = currenciesRepository.getBlockchains(
|
||||
cardFirmware = card?.firmwareVersion,
|
||||
isTestNet = isTestcard
|
||||
)
|
||||
val currencies = CurrencyListItem.createListOfCurrencies(
|
||||
blockchains, tokens
|
||||
)
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
}
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
val isTestcard = scanResponse.card.isTestCard
|
||||
|
||||
val tokens = currenciesRepository.getPopularTokens(isTestcard)
|
||||
val blockchains = currenciesRepository.getBlockchains(
|
||||
cardFirmware = scanResponse.card.firmwareVersion,
|
||||
isTestNet = isTestcard
|
||||
)
|
||||
val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList()
|
||||
if (scanResponse.isTangemWallet()) {
|
||||
currencies.forEach {
|
||||
when (it) {
|
||||
is CurrencyListItem.TitleListItem -> {
|
||||
}
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
val firstCurve = it.blockchain.getSupportedCurves()?.firstOrNull()
|
||||
if (firstCurve == EllipticCurve.Ed25519) {
|
||||
it.isLock = true
|
||||
}
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
val firstCurve = it.token.blockchain.getSupportedCurves()?.firstOrNull()
|
||||
if (firstCurve == EllipticCurve.Ed25519) {
|
||||
it.isLock = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
}
|
||||
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
|
||||
val candidatesToAdd = action.addedItems
|
||||
if (candidatesToAdd.isEmpty()) return
|
||||
|
||||
val blockchains = candidatesToAdd.filterIsInstance<CurrencyListItem.BlockchainListItem>()
|
||||
.map { it.blockchain }
|
||||
val tokens = candidatesToAdd.filterIsInstance<CurrencyListItem.TokenListItem>()
|
||||
.map { it.token }
|
||||
if (blockchains.isEmpty() && tokens.isEmpty()) return
|
||||
|
||||
if (scanResponse.isTangemWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, blockchains, tokens)
|
||||
} else {
|
||||
submitAdd(blockchains, tokens)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>
|
||||
) {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return
|
||||
|
||||
val tokenBlockchains = tokens.map { it.blockchain }
|
||||
val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct()
|
||||
.mapNotNull { it.derivationPath() }
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf()
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath }
|
||||
|
||||
val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data
|
||||
alreadyDerivedKeys.addAll(newDerivedKeys)
|
||||
val updatedScanResponse = scanResponse.copy(
|
||||
derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList())
|
||||
)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
|
||||
submitAdd(blockchains, tokens)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchErrorNotification(TapError.CustomError("Error adding tokens"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun submitAdd(blockchains: List<Blockchain>, tokens: List<Token>) {
|
||||
(blockchains.map {
|
||||
WalletAction.MultiWallet.AddBlockchain(it)
|
||||
} + tokens.map {
|
||||
WalletAction.MultiWallet.AddToken(it)
|
||||
}).forEach { store.dispatch(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -17,28 +17,23 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
|
||||
val tokensState = state.tokensState
|
||||
return when (action) {
|
||||
is TokensAction.ResetState -> TokensState()
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies())
|
||||
}
|
||||
is TokensAction.LoadCardTokens.Success -> {
|
||||
tokensState.copy(addedTokens = LinkedHashSet(
|
||||
action.tokens.map { TokenWithAmount(it, null) }
|
||||
))
|
||||
}
|
||||
|
||||
is TokensAction.ToggleShowTokensForBlockchain -> {
|
||||
if (action.isShown) {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.removeTokensForBlockchain(action.blockchain)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
} else {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.addTokensForBlockchain(action.blockchain, tokensState.currencies)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
|
|
@ -57,30 +58,35 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
setupPopularTokensRecyclerView()
|
||||
btn_tokens_save_changes.setOnClickListener {
|
||||
store.dispatch(TokensAction.SaveChanges(viewAdapter.getAddedItems()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setupPopularTokensRecyclerView() {
|
||||
viewAdapter = CurrenciesAdapter()
|
||||
viewAdapter.setOnItemAddListener {
|
||||
btn_tokens_save_changes.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
|
||||
}
|
||||
|
||||
rv_popular_tokens.layoutManager = LinearLayoutManager(context)
|
||||
rv_popular_tokens.adapter = viewAdapter
|
||||
}
|
||||
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
if (activity == null) return
|
||||
|
||||
viewAdapter.addedCurrencies = state.addedCurrencies
|
||||
viewAdapter.submitUnfilteredList(state.shownCurrencies)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_search -> {
|
||||
true
|
||||
}
|
||||
R.id.menu_search -> true
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +107,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.loadCurrenciesIcon
|
||||
|
|
@ -14,7 +17,6 @@ import com.tangem.tap.common.extensions.show
|
|||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.tokens.redux.CurrencyListItem
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.item_currency_subtitle.view.*
|
||||
|
|
@ -27,11 +29,28 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
|
||||
private var unfilteredList = listOf<CurrencyListItem>()
|
||||
|
||||
private val currentlyAddedItems = mutableListOf<CurrencyListItem>()
|
||||
|
||||
private var vhOnItemAddListener: ((CurrencyListItem) -> Unit) = {
|
||||
currentlyAddedItems.add(it)
|
||||
onItemAddListener?.invoke()
|
||||
}
|
||||
private var onItemAddListener: VoidCallback? = null
|
||||
|
||||
fun submitUnfilteredList(list: List<CurrencyListItem>) {
|
||||
unfilteredList = list
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
fun setOnItemAddListener(itemAddListener: VoidCallback) {
|
||||
onItemAddListener = itemAddListener
|
||||
if (currentlyAddedItems.isNotEmpty()) itemAddListener()
|
||||
}
|
||||
|
||||
fun getAddedItems(): List<CurrencyListItem> {
|
||||
return currentlyAddedItems.toList()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (currentList[position]) {
|
||||
is CurrencyListItem.TitleListItem -> 0
|
||||
|
|
@ -47,11 +66,13 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
)
|
||||
1 -> CurrenciesViewHolder(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_popular_token, parent, false)
|
||||
.inflate(R.layout.item_popular_token, parent, false),
|
||||
vhOnItemAddListener
|
||||
)
|
||||
else -> CurrenciesViewHolder(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_popular_token, parent, false)
|
||||
.inflate(R.layout.item_popular_token, parent, false),
|
||||
vhOnItemAddListener
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +82,7 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
if (holder is TitleViewHolder && listItem is CurrencyListItem.TitleListItem) {
|
||||
holder.bind(listItem)
|
||||
} else if (holder is CurrenciesViewHolder) {
|
||||
holder.bind(listItem, addedCurrencies)
|
||||
holder.bind(listItem, addedCurrencies, currentlyAddedItems)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,15 +107,15 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
is CurrencyListItem.BlockchainListItem -> {
|
||||
element.blockchain.currency.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized) ||
|
||||
element.blockchain.fullName.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized)
|
||||
element.blockchain.fullName.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized)
|
||||
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
element.token.name.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized) ||
|
||||
element.token.symbol.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized)
|
||||
element.token.symbol.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized)
|
||||
}
|
||||
is CurrencyListItem.TitleListItem -> true
|
||||
}
|
||||
|
|
@ -105,56 +126,85 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
|
|||
submitList(list)
|
||||
}
|
||||
|
||||
class CurrenciesViewHolder(val view: View) :
|
||||
RecyclerView.ViewHolder(view) {
|
||||
fun bind(currency: CurrencyListItem, addedCurrencies: CardCurrencies?) {
|
||||
class CurrenciesViewHolder(
|
||||
private val view: View,
|
||||
private val onItemAddListener: ((CurrencyListItem) -> Unit),
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun bind(
|
||||
currency: CurrencyListItem,
|
||||
addedCurrencies: CardCurrencies?,
|
||||
currentlyAddedItems: MutableList<CurrencyListItem>
|
||||
) {
|
||||
when (currency) {
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
val blockchain = currency.blockchain
|
||||
view.tv_currency_name.text = blockchain.fullName
|
||||
view.tv_currency_symbol.text = blockchain.currency
|
||||
val isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true
|
||||
view.btn_add_token.show(!isAdded)
|
||||
view.btn_token_added.show(isAdded)
|
||||
|
||||
Picasso.get().loadCurrenciesIcon(
|
||||
imageView = view.iv_currency,
|
||||
textView = view.tv_token_letter,
|
||||
blockchain = blockchain, token = null
|
||||
)
|
||||
|
||||
view.btn_add_token.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
|
||||
view.btn_add_token.hide()
|
||||
view.btn_token_added.show()
|
||||
}
|
||||
val addedBlockchains = addedCurrencies?.blockchains ?: listOf()
|
||||
val currentlyAdded = currentlyAddedItems.filterIsInstance<CurrencyListItem.BlockchainListItem>()
|
||||
bindBlockchain(currency, addedBlockchains, currentlyAdded)
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
val token = currency.token
|
||||
view.tv_currency_name.text = token.name
|
||||
view.tv_currency_symbol.text = token.symbol
|
||||
|
||||
val isAdded = addedCurrencies?.tokens
|
||||
?.any {
|
||||
it.symbol == token.symbol && it.contractAddress == token.contractAddress
|
||||
} == true
|
||||
|
||||
view.btn_add_token.show(!isAdded)
|
||||
view.btn_token_added.show(isAdded)
|
||||
|
||||
Picasso.get().loadCurrenciesIcon(
|
||||
imageView = view.iv_currency,
|
||||
textView = view.tv_token_letter,
|
||||
token = token, blockchain = token.blockchain
|
||||
)
|
||||
view.btn_add_token.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.AddToken(token))
|
||||
view.btn_add_token.hide()
|
||||
view.btn_token_added.show()
|
||||
}
|
||||
val addedTokens = addedCurrencies?.tokens ?: listOf()
|
||||
val currentlyAdded = currentlyAddedItems.filterIsInstance<CurrencyListItem.TokenListItem>()
|
||||
bindToken(currency, addedTokens, currentlyAdded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindBlockchain(
|
||||
currency: CurrencyListItem.BlockchainListItem,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
currentlyAdded: List<CurrencyListItem.BlockchainListItem>
|
||||
) {
|
||||
val blockchain = currency.blockchain
|
||||
Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, null, blockchain)
|
||||
|
||||
view.tv_currency_name.text = blockchain.fullName
|
||||
view.tv_currency_symbol.text = blockchain.currency
|
||||
view.btn_add_token.setOnClickListener {
|
||||
onItemAddListener.invoke(currency)
|
||||
currency.isAdded = true
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
val isAddedBefore = addedBlockchains.contains(blockchain)
|
||||
val isCurrentlyAdded = currentlyAdded.any { it.blockchain == blockchain }
|
||||
currency.isAdded = isAddedBefore || isCurrentlyAdded
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
private fun bindToken(
|
||||
currency: CurrencyListItem.TokenListItem,
|
||||
addedTokens: List<Token>,
|
||||
currentlyAdded: List<CurrencyListItem.TokenListItem>
|
||||
) {
|
||||
val token = currency.token
|
||||
Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, token, token.blockchain)
|
||||
|
||||
view.tv_currency_name.text = token.name
|
||||
view.tv_currency_symbol.text = token.symbol
|
||||
view.btn_add_token.setOnClickListener {
|
||||
onItemAddListener.invoke(currency)
|
||||
currency.isAdded = true
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
val isAddedBefore = addedTokens.any { it == token }
|
||||
val isCurrentlyAdded = currentlyAdded.any { it.token == token }
|
||||
currency.isAdded = isAddedBefore || isCurrentlyAdded
|
||||
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
private fun modifyAddTokenButton(currency: CurrencyListItem) {
|
||||
if (currency.isLock) {
|
||||
view.btn_add_token.setText(R.string.common_add)
|
||||
view.btn_add_token.isEnabled = false
|
||||
} else {
|
||||
val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add
|
||||
view.btn_add_token.setText(text)
|
||||
view.btn_add_token.isEnabled = !currency.isAdded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TitleViewHolder(val view: View) :
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ sealed class WalletAction : Action {
|
|||
data class AddToken(val token: Token) : MultiWallet()
|
||||
data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet()
|
||||
object FindTokensInUse : MultiWallet()
|
||||
data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) :
|
||||
MultiWallet()
|
||||
object FindBlockchainsInUse : MultiWallet()
|
||||
|
||||
data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet()
|
||||
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.*
|
|||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -24,10 +25,12 @@ class MultiWalletMiddleware {
|
|||
fun handle(
|
||||
action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?,
|
||||
) {
|
||||
val globalState = globalState ?: return
|
||||
val tapWalletManager = globalState.tapWalletManager
|
||||
|
||||
when (action) {
|
||||
is WalletAction.MultiWallet.AddWalletManagers -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
|
||||
}
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.walletData != null) {
|
||||
|
|
@ -35,7 +38,7 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.AddToken -> {
|
||||
globalState?.scanResponse?.card?.cardId?.let {
|
||||
globalState.scanResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveAddedToken(it, action.token)
|
||||
}
|
||||
addToken(action.token, walletState, globalState)
|
||||
|
|
@ -44,35 +47,33 @@ class MultiWalletMiddleware {
|
|||
addTokens(action.tokens, walletState, globalState)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
globalState?.scanResponse?.card?.let { card ->
|
||||
currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain)
|
||||
globalState.scanResponse?.let {
|
||||
currenciesRepository.saveAddedBlockchain(it.card.cardId, action.blockchain)
|
||||
if (walletState?.blockchains?.contains(action.blockchain) != true) {
|
||||
globalState.tapWalletManager.walletManagerFactory
|
||||
.makeWalletManagerForApp(card, action.blockchain)?.let {
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
|
||||
tapWalletManager.walletManagerFactory
|
||||
.makeWalletManagerForApp(it, action.blockchain)?.let { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
}
|
||||
}
|
||||
}
|
||||
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain)))
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
moonpayStatus = globalState?.moonpayStatus,
|
||||
moonpayStatus = globalState.moonpayStatus,
|
||||
blockchain = action.blockchain
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.SaveCurrencies -> {
|
||||
val cardId = globalState?.scanResponse?.card?.cardId
|
||||
cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) }
|
||||
globalState.scanResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveCardCurrencies(it, action.cardCurrencies)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
val cardId = globalState?.scanResponse?.card?.cardId
|
||||
val cardId = globalState.scanResponse?.card?.cardId
|
||||
when (val currency = action.walletData.currency) {
|
||||
is Currency.Blockchain -> {
|
||||
cardId?.let {
|
||||
currenciesRepository.removeBlockchain(
|
||||
it,
|
||||
currency.blockchain
|
||||
)
|
||||
currenciesRepository.removeBlockchain(it, currency.blockchain)
|
||||
}
|
||||
}
|
||||
is Currency.Token -> {
|
||||
|
|
@ -83,11 +84,14 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.FindBlockchainsInUse -> {
|
||||
val cardFirmware = globalState?.scanResponse?.card?.firmwareVersion
|
||||
val scanResponse = globalState.scanResponse ?: return
|
||||
if (scanResponse.isTangemWallet()) return
|
||||
|
||||
val cardFirmware = scanResponse.card.firmwareVersion
|
||||
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
|
||||
.filterNot { walletState?.blockchains?.contains(it) == true }
|
||||
val walletManagers =
|
||||
action.factory.makeWalletManagersForApp(action.card, blockchains)
|
||||
tapWalletManager.walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
|
||||
|
||||
scope.launch {
|
||||
walletManagers.map { walletManager ->
|
||||
|
|
@ -120,12 +124,14 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.FindTokensInUse -> {
|
||||
val card = globalState?.scanResponse?.card ?: return
|
||||
val scanResponse = globalState.scanResponse ?: return
|
||||
if (scanResponse.isTangemWallet()) return
|
||||
|
||||
val walletFactory = tapWalletManager.walletManagerFactory
|
||||
val card = scanResponse.card
|
||||
val walletManager = walletState?.getWalletManager(Blockchain.Ethereum)
|
||||
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
|
||||
card = card,
|
||||
blockchain = Blockchain.Ethereum
|
||||
)
|
||||
?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum)
|
||||
|
||||
val tokenFinder = walletManager as TokenFinder
|
||||
scope.launch {
|
||||
val result = tokenFinder.findTokens()
|
||||
|
|
@ -159,26 +165,33 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
|
||||
private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) {
|
||||
val card = globalState?.scanResponse?.card ?: return
|
||||
val scanResponse = globalState?.scanResponse ?: return
|
||||
|
||||
val walletManager = walletState?.getWalletManager(token)
|
||||
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
|
||||
card = card,
|
||||
blockchain = token.blockchain
|
||||
scanResponse,
|
||||
token.blockchain
|
||||
)?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
WalletAction.LoadFiatRate(currency = Currency.Token(token))
|
||||
)
|
||||
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token(token)))
|
||||
|
||||
scope.launch {
|
||||
when (val result = walletManager?.addToken(token)) {
|
||||
is Result.Success -> {
|
||||
store.dispatchOnMain(
|
||||
WalletAction.MultiWallet.TokenLoaded(amount = result.data, token = token)
|
||||
)
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, token))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (val result = walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val tokenAmount = result.data.getTokenAmount(token) ?: return@launch
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, token))
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -189,12 +202,13 @@ class MultiWalletMiddleware {
|
|||
walletState: WalletState?,
|
||||
globalState: GlobalState?,
|
||||
) {
|
||||
val card = globalState?.scanResponse?.card ?: return
|
||||
val scanResponse = globalState?.scanResponse ?: return
|
||||
|
||||
val tokensWithManagers = tokens.map { token ->
|
||||
val walletManager = walletState?.getWalletManager(token)
|
||||
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
|
||||
card = card,
|
||||
blockchain = token.blockchain
|
||||
scanResponse,
|
||||
token.blockchain
|
||||
)?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
|
|
@ -208,11 +222,17 @@ class MultiWalletMiddleware {
|
|||
tokensWithManagers.forEach {
|
||||
when (val result = it.walletManager?.addToken(it.token)) {
|
||||
is Result.Success -> {
|
||||
store.dispatchOnMain(
|
||||
WalletAction.MultiWallet.TokenLoaded(
|
||||
amount = result.data, token = it.token
|
||||
)
|
||||
)
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, it.token))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (val result = it.walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val tokenAmount = result.data.getTokenAmount(it.token) ?: return@launch
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, it.token))
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.ui.wallet
|
|||
|
||||
import android.app.Dialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
|
|
@ -49,8 +50,22 @@ class MultiWalletView : WalletView {
|
|||
btn_scan_multiwallet?.show()
|
||||
rv_multiwallet.show()
|
||||
btn_add_token.show()
|
||||
setupWalletCardNumber(fragment)
|
||||
}
|
||||
|
||||
private fun setupWalletCardNumber(fragment: WalletFragment) = with(fragment) {
|
||||
val card = store.state.globalState.scanResponse?.card
|
||||
if (card?.backupStatus is Card.BackupStatus.Active) {
|
||||
val cardCount = (card.backupStatus as Card.BackupStatus.Active).cardCount + 1
|
||||
tv_twin_card_number.show()
|
||||
tv_twin_card_number.text =
|
||||
getString(R.string.wallet_twins_chip_format, 1, cardCount)
|
||||
} else {
|
||||
tv_twin_card_number.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setupButtons(fragment: WalletFragment) = with(fragment) {
|
||||
btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,11 +96,11 @@ class SingleWalletView : WalletView {
|
|||
twinCardsState?.cardNumber?.let { cardNumber ->
|
||||
tv_twin_card_number.show()
|
||||
val number = when (cardNumber) {
|
||||
TwinCardNumber.First -> "1"
|
||||
TwinCardNumber.Second -> "2"
|
||||
TwinCardNumber.First -> 1
|
||||
TwinCardNumber.Second -> 2
|
||||
}
|
||||
tv_twin_card_number.text =
|
||||
this.getString(R.string.wallet_twins_chip_format, number)
|
||||
this.getString(R.string.wallet_twins_chip_format, number, 2)
|
||||
}
|
||||
if (twinCardsState?.cardNumber == null) {
|
||||
tv_twin_card_number.hide()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.FromJson
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.ToJson
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.common.json.TangemSdkAdapter
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
|
|
@ -41,7 +42,10 @@ fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.cre
|
|||
|
||||
fun createMoshi(): Moshi = Moshi.Builder()
|
||||
.add(BigDecimalAdapter)
|
||||
.add(KotlinJsonAdapterFactory()).build()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.add(TangemSdkAdapter.DerivationPathAdapter())
|
||||
.add(TangemSdkAdapter.DerivationNodeAdapter())
|
||||
.build()
|
||||
|
||||
private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
val logging = HttpLoggingInterceptor()
|
||||
|
|
|
|||
5
app/src/main/res/color/selector_btn_gray_text_color.xml
Normal file
5
app/src/main/res/color/selector_btn_gray_text_color.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="#ABABAB" android:state_enabled="false" />
|
||||
<item android:color="#060606" />
|
||||
</selector>
|
||||
7
app/src/main/res/color/selector_edit_text.xml
Normal file
7
app/src/main/res/color/selector_edit_text.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="#1ACE80" android:state_focused="true"/>
|
||||
<item android:color="#1ACE80" android:state_hovered="true"/>
|
||||
<item android:color="#E0E0E0" android:state_enabled="false"/>
|
||||
<item android:color="#E0E0E0"/> <!-- unfocused -->
|
||||
</selector>
|
||||
BIN
app/src/main/res/drawable-hdpi/card_placeholder_new.png
Normal file
BIN
app/src/main/res/drawable-hdpi/card_placeholder_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
BIN
app/src/main/res/drawable-ldpi/card_placeholder_new.png
Normal file
BIN
app/src/main/res/drawable-ldpi/card_placeholder_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
app/src/main/res/drawable-mdpi/card_placeholder_new.png
Normal file
BIN
app/src/main/res/drawable-mdpi/card_placeholder_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
app/src/main/res/drawable-xhdpi/card_placeholder_new.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/card_placeholder_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
BIN
app/src/main/res/drawable-xxhdpi/card_placeholder_new.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/card_placeholder_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 499 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 837 KiB |
24
app/src/main/res/drawable/ic_access_code.xml
Normal file
24
app/src/main/res/drawable/ic_access_code.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="119dp"
|
||||
android:height="119dp"
|
||||
android:viewportWidth="119"
|
||||
android:viewportHeight="119">
|
||||
<path
|
||||
android:pathData="M111.664,30.74H7.336C3.291,30.74 0,34.03 0,38.075V60.431C0,64.476 3.291,67.767 7.336,67.767H72.541C73.506,67.767 74.288,66.985 74.288,66.021C74.288,65.056 73.506,64.274 72.541,64.274H7.336C5.217,64.274 3.493,62.55 3.493,60.431V38.075C3.493,35.957 5.217,34.233 7.336,34.233H111.664C113.783,34.233 115.507,35.957 115.507,38.075V54.842C115.507,55.807 116.289,56.589 117.253,56.589C118.218,56.589 119,55.807 119,54.842V38.075C119,34.03 115.709,30.74 111.664,30.74Z"
|
||||
android:fillColor="#060606"/>
|
||||
<path
|
||||
android:pathData="M111.548,58.972V56.706C111.548,48.552 104.914,41.918 96.76,41.918C88.606,41.918 81.973,48.552 81.973,56.706V58.972C79.809,59.703 78.247,61.751 78.247,64.158V82.788C78.247,85.805 80.702,88.26 83.719,88.26H109.801C112.819,88.26 115.274,85.805 115.274,82.788V64.158C115.274,61.751 113.711,59.703 111.548,58.972ZM96.76,45.411C102.988,45.411 108.055,50.478 108.055,56.706V58.685H85.466V56.706C85.466,50.478 90.533,45.411 96.76,45.411ZM111.781,82.788C111.781,83.879 110.893,84.767 109.801,84.767H83.719C82.628,84.767 81.74,83.879 81.74,82.788V64.158C81.74,63.066 82.628,62.178 83.719,62.178H109.801C110.893,62.178 111.781,63.066 111.781,64.158V82.788Z"
|
||||
android:fillColor="#060606"/>
|
||||
<path
|
||||
android:pathData="M28.343,44.654C27.861,43.819 26.793,43.533 25.957,44.015L22.123,46.228V41.801C22.123,40.837 21.341,40.055 20.377,40.055C19.412,40.055 18.63,40.837 18.63,41.801V46.228L14.796,44.015C13.961,43.532 12.893,43.819 12.411,44.654C11.928,45.489 12.215,46.558 13.05,47.04L16.884,49.253L13.05,51.467C12.215,51.949 11.928,53.017 12.411,53.853C12.734,54.413 13.321,54.726 13.925,54.726C14.221,54.726 14.521,54.651 14.796,54.492L18.63,52.278V56.706C18.63,57.67 19.412,58.452 20.377,58.452C21.341,58.452 22.123,57.67 22.123,56.706V52.278L25.957,54.492C26.232,54.651 26.533,54.726 26.829,54.726C27.433,54.726 28.02,54.413 28.343,53.853C28.825,53.017 28.539,51.949 27.704,51.467L23.87,49.253L27.704,47.04C28.539,46.558 28.825,45.489 28.343,44.654Z"
|
||||
android:fillColor="#060606"/>
|
||||
<path
|
||||
android:pathData="M50.699,44.654C50.217,43.819 49.149,43.533 48.313,44.015L44.48,46.228V41.801C44.48,40.837 43.698,40.055 42.733,40.055C41.768,40.055 40.986,40.837 40.986,41.801V46.228L37.153,44.015C36.317,43.532 35.249,43.819 34.767,44.654C34.285,45.489 34.571,46.558 35.406,47.04L39.24,49.253L35.406,51.467C34.571,51.949 34.285,53.017 34.767,53.853C35.09,54.413 35.677,54.726 36.281,54.726C36.577,54.726 36.877,54.651 37.153,54.492L40.986,52.278V56.706C40.986,57.67 41.768,58.452 42.733,58.452C43.698,58.452 44.48,57.67 44.48,56.706V52.278L48.313,54.492C48.588,54.651 48.889,54.726 49.185,54.726C49.789,54.726 50.376,54.413 50.699,53.853C51.181,53.017 50.895,51.949 50.06,51.467L46.226,49.253L50.06,47.04C50.895,46.558 51.181,45.489 50.699,44.654Z"
|
||||
android:fillColor="#060606"/>
|
||||
<path
|
||||
android:pathData="M73.055,44.654C72.573,43.819 71.505,43.533 70.67,44.015L66.836,46.228V41.801C66.836,40.837 66.054,40.055 65.089,40.055C64.125,40.055 63.343,40.837 63.343,41.801V46.228L59.509,44.015C58.673,43.532 57.605,43.819 57.123,44.654C56.641,45.489 56.927,46.558 57.762,47.04L61.596,49.253L57.762,51.467C56.927,51.949 56.641,53.017 57.123,53.853C57.446,54.413 58.034,54.726 58.637,54.726C58.933,54.726 59.234,54.651 59.509,54.492L63.343,52.278V56.706C63.343,57.67 64.125,58.452 65.089,58.452C66.054,58.452 66.836,57.67 66.836,56.706V52.278L70.67,54.492C70.945,54.651 71.245,54.726 71.541,54.726C72.145,54.726 72.732,54.413 73.055,53.853C73.538,53.017 73.252,51.949 72.416,51.467L68.582,49.253L72.416,47.04C73.252,46.558 73.538,45.489 73.055,44.654Z"
|
||||
android:fillColor="#060606"/>
|
||||
<path
|
||||
android:pathData="M96.76,66.137C93.743,66.137 91.288,68.592 91.288,71.61C91.288,74.017 92.85,76.064 95.014,76.795V79.062C95.014,80.026 95.796,80.808 96.76,80.808C97.725,80.808 98.507,80.026 98.507,79.062V76.795C100.67,76.064 102.233,74.017 102.233,71.61C102.233,68.592 99.778,66.137 96.76,66.137ZM96.76,73.589C95.669,73.589 94.781,72.701 94.781,71.61C94.781,70.518 95.669,69.63 96.76,69.63C97.852,69.63 98.74,70.518 98.74,71.61C98.74,72.701 97.852,73.589 96.76,73.589Z"
|
||||
android:fillColor="#060606"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_dot.xml
Normal file
15
app/src/main/res/drawable/ic_dot.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="4.5dp"
|
||||
android:useLevel="false">
|
||||
<solid
|
||||
android:color="#80686868"
|
||||
android:fillAlpha="0.5"
|
||||
/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
11
app/src/main/res/drawable/ic_dot_not_selected.xml
Normal file
11
app/src/main/res/drawable/ic_dot_not_selected.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="4.5dp"
|
||||
android:height="4.5dp"
|
||||
android:viewportWidth="7"
|
||||
android:viewportHeight="7">
|
||||
<path
|
||||
android:pathData="M3.5,7C5.433,7 7,5.433 7,3.5C7,1.567 5.433,0 3.5,0C1.567,0 0,1.567 0,3.5C0,5.433 1.567,7 3.5,7Z"
|
||||
android:fillColor="#686868"
|
||||
android:fillAlpha="0.5"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
22
app/src/main/res/drawable/ic_feature_1.xml
Normal file
22
app/src/main/res/drawable/ic_feature_1.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="25"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M3,11H11C12.6569,11 14,12.3431 14,14V22C14,23.6569 12.6569,25 11,25H3C1.3432,25 0,23.6569 0,22V14C0,12.3431 1.3432,11 3,11Z"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M18.7143,11H23.2857C24.2325,11 25,11.7675 25,12.7143V17.2857C25,18.2325 24.2325,19 23.2857,19H18.7143C17.7675,19 17,18.2325 17,17.2857V12.7143C17,11.7675 17.7675,11 18.7143,11Z"
|
||||
android:strokeAlpha="0.15"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"
|
||||
android:fillAlpha="0.15"/>
|
||||
<path
|
||||
android:pathData="M7.7143,0H19.2857C20.2325,0 21,0.7675 21,1.7143V6.2857C21,7.2325 20.2325,8 19.2857,8H7.7143C6.7675,8 6,7.2325 6,6.2857V1.7143C6,0.7675 6.7675,0 7.7143,0Z"
|
||||
android:strokeAlpha="0.5"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"
|
||||
android:fillAlpha="0.5"/>
|
||||
</vector>
|
||||
22
app/src/main/res/drawable/ic_feature_2.xml
Normal file
22
app/src/main/res/drawable/ic_feature_2.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="25"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M18,25C21.866,25 25,21.866 25,18C25,14.134 21.866,11 18,11C14.134,11 11,14.134 11,18C11,21.866 14.134,25 18,25Z"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M9,10C11.7614,10 14,7.7614 14,5C14,2.2386 11.7614,0 9,0C6.2386,0 4,2.2386 4,5C4,7.7614 6.2386,10 9,10Z"
|
||||
android:strokeAlpha="0.5"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"
|
||||
android:fillAlpha="0.5"/>
|
||||
<path
|
||||
android:pathData="M4,21C6.2091,21 8,19.2091 8,17C8,14.7909 6.2091,13 4,13C1.7909,13 0,14.7909 0,17C0,19.2091 1.7909,21 4,21Z"
|
||||
android:strokeAlpha="0.15"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"
|
||||
android:fillAlpha="0.15"/>
|
||||
</vector>
|
||||
22
app/src/main/res/drawable/ic_feature_3.xml
Normal file
22
app/src/main/res/drawable/ic_feature_3.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="22">
|
||||
<path
|
||||
android:pathData="M19.3989,4.1971L13.3558,4.1971C12.3137,4.1512 11.3955,4.9319 11.3048,5.9408C11.2723,6.3027 11.3515,6.6613 11.5324,6.9712L14.7623,12.5027C15.2667,13.3667 16.4185,13.6429 17.3349,13.1197C17.6423,12.9442 17.8966,12.6917 18.0696,12.3902L21.041,6.9712C21.5566,6.0725 21.1048,4.9181 20.1946,4.4571C19.9583,4.3374 19.6695,4.209 19.3989,4.1971Z"
|
||||
android:strokeAlpha="0.5"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"
|
||||
android:fillAlpha="0.5"/>
|
||||
<path
|
||||
android:pathData="M20.1304,14.9339L17.4396,19.051C16.9906,19.738 17.1702,20.6677 17.8409,21.1276C18.0815,21.2926 18.3644,21.3806 18.6539,21.3806H23.8216C24.6287,21.3806 25.2829,20.7105 25.2829,19.8837C25.2829,19.6064 25.2077,19.3345 25.0657,19.0984L22.5889,14.9813C22.1655,14.2775 21.2652,14.0586 20.5781,14.4923C20.3997,14.6049 20.247,14.7555 20.1304,14.9339Z"
|
||||
android:strokeAlpha="0.15"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"
|
||||
android:fillAlpha="0.15"/>
|
||||
<path
|
||||
android:pathData="M5.1416,10.4031L0.4325,17.4279C-0.3532,18.6 -0.0387,20.1864 1.1349,20.9711C1.5559,21.2526 2.051,21.4029 2.5576,21.4029H11.6011C13.0135,21.4029 14.1585,20.2594 14.1585,18.8488C14.1585,18.3756 14.0269,17.9117 13.7783,17.5089L9.4439,10.4841C8.7029,9.2832 7.1275,8.9096 5.925,9.6496C5.6128,9.8417 5.3456,10.0987 5.1416,10.4031Z"
|
||||
android:fillColor="#1ACE80"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_hide_password.xml
Normal file
12
app/src/main/res/drawable/ic_hide_password.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<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="M12,18.1876C11.4712,18.1865 10.9435,18.1373 10.4236,18.0405C10.2623,18.0147 10.1155,17.9323 10.0094,17.8081C9.9033,17.6839 9.845,17.526 9.8447,17.3626C9.8464,17.2619 9.8699,17.1627 9.9136,17.0719C9.9572,16.9811 10.02,16.9008 10.0976,16.8366C10.1751,16.7723 10.2657,16.7256 10.3631,16.6996C10.4604,16.6737 10.5622,16.669 10.6615,16.6861C11.1028,16.7692 11.5509,16.8115 12,16.8126C16.0728,16.8126 19.61,13.2465 20.7224,12.0001C19.9811,11.1704 19.1666,10.4092 18.2886,9.7259C18.2172,9.6706 18.1574,9.6017 18.1125,9.5233C18.0677,9.4449 18.0388,9.3584 18.0274,9.2688C18.0044,9.0879 18.0542,8.9052 18.1659,8.761C18.2776,8.6167 18.442,8.5228 18.6229,8.4998C18.7125,8.4884 18.8035,8.4947 18.8906,8.5185C18.9778,8.5422 19.0594,8.5829 19.1308,8.6382C20.2501,9.5006 21.2685,10.4866 22.1668,11.5773C22.2615,11.6983 22.313,11.8475 22.313,12.0011C22.313,12.1548 22.2615,12.304 22.1668,12.425C21.9819,12.6594 17.5853,18.1876 12,18.1876Z"
|
||||
android:fillColor="#A4A4A4"/>
|
||||
<path
|
||||
android:pathData="M19.3608,4.6384C19.2319,4.5095 19.0571,4.4371 18.8748,4.4371C18.6925,4.4371 18.5176,4.5095 18.3887,4.6384L16.2024,6.8247C15.3747,6.4258 14.4989,6.1356 13.5968,5.9612C13.0702,5.8622 12.5356,5.8122 11.9998,5.812C6.4145,5.812 2.0179,11.3402 1.8337,11.5753C1.739,11.6963 1.6875,11.8455 1.6875,11.9991C1.6875,12.1528 1.739,12.302 1.8337,12.423C2.7311,13.5128 3.7486,14.4978 4.8669,15.3593C5.396,15.7739 5.9503,16.1553 6.5266,16.5012L4.6387,18.3884C4.573,18.4518 4.5207,18.5277 4.4846,18.6116C4.4486,18.6955 4.4296,18.7857 4.4288,18.877C4.428,18.9682 4.4454,19.0588 4.48,19.1433C4.5146,19.2278 4.5656,19.3045 4.6302,19.3691C4.6947,19.4336 4.7715,19.4847 4.856,19.5192C4.9405,19.5538 5.031,19.5712 5.1223,19.5704C5.2136,19.5696 5.3038,19.5507 5.3877,19.5146C5.4715,19.4786 5.5474,19.4262 5.6108,19.3606L19.3608,5.6105C19.4897,5.4816 19.5621,5.3068 19.5621,5.1245C19.5621,4.9422 19.4897,4.7674 19.3608,4.6384ZM5.7112,14.2744C4.8332,13.5908 4.0186,12.8294 3.2774,11.9995C4.3898,10.7537 7.927,7.187 11.9998,7.187C12.4521,7.1873 12.9034,7.2297 13.3479,7.3135C13.9703,7.4319 14.5783,7.6163 15.1616,7.8635L13.8952,9.1299C13.2342,8.6931 12.4427,8.4981 11.6544,8.5778C10.8662,8.6575 10.1297,9.007 9.5695,9.5672C9.0093,10.1274 8.6598,10.8639 8.5801,11.6521C8.5004,12.4403 8.6954,13.2319 9.1322,13.8929L7.531,15.4968C6.8943,15.1361 6.2859,14.7274 5.7112,14.2744ZM10.1435,12.8822C10.0095,12.6071 9.939,12.3055 9.9373,11.9995C9.9374,11.4541 10.1528,10.9308 10.5368,10.5434C10.8428,10.2523 11.229,10.0594 11.6455,9.9896C12.0621,9.9198 12.49,9.9761 12.8743,10.1515L10.1435,12.8822Z"
|
||||
android:fillColor="#A4A4A4"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_selected_dot.xml
Normal file
12
app/src/main/res/drawable/ic_selected_dot.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="4.5dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="#060606"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
12
app/src/main/res/drawable/ic_show_password.xml
Normal file
12
app/src/main/res/drawable/ic_show_password.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<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="M12.0003,18.1875C6.4136,18.1875 2.0185,12.6593 1.8342,12.4242C1.7393,12.3032 1.6877,12.1538 1.6877,12C1.6877,11.8462 1.7393,11.6968 1.8342,11.5758C2.0185,11.3407 6.4136,5.8125 12.0003,5.8125C17.5869,5.8125 21.9821,11.3407 22.1663,11.5758C22.2612,11.6968 22.3128,11.8462 22.3128,12C22.3128,12.1538 22.2612,12.3032 22.1663,12.4242C21.9821,12.6593 17.5869,18.1875 12.0003,18.1875ZM3.278,12C4.391,13.2471 7.9268,16.8125 12.0003,16.8125C16.0737,16.8125 19.6095,13.2471 20.7226,12C19.6095,10.7529 16.0737,7.1875 12.0003,7.1875C7.9268,7.1875 4.391,10.7529 3.278,12Z"
|
||||
android:fillColor="#A4A4A4"/>
|
||||
<path
|
||||
android:pathData="M12,15.4375C11.3201,15.4375 10.6555,15.2359 10.0902,14.8582C9.5249,14.4804 9.0843,13.9436 8.8242,13.3155C8.564,12.6873 8.4959,11.9962 8.6285,11.3294C8.7612,10.6626 9.0886,10.0501 9.5693,9.5693C10.0501,9.0886 10.6626,8.7612 11.3294,8.6285C11.9962,8.4959 12.6873,8.564 13.3155,8.8242C13.9436,9.0843 14.4804,9.5249 14.8582,10.0902C15.2359,10.6555 15.4375,11.3201 15.4375,12C15.4364,12.9113 15.0739,13.785 14.4295,14.4295C13.785,15.0739 12.9113,15.4364 12,15.4375ZM12,9.9375C11.5921,9.9375 11.1933,10.0585 10.8541,10.2851C10.515,10.5117 10.2506,10.8338 10.0945,11.2107C9.9384,11.5876 9.8975,12.0023 9.9771,12.4024C10.0567,12.8025 10.2531,13.17 10.5416,13.4584C10.83,13.7468 11.1975,13.9433 11.5976,14.0229C11.9977,14.1024 12.4124,14.0616 12.7893,13.9055C13.1661,13.7494 13.4883,13.485 13.7149,13.1459C13.9415,12.8067 14.0625,12.4079 14.0625,12C14.0625,11.453 13.8452,10.9284 13.4584,10.5416C13.0716,10.1548 12.547,9.9375 12,9.9375Z"
|
||||
android:fillColor="#A4A4A4"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/selector_password_toggle.xml
Normal file
5
app/src/main/res/drawable/selector_password_toggle.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_hide_password" android:state_checked="true"/>
|
||||
<item android:drawable="@drawable/ic_show_password"/>
|
||||
</selector>
|
||||
8
app/src/main/res/drawable/selector_tabs_dots.xml
Normal file
8
app/src/main/res/drawable/selector_tabs_dots.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@drawable/ic_selected_dot"
|
||||
android:state_selected="true"/>
|
||||
|
||||
<item android:drawable="@drawable/ic_dot"/>
|
||||
</selector>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/twins_dark" />
|
||||
<solid android:color="@color/darkGray1" />
|
||||
<corners
|
||||
android:bottomLeftRadius="30dp"
|
||||
android:bottomRightRadius="30dp"
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:fitsSystemWindows="true"
|
||||
>
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
|
@ -30,14 +29,34 @@
|
|||
android:id="@+id/cl_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_popular_tokens"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="80dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_tokens_save_changes"
|
||||
style="@style/TapBlackButton"
|
||||
android:layout_width="204dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:enabled="false"
|
||||
android:gravity="center"
|
||||
android:text="Save changes"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -160,15 +160,14 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_erase_wallet"
|
||||
android:id="@+id/tv_create_backup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="15dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_row_title_erase_wallet"
|
||||
android:text="@string/details_row_title_create_backup"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
|
|
@ -176,6 +175,21 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_security_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_reset_to_factory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="15dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/details_row_title_reset_factory_settings"
|
||||
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_create_backup" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_settings_title"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -187,7 +201,7 @@
|
|||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_erase_wallet" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_reset_to_factory" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,220 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator_details_confirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
style="@style/ThemeOverlay.MyTheme.Toolbar.AccentColorMenu"
|
||||
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:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:title="@string/onboarding_getting_started"
|
||||
app:menu="@menu/shop"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/onboarding_main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<include
|
||||
layout="@layout/view_onboarding_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_card_background"
|
||||
android:layout_width="236dp"
|
||||
android:layout_height="236dp"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/shape_circle"
|
||||
android:backgroundTint="@color/lightGray0"
|
||||
android:elevation="0dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintStart_toStartOf="@id/fl_cards_container"
|
||||
app:layout_constraintEnd_toEndOf="@id/fl_cards_container"
|
||||
app:layout_constraintTop_toTopOf="@id/fl_cards_container"
|
||||
app:layout_constraintBottom_toBottomOf="@id/fl_cards_container"
|
||||
android:layout_marginBottom="16dp"
|
||||
/>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fl_cards_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/guideline2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.495"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pb_state">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_second_backup_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_first_backup_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_front_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:transitionName="imv_front_card"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_success"
|
||||
android:layout_width="142dp"
|
||||
android:layout_height="142dp"
|
||||
android:src="@drawable/ic_activation_success"
|
||||
app:layout_constraintBottom_toTopOf="@id/guideline2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.495"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pb_state"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/ll_header_body_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="72dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline2">
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
tools:text="Header"
|
||||
android:textColor="#060606"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.504"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
tools:text="Body"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#686868"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/view_pager_backup_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintHorizontal_bias="0.504"
|
||||
app:flow_verticalBias="0"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline2"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout_backup_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="10dp"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/view_pager_backup_info"
|
||||
app:layout_constraintBottom_toTopOf="@id/layout_buttons_common"
|
||||
app:tabBackground="@drawable/selector_tabs_dots"
|
||||
app:tabIndicator="@null"
|
||||
app:tabPaddingStart="8dp"
|
||||
app:tabPaddingEnd="8dp"
|
||||
app:tabGravity="center"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_buttons_common"
|
||||
layout="@layout/layout_onboarding_buttons_common"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
/>
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_buttons_add_cards"
|
||||
layout="@layout/layout_onboarding_buttons_add_cards"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintGuide_percent="0.60"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/view_confetti"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
36
app/src/main/res/layout/item_backup_info_adapter.xml
Normal file
36
app/src/main/res/layout/item_backup_info_adapter.xml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="Header"
|
||||
android:textColor="#060606"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.504"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
android:text="Body"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#686868"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -74,33 +74,17 @@
|
|||
app:layout_constraintVertical_bias="0"
|
||||
tools:text="NODLE" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_token"
|
||||
style="@style/Widget.MaterialComponents.Chip.Action"
|
||||
android:layout_width="102dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="43dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:src="@drawable/ic_inactive"
|
||||
android:backgroundTint="@color/selector_btn_green"
|
||||
android:elevation="0dp"
|
||||
android:text="@string/common_add"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
app:chipBackgroundColor="@color/accent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/btn_token_added"
|
||||
style="@style/Widget.MaterialComponents.Chip.Action"
|
||||
android:layout_width="102dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:enabled="false"
|
||||
android:src="@drawable/ic_inactive"
|
||||
android:text="@string/add_token_added"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
app:chipBackgroundColor="@color/tapButtonDisabled"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/btn_tap_text_color"
|
||||
app:cornerRadius="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
@ -110,7 +94,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="start"
|
||||
app:constraint_referenced_ids="btn_add_token, btn_token_added" />
|
||||
|
||||
app:constraint_referenced_ids="btn_add_token" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
44
app/src/main/res/layout/layout_backup_access_code.xml
Normal file
44
app/src/main/res/layout/layout_backup_access_code.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/access_code_title"
|
||||
style="@style/heading"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="100dp"
|
||||
android:text="@string/onboarding_access_code_intro_title"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_backup_access_code_info"
|
||||
layout="@layout/layout_backup_access_code_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/access_code_title" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_backup_access_code_submit"
|
||||
layout="@layout/layout_backup_access_code_submit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/access_code_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
||||
132
app/src/main/res/layout/layout_backup_access_code_info.xml
Normal file
132
app/src/main/res/layout/layout_backup_access_code_info.xml
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_access_code_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="25dp"
|
||||
android:src="@drawable/ic_access_code"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_marginTop="20dp"
|
||||
android:id="@+id/tv_feature_1_title"
|
||||
style="@style/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_access_code_feature_1_title"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintStart_toStartOf="@id/tv_feature_1_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_access_code_icon" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_feature_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/ic_feature_1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_1_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_feature_1_description"
|
||||
style="@style/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="46dp"
|
||||
android:text="@string/onboarding_access_code_feature_1_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_feature_1"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_1_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_feature_2_title"
|
||||
style="@style/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:text="@string/onboarding_access_code_feature_2_title"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintStart_toStartOf="@id/tv_feature_1_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_1_description" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_feature_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/ic_feature_2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_2_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_feature_2_description"
|
||||
style="@style/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="46dp"
|
||||
android:text="@string/onboarding_access_code_feature_2_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_feature_2"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_2_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_feature_3_title"
|
||||
style="@style/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:text="@string/onboarding_access_code_feature_3_title"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintStart_toStartOf="@id/tv_feature_3_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_2_description" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_feature_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/ic_feature_3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_3_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_feature_3_description"
|
||||
style="@style/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="46dp"
|
||||
android:text="@string/onboarding_access_code_feature_3_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_feature_3"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_feature_3_title" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_access_code_create"
|
||||
style="@style/TapButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="78dp"
|
||||
android:layout_marginEnd="78dp"
|
||||
android:layout_marginBottom="92dp"
|
||||
android:text="@string/common_create"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
60
app/src/main/res/layout/layout_backup_access_code_submit.xml
Normal file
60
app/src/main/res/layout/layout_backup_access_code_submit.xml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_access_code_enter_description"
|
||||
style="DescriptionBig"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/onboarding_access_code_hint"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/til_access_code"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="44dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:hint="Access code"
|
||||
app:boxStrokeColor="@color/selector_edit_text"
|
||||
app:boxStrokeWidth="1dp"
|
||||
app:endIconDrawable="@drawable/selector_password_toggle"
|
||||
app:endIconMode="password_toggle"
|
||||
app:hintTextColor="@color/accent"
|
||||
android:theme="@style/EditTextThemeOverlay"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_access_code_enter_description">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_access_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_access_code_submit"
|
||||
style="@style/TapButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="78dp"
|
||||
android:layout_marginEnd="78dp"
|
||||
android:layout_marginBottom="92dp"
|
||||
android:text="@string/common_create"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_card"
|
||||
style="@style/OnboardingButton"
|
||||
android:backgroundTint="@color/gray_button"
|
||||
android:textColor="@color/selector_btn_gray_text_color"
|
||||
android:elevation="0dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="92dp"
|
||||
tools:text="+ Add backup card"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/btn_continue"
|
||||
app:layout_constraintStart_toStartOf="@id/btn_continue" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_continue"
|
||||
style="@style/OnboardingButton"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
tools:text="Main Action"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
33
app/src/main/res/layout/layout_onboarding_buttons_common.xml
Normal file
33
app/src/main/res/layout/layout_onboarding_buttons_common.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_main_action"
|
||||
style="@style/OnboardingButton"
|
||||
android:layout_width="204dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="92dp"
|
||||
tools:text="Main Action"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_alternative_action"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:textSize="16sp"
|
||||
android:layout_width="204dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
tools:text="Alternative action"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/tapButtonColorBlack"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue