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
2 changes: 2 additions & 0 deletions apps/flipcash/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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"))

Expand Down
17 changes: 17 additions & 0 deletions apps/flipcash/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@
<action android:name="android.intent.action.VIEW" />
<data android:host="phantom.app" />
</intent>

<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.APP_MESSAGING" />
</intent>

<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="smsto" />
</intent>

<!-- Messaging apps for invite share sheet filtering -->
<package android:name="com.whatsapp" />
<package android:name="com.whatsapp.w4b" />
<package android:name="org.thoughtcrime.securesms" />
<package android:name="org.telegram.messenger" />
<package android:name="com.facebook.orca" />
</queries>

<application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import com.flipcash.app.router.LocalRouter
import com.flipcash.app.router.Router
import com.flipcash.app.session.LocalSessionController
import com.flipcash.app.session.SessionController
import com.flipcash.app.invite.InviteController
import com.flipcash.app.invite.LocalInviteController
import com.flipcash.app.shareable.LocalShareController
import com.flipcash.app.shareable.ShareSheetController
import com.flipcash.app.updates.AppUpdateController
Expand Down Expand Up @@ -96,6 +98,9 @@ class MainActivity : FragmentActivity() {
@Inject
lateinit var shareController: ShareSheetController

@Inject
lateinit var inviteController: InviteController

@Inject
lateinit var appSettingsCoordinator: AppSettingsCoordinator

Expand Down Expand Up @@ -140,6 +145,7 @@ class MainActivity : FragmentActivity() {
LocalUserManager provides userManager,
LocalSessionController provides sessionController,
LocalShareController provides shareController,
LocalInviteController provides inviteController,
LocalAppSettings provides appSettingsCoordinator,
LocalFeatureFlags provides featureFlagController,
LocalPhoneUtils provides phoneUtils,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.flipcash.app.currencycreator.CurrencyCreatorFlowScreen
import com.flipcash.app.core.AppRoute
import com.flipcash.app.currency.RegionSelectionScreen
import com.flipcash.app.deposit.DepositFlowScreen
import com.flipcash.app.invite.InviteContactScreen
import com.flipcash.app.discovery.TokenDiscoveryScreen
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
import com.flipcash.app.lab.LabsScreen
Expand Down Expand Up @@ -79,16 +80,16 @@ fun appEntryProvider(
annotatedEntry<AppRoute.Main.AppRestricted> { key -> AppRestrictedScreen(key.restrictionType) }
annotatedEntry<AppRoute.Main.Scanner> { ScannerScreen() }
annotatedEntry<AppRoute.Main.RegionSelection> { RegionSelectionScreen() }
annotatedEntry<AppRoute.Main.InviteContact> { key -> InviteContactScreen(key.phoneNumber) }

// Sheets (inner content — wrapped in Main.Sheet by navigateTo())
annotatedEntry<AppRoute.Sheets.Give> { key -> CashScreen(key.mint, key.fromTokenInfo) }
annotatedEntry<AppRoute.Sheets.Send> { }
annotatedEntry<AppRoute.Sheets.Send> { }
annotatedEntry<AppRoute.Sheets.TokenSelection> { key -> TokenSelectScreen(key.purpose) }
annotatedEntry<AppRoute.Sheets.Wallet> { BalanceScreen() }
annotatedEntry<AppRoute.Sheets.ShareApp> { ShareAppScreen() }
annotatedEntry<AppRoute.Sheets.Menu> { MenuScreen() }


// Tokens
annotatedEntry<AppRoute.Token.Info> { key ->
TokenInfoScreen(key.mint, key.shortfall, key.fromDeeplink)
Expand All @@ -97,6 +98,7 @@ fun appEntryProvider(
annotatedEntry<AppRoute.Token.Swap> { key ->
SwapFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
}
// TODO: fold this into above entry
annotatedEntry<AppRoute.Token.TxProcessing> { key ->
TokenTxProcessingScreen(key.swapId, key.swapPurpose, key.amount, key.isFundingShortfall)
}
Expand All @@ -120,6 +122,7 @@ fun appEntryProvider(
annotatedEntry<AppRoute.Menu.DeviceLogs> { DeviceLogsScreen() }

annotatedEntry<AppRoute.UserFlags> { UserFlagsScreen() }

// Transfers
annotatedEntry<AppRoute.Transfers.Deposit> { key ->
DepositFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()}"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@

<string name="title_shareDownloadLink">Get the Flipcash App</string>
<string name="title_shareCashLink">Send %1$s</string>
<string name="title_inviteContact">Invite to Flipcash</string>
<string name="message_invite_contact">Download Flipcash so I can send you money\n\n%1$s</string>

<string name="title_enterAddress">Enter Solana address</string>
<string name="subtitle_whereWithdrawTo">Where would you like to withdraw your %1$s to?</string>
Expand Down Expand Up @@ -693,4 +695,7 @@
<string name="content_description_leaderboard">Learn more</string>
<string name="prompt_title_learnAboutLeaderboard">Leaderboard Ranking</string>
<string name="prompt_description_learnAboutLeaderboard">People must have a minimum balance of %1$s to be counted</string>

<string name="action_invite">Invite</string>
<string name="action_inviteMoreOptions">More</string>
</resources>
2 changes: 2 additions & 0 deletions apps/flipcash/features/invite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build/
.gradle/
11 changes: 11 additions & 0 deletions apps/flipcash/features/invite/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
alias(libs.plugins.flipcash.android.feature)
}

android {
namespace = "${Gradle.flipcashNamespace}.features.invite"
}

dependencies {
implementation(project(":apps:flipcash:shared:invite"))
}
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
2 changes: 2 additions & 0 deletions apps/flipcash/shared/invite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build/
.gradle/
11 changes: 11 additions & 0 deletions apps/flipcash/shared/invite/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
alias(libs.plugins.flipcash.android.feature)
}

android {
namespace = "${Gradle.flipcashNamespace}.shared.invite"
}

dependencies {
implementation(project(":apps:flipcash:shared:shareable"))
}
Original file line number Diff line number Diff line change
@@ -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<List<InviteChannel>>
fun refresh()
fun launch(channel: InviteChannel, phoneNumber: String)
}

private object NoOpInviteController : InviteController {
override val channels: StateFlow<List<InviteChannel>> = MutableStateFlow(emptyList())
override fun refresh() = Unit
override fun launch(channel: InviteChannel, phoneNumber: String) = Unit
}

val LocalInviteController: ProvidableCompositionLocal<InviteController> =
staticCompositionLocalOf { NoOpInviteController }
Original file line number Diff line number Diff line change
@@ -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,
)
}
Loading
Loading