Updated on 2026-08-14

This commit is contained in:
Tangem 2022-10-23 00:51:16 +05:00
parent b8e40c6e73
commit 342f3e4152
25 changed files with 333 additions and 147 deletions

View file

@ -54,7 +54,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
store.unsubscribe(this)
}
override fun newState(state: GlobalState) {
if (state.dialog == null) {
dialog?.dismiss()
@ -67,6 +66,8 @@ class DialogManager : StoreSubscriber<GlobalState> {
dialog = when (state.dialog) {
is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.SimpleOkErrorDialog -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.SimpleOkWarningDialog -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
@ -76,16 +77,16 @@ class DialogManager : StoreSubscriber<GlobalState> {
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet,
context = context
context = context,
)
is WalletConnectDialog.AddNetwork ->
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
message = context.getString(
R.string.wallet_connect_network_not_found_format,
state.dialog.network
state.dialog.network,
),
context = context
context = context,
)
is WalletConnectDialog.OpeningSessionRejected -> {
SimpleAlertDialog.create(
@ -123,7 +124,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
context = context
context = context,
)
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
@ -142,7 +143,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
message = context.getString(
state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle
state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle,
),
context = context,
)
@ -151,7 +152,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
messageRes = state.dialog.messageRes,
context = context,
primaryButtonRes = state.dialog.primaryButtonRes,
primaryButtonAction = state.dialog.onOk
primaryButtonAction = state.dialog.onOk,
)
is WalletDialog.RussianCardholdersWarningDialog ->
RussianCardholdersWarningBottomSheetDialog(context)

View file

@ -0,0 +1,11 @@
package com.tangem.tap.common.extensions
import android.widget.ImageView
import androidx.annotation.DrawableRes
/**
[REDACTED_AUTHOR]
*/
fun ImageView.setDrawable(@DrawableRes resId: Int) {
setImageDrawable(context.getDrawableCompat(resId))
}

View file

@ -12,10 +12,12 @@ interface StateDialog
sealed class AppDialog : StateDialog {
data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog()
data class SimpleOkErrorDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog()
data class SimpleOkWarningDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog()
data class SimpleOkDialogRes(
val headerId: Int,
val messageId: Int,
val onOk: VoidCallback? = null
val onOk: VoidCallback? = null,
) : AppDialog()
object ScanFailsDialog : AppDialog()
@ -25,6 +27,6 @@ sealed class AppDialog : StateDialog {
) : AppDialog()
data class TestActionsDialog(
val actionsList: List<TestAction>
val actionsList: List<TestAction>,
) : AppDialog()
}

View file

@ -10,7 +10,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
@ -28,8 +27,7 @@ import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
import com.tangem.tap.features.send.redux.states.ButtonState
@ -130,20 +128,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) {
}
is Result.Failure -> {
changeButtonState(ButtonState.ENABLED)
when (val error = result.error) {
is SaltPayRegistrationError -> {
val dialog = when (error) {
is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation
else -> SaltPayDialog.RegistrationError(error)
}
store.dispatchDialogShow(dialog)
}
else -> {
store.dispatchDebugErrorNotification(
error.localizedMessage ?: "SaltPay: Unknown error",
)
}
}
SaltPayExceptionHandler.handle(result.error)
}
}
}

View file

@ -16,7 +16,7 @@ class OnboardingSaltPayHelper {
suspend fun isOnboardingCase(
scanResponse: ScanResponse,
manager: SaltPayRegistrationManager
manager: SaltPayRegistrationManager,
): Result<Boolean> {
return try {
val status = manager.updateSaltPayStatus(
@ -24,7 +24,7 @@ class OnboardingSaltPayHelper {
step = SaltPayRegistrationStep.None,
).successOr { return it }
val isRegistrationCase = status.step != SaltPayRegistrationStep.ClaimSuccess
val isRegistrationCase = status.step != SaltPayRegistrationStep.Finished
val isBackupCase = scanResponse.card.backupStatus?.isActive == false
Result.Success(isRegistrationCase || isBackupCase)
} catch (ex: Exception) {

View file

@ -20,7 +20,6 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.WalletBalanceFetcher
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.scope
@ -130,7 +129,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading))
scope.launch {
val loadedBalance = WalletBalanceFetcher().updateBalance(walletManager)
val loadedBalance = onboardingManager.updateBalance(walletManager)
delay(if (isLoadedBefore) 0 else 300)
loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) }
withMainContext {

View file

@ -30,7 +30,7 @@ data class OnboardingWalletState(
}
fun getMaxProgress(): Int = when {
isSaltPay -> 14
isSaltPay -> 12
else -> 6
}
@ -54,12 +54,12 @@ data class OnboardingWalletState(
SaltPayRegistrationStep.None, SaltPayRegistrationStep.NoGas -> 0
SaltPayRegistrationStep.NeedPin -> 7
SaltPayRegistrationStep.CardRegistration -> 8
SaltPayRegistrationStep.KycIntro -> 9
SaltPayRegistrationStep.KycStart -> 10
SaltPayRegistrationStep.KycWaiting -> 11
SaltPayRegistrationStep.Claim -> 12
SaltPayRegistrationStep.ClaimInProgress -> 13
SaltPayRegistrationStep.ClaimSuccess -> getMaxProgress()
SaltPayRegistrationStep.KycIntro, SaltPayRegistrationStep.KycStart,
SaltPayRegistrationStep.KycWaiting, SaltPayRegistrationStep.KycReject,
-> 9
SaltPayRegistrationStep.Claim -> 10
SaltPayRegistrationStep.ClaimInProgress -> 11
SaltPayRegistrationStep.Finished -> getMaxProgress()
}
}
step == OnboardingWalletStep.Done -> getMaxProgress()

View file

@ -0,0 +1,43 @@
package com.tangem.tap.features.onboarding.products.wallet.saltPay
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
import com.tangem.tap.store
/**
[REDACTED_AUTHOR]
*/
class SaltPayExceptionHandler {
companion object {
fun handle(throwable: Throwable) {
when (throwable) {
is SaltPayRegistrationError -> {
val dialog = when (throwable) {
is SaltPayRegistrationError.NoGas -> SaltPayDialog.NoFundsForActivation
else -> SaltPayDialog.RegistrationError(throwable)
}
store.dispatchDialogShow(dialog)
}
is TangemSdkError -> {
when (throwable) {
is TangemSdkError.NetworkError -> {
val message = (throwable).customMessage
store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message))
}
else -> {
// do nothing
}
}
}
else -> {
val message = throwable.message ?: "SaltPay unknown error"
store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message))
}
}
}
}
}

View file

@ -4,24 +4,21 @@ import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.extensions.successOr
import com.tangem.common.Filter
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.successOr
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.network.api.paymentology.KYCStatus
import com.tangem.network.api.paymentology.RegistrationResponse
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.domain.TapError
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
import com.tangem.tap.scope
import com.tangem.tap.store
@ -76,37 +73,9 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
handleInProgress = false
withMainContext {
// store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step))
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycWaiting))
store.dispatch(OnboardingSaltPayAction.SetStep(updateResult.step))
}
}
// handleIsBusy = true
// scope.launch {
// checkGasIfNeeded(state.saltPayManager, state.step).successOr {
// onException(it.error)
// return@launch
// }
//
// registerKYCIfNeeded(state).successOr {
// onException(it.error)
// return@launch
// }
//
// val amount = getAmountToClaimIfNeeded(state)?.let {
// dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(it))
// }
//
// val newStep = checkRegistration(state, amount != null).successOr {
// onException(it.error)
// return@launch
// }
//
// withMainContext {
// handleIsBusy = false
// store.dispatch(OnboardingSaltPayAction.SetStep(newStep))
// }
// }
}
is OnboardingSaltPayAction.RegisterCard -> {
handleInProgress = true
@ -191,7 +160,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
store.dispatch(OnboardingSaltPayAction.SetPin(action.pin))
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.CardRegistration))
} catch (error: SaltPayRegistrationError) {
handleError = error
SaltPayExceptionHandler.handle(error)
}
}
is OnboardingSaltPayAction.Claim -> {
@ -237,7 +206,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
val state = getState()
scope.launch {
val balance = state.saltPayManager.getTokenAmount().successOr {
handleException = it.error
SaltPayExceptionHandler.handle(it.error)
handleClaimRefreshInProgress = false
return@launch
}
@ -245,7 +214,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
handleInProgress = false
handleClaimRefreshInProgress = false
dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(balance))
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.ClaimSuccess))
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.Finished))
}
}
else -> {
@ -283,7 +252,7 @@ suspend fun SaltPayRegistrationManager.updateSaltPayStatus(
updateResult = when {
saltPayStep == SaltPayRegistrationStep.Claim && fetchedAmountToClaim == null -> {
updateResult.copy(
step = SaltPayRegistrationStep.ClaimSuccess,
step = SaltPayRegistrationStep.KycReject,
amountToClaim = fetchedAmountToClaim,
)
}
@ -334,51 +303,17 @@ private var handleInProgress: Boolean = false
private suspend fun onError(error: SaltPayRegistrationError) {
withMainContext {
handleInProgress = false
handleError = error
SaltPayExceptionHandler.handle(error)
}
}
private suspend fun onException(error: Throwable) {
withMainContext {
handleInProgress = false
handleException = error
SaltPayExceptionHandler.handle(error)
}
}
private var handleException: Throwable = Throwable("Init")
set(value) {
field = value
when (value) {
is SaltPayRegistrationError -> handleError = value
else -> {
Timber.e(value)
when (value) {
is TangemSdkError -> {
// store.dispatchErrorNotification(TapError.CustomError(value.customMessage))
}
else -> {
store.dispatchErrorNotification(
TapError.CustomError(value.message ?: "Unknown SaltPay exception"),
)
}
}
}
}
}
private var handleError: SaltPayRegistrationError = SaltPayRegistrationError.Unknown
set(value) {
field = value
when (value) {
SaltPayRegistrationError.NoGas -> {
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.NoGas))
}
else -> {
store.dispatchDialogShow(SaltPayDialog.RegistrationError(value))
}
}
}
private suspend fun checkGasIfNeeded(
saltPayManager: SaltPayRegistrationManager,
step: SaltPayRegistrationStep,
@ -386,7 +321,7 @@ private suspend fun checkGasIfNeeded(
Timber.d("checkGasIfNeeded: for step: %s", step)
if (step == SaltPayRegistrationStep.KycStart ||
step == SaltPayRegistrationStep.KycWaiting ||
step == SaltPayRegistrationStep.ClaimSuccess
step == SaltPayRegistrationStep.Finished
) {
Timber.d("checkGasIfNeeded: no need to check for step: %s", step)
return Result.Success(Unit)
@ -451,6 +386,15 @@ fun RegistrationResponse.Item.toSaltPayStep(): SaltPayRegistrationStep {
// pinSet is false, go toPin screen
pinSet == false -> SaltPayRegistrationStep.NeedPin
kycStatus != null -> {
when (kycStatus) {
KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayRegistrationStep.KycStart
KYCStatus.WAITING_FOR_APPROVAL -> SaltPayRegistrationStep.KycWaiting
KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayRegistrationStep.KycReject
KYCStatus.APPROVED -> SaltPayRegistrationStep.Claim
null -> throw UnsupportedOperationException()
}
}
// kycDate is set, go to kyc waiting screen
kycDate != null -> SaltPayRegistrationStep.KycWaiting

View file

@ -123,7 +123,8 @@ enum class SaltPayRegistrationStep {
KycIntro,
KycStart,
KycWaiting,
KycReject,
Claim,
ClaimInProgress,
ClaimSuccess;
Finished;
}

View file

@ -20,6 +20,7 @@ import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.setDrawable
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.stop
import com.tangem.tap.common.extensions.toFormattedCurrencyString
@ -58,12 +59,15 @@ internal class OnboardingSaltPayView(
private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar }
private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim }
private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer }
private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer }
private val btnRefreshBalanceWidget by lazy {
RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer)
}
private var progressButton: SaltPayProgressButton? = null
private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None
fun newState(state: OnboardingWalletState) {
handleCardArtworks(state)
@ -135,9 +139,10 @@ internal class OnboardingSaltPayView(
SaltPayRegistrationStep.KycIntro -> handleKycIntro()
SaltPayRegistrationStep.KycStart -> handleKycStart(state)
SaltPayRegistrationStep.KycWaiting -> handleKycWaiting()
SaltPayRegistrationStep.KycReject -> handleKycReject()
SaltPayRegistrationStep.Claim -> handleClaim(state)
SaltPayRegistrationStep.ClaimInProgress -> handleClaim(state)
SaltPayRegistrationStep.ClaimSuccess -> handleClaim(state)
SaltPayRegistrationStep.Finished -> handleClaim(state)
}
progressButton?.changeState(state.mainButtonState)
}
@ -204,22 +209,41 @@ internal class OnboardingSaltPayView(
}
}
private fun handleKycWaiting() = with(walletFragment.bindingSaltPay) {
toolbar.title = getString(R.string.onboarding_navbar_kyc_waiting)
showOnlyView(verifyIdentityInProgress.root) {
progressButton = SaltPayProgressButton(verifyIdentityInProgress.root)
verifyIdentityInProgress.btnOpenSupportChat.setOnClickListener {
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
}
verifyIdentityInProgress.btnRefresh.setOnClickListener {
store.dispatch(OnboardingSaltPayAction.Update)
}
private fun handleKycWaiting() = with(walletFragment.bindingSaltPay.kycInProgress) {
toolbar.title = getString(R.string.onboarding_navbar_kyc_progress)
showOnlyView(root)
imvInProgress.setDrawable(R.drawable.ic_in_progress)
tvHeader.text = getString(R.string.onboarding_title_kyc_waiting)
tvBody.text = getString(R.string.onboarding_subtitle_kyc_waiting)
btnOpenSupportChat.hide()
btnOpenSupportChat.setOnClickListener {
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
}
btnKycAction.text = getString(R.string.onboarding_button_kyc_waiting)
btnKycAction.setOnClickListener {
store.dispatch(OnboardingSaltPayAction.Update)
}
progressButton = SaltPayProgressButton(root)
}
private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer }
private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { claimBinding.onboardingActionContainer }
private var previousStep: SaltPayRegistrationStep = SaltPayRegistrationStep.None
private fun handleKycReject() = with(walletFragment.bindingSaltPay.kycInProgress) {
toolbar.title = getString(R.string.onboarding_navbar_kyc_progress)
showOnlyView(root)
imvInProgress.setDrawable(R.drawable.ic_reject)
tvHeader.text = getString(R.string.onboarding_title_kyc_retry)
tvBody.text = getString(R.string.onboarding_subtitle_kyc_retry)
btnOpenSupportChat.hide()
btnKycAction.text = getString(R.string.onboarding_button_kyc_start)
btnKycAction.setOnClickListener {
store.dispatch(OnboardingSaltPayAction.Update)
}
progressButton = SaltPayProgressButton(root)
}
private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) {
toolbar.title = getString(R.string.onboarding_navbar_claim)
@ -270,7 +294,7 @@ internal class OnboardingSaltPayView(
tvBody.text = getString(R.string.onboarding_subtitle_claim_progress)
btnMain.text = getString(R.string.onboarding_button_claim)
}
SaltPayRegistrationStep.ClaimSuccess -> {
SaltPayRegistrationStep.Finished -> {
tvHeader.setText(R.string.common_success)
tvBody.setText(R.string.onboarding_subtitle_success_claim)
@ -350,4 +374,4 @@ internal class OnboardingSaltPayView(
}
}
}
}
}

View file

@ -36,6 +36,23 @@ class SimpleOkDialog {
}
}.create()
}
}
fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create(
dialog = AppDialog.SimpleOkDialog(
header = context.getString(R.string.common_error),
message = dialog.message,
onOk = dialog.onOk,
),
context = context,
)
fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create(
dialog = AppDialog.SimpleOkDialog(
header = context.getString(R.string.common_warning),
message = dialog.message,
onOk = dialog.onOk,
),
context = context,
)
}
}