diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b9e46866c1..810024d550 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -127,7 +127,7 @@
diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
index 212e39e905..c00ae2518f 100644
--- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
@@ -9,6 +9,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.ui.UiMessageSender
+import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.ui.R
import com.tangem.core.ui.coil.ImagePreloader
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.wallets.legacy.UserWalletsListManager
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.redux.global.GlobalAction
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 fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val onboardingRepository: OnboardingRepository,
+ private val deepLinksRegistry: DeepLinksRegistry,
+ private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel() {
@@ -97,6 +101,8 @@ internal class MainViewModel @Inject constructor(
sendKeyboardIdentifierEvent()
preloadImages()
+
+ initializeDeepLinks()
}
fun checkForUnfinishedBackup() {
@@ -333,4 +339,8 @@ internal class MainViewModel @Inject constructor(
)
}
}
+
+ private fun initializeDeepLinks() {
+ deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/OnrampApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/OnrampApi.kt
index 433cf8b3eb..360e713a62 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/OnrampApi.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/OnrampApi.kt
@@ -63,7 +63,7 @@ interface OnrampApi {
@GET("onramp-data")
suspend fun getData(
@Header("user-id") userWalletId: String,
- @Header("refcode") refCode: String,
+ @Header("refcode") refCode: String?,
@Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("fromPrecision") fromPrecision: Int,
@Query("toContractAddress") toContractAddress: String,
diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt
index 3001f28b09..b587d6f7e4 100644
--- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt
+++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt
@@ -1,5 +1,6 @@
package com.tangem.data.onramp
+import android.net.Uri
import com.squareup.moshi.Moshi
import com.tangem.blockchain.extensions.toBigDecimalOrDefault
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 country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" }
val requestId = UUID.randomUUID().toString()
+ val redirectUrl = quote.provider.toRedirectUrl() ?: error("Invalid onramp redirect url")
val data = safeApiCall(
call = {
onrampApi.getData(
@@ -362,7 +364,7 @@ internal class DefaultOnrampRepository(
toDecimals = cryptoCurrency.decimals,
providerId = quote.provider.id,
toAddress = address,
- redirectUrl = "",
+ redirectUrl = redirectUrl,
language = null,
theme = getTheme(isDarkTheme),
requestId = requestId,
@@ -370,7 +372,7 @@ internal class DefaultOnrampRepository(
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
- ),
+ ).takeUnless { it.isEmpty() },
).bind()
},
onError = { e ->
@@ -544,6 +546,11 @@ internal class DefaultOnrampRepository(
null
}
+ private fun OnrampProvider.toRedirectUrl() = Uri.Builder()
+ .path(REDIRECT_URL)
+ .appendPath(id)
+ .build().path
+
private companion object {
const val PAYMENT_METHODS_KEY = "onramp_payment_methods"
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 PROVIDER_THEME_DARK = "dark"
const val PROVIDER_THEME_LIGHT = "light"
+
+ const val REDIRECT_URL = "https://tangem.com/onramp"
}
}
\ No newline at end of file
diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt
index dae9cfdb2a..d91f65451b 100644
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt
+++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt
@@ -2,6 +2,7 @@ package com.tangem.domain.onramp
import arrow.core.Either
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.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
@@ -20,7 +21,7 @@ class GetOnrampRedirectUrlUseCase(
quote: OnrampProviderWithQuote.Data,
cryptoCurrency: CryptoCurrency,
isDarkTheme: Boolean,
- ): Either {
+ ): Either {
return Either.catch {
val transaction = repository.getOnrampData(
userWallet = userWallet,
@@ -29,7 +30,7 @@ class GetOnrampRedirectUrlUseCase(
isDarkTheme = isDarkTheme,
)
transactionRepository.storeTransaction(transaction)
- transaction.redirectUrl
+ transaction
}.mapLeft(errorResolver::resolve)
}
}
\ No newline at end of file
diff --git a/features/onramp/api/build.gradle.kts b/features/onramp/api/build.gradle.kts
index ac5210f2a6..598b224b08 100644
--- a/features/onramp/api/build.gradle.kts
+++ b/features/onramp/api/build.gradle.kts
@@ -13,6 +13,7 @@ dependencies {
/* Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
+ implementation(projects.core.deepLinks)
/* Project - Domain */
implementation(projects.domain.onramp.models)
diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt
new file mode 100644
index 0000000000..1b2ae8c801
--- /dev/null
+++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts
index b32858e319..2307564258 100644
--- a/features/onramp/impl/build.gradle.kts
+++ b/features/onramp/impl/build.gradle.kts
@@ -22,6 +22,7 @@ dependencies {
implementation(projects.core.analytics)
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
+ implementation(projects.core.deepLinks)
implementation(projects.core.navigation)
implementation(projects.core.ui)
implementation(projects.core.utils)
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLink.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLink.kt
new file mode 100644
index 0000000000..a7edc4cbf5
--- /dev/null
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLink.kt
@@ -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) {
+ 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"
+ }
+}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampRedirectResult.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampRedirectResult.kt
new file mode 100644
index 0000000000..11dba8841e
--- /dev/null
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampRedirectResult.kt
@@ -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
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/di/OnrampDeeplinkModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/di/OnrampDeeplinkModule.kt
new file mode 100644
index 0000000000..6663ec80d4
--- /dev/null
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/di/OnrampDeeplinkModule.kt
@@ -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
+}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt
index 7d8207b969..c9a11d1cb5 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt
@@ -1,9 +1,10 @@
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.decompose.model.Model
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.navigation.url.UrlOpener
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.message.DialogMessage
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.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM
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.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -30,16 +35,19 @@ internal class OnrampRedirectModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val urlOpener: UrlOpener,
private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase,
- private val getWalletsUseCase: GetWalletsUseCase,
private val messageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
- router: Router,
+ private val onrampSuccessScreenListener: OnrampSuccessScreenListener,
+ private val appRouter: AppRouter,
paramsContainer: ParamsContainer,
+ getWalletsUseCase: GetWalletsUseCase,
) : Model() {
private val params: OnrampRedirectComponent.Params = paramsContainer.require()
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
+ private var latestOnrampTransaction: OnrampTransaction? = null
+
val state = OnrampRedirectUM(
topBarConfig = OnrampRedirectTopBarUM(
title = combinedReference(
@@ -48,7 +56,7 @@ internal class OnrampRedirectModel @Inject constructor(
),
startButtonUM = TopAppBarButtonUM(
iconRes = R.drawable.ic_close_24,
- onIconClicked = router::pop,
+ onIconClicked = appRouter::pop,
enabled = true,
),
),
@@ -63,6 +71,10 @@ internal class OnrampRedirectModel @Inject constructor(
),
)
+ init {
+ subscribeToOnrampSuccessListener()
+ }
+
fun getRedirectUrl(isDarkTheme: Boolean) {
modelScope.launch {
getOnrampRedirectUrlUseCase.invoke(
@@ -73,12 +85,31 @@ internal class OnrampRedirectModel @Inject constructor(
)
.onLeft(::handleError)
.onRight {
- params.onBack()
- urlOpener.openUrl(it)
+ latestOnrampTransaction = 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) {
Timber.e(error.toString())
analyticsEventHandler.sendOnrampErrorEvent(
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/OnrampSuccessScreenTrigger.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/OnrampSuccessScreenTrigger.kt
new file mode 100644
index 0000000000..9dfa27bee6
--- /dev/null
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/OnrampSuccessScreenTrigger.kt
@@ -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
+}
+
+interface OnrampSuccessScreenTrigger {
+ suspend fun triggerOnrampSuccess(result: Boolean)
+}
+
+@Singleton
+internal class DefaultOnrampSuccessScreenTrigger @Inject constructor() :
+ OnrampSuccessScreenTrigger,
+ OnrampSuccessScreenListener {
+
+ override val onrampSuccessTriggerFlow = MutableSharedFlow()
+
+ override suspend fun triggerOnrampSuccess(result: Boolean) {
+ onrampSuccessTriggerFlow.emit(result)
+ }
+}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt
index fe2545877b..3a9cd54a03 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt
@@ -2,6 +2,9 @@ package com.tangem.features.onramp.success.di
import com.tangem.features.onramp.component.OnrampSuccessComponent
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.Module
import dagger.hilt.InstallIn
@@ -16,4 +19,12 @@ internal interface OnrampSuccessComponentModule {
fun bindOnrampSuccessComponentFactory(
factory: DefaultOnrampSuccessComponent.Factory,
): OnrampSuccessComponent.Factory
+
+ @Binds
+ @Singleton
+ fun bindOnrampSuccessScreenTrigger(impl: DefaultOnrampSuccessScreenTrigger): OnrampSuccessScreenTrigger
+
+ @Binds
+ @Singleton
+ fun bindOnrampSuccessScreenListener(impl: DefaultOnrampSuccessScreenTrigger): OnrampSuccessScreenListener
}
\ No newline at end of file