Updated on 2026-08-14
This commit is contained in:
commit
0725a039b4
61 changed files with 914 additions and 556 deletions
|
|
@ -77,9 +77,9 @@ dependencies {
|
|||
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-99'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-99'
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isScannedBefore = store.state.globalState.scanResponse != null
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingManager != null
|
||||
if (backStackIsEmpty && (!isOnboardingServiceActive && !isScannedBefore)) {
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
}
|
||||
intentHandler.handleIntent(intent)
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ 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.BuyMoreBackupCardsDialog
|
||||
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
|
||||
|
|
@ -79,7 +79,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
TransactionDialog.create(state.dialog.dialogData, context)
|
||||
is WalletConnectDialog.PersonalSign ->
|
||||
PersonalSignDialog.create(state.dialog.data, context)
|
||||
is BackupDialog.BuyMoreBackupCards -> BuyMoreBackupCardsDialog.create(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)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ object TangemSdk {
|
|||
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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ sealed class GlobalAction : Action {
|
|||
object HideDialog : GlobalAction()
|
||||
|
||||
sealed class Onboarding {
|
||||
data class Start(val scanResponse: ScanResponse, val fromHomeScreen: Boolean = true) : 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,7 +12,10 @@ 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
|
||||
|
|
@ -23,6 +26,7 @@ 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,6 +72,14 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)))
|
||||
}
|
||||
|
||||
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(
|
||||
ResetToFactorySettingsTask(),
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,33 +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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,13 +74,13 @@ 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,
|
||||
|
|
@ -98,16 +88,37 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletRes
|
|||
) {
|
||||
val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
|
||||
val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList()
|
||||
CreateProductWalletsTask(curves).run(session) { result ->
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success ->
|
||||
callback(CompletionResult.Success(result.data.createWalletResponses[0]))
|
||||
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(
|
||||
|
|
@ -117,9 +128,9 @@ private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletRespo
|
|||
) {
|
||||
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)
|
||||
|
|
@ -40,7 +40,7 @@ class ResetToFactorySettingsTask() : CardSessionRunnable<Card> {
|
|||
) {
|
||||
|
||||
val backupStatus = session.environment.card?.backupStatus
|
||||
if (backupStatus == null && backupStatus == Card.BackupStatus.NoBackup) {
|
||||
if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) {
|
||||
callback(CompletionResult.Success(session.environment.card!!))
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -184,16 +184,11 @@ private fun handleSecurityAction(
|
|||
private fun prepareAllowedSecurityOptions(
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -27,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/"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,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 {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ sealed class BackupAction : Action {
|
|||
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()
|
||||
|
|
@ -60,6 +61,7 @@ sealed class BackupAction : Action {
|
|||
|
||||
object FinishBackup : BackupAction()
|
||||
object DiscardBackup : BackupAction()
|
||||
object DiscardSavedBackup : BackupAction()
|
||||
object ResumeBackup : BackupAction()
|
||||
|
||||
}
|
||||
|
|
@ -12,9 +12,10 @@ 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.home.redux.HomeMiddleware
|
||||
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
|
||||
|
|
@ -22,6 +23,8 @@ import org.rekotlin.Middleware
|
|||
class OnboardingWalletMiddleware {
|
||||
companion object {
|
||||
val handler = onboardingWalletMiddleware
|
||||
|
||||
const val BUY_WALLET_URL = "https://wallet.tangem.com/"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +40,7 @@ private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state
|
|||
private fun handleWalletAction(action: Action) {
|
||||
if (action !is OnboardingWalletAction) return
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingManager
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
|
@ -71,7 +74,9 @@ private fun handleWalletAction(action: Action) {
|
|||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val updatedResponse = scanResponse.copy(card = result.data)
|
||||
//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)
|
||||
|
|
@ -86,157 +91,192 @@ private fun handleWalletAction(action: Action) {
|
|||
OnboardingWalletAction.FinishOnboarding -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
|
||||
(listOf(walletState.backupState.primaryCardId) +
|
||||
walletState.backupState.backupCardIds + scanResponse?.card?.cardId)
|
||||
.distinct().filterNotNull()
|
||||
.forEach { cardId ->
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
|
||||
if (scanResponse == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(HomeAction.ReadCard)
|
||||
} else {
|
||||
scope.launch { globalState.tapWalletManager.onCardScanned(scanResponse) }
|
||||
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.Preparing -> BackupAction.IntroduceBackup
|
||||
BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard
|
||||
is BackupService.State.FinalizingBackupCard ->
|
||||
BackupAction.PrepareToWriteBackupCard(backupState.index)
|
||||
BackupService.State.Finished -> BackupAction.FinishBackup
|
||||
else -> BackupAction.IntroduceBackup
|
||||
}
|
||||
store.dispatch(newAction)
|
||||
}
|
||||
OnboardingWalletAction.OnBackPressed -> {
|
||||
if (walletState.backupState.backupStep is BackupStep.WriteBackupCard ||
|
||||
walletState.backupState.backupStep is BackupStep.WritePrimaryCard) {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
} else {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
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 ->
|
||||
val backupState = state()?.onboardingWalletState?.backupState
|
||||
when (action) {
|
||||
is BackupAction.StartBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.ScanPrimaryCard -> {
|
||||
backupService.readPrimaryCard { 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(HomeMiddleware.CARD_SHOP_URI)
|
||||
}
|
||||
is BackupAction.FinishAddingBackupCards -> {
|
||||
if (backupService.addedBackupCardsCount == 1) {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.BuyMoreBackupCards))
|
||||
}
|
||||
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 -> {
|
||||
// store.dispatch(OnboardingWalletAction.Done)
|
||||
}
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.CheckForUnfinishedBackup -> {
|
||||
if (backupService.hasIncompletedBackup) {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound))
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeBackup -> {
|
||||
store.dispatch(OnboardingWalletAction.ProceedBackup)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
is BackupAction.DismissBackup -> {
|
||||
store.dispatch(BackupAction.FinishBackup)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,10 @@ class BackupReducer {
|
|||
): BackupState {
|
||||
|
||||
return when (action) {
|
||||
BackupAction.IntroduceBackup -> state.copy(backupStep = BackupStep.InitBackup)
|
||||
BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
canSkipBackup = state.canSkipBackup
|
||||
)
|
||||
|
||||
BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard)
|
||||
|
||||
|
|
@ -104,6 +107,10 @@ class BackupReducer {
|
|||
)
|
||||
}
|
||||
|
||||
BackupAction.OnAccessCodeDialogClosed -> {
|
||||
state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
}
|
||||
|
||||
BackupAction.WritePrimaryCard -> state
|
||||
is BackupAction.WriteBackupCard -> state
|
||||
is BackupAction.SaveAccessCodeConfirmation -> state
|
||||
|
|
@ -119,6 +126,7 @@ class BackupReducer {
|
|||
BackupAction.CheckForUnfinishedBackup -> state
|
||||
BackupAction.DiscardBackup -> state
|
||||
BackupAction.ResumeBackup -> state
|
||||
BackupAction.DiscardSavedBackup -> state
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ sealed class BackupStep {
|
|||
}
|
||||
|
||||
sealed class BackupDialog: StateDialog {
|
||||
object BuyMoreBackupCards : StateDialog
|
||||
object AddMoreBackupCards : StateDialog
|
||||
object BackupInProgress : StateDialog
|
||||
object UnfinishedBackupFound : StateDialog
|
||||
object ConfirmDiscardingBackup : StateDialog
|
||||
|
|
|
|||
|
|
@ -10,20 +10,19 @@ 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.addOnBackPressedDispatcher
|
||||
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.home.redux.HomeMiddleware
|
||||
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.fragment_onboarding_wallet.toolbar
|
||||
import kotlinx.android.synthetic.main.fragment_wallet_details.*
|
||||
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.*
|
||||
|
|
@ -31,7 +30,7 @@ import kotlinx.android.synthetic.main.view_onboarding_progress.*
|
|||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState> {
|
||||
StoreSubscriber<OnboardingWalletState>, FragmentOnBackPressedHandler {
|
||||
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
private lateinit var cardsWidget: BackupCardsWidget
|
||||
|
|
@ -41,11 +40,6 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
postponeEnterTransition()
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
|
||||
activity?.addOnBackPressedDispatcher {
|
||||
store.dispatch(OnboardingWalletAction.OnBackPressed)
|
||||
}
|
||||
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
|
|
@ -68,7 +62,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
store.dispatch(OnboardingWalletAction.Init)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
// store.dispatch(OnboardingWalletAction.LoadArtwork)
|
||||
addBackPressHandler(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -181,6 +175,9 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
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) {
|
||||
|
|
@ -224,7 +221,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
dismissWithAnimation = true
|
||||
create()
|
||||
setOnCancelListener {
|
||||
|
||||
store.dispatch(BackupAction.OnAccessCodeDialogClosed)
|
||||
}
|
||||
show()
|
||||
showInfoScreen()
|
||||
|
|
@ -366,7 +363,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.shop_menu -> {
|
||||
store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
|
||||
store.dispatchOpenUrl(BUY_WALLET_URL)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
|
@ -381,4 +378,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards
|
||||
menu.getItem(0).isVisible = shopMenuShouldBeVisible
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingWalletAction.OnBackPressed)
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
|
|||
|
||||
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 {
|
||||
|
|
@ -43,6 +44,7 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
|
|||
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()))
|
||||
|
|
@ -52,6 +54,7 @@ class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class BuyMoreBackupCardsDialog {
|
||||
class AddMoreBackupCardsDialog {
|
||||
companion object {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
|
|
@ -16,8 +16,8 @@ class BuyMoreBackupCardsDialog {
|
|||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ShowAccessCodeInfoScreen)
|
||||
}
|
||||
setNegativeButton(R.string.onboarding_button_buy_more_cards) { _, _ ->
|
||||
store.dispatch(BackupAction.GoToShop)
|
||||
setNegativeButton(R.string.onboarding_button_add_more_cards) { _, _ ->
|
||||
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
@ -17,7 +17,7 @@ class ConfirmDiscardingBackupDialog {
|
|||
store.dispatch(BackupAction.ResumeBackup)
|
||||
}
|
||||
setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ ->
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatch(BackupAction.DiscardSavedBackup)
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class MultiWalletView : WalletView {
|
|||
|
||||
private fun setupWalletCardNumber(fragment: WalletFragment) = with(fragment) {
|
||||
val card = store.state.globalState.scanResponse?.card
|
||||
if (card?.backupStatus?.isActive == true) {
|
||||
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 =
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class UsedCardsPrefStorage(
|
|||
}
|
||||
|
||||
fun activationIsStarted(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isActivationStarted ?: true
|
||||
return findCardInfo(cardId)?.isActivationStarted ?: false
|
||||
}
|
||||
|
||||
fun activationFinished(cardId: String) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -78,30 +78,27 @@
|
|||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_second_backup_card"
|
||||
android:layout_width="296dp"
|
||||
android:layout_height="186dp"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_black"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_first_backup_card"
|
||||
android:layout_width="296dp"
|
||||
android:layout_height="186dp"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_black"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_front_card"
|
||||
android:layout_width="296dp"
|
||||
android:layout_height="186dp"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_black"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:transitionName="imv_front_card"
|
||||
/>
|
||||
|
||||
|
|
@ -168,15 +165,14 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintHorizontal_bias="0.504"
|
||||
app:layout_constraintBottom_toTopOf="@id/layout_buttons_common"
|
||||
app:flow_verticalBias="0"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline2"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
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="wrap_content"
|
||||
android:layout_height="10dp"
|
||||
android:background="@color/backgroundLightGray"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -195,7 +191,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
/>
|
||||
|
||||
<include
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<color name="tapButtonColor">@color/accent</color>
|
||||
<color name="tapButtonDisabled">#661ACE80</color>
|
||||
<color name="tapButtonColorBlack">@color/darkGray6</color>
|
||||
<color name="tapButtonBlackDisabled">#661C1C1E</color>
|
||||
<color name="tapButtonBlackDisabled">#6A6A6A</color>
|
||||
|
||||
<color name="success">#5AC461</color>
|
||||
<color name="error">#C4291C</color>
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@
|
|||
<string name="onboarding_button_add_backup_card" translatable="false">+ Add backup card</string>
|
||||
<string name="onboarding_button_backup_card_format" translatable="false">Scan the card #%d</string>
|
||||
<string name="onboarding_button_scan_origin_card" translatable="false">Scan primary card</string>
|
||||
<string name="onboarding_button_buy_more_cards" translatable="false">Buy more cards</string>
|
||||
<string name="onboarding_button_add_more_cards" translatable="false">Add more cards</string>
|
||||
|
||||
<string name="onboarding_title_backup_card" translatable="false">Backup your wallet</string>
|
||||
<string name="onboarding_title_no_backup_cards" translatable="false">No backup cards</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue