Skip to content

Commit 5c24246

Browse files
committed
feat: add support for option list
1 parent b99c652 commit 5c24246

File tree

6 files changed

+72
-15
lines changed

6 files changed

+72
-15
lines changed

assets/feature_flags.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
22
"enable_dummy_feature": false,
33
"dummy_string_value": "Hello World",
4-
"dummy_int_value": 42
4+
"dummy_int_value": 42,
5+
"dummy_theme": {
6+
"value": "system",
7+
"options": ["light", "dark", "system"]
8+
}
59
}

lib/repositories/feature_flag_repository.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ class FeatureFlag {
88
final String key;
99
final dynamic defaultValue;
1010
final dynamic overrideValue;
11+
final List<dynamic>? options;
1112

1213
const FeatureFlag({
1314
required this.key,
1415
required this.defaultValue,
1516
this.overrideValue,
17+
this.options,
1618
});
1719

1820
dynamic get value => overrideValue ?? defaultValue;
@@ -56,7 +58,14 @@ class FeatureFlagRepository {
5658

5759
for (final entry in defaults.entries) {
5860
final key = entry.key;
59-
final defVal = entry.value;
61+
dynamic defVal = entry.value;
62+
List<dynamic>? options;
63+
64+
if (defVal is Map<String, dynamic> && defVal.containsKey('value') && defVal.containsKey('options')) {
65+
options = defVal['options'] as List;
66+
defVal = defVal['value'];
67+
}
68+
6069
dynamic overrideVal;
6170
switch (defVal) {
6271
case bool _: overrideVal = await _prefs.getBool('ff_$key');
@@ -70,6 +79,7 @@ class FeatureFlagRepository {
7079
key: key,
7180
defaultValue: defVal,
7281
overrideValue: overrideVal,
82+
options: options,
7383
),
7484
);
7585
}

lib/screens/main/profile/feature_flag_screen.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ class FeatureFlagScreen extends ConsumerWidget {
8080
}
8181

8282
void _editValue(BuildContext context, WidgetRef ref, FeatureFlag flag) {
83+
if (flag.options != null) {
84+
_showDropdownDialog(context, ref, flag, flag.options!.map((e) => e.toString()).toList());
85+
return;
86+
}
87+
8388
final controller = TextEditingController(text: flag.value.toString());
8489
showDialog(
8590
context: context,
@@ -117,4 +122,38 @@ class FeatureFlagScreen extends ConsumerWidget {
117122
},
118123
);
119124
}
125+
126+
void _showDropdownDialog(BuildContext context, WidgetRef ref, FeatureFlag flag, List<String> options) {
127+
showDialog(
128+
context: context,
129+
builder: (context) {
130+
return AlertDialog(
131+
title: Text(flag.key),
132+
content: Column(
133+
mainAxisSize: MainAxisSize.min,
134+
children: options.map((option) {
135+
return RadioListTile<String>(
136+
title: Text(option),
137+
value: option,
138+
groupValue: flag.value as String,
139+
onChanged: (newValue) {
140+
if (newValue != null) {
141+
ref.read(featureFlagsProvider.notifier).setFlag(flag.key, newValue);
142+
}
143+
Navigator.of(context).pop();
144+
},
145+
);
146+
}).toList(),
147+
),
148+
actions: [
149+
TextButton(
150+
onPressed: () => Navigator.of(context).pop(),
151+
child: const Text('Cancel'),
152+
),
153+
],
154+
);
155+
},
156+
);
157+
}
120158
}
159+

lib/screens/main/profile/profile_providers.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,14 @@ final dummyIntValueProvider = FutureProvider.autoDispose<int>((ref) async {
3939
return val as int? ?? 42;
4040
});
4141

42+
enum ThemeOption { light, dark, system }
43+
44+
final dummyThemeProvider = FutureProvider.autoDispose<ThemeOption>((ref) async {
45+
final val = await ref.watch(featureFlagValueProvider('dummy_theme').future);
46+
return switch (val) {
47+
'light' => ThemeOption.light,
48+
'dark' => ThemeOption.dark,
49+
_ => ThemeOption.system,
50+
};
51+
});
52+

lib/screens/main/profile/profile_screen.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class ProfileScreen extends ConsumerWidget {
114114
final isDummyFeatureEnabled = ref.watch(isDummyFeatureEnabledProvider).value == true;
115115
final dummyString = ref.watch(dummyStringValueProvider).value ?? 'Hello World';
116116
final dummyInt = ref.watch(dummyIntValueProvider).value ?? 42;
117+
final dummyTheme = ref.watch(dummyThemeProvider).value ?? ThemeOption.system;
117118

118119
// settings options for the profile tab
119120
final options = [
@@ -162,7 +163,7 @@ class ProfileScreen extends ConsumerWidget {
162163
OptionEntryTile.icon(
163164
icon: Icons.science_outlined,
164165
title: t.profile.options.dummyFeature,
165-
description: 'String: $dummyString | Int: $dummyInt',
166+
description: 'String: $dummyString | Int: $dummyInt | Theme: ${dummyTheme.name}',
166167
onTap: () => _showDemoTap(context),
167168
),
168169
OptionEntryTile.icon(

lib/services/feature_flag/feature_flag_service.dart

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,12 @@ import 'package:riverpod/riverpod.dart';
44

55
/// Provides the [FeatureFlagService] instance.
66
final featureFlagServiceProvider = Provider<FeatureFlagService>((ref) {
7-
return LocalFeatureFlagService();
7+
return FeatureFlagService();
88
});
99

10-
/// Abstract interface for fetching feature flags.
11-
abstract interface class FeatureFlagService {
12-
/// Fetches the default feature flags.
13-
/// This returns a map constructed from JSON config,
14-
/// suitable for extension with Firebase Remote Config later.
15-
Future<Map<String, dynamic>> fetchDefaultFlags();
16-
}
17-
18-
/// Implementation reading flags from the local `assets/feature_flags.json`.
19-
class LocalFeatureFlagService implements FeatureFlagService {
20-
@override
10+
/// Service for fetching default feature flags from the local JSON config.
11+
/// Designed for future extension with Firebase Remote Config.
12+
class FeatureFlagService {
2113
Future<Map<String, dynamic>> fetchDefaultFlags() async {
2214
final jsonString = await rootBundle.loadString('assets/feature_flags.json');
2315
return jsonDecode(jsonString) as Map<String, dynamic>;

0 commit comments

Comments
 (0)