mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 02:01:36 +03:00
Refactor feature flag support.
This commit is contained in:
parent
75f8f5be35
commit
5d9420f47f
@ -165,6 +165,7 @@ class AboutPage extends ConsumerWidget {
|
||||
'os': Platform.operatingSystem,
|
||||
'os_version': Platform.operatingSystemVersion,
|
||||
});
|
||||
data.insert(data.length - 1, ref.read(featureFlagProvider));
|
||||
final text = const JsonEncoder.withIndent(' ').convert(data);
|
||||
await ref.read(clipboardProvider).setText(text);
|
||||
await ref.read(withContextProvider)(
|
||||
|
@ -53,17 +53,21 @@ abstract class ApplicationStateNotifier<T>
|
||||
}
|
||||
|
||||
// Feature flags
|
||||
abstract class BaseFeature {
|
||||
sealed class BaseFeature {
|
||||
String get path;
|
||||
String _subpath(String key);
|
||||
|
||||
Feature feature(String key, {bool enabled = true}) =>
|
||||
Feature(this, key, enabled: enabled);
|
||||
Feature._(this, key, enabled: enabled);
|
||||
}
|
||||
|
||||
class _RootFeature extends BaseFeature {
|
||||
_RootFeature._();
|
||||
@override
|
||||
String get path => '';
|
||||
|
||||
@override
|
||||
String _subpath(String key) => key;
|
||||
}
|
||||
|
||||
class Feature extends BaseFeature {
|
||||
@ -71,18 +75,41 @@ class Feature extends BaseFeature {
|
||||
final String key;
|
||||
final bool _defaultState;
|
||||
|
||||
Feature(this.parent, this.key, {bool enabled = true})
|
||||
Feature._(this.parent, this.key, {bool enabled = true})
|
||||
: _defaultState = enabled;
|
||||
|
||||
@override
|
||||
String get path => '${parent.path}.$key';
|
||||
String get path => parent._subpath(key);
|
||||
|
||||
@override
|
||||
String _subpath(String key) => '$path.$key';
|
||||
}
|
||||
|
||||
final BaseFeature root = _RootFeature._();
|
||||
|
||||
typedef FeatureProvider = bool Function(Feature feature);
|
||||
|
||||
final featureFlagProvider =
|
||||
StateNotifierProvider<FeatureFlagsNotifier, Map<String, bool>>(
|
||||
(_) => FeatureFlagsNotifier());
|
||||
|
||||
class FeatureFlagsNotifier extends StateNotifier<Map<String, bool>> {
|
||||
FeatureFlagsNotifier() : super({});
|
||||
|
||||
void loadConfig(Map<String, dynamic> config) {
|
||||
const falsey = [0, false, null];
|
||||
state = {for (final k in config.keys) k: !falsey.contains(config[k])};
|
||||
}
|
||||
}
|
||||
|
||||
final featureProvider = Provider<FeatureProvider>((ref) {
|
||||
// TODO: Read file, check parents
|
||||
return (feature) => feature._defaultState;
|
||||
final featureMap = ref.watch(featureFlagProvider);
|
||||
|
||||
bool isEnabled(BaseFeature feature) => switch (feature) {
|
||||
_RootFeature() => true,
|
||||
Feature() => isEnabled(feature.parent) &&
|
||||
(featureMap[feature.path] ?? feature._defaultState),
|
||||
};
|
||||
|
||||
return isEnabled;
|
||||
});
|
||||
|
@ -157,6 +157,11 @@ Future<Widget> initialize(List<String> argv) async {
|
||||
.toFilePath();
|
||||
}
|
||||
|
||||
// Locate feature flags file
|
||||
final featureFile = File(Uri.file(Platform.resolvedExecutable)
|
||||
.resolve('features.json')
|
||||
.toFilePath());
|
||||
|
||||
final rpcFuture = _initHelper(exe!);
|
||||
_initLicenses();
|
||||
|
||||
@ -218,15 +223,33 @@ Future<Widget> initialize(List<String> argv) async {
|
||||
ref.read(rpcProvider).valueOrNull?.setLogLevel(level);
|
||||
});
|
||||
|
||||
// Load feature flags, if they exist
|
||||
featureFile.exists().then(
|
||||
(exists) async {
|
||||
if (exists) {
|
||||
try {
|
||||
final featureConfig =
|
||||
jsonDecode(await featureFile.readAsString());
|
||||
ref
|
||||
.read(featureFlagProvider.notifier)
|
||||
.loadConfig(featureConfig);
|
||||
} catch (error) {
|
||||
_log.error('Failed to parse feature flags', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Initialize systray
|
||||
ref.watch(systrayProvider);
|
||||
|
||||
// Show a loading or error page while the Helper isn't ready
|
||||
return ref.watch(rpcProvider).when(
|
||||
data: (data) => const MainPage(),
|
||||
error: (error, stackTrace) => AppFailurePage(cause: error),
|
||||
loading: () => _HelperWaiter(),
|
||||
);
|
||||
return Consumer(
|
||||
builder: (context, ref, child) => ref.watch(rpcProvider).when(
|
||||
data: (data) => const MainPage(),
|
||||
error: (error, stackTrace) => AppFailurePage(cause: error),
|
||||
loading: () => _HelperWaiter(),
|
||||
));
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
@ -20,12 +20,12 @@ final actions = piv.feature('actions');
|
||||
|
||||
final actionsPin = actions.feature('pin');
|
||||
final actionsPuk = actions.feature('puk');
|
||||
final actionsManagementKey = actions.feature('managementKey', enabled: false);
|
||||
final actionsReset = actions.feature('reset', enabled: false);
|
||||
final actionsManagementKey = actions.feature('managementKey');
|
||||
final actionsReset = actions.feature('reset');
|
||||
|
||||
final slots = piv.feature('slots');
|
||||
|
||||
final slotsGenerate = slots.feature('generate', enabled: false);
|
||||
final slotsImport = slots.feature('import', enabled: false);
|
||||
final slotsGenerate = slots.feature('generate');
|
||||
final slotsImport = slots.feature('import');
|
||||
final slotsExport = slots.feature('export');
|
||||
final slotsDelete = slots.feature('delete', enabled: false);
|
||||
final slotsDelete = slots.feature('delete');
|
||||
|
Loading…
Reference in New Issue
Block a user