Updated on 2026-08-14
This commit is contained in:
commit
b00a710ed0
17 changed files with 229 additions and 18 deletions
|
|
@ -127,7 +127,7 @@
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="onramp-success"
|
android:host="*"
|
||||||
android:scheme="tangem" />
|
android:scheme="tangem" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
||||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
|
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.coil.ImagePreloader
|
import com.tangem.core.ui.coil.ImagePreloader
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
|
@ -31,6 +32,7 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||||
|
import com.tangem.features.onramp.deeplink.OnrampDeepLink
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.setContext
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||||
|
|
@ -63,6 +65,8 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val imagePreloader: ImagePreloader,
|
private val imagePreloader: ImagePreloader,
|
||||||
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
||||||
private val onboardingRepository: OnboardingRepository,
|
private val onboardingRepository: OnboardingRepository,
|
||||||
|
private val deepLinksRegistry: DeepLinksRegistry,
|
||||||
|
private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
|
||||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
@ -97,6 +101,8 @@ internal class MainViewModel @Inject constructor(
|
||||||
sendKeyboardIdentifierEvent()
|
sendKeyboardIdentifierEvent()
|
||||||
|
|
||||||
preloadImages()
|
preloadImages()
|
||||||
|
|
||||||
|
initializeDeepLinks()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkForUnfinishedBackup() {
|
fun checkForUnfinishedBackup() {
|
||||||
|
|
@ -333,4 +339,8 @@ internal class MainViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initializeDeepLinks() {
|
||||||
|
deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +63,7 @@ interface OnrampApi {
|
||||||
@GET("onramp-data")
|
@GET("onramp-data")
|
||||||
suspend fun getData(
|
suspend fun getData(
|
||||||
@Header("user-id") userWalletId: String,
|
@Header("user-id") userWalletId: String,
|
||||||
@Header("refcode") refCode: String,
|
@Header("refcode") refCode: String?,
|
||||||
@Query("fromCurrencyCode") fromCurrencyCode: String,
|
@Query("fromCurrencyCode") fromCurrencyCode: String,
|
||||||
@Query("fromPrecision") fromPrecision: Int,
|
@Query("fromPrecision") fromPrecision: Int,
|
||||||
@Query("toContractAddress") toContractAddress: String,
|
@Query("toContractAddress") toContractAddress: String,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.data.onramp
|
package com.tangem.data.onramp
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.tangem.blockchain.extensions.toBigDecimalOrDefault
|
import com.tangem.blockchain.extensions.toBigDecimalOrDefault
|
||||||
import com.tangem.data.common.api.safeApiCall
|
import com.tangem.data.common.api.safeApiCall
|
||||||
|
|
@ -349,6 +350,7 @@ internal class DefaultOnrampRepository(
|
||||||
val currency = requireNotNull(getDefaultCurrencySync()) { "Default currency must not be null" }
|
val currency = requireNotNull(getDefaultCurrencySync()) { "Default currency must not be null" }
|
||||||
val country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" }
|
val country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" }
|
||||||
val requestId = UUID.randomUUID().toString()
|
val requestId = UUID.randomUUID().toString()
|
||||||
|
val redirectUrl = quote.provider.toRedirectUrl() ?: error("Invalid onramp redirect url")
|
||||||
val data = safeApiCall(
|
val data = safeApiCall(
|
||||||
call = {
|
call = {
|
||||||
onrampApi.getData(
|
onrampApi.getData(
|
||||||
|
|
@ -362,7 +364,7 @@ internal class DefaultOnrampRepository(
|
||||||
toDecimals = cryptoCurrency.decimals,
|
toDecimals = cryptoCurrency.decimals,
|
||||||
providerId = quote.provider.id,
|
providerId = quote.provider.id,
|
||||||
toAddress = address,
|
toAddress = address,
|
||||||
redirectUrl = "",
|
redirectUrl = redirectUrl,
|
||||||
language = null,
|
language = null,
|
||||||
theme = getTheme(isDarkTheme),
|
theme = getTheme(isDarkTheme),
|
||||||
requestId = requestId,
|
requestId = requestId,
|
||||||
|
|
@ -370,7 +372,7 @@ internal class DefaultOnrampRepository(
|
||||||
refCode = ExpressUtils.getRefCode(
|
refCode = ExpressUtils.getRefCode(
|
||||||
userWallet = userWallet,
|
userWallet = userWallet,
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
),
|
).takeUnless { it.isEmpty() },
|
||||||
).bind()
|
).bind()
|
||||||
},
|
},
|
||||||
onError = { e ->
|
onError = { e ->
|
||||||
|
|
@ -544,6 +546,11 @@ internal class DefaultOnrampRepository(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun OnrampProvider.toRedirectUrl() = Uri.Builder()
|
||||||
|
.path(REDIRECT_URL)
|
||||||
|
.appendPath(id)
|
||||||
|
.build().path
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val PAYMENT_METHODS_KEY = "onramp_payment_methods"
|
const val PAYMENT_METHODS_KEY = "onramp_payment_methods"
|
||||||
const val SELECTED_PAYMENT_METHOD_KEY = "onramp_selected_payment_method"
|
const val SELECTED_PAYMENT_METHOD_KEY = "onramp_selected_payment_method"
|
||||||
|
|
@ -553,5 +560,7 @@ internal class DefaultOnrampRepository(
|
||||||
const val CURRENCIES_KEY = "onramp_currencies"
|
const val CURRENCIES_KEY = "onramp_currencies"
|
||||||
const val PROVIDER_THEME_DARK = "dark"
|
const val PROVIDER_THEME_DARK = "dark"
|
||||||
const val PROVIDER_THEME_LIGHT = "light"
|
const val PROVIDER_THEME_LIGHT = "light"
|
||||||
|
|
||||||
|
const val REDIRECT_URL = "https://tangem.com/onramp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.onramp
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||||
|
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||||
import com.tangem.domain.onramp.model.error.OnrampError
|
import com.tangem.domain.onramp.model.error.OnrampError
|
||||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||||
|
|
@ -20,7 +21,7 @@ class GetOnrampRedirectUrlUseCase(
|
||||||
quote: OnrampProviderWithQuote.Data,
|
quote: OnrampProviderWithQuote.Data,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
): Either<OnrampError, String> {
|
): Either<OnrampError, OnrampTransaction> {
|
||||||
return Either.catch {
|
return Either.catch {
|
||||||
val transaction = repository.getOnrampData(
|
val transaction = repository.getOnrampData(
|
||||||
userWallet = userWallet,
|
userWallet = userWallet,
|
||||||
|
|
@ -29,7 +30,7 @@ class GetOnrampRedirectUrlUseCase(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
)
|
)
|
||||||
transactionRepository.storeTransaction(transaction)
|
transactionRepository.storeTransaction(transaction)
|
||||||
transaction.redirectUrl
|
transaction
|
||||||
}.mapLeft(errorResolver::resolve)
|
}.mapLeft(errorResolver::resolve)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ dependencies {
|
||||||
/* Project - Core */
|
/* Project - Core */
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
|
implementation(projects.core.deepLinks)
|
||||||
|
|
||||||
/* Project - Domain */
|
/* Project - Domain */
|
||||||
implementation(projects.domain.onramp.models)
|
implementation(projects.domain.onramp.models)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.features.onramp.deeplink
|
||||||
|
|
||||||
|
import com.tangem.core.deeplink.DeepLink
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
abstract class OnrampDeepLink : DeepLink() {
|
||||||
|
override val uri = "tangem://onramp"
|
||||||
|
|
||||||
|
interface Factory {
|
||||||
|
fun create(coroutineScope: CoroutineScope): OnrampDeepLink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ dependencies {
|
||||||
implementation(projects.core.analytics)
|
implementation(projects.core.analytics)
|
||||||
implementation(projects.core.configToggles)
|
implementation(projects.core.configToggles)
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
|
implementation(projects.core.deepLinks)
|
||||||
implementation(projects.core.navigation)
|
implementation(projects.core.navigation)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.tangem.features.onramp.deeplink
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.common.routing.AppRouter
|
||||||
|
import com.tangem.features.onramp.success.OnrampSuccessScreenTrigger
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
internal class DefaultOnrampDeepLink @AssistedInject constructor(
|
||||||
|
private val appRouter: AppRouter,
|
||||||
|
private val onrampSuccessScreenTrigger: OnrampSuccessScreenTrigger,
|
||||||
|
@Assisted private val scope: CoroutineScope,
|
||||||
|
) : OnrampDeepLink() {
|
||||||
|
|
||||||
|
override fun onReceive(params: Map<String, String>) {
|
||||||
|
val txId = params[TX_ID_KEY]
|
||||||
|
val result = OnrampRedirectResult.getResult(params[RESULT_KEY])
|
||||||
|
|
||||||
|
when {
|
||||||
|
!txId.isNullOrEmpty() -> {
|
||||||
|
// finish current onramp flow and show onramp success screen
|
||||||
|
val replaceOnrampScreens = appRouter.stack
|
||||||
|
.filterNot { it is AppRoute.Onramp }
|
||||||
|
.toMutableList()
|
||||||
|
replaceOnrampScreens.add(AppRoute.OnrampSuccess(txId))
|
||||||
|
appRouter.replaceAll(*replaceOnrampScreens.toTypedArray())
|
||||||
|
}
|
||||||
|
result != OnrampRedirectResult.Unknown -> {
|
||||||
|
scope.launch {
|
||||||
|
onrampSuccessScreenTrigger.triggerOnrampSuccess(result == OnrampRedirectResult.Success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : OnrampDeepLink.Factory {
|
||||||
|
override fun create(coroutineScope: CoroutineScope): DefaultOnrampDeepLink
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val TX_ID_KEY = "tx_id"
|
||||||
|
const val RESULT_KEY = "result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.features.onramp.deeplink
|
||||||
|
|
||||||
|
enum class OnrampRedirectResult(val value: String) {
|
||||||
|
Success("success"),
|
||||||
|
Cancel("cancel"),
|
||||||
|
Unknown(""),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getResult(result: String?): OnrampRedirectResult {
|
||||||
|
return entries.firstOrNull { it.value == result } ?: Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.tangem.features.onramp.deeplink.di
|
||||||
|
|
||||||
|
import com.tangem.features.onramp.deeplink.DefaultOnrampDeepLink
|
||||||
|
import com.tangem.features.onramp.deeplink.OnrampDeepLink
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface OnrampDeeplinkModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindFactory(impl: DefaultOnrampDeepLink.Factory): OnrampDeepLink.Factory
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package com.tangem.features.onramp.redirect.model
|
package com.tangem.features.onramp.redirect.model
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.core.decompose.navigation.Router
|
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||||
|
|
@ -13,14 +14,18 @@ import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.message.DialogMessage
|
import com.tangem.core.ui.message.DialogMessage
|
||||||
import com.tangem.domain.onramp.GetOnrampRedirectUrlUseCase
|
import com.tangem.domain.onramp.GetOnrampRedirectUrlUseCase
|
||||||
|
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||||
import com.tangem.domain.onramp.model.error.OnrampError
|
import com.tangem.domain.onramp.model.error.OnrampError
|
||||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||||
import com.tangem.features.onramp.impl.R
|
import com.tangem.features.onramp.impl.R
|
||||||
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
|
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
|
||||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM
|
import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM
|
||||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectUM
|
import com.tangem.features.onramp.redirect.entity.OnrampRedirectUM
|
||||||
|
import com.tangem.features.onramp.success.OnrampSuccessScreenListener
|
||||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -30,16 +35,19 @@ internal class OnrampRedirectModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val urlOpener: UrlOpener,
|
private val urlOpener: UrlOpener,
|
||||||
private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase,
|
private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase,
|
||||||
private val getWalletsUseCase: GetWalletsUseCase,
|
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
router: Router,
|
private val onrampSuccessScreenListener: OnrampSuccessScreenListener,
|
||||||
|
private val appRouter: AppRouter,
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
|
getWalletsUseCase: GetWalletsUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params: OnrampRedirectComponent.Params = paramsContainer.require()
|
private val params: OnrampRedirectComponent.Params = paramsContainer.require()
|
||||||
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||||
|
|
||||||
|
private var latestOnrampTransaction: OnrampTransaction? = null
|
||||||
|
|
||||||
val state = OnrampRedirectUM(
|
val state = OnrampRedirectUM(
|
||||||
topBarConfig = OnrampRedirectTopBarUM(
|
topBarConfig = OnrampRedirectTopBarUM(
|
||||||
title = combinedReference(
|
title = combinedReference(
|
||||||
|
|
@ -48,7 +56,7 @@ internal class OnrampRedirectModel @Inject constructor(
|
||||||
),
|
),
|
||||||
startButtonUM = TopAppBarButtonUM(
|
startButtonUM = TopAppBarButtonUM(
|
||||||
iconRes = R.drawable.ic_close_24,
|
iconRes = R.drawable.ic_close_24,
|
||||||
onIconClicked = router::pop,
|
onIconClicked = appRouter::pop,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -63,6 +71,10 @@ internal class OnrampRedirectModel @Inject constructor(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
subscribeToOnrampSuccessListener()
|
||||||
|
}
|
||||||
|
|
||||||
fun getRedirectUrl(isDarkTheme: Boolean) {
|
fun getRedirectUrl(isDarkTheme: Boolean) {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
getOnrampRedirectUrlUseCase.invoke(
|
getOnrampRedirectUrlUseCase.invoke(
|
||||||
|
|
@ -73,12 +85,31 @@ internal class OnrampRedirectModel @Inject constructor(
|
||||||
)
|
)
|
||||||
.onLeft(::handleError)
|
.onLeft(::handleError)
|
||||||
.onRight {
|
.onRight {
|
||||||
params.onBack()
|
latestOnrampTransaction = it
|
||||||
urlOpener.openUrl(it)
|
urlOpener.openUrl(it.redirectUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun subscribeToOnrampSuccessListener() {
|
||||||
|
onrampSuccessScreenListener.onrampSuccessTriggerFlow
|
||||||
|
.onEach { result ->
|
||||||
|
val latestTxId = latestOnrampTransaction?.txId
|
||||||
|
if (latestTxId != null && result) {
|
||||||
|
// Finish current onramp flow and show onramp success screen
|
||||||
|
val replaceOnrampScreens = appRouter.stack
|
||||||
|
.filterNot { it is AppRoute.Onramp }
|
||||||
|
.toMutableList()
|
||||||
|
replaceOnrampScreens.add(AppRoute.OnrampSuccess(latestTxId))
|
||||||
|
appRouter.replaceAll(*replaceOnrampScreens.toTypedArray())
|
||||||
|
} else {
|
||||||
|
// Close redirect screen and show last onramp state
|
||||||
|
params.onBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleError(error: OnrampError) {
|
private fun handleError(error: OnrampError) {
|
||||||
Timber.e(error.toString())
|
Timber.e(error.toString())
|
||||||
analyticsEventHandler.sendOnrampErrorEvent(
|
analyticsEventHandler.sendOnrampErrorEvent(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.tangem.features.onramp.success
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
interface OnrampSuccessScreenListener {
|
||||||
|
val onrampSuccessTriggerFlow: Flow<Boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnrampSuccessScreenTrigger {
|
||||||
|
suspend fun triggerOnrampSuccess(result: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
internal class DefaultOnrampSuccessScreenTrigger @Inject constructor() :
|
||||||
|
OnrampSuccessScreenTrigger,
|
||||||
|
OnrampSuccessScreenListener {
|
||||||
|
|
||||||
|
override val onrampSuccessTriggerFlow = MutableSharedFlow<Boolean>()
|
||||||
|
|
||||||
|
override suspend fun triggerOnrampSuccess(result: Boolean) {
|
||||||
|
onrampSuccessTriggerFlow.emit(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,9 @@ package com.tangem.features.onramp.success.di
|
||||||
|
|
||||||
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
||||||
import com.tangem.features.onramp.success.DefaultOnrampSuccessComponent
|
import com.tangem.features.onramp.success.DefaultOnrampSuccessComponent
|
||||||
|
import com.tangem.features.onramp.success.DefaultOnrampSuccessScreenTrigger
|
||||||
|
import com.tangem.features.onramp.success.OnrampSuccessScreenListener
|
||||||
|
import com.tangem.features.onramp.success.OnrampSuccessScreenTrigger
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -16,4 +19,12 @@ internal interface OnrampSuccessComponentModule {
|
||||||
fun bindOnrampSuccessComponentFactory(
|
fun bindOnrampSuccessComponentFactory(
|
||||||
factory: DefaultOnrampSuccessComponent.Factory,
|
factory: DefaultOnrampSuccessComponent.Factory,
|
||||||
): OnrampSuccessComponent.Factory
|
): OnrampSuccessComponent.Factory
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindOnrampSuccessScreenTrigger(impl: DefaultOnrampSuccessScreenTrigger): OnrampSuccessScreenTrigger
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindOnrampSuccessScreenListener(impl: DefaultOnrampSuccessScreenTrigger): OnrampSuccessScreenListener
|
||||||
}
|
}
|
||||||
|
|
@ -2,4 +2,5 @@ package com.tangem.features.onramp.success.entity
|
||||||
|
|
||||||
interface OnrampSuccessClickIntents {
|
interface OnrampSuccessClickIntents {
|
||||||
fun goToProviderClick(providerLink: String)
|
fun goToProviderClick(providerLink: String)
|
||||||
|
fun onCopyClick(copiedText: String)
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ internal class SetOnrampSuccessContentConverter(
|
||||||
private val cryptoCurrency: CryptoCurrency,
|
private val cryptoCurrency: CryptoCurrency,
|
||||||
private val transaction: OnrampTransaction,
|
private val transaction: OnrampTransaction,
|
||||||
private val goToProviderClick: (String) -> Unit,
|
private val goToProviderClick: (String) -> Unit,
|
||||||
|
private val onCopyClick: (String) -> Unit,
|
||||||
) : Converter<OnrampStatus, OnrampSuccessComponentUM> {
|
) : Converter<OnrampStatus, OnrampSuccessComponentUM> {
|
||||||
override fun convert(value: OnrampStatus): OnrampSuccessComponentUM {
|
override fun convert(value: OnrampStatus): OnrampSuccessComponentUM {
|
||||||
return OnrampSuccessComponentUM.Content(
|
return OnrampSuccessComponentUM.Content(
|
||||||
|
|
@ -46,8 +47,12 @@ internal class SetOnrampSuccessContentConverter(
|
||||||
),
|
),
|
||||||
providerName = stringReference(transaction.providerName),
|
providerName = stringReference(transaction.providerName),
|
||||||
providerImageUrl = transaction.providerImageUrl,
|
providerImageUrl = transaction.providerImageUrl,
|
||||||
statusBlock = convertStatuses(value.status, value.externalTxUrl),
|
statusBlock = convertStatuses(
|
||||||
notification = getNotification(value.status, value.externalTxUrl),
|
status = value.status,
|
||||||
|
externalTxId = value.externalTxId,
|
||||||
|
externalTxUrl = value.externalTxUrl,
|
||||||
|
),
|
||||||
|
notification = getNotification(status = value.status, externalTxUrl = value.externalTxUrl),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +78,11 @@ internal class SetOnrampSuccessContentConverter(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM {
|
private fun convertStatuses(
|
||||||
|
status: OnrampStatus.Status,
|
||||||
|
externalTxId: String?,
|
||||||
|
externalTxUrl: String?,
|
||||||
|
): ExpressStatusUM {
|
||||||
val statuses = with(status) {
|
val statuses = with(status) {
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
getAwaitingDepositItem(),
|
getAwaitingDepositItem(),
|
||||||
|
|
@ -85,7 +94,7 @@ internal class SetOnrampSuccessContentConverter(
|
||||||
|
|
||||||
return ExpressStatusUM(
|
return ExpressStatusUM(
|
||||||
title = resourceReference(R.string.common_transaction_status),
|
title = resourceReference(R.string.common_transaction_status),
|
||||||
link = getStatusLink(status, externalTxUrl),
|
link = getStatusLink(status = status, externalTxId = externalTxId, externalTxUrl = externalTxUrl),
|
||||||
statuses = statuses,
|
statuses = statuses,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +192,11 @@ internal class SetOnrampSuccessContentConverter(
|
||||||
state = getStatusState(OnrampStatus.Status.Sending),
|
state = getStatusState(OnrampStatus.Status.Sending),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM {
|
private fun getStatusLink(
|
||||||
|
status: OnrampStatus.Status,
|
||||||
|
externalTxId: String?,
|
||||||
|
externalTxUrl: String?,
|
||||||
|
): ExpressLinkUM {
|
||||||
if (externalTxUrl == null) return ExpressLinkUM.Empty
|
if (externalTxUrl == null) return ExpressLinkUM.Empty
|
||||||
return when (status) {
|
return when (status) {
|
||||||
OnrampStatus.Status.Verifying,
|
OnrampStatus.Status.Verifying,
|
||||||
|
|
@ -197,7 +210,15 @@ internal class SetOnrampSuccessContentConverter(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> ExpressLinkUM.Empty
|
else -> if (externalTxId != null) {
|
||||||
|
ExpressLinkUM.Content(
|
||||||
|
icon = R.drawable.ic_copy_24,
|
||||||
|
text = resourceReference(R.string.express_transaction_id, wrappedList(externalTxId)),
|
||||||
|
onClick = { onCopyClick(externalTxId) },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ExpressLinkUM.Empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.core.decompose.navigation.Router
|
import com.tangem.core.decompose.navigation.Router
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
|
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.message.DialogMessage
|
import com.tangem.core.ui.message.DialogMessage
|
||||||
|
|
@ -46,6 +47,7 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
||||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
|
private val clipboardManager: ClipboardManager,
|
||||||
private val router: Router,
|
private val router: Router,
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
) : Model(), OnrampSuccessClickIntents {
|
) : Model(), OnrampSuccessClickIntents {
|
||||||
|
|
@ -65,6 +67,10 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
||||||
urlOpener.openUrl(providerLink)
|
urlOpener.openUrl(providerLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCopyClick(copiedText: String) {
|
||||||
|
clipboardManager.setText(text = copiedText, isSensitive = false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
getOnrampTransactionUseCase(txId = params.txId)
|
getOnrampTransactionUseCase(txId = params.txId)
|
||||||
|
|
@ -124,6 +130,7 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
transaction = transaction,
|
transaction = transaction,
|
||||||
goToProviderClick = ::goToProviderClick,
|
goToProviderClick = ::goToProviderClick,
|
||||||
|
onCopyClick = ::onCopyClick,
|
||||||
).convert(status)
|
).convert(status)
|
||||||
}
|
}
|
||||||
removeTransactionIfTerminalStatus(
|
removeTransactionIfTerminalStatus(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue