Updated on 2026-08-14
This commit is contained in:
commit
a8fa824539
148 changed files with 3964 additions and 3579 deletions
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
|
|
@ -84,9 +84,9 @@
|
|||
<inspection_tool class="SimpleRedundantLet" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifyWhenWithBooleanConstantCondition" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryOptInAnnotation" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryVariable" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedReceiverParameter" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedSymbol" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedReceiverParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedSymbol" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WhenWithOnlyElse" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
|
|
@ -122,12 +122,12 @@ dependencies {
|
|||
|
||||
// Firebase
|
||||
implementation platform('com.google.firebase:firebase-bom:26.0.0')
|
||||
implementation 'com.google.firebase:firebase-crashlytics'
|
||||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
implementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ktx'
|
||||
|
||||
// AppsFlyer
|
||||
// Analytics
|
||||
implementation 'com.appsflyer:af-android-sdk:6.5.1'
|
||||
implementation 'com.amplitude:android-sdk:2.36.1'
|
||||
|
||||
// WalletConnect
|
||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit c8f577f2ab48e0d6c49f79b6cff21eabe67cd63e
|
||||
Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,6 @@ import com.tangem.tap.common.DialogManager
|
|||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.common.redux.global.AndroidResources
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -61,7 +60,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
internalActivity = this
|
||||
setContentView(R.layout.activity_main)
|
||||
systemActions()
|
||||
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
|
||||
|
|
@ -176,10 +174,4 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
override fun removeOnActivityResultCallback(callback: OnActivityResultCallback) {
|
||||
onActivityResultCallbacks.remove(callback)
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var internalActivity: AppCompatActivity
|
||||
//TODO: delete
|
||||
fun copyToClipboard(text: String) {
|
||||
internalActivity.copyToClipboard(text)
|
||||
}
|
||||
|
|
@ -5,16 +5,16 @@ import android.content.Context
|
|||
import android.content.pm.PackageManager
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.tangem.Log
|
||||
import com.tangem.LogFormat
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.AndroidAssetReader
|
||||
import com.tangem.tap.common.AssetReader
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandlerBuilder
|
||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
|
|
@ -97,15 +97,19 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private fun initWithConfigDependency(config: Config) {
|
||||
shopService = TangemShopService(this, config.shopify!!)
|
||||
initAppsFlyer(this, config)
|
||||
initAnalytics(this, config)
|
||||
initFeedbackManager(this, preferencesStorage, config)
|
||||
}
|
||||
|
||||
private fun initAppsFlyer(context: Context, config: Config) {
|
||||
AppsFlyerLib.getInstance().init(config.appsFlyerDevKey, null, context)
|
||||
AppsFlyerLib.getInstance().start(context)
|
||||
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(context)
|
||||
store.dispatch(GlobalAction.SetAnanlyticHandlers(analyticsHandler))
|
||||
private fun initAnalytics(application: Application, config: Config) {
|
||||
val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder(
|
||||
application = application,
|
||||
config = config,
|
||||
isDebug = BuildConfig.DEBUG,
|
||||
logConfig = LogConfig.analyticsHandlers,
|
||||
jsonConverter = MoshiJsonConverter.INSTANCE,
|
||||
).default()
|
||||
store.dispatch(GlobalAction.SetGlobalAnalyticsHandler(globalAnalyticsHandler))
|
||||
}
|
||||
|
||||
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
|||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.RegistrationErrorDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.TryToInterruptRegistrationDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
|
|
@ -54,7 +55,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
|
||||
override fun newState(state: GlobalState) {
|
||||
if (state.dialog == null) {
|
||||
dialog?.dismiss()
|
||||
|
|
@ -67,6 +67,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 +78,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,26 +125,23 @@ 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)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
|
||||
is SaltPayDialog.NoFundsForActivation -> NoFundsForActivationDialog.create(context)
|
||||
is SaltPayDialog.RegistrationError -> RegistrationErrorDialog.create(context, state.dialog)
|
||||
is WalletDialog.CurrencySelectionDialog ->
|
||||
CurrencySelectionDialog.create(state.dialog, context)
|
||||
is WalletDialog.ChooseTradeActionDialog ->
|
||||
ChooseTradeActionBottomSheetDialog(context)
|
||||
is WalletDialog.SelectAmountToSendDialog ->
|
||||
AmountToSendBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SignedHashesMultiWalletDialog ->
|
||||
SignedHashesWarningDialog.create(context)
|
||||
is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context)
|
||||
is SaltPayDialog.Activation.TryToInterrupt -> TryToInterruptRegistrationDialog.create(context, state.dialog)
|
||||
is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog)
|
||||
is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context)
|
||||
is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context)
|
||||
is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context)
|
||||
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 +150,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)
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ object TestActions {
|
|||
* @param isTestView - true must be used if you want to show or hide your view depends on BuildConfig
|
||||
*/
|
||||
fun initFor(view: View, actions: List<TestAction>, isTestView: Boolean = false) {
|
||||
if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED)
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED)
|
||||
|
||||
view.setOnClickListener {
|
||||
store.dispatchDialogShow(AppDialog.TestActionsDialog(actions))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
enum class AnalyticsEvent(val event: String) {
|
||||
enum class AnalyticsEventAnOld(val event: String) {
|
||||
CARD_IS_SCANNED("card_is_scanned"),
|
||||
TRANSACTION_IS_SENT("transaction_is_sent"),
|
||||
READY_TO_SCAN("ready_to_scan"),
|
||||
|
|
@ -19,7 +19,7 @@ enum class AnalyticsEvent(val event: String) {
|
|||
GET_CARD("get_card"),
|
||||
}
|
||||
|
||||
enum class AnalyticsParam(val param: String) {
|
||||
enum class AnalyticsParamAnOld(val param: String) {
|
||||
BLOCKCHAIN("blockchain"),
|
||||
CARD_ID("cardId"),
|
||||
BATCH_ID("batch_id"),
|
||||
|
|
@ -37,13 +37,13 @@ enum class AnalyticsParam(val param: String) {
|
|||
SOURCE("source"),
|
||||
}
|
||||
|
||||
enum class GetCardSourceParams(val param: String) {
|
||||
enum class GetCardSourceParamsAnOld(val param: String) {
|
||||
WELCOME("welcome"),
|
||||
ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"),
|
||||
ONBOARDING("wallet_onboarding")
|
||||
}
|
||||
|
||||
object Analytics {
|
||||
object AnalyticsAnOld {
|
||||
enum class ActionToLog(val key: String) {
|
||||
Scan("tap_scan_task"),
|
||||
SendTransaction("send_transaction"),
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.analytics.api.ErrorEventLogger
|
||||
import com.tangem.tap.common.analytics.api.EventLogger
|
||||
import timber.log.Timber
|
||||
|
||||
class AnalyticsEventsLogger(
|
||||
private val name: String,
|
||||
private val jsonConverter: MoshiJsonConverter,
|
||||
) : EventLogger, ErrorEventLogger {
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
Timber.d(jsonConverter.prettyPrint(PrintEventModel(name, event, params)))
|
||||
}
|
||||
|
||||
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
Timber.e(error, jsonConverter.prettyPrint(PrintEventModel(name, "error", params)))
|
||||
}
|
||||
}
|
||||
|
||||
private data class PrintEventModel(
|
||||
val client: String,
|
||||
val event: String,
|
||||
val params: Map<String, String>,
|
||||
)
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
|
||||
abstract class AnalyticsHandler {
|
||||
abstract fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
params: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
abstract fun triggerEvent(
|
||||
event: String,
|
||||
params: Map<String, String>
|
||||
)
|
||||
|
||||
abstract fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>? = null,
|
||||
card: Card? = null
|
||||
)
|
||||
|
||||
abstract fun logError(
|
||||
error: Throwable,
|
||||
params: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
protected fun prepareParams(card: Card?, blockchain: String? = null): Map<String, String> {
|
||||
return mapOf(
|
||||
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
|
||||
AnalyticsParam.BATCH_ID.param to card?.batchId,
|
||||
AnalyticsParam.BLOCKCHAIN.param to blockchain,
|
||||
).filterNotNull()
|
||||
}
|
||||
|
||||
fun logWcEvent(event: Analytics.WcAnalyticsEvent) {
|
||||
when (event) {
|
||||
is Analytics.WcAnalyticsEvent.Action -> {
|
||||
triggerEvent(
|
||||
event = AnalyticsEvent.WC_SUCCESS_RESPONSE,
|
||||
params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
|
||||
)
|
||||
)
|
||||
}
|
||||
is Analytics.WcAnalyticsEvent.Error -> {
|
||||
val params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action?.name,
|
||||
AnalyticsParam.ERROR_DESCRIPTION.param to event.error.message
|
||||
)
|
||||
.filterNotNull()
|
||||
logError(event.error, params)
|
||||
}
|
||||
is Analytics.WcAnalyticsEvent.InvalidRequest ->
|
||||
triggerEvent(
|
||||
event = AnalyticsEvent.WC_INVALID_REQUEST,
|
||||
params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
|
||||
).filterNotNull()
|
||||
)
|
||||
is Analytics.WcAnalyticsEvent.Session -> {
|
||||
val analyticsEvent = when (event.event) {
|
||||
Analytics.WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
|
||||
Analytics.WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
|
||||
}
|
||||
triggerEvent(
|
||||
event = analyticsEvent,
|
||||
params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
|
||||
).filterNotNull()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun logShopifyOrder(order: Storefront.Order) {
|
||||
triggerEvent(getOrderEvent(), getOrderParams(order))
|
||||
}
|
||||
|
||||
protected abstract fun getOrderEvent(): String
|
||||
|
||||
protected abstract fun getOrderParams(order: Storefront.Order): Map<String, String>
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import android.content.Context
|
||||
import com.appsflyer.AFInAppEventParameterName
|
||||
import com.appsflyer.AFInAppEventType
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
class AppsFlyerAnalyticsHandler(val context: Context): AnalyticsHandler() {
|
||||
|
||||
override fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
AppsFlyerLib.getInstance().logEvent(context, event.event, prepareParams(card, blockchain))
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>?,
|
||||
card: Card?
|
||||
) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
override fun logError(error: Throwable, params: Map<String, String>) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
override fun getOrderEvent(): String = AFInAppEventType.PURCHASE
|
||||
|
||||
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
|
||||
|
||||
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
|
||||
|
||||
val discountCode =
|
||||
(order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
|
||||
val discountParams = if (discountCode != null ){
|
||||
mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
|
||||
} else {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
AFInAppEventParameterName.CONTENT_ID to sku,
|
||||
AFInAppEventParameterName.REVENUE to order.totalPriceV2.amount,
|
||||
AFInAppEventParameterName.CURRENCY to order.currencyCode.name
|
||||
) + discountParams
|
||||
}
|
||||
|
||||
override fun triggerEvent(event: String, params: Map<String, String>) {
|
||||
AppsFlyerLib.getInstance().logEvent(context, event, params)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
object FirebaseAnalyticsHandler : AnalyticsHandler() {
|
||||
override fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
val fullParams = prepareParams(card, blockchain) + params
|
||||
Firebase.analytics.logEvent(event.event, fullParams.toBundle())
|
||||
}
|
||||
|
||||
fun logException(name: String, throwable: Throwable) {
|
||||
Firebase.analytics.logEvent(name, bundleOf(
|
||||
"message" to (throwable.message ?: "none"),
|
||||
"cause_message" to (throwable.cause?.message ?: "none"),
|
||||
"stack_trace" to throwable.stackTraceToString()
|
||||
))
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toBundle(): Bundle {
|
||||
return bundleOf(*this.toList().toTypedArray())
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>?,
|
||||
card: Card?,
|
||||
) {
|
||||
if (error is TangemSdkError.UserCancelled) return
|
||||
|
||||
val params = parameters?.toMutableMap() ?: mutableMapOf()
|
||||
if (card != null) params + prepareParams(card)
|
||||
params[AnalyticsParam.ACTION] = actionToLog.key
|
||||
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
|
||||
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
|
||||
params[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
|
||||
|
||||
params.forEach {
|
||||
FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
|
||||
}
|
||||
val cardError = TangemSdk.map(error)
|
||||
FirebaseCrashlytics.getInstance().recordException(cardError)
|
||||
}
|
||||
|
||||
override fun logError(error: Throwable, params: Map<String, String>) {
|
||||
params.forEach { (key, value) -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) }
|
||||
FirebaseCrashlytics.getInstance().recordException(error)
|
||||
}
|
||||
|
||||
override fun getOrderEvent(): String = FirebaseAnalytics.Event.PURCHASE
|
||||
|
||||
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
|
||||
|
||||
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
|
||||
|
||||
val discountCode =
|
||||
(order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
|
||||
val discountParams = if (discountCode != null ){
|
||||
mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
|
||||
} else {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
FirebaseAnalytics.Param.ITEM_ID to sku,
|
||||
FirebaseAnalytics.Param.VALUE to order.totalPriceV2.amount,
|
||||
FirebaseAnalytics.Param.CURRENCY to order.currencyCode.name
|
||||
) + discountParams
|
||||
}
|
||||
|
||||
override fun triggerEvent(event: String, params: Map<String, String>) {
|
||||
Firebase.analytics.logEvent(event, params.toBundle())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import android.app.Application
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.domain.common.AnalyticsHandlersLogConfig
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeClient
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeLogClient
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerClient
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerLogClient
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseClient
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseLogClient
|
||||
import com.tangem.tap.domain.configurable.config.Config
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GlobalAnalyticsEventHandlerBuilder(
|
||||
val application: Application,
|
||||
val config: Config,
|
||||
val isDebug: Boolean,
|
||||
val logConfig: AnalyticsHandlersLogConfig,
|
||||
val jsonConverter: MoshiJsonConverter,
|
||||
) {
|
||||
|
||||
private val analyticHandlers = mutableListOf<AnalyticsEventHandler>()
|
||||
|
||||
fun build(): GlobalAnalyticsEventHandler {
|
||||
return GlobalAnalyticsHandler(analyticHandlers.toList())
|
||||
}
|
||||
|
||||
fun default(): GlobalAnalyticsEventHandler {
|
||||
addFirebaseAnalytics()
|
||||
addAppsFlyer()
|
||||
addAmplitude()
|
||||
|
||||
return GlobalAnalyticsHandler(analyticHandlers.toList())
|
||||
}
|
||||
|
||||
fun addFirebaseAnalytics(): GlobalAnalyticsEventHandlerBuilder {
|
||||
if (isDebug) {
|
||||
if (logConfig.firebase) FirebaseLogClient(createLogger("FirebaseAnalyticsHandler")) else null
|
||||
} else {
|
||||
FirebaseClient()
|
||||
}?.let {
|
||||
analyticHandlers.add(FirebaseAnalyticsHandler(it))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAppsFlyer(): GlobalAnalyticsEventHandlerBuilder {
|
||||
if (isDebug) {
|
||||
if (logConfig.appsFlyer) AppsFlyerLogClient(createLogger("AppsFlyerAnalyticsHandler")) else null
|
||||
} else {
|
||||
AppsFlyerClient(application, config.appsFlyerDevKey)
|
||||
}?.let {
|
||||
analyticHandlers.add(AppsFlyerAnalyticsHandler(it))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAmplitude(): GlobalAnalyticsEventHandlerBuilder {
|
||||
if (isDebug) {
|
||||
if (logConfig.firebase) AmplitudeLogClient(createLogger("AmplitudeAnalyticsHandler")) else null
|
||||
} else {
|
||||
AmplitudeClient(application, config.amplitudeApiKey)
|
||||
}?.let {
|
||||
analyticHandlers.add(AmplitudeAnalyticsHandler(it))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun createLogger(name: String): AnalyticsEventsLogger {
|
||||
return AnalyticsEventsLogger(name, jsonConverter)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +1,160 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import android.content.Context
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler
|
||||
import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ErrorEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ErrorEventLogger
|
||||
import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
|
||||
AnalyticsHandler() {
|
||||
override fun triggerEvent(
|
||||
interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
|
||||
ErrorEventHandler,
|
||||
SdkErrorEventHandler,
|
||||
ShopifyOrderEventHandler {
|
||||
|
||||
fun attachToAllEvents(key: String, value: String)
|
||||
|
||||
companion object {
|
||||
fun stub(): GlobalAnalyticsEventHandler {
|
||||
return object : GlobalAnalyticsEventHandler {
|
||||
override fun attachToAllEvents(key: String, value: String) {}
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {}
|
||||
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {}
|
||||
override fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalAnalyticsHandler(
|
||||
private val analyticsHandlers: List<AnalyticsEventHandler>,
|
||||
) : GlobalAnalyticsEventHandler {
|
||||
|
||||
private val attachToAllEventsParams: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
override fun attachToAllEvents(key: String, value: String) {
|
||||
attachToAllEventsParams[key] = value
|
||||
}
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
analyticsHandlers.forEach { it.handleEvent(event, params) }
|
||||
}
|
||||
|
||||
override fun handleAnalyticsEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain, params) }
|
||||
analyticsHandlers.forEach { it.handleAnalyticsEvent(event, card, blockchain) }
|
||||
}
|
||||
|
||||
override fun triggerEvent(event: String, params: Map<String, String>) {
|
||||
analyticsHandlers.forEach { it.triggerEvent(event, params) }
|
||||
override fun handleAnalyticsEvent(
|
||||
event: AnalyticsEventAnOld,
|
||||
params: Map<String, String>,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
) {
|
||||
analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) }
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
analyticsHandlers.filterIsInstance<ErrorEventLogger>().forEach {
|
||||
it.logErrorEvent(error, params)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>?,
|
||||
card: Card?
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
analyticsHandlers.forEach { it.logCardSdkError(error, actionToLog, parameters, card) }
|
||||
analyticsHandlers.filterIsInstance<CardSdkErrorEventHandler>().forEach {
|
||||
it.handleCardSdkErrorEvent(error, action, params, card)
|
||||
}
|
||||
}
|
||||
|
||||
override fun logError(error: Throwable, params: Map<String, String>) {
|
||||
analyticsHandlers.forEach { it.logError(error, params) }
|
||||
override fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
analyticsHandlers.filterIsInstance<BlockchainSdkErrorEventHandler>().forEach {
|
||||
it.handleBlockchainSdkErrorEvent(error, action, params, card)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrderEvent(): String {
|
||||
return ""
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {
|
||||
analyticsHandlers.filterIsInstance<ShopifyOrderEventHandler>().forEach {
|
||||
it.handleShopifyOrderEvent(order)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
|
||||
return emptyMap()
|
||||
override fun prepareParams(card: Card?, blockchain: String?, params: Map<String, String>): Map<String, String> {
|
||||
return super.prepareParams(card, blockchain, params).toMutableMap().apply {
|
||||
putAll(attachToAllEventsParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createDefaultAnalyticHandlers(context: Context): GlobalAnalyticsHandler {
|
||||
return GlobalAnalyticsHandler(
|
||||
listOf(
|
||||
FirebaseAnalyticsHandler,
|
||||
AppsFlyerAnalyticsHandler(context)
|
||||
)
|
||||
fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEvent) {
|
||||
when (event) {
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.Action -> {
|
||||
handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.WC_SUCCESS_RESPONSE,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.Error -> {
|
||||
val params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action?.name,
|
||||
AnalyticsParamAnOld.ERROR_DESCRIPTION.param to event.error.message,
|
||||
).filterNotNull()
|
||||
handleErrorEvent(event.error, params)
|
||||
}
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.InvalidRequest ->
|
||||
handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.WC_INVALID_REQUEST,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_REQUEST.param to event.json,
|
||||
).filterNotNull(),
|
||||
)
|
||||
is AnalyticsAnOld.WcAnalyticsEvent.Session -> {
|
||||
val analyticsEvent = when (event.event) {
|
||||
AnalyticsAnOld.WcSessionEvent.Disconnect -> AnalyticsEventAnOld.WC_SESSION_DISCONNECTED
|
||||
AnalyticsAnOld.WcSessionEvent.Connect -> AnalyticsEventAnOld.WC_NEW_SESSION
|
||||
}
|
||||
handleAnalyticsEvent(
|
||||
event = analyticsEvent,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.WALLET_CONNECT_DAPP_URL.param to event.url,
|
||||
).filterNotNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.common.analytics.api
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface EventLogger {
|
||||
fun logEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
||||
interface ErrorEventLogger {
|
||||
fun logErrorEvent(
|
||||
error: Throwable,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.tap.common.analytics.api
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AnalyticsEventHandler {
|
||||
fun handleEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
)
|
||||
|
||||
fun handleAnalyticsEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
) {
|
||||
handleEvent(
|
||||
event = prepareEventString(event.category, event.event),
|
||||
params = prepareParams(card, blockchain, event.params),
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Migrate to AnalyticsEvent")
|
||||
fun handleAnalyticsEvent(
|
||||
event: AnalyticsEventAnOld,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
) {
|
||||
handleEvent(event.event, prepareParams(card, blockchain, params))
|
||||
}
|
||||
|
||||
fun prepareParams(
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
): Map<String, String> = mapOf(
|
||||
AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue,
|
||||
AnalyticsParam.BatchId to card?.batchId,
|
||||
AnalyticsParam.Blockchain to blockchain,
|
||||
).filterNotNull() + params
|
||||
|
||||
fun prepareEventString(category: String, event: String): String {
|
||||
return "[$category] $event"
|
||||
}
|
||||
}
|
||||
|
||||
interface ErrorEventHandler {
|
||||
fun handleErrorEvent(
|
||||
error: Throwable,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
||||
interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler
|
||||
|
||||
interface CardSdkErrorEventHandler {
|
||||
fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String> = emptyMap(),
|
||||
card: Card? = null,
|
||||
)
|
||||
}
|
||||
|
||||
interface BlockchainSdkErrorEventHandler {
|
||||
fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String> = mapOf(),
|
||||
card: Card? = null,
|
||||
)
|
||||
}
|
||||
|
||||
interface ShopifyOrderEventHandler {
|
||||
fun handleShopifyOrderEvent(order: Storefront.Order)
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.tap.common.analytics.converters
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.analytics.TangemSdk
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal data class ErrorEvent(
|
||||
val throwable: Throwable,
|
||||
val params: Map<String, String>,
|
||||
)
|
||||
|
||||
internal class CardSdkErrorConverter : Converter<CardSdkErrorConverter.Model, ErrorEvent?> {
|
||||
|
||||
override fun convert(value: Model): ErrorEvent? {
|
||||
if (value.error is TangemSdkError.UserCancelled) return null
|
||||
|
||||
val mutableParams = value.params.convertToParamValue().toMutableMap().apply {
|
||||
putAll(value.cardParams)
|
||||
}
|
||||
mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = value.error.code.toString()
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "TangemSdkError"
|
||||
|
||||
return ErrorEvent(TangemSdk.map(value.error), mutableParams)
|
||||
}
|
||||
|
||||
internal data class Model(
|
||||
val error: TangemSdkError,
|
||||
val action: AnalyticsAnOld.ActionToLog,
|
||||
val params: Map<AnalyticsParamAnOld, String>,
|
||||
val cardParams: Map<String, String>,
|
||||
)
|
||||
}
|
||||
|
||||
internal class BlockchainSdkErrorConverter(
|
||||
private val cardSdkErrorConverter: CardSdkErrorConverter,
|
||||
) : Converter<BlockchainSdkErrorConverter.Model, ErrorEvent?> {
|
||||
|
||||
override fun convert(value: Model): ErrorEvent? {
|
||||
val error = (value.error as? BlockchainSdkError) ?: return null
|
||||
|
||||
if (error is BlockchainSdkError.WrappedTangemError) {
|
||||
val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return null
|
||||
|
||||
val cardSdkModel = CardSdkErrorConverter.Model(
|
||||
error = tangemSdkError,
|
||||
action = value.action,
|
||||
params = value.params,
|
||||
cardParams = value.cardParams,
|
||||
)
|
||||
return cardSdkErrorConverter.convert(cardSdkModel)
|
||||
}
|
||||
|
||||
if (error.customMessage.contains(DemoTransactionSender.ID)) return null
|
||||
|
||||
val mutableParams = value.params.convertToParamValue().toMutableMap()
|
||||
mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = error.code.toString()
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}"
|
||||
mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "BlockchainSdkError"
|
||||
|
||||
return ErrorEvent(value.error, mutableParams)
|
||||
}
|
||||
|
||||
data class Model(
|
||||
val error: BlockchainError,
|
||||
val action: AnalyticsAnOld.ActionToLog,
|
||||
val params: Map<AnalyticsParamAnOld, String>,
|
||||
val cardParams: Map<String, String>,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun Map<AnalyticsParamAnOld, String>.convertToParamValue(): Map<String, String> {
|
||||
return mutableMapOf<String, String>().also { newMap ->
|
||||
this.forEach {
|
||||
newMap[it.key.param] = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class AnalyticsEvent(
|
||||
val category: String,
|
||||
val event: String,
|
||||
val params: Map<String, String> = mapOf(),
|
||||
)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
sealed class AnalyticsParam {
|
||||
|
||||
sealed class CurrencyType(val value: String) {
|
||||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
}
|
||||
|
||||
// Multicurrency or CurrencyType
|
||||
sealed class CardCurrency(val value: String) {
|
||||
object MultiCurrency : CardCurrency("Multicurrency")
|
||||
class SingleCurrency(type: CurrencyType) : CardCurrency(type.value)
|
||||
}
|
||||
|
||||
sealed class CardState(val value: String) {
|
||||
object Empty : CardState("Empty")
|
||||
object Full : CardState("Full")
|
||||
}
|
||||
|
||||
sealed class RateApp(val value: String) {
|
||||
object Liked : RateApp("Liked")
|
||||
object Disliked : RateApp("Disliked")
|
||||
object Closed : RateApp("Close")
|
||||
}
|
||||
|
||||
sealed class OnOffState(val value: String) {
|
||||
object On : OnOffState("On")
|
||||
object Off : OnOffState("Off")
|
||||
}
|
||||
|
||||
sealed class UserCode(val value: String) {
|
||||
object AccessCode : UserCode("Access Code")
|
||||
object Passcode : UserCode("Passcode")
|
||||
}
|
||||
|
||||
sealed class SecurityMode(val value: String) {
|
||||
object AccessCode : SecurityMode("Access Code")
|
||||
object Passcode : SecurityMode("Passcode")
|
||||
object LongTap : SecurityMode("Long Tap")
|
||||
}
|
||||
|
||||
sealed class SocialNetwork(val value: String) {
|
||||
object Facebook : SocialNetwork("Facebook")
|
||||
object Instagram : SocialNetwork("Instagram")
|
||||
object Youtube : SocialNetwork("Youtube")
|
||||
object Twitter : SocialNetwork("Twitter")
|
||||
object LinkedIn : SocialNetwork("LinkedIn")
|
||||
object GitHub : SocialNetwork("GitHub")
|
||||
}
|
||||
|
||||
companion object Key {
|
||||
const val Blockchain = "blockchain"
|
||||
const val BatchId = "batch_id"
|
||||
const val Firmware = "firmware"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Basic(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Basic", event, params) {
|
||||
|
||||
sealed class SignedIn(
|
||||
state: AnalyticsParam.CardState,
|
||||
currency: AnalyticsParam.CardCurrency,
|
||||
batch: String,
|
||||
) : Basic(
|
||||
event = "Signed in",
|
||||
params = mapOf(
|
||||
"State" to state.value,
|
||||
"Currency" to currency.value,
|
||||
"Batch" to batch,
|
||||
),
|
||||
)
|
||||
|
||||
class ToppedUp(currency: AnalyticsParam.CardCurrency) : Basic(
|
||||
event = "Topped up",
|
||||
params = mapOf("Currency" to currency.value),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Chat(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Chat", event, params) {
|
||||
|
||||
class ScreenOpened : Chat("Chat Screen Opened")
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class DetailsScreen(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Details Screen", event, params) {
|
||||
|
||||
class ScreenOpened : DetailsScreen("Details Screen Opened")
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
class ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
class ButtonScanCard : IntroductionProcess("Button - Scan Card")
|
||||
class CardWasScanned : IntroductionProcess("Card Was Scanned")
|
||||
class ButtonRequestSupport : IntroductionProcess("Button - Request Support")
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class MainScreen(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Main Screen", event, params) {
|
||||
|
||||
class ScreenOpened : MainScreen("Screen opened")
|
||||
|
||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||
class CardWasScanned : MainScreen("Card Was Scanned")
|
||||
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
||||
|
||||
class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen(
|
||||
event = "Main Currency Changed",
|
||||
params = mapOf("Currency Type" to currencyType.value),
|
||||
)
|
||||
|
||||
sealed class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
|
||||
event = "Notice - Rate The App Button Tapped",
|
||||
params = mapOf("Result" to result.value),
|
||||
)
|
||||
|
||||
class NoticeBackupYourWalletTapped : MainScreen("Notice - Backup Your Wallet Tapped")
|
||||
class NoticeScanYourCardTapped : MainScreen("Notice - Scan Your Card Tapped")
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class ManageTokens(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Manage Tokens", event, params) {
|
||||
|
||||
class ScreenOpened : ManageTokens("Manage Tokens Screen Opened")
|
||||
class TokenSearched : ManageTokens("Token Searched")
|
||||
class TokenSwitcherChanged : ManageTokens("Token Switcher Changed")
|
||||
class ButtonSaveChanges : ManageTokens("Button - Save Changes")
|
||||
class ButtonCustomToken : ManageTokens("Button - Custom Token")
|
||||
|
||||
sealed class CustomToken(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : ManageTokens(event, params) {
|
||||
|
||||
class ScreenOpened : ManageTokens("Custom Token Screen Opened")
|
||||
|
||||
class TokenWasAdded(symbol: String, network: String, address: String) : ManageTokens(
|
||||
event = "Custom Token Was Added",
|
||||
params = mapOf(
|
||||
"Symbol" to symbol,
|
||||
"Network" to network,
|
||||
"Address" to address,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Onboarding(
|
||||
category: String,
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
class Started : Onboarding("Onboarding", "Onboarding Started")
|
||||
class Finished : Onboarding("Onboarding", "Onboarding Finished")
|
||||
|
||||
sealed class CreateWallet(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Onboarding("Onboarding / Create Wallet", event, params) {
|
||||
|
||||
class ScreenOpened : CreateWallet("Create Wallet Screen Opened")
|
||||
class ButtonCreateWallet : CreateWallet("Button - Create Wallet")
|
||||
class WalletCreatedSuccessfully : CreateWallet("Wallet Created Successfully")
|
||||
}
|
||||
|
||||
sealed class Backup(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Onboarding("Onboarding / Backup", event, params) {
|
||||
|
||||
class ScreenOpened : Backup("Backup Screen Opened")
|
||||
class Started : Backup("Backup Started")
|
||||
class Skipped : Backup("Backup Skipped")
|
||||
class SettingAccessCodeStarted : Backup("Setting Access Code Started")
|
||||
class AccessCodeEntered : Backup("Access Code Entered")
|
||||
class AccessCodeReEntered : Backup("Access Code Re-entered")
|
||||
|
||||
class Finished(cardsCount: String) : Backup(
|
||||
event = "Backup Finished",
|
||||
params = mapOf("Cards count" to cardsCount),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Topup(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Onboarding("Onboarding / Top Up", event, params) {
|
||||
|
||||
class ScreenOpened : Topup("Activation Screen Opened")
|
||||
|
||||
class ButtonBuyCrypto(currency: AnalyticsParam.CurrencyType) : Topup(
|
||||
event = "Button - Buy Crypto",
|
||||
params = mapOf("Currency" to currency.value),
|
||||
)
|
||||
|
||||
class ButtonShowWalletAddress : Topup("Button - Show the Wallet Address")
|
||||
}
|
||||
|
||||
sealed class Twins(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Onboarding("Onboarding / Twins", event, params) {
|
||||
|
||||
class ScreenOpened : Twins("Twinning Screen Opened")
|
||||
class SetupStarted : Twins("Twin Setup Started")
|
||||
class SetupFinished : Twins("Twin Setup Finished")
|
||||
}
|
||||
|
||||
class PinCodeSet: Onboarding("Onboarding", "PIN code set")
|
||||
class ButtonConnect: Onboarding("Onboarding", "Button - Connect")
|
||||
class KYCStarted: Onboarding("Onboarding", "KYC started")
|
||||
class KYCInProgress: Onboarding("Onboarding", "KYC in progress")
|
||||
class KYCRejected: Onboarding("Onboarding", "KYC rejected")
|
||||
class ClaimScreenOpened: Onboarding("Onboarding", "Claim screen opened")
|
||||
class ButtonClaim: Onboarding("Onboarding", "Button - Claim")
|
||||
class ClaimWasSuccessfully: Onboarding("Onboarding", "Claim was successfully")
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
sealed class Portfolio(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Portfolio", event, params) {
|
||||
|
||||
class Refreshed : Portfolio("Refreshed")
|
||||
class ButtonManageTokens : Portfolio("Button - Manage Tokens")
|
||||
class TokenTapped : Portfolio("Token is Tapped")
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Settings(
|
||||
category: String = "Settings",
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
class ScreenOpened : Settings(event = "Settings Screen Opened")
|
||||
class ButtonChat : Settings(event = "Button - Chat")
|
||||
class ButtonSendFeedback : Settings(event = "Button - Send Feedback")
|
||||
class ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session")
|
||||
class ButtonStopWalletConnectSession : Settings(event = "Button - Stop Wallet Connect Session")
|
||||
class ButtonCardSettings : Settings(event = "Button - Card Settings")
|
||||
class ButtonAppSettings : Settings(event = "Button - App Settings")
|
||||
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
|
||||
|
||||
sealed class ButtonSocialNetwork(network: AnalyticsParam.SocialNetwork) : Settings(
|
||||
event = "Button - Social Network",
|
||||
params = mapOf("Network" to network.value),
|
||||
)
|
||||
|
||||
sealed class CardSettings(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Settings("Settings / Card Settings", event, params) {
|
||||
|
||||
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
|
||||
class FactoryResetFinished : CardSettings("Factory Reset Finished")
|
||||
class UserCodeChanged : CardSettings("User Code Changed")
|
||||
class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode")
|
||||
|
||||
sealed class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings(
|
||||
event = "Button - Change User Code",
|
||||
params = mapOf("Type" to type.value),
|
||||
)
|
||||
|
||||
sealed class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings(
|
||||
event = "Security Mode Changed",
|
||||
params = mapOf("Mode" to mode.value),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class AppSettings(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Settings("Settings / App Settings", event, params) {
|
||||
|
||||
sealed class FaceIDSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
|
||||
event = "Face ID Switcher Changed",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
sealed class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
|
||||
event = "Save Access Code Switcher Changed",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
class ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
class ScreenOpened : IntroductionProcess("Shop Screen Opened")
|
||||
|
||||
class Purchased(productSku: String) : Shop(
|
||||
event = "Purchased",
|
||||
params = mapOf("SKU" to productSku),
|
||||
)
|
||||
|
||||
class Redirected(partnerName: String) : Shop(
|
||||
event = "Redirected",
|
||||
params = mapOf("Partner" to partnerName),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Token(
|
||||
category: String,
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
class Refreshed : Token("Token", "Refreshed")
|
||||
class ButtonRemoveToken : Token("Token", "Button - Remove Token")
|
||||
class ButtonExplore : Token("Token", "Button - Explore")
|
||||
class ButtonBuy(token: String) : Token("Token", "Button - Buy", mapOf("Token" to token))
|
||||
class ButtonSell(token: String) : Token("Token", "Button - Sell", mapOf("Token" to token))
|
||||
class ButtonExchange(token: String) : Token("Token", "Button - Exchange", mapOf("Token" to token))
|
||||
class ButtonSend(token: String) : Token("Token", "Button - Send", mapOf("Token" to token))
|
||||
|
||||
sealed class Recieve(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Token("Token / Recieve", event, params) {
|
||||
|
||||
class ScreenOpened : Recieve("Recieve Screen Opened")
|
||||
class ButtonCopyAddress : Recieve("Button - Copy Address")
|
||||
class ButtonShareAddress : Recieve("Button - Share Address")
|
||||
}
|
||||
|
||||
sealed class Send(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Token("Token / Send", event, params) {
|
||||
|
||||
class ScreenOpened : Send("Send Screen Opened")
|
||||
class ButtonPaste : Send("Button - Paste")
|
||||
class ButtonQRCode : Send("Button - QR Code")
|
||||
class ButtonSwapCurrency : Send("Button - Swap Currency")
|
||||
|
||||
class TransactionSent(token: AnalyticsParam.CurrencyType) : Send(
|
||||
event = "Transaction Sent",
|
||||
params = mapOf("Token" to token.value),
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Topup(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Token("Token / Topup", event, params) {
|
||||
|
||||
class ScreenOpened : Topup("Top Up Screen Opened")
|
||||
class P2PScreenOpened : Topup("P2P Screen Opened")
|
||||
}
|
||||
|
||||
sealed class Withdraw(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Token("Token / Withdraw", event, params) {
|
||||
|
||||
class ScreenOpened : Withdraw("Withdraw Screen Opened")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class WalletConnect(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Wallet Connect", event, params) {
|
||||
|
||||
class NewSessionEstablished : WalletConnect("New Session Established")
|
||||
class SessionDisconnected : WalletConnect("Session Disconnected")
|
||||
class RequestSigned : WalletConnect("Request Signed")
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
|
||||
class AmplitudeAnalyticsHandler(
|
||||
private val client: AmplitudeAnalyticsClient,
|
||||
) : AnalyticsEventHandler {
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, params)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||
|
||||
import android.app.Application
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.AmplitudeClient
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.tap.common.analytics.api.EventLogger
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AmplitudeAnalyticsClient : EventLogger
|
||||
|
||||
internal class AmplitudeClient(
|
||||
application: Application,
|
||||
key: String,
|
||||
) : AmplitudeAnalyticsClient {
|
||||
|
||||
private val client: AmplitudeClient = Amplitude.getInstance()
|
||||
|
||||
init {
|
||||
client.initialize(application, key)
|
||||
client.enableForegroundTracking(application)
|
||||
}
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
|
||||
}
|
||||
}
|
||||
|
||||
private class ParamsToJSONObjectConverter : Converter<Map<String, String>, JSONObject> {
|
||||
override fun convert(value: Map<String, String>): JSONObject = JSONObject().apply {
|
||||
value.forEach { this.put(it.key, it.value) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AmplitudeLogClient(
|
||||
private val logger: AnalyticsEventsLogger,
|
||||
) : AmplitudeAnalyticsClient {
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.tap.common.analytics.handlers.appsFlyer
|
||||
|
||||
import com.appsflyer.AFInAppEventParameterName
|
||||
import com.appsflyer.AFInAppEventType
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
|
||||
|
||||
class AppsFlyerAnalyticsHandler(
|
||||
private val client: AppsFlyerAnalyticsClient,
|
||||
) : AnalyticsEventHandler, ShopifyOrderEventHandler {
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, params)
|
||||
}
|
||||
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {
|
||||
handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ORDER_EVENT = AFInAppEventType.PURCHASE
|
||||
}
|
||||
}
|
||||
|
||||
private class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
|
||||
|
||||
override fun convert(value: Storefront.Order): Map<String, String> {
|
||||
val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
|
||||
|
||||
val discountCode =
|
||||
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
|
||||
val discountParams = if (discountCode != null) {
|
||||
mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
|
||||
} else {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
AFInAppEventParameterName.CONTENT_ID to sku,
|
||||
AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount,
|
||||
AFInAppEventParameterName.CURRENCY to value.currencyCode.name,
|
||||
) + discountParams
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.common.analytics.handlers.appsFlyer
|
||||
|
||||
import android.content.Context
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.tangem.tap.common.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AppsFlyerAnalyticsClient : EventLogger
|
||||
|
||||
internal class AppsFlyerClient(
|
||||
private val context: Context,
|
||||
key: String,
|
||||
) : AppsFlyerAnalyticsClient {
|
||||
|
||||
private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance()
|
||||
|
||||
init {
|
||||
appsFlyerLib.init(key, null, context)
|
||||
appsFlyerLib.start(context)
|
||||
}
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
appsFlyerLib.logEvent(context, event, params)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.common.analytics.handlers.appsFlyer
|
||||
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AppsFlyerLogClient(
|
||||
private val logger: AnalyticsEventsLogger,
|
||||
) : AppsFlyerAnalyticsClient {
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.tap.common.analytics.handlers.firebase
|
||||
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ErrorEventHandler
|
||||
import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
|
||||
import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
|
||||
import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter
|
||||
import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter
|
||||
|
||||
class FirebaseAnalyticsHandler(
|
||||
private val client: FirebaseAnalyticsClient,
|
||||
) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler {
|
||||
|
||||
override fun handleEvent(event: String, params: Map<String, String>) {
|
||||
client.logEvent(event, params)
|
||||
}
|
||||
|
||||
override fun handleErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
client.logErrorEvent(error, params)
|
||||
}
|
||||
|
||||
override fun handleCardSdkErrorEvent(
|
||||
error: TangemSdkError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
|
||||
val converter = CardSdkErrorConverter()
|
||||
converter.convert(model)?.let {
|
||||
handleErrorEvent(it.throwable, it.params)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleBlockchainSdkErrorEvent(
|
||||
error: BlockchainError,
|
||||
action: AnalyticsAnOld.ActionToLog,
|
||||
params: Map<AnalyticsParamAnOld, String>,
|
||||
card: Card?,
|
||||
) {
|
||||
|
||||
val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card))
|
||||
val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter())
|
||||
converter.convert(model)?.let {
|
||||
handleErrorEvent(it.throwable, it.params)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleShopifyOrderEvent(order: Storefront.Order) {
|
||||
handleEvent(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order))
|
||||
}
|
||||
}
|
||||
|
||||
private class OrderToParamsConverter : Converter<Storefront.Order, Map<String, String>> {
|
||||
override fun convert(value: Storefront.Order): Map<String, String> {
|
||||
val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
|
||||
|
||||
val discountCode =
|
||||
(value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
|
||||
val discountParams = if (discountCode != null) {
|
||||
mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
|
||||
} else {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
FirebaseAnalytics.Param.ITEM_ID to sku,
|
||||
FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount,
|
||||
FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name,
|
||||
) + discountParams
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.tap.common.analytics.handlers.firebase
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.tap.common.analytics.api.ErrorEventLogger
|
||||
import com.tangem.tap.common.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface FirebaseAnalyticsClient : EventLogger, ErrorEventLogger
|
||||
|
||||
internal class FirebaseClient : FirebaseAnalyticsClient {
|
||||
|
||||
private val fbAnalytics = Firebase.analytics
|
||||
private val fbCrashlytics = Firebase.crashlytics
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
fbAnalytics.logEvent(event, params.toBundle())
|
||||
}
|
||||
|
||||
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
params.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
|
||||
fbCrashlytics.recordException(error)
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
|
||||
|
||||
companion object {
|
||||
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.common.analytics.handlers.firebase
|
||||
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class FirebaseLogClient(
|
||||
private val logger: AnalyticsEventsLogger,
|
||||
) : FirebaseAnalyticsClient {
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
logger.logEvent(event, params)
|
||||
}
|
||||
|
||||
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
logger.logErrorEvent(error, params)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.common.BlockchainError
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun AnalyticsHandler.logSendTransactionError(
|
||||
error: BlockchainError,
|
||||
action: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>? = mapOf(),
|
||||
card: Card? = null,
|
||||
) {
|
||||
when (val blockchainSdkError = (error as BlockchainSdkError)) {
|
||||
is BlockchainSdkError.WrappedTangemError -> {
|
||||
val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return
|
||||
|
||||
logCardSdkError(
|
||||
error = tangemSdkError,
|
||||
actionToLog = action,
|
||||
parameters = parameters,
|
||||
card = card,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
when {
|
||||
blockchainSdkError.customMessage.contains(DemoTransactionSender.ID) -> return
|
||||
else -> {
|
||||
val params = parameters?.toMutableMap() ?: mutableMapOf()
|
||||
params[AnalyticsParam.ACTION] = action.key
|
||||
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
|
||||
params[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}"
|
||||
params[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError"
|
||||
|
||||
FirebaseCrashlytics.getInstance().apply {
|
||||
params.forEach { setCustomKey(it.key.param, it.value) }
|
||||
recordException(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.operations.backup.BackupService
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun BackupService.primaryCardIsSaltPayVisa(): Boolean {
|
||||
return primaryCardId?.slice(0..3)?.let {
|
||||
SaltPayWorkaround.isVisaBatchId(it)
|
||||
} ?: false
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -44,8 +44,13 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
|||
if (exception is BlockchainSdkError.AccountNotFound && amountToCreateAccount != null) {
|
||||
Result.Failure(TapError.WalletManager.NoAccountError(amountToCreateAccount.toString()))
|
||||
} else {
|
||||
val message = exception.localizedMessage ?: "An error has occurred. Try later"
|
||||
Result.Failure(TapError.WalletManager.InternalError(message))
|
||||
when (exception) {
|
||||
is BlockchainSdkError -> Result.Failure(exception)
|
||||
else -> {
|
||||
val message = exception.cause?.localizedMessage ?: "Unknown error"
|
||||
Result.Failure(TapError.WalletManager.InternalError(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +77,7 @@ fun WalletManager?.getAddressData(): AddressData? {
|
|||
else addressDataList[0]
|
||||
}
|
||||
|
||||
fun<T> WalletManager.Companion.stub(): T {
|
||||
fun <T> WalletManager.Companion.stub(): T {
|
||||
val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null, null), setOf())
|
||||
return object : WalletManager(wallet) {
|
||||
override val currentHost: String = ""
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.webkit.WebView
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun WebView.configureSettings() {
|
||||
resumeTimers()
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
|
|
@ -15,5 +16,4 @@ fun WebView.configureSettings() {
|
|||
fun WebView.stop() {
|
||||
stopLoading()
|
||||
pauseTimers()
|
||||
destroy()
|
||||
}
|
||||
|
|
@ -12,19 +12,22 @@ 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()
|
||||
|
||||
data class AddressInfoDialog(
|
||||
val currency: Currency,
|
||||
val addressData: AddressData,
|
||||
) : AppDialog()
|
||||
|
||||
data class TestActionsDialog(
|
||||
val actionsList: List<TestAction>
|
||||
val actionsList: List<TestAction>,
|
||||
) : AppDialog()
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchain.common.WalletManager
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.feedback.FeedbackData
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
|
|
@ -38,8 +38,20 @@ sealed class GlobalAction : Action {
|
|||
object HideDialog : GlobalAction()
|
||||
|
||||
sealed class Onboarding : GlobalAction() {
|
||||
/**
|
||||
* Initiate an onboarding process.
|
||||
* For SaltPay cards it's additionally checks for unfinished backup.
|
||||
* For resuming unfinished backup for standard Wallet cards see CheckForUnfinishedBackup and
|
||||
* StartForUnfinishedBackup
|
||||
*/
|
||||
data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding()
|
||||
data class StartForUnfinishedBackup(val isSaltPayBackup: Boolean) : Onboarding()
|
||||
|
||||
/**
|
||||
* Initiate resuming of unfinished backup only for standard Wallet cards.
|
||||
* For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start.
|
||||
* See more Onboarding.Start, CheckForUnfinishedBackup
|
||||
*/
|
||||
object StartForUnfinishedBackup : Onboarding()
|
||||
object Stop : Onboarding()
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +89,7 @@ sealed class GlobalAction : Action {
|
|||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
|
||||
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
|
||||
data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction()
|
||||
data class SetGlobalAnalyticsHandler(val analyticsHandler: GlobalAnalyticsEventHandler) : GlobalAction()
|
||||
|
||||
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
|
||||
data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : GlobalAction()
|
||||
|
|
|
|||
|
|
@ -140,7 +140,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
secretKey = moonPaySecretKey,
|
||||
logEnabled = LogConfig.network.moonPayService,
|
||||
)
|
||||
val cardProvider = { store.state.globalState.scanResponse?.card }
|
||||
val cardProvider = {
|
||||
store.state.globalState.scanResponse?.card
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse?.card
|
||||
}
|
||||
|
||||
val exchangeManager = CurrencyExchangeManager(
|
||||
buyService = buyService,
|
||||
|
|
@ -163,7 +166,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
is GlobalAction.ScanCard -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
store.state.globalState.analyticsHandlers,
|
||||
store.state.globalState.analyticsHandler,
|
||||
userTokensRepository,
|
||||
action.additionalBlockchainsToDerive,
|
||||
action.messageResId,
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState.copy(configManager = action.configManager)
|
||||
}
|
||||
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
|
||||
is GlobalAction.SetAnanlyticHandlers ->
|
||||
globalState.copy(analyticsHandlers = action.analyticsHandlers)
|
||||
is GlobalAction.SetGlobalAnalyticsHandler -> globalState.copy(analyticsHandler = action.analyticsHandler)
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanResponse?.card ?: return globalState
|
||||
val wallet = card.wallet(action.walletPublicKey) ?: return globalState
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
|
|
@ -27,7 +27,7 @@ data class GlobalState(
|
|||
val dialog: StateDialog? = null,
|
||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||
val resources: AndroidResources = AndroidResources(),
|
||||
val analyticsHandlers: AnalyticsHandler? = null,
|
||||
val analyticsHandler: GlobalAnalyticsEventHandler = GlobalAnalyticsEventHandler.stub(),
|
||||
val userCountryCode: String? = null,
|
||||
) : StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import android.app.Application
|
|||
import android.content.Intent
|
||||
import com.google.android.gms.wallet.PaymentData
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
import com.tangem.tap.common.shop.data.ProductType
|
||||
import com.tangem.tap.common.shop.data.TangemProduct
|
||||
|
|
@ -195,15 +196,10 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
return checkouts[productType]!!.webUrl
|
||||
}
|
||||
|
||||
suspend fun waitForCheckout(
|
||||
productType: ProductType,
|
||||
analyticsHandler: AnalyticsHandler?
|
||||
) {
|
||||
suspend fun waitForCheckout(productType: ProductType, analyticsHandler: GlobalAnalyticsEventHandler?) {
|
||||
val result = shopifyService.checkout(true, checkouts[productType]!!.id)
|
||||
result.onSuccess {
|
||||
if (it.order != null) {
|
||||
analyticsHandler?.logShopifyOrder(order = it.order)
|
||||
}
|
||||
ifNotNull(analyticsHandler, it.order) { handler, order -> handler.handleShopifyOrderEvent(order) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ open class IndeterminateProgressButtonWidget(
|
|||
if (initialState != ProgressState.Done) changeState(initialState)
|
||||
}
|
||||
|
||||
var isEnabled: Boolean
|
||||
get() = button.isEnabled
|
||||
set(value) {
|
||||
button.isEnabled = value
|
||||
}
|
||||
|
||||
override val mainView: View = button
|
||||
|
||||
override fun changeState(state: WidgetState) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ package com.tangem.tap.common.toggleWidget
|
|||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.*
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.AlphaAnimation
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationSet
|
||||
import android.view.animation.AnticipateOvershootInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.view.animation.RotateAnimation
|
||||
import android.view.animation.ScaleAnimation
|
||||
import android.widget.ViewSwitcher
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -11,7 +18,7 @@ import com.tangem.wallet.R
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class RefreshBalanceWidget(
|
||||
private val root: ViewGroup
|
||||
private val root: ViewGroup,
|
||||
) : ViewStateWidget {
|
||||
|
||||
var isShowing: Boolean? = null
|
||||
|
|
@ -19,7 +26,6 @@ class RefreshBalanceWidget(
|
|||
|
||||
override val mainView: View = root.findViewById(R.id.btn_refresh_balance)
|
||||
|
||||
|
||||
private val viewSwitcher: ViewSwitcher by lazy { mainView.findViewById(R.id.switcher_refresh_arrow_to_progress) }
|
||||
|
||||
private val arrowView = mainView.findViewById<View>(R.id.imv_arrow_refresh)
|
||||
|
|
@ -46,6 +52,7 @@ class RefreshBalanceWidget(
|
|||
currentStateByView == ProgressState.Done -> if (progressState == ProgressState.Error) return
|
||||
}
|
||||
|
||||
mainView.isClickable = currentStateByView == ProgressState.Done || currentStateByView == ProgressState.Error
|
||||
animateState(progressState)
|
||||
viewSwitcher.showNext()
|
||||
}
|
||||
|
|
@ -60,7 +67,7 @@ class RefreshBalanceWidget(
|
|||
progressViewAnimation = RotateAnimation(
|
||||
0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
)
|
||||
progressViewAnimation?.duration = 700
|
||||
progressViewAnimation?.interpolator = AccelerateInterpolator()
|
||||
|
|
@ -88,11 +95,13 @@ class RefreshBalanceWidget(
|
|||
|
||||
class ShowAnimation : AnimationSet(true) {
|
||||
init {
|
||||
addAnimation(ScaleAnimation(
|
||||
0f, 1f,
|
||||
0f, 1f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f)
|
||||
addAnimation(
|
||||
ScaleAnimation(
|
||||
0f, 1f,
|
||||
0f, 1f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
),
|
||||
)
|
||||
addAnimation(AlphaAnimation(0f, 1f))
|
||||
duration = 300
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
|||
import com.tangem.operations.pins.CheckUserCodesCommand
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
|
|
@ -45,12 +45,12 @@ import kotlin.coroutines.suspendCoroutine
|
|||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
suspend fun scanProduct(
|
||||
analyticsHandler: AnalyticsHandler?,
|
||||
analyticsHandler: GlobalAnalyticsEventHandler?,
|
||||
userTokensRepository: UserTokensRepository,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanResponse> {
|
||||
analyticsHandler?.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.READY_TO_SCAN, card = null)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(
|
||||
|
|
@ -70,30 +70,30 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
}
|
||||
|
||||
private fun sendScanResultsToAnalytics(
|
||||
analyticsHandler: AnalyticsHandler?,
|
||||
analyticsHandler: GlobalAnalyticsEventHandler?,
|
||||
result: CompletionResult<ScanResponse>,
|
||||
) {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
analyticsHandler?.triggerEvent(
|
||||
event = AnalyticsEvent.CARD_IS_SCANNED,
|
||||
analyticsHandler?.handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.CARD_IS_SCANNED,
|
||||
card = result.data.card,
|
||||
blockchain = result.data.walletData?.blockchain,
|
||||
)
|
||||
if (DemoHelper.isDemoCard(result.data)) {
|
||||
analyticsHandler?.triggerEvent(
|
||||
event = AnalyticsEvent.DEMO_MODE_ACTIVATED,
|
||||
analyticsHandler?.handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.DEMO_MODE_ACTIVATED,
|
||||
params = mapOf(AnalyticsParamAnOld.CARD_ID.param to result.data.card.cardId),
|
||||
card = result.data.card,
|
||||
blockchain = result.data.walletData?.blockchain,
|
||||
params = mapOf(AnalyticsParam.CARD_ID.param to result.data.card.cardId),
|
||||
)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
analyticsHandler?.logCardSdkError(
|
||||
analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error = error,
|
||||
actionToLog = Analytics.ActionToLog.Scan,
|
||||
action = AnalyticsAnOld.ActionToLog.Scan,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
|
||||
suspend fun createWallet(cardId: String?): CompletionResult<Card> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
CreateWalletAndRescanTask(), cardId,
|
||||
CreateWalletAndRescanTask(),
|
||||
cardId,
|
||||
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class TangemSigner(
|
|||
private val card: Card,
|
||||
private val tangemSdk: TangemSdk,
|
||||
private val initialMessage: Message,
|
||||
private val accessCode: String? = null,
|
||||
private val signerCallback: (TangemSignerResponse) -> Unit,
|
||||
) : TransactionSigner {
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ class TangemSigner(
|
|||
runnable = task,
|
||||
cardId = cardId,
|
||||
initialMessage = initialMessage,
|
||||
accessCode = accessCode,
|
||||
) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ data class Config(
|
|||
val mercuryoWidgetId: String = "",
|
||||
val mercuryoSecret: String = "",
|
||||
val appsFlyerDevKey: String = "",
|
||||
val amplitudeApiKey: String = "",
|
||||
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
val isTopUpEnabled: Boolean = false,
|
||||
|
|
@ -90,6 +91,7 @@ class ConfigManager {
|
|||
tronGridApiKey = values.tronGridApiKey
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
amplitudeApiKey = values.amplitudeApiKey,
|
||||
shopify = values.shopifyShop,
|
||||
zendesk = values.zendesk,
|
||||
saltPayConfig = values.saltPay,
|
||||
|
|
@ -107,6 +109,7 @@ class ConfigManager {
|
|||
infuraProjectId = values.infuraProjectId
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
amplitudeApiKey = values.amplitudeApiKey,
|
||||
shopify = values.shopifyShop,
|
||||
zendesk = values.zendesk,
|
||||
saltPayConfig = values.saltPay,
|
||||
|
|
|
|||
|
|
@ -30,9 +30,13 @@ class ConfigValueModel(
|
|||
val zendesk: ZendeskConfig?,
|
||||
val saltPay: SaltPayConfig,
|
||||
val tronGridApiKey: String,
|
||||
val amplitudeApiKey: String,
|
||||
)
|
||||
|
||||
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||
class ConfigModel(
|
||||
val features: FeatureModel?,
|
||||
val configValues: ConfigValueModel?,
|
||||
) {
|
||||
companion object {
|
||||
fun empty(): ConfigModel = ConfigModel(null, null)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package com.tangem.tap.domain.moduleMessage.saltPay
|
|||
import android.content.Context
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.tap.domain.moduleMessage.ConvertedMessage
|
||||
import com.tangem.tap.domain.moduleMessage.saltPay.converter.SaltPayRegistrationErrorConverter
|
||||
import com.tangem.tap.domain.moduleMessage.saltPay.converter.SaltPayActivationErrorConverter
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleMessage
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -28,7 +28,7 @@ class SaltPayErrorConverter(
|
|||
) : ModuleMessageConverter<SaltPayModuleError, ConvertedMessage?> {
|
||||
|
||||
override fun convert(message: SaltPayModuleError): ConvertedMessage? = when (message) {
|
||||
is SaltPayRegistrationError -> SaltPayRegistrationErrorConverter(context).convert(message)
|
||||
is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,40 +3,40 @@ package com.tangem.tap.domain.moduleMessage.saltPay.converter
|
|||
import android.content.Context
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.tap.domain.moduleMessage.ConvertedDialogMessage
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SaltPayRegistrationErrorConverter(
|
||||
internal class SaltPayActivationErrorConverter(
|
||||
private val context: Context,
|
||||
) : ModuleMessageConverter<SaltPayRegistrationError, ConvertedDialogMessage?> {
|
||||
) : ModuleMessageConverter<SaltPayActivationError, ConvertedDialogMessage?> {
|
||||
|
||||
override fun convert(message: SaltPayRegistrationError): ConvertedDialogMessage? {
|
||||
val saltPayError = (message as? SaltPayRegistrationError) ?: throw UnsupportedOperationException()
|
||||
override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? {
|
||||
val saltPayError = (message as? SaltPayActivationError) ?: throw UnsupportedOperationException()
|
||||
|
||||
val dialogMessage = when (saltPayError) {
|
||||
SaltPayRegistrationError.NoGas -> {
|
||||
SaltPayActivationError.NoGas -> {
|
||||
ConvertedDialogMessage(
|
||||
title = context.getString(R.string.saltpay_error_no_gas_title),
|
||||
message = context.getString(R.string.saltpay_error_no_gas_message),
|
||||
)
|
||||
}
|
||||
SaltPayRegistrationError.EmptyBackupCardScanned -> {
|
||||
SaltPayActivationError.EmptyBackupCardScanned -> {
|
||||
ConvertedDialogMessage(
|
||||
title = context.getString(R.string.saltpay_error_empty_backup_title),
|
||||
message = context.getString(R.string.saltpay_error_empty_backup_message),
|
||||
)
|
||||
}
|
||||
SaltPayRegistrationError.WeakPin -> {
|
||||
SaltPayActivationError.WeakPin -> {
|
||||
ConvertedDialogMessage(
|
||||
title = context.getString(R.string.saltpay_error_pin_weak_title),
|
||||
message = context.getString(R.string.saltpay_error_pin_weak_message),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val errorMessage = if (saltPayError.message == SaltPayRegistrationError.EMPTY_MESSAGE) {
|
||||
val errorMessage = if (saltPayError.message == SaltPayActivationError.EMPTY_MESSAGE) {
|
||||
saltPayError::class.java.simpleName
|
||||
} else {
|
||||
saltPayError.message
|
||||
|
|
@ -14,14 +14,14 @@ import com.tangem.domain.common.ScanResponse
|
|||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.common.AssetReader
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
||||
class TwinCardsManager(
|
||||
private val card: Card,
|
||||
assetReader: AssetReader,
|
||||
val analyticsHandler: AnalyticsHandler?
|
||||
val analyticsHandler: GlobalAnalyticsEventHandler?
|
||||
) {
|
||||
|
||||
private val currentCardId: String = card.cardId
|
||||
|
|
@ -37,9 +37,9 @@ class TwinCardsManager(
|
|||
is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString()
|
||||
is CompletionResult.Failure -> {
|
||||
(response.error as? TangemSdkError)?.let { error ->
|
||||
analyticsHandler?.logCardSdkError(
|
||||
analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.CreateWallet,
|
||||
AnalyticsAnOld.ActionToLog.CreateWallet,
|
||||
card = card
|
||||
)
|
||||
}
|
||||
|
|
@ -67,9 +67,9 @@ class TwinCardsManager(
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(response.error as? TangemSdkError)?.let { error ->
|
||||
analyticsHandler?.logCardSdkError(
|
||||
analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.CreateWallet,
|
||||
AnalyticsAnOld.ActionToLog.CreateWallet,
|
||||
card = card
|
||||
)
|
||||
}
|
||||
|
|
@ -87,9 +87,9 @@ class TwinCardsManager(
|
|||
is CompletionResult.Success -> Result.Success(response.data)
|
||||
is CompletionResult.Failure -> {
|
||||
(response.error as? TangemSdkError)?.let { error ->
|
||||
analyticsHandler?.logCardSdkError(
|
||||
analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.WriteIssuerData,
|
||||
AnalyticsAnOld.ActionToLog.WriteIssuerData,
|
||||
card = card
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.logWcEvent
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -222,9 +223,9 @@ class WalletConnectManager {
|
|||
}
|
||||
|
||||
private fun onSessionClosed(session: WCSession) {
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Session(
|
||||
Analytics.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url
|
||||
store.state.globalState.analyticsHandler?.logWcEvent(
|
||||
AnalyticsAnOld.WcAnalyticsEvent.Session(
|
||||
AnalyticsAnOld.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url
|
||||
)
|
||||
)
|
||||
sessions.remove(session)
|
||||
|
|
@ -371,9 +372,9 @@ class WalletConnectManager {
|
|||
),
|
||||
)
|
||||
}
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Session(
|
||||
Analytics.WcSessionEvent.Connect, peer.url,
|
||||
store.state.globalState.analyticsHandler?.logWcEvent(
|
||||
AnalyticsAnOld.WcAnalyticsEvent.Session(
|
||||
AnalyticsAnOld.WcSessionEvent.Connect, peer.url,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -386,9 +387,9 @@ class WalletConnectManager {
|
|||
}
|
||||
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
|
||||
Timber.d("onEthSendTransaction: $transaction")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.SendTransaction
|
||||
store.state.globalState.analyticsHandler?.logWcEvent(
|
||||
AnalyticsAnOld.WcAnalyticsEvent.Action(
|
||||
AnalyticsAnOld.WcAction.SendTransaction
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
|
|
@ -404,9 +405,9 @@ class WalletConnectManager {
|
|||
}
|
||||
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
|
||||
Timber.d("onEthSignTransaction: $transaction")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.SignTransaction
|
||||
store.state.globalState.analyticsHandler?.logWcEvent(
|
||||
AnalyticsAnOld.WcAnalyticsEvent.Action(
|
||||
AnalyticsAnOld.WcAction.SignTransaction
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
|
|
@ -422,9 +423,9 @@ class WalletConnectManager {
|
|||
}
|
||||
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
|
||||
Timber.d("onEthSign: $message")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.PersonalSign
|
||||
store.state.globalState.analyticsHandler?.logWcEvent(
|
||||
AnalyticsAnOld.WcAnalyticsEvent.Action(
|
||||
AnalyticsAnOld.WcAction.PersonalSign
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
|
|
@ -498,9 +499,9 @@ class WalletConnectManager {
|
|||
val message = EthSignHelper.tryToParseEthTypedMessage(request)
|
||||
if (message != null) {
|
||||
Timber.d("onEthSign_v4: $message")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.PersonalSign
|
||||
store.state.globalState.analyticsHandler?.logWcEvent(
|
||||
AnalyticsAnOld.WcAnalyticsEvent.Action(
|
||||
AnalyticsAnOld.WcAction.PersonalSign
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ import com.tangem.common.extensions.toDecompressedPublicKey
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.extensions.logSendTransactionError
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
|
|
@ -144,9 +143,9 @@ class WalletConnectSdkHelper {
|
|||
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
|
||||
}
|
||||
is SimpleResult.Failure -> {
|
||||
store.state.globalState.analyticsHandlers?.logSendTransactionError(
|
||||
store.state.globalState.analyticsHandler?.handleBlockchainSdkErrorEvent(
|
||||
result.error,
|
||||
Analytics.ActionToLog.WalletConnectTransaction,
|
||||
AnalyticsAnOld.ActionToLog.WalletConnectTransaction,
|
||||
)
|
||||
Timber.e(result.error as BlockchainSdkError)
|
||||
null
|
||||
|
|
@ -174,9 +173,9 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.WalletConnectSign,
|
||||
AnalyticsAnOld.ActionToLog.WalletConnectSign,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error.customMessage)
|
||||
|
|
@ -203,9 +202,9 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.WalletConnectTransaction,
|
||||
AnalyticsAnOld.ActionToLog.WalletConnectTransaction,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error.customMessage)
|
||||
|
|
@ -279,9 +278,9 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.WalletConnectSign,
|
||||
AnalyticsAnOld.ActionToLog.WalletConnectSign,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error.customMessage)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.MenuHost
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
|
@ -42,6 +45,17 @@ abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackP
|
|||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
loadToolbarMenu()?.let {
|
||||
val menuHost = requireActivity() as? MenuHost ?: return
|
||||
menuHost.addMenuProvider(it, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun loadToolbarMenu():MenuProvider? = null
|
||||
|
||||
fun showRetrySnackbar(message: String, action: VoidCallback) {
|
||||
val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbar.setAction(getString(R.string.common_retry)) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.features.details.redux
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.AnalyticsAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -110,9 +110,9 @@ class DetailsMiddleware {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error,
|
||||
Analytics.ActionToLog.PurgeWallet,
|
||||
AnalyticsAnOld.ActionToLog.PurgeWallet,
|
||||
card = store.state.detailsState.scanResponse?.card,
|
||||
)
|
||||
}
|
||||
|
|
@ -154,11 +154,11 @@ class DetailsMiddleware {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent(
|
||||
error = error,
|
||||
actionToLog = Analytics.ActionToLog.ChangeSecOptions,
|
||||
parameters = mapOf(
|
||||
AnalyticsParam.NEW_SECURITY_OPTION to
|
||||
action = AnalyticsAnOld.ActionToLog.ChangeSecOptions,
|
||||
params = mapOf(
|
||||
AnalyticsParamAnOld.NEW_SECURITY_OPTION to
|
||||
(selectedOption?.name ?: ""),
|
||||
),
|
||||
card = store.state.detailsState.scanResponse?.card,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
SettingsElement.WalletConnect -> {
|
||||
if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null
|
||||
}
|
||||
SettingsElement.SendFeedback -> if (state.scanResponse?.card?.isSaltPay != true) it else null
|
||||
SettingsElement.LinkMoreCards -> {
|
||||
// if (state.createBackupAllowed) it else null
|
||||
// TODO: SaltPay: temporary excluding backup process for Visa cards
|
||||
|
|
@ -78,8 +79,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
store.dispatch(DetailsAction.CreateBackup)
|
||||
}
|
||||
SettingsElement.TermsOfService -> {
|
||||
store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
store.dispatch(DisclaimerAction.Show())
|
||||
}
|
||||
SettingsElement.TermsOfUse -> {
|
||||
store.dispatch(DetailsAction.ShowDisclaimer)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class DisclaimerAction: Action {
|
||||
object AcceptDisclaimer : DisclaimerAction()
|
||||
object ShowAcceptedDisclaimer : DisclaimerAction()
|
||||
sealed class DisclaimerAction : Action {
|
||||
|
||||
data class SetDisclaimerType(
|
||||
val type: DisclaimerType,
|
||||
) : DisclaimerAction()
|
||||
|
||||
data class Show(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction()
|
||||
data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction()
|
||||
|
||||
internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction()
|
||||
internal data class SetOnAcceptCallback(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction()
|
||||
}
|
||||
|
|
@ -1,28 +1,65 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.tap.common.post
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class DisclaimerMiddleware {
|
||||
val disclaimerMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
preferencesStorage.saveDisclaimerAccepted()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
|
||||
//delayed sending used for better animation
|
||||
post(550) { store.dispatch(HomeAction.ReadCard) }
|
||||
}
|
||||
}
|
||||
state()?.let { handleDisclaimerMiddleware(action, it) }
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
|
||||
val state = appState.disclaimerState
|
||||
|
||||
when (action) {
|
||||
is DisclaimerAction.SetDisclaimerType -> {
|
||||
handleUpdateState = action.type.createUpdateState()
|
||||
}
|
||||
is DisclaimerAction.Show -> {
|
||||
handleUpdateState = state.type.createUpdateState()
|
||||
store.dispatch(DisclaimerAction.SetOnAcceptCallback(action.onAcceptCallback))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
}
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
action.type.accept()
|
||||
handleUpdateState = action.type.createUpdateState()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
state.onAcceptCallback?.invoke()
|
||||
store.dispatch(DisclaimerAction.SetOnAcceptCallback(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DisclaimerType.accept() = when (this) {
|
||||
DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted = true
|
||||
DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted = true
|
||||
}
|
||||
|
||||
fun DisclaimerType.isAccepted(): Boolean = when (this) {
|
||||
DisclaimerType.SaltPay -> preferencesStorage.disclaimerPrefStorage.hasSaltPayTosAccepted
|
||||
DisclaimerType.Tangem -> preferencesStorage.disclaimerPrefStorage.hasTangemTosAccepted
|
||||
}
|
||||
|
||||
private fun DisclaimerType.createUpdateState(): DisclaimerAction.UpdateState {
|
||||
return DisclaimerAction.UpdateState(this, this.isAccepted())
|
||||
}
|
||||
|
||||
private var handleUpdateState: DisclaimerAction.UpdateState = DisclaimerAction.UpdateState(
|
||||
type = DisclaimerType.Tangem,
|
||||
accepted = false,
|
||||
)
|
||||
set(value) {
|
||||
field = value
|
||||
store.dispatch(value)
|
||||
}
|
||||
|
|
@ -13,9 +13,15 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState {
|
|||
if (action !is DisclaimerAction) return state.disclaimerState
|
||||
|
||||
val disclaimerState = state.disclaimerState
|
||||
|
||||
return when (action) {
|
||||
is DisclaimerAction.AcceptDisclaimer, DisclaimerAction.ShowAcceptedDisclaimer -> {
|
||||
disclaimerState.copy(accepted = true)
|
||||
}
|
||||
is DisclaimerAction.UpdateState -> disclaimerState.copy(
|
||||
type = action.type,
|
||||
accepted = action.accepted,
|
||||
)
|
||||
is DisclaimerAction.SetOnAcceptCallback -> disclaimerState.copy(
|
||||
onAcceptCallback = action.onAcceptCallback,
|
||||
)
|
||||
else -> disclaimerState
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,30 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DisclaimerState(
|
||||
val accepted: Boolean = false
|
||||
val accepted: Boolean = false,
|
||||
val type: DisclaimerType = DisclaimerType.Tangem,
|
||||
val onAcceptCallback: VoidCallback? = null,
|
||||
) : StateType
|
||||
|
||||
sealed class DisclaimerType(
|
||||
val uri: Uri,
|
||||
) {
|
||||
object Tangem : DisclaimerType(Uri.parse("https://tangem.com/tangem_tos.html"))
|
||||
object SaltPay : DisclaimerType(Uri.parse("https://tangem.com/soltpay_tos.html"))
|
||||
|
||||
companion object {
|
||||
fun get(scanResponse: ScanResponse): DisclaimerType = get(scanResponse.card)
|
||||
|
||||
fun get(card: Card): DisclaimerType = when {
|
||||
card.isSaltPay -> SaltPay
|
||||
else -> Tangem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,25 +55,25 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
|
|||
binding.toolbar.setNavigationOnClickListener {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
initAndLoadTOS()
|
||||
setOnClickListeners()
|
||||
initViews()
|
||||
}
|
||||
|
||||
private fun initAndLoadTOS() {
|
||||
private fun initViews() {
|
||||
binding.webView.configureSettings()
|
||||
binding.webView.loadUrl("file:///android_asset/tos.html")
|
||||
}
|
||||
|
||||
private fun setOnClickListeners() {
|
||||
binding.btnAccept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) }
|
||||
}
|
||||
|
||||
override fun newState(state: DisclaimerState) {
|
||||
override fun newState(state: DisclaimerState) = with(binding) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
if (state.accepted) {
|
||||
binding.halfTransparentOverlay.hide()
|
||||
binding.btnAccept.hide()
|
||||
halfTransparentOverlay.hide()
|
||||
btnAccept.hide()
|
||||
}
|
||||
|
||||
btnAccept.setOnClickListener {
|
||||
store.dispatch(DisclaimerAction.AcceptDisclaimer(state.type))
|
||||
}
|
||||
|
||||
webView.loadUrl(state.type.uri.toString())
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -38,7 +37,6 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
): View? {
|
||||
val context = container?.context ?: return null
|
||||
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
composeView = ComposeView(context).apply {
|
||||
setContent {
|
||||
AppCompatTheme {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@ import com.tangem.tap.common.entities.IndeterminateProgressButton
|
|||
import org.rekotlin.Action
|
||||
|
||||
sealed class HomeAction : Action {
|
||||
|
||||
object Init : HomeAction()
|
||||
data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction()
|
||||
|
||||
// from ui
|
||||
object ReadCard : HomeAction()
|
||||
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
|
||||
data class GoToShop(val userCountryCode: String?) : HomeAction()
|
||||
|
||||
// internal
|
||||
data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction()
|
||||
object Init : HomeAction()
|
||||
|
||||
data class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction()
|
||||
data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
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.backupService
|
||||
import com.tangem.tap.common.analytics.AnalyticsEventAnOld
|
||||
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
|
||||
import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
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
|
||||
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
|
||||
import com.tangem.tap.common.postUiDelayBg
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -21,6 +25,9 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerType
|
||||
import com.tangem.tap.features.disclaimer.redux.isAccepted
|
||||
import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL
|
||||
|
|
@ -28,8 +35,8 @@ 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.redux.BackupAction
|
||||
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
|
||||
|
|
@ -65,8 +72,8 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch
|
|||
is HomeAction.Init -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.ExchangeManager.Init)
|
||||
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
|
||||
store.dispatch(GlobalAction.FetchUserCountry)
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
is HomeAction.ShouldScanCardOnResume -> {
|
||||
if (action.shouldScanCard) {
|
||||
|
|
@ -75,43 +82,97 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch
|
|||
}
|
||||
}
|
||||
is HomeAction.ReadCard -> {
|
||||
if (!preferencesStorage.wasDisclaimerAccepted()) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
} else {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
val scanCardAction = GlobalAction.ScanCard(
|
||||
onSuccess = ::onScanSuccess,
|
||||
onFailure = ::onScanFailure,
|
||||
)
|
||||
store.dispatch(HomeAction.ScanInProgress(true))
|
||||
postUiDelayBg(300) { store.dispatch(scanCardAction) }
|
||||
}
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
val scanCardAction = GlobalAction.ScanCard(
|
||||
onSuccess = { scanResponse ->
|
||||
store.dispatch(HomeAction.ScanInProgress(false))
|
||||
checkForUnfinishedBackupForSaltPay(
|
||||
backupService = backupService,
|
||||
scanResponse = scanResponse,
|
||||
nextHandler = {
|
||||
showDisclaimerIfNeed(
|
||||
scanResponse = scanResponse,
|
||||
nextHandler = ::onScanSuccess,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
onFailure = ::onScanFailure,
|
||||
)
|
||||
store.dispatch(HomeAction.ScanInProgress(true))
|
||||
postUiDelayBg(300) { store.dispatch(scanCardAction) }
|
||||
}
|
||||
is HomeAction.GoToShop -> {
|
||||
when (action.userCountryCode) {
|
||||
RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL)
|
||||
else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
|
||||
}
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.GET_CARD,
|
||||
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param),
|
||||
store.state.globalState.analyticsHandler?.handleAnalyticsEvent(
|
||||
event = AnalyticsEventAnOld.GET_CARD,
|
||||
params = mapOf(AnalyticsParamAnOld.SOURCE.param to GetCardSourceParamsAnOld.WELCOME.param),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards
|
||||
* see BackupAction.CheckForUnfinishedBackup
|
||||
* If user touches card other than Visa SaltPay - show dialog and block next processing
|
||||
*/
|
||||
private fun checkForUnfinishedBackupForSaltPay(
|
||||
backupService: BackupService,
|
||||
scanResponse: ScanResponse,
|
||||
nextHandler: (ScanResponse) -> Unit,
|
||||
) {
|
||||
if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) {
|
||||
nextHandler(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
fun isTheSamePrimaryCard(card: Card): Boolean {
|
||||
return backupService.primaryCardId?.let { it == card.cardId } ?: false
|
||||
}
|
||||
|
||||
if (scanResponse.isSaltPayWallet() || !isTheSamePrimaryCard(scanResponse.card)) {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
showSaltPayTapVisaLogoCardDialog()
|
||||
} else {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDisclaimerIfNeed(scanResponse: ScanResponse, nextHandler: (ScanResponse) -> Unit) {
|
||||
val disclaimerType = DisclaimerType.get(scanResponse)
|
||||
store.dispatch(DisclaimerAction.SetDisclaimerType(disclaimerType))
|
||||
|
||||
if (disclaimerType.isAccepted()) {
|
||||
nextHandler((scanResponse))
|
||||
} else {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatch(
|
||||
DisclaimerAction.Show {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
nextHandler(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanSuccess(scanResponse: ScanResponse) {
|
||||
val globalState = store.state.globalState
|
||||
val tapWalletManager = globalState.tapWalletManager
|
||||
tapWalletManager.updateConfigManager(scanResponse)
|
||||
|
||||
store.dispatch(HomeAction.ScanInProgress(false))
|
||||
globalState.analyticsHandler.attachToAllEvents(AnalyticsParam.BatchId, scanResponse.card.batchId)
|
||||
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
if (scanResponse.isSaltPay()) {
|
||||
if (scanResponse.isSaltPayVisa()) {
|
||||
scope.launch {
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse)
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
|
||||
delay(500)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
|
|
@ -119,10 +180,8 @@ private fun onScanSuccess(scanResponse: ScanResponse) {
|
|||
val isOnboardingCase = result.data
|
||||
if (isOnboardingCase) {
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
store.dispatch(OnboardingSaltPayAction.Init.SetDependencies(manager, config))
|
||||
store.dispatch(OnboardingSaltPayAction.Update)
|
||||
// store.dispatch(OnboardingSaltPayAction.Init.DiscardBackupSteps)
|
||||
navigateTo(AppScreen.OnboardingWallet)
|
||||
} else {
|
||||
navigateTo(AppScreen.Wallet)
|
||||
|
|
@ -131,20 +190,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,12 +198,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) {
|
|||
} else {
|
||||
if (scanResponse.card.backupStatus?.isActive == false) {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.saltpay_error_empty_backup_title,
|
||||
messageId = R.string.saltpay_error_empty_backup_message,
|
||||
),
|
||||
)
|
||||
showSaltPayTapVisaLogoCardDialog()
|
||||
} else {
|
||||
navigateTo(AppScreen.Wallet, null)
|
||||
scope.launch { store.onCardScanned(scanResponse) }
|
||||
|
|
@ -180,6 +221,15 @@ private fun onScanSuccess(scanResponse: ScanResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun showSaltPayTapVisaLogoCardDialog() {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.saltpay_error_empty_backup_title,
|
||||
messageId = R.string.saltpay_error_empty_backup_message,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onScanFailure(error: TangemError) {
|
||||
store.dispatch(HomeAction.ScanInProgress(false))
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingMenuProvider : MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_onboarding, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.menu_item_chat_support -> {
|
||||
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,11 @@ package com.tangem.tap.features.onboarding
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.successOr
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.toSaltPayStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.update
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -14,21 +15,36 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.toSaltPa
|
|||
class OnboardingSaltPayHelper {
|
||||
companion object {
|
||||
|
||||
suspend fun isOnboardingCase(scanResponse: ScanResponse): Result<Boolean> {
|
||||
suspend fun isOnboardingCase(
|
||||
scanResponse: ScanResponse,
|
||||
manager: SaltPayActivationManager,
|
||||
): Result<Boolean> {
|
||||
return try {
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
val registrationResponseItem = manager.checkRegistration().successOr { return it }
|
||||
val registrationStep = registrationResponseItem.toSaltPayStep()
|
||||
manager.checkHasGas().successOr { return it }
|
||||
var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it }
|
||||
|
||||
val isRegistrationOnboardingCase = registrationStep != SaltPayRegistrationStep.Finished
|
||||
val isBackupOnboardingCase = scanResponse.card.backupStatus?.isActive == false
|
||||
Result.Success(isRegistrationOnboardingCase || isBackupOnboardingCase)
|
||||
val cardStorage = preferencesStorage.usedCardsPrefStorage
|
||||
val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId)
|
||||
if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) {
|
||||
updatedStep = SaltPayActivationStep.Finished
|
||||
}
|
||||
val isActivationCase = updatedStep != SaltPayActivationStep.Finished
|
||||
val isBackupCase = scanResponse.card.backupStatus?.isActive == false
|
||||
Result.Success(isActivationCase || isBackupCase)
|
||||
} catch (ex: Exception) {
|
||||
Result.Failure(ex)
|
||||
} catch (error: SaltPayRegistrationError){
|
||||
} catch (error: SaltPayActivationError) {
|
||||
Result.Failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
fun testProceedToOnboarding(
|
||||
scanResponse: ScanResponse,
|
||||
manager: SaltPayActivationManager,
|
||||
): Result<Boolean> = Result.Success(true)
|
||||
|
||||
fun testProceedToMainScreen(
|
||||
scanResponse: ScanResponse,
|
||||
manager: SaltPayActivationManager,
|
||||
): Result<Boolean> = Result.Success(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
package com.tangem.tap.features.onboarding.products
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.MenuProvider
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
|
|
@ -22,6 +27,12 @@ abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_o
|
|||
exitTransition = HomeToOnboardingTransition()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider()
|
||||
|
||||
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
package com.tangem.tap.features.onboarding.products.note
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tangem_sdk_new.extensions.fadeIn
|
||||
import com.tangem.tangem_sdk_new.extensions.fadeOut
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.redux.navigation.ShareElement
|
||||
|
|
@ -33,23 +28,18 @@ import com.tangem.wallet.R
|
|||
class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
||||
|
||||
private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
|
||||
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
startPostponedEnterTransition()
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingWalletContainer)
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
|
||||
|
||||
store.dispatch(OnboardingNoteAction.LoadCardArtwork)
|
||||
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
|
||||
|
|
@ -67,7 +57,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
override fun newState(state: OnboardingNoteState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
binding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
|
|
@ -90,7 +80,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
private fun setBalance(state: OnboardingNoteState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
with (binding.onboardingTopContainer.onboardingTvBalance) {
|
||||
with(mainBinding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
|
|
@ -102,7 +92,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupNoneState() = with(binding.onboardingActionContainer) {
|
||||
private fun setupNoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.isVisible = false
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnRefreshBalanceWidget.show(false)
|
||||
|
|
@ -116,7 +106,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
}
|
||||
|
||||
private fun setupCreateWalletState(state: OnboardingNoteState) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
|
|
@ -127,15 +117,15 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
|
||||
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) {
|
||||
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
if (state.isBuyAllowed) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
|
|
@ -163,7 +153,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
state.walletBalance.amountToCreateAccount?.let { amount ->
|
||||
val tvBodyMessage = getString(
|
||||
R.string.onboarding_top_up_body_no_account_error,
|
||||
amount, state.walletBalance.currency.currencySymbol
|
||||
amount, state.walletBalance.currency.currencySymbol,
|
||||
)
|
||||
tvBody.text = tvBodyMessage
|
||||
}
|
||||
|
|
@ -175,12 +165,13 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
binding.onboardingTopContainer.imvCardBackground
|
||||
.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
|
||||
|
||||
val drawable = requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(drawable)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) {
|
||||
private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_done_button_continue)
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
|
|
@ -188,15 +179,15 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.setText("")
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation)
|
||||
}
|
||||
|
|
@ -204,7 +195,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(binding.onboardingTopContainer) {
|
||||
with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
|
|
@ -213,13 +204,4 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ImageView.swapToBitmapDrawable(bitmap: Bitmap?, duration: Long = 200) {
|
||||
if (drawable is BitmapDrawable && (drawable as BitmapDrawable).bitmap == bitmap) return
|
||||
|
||||
fadeOut(duration) {
|
||||
setImageBitmap(bitmap)
|
||||
fadeIn(duration)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,12 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.getAddressData
|
||||
import com.tangem.tap.common.extensions.getToUpUrl
|
||||
import com.tangem.tap.common.extensions.onCardScanned
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -115,11 +120,11 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
val balanceIsLoading = noteState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null
|
||||
criticalError = null,
|
||||
)
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading))
|
||||
|
||||
|
|
|
|||
|
|
@ -24,17 +24,13 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCardsState>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
}
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
startPostponedEnterTransition()
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
|
||||
|
|
@ -54,7 +50,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
|
|||
if (activity == null || view == null) return
|
||||
if (state.currentStep == OnboardingOtherCardsStep.None) return
|
||||
|
||||
binding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
|
|
@ -70,29 +66,25 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
|
|||
showConfetti(state.showConfetti)
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() = with(binding) {
|
||||
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
with(onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
}
|
||||
|
||||
|
||||
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle),
|
||||
)
|
||||
updateConstraints(R.layout.lp_onboarding_create_wallet)
|
||||
|
||||
}
|
||||
|
||||
private fun setupDoneState() = with(binding.onboardingActionContainer) {
|
||||
private fun setupDoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_done_button_continue)
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
|
|
@ -106,20 +98,18 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
|
|||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
binding.onboardingTopContainer.imvCardBackground?.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground?.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(R.layout.lp_onboarding_done)
|
||||
}
|
||||
|
||||
private fun updateConstraints(@LayoutRes layoutId: Int) {
|
||||
with(binding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
private fun updateConstraints(@LayoutRes layoutId: Int) = with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
val manager = TwinCardsManager(
|
||||
card = getScanResponse().card,
|
||||
assetReader = action.reader,
|
||||
analyticsHandler = store.state.globalState.analyticsHandlers
|
||||
analyticsHandler = store.state.globalState.analyticsHandler
|
||||
)
|
||||
store.dispatch(TwinCardsAction.CardsManager.Set(manager))
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
|||
|
||||
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
private var previousStep = TwinCardsStep.None
|
||||
|
||||
private lateinit var twinsWidget: TwinsCardWidget
|
||||
|
|
@ -47,7 +48,6 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
store.dispatch(TwinCardsAction.Init)
|
||||
}
|
||||
|
||||
|
|
@ -66,30 +66,30 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
reconfigureLayoutForTwins(binding.onboardingTopContainer)
|
||||
reconfigureLayoutForTwins(mainBinding.onboardingTopContainer)
|
||||
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
val deviceScaleFactorForWelcomeState = typedValue.float
|
||||
|
||||
twinsWidget = TwinsCardWidget(
|
||||
LeapfrogWidget(binding.onboardingTopContainer.cardsContainer),
|
||||
deviceScaleFactorForWelcomeState
|
||||
LeapfrogWidget(mainBinding.onboardingTopContainer.cardsContainer),
|
||||
deviceScaleFactorForWelcomeState,
|
||||
) {
|
||||
285f * deviceScaleFactorForWelcomeState
|
||||
}
|
||||
btnRefreshBalanceWidget =
|
||||
RefreshBalanceWidget(binding.onboardingTopContainer.onboardingWalletContainer)
|
||||
RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
|
||||
|
||||
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
|
||||
|
||||
binding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1) {
|
||||
mainBinding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
binding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2) {
|
||||
mainBinding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2) {
|
||||
placeholder(R.drawable.card_placeholder_white)
|
||||
error(R.drawable.card_placeholder_white)
|
||||
fallback(R.drawable.card_placeholder_white)
|
||||
|
|
@ -143,7 +143,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
private fun setBalance(state: TwinCardsState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
with(binding.onboardingTopContainer.onboardingTvBalance) {
|
||||
with(mainBinding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
|
|
@ -167,26 +167,26 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
|
||||
with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show()
|
||||
pbBinding.pbState.hide()
|
||||
|
||||
tvHeader.setText(R.string.twins_onboarding_subtitle)
|
||||
tvBody.text = getString(
|
||||
R.string.twins_onboarding_description_format,
|
||||
state.cardNumber?.pairIndexNumber()
|
||||
state.cardNumber?.pairIndexNumber(),
|
||||
)
|
||||
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.setOnClickListener { mainAction() }
|
||||
}
|
||||
|
||||
private fun setupWarningState(state: TwinCardsState) = with(binding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
|
||||
private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
pbBinding.pbState.hide()
|
||||
chbUnderstand.show()
|
||||
|
||||
|
|
@ -203,184 +203,175 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupCreateFirstWalletState(state: TwinCardsState) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
|
||||
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
|
||||
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
|
||||
chbUnderstand.hide()
|
||||
pbBinding.pbState.show()
|
||||
private fun setupCreateFirstWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
|
||||
chbUnderstand.hide()
|
||||
pbBinding.pbState.show()
|
||||
|
||||
binding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition()
|
||||
mainBinding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition()
|
||||
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None -> {
|
||||
twinsWidget.toLeapfrog(false) {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> startPostponedEnterTransition()
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(
|
||||
state.cardNumber,
|
||||
false
|
||||
) { startPostponedEnterTransition() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
|
||||
twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
|
||||
}
|
||||
}
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchFirstStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
AndroidAssetReader(requireContext()),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
}
|
||||
|
||||
private fun setupCreateSecondWalletState(state: TwinCardsState) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber?.pairNumber())
|
||||
|
||||
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text =
|
||||
getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchSecondStep(
|
||||
Message(
|
||||
getString(
|
||||
R.string.twins_recreate_title_format,
|
||||
twinPairIndexNumber
|
||||
)
|
||||
),
|
||||
Message(getString(R.string.twins_recreate_title_preparing)),
|
||||
Message(getString(R.string.twins_recreate_title_creating_wallet)),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateThirdWalletState(state: TwinCardsState) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber)
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchThirdStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setupTopUpWalletState(state: TwinCardsState) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None -> {
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None -> {
|
||||
twinsWidget.toLeapfrog(false) {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> {
|
||||
twinsWidget.leapfrogWidget.unfold(false) {
|
||||
twinsWidget.toActivate(false) {
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
TwinCardNumber.First -> {}
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(state.cardNumber, false) {
|
||||
twinsWidget.toActivate(false)
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
switchToCard(
|
||||
state.cardNumber,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.CreateThirdWallet -> {
|
||||
twinsWidget.toActivate()
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isBuyAllowed) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.TopUp)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = true
|
||||
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btnAlternativeAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
|
||||
twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
|
||||
}
|
||||
|
||||
tvHeader.setText(R.string.onboarding_top_up_header)
|
||||
tvBody.setText(R.string.onboarding_top_up_body)
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: TwinCardsState) =
|
||||
with(binding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_done_button_continue)
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchFirstStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
AndroidAssetReader(requireContext()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
}
|
||||
|
||||
private fun setupCreateSecondWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber?.pairNumber())
|
||||
|
||||
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text =
|
||||
getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchSecondStep(
|
||||
Message(
|
||||
getString(
|
||||
R.string.twins_recreate_title_format,
|
||||
twinPairIndexNumber,
|
||||
),
|
||||
),
|
||||
Message(getString(R.string.twins_recreate_title_preparing)),
|
||||
Message(getString(R.string.twins_recreate_title_creating_wallet)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateThirdWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber)
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchThirdStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None -> {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> {
|
||||
twinsWidget.leapfrogWidget.unfold(false) {
|
||||
twinsWidget.toActivate(false)
|
||||
}
|
||||
}
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(state.cardNumber, false) {
|
||||
twinsWidget.toActivate(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.CreateThirdWallet -> {
|
||||
twinsWidget.toActivate()
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isBuyAllowed) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
store.dispatch(TwinCardsAction.Done)
|
||||
store.dispatch(TwinCardsAction.TopUp)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = true
|
||||
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btnAlternativeAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
val layout = when (state.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
|
||||
CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
|
||||
}
|
||||
updateConstraints(state.currentStep, layout)
|
||||
}
|
||||
|
||||
tvHeader.setText(R.string.onboarding_top_up_header)
|
||||
tvBody.setText(R.string.onboarding_top_up_body)
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_done_button_continue)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
store.dispatch(TwinCardsAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
val layout = when (state.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
|
||||
CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
|
||||
}
|
||||
updateConstraints(state.currentStep, layout)
|
||||
}
|
||||
|
||||
private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(binding.onboardingTopContainer) {
|
||||
with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
|
|
@ -390,11 +381,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun switchToCard(
|
||||
cardNumber: TwinCardNumber?,
|
||||
animate: Boolean = true,
|
||||
onEnd: VoidCallback = {}
|
||||
) {
|
||||
private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
val position = cardNumber?.number ?: return
|
||||
|
||||
if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) {
|
||||
|
|
@ -405,12 +392,14 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction ->
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
showConfetti(false)
|
||||
if (should) switchToCard(TwinCardNumber.First, true, popAction)
|
||||
else popAction()
|
||||
})
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction ->
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
showConfetti(false)
|
||||
if (should) switchToCard(TwinCardNumber.First, true, popAction)
|
||||
else popAction()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import org.rekotlin.Action
|
|||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
object Init : OnboardingWalletAction()
|
||||
object StartSaltPayCardActivation: OnboardingWalletAction()
|
||||
object StartSaltPayCardActivation : OnboardingWalletAction()
|
||||
object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
object GetToSaltPayStep : OnboardingWalletAction()
|
||||
object CreateWallet : OnboardingWalletAction()
|
||||
|
|
@ -30,6 +30,11 @@ sealed class BackupAction : Action {
|
|||
object StartAddingPrimaryCard : BackupAction()
|
||||
object ScanPrimaryCard : BackupAction()
|
||||
|
||||
/**
|
||||
* Check for unfinished backup of standard Wallet cards.
|
||||
* For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start.
|
||||
* See more Onboarding.Start, CheckForUnfinishedBackup, StartForUnfinishedBackup
|
||||
*/
|
||||
object CheckForUnfinishedBackup : BackupAction()
|
||||
|
||||
object StartAddingBackupCards : BackupAction()
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.tap.domain.extensions.hasWallets
|
|||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
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.wallet.redux.Artwork
|
||||
|
|
@ -60,24 +61,26 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
|
||||
when (action) {
|
||||
OnboardingWalletAction.Init -> {
|
||||
// if (walletState.onboardingSaltPayState?.isTest == true) {
|
||||
// store.dispatch(BackupAction.FinishBackup)
|
||||
// return
|
||||
// }
|
||||
|
||||
when {
|
||||
card == null -> {
|
||||
// StartForUnfinishedBackup -> navigateTo(OnboardingWallet) -> OnboardingWalletAction.Init
|
||||
// it's possible when found unfinished backup for standard Wallet cards
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
card.hasWallets() && card.backupStatus == Card.BackupStatus.NoBackup -> {
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
card.hasWallets() && card.backupStatus?.isActive == true -> {
|
||||
if (scanResponse.isSaltPay()) {
|
||||
store.dispatch(OnboardingWalletAction.GetToSaltPayStep)
|
||||
when {
|
||||
// check for unfinished backup for saltPay cards. See more
|
||||
scanResponse.isSaltPay() && backupService.hasIncompletedBackup -> {
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
scanResponse.isSaltPay() -> {
|
||||
store.dispatch(OnboardingWalletAction.GetToSaltPayStep)
|
||||
store.dispatch(BackupAction.FinishBackup)
|
||||
}
|
||||
else -> store.dispatch(BackupAction.FinishBackup)
|
||||
}
|
||||
store.dispatch(BackupAction.FinishBackup)
|
||||
}
|
||||
else -> {
|
||||
store.dispatch(OnboardingWalletAction.GetToCreateWalletStep)
|
||||
|
|
@ -87,10 +90,8 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
is OnboardingWalletAction.StartSaltPayCardActivation -> {
|
||||
// we need to do this because the standard activation happens in the OnboardingWalletAction.CreateWallet
|
||||
// step which is omitted for SaltPay cards
|
||||
if (onboardingWalletState.isSaltPay) {
|
||||
ifNotNull(onboardingManager, scanResponse?.card?.cardId) { manager, cardId ->
|
||||
manager.activationStarted(cardId)
|
||||
}
|
||||
if (onboardingWalletState.isSaltPay && scanResponse != null) {
|
||||
startCardActivation(scanResponse)
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.LoadArtwork -> {
|
||||
|
|
@ -128,7 +129,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
card = result.data.card,
|
||||
),
|
||||
)
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -141,13 +142,15 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
OnboardingWalletAction.FinishOnboarding -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
|
||||
//TODO: SaltPay: scanResponse
|
||||
if (scanResponse == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(HomeAction.ReadCard)
|
||||
} else {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||
if (onboardingWalletState.isSaltPay) {
|
||||
finishCardActivation(backupState, scanResponse.card)
|
||||
}
|
||||
scope.launch { globalState.tapWalletManager.onCardScanned(updatedScanResponse) }
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
|
|
@ -173,16 +176,9 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
|
|||
newAction?.let { store.dispatch(it) }
|
||||
}
|
||||
OnboardingWalletAction.OnBackPressed -> {
|
||||
when (onboardingWalletState.backupState.backupStep) {
|
||||
BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo())
|
||||
BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard,
|
||||
-> {
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
is BackupStep.WriteBackupCard ->
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
when {
|
||||
onboardingWalletState.isSaltPay -> handleOnBackPressedSaltPay(onboardingWalletState)
|
||||
else -> handleOnBackPressed(onboardingWalletState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -316,31 +312,24 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
}
|
||||
}
|
||||
is BackupAction.FinishBackup -> {
|
||||
(listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||
.distinct().filterNotNull()
|
||||
.forEach { cardId ->
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
if (!onboardingWalletState.isSaltPay) {
|
||||
finishCardActivation(backupState, card)
|
||||
}
|
||||
}
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
backupService.primaryCardId?.let {
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(it)
|
||||
}
|
||||
backupService.primaryCardId?.let { finishCardsActivationForDiscardedUnfinishedBackup(it) }
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.CheckForUnfinishedBackup -> {
|
||||
if (backupService.hasIncompletedBackup) {
|
||||
if (backupService.hasIncompletedBackup && !backupService.primaryCardIsSaltPayVisa()) {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound))
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeFoundUnfinishedBackup -> {
|
||||
val isSaltPay = backupService.primaryCardId?.slice(0..3)?.let {
|
||||
SaltPayWorkaround.isVisaBatchId(it)
|
||||
} ?: false
|
||||
store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup(isSaltPay))
|
||||
store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
is BackupAction.DismissBackup -> {
|
||||
|
|
@ -350,6 +339,33 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Wallet cards start activation at OnboardingWalletAction.CreateWallet
|
||||
* SaltPay cards start activation at OnboardingWalletAction.StartSaltPayCardActivation
|
||||
*/
|
||||
private fun startCardActivation(scanResponse: ScanResponse) {
|
||||
preferencesStorage.usedCardsPrefStorage.activationStarted(scanResponse.card.cardId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Wallet cards finish activation at BackupAction.FinishBackup
|
||||
* SaltPay cards finish activation at OnboardingWalletAction.FinishOnboarding
|
||||
*/
|
||||
private fun finishCardActivation(backupState: BackupState, card: Card?) {
|
||||
(listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||
.distinct().filterNotNull()
|
||||
.forEach { cardId ->
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used for SaltPay cards, because discarding unfinished backup is not possible
|
||||
*/
|
||||
private fun finishCardsActivationForDiscardedUnfinishedBackup(cardId: String) {
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
|
||||
/**
|
||||
* SaltPay state maybe not initialized if backup resumed from ResumeFoundUnfinishedBackup action
|
||||
*/
|
||||
|
|
@ -358,11 +374,38 @@ private fun initSaltPayOnBackupFinishedIfNeeded(
|
|||
onboardingWalletState: OnboardingWalletState,
|
||||
) {
|
||||
if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) {
|
||||
//TODO: SaltPay: scanResponse
|
||||
if (scanResponse == null) throw IllegalArgumentException()
|
||||
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.Init.SetDependencies(manager, config))
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.Update)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnBackPressedSaltPay(state: OnboardingWalletState) {
|
||||
when (state.backupState.backupStep) {
|
||||
BackupStep.Finished -> {
|
||||
store.dispatchDialogShow(
|
||||
SaltPayDialog.Activation.TryToInterrupt(
|
||||
onOk = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
onCancel = { /* do nothing */ },
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> handleOnBackPressed(state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnBackPressed(state: OnboardingWalletState) {
|
||||
when (state.backupState.backupStep) {
|
||||
BackupStep.InitBackup, BackupStep.Finished -> store.dispatch(NavigationAction.PopBackTo())
|
||||
BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard,
|
||||
-> {
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
is BackupStep.WriteBackupCard ->
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
}
|
||||
}
|
||||
|
|
@ -38,26 +38,28 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet
|
|||
|
||||
private class ReducerForGlobalAction {
|
||||
companion object {
|
||||
fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState = when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2,
|
||||
canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup,
|
||||
),
|
||||
isSaltPay = action.scanResponse.isSaltPay()
|
||||
)
|
||||
fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState {
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2,
|
||||
canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup,
|
||||
),
|
||||
isSaltPay = action.scanResponse.isSaltPay(),
|
||||
)
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = false,
|
||||
),
|
||||
isSaltPay = false,
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = if (action.isSaltPayBackup) 1 else 2,
|
||||
canSkipBackup = false,
|
||||
),
|
||||
isSaltPay = action.isSaltPayBackup,
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +76,7 @@ private class BackupReducer {
|
|||
is BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
maxBackupCards = if (isSaltPay) 1 else 2,
|
||||
canSkipBackup = !isSaltPay,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
)
|
||||
BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard)
|
||||
BackupAction.StartAddingBackupCards -> {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.common.CardFilter
|
|||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayRegistrationStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +41,6 @@ data class OnboardingWalletState(
|
|||
when (backupState.backupStep) {
|
||||
BackupStep.InitBackup -> 2
|
||||
BackupStep.ScanOriginCard -> 3
|
||||
|
||||
BackupStep.AddBackupCards -> 4
|
||||
BackupStep.EnterAccessCode -> 4
|
||||
BackupStep.ReenterAccessCode -> 4
|
||||
|
|
@ -52,13 +51,16 @@ data class OnboardingWalletState(
|
|||
}
|
||||
step == OnboardingWalletStep.SaltPay && isSaltPay -> {
|
||||
when (onboardingSaltPayState!!.step) {
|
||||
SaltPayRegistrationStep.NoGas -> 1
|
||||
SaltPayRegistrationStep.NeedPin -> 7
|
||||
SaltPayRegistrationStep.CardRegistration -> 8
|
||||
SaltPayRegistrationStep.KycIntro -> 9
|
||||
SaltPayRegistrationStep.KycStart -> 10
|
||||
SaltPayRegistrationStep.KycWaiting -> 11
|
||||
SaltPayRegistrationStep.Finished -> getMaxProgress()
|
||||
SaltPayActivationStep.None, SaltPayActivationStep.NoGas -> 0
|
||||
SaltPayActivationStep.NeedPin -> 7
|
||||
SaltPayActivationStep.CardRegistration -> 8
|
||||
SaltPayActivationStep.KycIntro, SaltPayActivationStep.KycStart,
|
||||
SaltPayActivationStep.KycWaiting, SaltPayActivationStep.KycReject,
|
||||
-> 9
|
||||
SaltPayActivationStep.Claim -> 10
|
||||
SaltPayActivationStep.ClaimInProgress -> 11
|
||||
SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, SaltPayActivationStep.Finished,
|
||||
-> getMaxProgress()
|
||||
}
|
||||
}
|
||||
step == OnboardingWalletStep.Done -> getMaxProgress()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.blockchain.common.Amount
|
|||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
|
|
@ -42,6 +43,8 @@ class GnosisRegistrator(
|
|||
else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain")
|
||||
}
|
||||
|
||||
private val addressTreasureSafe = "0x8e9260a049d3Aa9ac60D0d4F27017320E0e2396B"
|
||||
|
||||
private val atomicNonce = AtomicLong()
|
||||
|
||||
suspend fun checkHasGas(): Result<Boolean> {
|
||||
|
|
@ -142,6 +145,21 @@ class GnosisRegistrator(
|
|||
return Result.Success(compiledEthereumTransaction)
|
||||
}
|
||||
|
||||
suspend fun getAllowance(): Result<Amount> {
|
||||
return walletManager.getAllowance(addressTreasureSafe, token)
|
||||
}
|
||||
|
||||
suspend fun transferFrom(amountToClaim: BigDecimal, signer: TransactionSigner): Result<Unit> {
|
||||
val amount = Amount(token, amountToClaim)
|
||||
val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe)
|
||||
.extractFeeAmount().successOr { return it }
|
||||
|
||||
val transactionData = walletManager.createTransferFromTransaction(amount, feeAmount, addressTreasureSafe)
|
||||
walletManager.transferFrom(transactionData, signer).successOr { return Result.Failure(it.error) }
|
||||
|
||||
return Result.Success(Unit)
|
||||
}
|
||||
|
||||
private fun Result<List<Amount>>.extractFeeAmount(): Result<Amount> {
|
||||
return when (this) {
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay
|
|||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.blockchain.extensions.successOr
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.successOr
|
||||
import com.tangem.network.api.paymentology.AttestationResponse
|
||||
|
|
@ -10,23 +17,24 @@ import com.tangem.network.api.paymentology.RegisterKYCRequest
|
|||
import com.tangem.network.api.paymentology.RegisterWalletRequest
|
||||
import com.tangem.network.api.paymentology.RegisterWalletResponse
|
||||
import com.tangem.network.api.paymentology.RegistrationResponse
|
||||
import com.tangem.network.api.paymentology.tryExtractError
|
||||
import com.tangem.operations.attestation.AttestWalletKeyResponse
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.UserWalletId
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
|
||||
import com.tangem.tap.persistence.SaltPayRegistrationStorage
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SaltPayRegistrationManager(
|
||||
class SaltPayActivationManager(
|
||||
val cardId: String,
|
||||
val cardPublicKey: ByteArray,
|
||||
val walletPublicKey: ByteArray,
|
||||
private val kycProvider: KYCProvider,
|
||||
private val paymentologyService: PaymentologyApiService,
|
||||
private val gnosisRegistrator: GnosisRegistrator,
|
||||
private val registrationStorage: SaltPayRegistrationStorage,
|
||||
) {
|
||||
val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider)
|
||||
|
||||
|
|
@ -35,18 +43,14 @@ class SaltPayRegistrationManager(
|
|||
|
||||
private val spendLimitValue: BigDecimal = BigDecimal("100")
|
||||
|
||||
fun transactionIsSent(): Boolean {
|
||||
return registrationStorage.data.transactionsSent
|
||||
}
|
||||
|
||||
suspend fun checkHasGas(): Result<Unit> {
|
||||
return when (val hasGasResult = gnosisRegistrator.checkHasGas()) {
|
||||
is com.tangem.blockchain.extensions.Result.Success -> if (hasGasResult.data) {
|
||||
Result.Success(Unit)
|
||||
} else {
|
||||
Result.Failure(SaltPayRegistrationError.NoGas)
|
||||
Result.Failure(SaltPayActivationError.NoGas)
|
||||
}
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> Result.Failure(SaltPayRegistrationError.NoGas)
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,20 +62,27 @@ class SaltPayRegistrationManager(
|
|||
}
|
||||
|
||||
suspend fun checkRegistration(): Result<RegistrationResponse.Item> {
|
||||
val registrationResponse = paymentologyService.checkRegistration(cardId, cardPublicKey).successOr { return it }
|
||||
val response: RegistrationResponse = paymentologyService.checkRegistration(cardId, cardPublicKey)
|
||||
.successOr { return it }
|
||||
.tryExtractError<RegistrationResponse>()
|
||||
.successOr { return it }
|
||||
|
||||
return try {
|
||||
if (!registrationResponse.success || registrationResponse.results.isEmpty()) {
|
||||
throw SaltPayRegistrationError.EmptyResponse(registrationResponse.error)
|
||||
}
|
||||
Result.Success(registrationResponse.results[0])
|
||||
} catch (ex: SaltPayRegistrationError) {
|
||||
if (response.results.isEmpty()) throw SaltPayActivationError.EmptyResponse
|
||||
|
||||
val item = response.results[0]
|
||||
if (item.error != null) throw Exception(response.makeErrorMessage())
|
||||
|
||||
Result.Success(item)
|
||||
} catch (ex: Exception) {
|
||||
Result.Failure(ex)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestAttestationChallenge(): Result<AttestationResponse> {
|
||||
return paymentologyService.requestAttestationChallenge(cardId, cardPublicKey)
|
||||
.successOr { return it }
|
||||
.tryExtractError()
|
||||
}
|
||||
|
||||
suspend fun sendTransactions(
|
||||
|
|
@ -94,14 +105,41 @@ class SaltPayRegistrationManager(
|
|||
cardSignature = attestResponse.cardSignature ?: byteArrayOf(),
|
||||
pin = pinCode,
|
||||
)
|
||||
return paymentologyService.registerWallet(request)
|
||||
.successOr { return it }
|
||||
.tryExtractError()
|
||||
}
|
||||
|
||||
val result = paymentologyService.registerWallet(request)
|
||||
suspend fun getAmountToClaim(): Result<Amount> {
|
||||
val amount = gnosisRegistrator.getAllowance().successOr {
|
||||
return Result.Failure(SaltPayActivationError.FailedToGetFundsToClaim)
|
||||
}
|
||||
|
||||
registrationStorage.data = registrationStorage.data.copy(
|
||||
transactionsSent = result is Result.Success,
|
||||
)
|
||||
return if (amount.value == null || amount.value?.isZero() == true) {
|
||||
Result.Failure(SaltPayActivationError.NoFundsToClaim)
|
||||
} else {
|
||||
Result.Success(amount)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
suspend fun claim(amountToClaim: BigDecimal, signer: TransactionSigner): Result<Unit> {
|
||||
val result = gnosisRegistrator.transferFrom(amountToClaim, signer).successOr {
|
||||
return Result.Failure(SaltPayActivationError.ClaimTransactionFailed)
|
||||
}
|
||||
return Result.Success(Unit)
|
||||
}
|
||||
|
||||
suspend fun getTokenAmount(): Result<BigDecimal> {
|
||||
val wallet = gnosisRegistrator.walletManager.safeUpdate().successOr { return it }
|
||||
val token = wallet.getFirstToken().guard {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
val amountValue = wallet.amounts[AmountType.Token(token)]?.value.guard {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
return Result.Success(amountValue)
|
||||
}
|
||||
|
||||
fun makeRegistrationTask(challenge: ByteArray): SaltPayRegistrationTask {
|
||||
|
|
@ -122,11 +160,10 @@ class SaltPayRegistrationManager(
|
|||
)
|
||||
|
||||
companion object {
|
||||
fun stub(): SaltPayRegistrationManager = SaltPayRegistrationManager(
|
||||
fun stub(): SaltPayActivationManager = SaltPayActivationManager(
|
||||
kycProvider = SaltPayConfig.stub().kycProvider,
|
||||
paymentologyService = PaymentologyApiService.stub(),
|
||||
gnosisRegistrator = GnosisRegistrator.stub(),
|
||||
registrationStorage = SaltPayRegistrationStorage.stub(),
|
||||
cardId = "",
|
||||
cardPublicKey = byteArrayOf(),
|
||||
walletPublicKey = byteArrayOf(),
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
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.SaltPayActivationError
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SaltPayExceptionHandler {
|
||||
companion object {
|
||||
fun handle(throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is SaltPayActivationError -> {
|
||||
val dialog = when (throwable) {
|
||||
is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas
|
||||
else -> SaltPayDialog.Activation.OnError(throwable)
|
||||
}
|
||||
store.dispatchDialogShow(dialog)
|
||||
}
|
||||
is TangemSdkError -> {
|
||||
when (throwable) {
|
||||
is TangemSdkError.NetworkError -> {
|
||||
val message = (throwable).customMessage
|
||||
store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message))
|
||||
}
|
||||
else -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
is BlockchainSdkError -> {
|
||||
store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage))
|
||||
}
|
||||
else -> {
|
||||
val message = throwable.localizedMessage ?: "SaltPay unknown error"
|
||||
store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
class RegistrationErrorDialog {
|
||||
companion object {
|
||||
fun create(context: Context, dialog: SaltPayDialog.RegistrationError): Dialog {
|
||||
fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog {
|
||||
val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage
|
||||
|
||||
return AlertDialog.Builder(context).apply {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayRegistrationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class SaltPayDialog : StateDialog {
|
||||
object NoFundsForActivation : SaltPayDialog()
|
||||
data class RegistrationError(val error: SaltPayRegistrationError) : SaltPayDialog()
|
||||
sealed class Activation : SaltPayDialog() {
|
||||
object NoGas : SaltPayDialog()
|
||||
data class OnError(val error: SaltPayActivationError) : SaltPayDialog()
|
||||
data class TryToInterrupt(val onOk: VoidCallback, val onCancel: VoidCallback) : SaltPayDialog()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TryToInterruptRegistrationDialog {
|
||||
companion object {
|
||||
fun create(context: Context, dialog: SaltPayDialog.Activation.TryToInterrupt): Dialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.onboarding_exit_alert_title))
|
||||
setMessage(context.getString(R.string.onboarding_exit_alert_message))
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() }
|
||||
setNegativeButton(R.string.common_cancel) { _, _ -> dialog.onCancel() }
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
setCancelable(false)
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ sealed class SaltPayModuleError(
|
|||
}
|
||||
}
|
||||
|
||||
sealed class SaltPayRegistrationError(
|
||||
sealed class SaltPayActivationError(
|
||||
subCode: Int,
|
||||
message: String? = null,
|
||||
override val data: Any? = null,
|
||||
|
|
@ -28,22 +28,27 @@ sealed class SaltPayRegistrationError(
|
|||
subCode = ERROR_CODE_REGISTRATION + subCode,
|
||||
message = message ?: EMPTY_MESSAGE,
|
||||
) {
|
||||
object Unknown : SaltPayRegistrationError(4)
|
||||
object Empty : SaltPayRegistrationError(4)
|
||||
object Unknown : SaltPayActivationError(4)
|
||||
object Empty : SaltPayActivationError(4)
|
||||
|
||||
// gnosis
|
||||
object FailedToMakeTxData : SaltPayRegistrationError(1)
|
||||
object FailedToSendTx : SaltPayRegistrationError(2)
|
||||
object FailedToMakeTxData : SaltPayActivationError(1)
|
||||
object FailedToSendTx : SaltPayActivationError(2)
|
||||
|
||||
object NeedPin : SaltPayRegistrationError(3)
|
||||
object NoGas : SaltPayRegistrationError(5) //
|
||||
class EmptyResponse(message: String?) : SaltPayRegistrationError(6, message)
|
||||
class CardNotFound(message: String?) : SaltPayRegistrationError(8, message)
|
||||
class CardDisabled(message: String?) : SaltPayRegistrationError(9, message)
|
||||
class CardNotPassed(message: String?) : SaltPayRegistrationError(10, message)
|
||||
object EmptyDynamicAttestResponse : SaltPayRegistrationError(11)
|
||||
object EmptyBackupCardScanned : SaltPayRegistrationError(12) //
|
||||
object WeakPin : SaltPayRegistrationError(13) //
|
||||
object NeedPin : SaltPayActivationError(3)
|
||||
object NoGas : SaltPayActivationError(5) //
|
||||
object EmptyResponse : SaltPayActivationError(6)
|
||||
object CardNotFound : SaltPayActivationError(8)
|
||||
class CardDisabled(message: String?) : SaltPayActivationError(9, message)
|
||||
class CardNotPassed(message: String?) : SaltPayActivationError(10, message)
|
||||
object EmptyDynamicAttestResponse : SaltPayActivationError(11)
|
||||
object EmptyBackupCardScanned : SaltPayActivationError(12)
|
||||
object WeakPin : SaltPayActivationError(13)
|
||||
|
||||
object FailedToGetFundsToClaim : SaltPayActivationError(14)
|
||||
object NoFundsToClaim : SaltPayActivationError(15)
|
||||
object ClaimTransactionFailed : SaltPayActivationError(16)
|
||||
object NotClaimed : SaltPayActivationError(17)
|
||||
|
||||
companion object {
|
||||
const val EMPTY_MESSAGE = ""
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -10,24 +12,28 @@ import org.rekotlin.Action
|
|||
sealed class OnboardingSaltPayAction : Action {
|
||||
sealed class Init : OnboardingSaltPayAction() {
|
||||
data class SetDependencies(
|
||||
val registrationManager: SaltPayRegistrationManager,
|
||||
val registrationManager: SaltPayActivationManager,
|
||||
val saltPayConfig: SaltPayConfig,
|
||||
) : Init()
|
||||
|
||||
// only for tests
|
||||
object DiscardBackupSteps : Init()
|
||||
}
|
||||
|
||||
data class SetIsBusy(val isBusy: Boolean) : OnboardingSaltPayAction()
|
||||
data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction()
|
||||
data class SetClaimRefreshInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction()
|
||||
|
||||
data class SetAccessCode(val accessCode: String?) : OnboardingSaltPayAction()
|
||||
|
||||
object Update : OnboardingSaltPayAction()
|
||||
object RegisterCard : OnboardingSaltPayAction()
|
||||
object KYCStart : OnboardingSaltPayAction()
|
||||
object OnFinishKYC : OnboardingSaltPayAction()
|
||||
object OpenUtorgKYC : OnboardingSaltPayAction()
|
||||
object UtorgKYCRedirectSuccess : OnboardingSaltPayAction()
|
||||
object RegisterKYC : OnboardingSaltPayAction()
|
||||
object Claim : OnboardingSaltPayAction()
|
||||
object RefreshClaim : OnboardingSaltPayAction()
|
||||
|
||||
data class TrySetPin(val pin: String) : OnboardingSaltPayAction()
|
||||
data class SetPin(val pin: String) : OnboardingSaltPayAction()
|
||||
|
||||
data class SetStep(val newStep: SaltPayRegistrationStep? = null) : OnboardingSaltPayAction()
|
||||
data class SetStep(val newStep: SaltPayActivationStep) : OnboardingSaltPayAction()
|
||||
data class SetAmountToClaim(val amount: Amount?) : OnboardingSaltPayAction()
|
||||
data class SetTokenBalance(val balanceValue: BigDecimal) : OnboardingSaltPayAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,31 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
|
||||
|
||||
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.analytics.GlobalAnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.isPositive
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TangemSigner
|
||||
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.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.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -55,10 +59,27 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
fun getAppState(): AppState = appState()!!
|
||||
fun getOnboardingWalletState(): OnboardingWalletState = getAppState().onboardingWalletState
|
||||
fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!!
|
||||
val analyticsHandler = getAppState().globalState.analyticsHandler
|
||||
|
||||
when (action) {
|
||||
is OnboardingSaltPayAction.Update -> {
|
||||
handleInProgress = true
|
||||
|
||||
val state = getState()
|
||||
scope.launch {
|
||||
val updateStep = state.saltPayManager.update(state.step, state.amountToClaim).successOr {
|
||||
onException(it.error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
handleInProgress = false
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep))
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.RegisterCard -> {
|
||||
handleIsBusy = true
|
||||
analyticsHandler.handleAnalyticsEvent(Onboarding.ButtonConnect())
|
||||
|
||||
handleInProgress = true
|
||||
val state = getState()
|
||||
|
||||
scope.launch {
|
||||
|
|
@ -69,7 +90,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
}
|
||||
|
||||
val pinCode = state.pinCode.guard {
|
||||
onError(SaltPayRegistrationError.NeedPin)
|
||||
onError(SaltPayActivationError.NeedPin)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
|
@ -78,11 +99,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
return@launch
|
||||
}
|
||||
|
||||
if (!attestationResponse.success) {
|
||||
onError(SaltPayRegistrationError.CardNotFound(attestationResponse.error))
|
||||
return@launch
|
||||
}
|
||||
|
||||
val registrationResponse = tangemSdkManager.runTaskAsync(
|
||||
runnable = saltPayManager.makeRegistrationTask(attestationResponse.challenge!!),
|
||||
cardId = saltPayManager.cardId,
|
||||
|
|
@ -97,7 +113,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
}
|
||||
|
||||
saltPayManager.sendTransactions(registrationResponse.signedTransactions).successOr {
|
||||
onError(SaltPayRegistrationError.FailedToSendTx)
|
||||
onError(SaltPayActivationError.FailedToSendTx)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
|
@ -106,184 +122,314 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
|
|||
return@launch
|
||||
}
|
||||
|
||||
withMainContext {
|
||||
handleIsBusy = false
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycIntro))
|
||||
}
|
||||
handleInProgress = false
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycIntro))
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.KYCStart -> {
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycStart))
|
||||
is OnboardingSaltPayAction.OpenUtorgKYC -> {
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycStart))
|
||||
}
|
||||
is OnboardingSaltPayAction.OnFinishKYC -> {
|
||||
is OnboardingSaltPayAction.UtorgKYCRedirectSuccess -> {
|
||||
store.dispatch(OnboardingSaltPayAction.RegisterKYC)
|
||||
}
|
||||
is OnboardingSaltPayAction.RegisterKYC -> {
|
||||
val state = getState()
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycWaiting))
|
||||
handleInProgress = true
|
||||
|
||||
scope.launch {
|
||||
registerKYCIfNeeded(getState()).successOr {
|
||||
Timber.d("saltPayManager.registerKYC()")
|
||||
state.saltPayManager.registerKYC().successOr {
|
||||
onException(it.error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
handleInProgress = false
|
||||
dispatchOnMain(OnboardingSaltPayAction.Update)
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.TrySetPin -> {
|
||||
try {
|
||||
assertPinValid(action.pin, getState().pinLength)
|
||||
analyticsHandler.handleAnalyticsEvent(Onboarding.PinCodeSet())
|
||||
store.dispatch(OnboardingSaltPayAction.SetPin(action.pin))
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.CardRegistration))
|
||||
} catch (error: SaltPayRegistrationError) {
|
||||
handleError = error
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration))
|
||||
} catch (error: SaltPayActivationError) {
|
||||
SaltPayExceptionHandler.handle(error)
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.Update -> {
|
||||
handleIsBusy = true
|
||||
is OnboardingSaltPayAction.Claim -> {
|
||||
analyticsHandler.handleAnalyticsEvent(Onboarding.ButtonClaim())
|
||||
val state = getState()
|
||||
handleInProgress = true
|
||||
|
||||
scope.launch {
|
||||
val amountToClaim = getAmountToClaimIfNeeded(state.saltPayManager, state.amountToClaim).guard {
|
||||
onException(SaltPayActivationError.NoFundsToClaim)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
val signer = TangemSigner(
|
||||
card = scanResponse!!.card,
|
||||
tangemSdk = tangemSdk,
|
||||
initialMessage = Message(),
|
||||
accessCode = state.accessCode,
|
||||
) { signResponse ->
|
||||
store.dispatch(
|
||||
GlobalAction.UpdateWalletSignedHashes(
|
||||
walletSignedHashes = signResponse.totalSignedHashes,
|
||||
walletPublicKey = state.saltPayManager.walletPublicKey,
|
||||
remainingSignatures = signResponse.remainingSignatures,
|
||||
),
|
||||
)
|
||||
}
|
||||
state.saltPayManager.claim(amountToClaim.value!!, signer).successOr {
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim))
|
||||
onException(it.error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
handleInProgress = false
|
||||
analyticsHandler.handleAnalyticsEvent(Onboarding.ClaimWasSuccessfully())
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress))
|
||||
dispatchOnMain(OnboardingSaltPayAction.RefreshClaim)
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.RefreshClaim -> {
|
||||
handleClaimRefreshInProgress = true
|
||||
|
||||
val state = getState()
|
||||
scope.launch {
|
||||
checkGasIfNeeded(state.saltPayManager, state.step).successOr {
|
||||
onException(it.error)
|
||||
return@launch
|
||||
}
|
||||
when (val amountToClaimResult = state.saltPayManager.getAmountToClaim()) {
|
||||
is Result.Success -> {
|
||||
// need to re claim
|
||||
handleClaimRefreshInProgress = false
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(amountToClaimResult.data))
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (amountToClaimResult.error) {
|
||||
is SaltPayActivationError.NoFundsToClaim -> {
|
||||
val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr {
|
||||
SaltPayExceptionHandler.handle(it.error)
|
||||
handleClaimRefreshInProgress = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
registerKYCIfNeeded(state).successOr {
|
||||
onException(it.error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val newStep = checkRegistration(state).successOr {
|
||||
onException(it.error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
withMainContext {
|
||||
handleIsBusy = false
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(newStep))
|
||||
handleClaimRefreshInProgress = false
|
||||
if (tokenAmountValue.isPositive()) {
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue))
|
||||
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
handleClaimRefreshInProgress = false
|
||||
SaltPayExceptionHandler.handle(amountToClaimResult.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.SetStep -> handleAnalytics(analyticsHandler, action.newStep)
|
||||
else -> {
|
||||
/* do nothing, only reduce */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var handleIsBusy: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.SetIsBusy(value))
|
||||
private fun handleAnalytics(analyticsHandler: GlobalAnalyticsEventHandler, step: SaltPayActivationStep) {
|
||||
when (step) {
|
||||
SaltPayActivationStep.KycStart -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCStarted())
|
||||
SaltPayActivationStep.KycWaiting -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCInProgress())
|
||||
SaltPayActivationStep.KycReject -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCRejected())
|
||||
SaltPayActivationStep.Claim -> analyticsHandler.handleAnalyticsEvent(Onboarding.ClaimScreenOpened())
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun SaltPayActivationManager.update(
|
||||
currentStep: SaltPayActivationStep,
|
||||
currentAmountToClaim: Amount?,
|
||||
): Result<SaltPayActivationStep> {
|
||||
Timber.d("updateSaltPayStatus")
|
||||
|
||||
val registrationResponse = checkRegistration().successOr { return it }
|
||||
val amountToClaim = getAmountToClaimIfNeeded(this, currentAmountToClaim)
|
||||
|
||||
val newStep = try {
|
||||
determineStep(currentStep, amountToClaim, registrationResponse)
|
||||
} catch (ex: Exception) {
|
||||
return Result.Failure(ex)
|
||||
}
|
||||
|
||||
private suspend fun onError(error: SaltPayRegistrationError) {
|
||||
checkGasIfNeeded(this, newStep).successOr { return it }
|
||||
|
||||
Timber.d("update: success: %s", newStep)
|
||||
return Result.Success(newStep)
|
||||
}
|
||||
|
||||
private fun determineStep(
|
||||
currentStep: SaltPayActivationStep,
|
||||
amountToClaim: Amount?,
|
||||
response: RegistrationResponse.Item,
|
||||
): SaltPayActivationStep {
|
||||
fun getStepBaseOnAmountToClaim(amount: Amount?): SaltPayActivationStep = when (amount) {
|
||||
null -> SaltPayActivationStep.Success
|
||||
else -> SaltPayActivationStep.Claim
|
||||
}
|
||||
|
||||
return when {
|
||||
response.passed != true -> throw SaltPayActivationError.CardNotPassed(response.error)
|
||||
response.disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(response.error)
|
||||
|
||||
// go to claim screen
|
||||
response.active == true -> getStepBaseOnAmountToClaim(amountToClaim)
|
||||
|
||||
// pinSet is false, go toPin screen
|
||||
response.pinSet == false -> SaltPayActivationStep.NeedPin
|
||||
|
||||
response.kycStatus != null -> {
|
||||
when (currentStep) {
|
||||
SaltPayActivationStep.KycWaiting -> when (response.kycStatus) {
|
||||
KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro
|
||||
KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting
|
||||
KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject
|
||||
KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim)
|
||||
else -> SaltPayActivationStep.KycIntro
|
||||
}
|
||||
SaltPayActivationStep.KycReject -> when (response.kycStatus) {
|
||||
KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycStart
|
||||
KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting
|
||||
KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart
|
||||
KYCStatus.REJECTED -> SaltPayActivationStep.KycStart
|
||||
KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim)
|
||||
else -> SaltPayActivationStep.KycIntro
|
||||
}
|
||||
else -> when (response.kycStatus) {
|
||||
KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro
|
||||
KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting
|
||||
KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject
|
||||
KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim)
|
||||
else -> SaltPayActivationStep.KycIntro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// kycDate is set, go to kyc waiting screen
|
||||
response.kycDate != null -> SaltPayActivationStep.KycWaiting
|
||||
|
||||
else -> SaltPayActivationStep.KycIntro
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAmountToClaimIfNeeded(
|
||||
saltPayManager: SaltPayActivationManager,
|
||||
amountToClaim: Amount?,
|
||||
): Amount? {
|
||||
Timber.d("getAmountToClaimIfNeeded: for amount: %s", amountToClaim)
|
||||
if (amountToClaim != null) {
|
||||
Timber.d("getAmountToClaimIfNeeded: checks no need, amount already persist")
|
||||
return amountToClaim
|
||||
}
|
||||
|
||||
return when (val result = saltPayManager.getAmountToClaim()) {
|
||||
is Result.Success -> {
|
||||
Timber.d("getAmountToClaimIfNeeded: success: %s", result.data)
|
||||
return result.data
|
||||
}
|
||||
is Result.Failure -> {
|
||||
Timber.e(result.error, "getAmountToClaimIfNeeded: failure")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var handleClaimRefreshInProgress: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.SetClaimRefreshInProgress(value))
|
||||
}
|
||||
|
||||
private var handleInProgress: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.SetInProgress(value))
|
||||
}
|
||||
|
||||
private suspend fun onError(error: SaltPayActivationError) {
|
||||
withMainContext {
|
||||
handleIsBusy = false
|
||||
handleError = error
|
||||
handleInProgress = false
|
||||
SaltPayExceptionHandler.handle(error)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onException(error: Throwable) {
|
||||
withMainContext {
|
||||
handleIsBusy = false
|
||||
handleException = error
|
||||
handleInProgress = false
|
||||
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,
|
||||
saltPayManager: SaltPayActivationManager,
|
||||
step: SaltPayActivationStep,
|
||||
): Result<Unit> {
|
||||
if (step == SaltPayRegistrationStep.KycStart ||
|
||||
step == SaltPayRegistrationStep.KycWaiting ||
|
||||
step == SaltPayRegistrationStep.Finished
|
||||
) {
|
||||
Timber.d("checkGasIfNeeded: for step: %s", step)
|
||||
if (step.ordinal >= SaltPayActivationStep.KycIntro.ordinal) {
|
||||
Timber.d("checkGasIfNeeded: no need to check for step: %s", step)
|
||||
return Result.Success(Unit)
|
||||
}
|
||||
|
||||
return saltPayManager.checkHasGas()
|
||||
val result = saltPayManager.checkHasGas()
|
||||
when (result) {
|
||||
is Result.Success -> Timber.d("checkGasIfNeeded: has GAS")
|
||||
is Result.Failure -> Timber.d("checkGasIfNeeded: has NO GAS")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun registerKYCIfNeeded(state: OnboardingSaltPayState): Result<Unit> {
|
||||
if (state.step != SaltPayRegistrationStep.KycStart) {
|
||||
return Result.Success(Unit)
|
||||
}
|
||||
|
||||
withMainContext {
|
||||
store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayRegistrationStep.KycWaiting))
|
||||
}
|
||||
return state.saltPayManager.registerKYC()
|
||||
}
|
||||
|
||||
private suspend fun checkRegistration(
|
||||
state: OnboardingSaltPayState,
|
||||
): Result<SaltPayRegistrationStep> {
|
||||
return try {
|
||||
val registrationResponseItem = state.saltPayManager.checkRegistration().successOr { return it }
|
||||
val step = registrationResponseItem.toSaltPayStep()
|
||||
Result.Success(step)
|
||||
} catch (ex: SaltPayRegistrationError) {
|
||||
Result.Failure(ex)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SaltPayRegistrationError::class)
|
||||
fun RegistrationResponse.Item.toSaltPayStep(): SaltPayRegistrationStep {
|
||||
@Throws(SaltPayActivationError::class)
|
||||
fun RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep {
|
||||
return when {
|
||||
passed != true -> throw SaltPayRegistrationError.CardNotPassed(this.error)
|
||||
disabledByAdmin == true -> throw SaltPayRegistrationError.CardDisabled(this.error)
|
||||
passed != true -> throw SaltPayActivationError.CardNotPassed(this.error)
|
||||
disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(this.error)
|
||||
|
||||
// go to success screen
|
||||
active == true -> SaltPayRegistrationStep.Finished
|
||||
// go to claim screen
|
||||
active == true -> SaltPayActivationStep.Claim
|
||||
|
||||
// pinSet is false, go toPin screen
|
||||
pinSet == false -> SaltPayRegistrationStep.NeedPin
|
||||
pinSet == false -> SaltPayActivationStep.NeedPin
|
||||
|
||||
kycStatus != null -> {
|
||||
when (kycStatus) {
|
||||
KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro
|
||||
KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting
|
||||
KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject
|
||||
KYCStatus.APPROVED -> SaltPayActivationStep.Claim
|
||||
null -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
// kycDate is set, go to kyc waiting screen
|
||||
kycDate != null -> SaltPayRegistrationStep.KycWaiting
|
||||
kycDate != null -> SaltPayActivationStep.KycWaiting
|
||||
|
||||
else -> SaltPayRegistrationStep.KycIntro
|
||||
else -> SaltPayActivationStep.KycIntro
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SaltPayRegistrationError::class)
|
||||
@Throws(SaltPayActivationError::class)
|
||||
private fun assertPinValid(pin: String, pinLength: Int) {
|
||||
val array = pin.toCharArray()
|
||||
|
||||
if (array.size < pinLength) throw SaltPayRegistrationError.WeakPin
|
||||
if (array.size < pinLength) throw SaltPayActivationError.WeakPin
|
||||
|
||||
val filters: List<Filter<String>> = listOf(
|
||||
AllSymbolsTheSameFilter(),
|
||||
)
|
||||
|
||||
if (filters.any { it.filter(pin) }) {
|
||||
throw SaltPayRegistrationError.WeakPin
|
||||
throw SaltPayActivationError.WeakPin
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
|
||||
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
|
|
@ -31,20 +29,6 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW
|
|||
val oldState = onboardingWalletState.onboardingSaltPayState ?: return onboardingWalletState
|
||||
|
||||
val newState = when (action) {
|
||||
OnboardingSaltPayAction.Init.DiscardBackupSteps -> {
|
||||
return onboardingWalletState.copy(
|
||||
step = OnboardingWalletStep.SaltPay,
|
||||
backupState = onboardingWalletState.backupState.copy(
|
||||
backupStep = BackupStep.Finished,
|
||||
maxBackupCards = 1,
|
||||
canSkipBackup = false,
|
||||
),
|
||||
onboardingSaltPayState = oldState.copy(
|
||||
isTest = true,
|
||||
step = SaltPayRegistrationStep.NeedPin,
|
||||
),
|
||||
)
|
||||
}
|
||||
is OnboardingSaltPayAction.SetAccessCode -> {
|
||||
oldState.copy(
|
||||
accessCode = action.accessCode,
|
||||
|
|
@ -62,19 +46,27 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW
|
|||
)
|
||||
}
|
||||
}
|
||||
is OnboardingSaltPayAction.SetIsBusy -> {
|
||||
is OnboardingSaltPayAction.SetAmountToClaim -> {
|
||||
oldState.copy(
|
||||
isBusy = action.isBusy,
|
||||
amountToClaim = action.amount,
|
||||
)
|
||||
}
|
||||
is OnboardingSaltPayAction.SetTokenBalance -> {
|
||||
oldState.copy(
|
||||
tokenAmount = oldState.tokenAmount.copy(value = action.balanceValue),
|
||||
)
|
||||
}
|
||||
is OnboardingSaltPayAction.SetInProgress -> {
|
||||
oldState.copy(
|
||||
inProgress = action.isInProgress,
|
||||
)
|
||||
}
|
||||
is OnboardingSaltPayAction.SetClaimRefreshInProgress -> {
|
||||
oldState.copy(
|
||||
claimInProgress = action.isInProgress,
|
||||
)
|
||||
}
|
||||
else -> oldState
|
||||
// is OnboardingSaltPayAction.Init.PrepareAndSetDependencies -> oldState
|
||||
// OnboardingSaltPayAction.OnCreate -> oldState
|
||||
// is OnboardingSaltPayAction.OnFinishKYC -> oldState
|
||||
// is OnboardingSaltPayAction.Register -> oldState
|
||||
// is OnboardingSaltPayAction.TrySetPin -> oldState
|
||||
// is OnboardingSaltPayAction.Update -> oldState
|
||||
// is OnboardingSaltPayAction.TryUpdateStep -> oldState
|
||||
}
|
||||
|
||||
return when (newState) {
|
||||
|
|
|
|||
|
|
@ -1,45 +1,48 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.network.api.paymentology.PaymentologyApiService
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.copyToClipboard
|
||||
import com.tangem.tap.domain.extensions.makeSaltPayWalletManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayRegistrationManager
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.persistence.SaltPayRegistrationStorage
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingSaltPayState(
|
||||
@Transient
|
||||
val saltPayManager: SaltPayRegistrationManager = SaltPayRegistrationManager.stub(),
|
||||
val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(),
|
||||
val saltPayConfig: SaltPayConfig = SaltPayConfig.stub(),
|
||||
val pinCode: String? = null,
|
||||
val accessCode: String? = null,
|
||||
val step: SaltPayRegistrationStep = SaltPayRegistrationStep.NeedPin,
|
||||
val amountToClaim: Amount? = null,
|
||||
val tokenAmount: Amount = Amount(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay), BigDecimal.ZERO),
|
||||
val step: SaltPayActivationStep = SaltPayActivationStep.None,
|
||||
val saltPayCardArtworkUrl: String? = null,
|
||||
val isBusy: Boolean = false,
|
||||
val isTest: Boolean = false,
|
||||
val inProgress: Boolean = false,
|
||||
val claimInProgress: Boolean = false,
|
||||
) {
|
||||
|
||||
val mainButtonState: WidgetState
|
||||
get() = if (isBusy) ProgressState.Loading else ProgressState.Done
|
||||
get() = if (inProgress) ProgressState.Loading else ProgressState.Done
|
||||
|
||||
fun readyToClaim(): Boolean = amountToClaim != null
|
||||
|
||||
val pinLength: Int = 4
|
||||
|
||||
companion object {
|
||||
fun initDependency(scanResponse: ScanResponse): Pair<SaltPayRegistrationManager, SaltPayConfig> {
|
||||
fun initDependency(scanResponse: ScanResponse): Pair<SaltPayActivationManager, SaltPayConfig> {
|
||||
val globalState = store.state.globalState
|
||||
val saltPayConfig = globalState.configManager?.config?.saltPayConfig.guard {
|
||||
throw NullPointerException("SaltPayConfig is not initialized")
|
||||
|
|
@ -52,32 +55,17 @@ data class OnboardingSaltPayState(
|
|||
scanResponse = scanResponse,
|
||||
gnosisRegistrator = gnosisRegistrator,
|
||||
paymentologyService = store.state.domainNetworks.paymentologyService,
|
||||
registrationStorage = preferencesStorage.saltPayRegistrationStorage,
|
||||
kycProvider = saltPayConfig.kycProvider,
|
||||
)
|
||||
test(scanResponse, gnosisRegistrator)
|
||||
return registrationManager to saltPayConfig
|
||||
}
|
||||
|
||||
//TODO: delete
|
||||
private fun test(scanResponse: ScanResponse, gnosisRegistrator: GnosisRegistrator) {
|
||||
val map = mapOf(
|
||||
"CID" to scanResponse.card.cardId,
|
||||
"cardPublicKey" to scanResponse.card.cardPublicKey.toHexString(),
|
||||
"walletPublicKey" to gnosisRegistrator.walletManager.wallet.publicKey.blockchainKey.toHexString(),
|
||||
"walletAddress" to gnosisRegistrator.walletManager.wallet.address,
|
||||
)
|
||||
val json = MoshiJsonConverter.INSTANCE.prettyPrint(map)
|
||||
copyToClipboard(json)
|
||||
}
|
||||
|
||||
fun makeSaltPayRegistrationManager(
|
||||
scanResponse: ScanResponse,
|
||||
gnosisRegistrator: GnosisRegistrator,
|
||||
paymentologyService: PaymentologyApiService,
|
||||
registrationStorage: SaltPayRegistrationStorage,
|
||||
kycProvider: KYCProvider,
|
||||
): SaltPayRegistrationManager {
|
||||
): SaltPayActivationManager {
|
||||
if (!scanResponse.isSaltPay()) {
|
||||
throw IllegalArgumentException("Can't initialize the OnboardingSaltPayMiddleware if card is not SalPay")
|
||||
}
|
||||
|
|
@ -85,14 +73,13 @@ data class OnboardingSaltPayState(
|
|||
throw NullPointerException("SaltPay card must have one wallet at least")
|
||||
}
|
||||
|
||||
return SaltPayRegistrationManager(
|
||||
return SaltPayActivationManager(
|
||||
cardId = scanResponse.card.cardId,
|
||||
cardPublicKey = scanResponse.card.cardPublicKey,
|
||||
walletPublicKey = saltPaySingleWallet.publicKey,
|
||||
kycProvider = kycProvider,
|
||||
paymentologyService = paymentologyService,
|
||||
gnosisRegistrator = gnosisRegistrator,
|
||||
registrationStorage = registrationStorage,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -107,12 +94,18 @@ data class OnboardingSaltPayState(
|
|||
}
|
||||
}
|
||||
|
||||
enum class SaltPayRegistrationStep {
|
||||
enum class SaltPayActivationStep {
|
||||
None,
|
||||
NoGas,
|
||||
NeedPin,
|
||||
CardRegistration,
|
||||
KycIntro,
|
||||
KycStart,
|
||||
KycWaiting,
|
||||
KycReject,
|
||||
Claim,
|
||||
ClaimInProgress,
|
||||
ClaimSuccess,
|
||||
Success,
|
||||
Finished;
|
||||
}
|
||||
|
|
@ -0,0 +1,388 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.ProgressBar
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.children
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import coil.size.Scale
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.compose.PinCodeWidget
|
||||
import com.tangem.tap.common.extensions.configureSettings
|
||||
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
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
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.common.toggleWidget.IndeterminateProgressButtonWidget
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebChromeClient
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
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.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerBottomBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingMainBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSaltPayView(
|
||||
private val walletFragment: OnboardingWalletFragment,
|
||||
) {
|
||||
|
||||
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: SaltPayActivationStep = SaltPayActivationStep.None
|
||||
|
||||
fun newState(state: OnboardingWalletState) {
|
||||
handleCardArtworks(state)
|
||||
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState)
|
||||
OnboardingWalletStep.Backup -> {
|
||||
if (state.backupState.backupStep == BackupStep.Finished) {
|
||||
// if backup finished -> switch OnboardingWalletStep to the SaltPay
|
||||
store.dispatch(OnboardingWalletAction.GetToSaltPayStep)
|
||||
} else {
|
||||
// if not -> back to the standard backup process
|
||||
walletFragment.handleOnboardingStep(state)
|
||||
}
|
||||
}
|
||||
else -> walletFragment.handleOnboardingStep(state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) {
|
||||
//TODO: SaltPay: remove hardCode
|
||||
if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) {
|
||||
imvFrontCard.load(R.drawable.img_salt_pay_visa) {
|
||||
scale(Scale.FILL)
|
||||
crossfade(enable = true)
|
||||
}
|
||||
} else {
|
||||
walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard)
|
||||
}
|
||||
imvFirstBackupCard.load(R.drawable.card_placeholder_wallet)
|
||||
|
||||
// if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) {
|
||||
// //if saltPay url not loaded -> load from resource
|
||||
// val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa)
|
||||
// imvFrontCard.setImageBitmap(bitmap)
|
||||
// } else {
|
||||
// walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard)
|
||||
// }
|
||||
// walletFragment.loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard)
|
||||
}
|
||||
|
||||
fun showAddBackupCards(state: BackupState) = with(walletFragment.binding) {
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_no_backup_card)
|
||||
tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_no_backup_cards)
|
||||
}
|
||||
1 -> {
|
||||
tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_one_backup_card)
|
||||
tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_one_backup_card)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSaltPayStep(onboardingSaltPayState: OnboardingSaltPayState?) {
|
||||
val state = onboardingSaltPayState.guard {
|
||||
store.dispatchDebugErrorNotification("SaltPayView: setSaltPayStep: onboardingSaltPayState = null")
|
||||
return
|
||||
}
|
||||
|
||||
walletFragment.binding.onboardingWalletContainer.hide()
|
||||
walletFragment.bindingSaltPay.onboardingSaltpayContainer.show()
|
||||
|
||||
when (state.step) {
|
||||
SaltPayActivationStep.NoGas -> handleNoGas()
|
||||
SaltPayActivationStep.NeedPin -> handleNeedPin()
|
||||
SaltPayActivationStep.CardRegistration -> handleCardRegistration()
|
||||
SaltPayActivationStep.KycIntro -> handleKycIntro()
|
||||
SaltPayActivationStep.KycStart -> handleKycStart(state)
|
||||
SaltPayActivationStep.KycWaiting -> handleKycWaiting()
|
||||
SaltPayActivationStep.KycReject -> handleKycReject()
|
||||
SaltPayActivationStep.Claim -> handleClaim(state)
|
||||
SaltPayActivationStep.ClaimInProgress -> handleClaim(state)
|
||||
SaltPayActivationStep.ClaimSuccess -> handleClaim(state)
|
||||
SaltPayActivationStep.Success -> handleSuccess(state)
|
||||
}
|
||||
progressButton?.changeState(state.mainButtonState)
|
||||
}
|
||||
|
||||
private fun handleNoGas() {
|
||||
// normally this shouldn't happen
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchDialogShow(SaltPayDialog.Activation.NoGas)
|
||||
}
|
||||
|
||||
private fun handleNeedPin() = with(walletFragment.bindingSaltPay) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_pin)
|
||||
showOnlyView(pinCode.root) {
|
||||
progressButton = SaltPayProgressButton(pinCode.root)
|
||||
progressButton?.mainView?.isEnabled = false
|
||||
|
||||
var enteredPinCode = ""
|
||||
pinCode.flPinCodeContainer.setContent {
|
||||
PinCodeWidget { code, isLastSymbolEntered ->
|
||||
enteredPinCode = code
|
||||
progressButton?.mainView?.isEnabled = isLastSymbolEntered
|
||||
}
|
||||
}
|
||||
pinCode.btnSetPin.setOnClickListener {
|
||||
store.dispatch(OnboardingSaltPayAction.TrySetPin(enteredPinCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCardRegistration() = with(walletFragment.bindingSaltPay) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_register_wallet)
|
||||
showOnlyView(connectCard.root) {
|
||||
progressButton = SaltPayProgressButton(connectCard.root)
|
||||
connectCard.btnConnect.setOnClickListener {
|
||||
store.dispatch(OnboardingSaltPayAction.RegisterCard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleKycIntro() = with(walletFragment.bindingSaltPay) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_kyc_start)
|
||||
showOnlyView(verifyIdentity.root) {
|
||||
progressButton = SaltPayProgressButton(verifyIdentity.root)
|
||||
verifyIdentity.btnVerify.setOnClickListener {
|
||||
store.dispatch(OnboardingSaltPayAction.OpenUtorgKYC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleKycStart(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_kyc_start)
|
||||
showOnlyView(webvVerifyIdentity) {
|
||||
progressButton = null
|
||||
webvVerifyIdentity.configureSettings()
|
||||
webvVerifyIdentity.webViewClient = UtorgWebViewClient(
|
||||
successUrl = state.saltPayManager.kycUrlProvider.doneUrl,
|
||||
onSuccess = {
|
||||
webvVerifyIdentity.stop()
|
||||
store.dispatch(OnboardingSaltPayAction.UtorgKYCRedirectSuccess)
|
||||
},
|
||||
)
|
||||
webvVerifyIdentity.webChromeClient = UtorgWebChromeClient(walletFragment.requireActivity())
|
||||
webvVerifyIdentity.loadUrl(state.saltPayManager.kycUrlProvider.requestUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleKycWaiting() = with(walletFragment.bindingSaltPay.kycInProgress) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_kyc_progress)
|
||||
showOnlyView(root)
|
||||
|
||||
imvInProgress.setDrawable(R.drawable.ic_in_progress)
|
||||
tvHeader.text = getText(R.string.onboarding_title_kyc_waiting)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_kyc_waiting)
|
||||
|
||||
btnOpenSupportChat.hide()
|
||||
btnOpenSupportChat.setOnClickListener {
|
||||
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
|
||||
}
|
||||
|
||||
btnKycAction.text = getText(R.string.onboarding_button_kyc_waiting)
|
||||
btnKycAction.setOnClickListener {
|
||||
store.dispatch(OnboardingSaltPayAction.Update)
|
||||
}
|
||||
progressButton = SaltPayProgressButton(root)
|
||||
}
|
||||
|
||||
private fun handleKycReject() = with(walletFragment.bindingSaltPay.kycInProgress) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_kyc_progress)
|
||||
showOnlyView(root)
|
||||
|
||||
imvInProgress.setDrawable(R.drawable.ic_reject)
|
||||
tvHeader.text = getText(R.string.onboarding_title_kyc_retry)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_kyc_retry)
|
||||
|
||||
btnOpenSupportChat.hide()
|
||||
btnKycAction.text = getText(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 = getText(R.string.onboarding_navbar_claim)
|
||||
val btnMain = actionContainer.btnContainer.findViewById<MaterialButton>(R.id.btn_main_action)
|
||||
val tvHeader = actionContainer.tvHeader
|
||||
val tvBody = actionContainer.tvBody
|
||||
|
||||
showOnlyView(claimBinding.root) {
|
||||
// topContainer.progress.pbState.hide()
|
||||
progressButton = SaltPayProgressButton(actionContainer.root)
|
||||
topContainer.imvCardBackground.apply {
|
||||
setBackgroundDrawable(context.getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
|
||||
}
|
||||
topContainer.imvFrontCard.load(R.drawable.img_salt_pay_visa) {
|
||||
scale(Scale.FILL)
|
||||
crossfade(enable = true)
|
||||
}
|
||||
actionContainer.btnAlternativeAction.hide()
|
||||
updateConstraints(state.step, R.layout.lp_onboarding_topup_wallet)
|
||||
}
|
||||
|
||||
btnRefreshBalanceWidget.changeState(if (state.claimInProgress) ProgressState.Loading else ProgressState.Done)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(OnboardingSaltPayAction.RefreshClaim)
|
||||
}
|
||||
}
|
||||
val tokenAmountString = tokenAmountString(state)
|
||||
topContainer.onboardingTvBalance.tvBalanceValue.text = tokenAmountString ?: ""
|
||||
topContainer.onboardingTvBalance.tvBalanceCurrency.text = ""
|
||||
|
||||
when (state.step) {
|
||||
SaltPayActivationStep.Claim -> {
|
||||
claimValueString(state)?.let {
|
||||
tvHeader.text = getText(R.string.onboarding_title_claim, it)
|
||||
}
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_claim)
|
||||
|
||||
btnMain.text = getText(R.string.onboarding_button_claim)
|
||||
btnMain.isEnabled
|
||||
btnMain.setOnClickListener {
|
||||
store.dispatch(OnboardingSaltPayAction.Claim)
|
||||
}
|
||||
progressButton = SaltPayProgressButton(actionContainer.root)
|
||||
progressButton?.isEnabled = true
|
||||
}
|
||||
SaltPayActivationStep.ClaimInProgress -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_claim_progress)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_claim_progress)
|
||||
btnMain.text = getText(R.string.onboarding_button_claim)
|
||||
progressButton = SaltPayProgressButton(actionContainer.root)
|
||||
progressButton?.isEnabled = false
|
||||
}
|
||||
SaltPayActivationStep.ClaimSuccess -> {
|
||||
tvHeader.setText(R.string.common_success)
|
||||
tvBody.setText(R.string.onboarding_subtitle_success_claim)
|
||||
|
||||
btnMain.setText(R.string.onboarding_button_continue_wallet)
|
||||
btnMain.setOnClickListener {
|
||||
walletFragment.showConfetti(false)
|
||||
store.dispatch(OnboardingWalletAction.FinishOnboarding)
|
||||
}
|
||||
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
updateConstraints(state.step, R.layout.lp_onboarding_done_activation)
|
||||
walletFragment.showConfetti(true)
|
||||
progressButton = SaltPayProgressButton(actionContainer.root)
|
||||
progressButton?.isEnabled = true
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSuccess(state: OnboardingSaltPayState) = with(walletFragment.binding) {
|
||||
walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide()
|
||||
onboardingWalletContainer.show()
|
||||
walletFragment.showSuccess()
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_success_claim)
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
}
|
||||
|
||||
private fun tokenAmountString(state: OnboardingSaltPayState): String? {
|
||||
return state.tokenAmount.value?.toFormattedCurrencyString(
|
||||
decimals = state.tokenAmount.decimals,
|
||||
currency = state.tokenAmount.currencySymbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun claimValueString(state: OnboardingSaltPayState): String? {
|
||||
return state.amountToClaim?.value?.toFormattedCurrencyString(
|
||||
decimals = state.amountToClaim.decimals,
|
||||
currency = state.amountToClaim.currencySymbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateConstraints(currentStep: SaltPayActivationStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(claimBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(root.context, layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) {
|
||||
walletFragment.bindingSaltPay.onboardingSaltpayContainer.children
|
||||
.filter { it.id != view.id }
|
||||
.forEach { it.hide() }
|
||||
view.show { onShowListener?.invoke() }
|
||||
}
|
||||
|
||||
private fun getText(@StringRes resId: Int): String {
|
||||
return walletFragment.getString(resId)
|
||||
}
|
||||
|
||||
private fun getText(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||
return walletFragment.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
private class SaltPayProgressButton(
|
||||
root: ViewGroup,
|
||||
) : IndeterminateProgressButtonWidget(findButton(root), findProgress(root)) {
|
||||
companion object {
|
||||
fun findButton(view: ViewGroup): MaterialButton {
|
||||
return findContainer(view).children.firstOrNull { it is MaterialButton } as MaterialButton
|
||||
}
|
||||
|
||||
fun findProgress(view: ViewGroup): View {
|
||||
return findContainer(view).children.firstOrNull { it is ProgressBar }!!
|
||||
}
|
||||
|
||||
fun findContainer(view: ViewGroup): ViewGroup {
|
||||
return view.children.first { it.id == R.id.btn_container } as ViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue