Updated on 2026-08-14

This commit is contained in:
Tangem 2021-12-08 15:48:34 +00:00
commit 351e7d3eeb
28 changed files with 282 additions and 221 deletions

View file

@ -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-50'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-104'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-104'
implementation 'com.tangem:blockchain:develop-51'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-105'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-105'
// WebView
implementation "androidx.browser:browser:1.3.0"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -113,7 +113,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletRes
//TODO: updating derived wallet public keys - make it better later
private fun updateDerivedKeys(wallet: CardWallet, derivedKeys: ExtendedPublicKeyList) {
val onboardingManager = store.state.globalState.onboardingManager ?: return
val onboardingManager = store.state.globalState.onboardingState.onboardingManager ?: return
onboardingManager.scanResponse = onboardingManager.scanResponse.copy(
derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys)
)

View file

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

View file

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

View file

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

View file

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

View file

@ -61,6 +61,7 @@ sealed class BackupAction : Action {
object FinishBackup : BackupAction()
object DiscardBackup : BackupAction()
object DiscardSavedBackup : BackupAction()
object ResumeBackup : BackupAction()
}

View file

@ -14,8 +14,8 @@ 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
@ -23,6 +23,8 @@ import org.rekotlin.Middleware
class OnboardingWalletMiddleware {
companion object {
val handler = onboardingWalletMiddleware
const val BUY_WALLET_URL = "https://wallet.tangem.com/"
}
}
@ -38,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
@ -73,7 +75,8 @@ private fun handleWalletAction(action: Action) {
when (result) {
is CompletionResult.Success -> {
//here we must use updated scanResponse after createWallet & derivation
val updatedResponse = globalState.onboardingManager.scanResponse.copy(card = result.data)
val updatedResponse =
globalState.onboardingState.onboardingManager.scanResponse.copy(card = result.data)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingWalletAction.ProceedBackup)
@ -88,13 +91,6 @@ 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)
@ -118,7 +114,7 @@ private fun handleWalletAction(action: Action) {
when (walletState.backupState.backupStep) {
BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo())
BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard,
-> {
store.dispatch(BackupAction.DiscardBackup)
store.dispatch(NavigationAction.PopBackTo())
@ -131,10 +127,10 @@ private fun handleWalletAction(action: Action) {
}
private fun updateScanResponseAfterBackup(
scanResponse: ScanResponse, backupState: BackupState
scanResponse: ScanResponse, backupState: BackupState,
): ScanResponse {
val card = if (backupState.backupStep == BackupStep.Finished) {
val cardsCount = backupState.backupCardsNumber + 1
val card = if (backupState.backupCardsNumber > 0) {
val cardsCount = backupState.backupCardsNumber
scanResponse.card.copy(
backupStatus = Card.BackupStatus.Active(cardCount = cardsCount),
isAccessCodeSet = true
@ -149,116 +145,138 @@ 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)
}
}
}

View file

@ -126,6 +126,7 @@ class BackupReducer {
BackupAction.CheckForUnfinishedBackup -> state
BackupAction.DiscardBackup -> state
BackupAction.ResumeBackup -> state
BackupAction.DiscardSavedBackup -> state
}
}

View file

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

View file

@ -17,8 +17,8 @@ import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.postUi
import com.tangem.tap.features.FragmentOnBackPressedHandler
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.home.redux.HomeMiddleware
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
@ -363,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)

View file

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

View file

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

View file

@ -21,9 +21,5 @@ sealed class TokensAction : Action {
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
sealed class TokensList : TokensAction() {
data class AddBlockchain(val blockchain: Blockchain) : TokensList()
data class AddToken(val token: Token) : TokensList()
object SaveChanges : TokensList()
}
data class SaveChanges(val addedItems: List<CurrencyListItem>) : TokensAction()
}

View file

@ -29,7 +29,7 @@ class TokensMiddleware {
{ action ->
when (action) {
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
is TokensAction.TokensList.SaveChanges -> handleSaveChanges(action)
is TokensAction.SaveChanges -> handleSaveChanges(action)
}
next(action)
}
@ -69,10 +69,10 @@ class TokensMiddleware {
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
}
private fun handleSaveChanges(action: TokensAction.TokensList.SaveChanges) {
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
val scanResponse = store.state.globalState.scanResponse ?: return
val candidatesToAdd = store.state.tokensState.candidateToAdd
val candidatesToAdd = action.addedItems
if (candidatesToAdd.isEmpty()) return
val blockchains = candidatesToAdd.filterIsInstance<CurrencyListItem.BlockchainListItem>()

View file

@ -43,16 +43,6 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
tokensState.copy(shownCurrencies = shownCurrencies)
}
}
is TokensAction.TokensList.AddBlockchain -> {
val candidates = tokensState.candidateToAdd.toMutableSet()
candidates.add(CurrencyListItem.BlockchainListItem(action.blockchain))
tokensState.copy(candidateToAdd = candidates.toList())
}
is TokensAction.TokensList.AddToken -> {
val candidates = tokensState.candidateToAdd.toMutableSet()
candidates.add(CurrencyListItem.TokenListItem(action.token))
tokensState.copy(candidateToAdd = candidates.toList())
}
else -> tokensState
}
}

View file

@ -10,7 +10,6 @@ data class TokensState(
val addedCurrencies: CardCurrencies? = null,
val currencies: List<CurrencyListItem> = emptyList(),
val shownCurrencies: List<CurrencyListItem> = emptyList(),
val candidateToAdd: List<CurrencyListItem> = emptyList(),
) : StateType
data class TokenWithAmount(

View file

@ -61,12 +61,18 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
btn_tokens_save_changes.setOnClickListener { store.dispatch(TokensAction.TokensList.SaveChanges)}
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
}
@ -74,8 +80,6 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
override fun newState(state: TokensState) {
if (activity == null) return
btn_tokens_save_changes.isEnabled = state.candidateToAdd.isNotEmpty()
viewAdapter.addedCurrencies = state.addedCurrencies
viewAdapter.submitUnfilteredList(state.shownCurrencies)
}

View file

@ -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
@ -26,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
@ -46,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
)
}
}
@ -60,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)
}
}
@ -104,56 +126,78 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
submitList(list)
}
class CurrenciesViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
class CurrenciesViewHolder(
private val view: View,
private val onItemAddListener: ((CurrencyListItem) -> Unit),
) : RecyclerView.ViewHolder(view) {
fun bind(currency: CurrencyListItem, addedCurrencies: CardCurrencies?) {
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
currency.isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true
modifyAddTokenButton(currency)
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
textView = view.tv_token_letter,
blockchain = blockchain, token = null
)
view.btn_add_token.setOnClickListener {
store.dispatch(TokensAction.TokensList.AddBlockchain(blockchain))
currency.isAdded = !currency.isAdded
modifyAddTokenButton(currency)
}
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
currency.isAdded = addedCurrencies?.tokens
?.any {
it.symbol == token.symbol && it.contractAddress == token.contractAddress
} == true
modifyAddTokenButton(currency)
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
textView = view.tv_token_letter,
token = token, blockchain = token.blockchain
)
view.btn_add_token.setOnClickListener {
store.dispatch(TokensAction.TokensList.AddToken(token))
currency.isAdded = !currency.isAdded
modifyAddTokenButton(currency)
}
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 file

@ -84,6 +84,7 @@ class MultiWalletMiddleware {
}
is WalletAction.MultiWallet.FindBlockchainsInUse -> {
val scanResponse = globalState.scanResponse ?: return
if (scanResponse.isTangemWallet()) return
val cardFirmware = scanResponse.card.firmwareVersion
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
@ -123,6 +124,7 @@ class MultiWalletMiddleware {
}
is WalletAction.MultiWallet.FindTokensInUse -> {
val scanResponse = globalState.scanResponse ?: return
if (scanResponse.isTangemWallet()) return
val walletFactory = tapWalletManager.walletManagerFactory
val card = scanResponse.card

View file

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

View file

@ -165,7 +165,6 @@
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="8dp"/>
@ -173,7 +172,7 @@
<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"
@ -192,7 +191,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
/>
<include
@ -209,7 +207,7 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_percent="0.55"
app:layout_constraintGuide_percent="0.60"
app:layout_constraintTop_toTopOf="parent" />

View file

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