Skip to content

Commit 73827fd

Browse files
committed
chore: support directly handling tipcard deeplinks
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 8b73d62 commit 73827fd

7 files changed

Lines changed: 82 additions & 36 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@
138138
android:scheme="codewallet" />
139139
</intent-filter>
140140

141+
<intent-filter android:autoVerify="true">
142+
<action android:name="android.intent.action.VIEW" />
143+
144+
<category android:name="android.intent.category.DEFAULT" />
145+
<category android:name="android.intent.category.BROWSABLE" />
146+
147+
<data
148+
android:host="@string/root_url_tipcard_no_protocol"
149+
android:pathPattern="/x/.*"
150+
android:scheme="https" />
151+
</intent-filter>
152+
141153
</activity>
142154

143155
<service

app/src/main/java/com/getcode/models/PaymentRequest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ data class DeepLinkRequest(
3535
}
3636

3737
companion object {
38+
fun fromTipCardUsername(platform: String, username: String): DeepLinkRequest {
39+
return DeepLinkRequest(
40+
mode = Mode.Tip,
41+
clientSecret = emptyList(),
42+
tipRequest = TipRequest(
43+
platformName = platform,
44+
username = username
45+
),
46+
cancelUrl = null,
47+
successUrl = null,
48+
)
49+
}
50+
3851
fun from(data: ByteArray?): DeepLinkRequest? {
3952
data ?: return null
4053
val container = runCatching {

app/src/main/java/com/getcode/navigation/screens/MainScreens.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
88
import cafe.adriel.voyager.hilt.getViewModel
99
import com.getcode.R
1010
import com.getcode.model.KinAmount
11+
import com.getcode.models.DeepLinkRequest
1112
import com.getcode.navigation.core.LocalCodeNavigator
1213
import com.getcode.ui.utils.RepeatOnLifecycle
1314
import com.getcode.ui.utils.getActivityScopedViewModel
@@ -19,6 +20,7 @@ import com.getcode.view.main.giveKin.GiveKinScreen
1920
import com.getcode.view.main.home.HomeScreen
2021
import com.getcode.view.main.home.HomeViewModel
2122
import com.getcode.view.main.requestKin.RequestKinScreen
23+
import com.google.firebase.encoders.annotations.Encodable.Ignore
2224
import kotlinx.coroutines.flow.filterNotNull
2325
import kotlinx.coroutines.flow.launchIn
2426
import kotlinx.coroutines.flow.mapNotNull
@@ -38,7 +40,8 @@ sealed interface HomeResult {
3840
data class HomeScreen(
3941
val seed: String? = null,
4042
val cashLink: String? = null,
41-
val requestPayload: String? = null,
43+
@IgnoredOnParcel
44+
val request: DeepLinkRequest? = null,
4245
) : AppScreen(), MainGraph {
4346
@IgnoredOnParcel
4447
override val key: ScreenKey = uniqueScreenKey
@@ -48,7 +51,7 @@ data class HomeScreen(
4851
val vm = getViewModel<HomeViewModel>()
4952

5053
trace("home rendered")
51-
HomeScreen(vm, cashLink, requestPayload)
54+
HomeScreen(vm, cashLink, request)
5255

5356
OnScreenResult<HomeResult> { result ->
5457
when (result) {

app/src/main/java/com/getcode/ui/components/AuthCheck.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fun AuthCheck(
8585
when (result.type) {
8686
is DeeplinkHandler.Type.Login -> true
8787
is DeeplinkHandler.Type.Cash,
88+
is DeeplinkHandler.Type.Tip,
8889
is DeeplinkHandler.Type.Sdk -> {
8990
val hasAuth = state.isAuthenticated == true
9091
if (!hasAuth) {

app/src/main/java/com/getcode/util/DeeplinkHandler.kt

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package com.getcode.util
33
import android.content.Intent
44
import android.net.Uri
55
import cafe.adriel.voyager.core.screen.Screen
6+
import com.getcode.models.DeepLinkRequest
67
import com.getcode.navigation.screens.HomeScreen
78
import com.getcode.navigation.screens.LoginScreen
89
import com.getcode.network.repository.urlDecode
910
import com.getcode.utils.TraceType
11+
import com.getcode.utils.base64EncodedData
1012
import com.getcode.utils.trace
1113
import kotlinx.coroutines.flow.MutableStateFlow
1214
import timber.log.Timber
@@ -61,9 +63,18 @@ class DeeplinkHandler @Inject constructor() {
6163

6264
is Type.Sdk -> {
6365
Timber.d("sdk=${type.payload}")
66+
val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest.from(it) }
6467
DeeplinkResult(
6568
type,
66-
listOf(HomeScreen(requestPayload = type.payload)),
69+
listOf(HomeScreen(request = request)),
70+
)
71+
}
72+
73+
is Type.Tip -> {
74+
Timber.d("tipcard for ${type.username} on ${type.platform}")
75+
DeeplinkResult(
76+
type,
77+
listOf(HomeScreen(request = DeepLinkRequest.fromTipCardUsername(type.platform, type.username))),
6778
)
6879
}
6980

@@ -72,20 +83,29 @@ class DeeplinkHandler @Inject constructor() {
7283
}
7384

7485
private val Uri.deeplinkType: Type
75-
get() = when (val segment = lastPathSegment) {
76-
"login" -> {
77-
var entropy = fragments[Key.entropy]
78-
if (entropy == null) {
79-
entropy = this.getQueryParameter("data")
86+
get() {
87+
// check for tipcard URLs
88+
val components = pathSegments
89+
if (components.count() == 2 && components[0] == "x" && components[1].isNotEmpty()) {
90+
return Type.Tip(components[0], components[1])
91+
}
92+
93+
return when (val segment = lastPathSegment) {
94+
"login" -> {
95+
var entropy = fragments[Key.entropy]
96+
if (entropy == null) {
97+
entropy = this.getQueryParameter("data")
98+
}
99+
100+
Type.Login(entropy.also { Timber.d("entropy=$it") })
80101
}
81102

82-
Type.Login(entropy.also { Timber.d("entropy=$it") })
83-
}
103+
"cash", "c" -> Type.Cash(fragments[Key.entropy])
84104

85-
"cash", "c" -> Type.Cash(fragments[Key.entropy])
86-
// support all variations of SDK request triggers
87-
in Type.Sdk.regex -> Type.Sdk(fragments[Key.payload]?.urlDecode())
88-
else -> Type.Unknown(path = segment)
105+
// support all variations of SDK request triggers
106+
in Type.Sdk.regex -> Type.Sdk(fragments[Key.payload]?.urlDecode())
107+
else -> Type.Unknown(path = segment)
108+
}
89109
}
90110

91111
private val Uri.fragments: Map<Key, String>
@@ -104,6 +124,7 @@ class DeeplinkHandler @Inject constructor() {
104124
sealed interface Type {
105125
data class Login(val link: String?) : Type
106126
data class Cash(val link: String?) : Type
127+
data class Tip(val platform: String, val username: String): Type
107128
data class Sdk(val payload: String?) : Type {
108129
companion object {
109130
val regex = Regex("^(login|payment|tip)?-?request-(modal|page)-(mobile|desktop)\$")

app/src/main/java/com/getcode/view/main/home/HomeScan.kt

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,17 @@ import androidx.compose.runtime.remember
3131
import androidx.compose.runtime.rememberCoroutineScope
3232
import androidx.compose.runtime.rememberUpdatedState
3333
import androidx.compose.runtime.setValue
34-
import androidx.compose.runtime.snapshotFlow
3534
import androidx.compose.ui.Alignment.Companion.BottomCenter
3635
import androidx.compose.ui.Modifier
3736
import androidx.compose.ui.platform.LocalContext
3837
import androidx.compose.ui.platform.LocalFocusManager
3938
import androidx.compose.ui.unit.dp
4039
import androidx.lifecycle.Lifecycle
41-
import cafe.adriel.voyager.core.stack.StackEvent
4240
import com.getcode.LocalBiometricsState
4341
import com.getcode.R
4442
import com.getcode.manager.TopBarManager
4543
import com.getcode.models.Bill
44+
import com.getcode.models.DeepLinkRequest
4645
import com.getcode.navigation.core.CodeNavigator
4746
import com.getcode.navigation.core.LocalCodeNavigator
4847
import com.getcode.navigation.screens.AccountModal
@@ -69,7 +68,6 @@ import com.getcode.view.main.home.components.PermissionsBlockingView
6968
import com.getcode.view.main.home.components.ReceivedKinConfirmation
7069
import com.getcode.view.main.home.components.TipConfirmation
7170
import kotlinx.coroutines.delay
72-
import kotlinx.coroutines.flow.filter
7371
import kotlinx.coroutines.flow.launchIn
7472
import kotlinx.coroutines.flow.onEach
7573
import kotlinx.coroutines.launch
@@ -89,8 +87,8 @@ enum class HomeBottomSheet {
8987
@Composable
9088
fun HomeScreen(
9189
homeViewModel: HomeViewModel,
92-
deepLink: String? = null,
93-
requestPayload: String? = null,
90+
cashLink: String? = null,
91+
request: DeepLinkRequest? = null,
9492
) {
9593
val navigator = LocalCodeNavigator.current
9694
val dataState by homeViewModel.uiFlow.collectAsState()
@@ -108,8 +106,8 @@ fun HomeScreen(
108106
HomeScan(
109107
homeViewModel = homeViewModel,
110108
dataState = dataState,
111-
deepLink = deepLink,
112-
requestPayload = requestPayload,
109+
cashLink = cashLink,
110+
request = request,
113111
)
114112

115113
val context = LocalContext.current
@@ -136,8 +134,8 @@ fun HomeScreen(
136134
private fun HomeScan(
137135
homeViewModel: HomeViewModel,
138136
dataState: HomeUiModel,
139-
deepLink: String?,
140-
requestPayload: String?,
137+
cashLink: String?,
138+
request: DeepLinkRequest?,
141139
) {
142140
val navigator = LocalCodeNavigator.current
143141
val scope = rememberCoroutineScope()
@@ -159,28 +157,28 @@ private fun HomeScan(
159157

160158
val focusManager = LocalFocusManager.current
161159

162-
var deepLinkSaved by remember(deepLink) {
163-
mutableStateOf(deepLink)
160+
var cashLinkSaved by remember(cashLink) {
161+
mutableStateOf(cashLink)
164162
}
165163

166-
var requestPayloadSaved by remember(requestPayload) {
167-
mutableStateOf(requestPayload)
164+
var requestPayloadSaved by remember(request) {
165+
mutableStateOf(request)
168166
}
169167

170168
val biometricsState = LocalBiometricsState.current
171-
LaunchedEffect(biometricsState, previewing, dataState.balance, deepLinkSaved, requestPayloadSaved) {
169+
LaunchedEffect(biometricsState, previewing, dataState.balance, cashLinkSaved, requestPayloadSaved) {
172170
if (previewing) {
173171
focusManager.clearFocus()
174172
}
175173

176-
if (biometricsState.passed && !deepLinkSaved.isNullOrBlank()) {
177-
homeViewModel.openCashLink(deepLink)
178-
deepLinkSaved = null
174+
if (biometricsState.passed && !cashLinkSaved.isNullOrBlank()) {
175+
homeViewModel.openCashLink(cashLink)
176+
cashLinkSaved = null
179177
}
180178

181-
if (biometricsState.passed && !requestPayloadSaved.isNullOrBlank() && dataState.balance != null) {
179+
if (biometricsState.passed && requestPayloadSaved != null && dataState.balance != null) {
182180
delay(500.milliseconds)
183-
homeViewModel.handleRequest(requestPayload)
181+
homeViewModel.handleRequest(request)
184182
requestPayloadSaved = null
185183
}
186184
}

app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,9 +1430,7 @@ class HomeViewModel @Inject constructor(
14301430
}
14311431
}
14321432

1433-
fun handleRequest(bytes: String?) {
1434-
val data = bytes?.base64EncodedData() ?: return
1435-
val request = DeepLinkRequest.from(data)
1433+
fun handleRequest(request: DeepLinkRequest?) {
14361434
if (request != null) {
14371435
if (request.paymentRequest != null) {
14381436
viewModelScope.launch {

0 commit comments

Comments
 (0)