11package com.getcode.util
22
3+ import android.content.Context
34import android.content.Intent
45import android.net.Uri
6+ import androidx.core.net.toUri
57import cafe.adriel.voyager.core.screen.Screen
8+ import com.getcode.model.BetaFlag
9+ import com.getcode.model.PrefsBool
610import com.getcode.models.DeepLinkRequest
11+ import com.getcode.models.encode
712import com.getcode.navigation.screens.HomeScreen
813import com.getcode.navigation.screens.LoginScreen
14+ import com.getcode.network.repository.BetaFlagsRepository
15+ import com.getcode.network.repository.encodeBase64
916import com.getcode.network.repository.urlDecode
17+ import com.getcode.ui.utils.getActivity
1018import com.getcode.utils.TraceType
1119import com.getcode.utils.base64EncodedData
1220import com.getcode.utils.trace
21+ import dagger.hilt.android.qualifiers.ApplicationContext
1322import kotlinx.coroutines.flow.MutableStateFlow
23+ import kotlinx.serialization.json.buildJsonObject
24+ import kotlinx.serialization.json.put
1425import timber.log.Timber
1526import javax.inject.Inject
1627import javax.inject.Singleton
@@ -19,6 +30,7 @@ data class DeeplinkResult(
1930 val type : DeeplinkHandler .Type ,
2031 val stack : List <Screen >,
2132)
33+
2234/* *
2335 * This class is used to manage intent state across navigation.
2436 *
@@ -33,7 +45,10 @@ data class DeeplinkResult(
3345 * in favour of the latest request in the navigation graph.
3446 */
3547@Singleton
36- class DeeplinkHandler @Inject constructor() {
48+ class DeeplinkHandler @Inject constructor(
49+ @ApplicationContext private val context : Context ,
50+ private val betaFlags : BetaFlagsRepository
51+ ) {
3752 var debounceIntent: Intent ? = null
3853 set(value) {
3954 intent.value = value
@@ -43,8 +58,18 @@ class DeeplinkHandler @Inject constructor() {
4358
4459 val intent = MutableStateFlow (debounceIntent)
4560
46- fun handle (intent : Intent ? = debounceIntent): DeeplinkResult ? {
47- val uri = intent?.data ? : return null
61+ suspend fun handle (intent : Intent ? = debounceIntent): DeeplinkResult ? {
62+ println (intent)
63+ val uri = when {
64+ intent?.data != null -> intent.data
65+ intent?.getStringExtra(Intent .EXTRA_TEXT ) != null -> {
66+ val sharedLink = intent.getStringExtra(Intent .EXTRA_TEXT )?.toUri() ? : return null
67+ sharedLink.resolveSharedEntity()
68+ }
69+
70+ else -> null
71+ } ? : return null
72+
4873 return when (val type = uri.deeplinkType) {
4974 is Type .Login -> {
5075 DeeplinkResult (
@@ -63,7 +88,7 @@ class DeeplinkHandler @Inject constructor() {
6388
6489 is Type .Sdk -> {
6590 Timber .d(" sdk=${type.payload} " )
66- val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest .from(it) }
91+ val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest .from(it) }
6792 DeeplinkResult (
6893 type,
6994 listOf (HomeScreen (request = request)),
@@ -74,19 +99,44 @@ class DeeplinkHandler @Inject constructor() {
7499 Timber .d(" tipcard for ${type.username} on ${type.platform} " )
75100 DeeplinkResult (
76101 type,
77- listOf (HomeScreen (request = DeepLinkRequest .fromTipCardUsername(type.platform, type.username))),
102+ listOf (
103+ HomeScreen (
104+ request = DeepLinkRequest .fromTipCardUsername(
105+ type.platform,
106+ type.username
107+ )
108+ )
109+ ),
78110 )
79111 }
80112
81113 is Type .Unknown -> null
82114 }
83115 }
84116
117+ /* *
118+ * Handles converting inbound shared content with possible deeplinks
119+ * e.g sharing a tweet to trigger a tipcard flow
120+ */
121+ private suspend fun Uri.resolveSharedEntity (): Uri {
122+ when {
123+ this .host == " x.com" || this .host == " twitter.com" -> {
124+ // https://x.com/<username>/status/<tweetId>
125+ if (betaFlags.isEnabled(PrefsBool .SHARE_TWEET_TO_TIP )) {
126+ // convert shared tweets to owner's tip card
127+ val username = pathSegments.firstOrNull() ? : return this
128+ return Uri .parse(Linkify .tipCard(username, " x" ))
129+ }
130+ }
131+ }
132+ return this
133+ }
134+
85135 private val Uri .deeplinkType: Type
86136 get() {
87137 // check for tipcard URLs
88138 val components = pathSegments
89- if (components.count() = = 2 && components[0 ] == " x" && components[1 ].isNotEmpty()) {
139+ if (components.count() > = 2 && components[0 ] == " x" && components[1 ].isNotEmpty()) {
90140 return Type .Tip (components[0 ], components[1 ])
91141 }
92142
@@ -124,12 +174,13 @@ class DeeplinkHandler @Inject constructor() {
124174 sealed interface Type {
125175 data class Login (val link : String? ) : Type
126176 data class Cash (val link : String? ) : Type
127- data class Tip (val platform : String , val username : String ): Type
177+ data class Tip (val platform : String , val username : String ) : Type
128178 data class Sdk (val payload : String? ) : Type {
129179 companion object {
130180 val regex = Regex (" ^(login|payment|tip)?-?request-(modal|page)-(mobile|desktop)\$ " )
131181 }
132182 }
183+
133184 data class Unknown (val path : String? ) : Type
134185 }
135186
@@ -161,4 +212,5 @@ class DeeplinkHandler @Inject constructor() {
161212 }
162213}
163214
164- private operator fun Regex.contains (text : String? ): Boolean = text?.let { this .matches(it) } ? : false
215+ private operator fun Regex.contains (text : String? ): Boolean =
216+ text?.let { this .matches(it) } ? : false
0 commit comments