Updated on 2026-08-14

This commit is contained in:
Tangem 2021-10-22 02:59:15 +03:00
parent bcfca80f54
commit 66f7ab98e9
30 changed files with 412 additions and 442 deletions

View file

@ -11,6 +11,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.twins.redux.TwinCardsReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import org.rekotlin.Action
@ -26,6 +27,7 @@ fun appReducer(action: Action, state: AppState?): AppState {
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
walletState = WalletReducer.reduce(action, state),
twinCardsState = TwinCardsReducer.reduce(action, state),
sendState = SendScreenReducer.reduce(action, state.sendState),
detailsState = DetailsReducer.reduce(action, state),
disclaimerState = DisclaimerReducer.reduce(action, state),

View file

@ -22,7 +22,8 @@ import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.twins.redux.CreateTwinWalletMiddleware
import com.tangem.tap.features.twins.redux.TwinCardsMiddleware
import com.tangem.tap.features.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
import org.rekotlin.Middleware
@ -36,6 +37,7 @@ data class AppState(
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
val walletState: WalletState = WalletState(),
val twinCardsState: TwinCardsState = TwinCardsState(),
val sendState: SendState = SendState(),
val detailsState: DetailsState = DetailsState(),
val disclaimerState: DisclaimerState = DisclaimerState(),
@ -53,7 +55,7 @@ data class AppState(
OnboardingWalletMiddleware.handler,
OnboardingOtherCardsMiddleware.handler,
WalletMiddleware().walletMiddleware,
CreateTwinWalletMiddleware.handler,
TwinCardsMiddleware.handler,
SendMiddleware().sendMiddleware,
DetailsMiddleware().detailsMiddleware,
DisclaimerMiddleware().disclaimerMiddleware,

View file

@ -19,6 +19,7 @@ import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.twins.getTwinCardNumber
import com.tangem.tap.domain.twins.isTangemTwin
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
@ -82,7 +83,8 @@ class TapWalletManager {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain)
}
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
updateConfigManager(data)
val configManager = store.state.globalState.configManager
updateConfigManager(configManager, data)
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
@ -91,7 +93,8 @@ class TapWalletManager {
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
if (data.card.isTangemTwin()) {
data.card.getTwinCardNumber()?.let {
store.dispatch(WalletAction.TwinsAction.SetTwinCard(null, it, true))
val isCreatingTwinsAllowed = configManager?.config?.isCreatingTwinCardsAllowed ?: false
store.dispatch(TwinCardsAction.SetTwinCard(it, null, isCreatingTwinsAllowed))
}
}
@ -104,8 +107,7 @@ class TapWalletManager {
}
}
private fun updateConfigManager(data: ScanResponse) {
val configManager = store.state.globalState.configManager
private fun updateConfigManager(configManager: ConfigManager?, data: ScanResponse) {
val blockchain = data.getBlockchain()
if (data.card.isStart2Coin) {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
@ -122,53 +124,42 @@ class TapWalletManager {
suspend fun loadData(data: ScanResponse) {
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.LoadCardInfo(data.card))
getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
store.dispatch(it)
return@withContext
}
when {
data.card.isTangemTwin() && data.secondTwinPublicKey == null -> {
store.dispatch(WalletAction.EmptyWallet)
val config = store.state.globalState.configManager?.config ?: return@withContext
val blockchain = data.getBlockchain()
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
val primaryToken = data.getPrimaryToken()
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
if (primaryToken != null) {
primaryWalletManager.addToken(primaryToken)
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
}
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, primaryWalletManager)
} else {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
}
data.card.wallets.isEmpty() -> {
store.dispatch(WalletAction.EmptyWallet)
}
else -> {
val config = store.state.globalState.configManager?.config ?: return@withContext
val blockchain = data.getBlockchain()
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
val primaryToken = data.getPrimaryToken()
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
if (primaryToken != null) {
primaryWalletManager.addToken(primaryToken)
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
}
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, primaryWalletManager)
} else {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
}
} else {
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, null)
}
}
val moonPayUserStatus = store.state.globalState.moonPayUserStatus
store.dispatch(WalletAction.LoadWallet(
allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true,
allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true,
))
store.dispatch(WalletAction.LoadFiatRate())
} else {
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, null)
}
}
val moonPayUserStatus = store.state.globalState.moonPayUserStatus
store.dispatch(WalletAction.LoadWallet(
allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true,
allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true,
))
store.dispatch(WalletAction.LoadFiatRate())
}
}
@ -220,28 +211,38 @@ class TapWalletManager {
suspend fun reloadData(data: ScanResponse) {
withContext(Dispatchers.Main) {
when {
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
}
data.card.wallets.isEmpty() ||
(data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> {
store.dispatch(WalletAction.EmptyWallet)
}
else -> {
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
return@withContext
}
val config = store.state.globalState.configManager?.config ?: return@withContext
val moonpayUserStatus = store.state.globalState.moonPayUserStatus
store.dispatch(WalletAction.LoadWallet(
allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true,
allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true,
))
store.dispatch(WalletAction.LoadFiatRate())
}
getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
store.dispatch(it)
return@withContext
}
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
return@withContext
}
val config = store.state.globalState.configManager?.config ?: return@withContext
val moonpayUserStatus = store.state.globalState.moonPayUserStatus
store.dispatch(WalletAction.LoadWallet(
allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true,
allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true,
))
store.dispatch(WalletAction.LoadFiatRate())
}
}
private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? {
return when {
// check order is important
data.card.isTangemTwin() && !data.twinsIsTwinned() -> {
WalletAction.EmptyWallet
}
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
}
data.card.wallets.isEmpty() -> {
WalletAction.EmptyWallet
}
else -> null
}
}

View file

@ -28,10 +28,13 @@ object TapWorkarounds {
return excludedBatch || excludedIssuerName
}
fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId)
fun Card.isTangemWallet(): Boolean = tangemWalletBatches.contains(batchId)
@Deprecated("Use ScanResponse.isTangemNote")
fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId)
fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId]
@Deprecated("Use ScanResponse.isTangemWallet")
fun isTangemWallet(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
fun getTangemNoteBlockchain(card: Card): Blockchain? = tangemNoteBatches[card.batchId]
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
@ -64,7 +67,7 @@ val DELAY_SDK_DIALOG_CLOSE = 1400L
val Card.isMultiwalletAllowed: Boolean
get() {
return !isTangemTwin() && !isStart2Coin && !isTangemNote()
return !isTangemTwin() && !isStart2Coin && !isTangemNote(this)
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
}

View file

@ -40,7 +40,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanResponse> {
if (card.isTangemTwin()) {
dealWithTwinCard(card, session, callback)
} else if (!card.isTangemNote() && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) {
} else if (!isTangemNote(card) && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) {
createMissingWalletsIfNeeded(card, session, callback)
} else {
callback(CompletionResult.Success(

View file

@ -64,7 +64,7 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
return
}
val curvesSupportedByBlockchain = card.getTangemNoteBlockchain()?.getSupportedCurves()
val curvesSupportedByBlockchain = getTangemNoteBlockchain(card)?.getSupportedCurves()
if (curvesSupportedByBlockchain == null || curvesSupportedByBlockchain.isEmpty()) {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return

View file

@ -19,10 +19,9 @@ import com.tangem.operations.ScanTask
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
import com.tangem.tap.domain.TapWorkarounds.isTangemWallet
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.domain.twins.isTangemTwin
@ -35,7 +34,7 @@ data class ScanResponse(
) : CommandResponse {
fun getBlockchain(): Blockchain {
if (productType == ProductType.Note) return card.getTangemNoteBlockchain() ?: return Blockchain.Unknown
if (productType == ProductType.Note) return getTangemNoteBlockchain(card) ?: return Blockchain.Unknown
val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown
return Blockchain.fromId(blockchainName)
}
@ -51,11 +50,20 @@ data class ScanResponse(
)
}
fun isTangemNote(): Boolean = productType == ProductType.Note
fun isTangemWallet(): Boolean = productType == ProductType.Wallet
fun isTangemTwins(): Boolean = productType == ProductType.Twins
fun isTangemOtherCards(): Boolean = productType == ProductType.Other
fun twinsIsTwinned(): Boolean {
return walletData != null && secondTwinPublicKey != null
return card.isTangemTwin() && walletData != null && secondTwinPublicKey != null
}
}
private fun Card.isTangemTwin(): Boolean {
return TwinsHelper.getTwinCardNumber(cardId) != null
}
class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanResponse> {
override fun run(
@ -74,9 +82,9 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanResponse
}
val commandProcessor = when {
card.isTangemNote() -> ScanNoteProcessor()
card.isTangemTwin() -> ScanTwinProcessor()
card.isTangemWallet() -> ScanWalletProcessor()
TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
TwinsHelper.getTwinCardNumber(card.cardId) != null -> ScanTwinProcessor()
TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor()
else -> ScanOtherCardsProcessor()
}
commandProcessor.proceed(card, session, callback)

View file

@ -51,6 +51,7 @@ enum class TwinCardNumber(val number: Int) {
}
}
@Deprecated("Use ScanResponse.isTangemTwin")
fun Card.isTangemTwin(): Boolean {
return TwinsHelper.getTwinCardNumber(cardId) != null
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.details.redux
import com.tangem.blockchain.common.Wallet
import com.tangem.common.card.Card
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.FiatCurrencyName
@ -14,10 +13,8 @@ import org.rekotlin.Action
sealed class DetailsAction : Action {
data class PrepareScreen(
val card: Card,
val scanResponse: ScanResponse,
val wallets: List<Wallet>,
val isCreatingTwinWalletAllowed: Boolean?,
val cardTou: CardTou,
val fiatCurrencyName: FiatCurrencyName,
val fiatCurrencies: List<FiatCurrencyName>? = null,

View file

@ -7,9 +7,6 @@ import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.extensions.isWalletDataSupported
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.twins.getTwinCardNumber
import com.tangem.tap.domain.twins.isTangemTwin
import com.tangem.tap.features.twins.redux.CreateTwinWalletState
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import org.rekotlin.Action
import java.util.*
@ -42,26 +39,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
}
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState {
val twinsState = if (action.card.isTangemTwin()) {
CreateTwinWalletState(
scanResponse = action.scanResponse,
twinCardNumber = action.card.getTwinCardNumber(),
createTwinWallet = null,
showAlert = false,
allowRecreatingWallet = action.isCreatingTwinWalletAllowed
)
} else {
null
}
return DetailsState(
card = action.card, wallets = action.wallets,
cardInfo = action.card.toCardInfo(),
appCurrencyState = AppCurrencyState(
action.fiatCurrencyName
),
createTwinWalletState = twinsState,
cardTermsOfUseUrl = action.cardTou.getUrl(action.card)
card = action.scanResponse.card,
wallets = action.wallets,
cardInfo = action.scanResponse.card.toCardInfo(),
appCurrencyState = AppCurrencyState(action.fiatCurrencyName),
cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card)
)
}

View file

@ -6,10 +6,12 @@ import com.tangem.common.card.Card
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.features.twins.redux.CreateTwinWalletState
import com.tangem.tap.features.twins.redux.TwinCardsState
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import com.tangem.tap.store
import org.rekotlin.StateType
import java.util.*
import kotlin.properties.ReadOnlyProperty
data class DetailsState(
val card: Card? = null,
@ -19,9 +21,18 @@ data class DetailsState(
val eraseWalletState: EraseWalletState? = null,
val confirmScreenState: ConfirmScreenState? = null,
val securityScreenState: SecurityScreenState? = null,
val createTwinWalletState: CreateTwinWalletState? = null,
val cardTermsOfUseUrl: Uri? = null,
) : StateType
) : StateType {
// if you do not delegate - the application crashes on startup,
// because twinCardsState has not been created yet
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { thisRef, property ->
store.state.twinCardsState
}
val isTangemTwins: Boolean
get() = store.state.globalState.scanResponse?.isTangemTwins() == true
}
data class CardInfo(
val cardId: String,

View file

@ -17,8 +17,8 @@ import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.features.twins.redux.CreateTwinWallet
import com.tangem.tap.features.twins.redux.CreateTwinWalletAction
import com.tangem.tap.features.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.twins.redux.TwinCardsAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_details.*
@ -91,12 +91,15 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
startActivity(intent)
}
if (state.createTwinWalletState != null) {
if (state.createTwinWalletState.allowRecreatingWallet == true) {
if (state.isTangemTwins) {
if (state.twinCardsState.isCreatingTwinCardsAllowed) {
tv_erase_wallet.show()
tv_erase_wallet.text = getText(R.string.details_row_title_twins_recreate)
tv_erase_wallet.setOnClickListener {
store.dispatch(CreateTwinWalletAction.ShowWarning(null, CreateTwinWallet.RecreateWallet))
store.dispatch(TwinCardsAction.CreateWallet.Create(
state.twinCardsState.cardNumber!!,
CreateTwinWalletMode.RecreateWallet
))
}
} else {
tv_erase_wallet.hide()

View file

@ -11,8 +11,8 @@ import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.features.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.wallet.R
@ -37,9 +37,9 @@ class OnboardingTwinsFragment : Fragment(R.layout.fragment_twin_cards_welcome) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
store.dispatch(WalletAction.TwinsAction.SetOnboardingShown)
store.dispatch(TwinCardsAction.SetOnboardingShown)
val secondCardId = store.state.walletState.twinCardsState?.secondCardId ?: ""
val secondCardId = store.state.twinCardsState.secondCardId ?: ""
val secondTwinCardId = TwinsHelper.getTwinCardIdForUser(secondCardId)
val text = getString(R.string.twins_onboarding_description_format, secondTwinCardId)
tv_twin_cards_description_1.text = text

View file

@ -1,52 +0,0 @@
package com.tangem.tap.features.twins.redux
import android.content.Context
import com.tangem.Message
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.wallet.R
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
sealed class CreateTwinWalletAction : Action {
data class ShowWarning(
val twinCardNumber: TwinCardNumber?,
val createTwinWallet: CreateTwinWallet = CreateTwinWallet.RecreateWallet
) : CreateTwinWalletAction()
object NotEmpty : CreateTwinWalletAction(), NotificationAction {
override val messageResource = R.string.details_notification_erase_wallet_not_possible
}
object ShowAlert : CreateTwinWalletAction()
object HideAlert : CreateTwinWalletAction()
object Proceed : CreateTwinWalletAction()
object Cancel : CreateTwinWalletAction() {
object Confirm : CreateTwinWalletAction()
}
data class LaunchFirstStep(
val message: Message, val context: Context
) : CreateTwinWalletAction() {
object Success : CreateTwinWalletAction()
object Failure : CreateTwinWalletAction()
}
data class LaunchSecondStep(
val initialMessage: Message,
val preparingMessage: Message,
val creatingWalletMessage: Message,
) : CreateTwinWalletAction() {
object Success : CreateTwinWalletAction()
object Failure : CreateTwinWalletAction()
}
data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() {
data class Success(val scanResponse: ScanResponse) : CreateTwinWalletAction()
object Failure : CreateTwinWalletAction()
}
}

View file

@ -1,67 +0,0 @@
package com.tangem.tap.features.twins.redux
import com.tangem.tap.features.details.redux.DetailsState
class CreateTwinWalletReducer {
companion object {
fun handle(
action: CreateTwinWalletAction, state: DetailsState
): DetailsState {
return when (action) {
is CreateTwinWalletAction.ShowWarning -> {
state.copy(createTwinWalletState = CreateTwinWalletState(
scanResponse = null,
twinCardNumber = action.twinCardNumber
?: state.createTwinWalletState?.twinCardNumber,
createTwinWallet = action.createTwinWallet,
showAlert = false,
allowRecreatingWallet = state.createTwinWalletState?.allowRecreatingWallet
))
}
CreateTwinWalletAction.NotEmpty -> state
CreateTwinWalletAction.ShowAlert -> {
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
showAlert = true
))
}
CreateTwinWalletAction.HideAlert -> {
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
showAlert = false
))
}
is CreateTwinWalletAction.Proceed -> {
state
}
CreateTwinWalletAction.Cancel -> state
CreateTwinWalletAction.Cancel.Confirm -> state
is CreateTwinWalletAction.LaunchFirstStep -> state
CreateTwinWalletAction.LaunchFirstStep.Success -> {
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
step = CreateTwinWalletStep.SecondStep
))
}
CreateTwinWalletAction.LaunchFirstStep.Failure -> state
is CreateTwinWalletAction.LaunchSecondStep -> state
CreateTwinWalletAction.LaunchSecondStep.Success ->
state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
step = CreateTwinWalletStep.ThirdStep
))
CreateTwinWalletAction.LaunchSecondStep.Failure -> {
state
}
is CreateTwinWalletAction.LaunchThirdStep -> {
state
}
is CreateTwinWalletAction.LaunchThirdStep.Success -> {
state
}
CreateTwinWalletAction.LaunchThirdStep.Failure -> {
state
}
}
}
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.tap.features.twins.redux
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.twins.TwinCardNumber
data class CreateTwinWalletState(
val scanResponse: ScanResponse?,
val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep,
val twinCardNumber: TwinCardNumber?,
val createTwinWallet: CreateTwinWallet?,
val showAlert: Boolean,
val allowRecreatingWallet: Boolean? = null
)
enum class CreateTwinWalletStep { FirstStep, SecondStep, ThirdStep }
enum class CreateTwinWallet { CreateWallet, RecreateWallet }

View file

@ -0,0 +1,62 @@
package com.tangem.tap.features.twins.redux
import android.content.Context
import com.tangem.Message
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.wallet.R
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
sealed class TwinCardsAction : Action {
object ShowOnboarding : TwinCardsAction()
object SetOnboardingShown : TwinCardsAction()
data class SetTwinCard(
val number: TwinCardNumber,
val secondCardId: String?,
val isCreatingTwinCardsAllowed: Boolean,
) : TwinCardsAction()
sealed class CreateWallet : TwinCardsAction() {
data class Create(
val number: TwinCardNumber,
val createTwinWalletMode: CreateTwinWalletMode
) : CreateWallet()
object NotEmpty : CreateWallet(), NotificationAction {
override val messageResource = R.string.details_notification_erase_wallet_not_possible
}
object ShowAlert : CreateWallet()
object HideAlert : CreateWallet()
object Proceed : CreateWallet()
object Cancel : CreateWallet() {
object Confirm : CreateWallet()
}
data class LaunchFirstStep(
val message: Message, val context: Context
) : CreateWallet() {
object Success : CreateWallet()
object Failure : CreateWallet()
}
data class LaunchSecondStep(
val initialMessage: Message,
val preparingMessage: Message,
val creatingWalletMessage: Message,
) : CreateWallet() {
object Success : CreateWallet()
object Failure : CreateWallet()
}
data class LaunchThirdStep(val message: Message) : CreateWallet() {
data class Success(val scanResponse: ScanResponse) : CreateWallet()
object Failure : CreateWallet()
}
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
@ -16,7 +17,7 @@ import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
class CreateTwinWalletMiddleware {
class TwinCardsMiddleware {
companion object {
val handler = twinsWalletMiddleware
}
@ -34,40 +35,47 @@ private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, state ->
private var twinsManager: TwinCardsManager? = null
private fun handle(action: Action, dispatch: DispatchFunction) {
val action = action as? CreateTwinWalletAction ?: return
val action = action as? TwinCardsAction ?: return
val twinCardsState = store.state.twinCardsState
when (action) {
is CreateTwinWalletAction.ShowWarning -> {
val wallet = store.state.detailsState.wallets.firstOrNull()
is TwinCardsAction.SetTwinCard -> {
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
if (showOnboarding) store.dispatch(TwinCardsAction.ShowOnboarding)
}
TwinCardsAction.SetOnboardingShown -> {
preferencesStorage.saveTwinsOnboardingShown()
}
is TwinCardsAction.CreateWallet.Create -> {
val wallet = store.state.walletState.walletManagers.map { it.wallet }.firstOrNull()
if (wallet == null) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
return
}
val notEmpty = wallet.recentTransactions.isNotEmpty() || wallet.amounts.toSendableAmounts().isNotEmpty()
if (notEmpty) {
store.dispatch(CreateTwinWalletAction.NotEmpty)
store.dispatch(TwinCardsAction.CreateWallet.NotEmpty)
} else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
}
}
is CreateTwinWalletAction.Proceed ->
is TwinCardsAction.CreateWallet.Proceed -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWallet))
is CreateTwinWalletAction.Cancel -> {
val step = store.state.detailsState.createTwinWalletState?.step
if (step != null && step != CreateTwinWalletStep.FirstStep
) {
store.dispatch(CreateTwinWalletAction.ShowAlert)
}
is TwinCardsAction.CreateWallet.Cancel -> {
val step = twinCardsState.createWalletState?.step
if (step != null && step != CreateTwinWalletStep.FirstStep) {
store.dispatch(TwinCardsAction.CreateWallet.ShowAlert)
} else {
twinsManager = null
store.dispatch(NavigationAction.PopBackTo())
}
}
is CreateTwinWalletAction.Cancel.Confirm -> {
is TwinCardsAction.CreateWallet.Cancel.Confirm -> {
twinsManager = null
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
is CreateTwinWalletAction.LaunchFirstStep -> {
is TwinCardsAction.CreateWallet.LaunchFirstStep -> {
store.state.globalState.scanResponse?.let {
twinsManager = TwinCardsManager(it, action.context)
}
@ -76,62 +84,59 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
withContext(Dispatchers.Main) {
when (result) {
SimpleResult.Success ->
store.dispatch(CreateTwinWalletAction.LaunchFirstStep.Success)
store.dispatch(TwinCardsAction.CreateWallet.LaunchFirstStep.Success)
is SimpleResult.Failure ->
store.dispatch(CreateTwinWalletAction.LaunchFirstStep.Failure)
store.dispatch(TwinCardsAction.CreateWallet.LaunchFirstStep.Failure)
}
}
}
}
CreateTwinWalletAction.LaunchFirstStep.Success -> {
TwinCardsAction.CreateWallet.LaunchFirstStep.Success -> {
}
CreateTwinWalletAction.LaunchFirstStep.Failure -> {
TwinCardsAction.CreateWallet.LaunchFirstStep.Failure -> {
}
is CreateTwinWalletAction.LaunchSecondStep ->
is TwinCardsAction.CreateWallet.LaunchSecondStep ->
scope.launch {
val result = twinsManager?.createSecondWallet(action.initialMessage,
action.preparingMessage, action.creatingWalletMessage)
withContext(Dispatchers.Main) {
when (result) {
SimpleResult.Success ->
store.dispatch(CreateTwinWalletAction.LaunchSecondStep.Success)
store.dispatch(TwinCardsAction.CreateWallet.LaunchSecondStep.Success)
is SimpleResult.Failure ->
store.dispatch(CreateTwinWalletAction.LaunchSecondStep.Failure)
store.dispatch(TwinCardsAction.CreateWallet.LaunchSecondStep.Failure)
}
}
}
CreateTwinWalletAction.LaunchSecondStep.Success -> {
TwinCardsAction.CreateWallet.LaunchSecondStep.Success -> {
}
CreateTwinWalletAction.LaunchSecondStep.Failure -> {
TwinCardsAction.CreateWallet.LaunchSecondStep.Failure -> {
}
is CreateTwinWalletAction.LaunchThirdStep -> {
is TwinCardsAction.CreateWallet.LaunchThirdStep -> {
scope.launch {
val result = twinsManager?.complete(action.message)
withContext(Dispatchers.Main) {
when (result) {
is Result.Success ->
store.dispatch(
CreateTwinWalletAction
.LaunchThirdStep.Success(result.data)
)
store.dispatch(TwinCardsAction.CreateWallet.LaunchThirdStep.Success(result.data))
is Result.Failure ->
store.dispatch(CreateTwinWalletAction.LaunchThirdStep.Failure)
store.dispatch(TwinCardsAction.CreateWallet.LaunchThirdStep.Failure)
}
}
}
}
is CreateTwinWalletAction.LaunchThirdStep.Success -> {
is TwinCardsAction.CreateWallet.LaunchThirdStep.Success -> {
scope.launch {
store.state.globalState.tapWalletManager.onCardScanned(action.scanResponse)
}
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
CreateTwinWalletAction.LaunchThirdStep.Failure -> {
TwinCardsAction.CreateWallet.LaunchThirdStep.Failure -> {
}
}

View file

@ -0,0 +1,63 @@
package com.tangem.tap.features.twins.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
class TwinCardsReducer {
companion object {
fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state)
}
}
private fun internalReduce(action: Action, state: AppState): TwinCardsState {
if (action !is TwinCardsAction) return state.twinCardsState
val twinCardsState = state.twinCardsState
return when (action) {
is TwinCardsAction.SetTwinCard -> {
twinCardsState.copy(
cardNumber = action.number,
secondCardId = action.secondCardId,
showTwinOnboarding = state.twinCardsState.showTwinOnboarding,
isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed,
)
}
is TwinCardsAction.ShowOnboarding -> {
state.twinCardsState.copy(showTwinOnboarding = true)
}
is TwinCardsAction.SetOnboardingShown -> {
twinCardsState.copy(showTwinOnboarding = false)
}
is TwinCardsAction.CreateWallet.Create -> {
val prevState = twinCardsState.createWalletState
twinCardsState.copy(createWalletState = CreateTwinWalletState(
prevState?.scanResponse,
prevState?.number ?: action.number,
prevState?.mode ?: action.createTwinWalletMode,
))
}
TwinCardsAction.CreateWallet.LaunchFirstStep.Success -> {
twinCardsState.copy(createWalletState = twinCardsState.createWalletState?.copy(
step = CreateTwinWalletStep.SecondStep
))
}
TwinCardsAction.CreateWallet.LaunchSecondStep.Success ->
twinCardsState.copy(createWalletState = twinCardsState.createWalletState?.copy(
step = CreateTwinWalletStep.ThirdStep
))
TwinCardsAction.CreateWallet.ShowAlert -> twinCardsState
TwinCardsAction.CreateWallet.HideAlert -> twinCardsState
is TwinCardsAction.CreateWallet.Proceed -> twinCardsState
TwinCardsAction.CreateWallet.NotEmpty -> twinCardsState
TwinCardsAction.CreateWallet.Cancel -> twinCardsState
TwinCardsAction.CreateWallet.Cancel.Confirm -> twinCardsState
is TwinCardsAction.CreateWallet.LaunchFirstStep -> twinCardsState
TwinCardsAction.CreateWallet.LaunchFirstStep.Failure -> twinCardsState
is TwinCardsAction.CreateWallet.LaunchSecondStep -> twinCardsState
TwinCardsAction.CreateWallet.LaunchSecondStep.Failure -> twinCardsState
is TwinCardsAction.CreateWallet.LaunchThirdStep -> twinCardsState
is TwinCardsAction.CreateWallet.LaunchThirdStep.Success -> twinCardsState
TwinCardsAction.CreateWallet.LaunchThirdStep.Failure -> twinCardsState
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.tap.features.twins.redux
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.twins.TwinCardNumber
import org.rekotlin.StateType
/**
[REDACTED_AUTHOR]
*/
data class TwinCardsState(
// if cardNumber is not set -> the scanned card is not Twin
val cardNumber: TwinCardNumber? = null,
val secondCardId: String? = null,
val showTwinOnboarding: Boolean = false,
val isCreatingTwinCardsAllowed: Boolean = false,
val createWalletState: CreateTwinWalletState? = null
) : StateType {
val isReadyToUse: Boolean
get() = cardNumber != null
}
data class CreateTwinWalletState(
val scanResponse: ScanResponse?,
val number: TwinCardNumber = TwinCardNumber.First,
val mode: CreateTwinWalletMode = CreateTwinWalletMode.CreateWallet,
val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep,
) {
val showAlert: Boolean
get() = mode != CreateTwinWalletMode.RecreateWallet
}
enum class CreateTwinWalletStep { FirstStep, SecondStep, ThirdStep }
enum class CreateTwinWalletMode { CreateWallet, RecreateWallet }

View file

@ -11,10 +11,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.squareup.picasso.Picasso
import com.tangem.Message
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.twins.redux.CreateTwinWallet
import com.tangem.tap.features.twins.redux.CreateTwinWalletAction
import com.tangem.tap.features.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.twins.redux.CreateTwinWalletStep
import com.tangem.tap.features.twins.redux.TwinCardsAction
import com.tangem.tap.features.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.store
import com.tangem.wallet.R
@ -22,7 +22,7 @@ import kotlinx.android.synthetic.main.fragment_twin_cards_create.*
import org.rekotlin.StoreSubscriber
class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
StoreSubscriber<DetailsState> {
StoreSubscriber<TwinCardsState> {
private var dialog: Dialog? = null
@ -30,7 +30,7 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(CreateTwinWalletAction.Cancel)
store.dispatch(TwinCardsAction.CreateWallet.Cancel)
}
})
val inflater = TransitionInflater.from(requireContext())
@ -42,8 +42,8 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
super.onStart()
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.detailsState == newState.detailsState
}.select { it.detailsState }
oldState.twinCardsState == newState.twinCardsState
}.select { it.twinCardsState }
}
}
@ -56,7 +56,7 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
super.onViewCreated(view, savedInstanceState)
toolbar.setNavigationOnClickListener {
store.dispatch(CreateTwinWalletAction.Cancel)
store.dispatch(TwinCardsAction.CreateWallet.Cancel)
}
Picasso.get()
@ -73,21 +73,21 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
}
override fun newState(state: DetailsState) {
override fun newState(state: TwinCardsState) {
if (activity == null) return
toolbar.title = when (state.createTwinWalletState?.createTwinWallet) {
CreateTwinWallet.CreateWallet -> getText(R.string.wallet_button_create_wallet)
CreateTwinWallet.RecreateWallet, null -> getText(R.string.details_twins_recreate_toolbar)
toolbar.title = when (state.createWalletState?.mode) {
CreateTwinWalletMode.CreateWallet -> getText(R.string.wallet_button_create_wallet)
CreateTwinWalletMode.RecreateWallet, null -> getText(R.string.details_twins_recreate_toolbar)
}
val selectedColor = getColor(requireContext(), R.color.colorSecondary)
val defaultColor = getColor(requireContext(), R.color.blue_pale)
val twinCardNumber = state.createTwinWalletState?.twinCardNumber ?: TwinCardNumber.First
val twinCardNumber = state.createWalletState?.number ?: TwinCardNumber.First
val cardNumber = when (state.createTwinWalletState?.step) {
val cardNumber = when (state.createWalletState?.step) {
CreateTwinWalletStep.FirstStep -> {
val twinCardNumberString = twinCardNumber.number.toString()
tv_step_number.text =
@ -96,7 +96,7 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
v_step_2.setBackgroundColor(defaultColor)
v_step_3.setBackgroundColor(defaultColor)
btn_tap.setOnClickListener {
store.dispatch(CreateTwinWalletAction.LaunchFirstStep(
store.dispatch(TwinCardsAction.CreateWallet.LaunchFirstStep(
Message(getString(
R.string.details_twins_recreate_title_format,
twinCardNumberString
@ -117,7 +117,7 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
v_step_2.setBackgroundColor(selectedColor)
v_step_3.setBackgroundColor(defaultColor)
btn_tap.setOnClickListener {
store.dispatch(CreateTwinWalletAction.LaunchSecondStep(
store.dispatch(TwinCardsAction.CreateWallet.LaunchSecondStep(
Message(getString(R.string.details_twins_recreate_title_format, twinCardNumberString)),
Message(getString(R.string.details_twins_recreate_title_preparing)),
Message(getString(R.string.details_twins_recreate_title_creating_wallet)),
@ -135,7 +135,7 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
v_step_2.setBackgroundColor(selectedColor)
v_step_3.setBackgroundColor(selectedColor)
btn_tap.setOnClickListener {
store.dispatch(CreateTwinWalletAction.LaunchThirdStep(
store.dispatch(TwinCardsAction.CreateWallet.LaunchThirdStep(
Message(getString(
R.string.details_twins_recreate_title_format, twinCardNumberString)
)
@ -149,18 +149,18 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_twin_cards_create),
btn_tap.text = getString(R.string.details_twins_recreate_button_format, cardNumber)
tv_twin_title.text = getString(R.string.details_twins_recreate_title_format, cardNumber)
if (state.createTwinWalletState?.showAlert == true) {
if (state.createWalletState?.showAlert == true) {
if (dialog == null) {
dialog = MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.details_twins_recreate_alert)
.setPositiveButton(R.string.common_ok) { _, _ ->
store.dispatch(CreateTwinWalletAction.Cancel.Confirm)
store.dispatch(TwinCardsAction.CreateWallet.Cancel.Confirm)
}
.setNegativeButton(R.string.common_cancel) { _, _ ->
dialog?.cancel()
}
.setOnCancelListener {
store.dispatch(CreateTwinWalletAction.HideAlert)
store.dispatch(TwinCardsAction.CreateWallet.HideAlert)
}
.create()
dialog?.show()

View file

@ -7,8 +7,8 @@ import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.squareup.picasso.Picasso
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.twins.redux.CreateTwinWallet
import com.tangem.tap.features.twins.redux.CreateTwinWalletAction
import com.tangem.tap.features.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.store
import com.tangem.wallet.R
@ -31,15 +31,15 @@ class TwinWalletWarningFragment : Fragment(R.layout.fragment_twin_cards_warning)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val createTwinWallet = store.state.detailsState.createTwinWalletState?.createTwinWallet
if (createTwinWallet == CreateTwinWallet.CreateWallet) {
val createTwinWallet = store.state.twinCardsState.createWalletState?.mode
if (createTwinWallet == CreateTwinWalletMode.CreateWallet) {
tv_twin_cards_description.text = getText(R.string.details_twins_recreate_subtitle)
} else if (createTwinWallet == CreateTwinWallet.RecreateWallet) {
} else if (createTwinWallet == CreateTwinWalletMode.RecreateWallet) {
tv_twin_cards_description.text = getText(R.string.details_twins_recreate_warning)
}
btn_cancel.setOnClickListener { store.dispatch(NavigationAction.PopBackTo()) }
btn_start.setOnClickListener { store.dispatch(CreateTwinWalletAction.Proceed) }
btn_start.setOnClickListener { store.dispatch(TwinCardsAction.CreateWallet.Proceed) }
Picasso.get()
.load(Artwork.TWIN_CARD_1)
.placeholder(R.drawable.card_placeholder)

View file

@ -9,7 +9,6 @@ import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
@ -147,12 +146,4 @@ sealed class WalletAction : Action {
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
sealed class TwinsAction : WalletAction() {
object ShowOnboarding : TwinsAction()
object SetOnboardingShown : TwinsAction()
data class SetTwinCard(
val secondCardId: String?, val number: TwinCardNumber,
val isCreatingTwinCardsAllowed: Boolean,
) : TwinsAction()
}
}

View file

@ -12,39 +12,49 @@ import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.topup.TradeCryptoHelper
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
import kotlin.properties.ReadOnlyProperty
data class WalletState(
val state: ProgressState = ProgressState.Done,
val error: ErrorType? = null,
val cardImage: Artwork? = null,
val hashesCountVerified: Boolean? = null,
val walletDialog: StateDialog? = null,
val twinCardsState: TwinCardsState? = null,
val mainWarningsList: List<WarningMessage> = mutableListOf(),
val wallets: List<WalletData> = emptyList(),
val walletManagers: List<WalletManager> = emptyList(),
val isMultiwalletAllowed: Boolean = false,
val cardCurrency: CryptoCurrencyName? = null,
val selectedWallet: Currency? = null,
val primaryBlockchain: Blockchain? = null,
val primaryToken: Token? = null,
val isTestnet: Boolean = false,
val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability()
val state: ProgressState = ProgressState.Done,
val error: ErrorType? = null,
val cardImage: Artwork? = null,
val hashesCountVerified: Boolean? = null,
val walletDialog: StateDialog? = null,
val mainWarningsList: List<WarningMessage> = mutableListOf(),
val wallets: List<WalletData> = emptyList(),
val walletManagers: List<WalletManager> = emptyList(),
val isMultiwalletAllowed: Boolean = false,
val cardCurrency: CryptoCurrencyName? = null,
val selectedWallet: Currency? = null,
val primaryBlockchain: Blockchain? = null,
val primaryToken: Token? = null,
val isTestnet: Boolean = false,
val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability()
) : StateType {
// if you do not delegate - the application crashes on startup,
// because twinCardsState has not been created yet
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { thisRef, property ->
store.state.twinCardsState
}
val isTangemTwins: Boolean
get() = store.state.globalState.scanResponse?.isTangemTwins() == true
val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null
val shouldShowDetails: Boolean =
primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
val blockchains: List<Blockchain>
get() = walletManagers.map { it.wallet.blockchain }
@ -57,16 +67,16 @@ data class WalletState(
return walletManagers.find { it.wallet.blockchain == token.blockchain }
}
fun getWalletManager(currency: Currency?) : WalletManager? {
fun getWalletManager(currency: Currency?): WalletManager? {
if (currency?.blockchain == null) return null
return walletManagers.find { it.wallet.blockchain == currency.blockchain }
}
fun getWalletManager(blockchain: Blockchain) : WalletManager? {
fun getWalletManager(blockchain: Blockchain): WalletManager? {
return walletManagers.find { it.wallet.blockchain == blockchain }
}
fun getWalletData(currency: Currency?) : WalletData? {
fun getWalletData(currency: Currency?): WalletData? {
if (currency == null) return null
return wallets.find { it.currency == currency }
}
@ -93,7 +103,7 @@ data class WalletState(
?: return true
if (walletData.currency is Currency.Blockchain &&
walletManager.cardTokens.isNotEmpty()
walletManager.cardTokens.isNotEmpty()
) {
return false
}
@ -105,9 +115,9 @@ data class WalletState(
wallet.amounts.toSendableAmounts().isEmpty()
} else if (walletData.currency is Currency.Token) (
return wallet.recentTransactions.toPendingTransactionsForToken(
walletData.currency.token, wallet.address).isEmpty()
walletData.currency.token, wallet.address).isEmpty()
&& wallet.amounts[AmountType.Token(token = walletData.currency.token)]
?.isAboveZero() != true
?.isAboveZero() != true
)
}
return false
@ -137,9 +147,9 @@ data class WalletState(
}
}
sealed class WalletDialog: StateDialog {
sealed class WalletDialog : StateDialog {
data class QrDialog(
val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName?
val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName?
) : WalletDialog()
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
@ -147,7 +157,7 @@ sealed class WalletDialog: StateDialog {
object ChooseTradeActionDialog : WalletDialog()
}
enum class ProgressState: WidgetState { Loading, Done, Error }
enum class ProgressState : WidgetState { Loading, Done, Error }
enum class ErrorType { NoInternetConnection }
@ -157,22 +167,22 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
}
data class WalletAddresses(
val selectedAddress: AddressData,
val list: List<AddressData>
val selectedAddress: AddressData,
val list: List<AddressData>
)
data class AddressData(
val address: String,
val type: AddressType,
val shareUrl: String,
val exploreUrl: String,
val address: String,
val type: AddressType,
val shareUrl: String,
val exploreUrl: String,
) {
val qrCode: Bitmap by lazy { shareUrl.toQrCode() }
}
data class Artwork(
val artworkId: String,
val artwork: Bitmap? = null
val artworkId: String,
val artwork: Bitmap? = null
) {
companion object {
const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png"
@ -198,13 +208,6 @@ data class TradeCryptoAvailability(
val availableToSell: Set<String> = TradeCryptoHelper.AVAILABLE_TO_SELL,
)
data class TwinCardsState(
val secondCardId: String?,
val cardNumber: TwinCardNumber?,
val showTwinOnboarding: Boolean,
val isCreatingTwinCardsAllowed: Boolean
)
data class WalletData(
val pendingTransactions: List<PendingTransaction> = emptyList(),
val hashesCountVerified: Boolean? = null,

View file

@ -1,19 +0,0 @@
package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
class TwinsMiddleware {
fun handle(action: WalletAction.TwinsAction) {
when (action) {
is WalletAction.TwinsAction.SetTwinCard -> {
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
}
WalletAction.TwinsAction.SetOnboardingShown -> {
preferencesStorage.saveTwinsOnboardingShown()
}
}
}
}

View file

@ -17,11 +17,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.toSendableAmounts
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.twins.redux.CreateTwinWallet
import com.tangem.tap.features.twins.redux.CreateTwinWalletAction
import com.tangem.tap.features.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.network.NetworkStateChanged
import kotlinx.coroutines.launch
@ -31,7 +30,6 @@ import org.rekotlin.Middleware
class WalletMiddleware {
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
private val twinsMiddleware = TwinsMiddleware()
private val warningsMiddleware = WarningsMiddleware()
private val multiWalletMiddleware = MultiWalletMiddleware()
@ -50,7 +48,6 @@ class WalletMiddleware {
when (action) {
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action)
is WalletAction.TwinsAction -> twinsMiddleware.handle(action)
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState)
is WalletAction.LoadWallet -> {
@ -99,12 +96,11 @@ class WalletMiddleware {
}
}
is WalletAction.CreateWallet -> {
if (walletState.twinCardsState != null) {
val cardId = globalState.scanResponse?.card?.cardId
val twinCardNumber = cardId?.let { TwinsHelper.getTwinCardNumber(it) }
store.dispatch(
CreateTwinWalletAction.ShowWarning(twinCardNumber, CreateTwinWallet.CreateWallet)
)
if (walletState.isTangemTwins) {
store.dispatch(TwinCardsAction.CreateWallet.Create(
walletState.twinCardsState.cardNumber!!,
CreateTwinWalletMode.CreateWallet
))
} else {
scope.launch {
val result = tangemSdkManager.createWallet(

View file

@ -1,36 +0,0 @@
package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.tap.features.wallet.redux.TwinCardsState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
class TwinsReducer {
fun reduce(action: WalletAction.TwinsAction, state: WalletState): WalletState {
return when (action) {
is WalletAction.TwinsAction.SetTwinCard -> {
state.copy(
twinCardsState = TwinCardsState(
secondCardId = action.secondCardId,
cardNumber = action.number,
showTwinOnboarding = state.twinCardsState?.showTwinOnboarding
?: false,
isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
)
)
}
is WalletAction.TwinsAction.ShowOnboarding -> {
state.copy(
twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = true)
?: TwinCardsState(null, null,
showTwinOnboarding = true,
isCreatingTwinCardsAllowed = false)
)
}
is WalletAction.TwinsAction.SetOnboardingShown -> {
state.copy(
twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = false)
)
}
}
}
}

View file

@ -25,7 +25,6 @@ class WalletReducer {
private fun internalReduce(action: Action, state: AppState): WalletState {
val multiWalletReducer = MultiWalletReducer()
val twinsReducer = TwinsReducer()
val onWalletLoadedReducer = OnWalletLoadedReducer()
if (action !is WalletAction) return state.walletState
@ -34,15 +33,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
when (action) {
is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
is WalletAction.TwinsAction -> newState = twinsReducer.reduce(action, newState)
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
is WalletAction.ResetState -> newState = WalletState()
is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet)
is WalletAction.EmptyWallet -> {
val creatingWalletAllowed = !(newState.twinCardsState != null &&
newState.twinCardsState?.isCreatingTwinCardsAllowed != true)
val creatingWalletAllowed = state.twinCardsState.isCreatingTwinCardsAllowed
newState = newState.copy(
state = ProgressState.Done,
wallets = listOf(
@ -215,7 +211,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
is WalletAction.LoadArtwork -> {
val artworkUrl = action.card.getArtworkUrl(action.artworkId)
?: when (newState.twinCardsState?.cardNumber) {
?: when (state.twinCardsState.cardNumber) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
else -> Artwork.DEFAULT_IMG_URL

View file

@ -135,8 +135,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
if (state.error == ErrorType.NoInternetConnection) {
srl_wallet_details?.isRefreshing = false
(activity as? MainActivity)?.showSnackbar(
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
) { store.dispatch(WalletAction.LoadData) }
}
} else {
@ -158,11 +158,10 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers))
store.state.globalState.scanResponse?.let { scanNoteResponse ->
store.dispatch(DetailsAction.PrepareScreen(
scanNoteResponse.card, scanNoteResponse,
store.state.walletState.walletManagers.map { it.wallet },
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
CardTou(),
store.state.globalState.appCurrency
scanNoteResponse,
store.state.walletState.walletManagers.map { it.wallet },
CardTou(),
store.state.globalState.appCurrency
))
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
true

View file

@ -10,6 +10,7 @@ import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.ui.BalanceWidget
@ -71,7 +72,7 @@ class SingleWalletView : WalletView {
state.primaryWallet ?: return
setupTwinCards(state.twinCardsState, fragment)
setupButtons(state.primaryWallet, state.twinCardsState != null, fragment)
setupButtons(state.primaryWallet, state.isTangemTwins, fragment)
setupAddressCard(state.primaryWallet, fragment)
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
setupBalance(state, state.primaryWallet)
@ -87,7 +88,7 @@ class SingleWalletView : WalletView {
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
fragment?.apply {
this.l_balance.show()
BalanceWidget(this, primaryWallet.currencyData, state.twinCardsState != null).setup()
BalanceWidget(this, primaryWallet.currencyData, state.isTangemTwins).setup()
}
}