Updated on 2026-08-14
This commit is contained in:
parent
261a16113f
commit
5036f8a383
39 changed files with 1168 additions and 503 deletions
|
|
@ -75,8 +75,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isScannedBefore = store.state.globalState.scanNoteResponse != null
|
||||
val isOnboardingStarted = store.state.onboardingState.onboardingData?.scanNoteResponse != null
|
||||
if ((backStackIsEmpty || isScannedBefore) && !isOnboardingStarted) {
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingService != null
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
}
|
||||
intentHandler.handleIntent(intent)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import com.tangem.tap.common.redux.AppDialog
|
|||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.products.note.OnboardingAddressInfoDialog
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -43,6 +42,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
|
||||
dialog = when (state.dialog) {
|
||||
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect,
|
||||
|
|
@ -71,9 +71,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
TransactionDialog.create(state.dialog.dialogData, context)
|
||||
is WalletConnectDialog.PersonalSign ->
|
||||
PersonalSignDialog.create(state.dialog.data, context)
|
||||
is OnboardingAction.Dialog.AddressInfo -> {
|
||||
OnboardingAddressInfoDialog(state.dialog, context)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
dialog?.show()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.tap.network.NetworkConnectivity
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
suspend fun WalletManager.loadWalletData(): Result<Wallet> = try {
|
||||
suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
||||
update()
|
||||
Result.Success(wallet)
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -23,10 +23,10 @@ suspend fun WalletManager.loadWalletData(): Result<Wallet> = try {
|
|||
val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken())
|
||||
|
||||
if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) {
|
||||
Result.Failure(TapError.NoAccount(amountToCreateAccount.toString()))
|
||||
Result.Failure(TapError.WalletManagerUpdate.NoAccountError(amountToCreateAccount.toString()))
|
||||
} else {
|
||||
val message = exception.localizedMessage ?: "Unknown exception during WalletManager update"
|
||||
Result.Failure(TapError.CustomError(message))
|
||||
Result.Failure(TapError.WalletManagerUpdate.InternalError(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
|
|||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingReducer
|
||||
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
|
||||
import com.tangem.tap.features.tokens.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
|
|
@ -22,7 +21,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
navigationState = NavigationReducer.reduce(action, state),
|
||||
globalState = globalReducer(action, state),
|
||||
homeState = HomeReducer.reduce(action, state),
|
||||
onboardingState = OnboardingReducer.reduce(action, state),
|
||||
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
|
||||
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
|
||||
walletState = WalletReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMidd
|
|||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingMiddleware
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingState
|
||||
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
|
||||
|
|
@ -31,7 +29,6 @@ data class AppState(
|
|||
val navigationState: NavigationState = NavigationState(),
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val homeState: HomeState = HomeState(),
|
||||
val onboardingState: OnboardingState = OnboardingState(),
|
||||
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
|
||||
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
|
||||
val walletState: WalletState = WalletState(),
|
||||
|
|
@ -48,7 +45,6 @@ data class AppState(
|
|||
logMiddleware, navigationMiddleware, notificationsMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
HomeMiddleware.handler,
|
||||
OnboardingMiddleware.handler,
|
||||
OnboardingNoteMiddleware.handler,
|
||||
OnboardingWalletMiddleware.handler,
|
||||
WalletMiddleware().walletMiddleware,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -7,4 +9,9 @@ interface StateDialog
|
|||
|
||||
sealed class AppDialog : StateDialog {
|
||||
object ScanFailsDialog : AppDialog()
|
||||
data class AddressInfoDialog(
|
||||
val addressData: AddressData,
|
||||
val onCopyAddress: () -> Unit,
|
||||
val onExploreAddress: () -> Unit
|
||||
) : AppDialog()
|
||||
}
|
||||
|
|
@ -3,10 +3,7 @@ package com.tangem.tap.common.redux.global
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.tap.common.redux.DebugErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
import com.tangem.tap.common.redux.*
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
|
|
@ -15,6 +12,7 @@ import com.tangem.tap.domain.tasks.ScanNoteResponse
|
|||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.feedback.EmailData
|
||||
import com.tangem.tap.features.feedback.FeedbackManager
|
||||
import com.tangem.tap.features.onboarding.service.ProductOnboardingService
|
||||
import com.tangem.tap.network.moonpay.MoonPayUserStatus
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -23,16 +21,21 @@ sealed class GlobalAction : Action {
|
|||
// notifications
|
||||
data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction
|
||||
data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction
|
||||
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
|
||||
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
|
||||
|
||||
// dialogs
|
||||
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
|
||||
object HideDialog : GlobalAction()
|
||||
|
||||
data class DebugShowError(override val error: TapError) : GlobalAction(), DebugErrorAction
|
||||
sealed class Onboarding {
|
||||
data class Activate(val onboardingService: ProductOnboardingService) : GlobalAction()
|
||||
object Deactivate : GlobalAction()
|
||||
}
|
||||
|
||||
data class ReadCard(
|
||||
val onSuccess: (suspend (ScanNoteResponse) -> Unit)? = null,
|
||||
val onFailure: (suspend (TangemError) -> Unit)? = null,
|
||||
val onSuccess: ((ScanNoteResponse) -> Unit)? = null,
|
||||
val onFailure: ((TangemError) -> Unit)? = null,
|
||||
val messageResId: Int? = null,
|
||||
) : GlobalAction()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.services.Result
|
|||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
|
|
@ -16,9 +17,7 @@ import com.tangem.tap.preferencesStorage
|
|||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class GlobalMiddleware {
|
||||
|
|
@ -73,7 +72,7 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
}
|
||||
is GlobalAction.UpdateFeedbackInfo -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
}
|
||||
is GlobalAction.GetMoonPayUserStatus -> {
|
||||
val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey
|
||||
|
|
@ -82,7 +81,7 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
val userStatusResponse = MoonpayService().getUserStatus(apiKey)
|
||||
if (userStatusResponse is Result.Success) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data)
|
||||
GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -90,9 +89,9 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
}
|
||||
is GlobalAction.ReadCard -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler, action.messageResId)
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId)
|
||||
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
val globalState = state.globalState
|
||||
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Activate -> {
|
||||
globalState.copy(onboardingService = action.onboardingService)
|
||||
}
|
||||
is GlobalAction.Onboarding.Deactivate -> {
|
||||
globalState.copy(onboardingService = null)
|
||||
}
|
||||
is GlobalAction.ScanFailsCounter.Increment -> {
|
||||
globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.tap.domain.configurable.config.ConfigManager
|
|||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.features.feedback.FeedbackManager
|
||||
import com.tangem.tap.features.onboarding.service.ProductOnboardingService
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import com.tangem.tap.network.moonpay.MoonPayUserStatus
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -24,7 +25,8 @@ data class GlobalState(
|
|||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val moonPayUserStatus: MoonPayUserStatus? = null
|
||||
val moonPayUserStatus: MoonPayUserStatus? = null,
|
||||
val onboardingService: ProductOnboardingService? = null,
|
||||
) : StateType
|
||||
|
||||
typealias CryptoCurrencyName = String
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import CreateProductWalletAndRescanTask
|
||||
import android.content.Context
|
||||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
|||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.tasks.ScanNoteTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -31,8 +33,10 @@ import kotlin.coroutines.suspendCoroutine
|
|||
|
||||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
@Deprecated("Use can product")
|
||||
suspend fun scanNote(
|
||||
analyticsHandler: AnalyticsHandler, messageRes: Int? = null,
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanNoteResponse> {
|
||||
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
|
||||
|
|
@ -42,6 +46,21 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
).also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
suspend fun scanProduct(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanNoteResponse> {
|
||||
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(ScanProductTask(), null, message)
|
||||
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
suspend fun createProductWallet(cardId: String?): CompletionResult<Card> {
|
||||
return runTaskAsync(CreateProductWalletAndRescanTask(), cardId, Message(context.getString(R.string.initial_message_create_wallet_body)))
|
||||
}
|
||||
|
||||
private fun sendScanFailuresToAnalytics(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
result: CompletionResult<ScanNoteResponse>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.common.services.Result
|
|||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.loadWalletData
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
|
|
@ -40,22 +40,17 @@ class TapWalletManager {
|
|||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
suspend fun loadWalletData(walletManager: WalletManager) {
|
||||
handleUpdateWalletResult(walletManager.loadWalletData(), walletManager)
|
||||
handleUpdateWalletResult(walletManager.safeUpdate(), walletManager)
|
||||
}
|
||||
|
||||
suspend fun updateWallet(walletManager: WalletManager) {
|
||||
val result = try {
|
||||
walletManager.update()
|
||||
Result.Success(walletManager.wallet)
|
||||
} catch (exception: Exception) {
|
||||
Result.Failure(exception)
|
||||
}
|
||||
val result = walletManager.safeUpdate()
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success ->
|
||||
store.dispatch(WalletAction.UpdateWallet.Success(result.data))
|
||||
is Result.Failure ->
|
||||
store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage))
|
||||
store.dispatch(WalletAction.UpdateWallet.Failure(result.error.localizedMessage))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,11 +257,11 @@ class TapWalletManager {
|
|||
TapError.NoInternetConnection -> {
|
||||
store.dispatch(WalletAction.LoadData.Failure(error))
|
||||
}
|
||||
is TapError.NoAccount -> {
|
||||
is TapError.WalletManagerUpdate.NoAccountError -> {
|
||||
store.dispatch(WalletAction.LoadWallet
|
||||
.NoAccount(walletManager.wallet, error.amountToCreateAccount))
|
||||
.NoAccount(walletManager.wallet, error.customMessage))
|
||||
}
|
||||
is TapError.CustomError -> {
|
||||
is TapError.WalletManagerUpdate.InternalError -> {
|
||||
store.dispatch(WalletAction.LoadWallet
|
||||
.Failure(walletManager.wallet, error.customMessage))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ object TapWorkarounds {
|
|||
)
|
||||
}
|
||||
|
||||
val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
|
||||
val Card.isMultiwalletAllowed: Boolean
|
||||
get() {
|
||||
return !isTangemTwin() && !isStart2Coin && !isTangemNote()
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
|
||||
if (card.isTangemTwin()) {
|
||||
dealWithTwinCard(card, session, callback)
|
||||
} else if (card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) {
|
||||
} else if (!card.isTangemNote() && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) {
|
||||
createMissingWalletsIfNeeded(card, session, callback)
|
||||
} else {
|
||||
callback(CompletionResult.Success(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.wallet.CreateWalletCommand
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemWallet
|
||||
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CreateProductWalletAndRescanTask : CardSessionRunnable<Card> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
CreateProductWalletTask().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateProductWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
val card = session.environment.card.guard {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
|
||||
val commandProcessor = when {
|
||||
card.isTangemNote() -> CreateWalletTangemNote()
|
||||
card.isTangemTwin() -> CreateWalletTangemTwin()
|
||||
card.isTangemWallet() -> CreateWalletTangemWallet()
|
||||
else -> CreateWalletOtherCards()
|
||||
}
|
||||
commandProcessor.proceed(card, session, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
) {
|
||||
if (card.supportedCurves.isEmpty()) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
|
||||
val curvesSupportedByBlockchain = card.getTangemNoteBlockchain()?.getSupportedCurves()
|
||||
if (curvesSupportedByBlockchain == null || curvesSupportedByBlockchain.isEmpty()) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
|
||||
val intersectCurves = card.supportedCurves.intersect(curvesSupportedByBlockchain).toList()
|
||||
if (intersectCurves.isEmpty()) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
} else {
|
||||
val curve = if (intersectCurves.contains(EllipticCurve.Secp256k1)) {
|
||||
EllipticCurve.Secp256k1
|
||||
} else {
|
||||
intersectCurves[0]
|
||||
}
|
||||
CreateWalletCommand(curve).run(session, callback)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletTangemTwin : ProductCommandProcessor<CreateWalletResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletResponse> {
|
||||
|
||||
private var index = 0
|
||||
|
||||
private val curves = EllipticCurve.values().toList()
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
) {
|
||||
val curve = curves[index]
|
||||
createWallet(curve, session, callback)
|
||||
}
|
||||
|
||||
private fun createWallet(
|
||||
curve: EllipticCurve,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
) {
|
||||
CreateWalletCommand(curve).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (index == curves.lastIndex) {
|
||||
callback(CompletionResult.Success(result.data))
|
||||
return@run
|
||||
}
|
||||
index += 1
|
||||
createWallet(curves[index], session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.domain.tasks.product
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.CardSession
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ProductCommandProcessor<T> {
|
||||
fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<T>) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package com.tangem.tap.domain.tasks.product
|
||||
|
||||
import com.tangem.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
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.tasks.CreateWalletsTask
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
|
||||
class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse> {
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit,
|
||||
) {
|
||||
ScanTask().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val card = this.card ?: result.data
|
||||
|
||||
val error = getErrorIfExcludedCard(card)
|
||||
if (error != null) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
return@run
|
||||
}
|
||||
|
||||
val commandProcessor = when {
|
||||
card.isTangemNote() -> ScanNoteProcessor()
|
||||
card.isTangemTwin() -> ScanTwinProcessor()
|
||||
card.isTangemWallet() -> ScanWalletProcessor()
|
||||
else -> ScanOtherCardsProcessor()
|
||||
}
|
||||
commandProcessor.proceed(card, session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getErrorIfExcludedCard(card: Card): TangemError? {
|
||||
if (card.isExcluded()) return TapSdkError.CardForDifferentApp
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanNoteProcessor : ProductCommandProcessor<ScanNoteResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
|
||||
) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData)))
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanWalletProcessor : ProductCommandProcessor<ScanNoteResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
|
||||
) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData)))
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanTwinProcessor : ProductCommandProcessor<ScanNoteResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit,
|
||||
) {
|
||||
ReadIssuerDataCommand().run(session) { readDataResult ->
|
||||
when (readDataResult) {
|
||||
is CompletionResult.Success -> {
|
||||
val publicKey = card.getSingleWallet()?.publicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
|
||||
return@run
|
||||
}
|
||||
val verified = TwinCardsManager.verifyTwinPublicKey(
|
||||
readDataResult.data.issuerData, publicKey
|
||||
)
|
||||
if (verified) {
|
||||
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
|
||||
callback(CompletionResult.Success(
|
||||
ScanNoteResponse(
|
||||
card = card,
|
||||
walletData = session.environment.walletData,
|
||||
secondTwinPublicKey = twinPublicKey.toHexString())
|
||||
))
|
||||
return@run
|
||||
} else {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ScanOtherCardsProcessor : ProductCommandProcessor<ScanNoteResponse> {
|
||||
override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
|
||||
val walletData = session.environment.walletData
|
||||
if (card.wallets.isEmpty()) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, walletData)))
|
||||
return
|
||||
}
|
||||
|
||||
val curvesPresent = card.wallets.map { it.curve }
|
||||
val curvesToCreate = card.supportedCurves.subtract(curvesPresent)
|
||||
|
||||
if (curvesToCreate.isEmpty()) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, walletData)))
|
||||
return
|
||||
}
|
||||
|
||||
CreateWalletsTask().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success ->
|
||||
callback(CompletionResult.Success(ScanNoteResponse(result.data, walletData)))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import com.tangem.tap.features.home.redux.HomeState
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_home.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
|
|
@ -40,10 +41,15 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
|
|||
store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition))
|
||||
store.dispatch(HomeAction.Init)
|
||||
|
||||
btn_scan_card?.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
|
||||
btn_get_new_card?.setOnClickListener { store.dispatch(HomeAction.GoToShop) }
|
||||
tv_header.setText(R.string.home_welcome_header)
|
||||
tv_body.setText(R.string.home_welcome_body)
|
||||
|
||||
btnScanCard = IndeterminateProgressButtonWidget(btn_scan_card, progress)
|
||||
btn_main_action.setText(R.string.home_button_scan)
|
||||
btn_main_action.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
|
||||
btn_alternative_action.setText(R.string.home_button_get_new_card)
|
||||
btn_alternative_action.setOnClickListener { store.dispatch(HomeAction.GoToShop) }
|
||||
|
||||
btnScanCard = IndeterminateProgressButtonWidget(btn_main_action, progress)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,24 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.post
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.OnboardingService
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.features.onboarding.service.OnboardingHelper
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class HomeMiddleware {
|
||||
|
|
@ -32,13 +39,7 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
store.dispatch(GlobalAction.GetMoonPayUserStatus)
|
||||
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
|
||||
}
|
||||
is HomeAction.ReadCard -> {
|
||||
if (!preferencesStorage.wasDisclaimerAccepted()) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
} else {
|
||||
launchOnboardingService()
|
||||
}
|
||||
}
|
||||
is HomeAction.ReadCard -> handleReadCard()
|
||||
is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
|
||||
}
|
||||
next(action)
|
||||
|
|
@ -46,39 +47,59 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
}
|
||||
}
|
||||
|
||||
private fun launchOnboardingService() {
|
||||
fun changeButtonState(state: ButtonState) {
|
||||
val btnState = IndeterminateProgressButton(state)
|
||||
store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
|
||||
private fun handleReadCard() {
|
||||
if (!preferencesStorage.wasDisclaimerAccepted()) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
} else {
|
||||
fun changeButtonState(state: ButtonState) {
|
||||
val btnState = IndeterminateProgressButton(state)
|
||||
store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
|
||||
}
|
||||
|
||||
fun proceedToWalletScreen(tapWalletManager: TapWalletManager, scanResponse: ScanNoteResponse) {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
scope.launch {
|
||||
tapWalletManager.onCardScanned(scanResponse)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
|
||||
store.dispatch(GlobalAction.ReadCard({ scanResponse ->
|
||||
val tapWalletManager = store.state.globalState.tapWalletManager
|
||||
if (!OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
proceedToWalletScreen(tapWalletManager, scanResponse)
|
||||
return@ReadCard
|
||||
}
|
||||
|
||||
val service = OnboardingHelper.createService(
|
||||
AppScreen.Home,
|
||||
scanResponse,
|
||||
tapWalletManager.walletManagerFactory
|
||||
)
|
||||
if (service == null) {
|
||||
proceedToWalletScreen(tapWalletManager, scanResponse)
|
||||
return@ReadCard
|
||||
}
|
||||
|
||||
service.onInitializationProgress = {
|
||||
when (it) {
|
||||
ProgressState.Loading -> changeButtonState(ButtonState.PROGRESS)
|
||||
ProgressState.Done -> changeButtonState(ButtonState.ENABLED)
|
||||
ProgressState.Error -> changeButtonState(ButtonState.ENABLED)
|
||||
}
|
||||
}
|
||||
service.onInitialized = {
|
||||
store.dispatch(GlobalAction.Onboarding.Activate(service))
|
||||
store.dispatch(NavigationAction.NavigateTo(it, store.state.homeState.shareTransition))
|
||||
}
|
||||
service.onError = {
|
||||
store.dispatchErrorNotification(it)
|
||||
}
|
||||
post(DELAY_SDK_DIALOG_CLOSE) { service.initializeOnboarding() }
|
||||
}, {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
}))
|
||||
}
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
|
||||
val walletManager = store.state.globalState.tapWalletManager
|
||||
val service = OnboardingService(
|
||||
walletManager,
|
||||
preferencesStorage.usedCardsPrefStorage
|
||||
)
|
||||
|
||||
service.onReadyToProceed = {
|
||||
store.dispatch(OnboardingAction.SetData(it))
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
val sharedTransition = store.state.homeState.shareTransition
|
||||
store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen, fragmentShareTransition = sharedTransition))
|
||||
}
|
||||
|
||||
service.onFailedToProceedToOnboardingCase = {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
walletManager.onCardScanned(it)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
|
||||
service.onError = {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatch(GlobalAction.DebugShowError(TapError.CustomError(it)))
|
||||
}
|
||||
|
||||
store.dispatch(GlobalAction.ReadCard(service::onSuccess, {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatch(GlobalAction.DebugShowError(TapError.ScanCardError))
|
||||
}))
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.dialog_onboarding_address_info.*
|
||||
import kotlinx.android.synthetic.main.layout_pseudo_toolbar.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddressInfoBottomSheetDialog(
|
||||
private val stateDialog: AppDialog.AddressInfoDialog,
|
||||
context: Context
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
init {
|
||||
this.setContentView(R.layout.dialog_onboarding_address_info)
|
||||
setOnCancelListener { store.dispatchDialogHide() }
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
val data = stateDialog.addressData
|
||||
|
||||
imv_close.setOnClickListener {
|
||||
dismissWithAnimation = true
|
||||
cancel()
|
||||
}
|
||||
imv_qr_code.setImageBitmap(data.qrCode)
|
||||
tv_address.text = data.address
|
||||
btn_fl_copy_address.setOnClickListener {
|
||||
context.copyToClipboard(data.address)
|
||||
stateDialog.onCopyAddress()
|
||||
}
|
||||
btn_fl_explore.setOnClickListener {
|
||||
stateDialog.onExploreAddress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemWallet
|
||||
import com.tangem.tap.domain.UrlBitmapLoader
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.extensions.hasWallets
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingData
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.persistence.UsedCardsPrefStorage
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingService(
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
private val cardInfoStorage: UsedCardsPrefStorage
|
||||
) {
|
||||
|
||||
var onReadyToProceed: ((OnboardingData) -> Unit)? = null
|
||||
set(value) {
|
||||
field = value
|
||||
onboardingDataLinker.onReady = { value?.invoke(it) }
|
||||
}
|
||||
var onFailedToProceedToOnboardingCase: (suspend (ScanNoteResponse) -> Unit)? = null
|
||||
var onError: ((String) -> Unit)? = null
|
||||
|
||||
private val onboardingDataLinker = OnboardingDataLinker()
|
||||
|
||||
suspend fun onSuccess(response: ScanNoteResponse) {
|
||||
onboardingDataLinker.scanNoteResponse = response
|
||||
val card = response.card
|
||||
val hasWallets = card.hasWallets()
|
||||
|
||||
if (!card.isTangemNote() && !card.isTangemWallet() && !card.isTangemTwin()) {
|
||||
if (hasWallets) {
|
||||
cardInfoStorage.activate(card.cardId)
|
||||
onFailedToProceedToOnboardingCase?.invoke(response)
|
||||
return
|
||||
} else {
|
||||
cardInfoStorage.activate(card.cardId) // вызвать только после того, как был создан кошелек
|
||||
onboardingDataLinker.navigateTo = AppScreen.OnboardingOther
|
||||
}
|
||||
}
|
||||
if (cardInfoStorage.wasActivated(card.cardId)) {
|
||||
onFailedToProceedToOnboardingCase?.invoke(response)
|
||||
return
|
||||
}
|
||||
|
||||
loadCardImage(card) { onboardingDataLinker.cardArtwork = it }
|
||||
|
||||
if (hasWallets) {
|
||||
val walletManagerFactory = tapWalletManager.walletManagerFactory
|
||||
when {
|
||||
card.isTangemNote() -> loadBalanceNote(walletManagerFactory, response)
|
||||
card.isTangemWallet() -> loadBalanceWallet(walletManagerFactory, response)
|
||||
card.isTangemTwin() -> loadBalanceTwins(walletManagerFactory, response)
|
||||
}
|
||||
} else {
|
||||
onboardingDataLinker.valueOfWalletAmount = BigDecimal.ZERO
|
||||
}
|
||||
|
||||
onboardingDataLinker.navigateTo = when {
|
||||
card.isTangemNote() -> AppScreen.OnboardingNote
|
||||
card.isTangemWallet() -> AppScreen.OnboardingWallet
|
||||
card.isTangemTwin() -> AppScreen.OnboardingTwins
|
||||
else -> AppScreen.OnboardingOther
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadBalanceNote(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) {
|
||||
val walletManager: WalletManager = walletManagerFactory.makePrimaryWalletManager(response).guard {
|
||||
onError?.invoke("Can't proceed: primary wallet manager didn't create")
|
||||
return
|
||||
}
|
||||
walletManager.update()
|
||||
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
if (valueOfAmount == null) {
|
||||
onError?.invoke("Amount can't be NULL")
|
||||
return
|
||||
}
|
||||
|
||||
onboardingDataLinker.valueOfWalletAmount = valueOfAmount
|
||||
}
|
||||
|
||||
private suspend fun loadBalanceWallet(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) {
|
||||
|
||||
}
|
||||
|
||||
private suspend fun loadBalanceTwins(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) {
|
||||
|
||||
}
|
||||
|
||||
private suspend fun loadCardImage(card: Card, callback: (Artwork?) -> Unit) {
|
||||
fun loadBitmap(url: String, callback: (Artwork?) -> Unit) {
|
||||
UrlBitmapLoader().loadBitmap(url) {
|
||||
when (it) {
|
||||
is Result.Success -> callback(Artwork(url, it.data))
|
||||
is Result.Failure -> callback(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL
|
||||
loadBitmap(artworkUrl, callback)
|
||||
}
|
||||
is Result.Failure -> loadBitmap(Artwork.DEFAULT_IMG_URL, callback)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class OnboardingDataLinker {
|
||||
var onReady: ((OnboardingData) -> Unit)? = null
|
||||
|
||||
internal var scanNoteResponse: ScanNoteResponse? = null
|
||||
set(value) {
|
||||
field = value
|
||||
tryToProceed()
|
||||
}
|
||||
internal var cardArtwork: Artwork? = null
|
||||
set(value) {
|
||||
field = value
|
||||
tryToProceed()
|
||||
}
|
||||
internal var valueOfWalletAmount: BigDecimal? = null
|
||||
set(value) {
|
||||
field = value
|
||||
tryToProceed()
|
||||
}
|
||||
internal var navigateTo: AppScreen? = null
|
||||
set(value) {
|
||||
field = value
|
||||
tryToProceed()
|
||||
}
|
||||
|
||||
private fun tryToProceed() {
|
||||
if (!isReady()) return
|
||||
|
||||
val data = OnboardingData(scanNoteResponse!!, cardArtwork!!, valueOfWalletAmount, navigateTo!!)
|
||||
onReady?.invoke(data)
|
||||
}
|
||||
|
||||
private fun isReady(): Boolean {
|
||||
val objList = mutableListOf(scanNoteResponse, cardArtwork, valueOfWalletAmount, navigateTo)
|
||||
return !objList.any { it == null }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.tap.features.onboarding.products
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.extensions.inflate
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
|
||||
import com.tangem.tap.features.send.BaseStoreFragment
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
|
||||
import kotlinx.android.synthetic.main.layout_pseudo_toolbar.*
|
||||
import kotlinx.android.synthetic.main.view_confetti.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber<T> {
|
||||
|
||||
protected var fromScreen: AppScreen? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
fromScreen = store.state.globalState.onboardingService?.fromScreen
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun configureTransitions() {
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
when (fromScreen) {
|
||||
AppScreen.Home -> {
|
||||
enterTransition = HomeToOnboardingTransition()
|
||||
exitTransition = HomeToOnboardingTransition()
|
||||
}
|
||||
AppScreen.Wallet -> {
|
||||
enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom)
|
||||
exitTransition = inflater.inflateTransition(android.R.transition.slide_top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
onboarding_top_container.inflate(getOnboardingTopContainerId(), true)
|
||||
configureToolbar()
|
||||
}
|
||||
|
||||
private fun configureToolbar() {
|
||||
when (fromScreen) {
|
||||
AppScreen.Home -> {
|
||||
toolbar.show(true)
|
||||
pseudo_toolbar.visibility = View.GONE
|
||||
}
|
||||
AppScreen.Wallet -> {
|
||||
toolbar.visibility = View.GONE
|
||||
pseudo_toolbar.show(true)
|
||||
imv_close.setOnClickListener {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getOnboardingTopContainerId(): Int
|
||||
|
||||
protected fun showConfetti(show: Boolean) {
|
||||
lav_confetti.show(show)
|
||||
|
||||
if (show) {
|
||||
lav_confetti.playAnimation()
|
||||
} else {
|
||||
lav_confetti.cancelAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,46 +2,46 @@ package com.tangem.tap.features.onboarding.products.note
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.extensions.addOnBackPressedDispatcher
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
|
||||
import com.tangem.tap.features.send.BaseStoreFragment
|
||||
import androidx.transition.TransitionManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.*
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_onboarding.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_note.*
|
||||
import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber<OnboardingNoteState> {
|
||||
class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
|
||||
|
||||
activity?.addOnBackPressedDispatcher {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingNoteStep.None))
|
||||
}
|
||||
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
store.dispatch(OnboardingNoteAction.Init)
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
|
||||
val resources = AndroidResources(
|
||||
OnboardingStringResources(
|
||||
R.string.copy_toast_msg
|
||||
)
|
||||
)
|
||||
|
||||
imv_front_card.setImageBitmap(store.state.onboardingState.onboardingData?.cardArtwork?.artwork)
|
||||
store.dispatch(OnboardingNoteAction.SetResources(resources))
|
||||
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
|
|
@ -55,79 +55,107 @@ class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding),
|
|||
override fun newState(state: OnboardingNoteState) {
|
||||
if (activity == null) return
|
||||
|
||||
state.artworkBitmap?.let { imv_front_card.setImageBitmap(it) }
|
||||
pb_state.max = state.steps.size - 1
|
||||
pb_state.progress = state.progress
|
||||
|
||||
onboarding_main_container.beginDelayedTransition()
|
||||
when (state.currentStep) {
|
||||
OnboardingNoteStep.None -> setupNoneState(state)
|
||||
OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
|
||||
OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
|
||||
OnboardingNoteStep.Done -> setupSuccessState(state)
|
||||
OnboardingNoteStep.Done -> setupDoneState(state)
|
||||
}
|
||||
setBalance(state)
|
||||
showConfetti(state.showConfetti)
|
||||
}
|
||||
|
||||
private fun setBalance(state: OnboardingNoteState) {
|
||||
if (state.balanceCurrency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
val balanceValue = state.balanceValue.stripZeroPlainString()
|
||||
val currency = state.balanceCurrency.currencySymbol
|
||||
tv_balance_value.text = "$balanceValue $currency"
|
||||
}
|
||||
|
||||
private fun setupNoneState(state: OnboardingNoteState) {
|
||||
pb_state.progress = state.progress
|
||||
btn_main_action.isVisible = false
|
||||
btn_alternative_action.isVisible = false
|
||||
btnRefreshBalanceWidget.show(false)
|
||||
tv_header.isVisible = false
|
||||
tv_body.isVisible = false
|
||||
|
||||
btn_main_action.isVisible = true
|
||||
btn_alternative_action.isVisible = true
|
||||
btn_check_balance.isVisible = true
|
||||
tv_header.isVisible = true
|
||||
tv_body.isVisible = true
|
||||
|
||||
btn_main_action.text = "NULL"
|
||||
btn_alternative_action.text = "NULL"
|
||||
btn_check_balance.text = "NULL balance"
|
||||
tv_header.text = "NULL"
|
||||
tv_body.text = "NULL"
|
||||
btn_main_action.text = ""
|
||||
btn_alternative_action.text = ""
|
||||
tv_header.text = ""
|
||||
tv_body.text = ""
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState(state: OnboardingNoteState) {
|
||||
pb_state.progress = state.progress
|
||||
|
||||
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
|
||||
|
||||
btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btn_alternative_action.setOnClickListener { }
|
||||
|
||||
btn_check_balance.isVisible = false
|
||||
btn_check_balance.setOnClickListener { }
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tv_header.setText(R.string.onboarding_create_wallet_header)
|
||||
tv_body.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
|
||||
updateConstraints(R.layout.lp_onboarding_create_wallet)
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: OnboardingNoteState) {
|
||||
pb_state.progress = state.progress
|
||||
|
||||
btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.TopUp) }
|
||||
btn_main_action.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.TopUp)
|
||||
}
|
||||
|
||||
btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btn_alternative_action.setOnClickListener { }
|
||||
|
||||
btn_check_balance.isVisible = false
|
||||
btn_check_balance.setOnClickListener { }
|
||||
btn_alternative_action.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
tv_header.setText(R.string.onboarding_top_up_header)
|
||||
tv_body.setText(R.string.onboarding_top_up_body)
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.balanceState)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
postUi(300) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
}
|
||||
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
|
||||
updateConstraints(R.layout.lp_onboarding_topup_wallet)
|
||||
}
|
||||
|
||||
private fun setupSuccessState(state: OnboardingNoteState) {
|
||||
pb_state.progress = state.progress
|
||||
|
||||
private fun setupDoneState(state: OnboardingNoteState) {
|
||||
btn_main_action.setText(R.string.onboarding_done_button_continue)
|
||||
btn_main_action.setOnClickListener { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) }
|
||||
btn_main_action.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingNoteAction.Done)
|
||||
}
|
||||
|
||||
btn_alternative_action.isVisible = false
|
||||
btn_alternative_action.setText("")
|
||||
btn_alternative_action.setOnClickListener { }
|
||||
|
||||
btn_check_balance.isVisible = false
|
||||
btn_check_balance.setOnClickListener { }
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tv_header.setText(R.string.onboarding_done_header)
|
||||
tv_body.setText(R.string.onboarding_done_body)
|
||||
|
||||
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
|
||||
updateConstraints(R.layout.lp_onboarding_done)
|
||||
}
|
||||
|
||||
private fun updateConstraints(@LayoutRes layoutId: Int) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboarding_main_container)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,31 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.features.onboarding.service.OnboardingWalletBalance
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingNoteAction : Action {
|
||||
object Init : OnboardingNoteAction()
|
||||
// from user, ui
|
||||
data class SetResources(val resources: AndroidResources) : OnboardingNoteAction()
|
||||
object CreateWallet : OnboardingNoteAction()
|
||||
object TopUp : OnboardingNoteAction()
|
||||
object ShowAddressInfoDialog : OnboardingNoteAction()
|
||||
|
||||
// from redux
|
||||
object DetermineStepOfScreen : OnboardingNoteAction()
|
||||
object Done : OnboardingNoteAction()
|
||||
|
||||
data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction()
|
||||
data class SetStepOfScreen(val step: OnboardingNoteStep) : OnboardingNoteAction()
|
||||
|
||||
sealed class Balance {
|
||||
object Update : OnboardingNoteAction()
|
||||
data class Set(val balance: OnboardingWalletBalance) : OnboardingNoteAction()
|
||||
}
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : OnboardingNoteAction()
|
||||
object Hide : OnboardingNoteAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
|
||||
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.features.onboarding.service.balanceIsToppedUp
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
@ -13,8 +19,6 @@ import org.rekotlin.Middleware
|
|||
|
||||
class OnboardingNoteMiddleware {
|
||||
companion object {
|
||||
private const val CARD_SHOP_URI =
|
||||
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
|
||||
val handler = onboardingNoteMiddleware
|
||||
}
|
||||
}
|
||||
|
|
@ -22,51 +26,93 @@ class OnboardingNoteMiddleware {
|
|||
private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleMainOnboardingAction(action, ::handleNoteAction)
|
||||
handleNoteAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMainOnboardingAction(action: Action, isNotMainAction: (Action) -> Unit) {
|
||||
val action = action as? OnboardingAction ?: isNotMainAction(action)
|
||||
|
||||
when (action) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNoteAction(action: Action) {
|
||||
val onboardingState = store.state.onboardingState
|
||||
if (action !is OnboardingNoteAction) return
|
||||
val service = store.state.onboardingNoteState.onboardingService ?: return
|
||||
val noteState = store.state.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is OnboardingNoteAction.SetWalletManager -> {
|
||||
}
|
||||
is OnboardingNoteAction.Init -> {
|
||||
val step = if (noteState.walletManager == null) {
|
||||
OnboardingNoteStep.CreateWallet
|
||||
} else {
|
||||
OnboardingNoteStep.TopUpWallet
|
||||
is OnboardingNoteAction.DetermineStepOfScreen -> {
|
||||
val step = when {
|
||||
service.walletManager == null -> OnboardingNoteStep.CreateWallet
|
||||
service.balanceIsToppedUp() -> OnboardingNoteStep.Done
|
||||
else -> OnboardingNoteStep.TopUpWallet
|
||||
}
|
||||
store.dispatch((OnboardingNoteAction.SetStepOfScreen(step)))
|
||||
}
|
||||
is OnboardingNoteAction.SetStepOfScreen -> {
|
||||
if (action.step == OnboardingNoteStep.Done) {
|
||||
service.activationFinished()
|
||||
postUi(500) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
|
||||
}
|
||||
store.dispatch(OnboardingAction.SetInitialStepOfScreen(step))
|
||||
}
|
||||
is OnboardingNoteAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val scanNoteResponse = onboardingState.onboardingData?.scanNoteResponse
|
||||
val result = tangemSdkManager.createWallet(scanNoteResponse?.card?.cardId)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
// val scanNoteResponse = scanNoteResponse!!.copy(card = result.data)
|
||||
// store.dispatch(OnboardingAction.SetScanNoteResponse(scanNoteResponse))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatch(OnboardingAction.Error("Failed to create wallet"))
|
||||
val result = tangemSdkManager.createProductWallet(service.scanResponse.card.cardId)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val updatedResponse = service.scanResponse.copy(card = result.data)
|
||||
service.scanResponse = updatedResponse
|
||||
service.activationStarted()
|
||||
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
|
||||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.TopUp -> {
|
||||
is OnboardingNoteAction.Balance.Update -> {
|
||||
val loadingBalance = service.getBalance().copy(
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null
|
||||
)
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(loadingBalance))
|
||||
|
||||
scope.launch {
|
||||
val loadedBalance = service.updateBalance()
|
||||
if (loadedBalance.criticalError != null) {
|
||||
store.dispatchErrorNotification(loadedBalance.criticalError)
|
||||
}
|
||||
withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) }
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Set -> {
|
||||
if (service.balanceIsToppedUp()) {
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done))
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.ShowAddressInfoDialog -> {
|
||||
val addressData = noteState.onboardingService?.getAddressData() ?: return
|
||||
val addressWasCopied = noteState.resources.strings.addressWasCopied
|
||||
val appDialog = AppDialog.AddressInfoDialog(
|
||||
addressData,
|
||||
onCopyAddress = { store.dispatchToastNotification(addressWasCopied) },
|
||||
onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) }
|
||||
)
|
||||
store.dispatchDialogShow(appDialog)
|
||||
}
|
||||
is OnboardingNoteAction.TopUp -> {
|
||||
val topUpUrl = noteState.onboardingService?.getToUpUrl() ?: return
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
OnboardingNoteAction.Done -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Deactivate)
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.service.OnboardingNoteService
|
||||
import org.rekotlin.Action
|
||||
|
||||
class OnboardingNoteReducer {
|
||||
|
|
@ -15,23 +15,46 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt
|
|||
var state = appState.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is OnboardingAction.SetInitialStepOfScreen -> {
|
||||
(action.step as? OnboardingNoteStep)?.let {
|
||||
if (it != state.currentStep && state.steps.contains(it)) {
|
||||
state = state.copy(currentStep = it)
|
||||
}
|
||||
is GlobalAction.Onboarding.Activate -> {
|
||||
if (action.onboardingService is OnboardingNoteService) {
|
||||
val service = action.onboardingService
|
||||
val balance = service.getBalance()
|
||||
state = state.copy(
|
||||
onboardingService = service,
|
||||
artworkBitmap = service.getArtwork().value?.artwork,
|
||||
balanceValue = balance.value,
|
||||
balanceCurrency = balance.currency,
|
||||
balanceState = balance.state,
|
||||
amountToCreateAccount = balance.amountToCreateAccount,
|
||||
showConfetti = false
|
||||
)
|
||||
}
|
||||
}
|
||||
OnboardingAction.SwitchToNextStep -> {
|
||||
val progress = state.progress
|
||||
if (progress >= 0 && progress != state.steps.size - 1) {
|
||||
state = state.copy(currentStep = state.steps[progress + 1])
|
||||
GlobalAction.Onboarding.Deactivate -> {
|
||||
state = OnboardingNoteState()
|
||||
}
|
||||
is OnboardingNoteAction.SetResources -> {
|
||||
state = state.copy(resources = action.resources)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Set -> {
|
||||
state = state.copy(
|
||||
balanceValue = action.balance.value,
|
||||
balanceCurrency = action.balance.currency,
|
||||
balanceState = action.balance.state,
|
||||
amountToCreateAccount = action.balance.amountToCreateAccount,
|
||||
)
|
||||
}
|
||||
is OnboardingNoteAction.SetStepOfScreen -> {
|
||||
if (action.step != state.currentStep && state.steps.contains(action.step)) {
|
||||
state = state.copy(currentStep = action.step)
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.SetWalletManager -> {
|
||||
state = state.copy(walletManager = action.walletManager)
|
||||
is OnboardingNoteAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is OnboardingNoteAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return state
|
||||
|
|
|
|||
|
|
@ -1,18 +1,42 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.features.onboarding.service.OnboardingNoteService
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingNoteState(
|
||||
val walletManager: WalletManager? = null,
|
||||
val onboardingService: OnboardingNoteService? = null,
|
||||
val resources: AndroidResources = AndroidResources(),
|
||||
// UI
|
||||
val showConfetti: Boolean = false,
|
||||
val artworkBitmap: Bitmap? = null,
|
||||
val balanceValue: BigDecimal = BigDecimal.ZERO,
|
||||
val balanceCurrency: Currency = Currency.Blockchain(Blockchain.Unknown),
|
||||
val balanceState: ProgressState = ProgressState.Done,
|
||||
val amountToCreateAccount: String? = null,
|
||||
val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
|
||||
val steps: List<OnboardingNoteStep> = OnboardingNoteStep.values().toList(),
|
||||
) : StateType {
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
}
|
||||
}
|
||||
|
||||
enum class OnboardingNoteStep {
|
||||
None, CreateWallet, TopUpWallet, Done
|
||||
}
|
||||
|
||||
data class AndroidResources(
|
||||
val strings: OnboardingStringResources = OnboardingStringResources()
|
||||
)
|
||||
|
||||
data class OnboardingStringResources(
|
||||
val addressWasCopied: Int = -1
|
||||
)
|
||||
|
|
@ -1,31 +1,20 @@
|
|||
package com.tangem.tap.features.onboarding.products.old
|
||||
package com.tangem.tap.features.onboarding.products.otherCards
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.extensions.addOnBackPressedDispatcher
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingOtherCardsState
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep
|
||||
import com.tangem.tap.features.send.BaseStoreFragment
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber<OnboardingOtherCardsState> {
|
||||
class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber<OnboardingOtherCardsState> {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
activity?.addOnBackPressedDispatcher {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done))
|
||||
}
|
||||
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
|
|
@ -47,4 +36,6 @@ class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboard
|
|||
if (activity == null) return
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OnboardingOtherCardsState {}
|
||||
|
|
@ -3,11 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet
|
|||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tap.common.extensions.addOnBackPressedDispatcher
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingAction
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep
|
||||
import com.tangem.tap.features.send.BaseStoreFragment
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -21,11 +17,6 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
activity?.addOnBackPressedDispatcher {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done))
|
||||
}
|
||||
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
|
|
@ -33,8 +24,6 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
store.dispatch(OnboardingAction.OnboardingOtherAction.Init)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.tap.features.onboarding.redux.OnboardingStep
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
|
|
@ -10,6 +9,6 @@ data class OnboardingWalletState(
|
|||
val any: String? = null
|
||||
) : StateType
|
||||
|
||||
enum class OnboardingWalletStep : OnboardingStep {
|
||||
enum class OnboardingWalletStep {
|
||||
None, CreateWallet, TopUpWallet, Backup, Done
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingAction : Action {
|
||||
|
||||
data class SetInitialStepOfScreen(val step: OnboardingStep) : OnboardingAction()
|
||||
object SwitchToNextStep : OnboardingAction()
|
||||
|
||||
data class SetData(val data: OnboardingData) : OnboardingAction()
|
||||
|
||||
sealed class OnboardingOtherAction : Action {
|
||||
object Init : OnboardingOtherAction()
|
||||
object CreateWallet : OnboardingOtherAction()
|
||||
object Done : OnboardingOtherAction()
|
||||
}
|
||||
|
||||
class Error(val message: String) : OnboardingAction()
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class OnboardingMiddleware {
|
||||
companion object {
|
||||
val handler = onboardingMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
private val onboardingMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
val onboardingState = store.state.onboardingState
|
||||
when (action) {
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
class OnboardingReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: AppState): OnboardingState = internalReduce(action, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingState {
|
||||
|
||||
if (action !is OnboardingAction) return appState.onboardingState
|
||||
|
||||
var state = appState.onboardingState
|
||||
when (action) {
|
||||
is OnboardingAction.SetData -> {
|
||||
state = state.copy(onboardingData = action.data)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.redux
|
||||
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class OnboardingState(
|
||||
val onboardingData: OnboardingData? = null,
|
||||
) : StateType
|
||||
|
||||
data class OnboardingOtherCardsState(
|
||||
val any: String? = null
|
||||
) : StateType
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.redux
|
||||
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingData(
|
||||
val scanNoteResponse: ScanNoteResponse,
|
||||
val cardArtwork: Artwork,
|
||||
val balance: BigDecimal?,
|
||||
val onboardingScreen: AppScreen
|
||||
)
|
||||
|
||||
interface OnboardingStep
|
||||
|
||||
enum class OnboardingNoteStep : OnboardingStep {
|
||||
None, CreateWallet, TopUpWallet, Done
|
||||
}
|
||||
|
||||
enum class OnboardingOtherStep : OnboardingStep {
|
||||
CreateWallet, Done
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.tap.features.onboarding.service
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
|
||||
import com.tangem.tap.domain.extensions.hasWallets
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingHelper {
|
||||
companion object {
|
||||
|
||||
fun isOnboardingCase(response: ScanNoteResponse): Boolean = if (response.card.hasWallets())
|
||||
preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true
|
||||
|
||||
fun createService(
|
||||
fromScreen: AppScreen,
|
||||
scanResponse: ScanNoteResponse,
|
||||
walletManagerFactory: WalletManagerFactory,
|
||||
): ProductOnboardingService? {
|
||||
val card = scanResponse.card
|
||||
val cardInfoStorage = preferencesStorage.usedCardsPrefStorage
|
||||
return when {
|
||||
card.isTangemNote() -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage,
|
||||
walletManagerFactory)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.features.onboarding.service
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.persistence.UsedCardsPrefStorage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingNoteService(
|
||||
override val fromScreen: AppScreen,
|
||||
override var scanResponse: ScanNoteResponse,
|
||||
cardInfoStorage: UsedCardsPrefStorage,
|
||||
walletManagerFactory: WalletManagerFactory,
|
||||
) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) {
|
||||
|
||||
override fun navigateToScreen(): AppScreen {
|
||||
return AppScreen.OnboardingNote
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
package com.tangem.tap.features.onboarding.service
|
||||
|
||||
import android.util.Log
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.isPositive
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.UrlBitmapLoader
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.topup.TradeCryptoHelper
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
||||
import com.tangem.tap.persistence.UsedCardsPrefStorage
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.math.BigDecimal
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface OnboardingService {
|
||||
val fromScreen: AppScreen
|
||||
val scanResponse: ScanNoteResponse
|
||||
|
||||
// val cardInfoStorage: UsedCardsPrefStorage
|
||||
var walletManager: WalletManager?
|
||||
|
||||
var onInitializationProgress: ((ProgressState) -> Unit)?
|
||||
var onInitialized: ((AppScreen) -> Unit)?
|
||||
var onError: ((TapError) -> Unit)?
|
||||
|
||||
fun initializeOnboarding()
|
||||
fun getArtwork(): OnboardingArtwork
|
||||
fun getBalance(): OnboardingWalletBalance
|
||||
suspend fun updateBalance(): OnboardingWalletBalance
|
||||
fun getToUpUrl(): String?
|
||||
fun getAddressData(): AddressData?
|
||||
|
||||
fun activationStarted()
|
||||
fun activationFinished()
|
||||
}
|
||||
|
||||
abstract class ProductOnboardingService(
|
||||
private val cardInfoStorage: UsedCardsPrefStorage,
|
||||
private val walletManagerFactory: WalletManagerFactory,
|
||||
) : OnboardingService {
|
||||
|
||||
override var walletManager: WalletManager? = null
|
||||
|
||||
override var onInitializationProgress: ((ProgressState) -> Unit)? = null
|
||||
override var onInitialized: ((AppScreen) -> Unit)? = null
|
||||
override var onError: ((TapError) -> Unit)? = null
|
||||
|
||||
protected var isProceeded = false
|
||||
|
||||
protected var loadedCardArtwork: OnboardingArtwork = OnboardingArtwork.loading()
|
||||
set(value) {
|
||||
field = value
|
||||
tryToProceed()
|
||||
}
|
||||
|
||||
protected var loadedBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading()
|
||||
set(value) {
|
||||
field = value
|
||||
tryToProceed()
|
||||
}
|
||||
|
||||
protected val internalScope = CoroutineScope(Job() + Dispatchers.IO + coroutineExceptionHandler())
|
||||
|
||||
override fun initializeOnboarding() {
|
||||
val card = scanResponse.card
|
||||
onInitializationProgress?.invoke(ProgressState.Loading)
|
||||
|
||||
internalScope.launch {
|
||||
val artwork = loadCardArtwork(card)
|
||||
withMainContext {
|
||||
loadedCardArtwork = if (artwork == null) OnboardingArtwork.error() else OnboardingArtwork.done(artwork)
|
||||
}
|
||||
}
|
||||
internalScope.launch { updateBalance() }
|
||||
}
|
||||
|
||||
override fun getArtwork(): OnboardingArtwork = loadedCardArtwork
|
||||
|
||||
override fun getBalance(): OnboardingWalletBalance = loadedBalance
|
||||
|
||||
override fun getToUpUrl(): String? {
|
||||
val wallet = walletManager?.wallet ?: return null
|
||||
val config = store.state.globalState.configManager?.config ?: return null
|
||||
val defaultAddress = wallet.address
|
||||
|
||||
return TradeCryptoHelper.getUrl(
|
||||
TradeCryptoHelper.Action.Buy,
|
||||
wallet.blockchain,
|
||||
wallet.blockchain.currency,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAddressData(): AddressData? {
|
||||
val wallet = walletManager?.wallet ?: return null
|
||||
|
||||
val addressDataList = wallet.createAddressesData()
|
||||
return if (addressDataList.isEmpty()) null
|
||||
else addressDataList[0]
|
||||
}
|
||||
|
||||
protected suspend fun loadCardArtwork(card: Card): Artwork? {
|
||||
return when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL
|
||||
loadBitmap(artworkUrl)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
loadBitmap(Artwork.DEFAULT_IMG_URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateBalance(): OnboardingWalletBalance {
|
||||
withMainContext { loadedBalance = OnboardingWalletBalance.loading(loadedBalance.value) }
|
||||
|
||||
val walletManager = walletManager ?: walletManagerFactory.makePrimaryWalletManager(scanResponse).guard {
|
||||
val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created")
|
||||
loadedBalance = OnboardingWalletBalance.error(customError)
|
||||
return loadedBalance
|
||||
}
|
||||
|
||||
this.walletManager = walletManager
|
||||
val currency = Currency.Blockchain(walletManager.wallet.blockchain)
|
||||
val updatedBalance = when (val result = walletManager.safeUpdate()) {
|
||||
is Result.Success -> {
|
||||
val wallet = walletManager.wallet
|
||||
val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
if (valueOfAmount == null) {
|
||||
val customError = TapError.CustomError("Amount is NULL")
|
||||
OnboardingWalletBalance.criticalError(customError)
|
||||
} else {
|
||||
val balance = if (valueOfAmount.isZero()) {
|
||||
OnboardingWalletBalance.done(valueOfAmount, hasForIncomingTransactions(), currency)
|
||||
} else {
|
||||
OnboardingWalletBalance.done(valueOfAmount, false, currency)
|
||||
}
|
||||
balance
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
val error = (result.error as? TapError) ?: TapError.UnknownError
|
||||
when (error) {
|
||||
is TapError.WalletManagerUpdate.NoAccountError -> OnboardingWalletBalance.error(error)
|
||||
// NoInternetConnection, WalletManagerUpdate.InternalError
|
||||
else -> OnboardingWalletBalance.criticalError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
withMainContext { loadedBalance = updatedBalance }
|
||||
return loadedBalance
|
||||
}
|
||||
|
||||
override fun activationStarted() {
|
||||
cardInfoStorage.activationStarted(scanResponse.card.cardId)
|
||||
|
||||
}
|
||||
|
||||
override fun activationFinished() {
|
||||
cardInfoStorage.activationFinished(scanResponse.card.cardId)
|
||||
}
|
||||
|
||||
protected open suspend fun hasForIncomingTransactions(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun tryToProceed() {
|
||||
if (isProceeded) return
|
||||
|
||||
loadedBalance.criticalError?.let {
|
||||
sendError(it)
|
||||
return
|
||||
}
|
||||
if (!isReadyToProceed()) return
|
||||
|
||||
isProceeded = true
|
||||
onInitializationProgress?.invoke(ProgressState.Done)
|
||||
onInitialized?.invoke(navigateToScreen())
|
||||
}
|
||||
|
||||
protected open fun isReadyToProceed(): Boolean {
|
||||
return loadedBalance.state != ProgressState.Loading && loadedCardArtwork.state != ProgressState.Loading
|
||||
}
|
||||
|
||||
protected fun sendError(error: TapError) {
|
||||
onInitializationProgress?.invoke(ProgressState.Error)
|
||||
onError?.invoke(error)
|
||||
}
|
||||
|
||||
protected abstract fun navigateToScreen(): AppScreen
|
||||
|
||||
private suspend fun loadBitmap(url: String): Artwork? {
|
||||
return withContext(Dispatchers.Main) {
|
||||
suspendCoroutine { continuation ->
|
||||
UrlBitmapLoader().loadBitmap(url) {
|
||||
val result = when (it) {
|
||||
is Result.Success -> Artwork(url, it.data)
|
||||
is Result.Failure -> null
|
||||
}
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun coroutineExceptionHandler(): CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||
StringWriter().apply {
|
||||
throwable.printStackTrace(PrintWriter(this))
|
||||
Log.e("Coroutine", this.toString())
|
||||
store.dispatchDebugErrorNotification(this.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class OnboardingArtwork(
|
||||
val value: Artwork? = null,
|
||||
val state: ProgressState
|
||||
) {
|
||||
companion object {
|
||||
// balance was not loaded at all when any error occurred
|
||||
fun error(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Error)
|
||||
|
||||
// balance is loading
|
||||
fun loading(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Loading)
|
||||
|
||||
// balance was loaded
|
||||
fun done(artwork: Artwork): OnboardingArtwork = OnboardingArtwork(artwork, ProgressState.Done)
|
||||
}
|
||||
}
|
||||
|
||||
data class OnboardingWalletBalance(
|
||||
val value: BigDecimal = BigDecimal.ZERO,
|
||||
val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown),
|
||||
val hasIncomingTransaction: Boolean = false,
|
||||
val state: ProgressState,
|
||||
val error: TapError? = null,
|
||||
val criticalError: TapError? = null,
|
||||
) {
|
||||
|
||||
val amountToCreateAccount: String?
|
||||
get() = if (error is TapError.WalletManagerUpdate.NoAccountError) error.customMessage else null
|
||||
|
||||
companion object {
|
||||
fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
state = ProgressState.Error,
|
||||
error = error
|
||||
)
|
||||
|
||||
fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
state = ProgressState.Error,
|
||||
criticalError = error
|
||||
)
|
||||
|
||||
fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
value,
|
||||
state = ProgressState.Loading
|
||||
)
|
||||
|
||||
fun done(value: BigDecimal, hasTransactions: Boolean, currency: Currency.Blockchain): OnboardingWalletBalance =
|
||||
OnboardingWalletBalance(value, currency, hasTransactions, ProgressState.Done)
|
||||
}
|
||||
}
|
||||
|
||||
fun ProductOnboardingService.balanceIsToppedUp(): Boolean {
|
||||
val balance = getBalance()
|
||||
return balance.value.isPositive() || balance.hasIncomingTransaction
|
||||
}
|
||||
|
|
@ -24,7 +24,10 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
configureTransitions()
|
||||
}
|
||||
|
||||
protected open fun configureTransitions() {
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue