diff --git a/apps/flipcash/app/build.gradle.kts b/apps/flipcash/app/build.gradle.kts
index 3afcf1b8e..ff4ff6f12 100644
--- a/apps/flipcash/app/build.gradle.kts
+++ b/apps/flipcash/app/build.gradle.kts
@@ -161,6 +161,7 @@ dependencies {
implementation(project(":apps:flipcash:shared:permissions"))
implementation(project(":apps:flipcash:shared:phone"))
implementation(project(":apps:flipcash:shared:shareable"))
+ implementation(project(":apps:flipcash:shared:invite"))
implementation(project(":apps:flipcash:shared:tokens"))
implementation(project(":apps:flipcash:shared:web"))
implementation(project(":apps:flipcash:shared:workers"))
@@ -186,6 +187,7 @@ dependencies {
implementation(project(":apps:flipcash:features:bill-customization"))
implementation(project(":apps:flipcash:features:currency-creator"))
implementation(project(":apps:flipcash:features:direct-send"))
+ implementation(project(":apps:flipcash:features:invite"))
implementation(project(":apps:flipcash:features:discovery"))
implementation(project(":apps:flipcash:features:userflags"))
diff --git a/apps/flipcash/app/src/main/AndroidManifest.xml b/apps/flipcash/app/src/main/AndroidManifest.xml
index 525dd6a7c..0c8b07619 100644
--- a/apps/flipcash/app/src/main/AndroidManifest.xml
+++ b/apps/flipcash/app/src/main/AndroidManifest.xml
@@ -26,6 +26,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{ key -> AppRestrictedScreen(key.restrictionType) }
annotatedEntry { ScannerScreen() }
annotatedEntry { RegionSelectionScreen() }
+ annotatedEntry { key -> InviteContactScreen(key.phoneNumber) }
// Sheets (inner content — wrapped in Main.Sheet by navigateTo())
annotatedEntry { key -> CashScreen(key.mint, key.fromTokenInfo) }
- annotatedEntry { }
+ annotatedEntry { }
annotatedEntry { key -> TokenSelectScreen(key.purpose) }
annotatedEntry { BalanceScreen() }
annotatedEntry { ShareAppScreen() }
annotatedEntry { MenuScreen() }
-
// Tokens
annotatedEntry { key ->
TokenInfoScreen(key.mint, key.shortfall, key.fromDeeplink)
@@ -97,6 +98,7 @@ fun appEntryProvider(
annotatedEntry { key ->
SwapFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
}
+ // TODO: fold this into above entry
annotatedEntry { key ->
TokenTxProcessingScreen(key.swapId, key.swapPurpose, key.amount, key.isFundingShortfall)
}
@@ -120,6 +122,7 @@ fun appEntryProvider(
annotatedEntry { DeviceLogsScreen() }
annotatedEntry { UserFlagsScreen() }
+
// Transfers
annotatedEntry { key ->
DepositFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt
index 98da0cc1f..b8fd13daf 100644
--- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt
+++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt
@@ -101,6 +101,9 @@ sealed interface AppRoute : NavKey, Parcelable {
@Serializable
data object RegionSelection : Main
+ @Serializable
+ data class InviteContact(val phoneNumber: String) : com.getcode.navigation.Sheet, com.getcode.navigation.WrapContentSheet
+
@Serializable
@Parcelize
data class Sheet(
diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/Linkify.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/Linkify.kt
index ca0552371..0ba107fbf 100644
--- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/Linkify.kt
+++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/Linkify.kt
@@ -8,6 +8,8 @@ import com.getcode.utils.urlEncode
object Linkify {
fun cashLink(entropy: String): String = "https://send.flipcash.com/c/#/e=${entropy}"
fun download(shareRef: String): String = "https://flipcash.com/download?r=${shareRef}"
+ fun whatsApp(phoneNumber: String, message: String): String =
+ "https://wa.me/${phoneNumber.removePrefix("+")}?text=${message.urlEncode()}"
fun tweet(message: String): String = "https://www.twitter.com/intent/tweet?text=${message.urlEncode()}"
fun tokenInfo(token: Token): String = tokenInfo(token.address)
fun tokenInfo(mint: Mint): String = "https://app.flipcash.com/token/${mint.base58()}"
diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/MessagingPackages.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/MessagingPackages.kt
new file mode 100644
index 000000000..4b33a8945
--- /dev/null
+++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/util/MessagingPackages.kt
@@ -0,0 +1,14 @@
+package com.flipcash.app.core.util
+
+object MessagingPackages {
+ val whatsApp = listOf(
+ "com.whatsapp",
+ "com.whatsapp.w4b",
+ )
+
+ val all = setOf(
+ "org.thoughtcrime.securesms", // Signal
+ "org.telegram.messenger", // Telegram
+ "com.facebook.orca", // Messenger
+ ) + whatsApp
+}
diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index b6ac480e9..fed1e7925 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -152,6 +152,8 @@
Get the Flipcash App
Send %1$s
+ Invite to Flipcash
+ Download Flipcash so I can send you money\n\n%1$s
Enter Solana address
Where would you like to withdraw your %1$s to?
@@ -693,4 +695,7 @@
Learn more
Leaderboard Ranking
People must have a minimum balance of %1$s to be counted
+
+ Invite
+ More
\ No newline at end of file
diff --git a/apps/flipcash/features/invite/.gitignore b/apps/flipcash/features/invite/.gitignore
new file mode 100644
index 000000000..9f2a07880
--- /dev/null
+++ b/apps/flipcash/features/invite/.gitignore
@@ -0,0 +1,2 @@
+build/
+.gradle/
diff --git a/apps/flipcash/features/invite/build.gradle.kts b/apps/flipcash/features/invite/build.gradle.kts
new file mode 100644
index 000000000..806212c4c
--- /dev/null
+++ b/apps/flipcash/features/invite/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ alias(libs.plugins.flipcash.android.feature)
+}
+
+android {
+ namespace = "${Gradle.flipcashNamespace}.features.invite"
+}
+
+dependencies {
+ implementation(project(":apps:flipcash:shared:invite"))
+}
diff --git a/apps/flipcash/features/invite/src/main/kotlin/com/flipcash/app/invite/InviteContactScreen.kt b/apps/flipcash/features/invite/src/main/kotlin/com/flipcash/app/invite/InviteContactScreen.kt
new file mode 100644
index 000000000..baf0c06d1
--- /dev/null
+++ b/apps/flipcash/features/invite/src/main/kotlin/com/flipcash/app/invite/InviteContactScreen.kt
@@ -0,0 +1,102 @@
+package com.flipcash.app.invite
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.MoreHoriz
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import coil3.compose.AsyncImage
+import com.flipcash.core.R
+import com.getcode.navigation.scenes.LocalBottomSheetDismissDispatcher
+import com.getcode.theme.CodeTheme
+import com.getcode.ui.core.rememberedClickable
+
+@Composable
+fun InviteContactScreen(phoneNumber: String) {
+ val controller = LocalInviteController.current
+ val dismiss = LocalBottomSheetDismissDispatcher.current
+ val channels by controller.channels.collectAsStateWithLifecycle()
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = CodeTheme.dimens.inset,
+ vertical = CodeTheme.dimens.grid.x4,
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(R.string.title_inviteContact),
+ style = CodeTheme.typography.textLarge,
+ color = CodeTheme.colors.textMain,
+ modifier = Modifier.padding(bottom = CodeTheme.dimens.grid.x4),
+ )
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ ) {
+ channels.forEach { channel ->
+ ChannelItem(
+ channel = channel,
+ onClick = {
+ controller.launch(channel, phoneNumber)
+ dismiss()
+ },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ChannelItem(
+ channel: InviteChannel,
+ onClick: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .rememberedClickable(onClick = onClick)
+ .padding(CodeTheme.dimens.grid.x2),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1),
+ ) {
+ if (channel.icon != null) {
+ AsyncImage(
+ model = channel.icon,
+ contentDescription = channel.label,
+ modifier = Modifier.size(CodeTheme.dimens.staticGrid.x10),
+ )
+ } else {
+ Icon(
+ imageVector = Icons.Filled.MoreHoriz,
+ contentDescription = channel.label,
+ modifier = Modifier.size(CodeTheme.dimens.staticGrid.x10),
+ tint = CodeTheme.colors.textMain,
+ )
+ }
+ Text(
+ text = channel.label,
+ style = CodeTheme.typography.textSmall,
+ color = CodeTheme.colors.textMain,
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+}
diff --git a/apps/flipcash/shared/invite/.gitignore b/apps/flipcash/shared/invite/.gitignore
new file mode 100644
index 000000000..9f2a07880
--- /dev/null
+++ b/apps/flipcash/shared/invite/.gitignore
@@ -0,0 +1,2 @@
+build/
+.gradle/
diff --git a/apps/flipcash/shared/invite/build.gradle.kts b/apps/flipcash/shared/invite/build.gradle.kts
new file mode 100644
index 000000000..e5dd16d47
--- /dev/null
+++ b/apps/flipcash/shared/invite/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ alias(libs.plugins.flipcash.android.feature)
+}
+
+android {
+ namespace = "${Gradle.flipcashNamespace}.shared.invite"
+}
+
+dependencies {
+ implementation(project(":apps:flipcash:shared:shareable"))
+}
diff --git a/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/InviteController.kt b/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/InviteController.kt
new file mode 100644
index 000000000..24e515e8a
--- /dev/null
+++ b/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/InviteController.kt
@@ -0,0 +1,34 @@
+package com.flipcash.app.invite
+
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.staticCompositionLocalOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+data class InviteChannel(
+ val label: String,
+ val icon: Drawable?,
+ val type: InviteChannelType,
+)
+
+sealed interface InviteChannelType {
+ data object Sms : InviteChannelType
+ data class WhatsApp(val packageName: String) : InviteChannelType
+ data object More : InviteChannelType
+}
+
+interface InviteController {
+ val channels: StateFlow>
+ fun refresh()
+ fun launch(channel: InviteChannel, phoneNumber: String)
+}
+
+private object NoOpInviteController : InviteController {
+ override val channels: StateFlow> = MutableStateFlow(emptyList())
+ override fun refresh() = Unit
+ override fun launch(channel: InviteChannel, phoneNumber: String) = Unit
+}
+
+val LocalInviteController: ProvidableCompositionLocal =
+ staticCompositionLocalOf { NoOpInviteController }
diff --git a/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/inject/InviteModule.kt b/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/inject/InviteModule.kt
new file mode 100644
index 000000000..4ddb603da
--- /dev/null
+++ b/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/inject/InviteModule.kt
@@ -0,0 +1,30 @@
+package com.flipcash.app.invite.inject
+
+import android.content.Context
+import com.flipcash.app.invite.InviteController
+import com.flipcash.app.invite.internal.InternalInviteController
+import com.flipcash.app.shareable.ShareSheetController
+import com.getcode.util.resources.ResourceHelper
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object InviteModule {
+
+ @Provides
+ @Singleton
+ fun provideInviteController(
+ @ApplicationContext context: Context,
+ resources: ResourceHelper,
+ shareController: ShareSheetController,
+ ): InviteController = InternalInviteController(
+ context = context,
+ resources = resources,
+ shareController = shareController,
+ )
+}
diff --git a/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/internal/InternalInviteController.kt b/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/internal/InternalInviteController.kt
new file mode 100644
index 000000000..620f4b9a4
--- /dev/null
+++ b/apps/flipcash/shared/invite/src/main/kotlin/com/flipcash/app/invite/internal/InternalInviteController.kt
@@ -0,0 +1,124 @@
+package com.flipcash.app.invite.internal
+
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import androidx.core.net.toUri
+import com.flipcash.app.core.util.Linkify
+import com.flipcash.app.core.util.MessagingPackages
+import com.flipcash.app.invite.InviteChannel
+import com.flipcash.app.invite.InviteChannelType
+import com.flipcash.app.invite.InviteController
+import com.flipcash.app.shareable.ShareSheetController
+import com.flipcash.app.shareable.Shareable
+import com.flipcash.shared.invite.R
+import com.getcode.util.resources.ResourceHelper
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.runBlocking
+
+internal class InternalInviteController(
+ @param:ApplicationContext
+ private val context: Context,
+ private val resources: ResourceHelper,
+ private val shareController: ShareSheetController,
+) : InviteController {
+
+ private val _channels = MutableStateFlow(resolveChannels())
+ override val channels: StateFlow> = _channels
+
+ override fun refresh() {
+ _channels.value = resolveChannels()
+ }
+
+ private fun resolveChannels(): List = buildList {
+ val pm = context.packageManager
+
+ // SMS — always present
+ val smsIntent = Intent(Intent.ACTION_SENDTO, "smsto:".toUri())
+ val smsApps = pm.queryIntentActivities(smsIntent, 0)
+ val smsInfo = smsApps.firstOrNull()
+ add(
+ InviteChannel(
+ label = smsInfo?.loadLabel(pm)?.toString() ?: "SMS",
+ icon = smsInfo?.loadIcon(pm),
+ type = InviteChannelType.Sms,
+ )
+ )
+
+ // WhatsApp
+ for (pkg in MessagingPackages.whatsApp) {
+ try {
+ val appInfo = pm.getApplicationInfo(pkg, 0)
+ add(
+ InviteChannel(
+ label = pm.getApplicationLabel(appInfo).toString(),
+ icon = pm.getApplicationIcon(appInfo),
+ type = InviteChannelType.WhatsApp(pkg),
+ )
+ )
+ break // only show one WhatsApp variant
+ } catch (_: PackageManager.NameNotFoundException) {
+ // not installed
+ }
+ }
+
+ // More (fallback)
+ add(
+ InviteChannel(
+ label = resources.getString(R.string.action_inviteMoreOptions),
+ icon = null,
+ type = InviteChannelType.More,
+ )
+ )
+ }
+
+ private fun inviteMessage(): String {
+ val shareRef = resources.getString(R.string.app_download_link_share_ref)
+ val url = Linkify.download(shareRef)
+ return resources.getString(R.string.message_invite_contact, url)
+ }
+
+ override fun launch(channel: InviteChannel, phoneNumber: String) {
+ when (channel.type) {
+ is InviteChannelType.Sms -> launchSms(phoneNumber)
+ is InviteChannelType.WhatsApp -> launchWhatsApp(channel.type.packageName, phoneNumber)
+ is InviteChannelType.More -> launchMore()
+ }
+ }
+
+ private fun launchSms(phoneNumber: String) {
+ val intent = Intent(
+ Intent.ACTION_SENDTO,
+ "smsto:$phoneNumber".toUri()
+ ).apply {
+ putExtra("sms_body", inviteMessage())
+ }
+ try {
+ context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ } catch (_: ActivityNotFoundException) {
+ launchMore()
+ }
+ }
+
+ private fun launchWhatsApp(packageName: String, phoneNumber: String) {
+ val intent = Intent(
+ Intent.ACTION_VIEW,
+ Linkify.whatsApp(phoneNumber, inviteMessage()).toUri()
+ ).apply {
+ setPackage(packageName)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ try {
+ context.startActivity(intent)
+ } catch (_: ActivityNotFoundException) {
+ launchMore()
+ }
+ }
+
+ private fun launchMore() {
+ runBlocking { shareController.present(Shareable.Invite) }
+ }
+}
diff --git a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt
index 81532bae5..a373191e8 100644
--- a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt
+++ b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt
@@ -36,6 +36,10 @@ sealed interface Shareable {
): Shareable {
override val pendingData: ShareablePendingData? = null
}
+
+ data object Invite : Shareable {
+ override val pendingData: ShareablePendingData? = null
+ }
}
sealed interface ShareablePendingData {
diff --git a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt
index 9f50c14ea..01dbeefee 100644
--- a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt
+++ b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt
@@ -2,7 +2,6 @@ package com.flipcash.app.shareable.internal
import com.flipcash.app.core.internal.bill.BillController
import com.flipcash.app.shareable.ShareConfirmationResult
-import com.flipcash.app.shareable.ShareConfirmationResult.*
import com.flipcash.app.shareable.ShareResult
import com.flipcash.app.shareable.Shareable
import com.flipcash.app.shareable.ShareableConfirmationController
@@ -26,8 +25,9 @@ internal class InternalShareConfirmationController(
): ShareConfirmationResult {
return when (shareable) {
is Shareable.CashLink -> confirmCashLink(shareResult, shareable.autoConfirmationAfter)
- is Shareable.DownloadLink -> Confirmed(shareResult)
- is Shareable.TokenInfo -> Confirmed(shareResult)
+ is Shareable.DownloadLink -> ShareConfirmationResult.Confirmed(shareResult)
+ is Shareable.TokenInfo -> ShareConfirmationResult.Confirmed(shareResult)
+ is Shareable.Invite -> ShareConfirmationResult.Confirmed(shareResult)
}
}
diff --git a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt
index 000a897e6..fbd6048cf 100644
--- a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt
+++ b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt
@@ -3,12 +3,16 @@ package com.flipcash.app.shareable.internal
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.ClipboardManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.os.Build
+import androidx.core.net.toUri
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.flipcash.app.core.money.formatted
import com.flipcash.app.core.util.Linkify
+import com.flipcash.app.core.util.MessagingPackages
import com.flipcash.app.shareable.ShareResult
import com.flipcash.app.shareable.ShareSheetController
import com.flipcash.app.shareable.ShareSheetController.Companion.ACTION_CASH_LINK_SHARED
@@ -95,6 +99,7 @@ internal class InternalShareSheetController(
Shareable.DownloadLink -> Unit
is Shareable.TokenInfo -> Unit
+ is Shareable.Invite -> Unit
}
}
}
@@ -135,6 +140,10 @@ internal class InternalShareSheetController(
is Shareable.TokenInfo -> {
shareToken(shareable.token)
}
+
+ is Shareable.Invite -> {
+ shareInviteLink()
+ }
}
}
@@ -207,6 +216,50 @@ internal class InternalShareSheetController(
context.startActivity(share)
}
+ private fun shareInviteLink() {
+ val shareRef = resources.getString(R.string.app_download_link_share_ref)
+ val url = Linkify.download(shareRef)
+ val message = resources.getString(R.string.message_invite_contact, url)
+
+ val sendIntent = Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_TEXT, message)
+ type = "text/plain"
+ }
+
+ val chooser = Intent.createChooser(sendIntent, null).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val pm = context.packageManager
+ val messagingPackages = buildSet {
+ val messagingIntent = Intent(Intent.ACTION_MAIN).apply {
+ addCategory(Intent.CATEGORY_APP_MESSAGING)
+ }
+ pm.queryIntentActivities(messagingIntent, 0)
+ .forEach { add(it.activityInfo.packageName) }
+
+ val smsIntent = Intent(Intent.ACTION_SENDTO, "smsto:".toUri())
+ pm.queryIntentActivities(smsIntent, 0)
+ .forEach { add(it.activityInfo.packageName) }
+
+ addAll(MessagingPackages.all)
+ }
+
+ val excludedComponents = pm.queryIntentActivities(sendIntent, 0)
+ .filter { it.activityInfo.packageName !in messagingPackages }
+ .map { ComponentName(it.activityInfo.packageName, it.activityInfo.name) }
+ .toTypedArray()
+
+ if (excludedComponents.isNotEmpty()) {
+ chooser.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, excludedComponents)
+ }
+ }
+
+ context.startActivity(chooser)
+ }
+
private fun shareToken(token: Token) {
val url = Linkify.tokenInfo(token)
val intent = Intent().apply {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 6212ce594..7f131424c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -71,6 +71,7 @@ include(
":apps:flipcash:shared:router",
":apps:flipcash:shared:session",
":apps:flipcash:shared:shareable",
+ ":apps:flipcash:shared:invite",
":apps:flipcash:shared:tokens",
":apps:flipcash:shared:tokens:core",
":apps:flipcash:shared:theme",
@@ -91,6 +92,7 @@ include(
":apps:flipcash:features:advanced",
":apps:flipcash:features:currency-creator",
":apps:flipcash:features:direct-send",
+ ":apps:flipcash:features:invite",
":apps:flipcash:features:device-logs",
":apps:flipcash:features:myaccount",
":apps:flipcash:features:backupkey",