Skip to content

Commit 44c6eb4

Browse files
committed
feat(featureflags): add typed FeatureFlag<T> and background reset behavior
Make FeatureFlag generic (`FeatureFlag<T: Any>`) so option-based flags carry their typed default directly (e.g. `BackgroundResetTimeout.FiveMinutes`) instead of a raw string. Boolean flags use `FeatureFlag<Boolean>` unchanged. Add `FlagOption.isDisabled` for generic disabled-option detection, replacing hardcoded "never" string checks in the controller. Add `defaultEnabled` and `defaultOption` as computed properties derived from the typed `default`. Add `BackgroundResetEffect` composable that records a timestamp on ON_STOP and dismisses all sheets via `popUntil` when the configured timeout elapses on ON_RESUME. Only triggers on true backgrounding, not ON_PAUSE.
1 parent 6acdbe7 commit 44c6eb4

11 files changed

Lines changed: 249 additions & 65 deletions

File tree

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/App.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.compose.runtime.CompositionLocalProvider
1515
import androidx.compose.runtime.LaunchedEffect
1616
import androidx.compose.runtime.collectAsState
1717
import androidx.compose.runtime.getValue
18+
import androidx.compose.runtime.mutableLongStateOf
1819
import androidx.compose.runtime.mutableStateOf
1920
import androidx.compose.runtime.remember
2021
import androidx.compose.runtime.setValue
@@ -33,6 +34,9 @@ import com.flipcash.app.analytics.rememberAnalytics
3334
import com.flipcash.app.android.BuildConfig
3435
import com.flipcash.app.bill.customization.BillPlaygroundScaffold
3536
import com.flipcash.app.core.LocalUserManager
37+
import com.flipcash.app.featureflags.BackgroundResetTimeout
38+
import com.flipcash.app.featureflags.FeatureFlag
39+
import com.flipcash.app.featureflags.LocalFeatureFlags
3640
import com.flipcash.app.core.AppRoute
3741
import com.flipcash.app.core.verification.email.LocalEmailCodeChannel
3842
import com.flipcash.app.core.navigation.DeeplinkAction
@@ -51,6 +55,8 @@ import com.flipcash.services.user.AuthState
5155
import com.getcode.libs.biometrics.BiometricsError
5256
import com.getcode.libs.qr.rememberQrBitmapPainter
5357
import com.getcode.navigation.AppNavHost
58+
import com.getcode.navigation.Sheet
59+
import com.getcode.navigation.core.CodeNavigator
5460
import com.getcode.navigation.core.LocalCodeNavigator
5561
import com.getcode.navigation.core.rememberCodeNavigator
5662
import com.getcode.navigation.extensions.getActivityScopedViewModel
@@ -323,6 +329,8 @@ internal fun App(
323329
else -> Unit
324330
}
325331
}
332+
333+
BackgroundResetEffect(navigator = codeNavigator)
326334
}
327335
}
328336
}
@@ -331,3 +339,34 @@ internal fun App(
331339
}
332340
}
333341

342+
@Composable
343+
private fun BackgroundResetEffect(navigator: CodeNavigator) {
344+
val featureFlags = LocalFeatureFlags.current
345+
val option by featureFlags.getOption(FeatureFlag.BackgroundReset)
346+
.collectAsStateWithLifecycle()
347+
348+
var backgroundedAt by remember { mutableLongStateOf(0L) }
349+
350+
OnLifecycleEvent { _, event ->
351+
when (event) {
352+
Lifecycle.Event.ON_STOP -> {
353+
backgroundedAt = System.currentTimeMillis()
354+
}
355+
Lifecycle.Event.ON_RESUME -> {
356+
val timeout = runCatching { BackgroundResetTimeout.valueOf(option) }
357+
.getOrNull()
358+
?.duration
359+
360+
if (timeout != null && backgroundedAt > 0L) {
361+
val elapsed = System.currentTimeMillis() - backgroundedAt
362+
if (elapsed >= timeout.inWholeMilliseconds) {
363+
navigator.popUntil { it !is Sheet }
364+
}
365+
}
366+
backgroundedAt = 0L
367+
}
368+
else -> Unit
369+
}
370+
}
371+
}
372+

apps/flipcash/features/bill-customization/src/main/kotlin/com/flipcash/app/bill/customization/components/BillPlayground.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ internal fun Modifier.presenceBorder(
217217
)
218218

219219
private object PreviewFeatureFlagController : FeatureFlagController by NoOpFeatureFlagController {
220-
override fun observe(flag: FeatureFlag): StateFlow<Boolean> = MutableStateFlow(true)
220+
override fun observe(flag: FeatureFlag<*>): StateFlow<Boolean> = MutableStateFlow(true)
221221
}
222222

223223
@Composable

apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenContent.kt

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
2525
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2626
import com.flipcash.app.core.AppRoute
2727
import com.flipcash.app.core.extensions.navigateTo
28+
import com.flipcash.app.featureflags.FlagOption
2829
import com.flipcash.app.featureflags.LocalFeatureFlags
2930
import com.flipcash.app.featureflags.message
3031
import com.flipcash.app.featureflags.title
@@ -35,6 +36,7 @@ import com.getcode.ui.components.ListItem
3536
import com.getcode.ui.components.SettingsSwitchRow
3637
import com.getcode.ui.components.text.SectionHeader
3738
import com.getcode.ui.core.verticalScrollStateGradient
39+
import com.getcode.ui.theme.CodeSegmentedControl
3840
import com.getcode.ui.utils.sheetResignmentBehavior
3941

4042
@Composable
@@ -59,12 +61,24 @@ internal fun LabsScreenContent(viewModel: LabsScreenViewModel) {
5961
SectionHeader(stringResource(R.string.title_settingsSectionFeatures))
6062
}
6163
items(betaFlags, key = { it.flag.key }) { feature ->
62-
SettingsSwitchRow(
63-
title = feature.flag.title,
64-
subtitle = feature.flag.message,
65-
checked = feature.enabled
66-
) {
67-
betaFlagsController.set(feature.flag, !feature.enabled)
64+
if (feature.flag.isOptionFlag) {
65+
SettingsOptionRow(
66+
title = feature.flag.title,
67+
subtitle = feature.flag.message,
68+
options = feature.flag.options,
69+
selectedOption = feature.selectedOption ?: feature.flag.defaultOption,
70+
onOptionSelected = { optionKey ->
71+
betaFlagsController.setOption(feature.flag, optionKey)
72+
},
73+
)
74+
} else {
75+
SettingsSwitchRow(
76+
title = feature.flag.title,
77+
subtitle = feature.flag.message,
78+
checked = feature.enabled
79+
) {
80+
betaFlagsController.set(feature.flag, !feature.enabled)
81+
}
6882
}
6983

7084
HorizontalDivider(
@@ -133,4 +147,46 @@ internal fun LabsScreenContent(viewModel: LabsScreenViewModel) {
133147
}
134148
}
135149
}
150+
}
151+
152+
@Composable
153+
private fun SettingsOptionRow(
154+
title: String,
155+
subtitle: String?,
156+
options: List<FlagOption>,
157+
selectedOption: String,
158+
onOptionSelected: (String) -> Unit,
159+
) {
160+
Column(
161+
modifier = Modifier
162+
.fillMaxWidth()
163+
.padding(horizontal = CodeTheme.dimens.grid.x3)
164+
.padding(vertical = CodeTheme.dimens.grid.x3),
165+
) {
166+
Text(
167+
text = title,
168+
color = CodeTheme.colors.textMain,
169+
style = CodeTheme.typography.textMedium,
170+
)
171+
if (!subtitle.isNullOrEmpty()) {
172+
Text(
173+
text = subtitle,
174+
style = CodeTheme.typography.textSmall,
175+
color = CodeTheme.colors.textSecondary,
176+
)
177+
}
178+
CodeSegmentedControl(
179+
options = options,
180+
selected = options.find { it.key == selectedOption },
181+
modifier = Modifier
182+
.fillMaxWidth()
183+
.padding(top = CodeTheme.dimens.grid.x2),
184+
mapper = { option ->
185+
Text(text = option.label)
186+
},
187+
onSelectionChanged = { option ->
188+
onOptionSelected(option.key)
189+
},
190+
)
191+
}
136192
}

apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuItems.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal data object SwitchAccount : StaffMenuItem<MenuScreenViewModel.Event>()
6363
override val name: String
6464
@Composable get() = stringResource(R.string.title_switchAccounts)
6565
override val action: MenuScreenViewModel.Event = MenuScreenViewModel.Event.OnSwitchAccountsClicked
66-
override val featureFlag: FeatureFlag = FeatureFlag.CredentialManager
66+
override val featureFlag: FeatureFlag<*> = FeatureFlag.CredentialManager
6767
}
6868

6969
internal data object Labs : StaffMenuItem<MenuScreenViewModel.Event>() {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.flipcash.app.featureflags
22

33
data class BetaFeature(
4-
val flag: FeatureFlag,
4+
val flag: FeatureFlag<*>,
55
val enabled: Boolean,
6+
val selectedOption: String? = null,
67
)

0 commit comments

Comments
 (0)