From 50ac9ad02835dbc77611f8e72bc7793c13c44185 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 27 May 2026 14:02:18 -0400 Subject: [PATCH] feat(contacts): add phone-number send beta flag and gate send feature behind it Add FeatureFlag.PhoneNumberSend as a persistent beta toggle that combines with the server-side enablePhoneNumberSend user flag. When the effective value is false, the contact permission screen is skipped during onboarding and the Send button is hidden from the scanner nav bar. The user flag is surfaced as read-only in the user flags editor. Signed-off-by: Brandon McAnsh --- .../ui/navigation/AppScreenContent.kt | 1 + .../kotlin/com/flipcash/app/core/AppRoute.kt | 3 +++ .../app/core/navigation/NavBarButton.kt | 3 ++- .../com/flipcash/app/core/ui/NavigationBar.kt | 8 +++++++ .../core/src/main/res/values/strings.xml | 1 + apps/flipcash/features/direct-send/.gitignore | 2 ++ .../features/direct-send/build.gradle.kts | 24 +++++++++++++++++++ .../app/login/accesskey/AccessKeyScreen.kt | 18 ++++++++++---- .../accesskey/LoginAccessKeyViewModel.kt | 7 ++++++ .../app/scanner/internal/ScannerDecorItem.kt | 1 + .../internal/ui/components/DecorView.kt | 2 +- .../ui/components/ScannerNavigationBar.kt | 8 ++++++- .../userflags/internal/UserFlagsViewModel.kt | 4 ++++ .../app/contacts/ContactCoordinator.kt | 9 ++++++- .../flipcash/app/featureflags/FeatureFlag.kt | 11 +++++++++ .../flipcash/app/session/SessionController.kt | 1 + .../session/internal/RealSessionController.kt | 8 +++++++ .../SessionControllerGiftCardErrorTest.kt | 3 ++- .../app/userflags/ResolvedUserFlags.kt | 4 +++- .../userflags/src/main/res/values/strings.xml | 1 + .../account/v1/flipcash_account_service.proto | 3 +++ .../contact/v1/contact_list_service.proto | 1 - .../src/main/proto/contact/v1/model.proto | 1 - .../internal/domain/UserFlagsMapper.kt | 3 ++- .../com/flipcash/services/models/UserFlags.kt | 2 ++ .../internal/domain/UserFlagsMapperTest.kt | 11 +++++++++ settings.gradle.kts | 1 + 27 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 apps/flipcash/features/direct-send/.gitignore create mode 100644 apps/flipcash/features/direct-send/build.gradle.kts diff --git a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt index 7b23e8b09..a0f5c628e 100644 --- a/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt +++ b/apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt @@ -96,6 +96,7 @@ fun appEntryProvider( // Sheets (inner content — wrapped in Main.Sheet by navigateTo()) annotatedEntry { key -> CashScreen(key.mint, key.fromTokenInfo) } + annotatedEntry { } annotatedEntry { key -> TokenSelectScreen(key.purpose) } annotatedEntry { BalanceScreen() } annotatedEntry { ShareAppScreen() } 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 75e154124..3eb8d4d8a 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 @@ -110,6 +110,9 @@ sealed interface AppRoute : NavKey, Parcelable { data class TokenSelection(val purpose: TokenPurpose) : Sheets @Serializable data class Give(val mint: Mint? = null, val fromTokenInfo: Boolean = false) : Sheets + + @Serializable + data object Send: Sheets @Serializable data object Wallet : Sheets @Serializable diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt index df7647e53..27a910bf3 100644 --- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt +++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt @@ -4,9 +4,10 @@ enum class NavBarButton { Give, Wallet, Discover, + Send, ; companion object { - val defaultOrder = listOf(Give, Wallet, Discover) + val defaultOrder = listOf(Discover, Give, Send, Wallet,) } } diff --git a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt index e086cfdd5..7c46a3ec2 100644 --- a/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt +++ b/apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt @@ -132,6 +132,14 @@ fun NavigationBar( badgeCount = 0, onClick = { onButtonClick(NavBarButton.Discover) } ) + + NavBarButton.Send -> BottomBarAction( + modifier = buttonModifier, + label = stringResource(R.string.action_send), + painter = painterResource(R.drawable.ic_send_outlined), + badgeCount = 0, + onClick = { onButtonClick(NavBarButton.Send) } + ) } } } diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml index 2282dab20..02f489531 100644 --- a/apps/flipcash/core/src/main/res/values/strings.xml +++ b/apps/flipcash/core/src/main/res/values/strings.xml @@ -376,6 +376,7 @@ You Receive Give + Send Transaction History Transaction History Market Cap diff --git a/apps/flipcash/features/direct-send/.gitignore b/apps/flipcash/features/direct-send/.gitignore new file mode 100644 index 000000000..9f2a07880 --- /dev/null +++ b/apps/flipcash/features/direct-send/.gitignore @@ -0,0 +1,2 @@ +build/ +.gradle/ diff --git a/apps/flipcash/features/direct-send/build.gradle.kts b/apps/flipcash/features/direct-send/build.gradle.kts new file mode 100644 index 000000000..961c145ab --- /dev/null +++ b/apps/flipcash/features/direct-send/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(libs.plugins.flipcash.android.feature) +} + +android { + namespace = "${Gradle.flipcashNamespace}.features.directsend" +} + +dependencies { + testImplementation(kotlin("test")) + testImplementation(libs.bundles.unit.testing) + testImplementation(libs.mockito.kotlin) + testImplementation(libs.robolectric) + testImplementation(project(":libs:test-utils")) + + implementation(libs.kotlin.stdlib) + implementation(project(":apps:flipcash:shared:analytics")) + implementation(project(":apps:flipcash:shared:session")) + implementation(project(":apps:flipcash:shared:tokens")) + implementation(project(":libs:datetime")) + implementation(project(":libs:logging")) + implementation(project(":libs:messaging")) + implementation(project(":libs:permissions:bindings")) +} diff --git a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/AccessKeyScreen.kt b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/AccessKeyScreen.kt index 51d89cdfe..77a012f55 100644 --- a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/AccessKeyScreen.kt +++ b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/AccessKeyScreen.kt @@ -12,13 +12,11 @@ import com.flipcash.app.login.internal.AccessKeyScreen import com.flipcash.features.login.R import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.ui.components.AppBarWithTitle -import com.getcode.util.permissions.rememberNotificationPermission @Composable fun AccessKeyScreen() { val viewModel = hiltViewModel() val navigator = LocalCodeNavigator.current - val notificationPermissions = rememberNotificationPermission() Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -33,11 +31,21 @@ fun AccessKeyScreen() { if (requiresIap) { navigator.push(AppRoute.Onboarding.Purchase()) } else { - if (notificationPermissions.isPermanentlyDenied) { - navigator.push(AppRoute.Onboarding.NotificationPermissionRationale(true)) + val target = if (viewModel.isPhoneNumberSendEnabled) { + AppRoute.Onboarding.ContactPermission(postCreate = true) } else { - navigator.push(AppRoute.Onboarding.NotificationPermission(true)) + AppRoute.Onboarding.NotificationPermission(postCreate = true) } + + navigator.push( + AppRoute.Verification( + origin = AppRoute.Onboarding.AccessKey, + includePhone = true, + includeEmail = false, + target = target, + fullScreen = true, + ) + ) } } } diff --git a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/LoginAccessKeyViewModel.kt b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/LoginAccessKeyViewModel.kt index 7c545c92d..6edc6b0dc 100644 --- a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/LoginAccessKeyViewModel.kt +++ b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/accesskey/LoginAccessKeyViewModel.kt @@ -6,6 +6,8 @@ import com.flipcash.app.analytics.Button import com.flipcash.app.analytics.FlipcashAnalyticsService import com.flipcash.app.auth.AuthManager import com.flipcash.app.core.storage.MediaSaver +import com.flipcash.app.featureflags.FeatureFlag +import com.flipcash.app.featureflags.FeatureFlagController import com.flipcash.app.userflags.UserFlagsCoordinator import com.flipcash.services.user.UserManager import com.getcode.libs.qr.QRCodeGenerator @@ -25,10 +27,15 @@ class LoginAccessKeyViewModel @Inject constructor( mediaSaver: MediaSaver, userManager: UserManager, private val userFlags: UserFlagsCoordinator, + private val featureFlags: FeatureFlagController, private val authManager: AuthManager, private val analytics: FlipcashAnalyticsService, ): BaseAccessKeyViewModel(resources, mnemonicManager, mediaSaver, userManager, qrCodeGenerator) { + val isPhoneNumberSendEnabled: Boolean + get() = featureFlags.observe(FeatureFlag.PhoneNumberSend).value || + userFlags.resolvedFlags.value.enablePhoneNumberSend.effectiveValue + suspend fun onWroteDownInstead(): Result { trackButton(Button.WroteAccessKey) uiFlow.update { it.copy(skipState = LoadingSuccessState(loading = true)) } diff --git a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ScannerDecorItem.kt b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ScannerDecorItem.kt index 78a182089..6a776bf77 100644 --- a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ScannerDecorItem.kt +++ b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ScannerDecorItem.kt @@ -10,4 +10,5 @@ sealed class ScannerDecorItem(val screen: AppRoute) { data object Menu : ScannerDecorItem(AppRoute.Sheets.Menu) data object Logo: ScannerDecorItem(AppRoute.Sheets.ShareApp) data object Discover: ScannerDecorItem(AppRoute.Token.Discovery) + data object Send: ScannerDecorItem(AppRoute.Sheets.Send) } \ No newline at end of file diff --git a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/DecorView.kt b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/DecorView.kt index 25690a041..681cc38ac 100644 --- a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/DecorView.kt +++ b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/DecorView.kt @@ -58,9 +58,9 @@ internal fun DecorView( state: SessionState, billState: BillState, isPaused: Boolean, + modifier: Modifier = Modifier, isPinching: Boolean = false, zoomRatio: Float = 1f, - modifier: Modifier = Modifier, onAction: (ScannerDecorItem) -> Unit, ) { val billPlayground = LocalBillPlaygroundController.current diff --git a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/ScannerNavigationBar.kt b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/ScannerNavigationBar.kt index c2dc068bb..0fa687779 100644 --- a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/ScannerNavigationBar.kt +++ b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/ui/components/ScannerNavigationBar.kt @@ -31,9 +31,14 @@ internal fun ScannerNavigationBar( NavBarConfig.deserialize(navBarConfigString) } + val effectiveConfig = remember(config, state.isPhoneNumberSendEnabled) { + if (state.isPhoneNumberSendEnabled) config + else config.copy(order = config.order.filter { it != NavBarButton.Send }) + } + NavigationBar( modifier = modifier, - config = config, + config = effectiveConfig, state = NavigationBarState( notificationUnreadCount = state.notificationUnreadCount, showToast = billState.showToast && billState.toast != null, @@ -45,6 +50,7 @@ internal fun ScannerNavigationBar( NavBarButton.Give -> ScannerDecorItem.Give NavBarButton.Wallet -> ScannerDecorItem.Wallet NavBarButton.Discover -> ScannerDecorItem.Discover + NavBarButton.Send -> ScannerDecorItem.Send } onAction(item) }, diff --git a/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt b/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt index 5f68994c3..9ebf55f59 100644 --- a/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt +++ b/apps/flipcash/features/userflags/src/main/kotlin/com/flipcash/app/userflags/internal/UserFlagsViewModel.kt @@ -45,6 +45,10 @@ internal class UserFlagsViewModel @Inject constructor( R.string.label_flag_requiresPurchaseForAccount, flags.data.requiresIapForRegistration.effectiveValue ), + ReadOnlyEntry( + R.string.label_flag_enablePhoneNumberSend, + flags.data.enablePhoneNumberSend.effectiveValue + ), ) else -> emptyList() diff --git a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt index d93df3c72..370d4da8b 100644 --- a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt +++ b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt @@ -17,6 +17,7 @@ import com.flipcash.services.controllers.ResolverController import com.flipcash.services.models.CheckSyncError import com.flipcash.services.models.ContactMethod import com.flipcash.services.models.DeltaUploadError +import com.flipcash.services.models.GetContactsError import com.getcode.opencode.model.accounts.AccountCluster import com.getcode.opencode.providers.SessionListener import com.getcode.solana.keys.Checksum @@ -332,7 +333,13 @@ class ContactCoordinator @Inject constructor( _state.update { it.copy(flipcashE164s = flipcashE164s) } trace(tag = TAG, message = "Found ${flipcashE164s.size} contacts on Flipcash", type = TraceType.Process) }?.onFailure { error -> - trace(tag = TAG, message = "GetFlipcashContacts failed: ${error.message}", type = TraceType.Error) + if (error is GetContactsError.NotFound) { + dao.clearFlipcashStatus() + _state.update { it.copy(flipcashE164s = emptySet()) } + trace(tag = TAG, message = "No contacts on Flipcash yet", type = TraceType.Process) + } else { + trace(tag = TAG, message = "GetFlipcashContacts failed: ${error.message}", type = TraceType.Error) + } } } catch (e: Exception) { trace(tag = TAG, message = "GetFlipcashContacts exception: ${e.message}", error = e, type = TraceType.Error) diff --git a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt index e64b02724..64e3b444e 100644 --- a/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt +++ b/apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt @@ -179,6 +179,15 @@ sealed interface FeatureFlag { override val persistLogOut: Boolean = true } + @FeatureFlagMarker + data object PhoneNumberSend : FeatureFlag { + override val key: String = "phone_number_send_enabled" + override val default: Boolean = false + override val launched: Boolean = false + override val visible: Boolean = true + override val persistLogOut: Boolean = true + } + @FeatureFlagMarker data object NavBar : FeatureFlag { override val key: String = "nav_bar_config" @@ -219,6 +228,7 @@ val FeatureFlag<*>.title: String FeatureFlag.DepositUsdc -> "Deposit USDC" FeatureFlag.BackgroundReset -> "Background Reset" FeatureFlag.ContactPickerMode -> "Contact Picker Mode" + FeatureFlag.PhoneNumberSend -> "Phone Number Send" FeatureFlag.NavBar -> "Navigation Bar" } @@ -241,6 +251,7 @@ val FeatureFlag<*>.message: String FeatureFlag.DepositUsdc -> "When enabled, you'll gain the ability to deposit USDC directly from any external wallet app instead of purchasing a currency first and sell" FeatureFlag.BackgroundReset -> "Automatically returns the app to the camera screen after a period of inactivity with the app in the background" FeatureFlag.ContactPickerMode -> "When enabled, contacts will be accessed via the system contact picker instead of requesting full READ_CONTACTS permission" + FeatureFlag.PhoneNumberSend -> "When enabled, you'll gain the ability to send cash directly to contacts via phone number" FeatureFlag.NavBar -> "Customize the order and labels of navigation bar buttons" } diff --git a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/SessionController.kt b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/SessionController.kt index f073c1af7..c808fcffa 100644 --- a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/SessionController.kt +++ b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/SessionController.kt @@ -44,6 +44,7 @@ data class SessionState( val isRemoteSendLoading: Boolean = false, val notificationUnreadCount: Int = 0, val tokens: List = emptyList(), + val isPhoneNumberSendEnabled: Boolean = false, ) val LocalSessionController = staticCompositionLocalOf { null } \ No newline at end of file diff --git a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt index 228d127ae..6592f49ea 100644 --- a/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt +++ b/apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt @@ -63,6 +63,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -196,6 +197,13 @@ class RealSessionController @Inject constructor( _state.update { it.copy(tokens = tokens) } }.launchIn(scope) + combine( + featureFlagController.observe(FeatureFlag.PhoneNumberSend), + userManager.state.map { it.flags?.enablePhoneNumberSend == true } + ) { beta, server -> beta || server } + .onEach { enabled -> _state.update { it.copy(isPhoneNumberSendEnabled = enabled) } } + .launchIn(scope) + // Retry updateUserFlags when network is restored networkObserver.state .map { it.connected } diff --git a/apps/flipcash/shared/session/src/test/kotlin/com/flipcash/app/session/internal/SessionControllerGiftCardErrorTest.kt b/apps/flipcash/shared/session/src/test/kotlin/com/flipcash/app/session/internal/SessionControllerGiftCardErrorTest.kt index 86eb8055f..a2410ab74 100644 --- a/apps/flipcash/shared/session/src/test/kotlin/com/flipcash/app/session/internal/SessionControllerGiftCardErrorTest.kt +++ b/apps/flipcash/shared/session/src/test/kotlin/com/flipcash/app/session/internal/SessionControllerGiftCardErrorTest.kt @@ -108,10 +108,11 @@ class SessionControllerGiftCardErrorTest { toastController = mockk(relaxed = true), billingClient = mockk(relaxed = true), tokenCoordinator = tokenCoordinator, + contactCoordinator = mockk(relaxed = true), featureFlagController = mockk(relaxed = true), analytics = analytics, - appSettingsCoordinator = mockk(relaxed = true), usdcSweep = mockk(relaxed = true), + appSettingsCoordinator = mockk(relaxed = true), ) } diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt index 616ef6ab9..42903fc14 100644 --- a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt +++ b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt @@ -31,6 +31,7 @@ data class ResolvedUserFlags( val newCurrencyFeeAmount: ResolvedFlag, val withdrawalFeeAmount: ResolvedFlag, val usdcOnRampLiquidityPool: ResolvedFlag, + val enablePhoneNumberSend: ResolvedFlag, ) internal fun UserFlags.resolve(overrides: Overrides): ResolvedUserFlags = ResolvedUserFlags( @@ -44,5 +45,6 @@ internal fun UserFlags.resolve(overrides: Overrides): ResolvedUserFlags = Resolv newCurrencyPurchaseAmount = ResolvedFlag(newCurrencyPurchaseAmount, overrides.newCurrencyPurchaseAmount), newCurrencyFeeAmount = ResolvedFlag(newCurrencyFeeAmount, overrides.newCurrencyPurchaseAmount), withdrawalFeeAmount = ResolvedFlag(withdrawalFeeAmount, overrides.withdrawalFeeAmount), - usdcOnRampLiquidityPool = ResolvedFlag(preferredUsdcOnRampLiquidityPool, overrides.preferredUsdcOnRampLiquidityPool) + usdcOnRampLiquidityPool = ResolvedFlag(preferredUsdcOnRampLiquidityPool, overrides.preferredUsdcOnRampLiquidityPool), + enablePhoneNumberSend = ResolvedFlag(enablePhoneNumberSend, FieldOverride.None), ) \ No newline at end of file diff --git a/apps/flipcash/shared/userflags/src/main/res/values/strings.xml b/apps/flipcash/shared/userflags/src/main/res/values/strings.xml index f6137bcd3..2ac0fba1d 100644 --- a/apps/flipcash/shared/userflags/src/main/res/values/strings.xml +++ b/apps/flipcash/shared/userflags/src/main/res/values/strings.xml @@ -17,4 +17,5 @@ Withdrawal Fee Amount Enter amount Preferred USDC On-Ramp Liquidity Pool + Phone Number Send Enabled \ No newline at end of file diff --git a/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto b/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto index 7f8e6b247..3e8f225a2 100644 --- a/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto +++ b/definitions/flipcash/protos/src/main/proto/account/v1/flipcash_account_service.proto @@ -157,4 +157,7 @@ message UserFlags { // The preferred USDC liquidity pool for external wallet on ramp flows UsdcLiquidityPool preferred_on_ramp_usdc_liquidity_pool = 11; + + // Whether the send by phone number feature is enabled + bool enable_phone_number_send = 12; } diff --git a/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto b/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto index c78e4e273..9276a6c82 100644 --- a/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto +++ b/definitions/flipcash/protos/src/main/proto/contact/v1/contact_list_service.proto @@ -9,7 +9,6 @@ import "validate/validate.proto"; option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/contact/v1;contactpb"; option java_package = "com.codeinc.flipcash.gen.contact.v1"; -option objc_class_prefix = "FPBContactV1"; // ContactList manages a user's contact list and surfaces which contacts are // Flipcash users. diff --git a/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto b/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto index c6a62e8c0..c6f79bbd5 100644 --- a/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto +++ b/definitions/flipcash/protos/src/main/proto/contact/v1/model.proto @@ -7,7 +7,6 @@ import "validate/validate.proto"; option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/contact/v1;contactpb"; option java_package = "com.codeinc.flipcash.gen.contact.v1"; -option objc_class_prefix = "FPBContactV1"; message FlipcashContact { phone.v1.PhoneNumber phone = 1 [(validate.rules).message.required = true]; diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt index 3d8591e80..f4f123caf 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt @@ -25,7 +25,8 @@ internal class UserFlagsMapper @Inject constructor(): newCurrencyPurchaseAmount = Fiat(quarks = from.newCurrencyPurchaseAmount), newCurrencyFeeAmount = Fiat(quarks = from.newCurrencyFeeAmount), withdrawalFeeAmount = Fiat(quarks = from.withdrawalFeeAmount), - preferredUsdcOnRampLiquidityPool = from.preferredOnRampUsdcLiquidityPool.toDomain() + preferredUsdcOnRampLiquidityPool = from.preferredOnRampUsdcLiquidityPool.toDomain(), + enablePhoneNumberSend = from.enablePhoneNumberSend, ) } } diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt index 6d58daddc..6a8478780 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt @@ -17,6 +17,7 @@ data class UserFlags( val newCurrencyFeeAmount: Fiat, val withdrawalFeeAmount: Fiat, val preferredUsdcOnRampLiquidityPool: UsdcLiquidtyPool, + val enablePhoneNumberSend: Boolean, ) { companion object { val Default = UserFlags( @@ -31,6 +32,7 @@ data class UserFlags( newCurrencyFeeAmount = Fiat.MAX_VALUE, withdrawalFeeAmount = Fiat.Zero, preferredUsdcOnRampLiquidityPool = UsdcLiquidtyPool.Unknown, + enablePhoneNumberSend = false, ) } } \ No newline at end of file diff --git a/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt index 7b76bafe3..55ac8890d 100644 --- a/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt +++ b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt @@ -127,6 +127,17 @@ class UserFlagsMapperTest { assertEquals(5_000_000L, result.newCurrencyPurchaseAmount.quarks) } + @Test + fun `maps enable phone number send`() { + val proto = userFlags { + enablePhoneNumberSend = true + } + + val result = mapper.map(proto) + + assertTrue(result.enablePhoneNumberSend) + } + @Test fun `maps minimum version`() { val proto = userFlags { diff --git a/settings.gradle.kts b/settings.gradle.kts index 1c225aba7..6212ce594 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -90,6 +90,7 @@ include( ":apps:flipcash:features:deposit", ":apps:flipcash:features:advanced", ":apps:flipcash:features:currency-creator", + ":apps:flipcash:features:direct-send", ":apps:flipcash:features:device-logs", ":apps:flipcash:features:myaccount", ":apps:flipcash:features:backupkey",