Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@
android:scheme="codewallet" />
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="@string/root_url_tipcard_no_protocol"
android:pathPattern="/x/.*"
android:scheme="https" />
</intent-filter>

</activity>

<service
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/getcode/models/PaymentRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ data class DeepLinkRequest(
}

companion object {
fun fromTipCardUsername(platform: String, username: String): DeepLinkRequest {
return DeepLinkRequest(
mode = Mode.Tip,
clientSecret = emptyList(),
tipRequest = TipRequest(
platformName = platform,
username = username
),
cancelUrl = null,
successUrl = null,
)
}

fun from(data: ByteArray?): DeepLinkRequest? {
data ?: return null
val container = runCatching {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.hilt.getViewModel
import com.getcode.R
import com.getcode.model.KinAmount
import com.getcode.models.DeepLinkRequest
import com.getcode.navigation.core.LocalCodeNavigator
import com.getcode.ui.utils.RepeatOnLifecycle
import com.getcode.ui.utils.getActivityScopedViewModel
Expand All @@ -19,6 +20,7 @@ import com.getcode.view.main.giveKin.GiveKinScreen
import com.getcode.view.main.home.HomeScreen
import com.getcode.view.main.home.HomeViewModel
import com.getcode.view.main.requestKin.RequestKinScreen
import com.google.firebase.encoders.annotations.Encodable.Ignore
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
Expand All @@ -38,7 +40,8 @@ sealed interface HomeResult {
data class HomeScreen(
val seed: String? = null,
val cashLink: String? = null,
val requestPayload: String? = null,
@IgnoredOnParcel
val request: DeepLinkRequest? = null,
) : AppScreen(), MainGraph {
@IgnoredOnParcel
override val key: ScreenKey = uniqueScreenKey
Expand All @@ -48,7 +51,7 @@ data class HomeScreen(
val vm = getViewModel<HomeViewModel>()

trace("home rendered")
HomeScreen(vm, cashLink, requestPayload)
HomeScreen(vm, cashLink, request)

OnScreenResult<HomeResult> { result ->
when (result) {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/getcode/ui/components/AuthCheck.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
45 changes: 33 additions & 12 deletions app/src/main/java/com/getcode/util/DeeplinkHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))),
)
}

Expand All @@ -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<Key, String>
Expand All @@ -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)\$")
Expand Down
36 changes: 17 additions & 19 deletions app/src/main/java/com/getcode/view/main/home/HomeScan.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -108,8 +106,8 @@ fun HomeScreen(
HomeScan(
homeViewModel = homeViewModel,
dataState = dataState,
deepLink = deepLink,
requestPayload = requestPayload,
cashLink = cashLink,
request = request,
)

val context = LocalContext.current
Expand All @@ -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()
Expand All @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down