Updated on 2026-08-14
This commit is contained in:
parent
5b516f2c9b
commit
a82d8cdbba
43 changed files with 829 additions and 89 deletions
|
|
@ -207,6 +207,7 @@ dependencies {
|
||||||
implementation(projects.data.txhistory)
|
implementation(projects.data.txhistory)
|
||||||
implementation(projects.data.wallets)
|
implementation(projects.data.wallets)
|
||||||
implementation(projects.data.analytics)
|
implementation(projects.data.analytics)
|
||||||
|
implementation(projects.data.appsflyer)
|
||||||
implementation(projects.data.transaction)
|
implementation(projects.data.transaction)
|
||||||
implementation(projects.data.visa)
|
implementation(projects.data.visa)
|
||||||
implementation(projects.data.stories)
|
implementation(projects.data.stories)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.tap.common.analytics.appsflyer
|
package com.tangem.tap.common.analytics.appsflyer
|
||||||
|
|
||||||
import com.appsflyer.deeplink.DeepLink
|
import com.appsflyer.deeplink.DeepLink
|
||||||
|
import com.tangem.datasource.local.appsflyer.AppsFlyerDeeplinkSource
|
||||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||||
import com.tangem.domain.wallets.models.AppsFlyerConversionData
|
import com.tangem.domain.wallets.models.AppsFlyerConversionData
|
||||||
import com.tangem.feature.referral.domain.SetShouldShowMobileWalletPromoUseCase
|
import com.tangem.feature.referral.domain.SetShouldShowMobileWalletPromoUseCase
|
||||||
|
|
@ -40,11 +41,20 @@ class AppsFlyerReferralParamsHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handle(deepLinkValue: String?, deepLinkSub1: String?, deepLinkSub2: String?) {
|
private fun handle(deepLinkValue: String?, deepLinkSub1: String?, deepLinkSub2: String?) {
|
||||||
if (deepLinkValue != REFERRAL_DEEP_LINK_VALUE) {
|
when (deepLinkValue) {
|
||||||
TangemLogger.i("Ignoring deep link with value: ${deepLinkValue ?: "null"}")
|
REFERRAL_DEEP_LINK_VALUE -> handleReferral(deepLinkSub1, deepLinkSub2)
|
||||||
return
|
TANGEM_PAY_HOT_WALLET_ONBOARDING_DEEP_LINK_VALUE -> handleTangemPayHotWalletOnboarding(deepLinkValue)
|
||||||
|
else -> TangemLogger.i("Ignoring deep link with value: ${deepLinkValue ?: "null"}")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTangemPayHotWalletOnboarding(deepLinkValue: String) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
appsFlyerStore.storeDeeplink(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding, deepLinkValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleReferral(deepLinkSub1: String?, deepLinkSub2: String?) {
|
||||||
@Suppress("NullableToStringCall")
|
@Suppress("NullableToStringCall")
|
||||||
TangemLogger.i("refcode=$deepLinkSub1\ncampaign=$deepLinkSub2")
|
TangemLogger.i("refcode=$deepLinkSub1\ncampaign=$deepLinkSub2")
|
||||||
|
|
||||||
|
|
@ -80,6 +90,8 @@ class AppsFlyerReferralParamsHandler @Inject constructor(
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
const val REFERRAL_DEEP_LINK_VALUE = "referral"
|
const val REFERRAL_DEEP_LINK_VALUE = "referral"
|
||||||
|
const val TANGEM_PAY_HOT_WALLET_ONBOARDING_DEEP_LINK_VALUE = "tpay_mobileonboard"
|
||||||
|
|
||||||
const val DEEP_LINK_VALUE = "deep_link_value"
|
const val DEEP_LINK_VALUE = "deep_link_value"
|
||||||
const val DEEP_LINK_SUB_1 = "deep_link_sub1"
|
const val DEEP_LINK_SUB_1 = "deep_link_sub1"
|
||||||
const val DEEP_LINK_SUB_2 = "deep_link_sub2"
|
const val DEEP_LINK_SUB_2 = "deep_link_sub2"
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import com.tangem.core.analytics.models.Basic
|
||||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||||
import com.tangem.core.analytics.utils.TrackingContextProxy
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
|
import com.tangem.core.configtoggle.FeatureToggles
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.context.child
|
import com.tangem.core.decompose.context.child
|
||||||
import com.tangem.core.decompose.context.childByContext
|
import com.tangem.core.decompose.context.childByContext
|
||||||
|
|
@ -29,6 +31,8 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.message.DialogMessage
|
import com.tangem.core.ui.message.DialogMessage
|
||||||
import com.tangem.core.ui.message.EventMessageAction
|
import com.tangem.core.ui.message.EventMessageAction
|
||||||
import com.tangem.core.ui.message.SnackbarMessage
|
import com.tangem.core.ui.message.SnackbarMessage
|
||||||
|
import com.tangem.datasource.local.appsflyer.AppsFlyerDeeplinkSource
|
||||||
|
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||||
import com.tangem.domain.card.repository.CardRepository
|
import com.tangem.domain.card.repository.CardRepository
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
|
@ -83,6 +87,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
private val userWalletsListRepository: UserWalletsListRepository,
|
private val userWalletsListRepository: UserWalletsListRepository,
|
||||||
private val cardRepository: CardRepository,
|
private val cardRepository: CardRepository,
|
||||||
private val onboardingRepository: OnboardingRepository,
|
private val onboardingRepository: OnboardingRepository,
|
||||||
|
private val appsFlyerStore: AppsFlyerStore,
|
||||||
private val trackingContextProxy: TrackingContextProxy,
|
private val trackingContextProxy: TrackingContextProxy,
|
||||||
private val scanFailsComponentFactory: ScanFailsComponent.Factory,
|
private val scanFailsComponentFactory: ScanFailsComponent.Factory,
|
||||||
private val scanFailsRequesterProxy: ScanFailsRequesterProxy,
|
private val scanFailsRequesterProxy: ScanFailsRequesterProxy,
|
||||||
|
|
@ -93,6 +98,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
|
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
|
||||||
private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase,
|
private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase,
|
||||||
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
|
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
|
||||||
|
private val featureTogglesManager: FeatureTogglesManager,
|
||||||
) : RoutingComponent,
|
) : RoutingComponent,
|
||||||
AppComponentContext by context,
|
AppComponentContext by context,
|
||||||
SnackbarHandler {
|
SnackbarHandler {
|
||||||
|
|
@ -200,6 +206,21 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun navigateForEmptyWallets(): AppRoute {
|
private suspend fun navigateForEmptyWallets(): AppRoute {
|
||||||
|
if (featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING)) {
|
||||||
|
val tangemPayHotWalletOnboardingDeepLink = appsFlyerStore.getDeeplink(
|
||||||
|
AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding,
|
||||||
|
)
|
||||||
|
if (tangemPayHotWalletOnboardingDeepLink != null) {
|
||||||
|
val hotWalletRoute = AppRoute.TangemPayHotWalletOnboarding
|
||||||
|
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
||||||
|
return if (shouldShowTos) {
|
||||||
|
AppRoute.Disclaimer(isTosAccepted = false, nextRoute = hotWalletRoute)
|
||||||
|
} else {
|
||||||
|
hotWalletRoute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val shouldAskPushPermission = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrNull()
|
val shouldAskPushPermission = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrNull()
|
||||||
?: return AppRoute.Home(launchMode = launchMode)
|
?: return AppRoute.Home(launchMode = launchMode)
|
||||||
return if (shouldAskPushPermission) {
|
return if (shouldAskPushPermission) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import com.tangem.features.send.v2.api.SendComponent
|
||||||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||||
import com.tangem.features.staking.api.StakingComponent
|
import com.tangem.features.staking.api.StakingComponent
|
||||||
import com.tangem.features.swap.SwapComponent
|
import com.tangem.features.swap.SwapComponent
|
||||||
|
import com.tangem.features.tangempay.components.TangemPayHotWalletOnboardingComponent
|
||||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
|
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
|
||||||
|
|
@ -109,6 +110,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
|
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
|
||||||
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
||||||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||||
|
private val tangemPayWalletOnboardingComponentFactory: TangemPayHotWalletOnboardingComponent.Factory,
|
||||||
private val kycComponentFactory: KycComponent.Factory,
|
private val kycComponentFactory: KycComponent.Factory,
|
||||||
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
||||||
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
||||||
|
|
@ -131,7 +133,10 @@ internal class ChildFactory @Inject constructor(
|
||||||
is AppRoute.Disclaimer -> {
|
is AppRoute.Disclaimer -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = DisclaimerComponent.Params(route.isTosAccepted),
|
params = DisclaimerComponent.Params(
|
||||||
|
isTosAccepted = route.isTosAccepted,
|
||||||
|
nextRoute = route.nextRoute,
|
||||||
|
),
|
||||||
componentFactory = disclaimerComponentFactory,
|
componentFactory = disclaimerComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -573,9 +578,9 @@ internal class ChildFactory @Inject constructor(
|
||||||
params = CreateWalletBackupComponent.Params(
|
params = CreateWalletBackupComponent.Params(
|
||||||
userWalletId = route.userWalletId,
|
userWalletId = route.userWalletId,
|
||||||
isUpgradeFlow = route.isUpgradeFlow,
|
isUpgradeFlow = route.isUpgradeFlow,
|
||||||
shouldSetAccessCode = route.shouldSetAccessCode,
|
|
||||||
analyticsSource = route.analyticsSource,
|
analyticsSource = route.analyticsSource,
|
||||||
analyticsAction = route.analyticsAction,
|
analyticsAction = route.analyticsAction,
|
||||||
|
nextScreen = route.nextScreen,
|
||||||
),
|
),
|
||||||
componentFactory = createWalletBackupComponentFactory,
|
componentFactory = createWalletBackupComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
@ -586,6 +591,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
params = UpdateAccessCodeComponent.Params(
|
params = UpdateAccessCodeComponent.Params(
|
||||||
userWalletId = route.userWalletId,
|
userWalletId = route.userWalletId,
|
||||||
source = route.source,
|
source = route.source,
|
||||||
|
nextScreen = route.nextScreen,
|
||||||
),
|
),
|
||||||
componentFactory = updateAccessCodeComponentFactory,
|
componentFactory = updateAccessCodeComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
@ -668,6 +674,9 @@ internal class ChildFactory @Inject constructor(
|
||||||
is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding(
|
is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding(
|
||||||
userWalletId = mode.userWalletId,
|
userWalletId = mode.userWalletId,
|
||||||
)
|
)
|
||||||
|
is AppRoute.TangemPayOnboarding.Mode.FirstSetup -> HotWalletOnboarding(
|
||||||
|
userWalletId = mode.userWalletId,
|
||||||
|
)
|
||||||
is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(
|
is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(
|
||||||
deeplink = mode.deeplink,
|
deeplink = mode.deeplink,
|
||||||
)
|
)
|
||||||
|
|
@ -677,6 +686,13 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = tangemPayOnboardingComponentFactory,
|
componentFactory = tangemPayOnboardingComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is AppRoute.TangemPayHotWalletOnboarding -> {
|
||||||
|
createComponentChild(
|
||||||
|
context = context,
|
||||||
|
params = Unit,
|
||||||
|
componentFactory = tangemPayWalletOnboardingComponentFactory,
|
||||||
|
)
|
||||||
|
}
|
||||||
is AppRoute.Kyc -> {
|
is AppRoute.Kyc -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Disclaimer(
|
data class Disclaimer(
|
||||||
val isTosAccepted: Boolean,
|
val isTosAccepted: Boolean,
|
||||||
|
val nextRoute: AppRoute? = null,
|
||||||
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}")
|
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -405,13 +406,14 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val analyticsSource: String,
|
val analyticsSource: String,
|
||||||
val analyticsAction: String,
|
val analyticsAction: String,
|
||||||
val isUpgradeFlow: Boolean = false,
|
val isUpgradeFlow: Boolean = false,
|
||||||
val shouldSetAccessCode: Boolean = false,
|
val nextScreen: AppRoute? = null,
|
||||||
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
|
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UpdateAccessCode(
|
data class UpdateAccessCode(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val source: String,
|
val source: String,
|
||||||
|
val nextScreen: AppRoute? = null,
|
||||||
) : AppRoute(path = "/update_access_code/${userWalletId.stringValue}")
|
) : AppRoute(path = "/update_access_code/${userWalletId.stringValue}")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -457,6 +459,9 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val status: AccountStatus.Payment,
|
val status: AccountStatus.Payment,
|
||||||
) : AppRoute(path = "/tangem_pay_details/${status.account}")
|
) : AppRoute(path = "/tangem_pay_details/${status.account}")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object TangemPayHotWalletOnboarding : AppRoute(path = "/tangem_pay_hot_wallet_onboarding")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TangemPayOnboarding(
|
data class TangemPayOnboarding(
|
||||||
val mode: Mode,
|
val mode: Mode,
|
||||||
|
|
@ -474,6 +479,11 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
) : Mode()
|
) : Mode()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FirstSetup(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
) : Mode()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object FromBannerOnMain : Mode()
|
data object FromBannerOnMain : Mode()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ sealed class AnalyticsParam {
|
||||||
Portfolio("Portfolio"),
|
Portfolio("Portfolio"),
|
||||||
Staking("Staking"),
|
Staking("Staking"),
|
||||||
Earn("Earn"),
|
Earn("Earn"),
|
||||||
|
TangemPayHotWalletOnboarding("TangemPayHotWalletOnboarding"),
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TxSentFrom(val value: String) {
|
sealed class TxSentFrom(val value: String) {
|
||||||
|
|
|
||||||
|
|
@ -82,5 +82,9 @@
|
||||||
{
|
{
|
||||||
"name": "AND_15009_SWAP_PROVIDER_FILTER_ENABLED",
|
"name": "AND_15009_SWAP_PROVIDER_FILTER_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AND_15101_TANGEM_PAY_HOT_WALLET_ONBOARDING",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,19 @@ interface AppsFlyerStore {
|
||||||
suspend fun storeIfAbsent(value: AppsFlyerConversionData)
|
suspend fun storeIfAbsent(value: AppsFlyerConversionData)
|
||||||
|
|
||||||
suspend fun storeUIDIfAbsent(value: String)
|
suspend fun storeUIDIfAbsent(value: String)
|
||||||
|
|
||||||
|
suspend fun getDeeplink(source: AppsFlyerDeeplinkSource): String?
|
||||||
|
|
||||||
|
suspend fun storeDeeplink(source: AppsFlyerDeeplinkSource, deeplink: String)
|
||||||
|
|
||||||
|
suspend fun clearDeeplink(source: AppsFlyerDeeplinkSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class AppsFlyerDeeplinkSource {
|
||||||
|
TangemPayHotWalletOnboarding,
|
||||||
|
;
|
||||||
|
|
||||||
|
fun toStoreKey() = when (this) {
|
||||||
|
TangemPayHotWalletOnboarding -> "tangem_pay_hot_wallet_onboarding"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +56,22 @@ internal class DefaultAppsFlyerStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
override suspend fun getDeeplink(source: AppsFlyerDeeplinkSource): String? =
|
||||||
|
appPreferencesStore.getSyncOrNull(stringPreferencesKey(source.toStoreKey()))
|
||||||
|
|
||||||
|
override suspend fun storeDeeplink(source: AppsFlyerDeeplinkSource, deeplink: String) {
|
||||||
|
appPreferencesStore.editData { preferences ->
|
||||||
|
preferences[stringPreferencesKey(source.toStoreKey())] = deeplink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearDeeplink(source: AppsFlyerDeeplinkSource) {
|
||||||
|
appPreferencesStore.editData { preferences ->
|
||||||
|
preferences.remove(stringPreferencesKey(source.toStoreKey()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
val UID_KEY = stringPreferencesKey("APPS_FLYER_UID")
|
val UID_KEY = stringPreferencesKey("APPS_FLYER_UID")
|
||||||
val CONVERSION_DATA_KEY = stringPreferencesKey("APPS_FLYER_CONVERSION_DATA")
|
val CONVERSION_DATA_KEY = stringPreferencesKey("APPS_FLYER_CONVERSION_DATA")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
core/ui/src/main/res/drawable/img_hot_wallet_onboarding.webp
Normal file
BIN
core/ui/src/main/res/drawable/img_hot_wallet_onboarding.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
20
data/appsflyer/build.gradle.kts
Normal file
20
data/appsflyer/build.gradle.kts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
|
alias(deps.plugins.kotlin.kapt)
|
||||||
|
alias(deps.plugins.hilt.android)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tangem.data.appsflyer"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
|
||||||
|
implementation(projects.domain.appsflyer)
|
||||||
|
|
||||||
|
implementation(deps.hilt.android)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.tangem.data.appsflyer
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||||
|
import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource
|
||||||
|
import com.tangem.domain.appsflyer.repository.AppsFlyerRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
import com.tangem.datasource.local.appsflyer.AppsFlyerDeeplinkSource as StoreDeeplinkSource
|
||||||
|
|
||||||
|
internal class DefaultAppsFlyerRepository @Inject constructor(
|
||||||
|
private val appsFlyerStore: AppsFlyerStore,
|
||||||
|
) : AppsFlyerRepository {
|
||||||
|
|
||||||
|
override suspend fun clearDeeplink(source: AppsFlyerDeeplinkSource) {
|
||||||
|
appsFlyerStore.clearDeeplink(source.toStoreSource())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AppsFlyerDeeplinkSource.toStoreSource() = when (this) {
|
||||||
|
AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding -> StoreDeeplinkSource.TangemPayHotWalletOnboarding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.tangem.data.appsflyer.di
|
||||||
|
|
||||||
|
import com.tangem.data.appsflyer.DefaultAppsFlyerRepository
|
||||||
|
import com.tangem.domain.appsflyer.repository.AppsFlyerRepository
|
||||||
|
import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface AppsFlyerDataModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindAppsFlyerRepository(repository: DefaultAppsFlyerRepository): AppsFlyerRepository
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideClearAppsFlyerDeeplinkUseCase(
|
||||||
|
appsFlyerRepository: AppsFlyerRepository,
|
||||||
|
): ClearAppsFlyerDeeplinkUseCase {
|
||||||
|
return ClearAppsFlyerDeeplinkUseCase(appsFlyerRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,10 +26,7 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||||
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
|
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
|
||||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||||
import com.tangem.domain.pay.repository.*
|
import com.tangem.domain.pay.repository.*
|
||||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
import com.tangem.domain.pay.usecase.*
|
||||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
|
||||||
import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase
|
|
||||||
import com.tangem.domain.pay.usecase.UpdateTangemPayCardNameUseCase
|
|
||||||
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
|
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
|
||||||
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
||||||
import com.tangem.domain.tangempay.TangemPayWithdrawUseCase
|
import com.tangem.domain.tangempay.TangemPayWithdrawUseCase
|
||||||
|
|
|
||||||
13
domain/appsflyer/build.gradle.kts
Normal file
13
domain/appsflyer/build.gradle.kts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tangem.domain.appsflyer"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.tangem.domain.appsflyer
|
||||||
|
|
||||||
|
enum class AppsFlyerDeeplinkSource {
|
||||||
|
TangemPayHotWalletOnboarding,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.domain.appsflyer.repository
|
||||||
|
|
||||||
|
import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource
|
||||||
|
|
||||||
|
interface AppsFlyerRepository {
|
||||||
|
|
||||||
|
suspend fun clearDeeplink(source: AppsFlyerDeeplinkSource)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.domain.appsflyer.usecase
|
||||||
|
|
||||||
|
import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource
|
||||||
|
import com.tangem.domain.appsflyer.repository.AppsFlyerRepository
|
||||||
|
|
||||||
|
class ClearAppsFlyerDeeplinkUseCase(
|
||||||
|
private val appsFlyerRepository: AppsFlyerRepository,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(source: AppsFlyerDeeplinkSource) {
|
||||||
|
appsFlyerRepository.clearDeeplink(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ dependencies {
|
||||||
implementation(projects.domain.core)
|
implementation(projects.domain.core)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
|
||||||
/** Security */
|
/** Security */
|
||||||
implementation(deps.spongecastle.core)
|
implementation(deps.spongecastle.core)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.tangem.domain.wallets.usecase
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.wallets.builder.HotUserWalletBuilder
|
||||||
|
import com.tangem.hot.sdk.TangemHotSdk
|
||||||
|
import com.tangem.hot.sdk.model.HotAuth
|
||||||
|
import com.tangem.hot.sdk.model.MnemonicType
|
||||||
|
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CreateHotWalletUseCase @Inject constructor(
|
||||||
|
private val tangemHotSdk: TangemHotSdk,
|
||||||
|
private val hotUserWalletBuilderFactory: HotUserWalletBuilder.Factory,
|
||||||
|
private val saveWalletUseCase: SaveWalletUseCase,
|
||||||
|
private val syncWalletWithRemoteUseCase: SyncWalletWithRemoteUseCase,
|
||||||
|
private val appCoroutineScope: AppCoroutineScope,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(auth: HotAuth, mnemonicType: MnemonicType): Either<Throwable, UserWallet.Hot> {
|
||||||
|
return Either.catch {
|
||||||
|
val hotWalletId = tangemHotSdk.generateWallet(auth, mnemonicType)
|
||||||
|
val userWallet = hotUserWalletBuilderFactory.create(hotWalletId).build()
|
||||||
|
|
||||||
|
saveWalletUseCase(userWallet)
|
||||||
|
|
||||||
|
appCoroutineScope.launch {
|
||||||
|
syncWalletWithRemoteUseCase(userWalletId = userWallet.walletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
userWallet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,9 @@ dependencies {
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
|
|
||||||
|
/* Common */
|
||||||
|
implementation(projects.common.routing)
|
||||||
|
|
||||||
/* Compose */
|
/* Compose */
|
||||||
implementation(deps.compose.runtime)
|
implementation(deps.compose.runtime)
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.disclaimer.api.components
|
package com.tangem.features.disclaimer.api.components
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
|
||||||
|
|
@ -8,5 +9,6 @@ interface DisclaimerComponent : ComposableContentComponent {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val isTosAccepted: Boolean,
|
val isTosAccepted: Boolean,
|
||||||
|
val nextRoute: AppRoute? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +48,11 @@ internal class DisclaimerModel @Inject constructor(
|
||||||
router.pop()
|
router.pop()
|
||||||
} else {
|
} else {
|
||||||
cardRepository.acceptTangemTOS()
|
cardRepository.acceptTangemTOS()
|
||||||
|
val nextRoute = params.nextRoute
|
||||||
|
if (nextRoute != null) {
|
||||||
|
router.replaceAll(nextRoute)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
||||||
if (shouldAskPushPermission) {
|
if (shouldAskPushPermission) {
|
||||||
notificationsRepository.setShouldShowNotifications(
|
notificationsRepository.setShouldShowNotifications(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.hotwallet
|
package com.tangem.features.hotwallet
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
@ -9,9 +10,9 @@ interface CreateWalletBackupComponent : ComposableContentComponent {
|
||||||
data class Params(
|
data class Params(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val isUpgradeFlow: Boolean,
|
val isUpgradeFlow: Boolean,
|
||||||
val shouldSetAccessCode: Boolean,
|
|
||||||
val analyticsSource: String,
|
val analyticsSource: String,
|
||||||
val analyticsAction: String,
|
val analyticsAction: String,
|
||||||
|
val nextScreen: AppRoute? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, CreateWalletBackupComponent>
|
interface Factory : ComponentFactory<Params, CreateWalletBackupComponent>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.hotwallet
|
package com.tangem.features.hotwallet
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
@ -8,6 +9,7 @@ interface UpdateAccessCodeComponent : ComposableContentComponent {
|
||||||
data class Params(
|
data class Params(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val source: String,
|
val source: String,
|
||||||
|
val nextScreen: AppRoute? = null,
|
||||||
)
|
)
|
||||||
interface Factory : ComponentFactory<Params, UpdateAccessCodeComponent>
|
interface Factory : ComponentFactory<Params, UpdateAccessCodeComponent>
|
||||||
}
|
}
|
||||||
|
|
@ -97,19 +97,15 @@ internal class CreateWalletBackupModel @Inject constructor(
|
||||||
stackNavigation.push(
|
stackNavigation.push(
|
||||||
configuration = CreateWalletBackupRoute.BackupCompleted(
|
configuration = CreateWalletBackupRoute.BackupCompleted(
|
||||||
isUpgradeFlow = params.isUpgradeFlow,
|
isUpgradeFlow = params.isUpgradeFlow,
|
||||||
isLastScreen = !params.shouldSetAccessCode,
|
isLastScreen = params.nextScreen == null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onManualBackupCompleted() {
|
fun onManualBackupCompleted() {
|
||||||
if (params.shouldSetAccessCode) {
|
val nextScreen = params.nextScreen
|
||||||
router.replaceCurrent(
|
if (nextScreen != null) {
|
||||||
route = AppRoute.UpdateAccessCode(
|
router.replaceCurrent(nextScreen)
|
||||||
userWalletId = params.userWalletId,
|
|
||||||
source = params.analyticsSource,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
router.pop()
|
router.pop()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,12 @@ internal class UpdateAccessCodeModel @Inject constructor(
|
||||||
|
|
||||||
inner class MobileWalletSetupFinishedComponentModelCallbacks : MobileWalletSetupFinishedComponent.ModelCallbacks {
|
inner class MobileWalletSetupFinishedComponentModelCallbacks : MobileWalletSetupFinishedComponent.ModelCallbacks {
|
||||||
override fun onFinishClick() {
|
override fun onFinishClick() {
|
||||||
router.pop()
|
val nextScreen = params.nextScreen
|
||||||
|
if (nextScreen != null) {
|
||||||
|
router.replaceCurrent(nextScreen)
|
||||||
|
} else {
|
||||||
|
router.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.features.tangempay.components
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
|
||||||
|
interface TangemPayHotWalletOnboardingComponent : ComposableContentComponent {
|
||||||
|
interface Factory : ComponentFactory<Unit, TangemPayHotWalletOnboardingComponent>
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,10 @@ interface TangemPayOnboardingComponent : ComposableContentComponent {
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
) : Params()
|
) : Params()
|
||||||
|
|
||||||
|
data class HotWalletOnboarding(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
) : Params()
|
||||||
|
|
||||||
data object FromBannerOnMain : Params()
|
data object FromBannerOnMain : Params()
|
||||||
|
|
||||||
data object FromBannerInSettings : Params()
|
data object FromBannerInSettings : Params()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ android {
|
||||||
namespace = "com.tangem.features.tangempay.onboarding.impl"
|
namespace = "com.tangem.features.tangempay.onboarding.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
/** Core */
|
/** Core */
|
||||||
implementation(projects.core.analytics)
|
implementation(projects.core.analytics)
|
||||||
|
|
@ -32,8 +36,14 @@ dependencies {
|
||||||
implementation(projects.features.hotWallet.api)
|
implementation(projects.features.hotWallet.api)
|
||||||
|
|
||||||
/** Domain */
|
/** Domain */
|
||||||
|
implementation(projects.domain.appsflyer)
|
||||||
implementation(projects.domain.visa)
|
implementation(projects.domain.visa)
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
|
implementation(projects.domain.wallets.models)
|
||||||
|
implementation(projects.domain.hotWallet)
|
||||||
|
|
||||||
|
/** Libs */
|
||||||
|
implementation(tangemDeps.hot.core)
|
||||||
|
|
||||||
/** Data **/
|
/** Data **/
|
||||||
implementation(projects.data.visa)
|
implementation(projects.data.visa)
|
||||||
|
|
@ -52,4 +62,11 @@ dependencies {
|
||||||
/** Other */
|
/** Other */
|
||||||
implementation(deps.arrow.core)
|
implementation(deps.arrow.core)
|
||||||
implementation(deps.kotlin.immutable.collections)
|
implementation(deps.kotlin.immutable.collections)
|
||||||
|
|
||||||
|
/** Test */
|
||||||
|
testImplementation(deps.test.junit5)
|
||||||
|
testRuntimeOnly(deps.test.junit5.engine)
|
||||||
|
testImplementation(deps.test.mockk)
|
||||||
|
testImplementation(deps.test.truth)
|
||||||
|
testImplementation(deps.test.coroutine)
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.features.tangempay.di
|
package com.tangem.features.tangempay.di
|
||||||
|
|
||||||
import com.tangem.features.tangempay.components.DefaultTangemPayOnboardingComponent
|
import com.tangem.features.tangempay.components.DefaultTangemPayOnboardingComponent
|
||||||
|
import com.tangem.features.tangempay.components.TangemPayHotWalletOnboardingComponent
|
||||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||||
|
import com.tangem.features.tangempay.hotwallet.DefaultTangemPayHotWalletOnboardingComponent
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -13,4 +15,9 @@ internal interface TangemPayOnboardingFeatureModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindFactory(impl: DefaultTangemPayOnboardingComponent.Factory): TangemPayOnboardingComponent.Factory
|
fun bindFactory(impl: DefaultTangemPayOnboardingComponent.Factory): TangemPayOnboardingComponent.Factory
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindHotWalletOnboardingFactory(
|
||||||
|
impl: DefaultTangemPayHotWalletOnboardingComponent.Factory,
|
||||||
|
): TangemPayHotWalletOnboardingComponent.Factory
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.tangempay.di
|
||||||
|
|
||||||
import com.tangem.core.decompose.di.ModelComponent
|
import com.tangem.core.decompose.di.ModelComponent
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.features.tangempay.hotwallet.TangemPayHotWalletOnboardingModel
|
||||||
import com.tangem.features.tangempay.model.TangemPayOnboardingModel
|
import com.tangem.features.tangempay.model.TangemPayOnboardingModel
|
||||||
import com.tangem.features.tangempay.model.TangemPayWalletSelectorModel
|
import com.tangem.features.tangempay.model.TangemPayWalletSelectorModel
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
|
|
@ -23,4 +24,9 @@ internal interface TangemPayOnboardingModelsModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ClassKey(TangemPayWalletSelectorModel::class)
|
@ClassKey(TangemPayWalletSelectorModel::class)
|
||||||
fun bindTangemPayWalletSelectorModel(model: TangemPayWalletSelectorModel): Model
|
fun bindTangemPayWalletSelectorModel(model: TangemPayWalletSelectorModel): Model
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(TangemPayHotWalletOnboardingModel::class)
|
||||||
|
fun bindHotWalletOnboardingModel(model: TangemPayHotWalletOnboardingModel): Model
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.tangem.features.tangempay.hotwallet
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||||
|
import com.tangem.core.ui.res.ForceDarkTheme
|
||||||
|
import com.tangem.features.tangempay.components.TangemPayHotWalletOnboardingComponent
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
|
internal class DefaultTangemPayHotWalletOnboardingComponent @AssistedInject constructor(
|
||||||
|
@Assisted private val context: AppComponentContext,
|
||||||
|
@Assisted private val params: Unit,
|
||||||
|
) : TangemPayHotWalletOnboardingComponent, AppComponentContext by context {
|
||||||
|
|
||||||
|
private val model: TangemPayHotWalletOnboardingModel = getOrCreateModel(params)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content(modifier: Modifier) {
|
||||||
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
SystemBarsIconsDisposable(darkIcons = false)
|
||||||
|
ForceDarkTheme {
|
||||||
|
TangemPayHotWalletOnboardingScreen(
|
||||||
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : TangemPayHotWalletOnboardingComponent.Factory {
|
||||||
|
override fun create(context: AppComponentContext, params: Unit): DefaultTangemPayHotWalletOnboardingComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
package com.tangem.features.tangempay.hotwallet
|
||||||
|
|
||||||
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.core.decompose.navigation.Router
|
||||||
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.message.DialogMessage
|
||||||
|
import com.tangem.core.ui.message.dialog.Dialogs
|
||||||
|
import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource
|
||||||
|
import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase
|
||||||
|
import com.tangem.domain.hotwallet.IsHotWalletCreationSupported
|
||||||
|
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
|
||||||
|
import com.tangem.domain.wallets.usecase.CreateHotWalletUseCase
|
||||||
|
import com.tangem.features.tangempay.TangemPayConstants
|
||||||
|
import com.tangem.features.tangempay.onboarding.api.R
|
||||||
|
import com.tangem.hot.sdk.model.HotAuth
|
||||||
|
import com.tangem.hot.sdk.model.MnemonicType
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import com.tangem.utils.coroutines.runSuspendCatching
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
@ModelScoped
|
||||||
|
internal class TangemPayHotWalletOnboardingModel @Inject constructor(
|
||||||
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val createHotWalletUseCase: CreateHotWalletUseCase,
|
||||||
|
private val isHotWalletCreationSupported: IsHotWalletCreationSupported,
|
||||||
|
private val clearAppsFlyerDeeplinkUseCase: ClearAppsFlyerDeeplinkUseCase,
|
||||||
|
private val router: Router,
|
||||||
|
private val uiMessageSender: UiMessageSender,
|
||||||
|
private val urlOpener: UrlOpener,
|
||||||
|
) : Model() {
|
||||||
|
|
||||||
|
val uiState: StateFlow<TangemPayHotWalletOnboardingUM>
|
||||||
|
field = MutableStateFlow(
|
||||||
|
TangemPayHotWalletOnboardingUM(
|
||||||
|
isLoading = false,
|
||||||
|
onGetCardClick = ::onGetCardClick,
|
||||||
|
onTermsClick = ::onTermsClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun onTermsClick() {
|
||||||
|
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onGetCardClick() {
|
||||||
|
uiState.update { it.copy(isLoading = true) }
|
||||||
|
|
||||||
|
if (!isHotWalletCreationSupported()) {
|
||||||
|
uiMessageSender.send(
|
||||||
|
Dialogs.hotWalletCreationNotSupportedDialog(isHotWalletCreationSupported.getLeastVersionName()),
|
||||||
|
)
|
||||||
|
modelScope.launch { clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) }
|
||||||
|
router.replaceCurrent(AppRoute.Home())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modelScope.launch {
|
||||||
|
runSuspendCatching {
|
||||||
|
val userWallet = createHotWalletUseCase.invoke(
|
||||||
|
auth = HotAuth.NoAuth,
|
||||||
|
mnemonicType = MnemonicType.Words12,
|
||||||
|
).getOrElse { throw it }
|
||||||
|
|
||||||
|
clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding)
|
||||||
|
|
||||||
|
router.replaceCurrent(
|
||||||
|
AppRoute.CreateWalletBackup(
|
||||||
|
userWalletId = userWallet.walletId,
|
||||||
|
analyticsSource = AnalyticsParam.ScreensSources.TangemPayHotWalletOnboarding.value,
|
||||||
|
analyticsAction = WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction.Backup.value,
|
||||||
|
nextScreen = AppRoute.UpdateAccessCode(
|
||||||
|
userWalletId = userWallet.walletId,
|
||||||
|
source = AnalyticsParam.ScreensSources.TangemPayHotWalletOnboarding.value,
|
||||||
|
nextScreen = AppRoute.TangemPayOnboarding(
|
||||||
|
mode = AppRoute.TangemPayOnboarding.Mode.FirstSetup(userWallet.walletId),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}.onFailure {
|
||||||
|
uiState.update { state -> state.copy(isLoading = false) }
|
||||||
|
uiMessageSender.send(
|
||||||
|
DialogMessage(
|
||||||
|
title = TextReference.Res(R.string.common_something_went_wrong),
|
||||||
|
message = TextReference.Res(R.string.common_unknown_error),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package com.tangem.features.tangempay.hotwallet
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||||
|
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.components.TextButton
|
||||||
|
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||||
|
import com.tangem.features.tangempay.ui.TangemPayOnboardingBlock
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TangemPayHotWalletOnboardingScreen(state: TangemPayHotWalletOnboardingUM, modifier: Modifier = Modifier) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
contentWindowInsets = WindowInsetsZero,
|
||||||
|
content = { paddingValues ->
|
||||||
|
Content(
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Content(state: TangemPayHotWalletOnboardingUM, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
TangemTheme.colors.background.primary,
|
||||||
|
Color.Black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.systemBarsPadding()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(40.dp),
|
||||||
|
text = stringResourceSafe(R.string.tangempay_onboarding_title),
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
painter = painterResource(R.drawable.img_hot_wallet_onboarding),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
)
|
||||||
|
Features(modifier = Modifier.padding(horizontal = 40.dp))
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
NavigationPrimaryButton(
|
||||||
|
primaryButton = NavigationButton(
|
||||||
|
textReference = resourceReference(R.string.tangempay_onboarding_get_card_button_text),
|
||||||
|
iconRes = R.drawable.ic_tangem_24,
|
||||||
|
isIconVisible = true,
|
||||||
|
shouldShowProgress = state.isLoading,
|
||||||
|
onClick = state.onGetCardClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
text = stringResourceSafe(R.string.tangem_pay_terms_fees_limits),
|
||||||
|
onClick = state.onTermsClick,
|
||||||
|
colors = TangemButtonsDefaults.defaultTextButtonColors.copy(
|
||||||
|
contentColor = TangemTheme.colors.text.primary1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Features(modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||||
|
) {
|
||||||
|
TangemPayOnboardingBlock(
|
||||||
|
painterRes = R.drawable.ic_mobile_wallet_icon_24,
|
||||||
|
titleRef = TextReference.Res(R.string.tangempay_onboarding_setup_wallet_title),
|
||||||
|
descriptionRef = TextReference.Res(R.string.tangempay_onboarding_setup_wallet_description),
|
||||||
|
)
|
||||||
|
TangemPayOnboardingBlock(
|
||||||
|
painterRes = R.drawable.ic_shopping_basket_24,
|
||||||
|
titleRef = TextReference.Res(R.string.tangempay_onboarding_purchases_title),
|
||||||
|
descriptionRef = TextReference.Res(R.string.tangempay_onboarding_purchases_description),
|
||||||
|
)
|
||||||
|
TangemPayOnboardingBlock(
|
||||||
|
painterRes = R.drawable.ic_credit_card_add_24,
|
||||||
|
titleRef = TextReference.Res(R.string.tangempay_onboarding_pay_title),
|
||||||
|
descriptionRef = TextReference.Res(R.string.tangempay_onboarding_pay_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
TangemPayHotWalletOnboardingScreen(
|
||||||
|
state = TangemPayHotWalletOnboardingUM(
|
||||||
|
isLoading = false,
|
||||||
|
onGetCardClick = {},
|
||||||
|
onTermsClick = {},
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.tangem.features.tangempay.hotwallet
|
||||||
|
|
||||||
|
internal data class TangemPayHotWalletOnboardingUM(
|
||||||
|
val isLoading: Boolean,
|
||||||
|
val onGetCardClick: () -> Unit,
|
||||||
|
val onTermsClick: () -> Unit,
|
||||||
|
)
|
||||||
|
|
@ -70,6 +70,9 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
|
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
|
||||||
openKyc(userWalletId = params.userWalletId)
|
openKyc(userWalletId = params.userWalletId)
|
||||||
}
|
}
|
||||||
|
is TangemPayOnboardingComponent.Params.HotWalletOnboarding -> {
|
||||||
|
startOnboarding(userWalletId = params.userWalletId)
|
||||||
|
}
|
||||||
is TangemPayOnboardingComponent.Params.FromBannerInSettings,
|
is TangemPayOnboardingComponent.Params.FromBannerInSettings,
|
||||||
is TangemPayOnboardingComponent.Params.FromBannerOnMain,
|
is TangemPayOnboardingComponent.Params.FromBannerOnMain,
|
||||||
-> showOnboarding()
|
-> showOnboarding()
|
||||||
|
|
@ -104,7 +107,9 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (params) {
|
when (params) {
|
||||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> openKyc(userWalletId)
|
is TangemPayOnboardingComponent.Params.ContinueOnboarding,
|
||||||
|
is TangemPayOnboardingComponent.Params.HotWalletOnboarding,
|
||||||
|
-> openKyc(userWalletId)
|
||||||
else -> startOnboarding(userWalletId)
|
else -> startOnboarding(userWalletId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +232,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
|
|
||||||
is TangemPayOnboardingComponent.Params.Deeplink,
|
is TangemPayOnboardingComponent.Params.Deeplink,
|
||||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding,
|
is TangemPayOnboardingComponent.Params.ContinueOnboarding,
|
||||||
|
is TangemPayOnboardingComponent.Params.HotWalletOnboarding,
|
||||||
-> null
|
-> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.features.tangempay.ui
|
package com.tangem.features.tangempay.ui
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -9,7 +8,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -23,14 +21,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
|
||||||
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.SecondaryButton
|
|
||||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
@ -120,9 +113,10 @@ private fun TangemPayOnboardingContent(state: TangemPayOnboardingScreenState.Con
|
||||||
.padding(horizontal = 12.dp),
|
.padding(horizontal = 12.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
FooterButtons(
|
TangemPayOnboardingButtons(
|
||||||
modifier = Modifier.padding(bottom = 16.dp),
|
modifier = Modifier.padding(bottom = 16.dp),
|
||||||
primaryButtonConfig = state.buttonConfig,
|
onGetCardClick = state.buttonConfig.onClick,
|
||||||
|
isLoading = state.buttonConfig.isLoading,
|
||||||
onTermsClick = state.onTermsClick,
|
onTermsClick = state.onTermsClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -154,63 +148,6 @@ internal fun TangemPayOnboardingBlocks(modifier: Modifier = Modifier) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TangemPayOnboardingBlock(
|
|
||||||
@DrawableRes painterRes: Int,
|
|
||||||
titleRef: TextReference,
|
|
||||||
descriptionRef: TextReference,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Row(modifier = modifier) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = painterRes),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(width = 24.dp, height = 24.dp),
|
|
||||||
tint = TangemTheme.colors.icon.accent,
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 12.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = titleRef.resolveReference(),
|
|
||||||
style = TangemTheme.typography.subtitle1,
|
|
||||||
color = TangemTheme.colors.text.primary1,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = descriptionRef.resolveReference(),
|
|
||||||
style = TangemTheme.typography.body2,
|
|
||||||
color = TangemTheme.colors.text.secondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun FooterButtons(
|
|
||||||
primaryButtonConfig: TangemPayOnboardingScreenState.Content.ButtonConfig,
|
|
||||||
onTermsClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
SecondaryButton(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
text = stringResourceSafe(R.string.tangem_pay_terms_fees_limits),
|
|
||||||
onClick = onTermsClick,
|
|
||||||
)
|
|
||||||
NavigationPrimaryButton(
|
|
||||||
primaryButton = NavigationButton(
|
|
||||||
textReference = resourceReference(R.string.tangempay_onboarding_get_card_button_text),
|
|
||||||
iconRes = R.drawable.ic_tangem_24,
|
|
||||||
isIconVisible = true,
|
|
||||||
shouldShowProgress = primaryButtonConfig.isLoading,
|
|
||||||
onClick = primaryButtonConfig.onClick,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.tangem.features.tangempay.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||||
|
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.components.SecondaryButton
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TangemPayOnboardingButtons(
|
||||||
|
onGetCardClick: () -> Unit,
|
||||||
|
isLoading: Boolean,
|
||||||
|
onTermsClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
SecondaryButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
text = stringResourceSafe(R.string.tangem_pay_terms_fees_limits),
|
||||||
|
onClick = onTermsClick,
|
||||||
|
)
|
||||||
|
NavigationPrimaryButton(
|
||||||
|
primaryButton = NavigationButton(
|
||||||
|
textReference = resourceReference(R.string.tangempay_onboarding_get_card_button_text),
|
||||||
|
iconRes = R.drawable.ic_tangem_24,
|
||||||
|
isIconVisible = true,
|
||||||
|
shouldShowProgress = isLoading,
|
||||||
|
onClick = onGetCardClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.tangem.features.tangempay.ui
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TangemPayOnboardingBlock(
|
||||||
|
@DrawableRes painterRes: Int,
|
||||||
|
titleRef: TextReference,
|
||||||
|
descriptionRef: TextReference,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(modifier = modifier) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = painterRes),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(width = 24.dp, height = 24.dp),
|
||||||
|
tint = TangemTheme.colors.icon.accent,
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 12.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = titleRef.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle1,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = descriptionRef.resolveReference(),
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
package com.tangem.features.tangempay.hotwallet
|
||||||
|
|
||||||
|
import arrow.core.left
|
||||||
|
import arrow.core.right
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.core.decompose.navigation.Router
|
||||||
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
|
import com.tangem.core.ui.message.DialogMessage
|
||||||
|
import com.tangem.domain.appsflyer.AppsFlyerDeeplinkSource
|
||||||
|
import com.tangem.domain.appsflyer.usecase.ClearAppsFlyerDeeplinkUseCase
|
||||||
|
import com.tangem.domain.hotwallet.IsHotWalletCreationSupported
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.wallets.usecase.CreateHotWalletUseCase
|
||||||
|
import com.tangem.features.tangempay.TangemPayConstants
|
||||||
|
import com.tangem.hot.sdk.model.HotAuth
|
||||||
|
import com.tangem.hot.sdk.model.MnemonicType
|
||||||
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
|
import io.mockk.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Nested
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class TangemPayHotWalletOnboardingModelTest {
|
||||||
|
|
||||||
|
private val createHotWalletUseCase: CreateHotWalletUseCase = mockk()
|
||||||
|
private val isHotWalletCreationSupported: IsHotWalletCreationSupported = mockk() {
|
||||||
|
every { getLeastVersionName() } returns "Android 10"
|
||||||
|
}
|
||||||
|
private val clearAppsFlyerDeeplinkUseCase: ClearAppsFlyerDeeplinkUseCase = mockk()
|
||||||
|
private val router: Router = mockk(relaxed = true)
|
||||||
|
private val uiMessageSender: UiMessageSender = mockk(relaxed = true)
|
||||||
|
private val urlOpener: UrlOpener = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private val testUserWalletId = UserWalletId("1234567890ABCDEF")
|
||||||
|
private val testUserWallet: UserWallet.Hot = mockk(relaxed = true) {
|
||||||
|
every { walletId } returns testUserWalletId
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class OnTermsClick {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WHEN onTermsClick THEN urlOpener called with terms link`() = runTest {
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
model.uiState.value.onTermsClick.invoke()
|
||||||
|
|
||||||
|
verify { urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class OnGetCardClick {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN hot wallet creation not supported WHEN onGetCardClick THEN wallet creation not attempted`() =
|
||||||
|
runTest {
|
||||||
|
every { isHotWalletCreationSupported() } returns false
|
||||||
|
coEvery { clearAppsFlyerDeeplinkUseCase(any()) } just Runs
|
||||||
|
|
||||||
|
val model = createModel()
|
||||||
|
model.uiState.value.onGetCardClick.invoke()
|
||||||
|
|
||||||
|
verify { uiMessageSender.send(any()) }
|
||||||
|
verify { router.replaceCurrent(AppRoute.Home()) }
|
||||||
|
coVerify { clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) }
|
||||||
|
coVerify(exactly = 0) { createHotWalletUseCase(any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN hot wallet supported AND wallet creation succeeds WHEN onGetCardClick THEN deeplink cleared AND navigate to CreateWalletBackup`() =
|
||||||
|
runTest {
|
||||||
|
every { isHotWalletCreationSupported() } returns true
|
||||||
|
coEvery { createHotWalletUseCase(HotAuth.NoAuth, MnemonicType.Words12) } returns testUserWallet.right()
|
||||||
|
coEvery { clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) } just Runs
|
||||||
|
|
||||||
|
val model = createModel()
|
||||||
|
model.uiState.value.onGetCardClick.invoke()
|
||||||
|
|
||||||
|
coVerify { clearAppsFlyerDeeplinkUseCase(AppsFlyerDeeplinkSource.TangemPayHotWalletOnboarding) }
|
||||||
|
verify {
|
||||||
|
router.replaceCurrent(
|
||||||
|
match { it is AppRoute.CreateWalletBackup && it.userWalletId == testUserWalletId },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN hot wallet supported AND wallet creation fails WHEN onGetCardClick THEN error dialog sent`() =
|
||||||
|
runTest {
|
||||||
|
every { isHotWalletCreationSupported() } returns true
|
||||||
|
coEvery {
|
||||||
|
createHotWalletUseCase(HotAuth.NoAuth, MnemonicType.Words12)
|
||||||
|
} returns RuntimeException("error").left()
|
||||||
|
|
||||||
|
val model = createModel()
|
||||||
|
model.uiState.value.onGetCardClick.invoke()
|
||||||
|
|
||||||
|
assertThat(model.uiState.value.isLoading).isFalse()
|
||||||
|
verify { uiMessageSender.send(match<DialogMessage> { true }) }
|
||||||
|
coVerify(exactly = 0) { clearAppsFlyerDeeplinkUseCase(any()) }
|
||||||
|
verify(exactly = 0) { router.replaceCurrent(any()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createModel(): TangemPayHotWalletOnboardingModel {
|
||||||
|
return TangemPayHotWalletOnboardingModel(
|
||||||
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
|
createHotWalletUseCase = createHotWalletUseCase,
|
||||||
|
isHotWalletCreationSupported = isHotWalletCreationSupported,
|
||||||
|
clearAppsFlyerDeeplinkUseCase = clearAppsFlyerDeeplinkUseCase,
|
||||||
|
router = router,
|
||||||
|
uiMessageSender = uiMessageSender,
|
||||||
|
urlOpener = urlOpener,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -432,9 +432,12 @@ internal class WalletSettingsModel @Inject constructor(
|
||||||
AppRoute.CreateWalletBackup(
|
AppRoute.CreateWalletBackup(
|
||||||
userWalletId = params.userWalletId,
|
userWalletId = params.userWalletId,
|
||||||
isUpgradeFlow = false,
|
isUpgradeFlow = false,
|
||||||
shouldSetAccessCode = true,
|
|
||||||
analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value,
|
analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value,
|
||||||
analyticsAction = RecoveryPhraseScreenAction.AccessCode.value,
|
analyticsAction = RecoveryPhraseScreenAction.AccessCode.value,
|
||||||
|
nextScreen = AppRoute.UpdateAccessCode(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
source = AnalyticsParam.ScreensSources.WalletSettings.value,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
closeBs()
|
closeBs()
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,7 @@ include(":domain:push-notification-preferences")
|
||||||
include(":domain:transaction")
|
include(":domain:transaction")
|
||||||
include(":domain:transaction:models")
|
include(":domain:transaction:models")
|
||||||
include(":domain:analytics")
|
include(":domain:analytics")
|
||||||
|
include(":domain:appsflyer")
|
||||||
include(":domain:visa")
|
include(":domain:visa")
|
||||||
include(":domain:visa:models")
|
include(":domain:visa:models")
|
||||||
include(":domain:payment")
|
include(":domain:payment")
|
||||||
|
|
@ -423,6 +424,7 @@ include(":data:txhistory")
|
||||||
include(":data:wallets")
|
include(":data:wallets")
|
||||||
include(":data:analytics")
|
include(":data:analytics")
|
||||||
include(":data:transaction")
|
include(":data:transaction")
|
||||||
|
include(":data:appsflyer")
|
||||||
include(":data:visa")
|
include(":data:visa")
|
||||||
include(":data:payment")
|
include(":data:payment")
|
||||||
include(":data:virtual-account")
|
include(":data:virtual-account")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue