From 73827fd96734581d9d3447e16b680ffe163ef3c2 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 8 Aug 2024 22:36:32 -0400 Subject: [PATCH] chore: support directly handling tipcard deeplinks Signed-off-by: Brandon McAnsh --- app/src/main/AndroidManifest.xml | 12 +++++ .../java/com/getcode/models/PaymentRequest.kt | 13 ++++++ .../getcode/navigation/screens/MainScreens.kt | 7 ++- .../com/getcode/ui/components/AuthCheck.kt | 1 + .../java/com/getcode/util/DeeplinkHandler.kt | 45 ++++++++++++++----- .../com/getcode/view/main/home/HomeScan.kt | 36 +++++++-------- .../getcode/view/main/home/HomeViewModel.kt | 4 +- 7 files changed, 82 insertions(+), 36 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f5209785..31e85cf5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,18 @@ android:scheme="codewallet" /> + + + + + + + + + () trace("home rendered") - HomeScreen(vm, cashLink, requestPayload) + HomeScreen(vm, cashLink, request) OnScreenResult { result -> when (result) { diff --git a/app/src/main/java/com/getcode/ui/components/AuthCheck.kt b/app/src/main/java/com/getcode/ui/components/AuthCheck.kt index 4b3821814..6734bbe67 100644 --- a/app/src/main/java/com/getcode/ui/components/AuthCheck.kt +++ b/app/src/main/java/com/getcode/ui/components/AuthCheck.kt @@ -85,6 +85,7 @@ fun AuthCheck( when (result.type) { is DeeplinkHandler.Type.Login -> true is DeeplinkHandler.Type.Cash, + is DeeplinkHandler.Type.Tip, is DeeplinkHandler.Type.Sdk -> { val hasAuth = state.isAuthenticated == true if (!hasAuth) { diff --git a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt index 813209b9e..db994f286 100644 --- a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt +++ b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt @@ -3,10 +3,12 @@ package com.getcode.util import android.content.Intent import android.net.Uri import cafe.adriel.voyager.core.screen.Screen +import com.getcode.models.DeepLinkRequest import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen import com.getcode.network.repository.urlDecode import com.getcode.utils.TraceType +import com.getcode.utils.base64EncodedData import com.getcode.utils.trace import kotlinx.coroutines.flow.MutableStateFlow import timber.log.Timber @@ -61,9 +63,18 @@ class DeeplinkHandler @Inject constructor() { is Type.Sdk -> { Timber.d("sdk=${type.payload}") + val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest.from(it) } DeeplinkResult( type, - listOf(HomeScreen(requestPayload = type.payload)), + listOf(HomeScreen(request = request)), + ) + } + + is Type.Tip -> { + Timber.d("tipcard for ${type.username} on ${type.platform}") + DeeplinkResult( + type, + listOf(HomeScreen(request = DeepLinkRequest.fromTipCardUsername(type.platform, type.username))), ) } @@ -72,20 +83,29 @@ class DeeplinkHandler @Inject constructor() { } private val Uri.deeplinkType: Type - get() = when (val segment = lastPathSegment) { - "login" -> { - var entropy = fragments[Key.entropy] - if (entropy == null) { - entropy = this.getQueryParameter("data") + get() { + // check for tipcard URLs + val components = pathSegments + if (components.count() == 2 && components[0] == "x" && components[1].isNotEmpty()) { + return Type.Tip(components[0], components[1]) + } + + return when (val segment = lastPathSegment) { + "login" -> { + var entropy = fragments[Key.entropy] + if (entropy == null) { + entropy = this.getQueryParameter("data") + } + + Type.Login(entropy.also { Timber.d("entropy=$it") }) } - Type.Login(entropy.also { Timber.d("entropy=$it") }) - } + "cash", "c" -> Type.Cash(fragments[Key.entropy]) - "cash", "c" -> Type.Cash(fragments[Key.entropy]) - // support all variations of SDK request triggers - in Type.Sdk.regex -> Type.Sdk(fragments[Key.payload]?.urlDecode()) - else -> Type.Unknown(path = segment) + // support all variations of SDK request triggers + in Type.Sdk.regex -> Type.Sdk(fragments[Key.payload]?.urlDecode()) + else -> Type.Unknown(path = segment) + } } private val Uri.fragments: Map @@ -104,6 +124,7 @@ class DeeplinkHandler @Inject constructor() { sealed interface Type { data class Login(val link: String?) : Type data class Cash(val link: String?) : Type + data class Tip(val platform: String, val username: String): Type data class Sdk(val payload: String?) : Type { companion object { val regex = Regex("^(login|payment|tip)?-?request-(modal|page)-(mobile|desktop)\$") diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 455cc3c9b..89d20399d 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -31,18 +31,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment.Companion.BottomCenter import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle -import cafe.adriel.voyager.core.stack.StackEvent import com.getcode.LocalBiometricsState import com.getcode.R import com.getcode.manager.TopBarManager import com.getcode.models.Bill +import com.getcode.models.DeepLinkRequest import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.navigation.screens.AccountModal @@ -69,7 +68,6 @@ import com.getcode.view.main.home.components.PermissionsBlockingView import com.getcode.view.main.home.components.ReceivedKinConfirmation import com.getcode.view.main.home.components.TipConfirmation import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -89,8 +87,8 @@ enum class HomeBottomSheet { @Composable fun HomeScreen( homeViewModel: HomeViewModel, - deepLink: String? = null, - requestPayload: String? = null, + cashLink: String? = null, + request: DeepLinkRequest? = null, ) { val navigator = LocalCodeNavigator.current val dataState by homeViewModel.uiFlow.collectAsState() @@ -108,8 +106,8 @@ fun HomeScreen( HomeScan( homeViewModel = homeViewModel, dataState = dataState, - deepLink = deepLink, - requestPayload = requestPayload, + cashLink = cashLink, + request = request, ) val context = LocalContext.current @@ -136,8 +134,8 @@ fun HomeScreen( private fun HomeScan( homeViewModel: HomeViewModel, dataState: HomeUiModel, - deepLink: String?, - requestPayload: String?, + cashLink: String?, + request: DeepLinkRequest?, ) { val navigator = LocalCodeNavigator.current val scope = rememberCoroutineScope() @@ -159,28 +157,28 @@ private fun HomeScan( val focusManager = LocalFocusManager.current - var deepLinkSaved by remember(deepLink) { - mutableStateOf(deepLink) + var cashLinkSaved by remember(cashLink) { + mutableStateOf(cashLink) } - var requestPayloadSaved by remember(requestPayload) { - mutableStateOf(requestPayload) + var requestPayloadSaved by remember(request) { + mutableStateOf(request) } val biometricsState = LocalBiometricsState.current - LaunchedEffect(biometricsState, previewing, dataState.balance, deepLinkSaved, requestPayloadSaved) { + LaunchedEffect(biometricsState, previewing, dataState.balance, cashLinkSaved, requestPayloadSaved) { if (previewing) { focusManager.clearFocus() } - if (biometricsState.passed && !deepLinkSaved.isNullOrBlank()) { - homeViewModel.openCashLink(deepLink) - deepLinkSaved = null + if (biometricsState.passed && !cashLinkSaved.isNullOrBlank()) { + homeViewModel.openCashLink(cashLink) + cashLinkSaved = null } - if (biometricsState.passed && !requestPayloadSaved.isNullOrBlank() && dataState.balance != null) { + if (biometricsState.passed && requestPayloadSaved != null && dataState.balance != null) { delay(500.milliseconds) - homeViewModel.handleRequest(requestPayload) + homeViewModel.handleRequest(request) requestPayloadSaved = null } } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index 9d13dfb94..0586737b0 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -1430,9 +1430,7 @@ class HomeViewModel @Inject constructor( } } - fun handleRequest(bytes: String?) { - val data = bytes?.base64EncodedData() ?: return - val request = DeepLinkRequest.from(data) + fun handleRequest(request: DeepLinkRequest?) { if (request != null) { if (request.paymentRequest != null) { viewModelScope.launch {