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
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class AnalyticsManager @Inject constructor(
//Bill
Bill("Bill"),
Request("Request Card"),
TipCard("TIp Card"),
TipCard("Tip Card"),

//Transfer
Transfer("Transfer"),
Expand Down
3 changes: 1 addition & 2 deletions api/src/main/java/com/getcode/model/PrefBool.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ sealed class PrefsBool(val value: String) {
data object SHOW_CONNECTIVITY_STATUS: PrefsBool("debug_no_network"), BetaFlag
data object GIVE_REQUESTS_ENABLED: PrefsBool("give_requests_enabled"), BetaFlag
data object BUY_MODULE_ENABLED : PrefsBool("buy_kin_enabled"), BetaFlag


data object CHAT_UNSUB_ENABLED: PrefsBool("chat_unsub_enabled"), BetaFlag
data object TIPS_ENABLED : PrefsBool("tips_enabled"), BetaFlag
data object TIPS_CHAT_ENABLED: PrefsBool("tips_chat_enabled"), BetaFlag
data object TIPS_CHAT_CASH_ENABLED: PrefsBool("tips_chat_cash_enabled"), BetaFlag
data object BALANCE_CURRENCY_SELECTION_ENABLED: PrefsBool("balance_currency_enabled"), BetaFlag
data object KADO_WEBVIEW_ENABLED : PrefsBool("kado_inapp_enabled"), BetaFlag
data object SHARE_TWEET_TO_TIP : PrefsBool("share_tweet_to_tip"), BetaFlag
}

val APP_SETTINGS: List<AppSetting> = listOf(PrefsBool.CAMERA_START_BY_DEFAULT, PrefsBool.REQUIRE_BIOMETRICS)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ data class BetaOptions(
val tipsChatCashEnabled: Boolean,
val balanceCurrencySelectionEnabled: Boolean,
val kadoWebViewEnabled: Boolean,
val shareTweetToTip: Boolean,
) {
companion object {
// Default states for various beta flags in app.
Expand All @@ -36,6 +37,7 @@ data class BetaOptions(
tipsChatCashEnabled = false,
balanceCurrencySelectionEnabled = true,
kadoWebViewEnabled = false,
shareTweetToTip = false
)
}
}
Expand Down Expand Up @@ -70,7 +72,8 @@ class BetaFlagsRepository @Inject constructor(
observeBetaFlag(PrefsBool.TIPS_CHAT_CASH_ENABLED, default = defaults.tipsChatCashEnabled),
observeBetaFlag(PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED, defaults.balanceCurrencySelectionEnabled),
observeBetaFlag(PrefsBool.DISPLAY_ERRORS, default = defaults.displayErrors),
observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled)
observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled),
observeBetaFlag(PrefsBool.SHARE_TWEET_TO_TIP, default = defaults.shareTweetToTip)
) {
BetaOptions(
showNetworkDropOff = it[0],
Expand All @@ -86,6 +89,7 @@ class BetaFlagsRepository @Inject constructor(
balanceCurrencySelectionEnabled = it[10],
displayErrors = it[11],
kadoWebViewEnabled = it[12],
shareTweetToTip = it[13]
)
}
}
Expand All @@ -98,4 +102,8 @@ class BetaFlagsRepository @Inject constructor(
b.takeIf { a } ?: default
}
}

suspend fun isEnabled(flag: PrefsBool): Boolean {
return prefRepository.get(flag, false)
}
}
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,5 @@ dependencies {
implementation(Libs.timber)
implementation(Libs.bugsnag)

implementation("dev.chrisbanes.haze:haze:0.7.3")
implementation(Libs.haze)
}
12 changes: 12 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@

</activity>

<activity-alias
android:name="com.getcode.view.TweetShareHandler"
android:exported="true"
android:targetActivity="com.getcode.view.MainActivity"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity-alias>

<service
android:name="com.getcode.util.AuthenticatorService"
android:exported="false">
Expand Down
7 changes: 0 additions & 7 deletions app/src/main/java/com/getcode/util/Context.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
package com.getcode.util

import android.content.Context
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationError
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.getcode.R
import com.getcode.network.repository.TransactionRepository.ErrorSubmitIntent
import timber.log.Timber
import java.util.concurrent.Executors

fun Context.launchAppSettings() {
val intent = IntentUtils.appSettings()
Expand Down
68 changes: 60 additions & 8 deletions app/src/main/java/com/getcode/util/DeeplinkHandler.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
package com.getcode.util

import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import cafe.adriel.voyager.core.screen.Screen
import com.getcode.model.BetaFlag
import com.getcode.model.PrefsBool
import com.getcode.models.DeepLinkRequest
import com.getcode.models.encode
import com.getcode.navigation.screens.HomeScreen
import com.getcode.navigation.screens.LoginScreen
import com.getcode.network.repository.BetaFlagsRepository
import com.getcode.network.repository.encodeBase64
import com.getcode.network.repository.urlDecode
import com.getcode.ui.utils.getActivity
import com.getcode.utils.TraceType
import com.getcode.utils.base64EncodedData
import com.getcode.utils.trace
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -19,6 +30,7 @@ data class DeeplinkResult(
val type: DeeplinkHandler.Type,
val stack: List<Screen>,
)

/**
* This class is used to manage intent state across navigation.
*
Expand All @@ -33,7 +45,10 @@ data class DeeplinkResult(
* in favour of the latest request in the navigation graph.
*/
@Singleton
class DeeplinkHandler @Inject constructor() {
class DeeplinkHandler @Inject constructor(
@ApplicationContext private val context: Context,
private val betaFlags: BetaFlagsRepository
) {
var debounceIntent: Intent? = null
set(value) {
intent.value = value
Expand All @@ -43,8 +58,18 @@ class DeeplinkHandler @Inject constructor() {

val intent = MutableStateFlow(debounceIntent)

fun handle(intent: Intent? = debounceIntent): DeeplinkResult? {
val uri = intent?.data ?: return null
suspend fun handle(intent: Intent? = debounceIntent): DeeplinkResult? {
println(intent)
val uri = when {
intent?.data != null -> intent.data
intent?.getStringExtra(Intent.EXTRA_TEXT) != null -> {
val sharedLink = intent.getStringExtra(Intent.EXTRA_TEXT)?.toUri() ?: return null
sharedLink.resolveSharedEntity()
}

else -> null
} ?: return null

return when (val type = uri.deeplinkType) {
is Type.Login -> {
DeeplinkResult(
Expand All @@ -63,7 +88,7 @@ class DeeplinkHandler @Inject constructor() {

is Type.Sdk -> {
Timber.d("sdk=${type.payload}")
val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest.from(it) }
val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest.from(it) }
DeeplinkResult(
type,
listOf(HomeScreen(request = request)),
Expand All @@ -74,19 +99,44 @@ class DeeplinkHandler @Inject constructor() {
Timber.d("tipcard for ${type.username} on ${type.platform}")
DeeplinkResult(
type,
listOf(HomeScreen(request = DeepLinkRequest.fromTipCardUsername(type.platform, type.username))),
listOf(
HomeScreen(
request = DeepLinkRequest.fromTipCardUsername(
type.platform,
type.username
)
)
),
)
}

is Type.Unknown -> null
}
}

/**
* Handles converting inbound shared content with possible deeplinks
* e.g sharing a tweet to trigger a tipcard flow
*/
private suspend fun Uri.resolveSharedEntity(): Uri {
when {
this.host == "x.com" || this.host == "twitter.com" -> {
// https://x.com/<username>/status/<tweetId>
if (betaFlags.isEnabled(PrefsBool.SHARE_TWEET_TO_TIP)) {
// convert shared tweets to owner's tip card
val username = pathSegments.firstOrNull() ?: return this
return Uri.parse(Linkify.tipCard(username, "x"))
}
}
}
return this
}

private val Uri.deeplinkType: Type
get() {
// check for tipcard URLs
val components = pathSegments
if (components.count() == 2 && components[0] == "x" && components[1].isNotEmpty()) {
if (components.count() >= 2 && components[0] == "x" && components[1].isNotEmpty()) {
return Type.Tip(components[0], components[1])
}

Expand Down Expand Up @@ -124,12 +174,13 @@ 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 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)\$")
}
}

data class Unknown(val path: String?) : Type
}

Expand Down Expand Up @@ -161,4 +212,5 @@ class DeeplinkHandler @Inject constructor() {
}
}

private operator fun Regex.contains(text: String?): Boolean = text?.let { this.matches(it) } ?: false
private operator fun Regex.contains(text: String?): Boolean =
text?.let { this.matches(it) } ?: false
16 changes: 5 additions & 11 deletions app/src/main/java/com/getcode/util/IntentUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ import android.content.Intent
import android.net.Uri
import android.provider.Settings
import com.getcode.BuildConfig
import com.getcode.R
import com.getcode.model.Currency
import com.getcode.model.KinAmount
import com.getcode.model.Username
import com.getcode.network.repository.replaceParam
import com.getcode.network.repository.urlEncode
import com.getcode.solana.organizer.GiftCardAccount
import com.getcode.utils.makeE164


object IntentUtils {

fun appSettings() = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
Expand All @@ -30,7 +24,7 @@ object IntentUtils {
}

fun tweet(message: String) = Intent(Intent.ACTION_VIEW).apply {
val url = "https://www.twitter.com/intent/tweet?text=${message.urlEncode()}"
val url = Linkify.tweet(message)
setData(Uri.parse(url))
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
Expand All @@ -47,8 +41,8 @@ object IntentUtils {
return shareIntent
}

fun tipCard(username: String): Intent {
val url = "https://tipcard.getcode.com/x/$username"
fun tipCard(username: String, platform: String): Intent {
val url = Linkify.tipCard(username, platform)

val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
Expand All @@ -67,7 +61,7 @@ object IntentUtils {
entropy: String,
formattedAmount: String,
): Intent {
val url = "https://cash.getcode.com/c/#/e=$entropy"
val url = Linkify.cashLink(entropy)
val text = "$formattedAmount $url"

val sendIntent: Intent = Intent().apply {
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/getcode/util/Linkify.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.getcode.util

import com.getcode.network.repository.urlEncode

object Linkify {
fun cashLink(entropy: String): String = "https://cash.getcode.com/c/#/e=${entropy}"
fun tipCard(username: String, platform: String): String = "https://tipcard.getcode.com/${platform}/${username}"
fun tweet(message: String): String = "https://www.twitter.com/intent/tweet?text=${message.urlEncode()}"
}
30 changes: 30 additions & 0 deletions app/src/main/java/com/getcode/util/Pacman.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.getcode.util

import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject


class Pacman @Inject constructor(
@ApplicationContext private val context: Context
) {
private val packageManager = context.packageManager

fun enableTweetShare(enable: Boolean) {
val component = ComponentName(context.packageName, "com.getcode.view.TweetShareHandler")
if (enable) {
packageManager.setComponentEnabledSetting(
component,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
)
} else {
packageManager.setComponentEnabledSetting(
component,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
)
}
}

}
Loading