Skip to content

Commit 659599c

Browse files
committed
feat: share tweet to tip
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 211033f commit 659599c

3 files changed

Lines changed: 66 additions & 9 deletions

File tree

app/src/main/AndroidManifest.xml

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

141+
<intent-filter>
142+
<action android:name="android.intent.action.SEND"/>
143+
<category android:name="android.intent.category.DEFAULT"/>
144+
<data android:mimeType="text/plain"/>
145+
</intent-filter>
146+
141147
</activity>
142148

143149
<service

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.getcode.utils.ErrorUtils
1010
import com.getcode.vendor.Base58
1111
import kotlinx.serialization.SerialName
1212
import kotlinx.serialization.Serializable
13+
import kotlinx.serialization.encodeToString
1314
import kotlinx.serialization.json.Json
1415
import kotlinx.serialization.json.JsonObject
1516
import kotlinx.serialization.json.decodeFromJsonElement
@@ -44,20 +45,16 @@ data class DeepLinkRequest(
4445

4546
val mode = container.decode<Mode>("mode") ?: return null
4647
Timber.d("mode=$mode")
47-
val secret = container.decode<String>("clientSecret") ?: return null
48-
val clientSecret = Base58.decode(secret)
49-
if (clientSecret.size != 11) {
50-
Timber.e("Invalid client secret")
51-
return null
52-
}
48+
val secret = container.decode<String>("clientSecret")
49+
val clientSecret = secret?.let { Base58.decode(it) }
5350

5451
val (successUrl, cancelUrl) = container.decode<ConfirmParams>("confirmParams")
5552
?: ConfirmParams(null, null)
5653

5754

5855
val baseRequest = DeepLinkRequest(
5956
mode = mode,
60-
clientSecret = clientSecret.toList(),
57+
clientSecret = clientSecret?.toList().orEmpty(),
6158
successUrl = successUrl?.url,
6259
cancelUrl = cancelUrl?.url,
6360
)
@@ -154,6 +151,10 @@ private inline fun <reified T> JsonObject.decode(key: String): T? {
154151
}
155152
}
156153

154+
inline fun <reified T> encode(data: T): String {
155+
return runCatching { Json.encodeToString<T>(data) }.getOrNull().orEmpty()
156+
}
157+
157158
private inline fun <reified T, R> JsonObject.decode(key: String, map: (T) -> R): R? {
158159
return decode<T>(key)?.let { map(it) }
159160
}
@@ -166,7 +167,7 @@ private data class LoginKeys(
166167
)
167168

168169
@Serializable
169-
private data class PlatformKeys(
170+
data class PlatformKeys(
170171
val name: String,
171172
val username: String,
172173
)

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ package com.getcode.util
22

33
import android.content.Intent
44
import android.net.Uri
5+
import androidx.core.net.toUri
56
import cafe.adriel.voyager.core.screen.Screen
7+
import com.getcode.models.DeepLinkRequest
8+
import com.getcode.models.PlatformKeys
9+
import com.getcode.models.encode
610
import com.getcode.navigation.screens.HomeScreen
711
import com.getcode.navigation.screens.LoginScreen
12+
import com.getcode.network.repository.encodeBase64
813
import com.getcode.network.repository.urlDecode
914
import com.getcode.utils.TraceType
15+
import com.getcode.utils.base64EncodedData
1016
import com.getcode.utils.trace
17+
import com.getcode.vendor.Base58
1118
import kotlinx.coroutines.flow.MutableStateFlow
19+
import kotlinx.serialization.ExperimentalSerializationApi
20+
import kotlinx.serialization.encodeToString
21+
import kotlinx.serialization.json.buildJsonObject
22+
import kotlinx.serialization.json.put
1223
import timber.log.Timber
1324
import javax.inject.Inject
1425
import javax.inject.Singleton
@@ -42,7 +53,17 @@ class DeeplinkHandler @Inject constructor() {
4253
val intent = MutableStateFlow(debounceIntent)
4354

4455
fun handle(intent: Intent? = debounceIntent): DeeplinkResult? {
45-
val uri = intent?.data ?: return null
56+
val uri = when {
57+
intent?.data != null -> intent.data
58+
intent?.getStringExtra(Intent.EXTRA_TEXT) != null -> {
59+
val sharedLink = intent.getStringExtra(Intent.EXTRA_TEXT)?.toUri() ?: return null
60+
sharedLink.resolveSharedEntity
61+
}
62+
63+
else -> null
64+
} ?: return null
65+
66+
println("resolved uri=$uri")
4667
return when (val type = uri.deeplinkType) {
4768
is Type.Login -> {
4869
DeeplinkResult(
@@ -71,6 +92,35 @@ class DeeplinkHandler @Inject constructor() {
7192
}
7293
}
7394

95+
/**
96+
* Handles converting inbound shared content with possible deeplinks
97+
* e.g sharing a tweet to trigger a tipcard flow
98+
*/
99+
private val Uri.resolveSharedEntity: Uri
100+
get() {
101+
// https://x.com/<username>/status/<tweetId>
102+
when (this.host) {
103+
"x.com",
104+
"twitter.com" -> {
105+
// convert shared tweets to owner's tip card
106+
val username = pathSegments.firstOrNull() ?: return this
107+
val payload = buildJsonObject {
108+
put("mode", "tip")
109+
put(
110+
"platform",
111+
buildJsonObject {
112+
put("name", "Twitter")
113+
put("username", username)
114+
}
115+
)
116+
}
117+
val encodedPayload = encode(payload).toByteArray().encodeBase64()
118+
return "codewallet://sdk.getcode.com/v1/elements/tip-request-page-mobile/#/p=$encodedPayload".toUri()
119+
}
120+
else -> return this
121+
}
122+
}
123+
74124
private val Uri.deeplinkType: Type
75125
get() = when (val segment = lastPathSegment) {
76126
"login" -> {

0 commit comments

Comments
 (0)