import 'dart:convert'; import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:collection/collection.dart'; typedef FeatureFlagMap = Map; /// The [FeatureFlag] is used to control the front-end features of the app. /// /// For example, if your feature is still under development, /// you can set the value to `false` to hide the feature. enum FeatureFlag { // used to control the visibility of the collaborative workspace feature // if it's on, you can see the workspace list and the workspace settings // in the top-left corner of the app collaborativeWorkspace, // used to control the visibility of the members settings // if it's on, you can see the members settings in the settings page membersSettings, // used to control the sync feature of the document // if it's on, the document will be synced the events from server in real-time syncDocument, // used to control the sync feature of the database // if it's on, the collaborators will show in the database syncDatabase, // used for the search feature search, // used for controlling whether to show plan+billing options in settings planBilling, // used for space design spaceDesign, // used for ignore the conflicted feature flag unknown; static Future initialize() async { final values = await getIt().getWithFormat( KVKeys.featureFlag, (value) => Map.from(jsonDecode(value)).map( (key, value) { final k = FeatureFlag.values.firstWhereOrNull( (e) => e.name == key, ) ?? FeatureFlag.unknown; return MapEntry(k, value as bool); }, ), ) ?? {}; _values = { ...{for (final flag in FeatureFlag.values) flag: false}, ...values, }; } static UnmodifiableMapView get data => UnmodifiableMapView(_values); Future turnOn() async { await update(true); } Future turnOff() async { await update(false); } Future update(bool value) async { _values[this] = value; await getIt().set( KVKeys.featureFlag, jsonEncode( _values.map((key, value) => MapEntry(key.name, value)), ), ); } static Future clear() async { _values = {}; await getIt().remove(KVKeys.featureFlag); } bool get isOn { if ([ FeatureFlag.planBilling, // release this feature in version 0.6.1 FeatureFlag.spaceDesign, // release this feature in version 0.5.9 FeatureFlag.search, // release this feature in version 0.5.6 FeatureFlag.collaborativeWorkspace, FeatureFlag.membersSettings, // release this feature in version 0.5.4 FeatureFlag.syncDatabase, FeatureFlag.syncDocument, ].contains(this)) { return true; } if (_values.containsKey(this)) { return _values[this]!; } switch (this) { case FeatureFlag.planBilling: case FeatureFlag.search: case FeatureFlag.syncDocument: case FeatureFlag.syncDatabase: case FeatureFlag.spaceDesign: return true; case FeatureFlag.collaborativeWorkspace: case FeatureFlag.membersSettings: case FeatureFlag.unknown: return false; } } String get description { switch (this) { case FeatureFlag.collaborativeWorkspace: return 'if it\'s on, you can see the workspace list and the workspace settings in the top-left corner of the app'; case FeatureFlag.membersSettings: return 'if it\'s on, you can see the members settings in the settings page'; case FeatureFlag.syncDocument: return 'if it\'s on, the document will be synced in real-time'; case FeatureFlag.syncDatabase: return 'if it\'s on, the collaborators will show in the database'; case FeatureFlag.search: return 'if it\'s on, the command palette and search button will be available'; case FeatureFlag.planBilling: return 'if it\'s on, plan and billing pages will be available in Settings'; case FeatureFlag.spaceDesign: return 'if it\'s on, the space design feature will be available'; case FeatureFlag.unknown: return ''; } } String get key => 'appflowy_feature_flag_${toString()}'; } FeatureFlagMap _values = {};