Updated on 2026-08-14
This commit is contained in:
parent
40893754c5
commit
b319350dd5
5 changed files with 299 additions and 0 deletions
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.tap.common.pushes
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.tangem.common.routing.DeepLinkRoute
|
||||
import com.tangem.common.routing.deeplink.PayloadToDeeplinkConverter
|
||||
import com.tangem.utils.extensions.uriValidate
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Routes pushes received while the app is running to the matching in-app handler.
|
||||
*
|
||||
* Converts the push payload to a deeplink (via [PayloadToDeeplinkConverter]) and routes by its
|
||||
* [host][Uri.getHost] — the same routing key [DeepLinkFactory][com.tangem.tap.routing.utils.DeepLinkFactory] uses
|
||||
* for tapped deeplinks. Handlers receive the deeplink query params (not the raw payload), so both flat-key and
|
||||
* `deeplink`-style payloads are handled uniformly. Each handler owns its own reaction; add a `when` branch per
|
||||
* push type as new in-app reactions appear.
|
||||
*/
|
||||
internal class PushMessageHandler @Inject constructor(
|
||||
private val tokenDetailsPushHandler: TokenDetailsPushHandler,
|
||||
) {
|
||||
|
||||
fun onMessageReceived(data: Map<String, String>) {
|
||||
val deeplink = PayloadToDeeplinkConverter.convert(data)?.toUri() ?: return
|
||||
val queryParams = deeplink.getQueryParams()
|
||||
when (deeplink.host) {
|
||||
DeepLinkRoute.TokenDetails.host -> tokenDetailsPushHandler.handle(queryParams)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun Uri.getQueryParams(): Map<String, String> {
|
||||
val params = mutableMapOf<String, String>()
|
||||
queryParameterNames.forEach { name ->
|
||||
val value = getQueryParameter(name)
|
||||
if (name.uriValidate() && value?.uriValidate() == true) {
|
||||
params[name] = value
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,17 @@ import android.annotation.SuppressLint
|
|||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.customer.messagingpush.CustomerIOFirebaseMessagingService
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
|
||||
internal class TangemPushNotificationService : FirebaseMessagingService() {
|
||||
|
||||
@Inject
|
||||
lateinit var pushMessageHandler: PushMessageHandler
|
||||
|
||||
private val pushNotificationDelegate: PushNotificationDelegate by lazy {
|
||||
PushNotificationDelegate(applicationContext)
|
||||
}
|
||||
|
|
@ -29,6 +35,8 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
|
|||
handleNotificationTrigger = false,
|
||||
)
|
||||
|
||||
pushMessageHandler.onMessageReceived(message.data)
|
||||
|
||||
val notification = message.notification ?: return
|
||||
val channelId = notification.channelId ?: TANGEM_CHANNEL_ID
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.tap.common.pushes
|
||||
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.DERIVATION_PATH_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.NETWORK_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.TOKEN_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Handles a received token-details push (same payload as
|
||||
* [com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler]).
|
||||
*
|
||||
* When the app is open and the pushed token is not yet present in the wallet's portfolio (e.g. it was just added
|
||||
* on the backend), refreshes the wallet accounts so it appears locally — the open portfolio screen then updates
|
||||
* reactively via [SingleAccountListSupplier]. Does nothing else.
|
||||
*/
|
||||
class TokenDetailsPushHandler @Inject constructor(
|
||||
private val appCoroutineScope: AppCoroutineScope,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
) {
|
||||
|
||||
fun handle(queryParams: Map<String, String>) {
|
||||
// Only when the app is open: a token just added on the backend should appear in the already-open portfolio.
|
||||
// On cold start the fresh list is loaded by the regular auth flow instead.
|
||||
if (ForegroundActivityObserver.foregroundActivity == null) return
|
||||
appCoroutineScope.launch { refreshPortfolioIfTokenMissing(queryParams) }
|
||||
}
|
||||
|
||||
internal suspend fun refreshPortfolioIfTokenMissing(queryParams: Map<String, String>) {
|
||||
val networkId = queryParams[NETWORK_ID_KEY] ?: return
|
||||
val tokenId = queryParams[TOKEN_ID_KEY] ?: return
|
||||
val derivationPath = queryParams[DERIVATION_PATH_KEY]
|
||||
|
||||
val userWallet = resolveUserWallet(queryParams[WALLET_ID_KEY]) ?: return
|
||||
// Token list refresh only makes sense for an unlocked multi-currency wallet.
|
||||
if (userWallet.isLocked || !userWallet.isMultiCurrency) return
|
||||
|
||||
val isTokenPresent = singleAccountListSupplier.getSyncOrNull(userWallet.walletId)
|
||||
?.flattenCurrencies()
|
||||
?.any { it.matches(networkId = networkId, tokenId = tokenId, derivationPath = derivationPath) } == true
|
||||
|
||||
if (isTokenPresent) return
|
||||
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId = userWallet.walletId))
|
||||
.onLeft { TangemLogger.e("Error on refreshing portfolio from push", it) }
|
||||
}
|
||||
|
||||
private fun resolveUserWallet(walletId: String?): UserWallet? {
|
||||
val userWalletId = walletId?.let(::UserWalletId)
|
||||
return if (userWalletId != null) {
|
||||
getUserWalletUseCase(userWalletId).getOrNull()
|
||||
} else {
|
||||
getSelectedWalletSyncUseCase().getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.matches(networkId: String, tokenId: String, derivationPath: String?): Boolean {
|
||||
val isNetwork = network.rawId.equals(networkId, ignoreCase = true)
|
||||
val isCurrency = id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true
|
||||
val isDefaultDerivation = network.derivationPath is Network.DerivationPath.Card
|
||||
val isCustomDerivation = derivationPath?.equals(network.derivationPath.value) == true
|
||||
return isNetwork && isCurrency && (isDefaultDerivation || isCustomDerivation)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue