From 3408dcb811ff6e3fd842d7a0f020f0bb557cd5dc Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sun, 26 Jul 2026 12:30:11 -0400 Subject: [PATCH] feat(tips): gate frosted tip card behind a beta flag Add a FrostedTipCard beta feature flag (off by default) that gates the scanner's frosted-glass tip card. When the flag is off, or the device cannot support the blur (pre-API-31 / low-RAM), the card renders the existing opaque fallback. Also fix the frosted branch reading nearly opaque: it was inheriting the 0.82 TipCardAlpha default, which masked the blurred camera backdrop. Drop the frosted fill to 0.45 alpha and reduce the backdrop blur from 24dp to 16dp so the frost reads as glass. --- .../internal/bills/ScannableContainer.kt | 20 +++++++++++++------ .../app/bills/components/cards/TipCard.kt | 2 +- .../flipcash/app/featureflags/FeatureFlag.kt | 12 +++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/bills/ScannableContainer.kt b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/bills/ScannableContainer.kt index 801b1fd23..901a168e0 100644 --- a/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/bills/ScannableContainer.kt +++ b/apps/flipcash/features/scanner/src/main/kotlin/com/flipcash/app/scanner/internal/bills/ScannableContainer.kt @@ -44,6 +44,8 @@ import com.flipcash.app.bills.components.cards.LocalTipCardBackdrop import com.flipcash.app.bills.components.cards.LocalTipCardBaseAlpha import com.flipcash.app.bills.components.cards.LocalTipCardColor import com.flipcash.app.bills.components.cards.TipCardOpaqueFallback +import com.flipcash.app.featureflags.FeatureFlag +import com.flipcash.app.featureflags.LocalFeatureFlags import com.flipcash.app.core.android.extensions.launchAppSettings import com.flipcash.app.core.bill.Scannable import com.flipcash.app.core.tipping.LocalTipCoordinator @@ -128,10 +130,15 @@ internal fun ScannableContainer( // The frosted camera backdrop snapshots the feed (a GPU→CPU readback). Gate it to devices that // can absorb that — API 31+ for the blur, and not low-RAM. Everywhere else the card falls back // to an opaque approximation of the frosted tone. - val supportsFrostedTipCard = remember { + val deviceSupportsFrostedTipCard = remember { Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && context.getSystemService(ActivityManager::class.java)?.isLowRamDevice != true } + // Also gated behind a beta flag (off by default); the opaque fallback is used when it's disabled. + val frostedTipCardFlagEnabled by LocalFeatureFlags.current + .observe(FeatureFlag.FrostedTipCard) + .collectAsStateWithLifecycle() + val frostedTipCardEnabled = deviceSupportsFrostedTipCard && frostedTipCardFlagEnabled OnLifecycleEvent { _, event -> if (event == Lifecycle.Event.ON_STOP && !autoStart) { @@ -292,18 +299,19 @@ internal fun ScannableContainer( val isTipCard = updatedBillState.bill is Scannable.TipCard val tipCardBackdrop = rememberCameraTipCardBackdrop( previewView = previewView, - enabled = isTipCard && supportsFrostedTipCard, + enabled = isTipCard && frostedTipCardEnabled, containerOriginInWindow = containerOriginInWindow, ) - // Where the frosted backdrop is unavailable, render the tip card as an opaque approximation - // of the frosted tone instead of a translucent panel over the (stutter-prone) live camera. - val useOpaqueFallback = isTipCard && !supportsFrostedTipCard + // Where the frosted backdrop is unavailable (incapable device or the beta flag is off), + // render the tip card as an opaque approximation of the frosted tone instead of a + // translucent panel over the (stutter-prone) live camera. + val useOpaqueFallback = isTipCard && !frostedTipCardEnabled CompositionLocalProvider( LocalTipCardBackdrop provides tipCardBackdrop, LocalTipCardColor provides if (useOpaqueFallback) TipCardOpaqueFallback else Color.Unspecified, - LocalTipCardBaseAlpha provides if (useOpaqueFallback) 1f else LocalTipCardBaseAlpha.current, + LocalTipCardBaseAlpha provides if (useOpaqueFallback) 1f else 0.45f, ) { AnimatedScannable( modifier = Modifier.fillMaxSize(), diff --git a/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/components/cards/TipCard.kt b/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/components/cards/TipCard.kt index 62bf75c36..2c2fdcfa8 100644 --- a/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/components/cards/TipCard.kt +++ b/apps/flipcash/shared/bills/src/main/kotlin/com/flipcash/app/bills/components/cards/TipCard.kt @@ -55,7 +55,7 @@ val LocalTipCardBackdrop = staticCompositionLocalOf<(@Composable BoxScope.() -> /** Blur radius used for the card's backdrop, tuned to read like the design's 40px background blur. */ -val TipCardBlurRadius: Dp = 24.dp +val TipCardBlurRadius: Dp = 16.dp /** * Base tint color for the card. Defaults to the theme's tip-card color; overridden on the opaque 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 7c0b7226a..2bca1bf91 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 @@ -267,6 +267,16 @@ sealed interface FeatureFlag { override val minTrack: FeatureTrack = FeatureTrack.Production } + @FeatureFlagMarker + data object FrostedTipCard: FeatureFlag { + override val key: String = "frosted_tip_card_enabled" + override val default: Boolean = false + override val launched: Boolean = false + override val visible: Boolean = true + override val persistLogOut: Boolean = false + override val minTrack: FeatureTrack = FeatureTrack.Beta + } + companion object { val entries: List> get() = FeatureFlagEntries.entries @@ -305,6 +315,7 @@ val FeatureFlag<*>.title: String FeatureFlag.AddMoneyUX -> "Add Money UX" FeatureFlag.ShowNetworkState -> "Network Offline Indicator" FeatureFlag.Tipping -> "Tipping" + FeatureFlag.FrostedTipCard -> "Frosted Tip Card" } val FeatureFlag<*>.message: String @@ -334,6 +345,7 @@ val FeatureFlag<*>.message: String FeatureFlag.AddMoneyUX -> "When enabled, the user experience for getting money into the app will be focused around 'Adding Money'" FeatureFlag.ShowNetworkState -> "When enabled, you'll gain the ability to see the network state on the Scanner when offline" FeatureFlag.Tipping -> "When enabled, you'll gain the ability to tip other users and set up your own tip card to receive tips" + FeatureFlag.FrostedTipCard -> "When enabled, the tip card in the scanner renders as frosted glass over a blurred snapshot of the camera instead of a solid card" }