Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-16 19:27:20 +05:00
parent 220598aff7
commit f9f45e1c8a
8 changed files with 124 additions and 30 deletions

View file

@ -51,6 +51,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.google.GoogleServicesHelper
import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.BackupServiceHolder
@ -181,6 +182,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
internal lateinit var deeplinkFactory: DeepLinkFactory
@Inject
internal lateinit var walletConnectFeatureToggles: WalletConnectFeatureToggles
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -351,7 +355,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
}
}
private fun updateAppTheme(appThemeMode: AppThemeMode) {

View file

@ -1,34 +1,5 @@
package com.tangem.tap.common.extensions
import android.graphics.Bitmap
import android.graphics.Color
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import java.util.Hashtable
@Suppress("MagicNumber")
fun String.toQrCode(): Bitmap {
val hintMap = Hashtable<EncodeHintType, Any>()
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
hintMap[EncodeHintType.MARGIN] = 2
val qrCodeWriter = QRCodeWriter()
val size = 256
val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
val width = bitMatrix.width
val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
for (x in 0 until width) {
for (y in 0 until width) {
bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
}
}
return bmp
}
fun String.removePrefixOrNull(prefix: String): String? = when {
startsWith(prefix) -> substring(prefix.length)
else -> null

View file

@ -8,6 +8,7 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.hilt.android.scopes.ActivityScoped
@ -25,6 +26,7 @@ internal class DeepLinkFactory @Inject constructor(
private val sellDeepLink: SellDeepLinkHandler.Factory,
private val buyDeepLink: BuyDeepLinkHandler.Factory,
private val referralDeepLink: ReferralDeepLinkHandler.Factory,
private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory,
) {
private val permittedAppRoute = MutableStateFlow(false)
@ -73,6 +75,7 @@ internal class DeepLinkFactory @Inject constructor(
private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
when (deeplinkUri.scheme) {
DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope)
DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri)
else -> {
Timber.i(
"""

View file

@ -6,6 +6,7 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@ -33,6 +34,9 @@ class DeepLinkFactoryTest {
private val referralDeepLinkFactory = mockk<ReferralDeepLinkHandler.Factory>(relaxed = true) {
every { create() } returns mockk()
}
private val walletConnectDeepLinkFactory = mockk<WalletConnectDeepLinkHandler.Factory>(relaxed = true) {
every { create(any()) } returns mockk()
}
private val mockedUri = mockk<Uri>(relaxed = true)
@ -44,6 +48,7 @@ class DeepLinkFactoryTest {
sellDeepLinkFactory,
buyDeepLinkFactory,
referralDeepLinkFactory,
walletConnectDeepLinkFactory,
)
@OptIn(ExperimentalCoroutinesApi::class)
@ -127,6 +132,21 @@ class DeepLinkFactoryTest {
}
}
@Test
fun `launchDeepLink handles wc scheme correctly`() = runTest {
every { mockedUri.scheme } returns "wc"
every { mockedUri.host } returns ""
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
deepLinkFactory.handleDeeplink(mockedUri, testScope)
advanceUntilIdle()
verify {
walletConnectDeepLinkFactory.create(eq(mockedUri))
}
}
@Test
fun `launchDeepLink ignores unknown scheme`() = runTest {
every { mockedUri.scheme } returns "https"
@ -141,6 +161,7 @@ class DeepLinkFactoryTest {
sellDeepLinkFactory.create(any(), any())
buyDeepLinkFactory.create(any())
referralDeepLinkFactory.create()
walletConnectDeepLinkFactory.create(any())
}
}
@ -197,6 +218,7 @@ class DeepLinkFactoryTest {
sellDeepLinkFactory.create(any(), any())
buyDeepLinkFactory.create(any())
referralDeepLinkFactory.create()
walletConnectDeepLinkFactory.create(any())
}
}

View file

@ -16,4 +16,7 @@ dependencies {
/** Domain models */
implementation(projects.domain.wallets.models)
/** Compose */
implementation(deps.compose.runtime)
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.walletconnect.components.deeplink
import android.net.Uri
interface WalletConnectDeepLinkHandler {
interface Factory {
fun create(uri: Uri): WalletConnectDeepLinkHandler
}
}

View file

@ -0,0 +1,58 @@
package com.tangem.features.walletconnect.deeplink
import android.net.Uri
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import timber.log.Timber
import java.net.URLDecoder
internal class DefaultWalletConnectDeepLinkHandler @AssistedInject constructor(
@Assisted uri: Uri,
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
walletConnectFeatureToggles: WalletConnectFeatureToggles,
wcPairService: WcPairService,
) : WalletConnectDeepLinkHandler {
init {
val wcUri = uri.toString()
if (uri.toString().isNotBlank()) {
try {
// It is okay here, we are navigating from outside, and there is no other way to getting UserWallet
getSelectedWalletSyncUseCase().fold(
ifLeft = {
Timber.e("Error on getting user wallet: $it")
},
ifRight = { wallet ->
val decodedWcUri = URLDecoder.decode(wcUri, DEFAULT_CHARSET_NAME)
if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
wcPairService.pair(
request = WcPairRequest(
uri = decodedWcUri,
source = WcPairRequest.Source.DEEPLINK,
userWalletId = wallet.walletId,
),
)
}
},
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
@AssistedFactory
interface Factory : WalletConnectDeepLinkHandler.Factory {
override fun create(uri: Uri): DefaultWalletConnectDeepLinkHandler
}
private companion object {
const val DEFAULT_CHARSET_NAME = "UTF-8"
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.walletconnect.deeplink.di
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
import com.tangem.features.walletconnect.deeplink.DefaultWalletConnectDeepLinkHandler
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 WalletConnectDeepLinkModule {
@Binds
@Singleton
fun bindWalletConnectDeepLinkHandlerFactory(
impl: DefaultWalletConnectDeepLinkHandler.Factory,
): WalletConnectDeepLinkHandler.Factory
}