diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index dfd119816f..9e44bb3dc2 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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 @@ -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) { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 448ae3e36a..0396cf84fc 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -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() - 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 diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt index 699c1e184c..4eb339d9e0 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt @@ -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( """ diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt index 6bc9b58431..03badaf8ef 100644 --- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt @@ -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(relaxed = true) { every { create() } returns mockk() } + private val walletConnectDeepLinkFactory = mockk(relaxed = true) { + every { create(any()) } returns mockk() + } private val mockedUri = mockk(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()) } } diff --git a/features/walletconnect/api/build.gradle.kts b/features/walletconnect/api/build.gradle.kts index c1db369e4a..30db35c422 100644 --- a/features/walletconnect/api/build.gradle.kts +++ b/features/walletconnect/api/build.gradle.kts @@ -16,4 +16,7 @@ dependencies { /** Domain models */ implementation(projects.domain.wallets.models) + + /** Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/deeplink/WalletConnectDeepLinkHandler.kt b/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/deeplink/WalletConnectDeepLinkHandler.kt new file mode 100644 index 0000000000..6f2c7bd5bb --- /dev/null +++ b/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/deeplink/WalletConnectDeepLinkHandler.kt @@ -0,0 +1,10 @@ +package com.tangem.features.walletconnect.components.deeplink + +import android.net.Uri + +interface WalletConnectDeepLinkHandler { + + interface Factory { + fun create(uri: Uri): WalletConnectDeepLinkHandler + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/deeplink/DefaultWalletConnectDeepLinkHandler.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/deeplink/DefaultWalletConnectDeepLinkHandler.kt new file mode 100644 index 0000000000..53c98210ad --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/deeplink/DefaultWalletConnectDeepLinkHandler.kt @@ -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" + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/deeplink/di/WalletConnectDeepLinkModule.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/deeplink/di/WalletConnectDeepLinkModule.kt new file mode 100644 index 0000000000..072796c130 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/deeplink/di/WalletConnectDeepLinkModule.kt @@ -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 +} \ No newline at end of file