diff --git a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/OnboardingFlowScreen.kt b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/OnboardingFlowScreen.kt index 96d2515c40..489a50c9e3 100644 --- a/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/OnboardingFlowScreen.kt +++ b/apps/flipcash/features/login/src/main/kotlin/com/flipcash/app/login/OnboardingFlowScreen.kt @@ -33,8 +33,6 @@ import com.flipcash.app.core.LocalUserManager import com.flipcash.app.core.extensions.openAsSheet import com.flipcash.app.core.navigation.homeRoute import com.flipcash.app.core.navigation.launchRoute -import com.flipcash.app.featureflags.FeatureFlag -import com.flipcash.app.featureflags.LocalFeatureFlags import com.flipcash.app.core.onboarding.OnboardingResult import com.flipcash.app.core.onboarding.OnboardingStep import com.flipcash.app.login.internal.LoginAccessKeyViewModel @@ -109,10 +107,7 @@ import kotlin.time.Duration.Companion.milliseconds * Same as (1) but initialStack resumes at the AccessKey, Purchase, or Name step. * ``` * - * ¹ Contact permission is shown only when [FeatureFlag.ContactPickerMode] is off. When - * ContactPickerMode is on, contacts are accessed via the system picker at call site - * (no READ_CONTACTS needed). Already-granted permissions are auto-skipped via - * [PermissionsPhaseFlowHost]. + * ¹ Already-granted permissions are auto-skipped via [PermissionsPhaseFlowHost]. * ² Display-name entry is shown only when no display name is set. It reuses the * UpdateUserProfile subflow, whose `target` replaces the stack with the permissions phase. * ³ A new account (1, and 4 when it resumes account creation) lands on the Wallet tab, so a new @@ -159,21 +154,18 @@ private fun PermissionsPhaseFlowHost( val notificationConfig = PermissionConfigs.notifications() val analytics = rememberAnalytics() - val featureFlags = LocalFeatureFlags.current val userManager = LocalUserManager.current - val contactPickerMode by featureFlags.observe(FeatureFlag.ContactPickerMode).collectAsStateWithLifecycle() val home = onboardingLandingRoute(route) val permissionsSteps = buildList { - if (!route.skipContacts && !contactPickerMode) add(OnboardingStep.ContactPermission) + if (!route.skipContacts) add(OnboardingStep.ContactPermission) add(OnboardingStep.NotificationPermission) } - // Compute resumeAt once per steps-list identity. This recomputes when the flag loads - // (steps changes) but NOT when permissions are granted mid-flow, preventing a stale - // recomposition from triggering a spurious BackedOutOfRoot exit. - val resumeAt = remember(permissionsSteps.map { it::class }, contactPickerMode) { + // Compute resumeAt once per steps-list identity, NOT when permissions are granted mid-flow, + // preventing a stale recomposition from triggering a spurious BackedOutOfRoot exit. + val resumeAt = remember(permissionsSteps.map { it::class }) { val contactsGranted = checker.isGranted(contactConfig.permission) val notificationsGranted = !notificationConfig.requiresRuntimeRequest || checker.isGranted(notificationConfig.permission) diff --git a/apps/flipcash/shared/contacts/build.gradle.kts b/apps/flipcash/shared/contacts/build.gradle.kts index 551b6fcbb7..0414284580 100644 --- a/apps/flipcash/shared/contacts/build.gradle.kts +++ b/apps/flipcash/shared/contacts/build.gradle.kts @@ -17,7 +17,6 @@ dependencies { implementation(project(":apps:flipcash:shared:persistence:db")) implementation(project(":apps:flipcash:shared:persistence:sources")) implementation(project(":apps:flipcash:shared:phone")) - implementation(project(":apps:flipcash:shared:featureflags")) implementation(project(":libs:encryption:keys")) implementation(project(":libs:network:connectivity:public")) implementation(libs.androidx.datastore) diff --git a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/device/ScopeAwareContactReader.kt b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/device/ScopeAwareContactReader.kt index 2071464c27..32d26d9f5f 100644 --- a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/device/ScopeAwareContactReader.kt +++ b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/device/ScopeAwareContactReader.kt @@ -3,8 +3,6 @@ package com.flipcash.app.contacts.device import com.flipcash.app.contacts.device.internal.FullAccessContactReader import com.flipcash.app.contacts.device.internal.PickerContactReader import com.flipcash.app.core.contacts.DeviceContact -import com.flipcash.app.featureflags.FeatureFlag -import com.flipcash.app.featureflags.FeatureFlagController import javax.inject.Inject import javax.inject.Singleton @@ -12,14 +10,12 @@ import javax.inject.Singleton class ScopeAwareContactReader @Inject constructor( private val fullAccess: FullAccessContactReader, private val picker: PickerContactReader, - private val featureFlags: FeatureFlagController, ) : DeviceContactReader { override suspend fun readAll(): Result> { - val reader = activeReader() - val result = reader.readAll() + val result = fullAccess.readAll() // If full-access failed (no permission) but the picker has contacts, use those. - if (result.isFailure && reader === fullAccess) { + if (result.isFailure) { val pickerResult = picker.readAll() if (pickerResult.isSuccess && pickerResult.getOrThrow().isNotEmpty()) { return pickerResult @@ -40,15 +36,6 @@ class ScopeAwareContactReader @Inject constructor( picker.clearPickedContacts() } - /** - * Returns true if READ_CONTACTS was previously used but is now denied. - * Always false in picker mode — picker never holds READ_CONTACTS. - */ - suspend fun isPermissionRevoked(): Boolean { - if (featureFlags.observe(FeatureFlag.ContactPickerMode).value) return false - return fullAccess.readAll().isFailure - } - - private fun activeReader(): DeviceContactReader = - if (featureFlags.observe(FeatureFlag.ContactPickerMode).value) picker else fullAccess + /** Returns true if READ_CONTACTS was previously used but is now denied. */ + suspend fun isPermissionRevoked(): Boolean = fullAccess.readAll().isFailure } 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 a6c13c9dac..00e330d8dc 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 @@ -1,6 +1,5 @@ package com.flipcash.app.featureflags -import android.os.Build import com.flipcash.app.featureflags.model.BackgroundResetTimeout import com.flipcash.app.ksp.annotations.FeatureFlagMarker @@ -80,15 +79,6 @@ sealed interface FeatureFlag { .map { FlagOption(it.name, it.label, isDisabled = it.duration == null) } } - @FeatureFlagMarker - data object ContactPickerMode : FeatureFlag { - override val key: String = "contact_picker_mode" - override val default: Boolean = false - override val launched: Boolean = false - override val visible: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN - override val persistLogOut: Boolean = true - } - @FeatureFlagMarker data object ShowNetworkState: FeatureFlag { override val key: String = "show_network_state_enabled" @@ -98,15 +88,6 @@ sealed interface FeatureFlag { override val persistLogOut: Boolean = false } - @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 - } - companion object { val entries: List> get() = FeatureFlagEntries.entries @@ -125,9 +106,7 @@ val FeatureFlag<*>.title: String FeatureFlag.CoinbaseOnRampSandbox -> "Coinbase Onramp Sandbox" FeatureFlag.BillTextures -> "Bill Textures" FeatureFlag.BackgroundReset -> "Background Reset" - FeatureFlag.ContactPickerMode -> "Contact Picker Mode" FeatureFlag.ShowNetworkState -> "Network Offline Indicator" - FeatureFlag.FrostedTipCard -> "Frosted Tip Card" } val FeatureFlag<*>.message: String @@ -137,7 +116,5 @@ val FeatureFlag<*>.message: String FeatureFlag.CoinbaseOnRampSandbox -> "When enabled, Coinbase onramp purchases will use the sandbox environment for testing" FeatureFlag.BillTextures -> "When enabled, you'll gain the ability to select textures for bills during currency creation" 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.ShowNetworkState -> "When enabled, you'll gain the ability to see the network state on the Scanner when offline" - 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" }