[mob] Remove UserRemoteFlagService & switch to flagService (#5219)

## Description

## Tests
This commit is contained in:
Neeraj
2025-03-03 16:15:13 +05:30
committed by GitHub
19 changed files with 146 additions and 255 deletions

View File

@@ -13,7 +13,6 @@ import "package:photos/services/magic_cache_service.dart";
import "package:photos/services/storage_bonus_service.dart";
import "package:photos/services/trash_sync_service.dart";
import "package:photos/services/update_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/utils/local_settings.dart";
import "package:shared_preferences/shared_preferences.dart";
@@ -101,15 +100,6 @@ LocationService get locationService {
return _locationService!;
}
UserRemoteFlagService? _userRemoteFlagService;
UserRemoteFlagService get userRemoteFlagService {
_userRemoteFlagService ??= UserRemoteFlagService(
ServiceLocator.instance.enteDio,
ServiceLocator.instance.prefs,
);
return _userRemoteFlagService!;
}
MagicCacheService? _magicCacheService;
MagicCacheService get magicCacheService {
_magicCacheService ??= MagicCacheService(

View File

@@ -21,7 +21,6 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import 'package:photos/services/machine_learning/ml_result.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/utils/ml_util.dart";
import "package:photos/utils/network_util.dart";
import "package:photos/utils/ram_check_util.dart";
@@ -58,8 +57,7 @@ class MLService {
/// Only call this function once at app startup, after that you can directly call [runAllML]
Future<void> init() async {
if (_isInitialized) return;
if (!userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled)) {
if (!flagService.hasGrantedMLConsent) {
return;
}
_logger.info("init called");
@@ -74,8 +72,7 @@ class MLService {
// Listen on MachineLearningController
Bus.instance.on<MachineLearningControlEvent>().listen((event) {
if (!userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled)) {
if (!flagService.hasGrantedMLConsent) {
return;
}

View File

@@ -21,7 +21,6 @@ import "package:photos/services/collections_service.dart";
import "package:photos/services/machine_learning/ml_computer.dart";
import "package:photos/services/machine_learning/ml_result.dart";
import "package:photos/services/machine_learning/semantic_search/clip/clip_image_encoder.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:shared_preferences/shared_preferences.dart";
class SemanticSearchService {
@@ -48,8 +47,7 @@ class SemanticSearchService {
_logger.info("Initialized already");
return;
}
final hasGivenConsent = userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled);
final hasGivenConsent = flagService.hasGrantedMLConsent;
if (!hasGivenConsent) return;
_logger.info("init called");
@@ -67,9 +65,7 @@ class SemanticSearchService {
}
bool isMagicSearchEnabledAndReady() {
return userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled) &&
_textModelIsLoaded;
return flagService.hasGrantedMLConsent && _textModelIsLoaded;
}
// searchScreenQuery should only be used for the user initiate query on the search screen.
@@ -78,7 +74,7 @@ class SemanticSearchService {
if (!isMagicSearchEnabledAndReady()) {
if (flagService.internalUser) {
_logger.info(
"ML global consent: ${userRemoteFlagService.getCachedBoolValue(UserRemoteFlagService.mlEnabled)}, loaded: $_textModelIsLoaded ",
"ML global consent: ${flagService.hasGrantedMLConsent}, loaded: $_textModelIsLoaded ",
);
}
return (query, <EnteFile>[]);

View File

@@ -23,7 +23,6 @@ import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/remote_assets_service.dart";
import "package:photos/services/search_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/ui/viewer/search/result/magic_result_screen.dart";
import "package:photos/utils/file_util.dart";
import "package:photos/utils/navigation_util.dart";
@@ -203,8 +202,7 @@ class MagicCacheService {
return _prefs.getInt(_lastMagicCacheUpdateTime) ?? 0;
}
bool get enableDiscover =>
userRemoteFlagService.getCachedBoolValue(UserRemoteFlagService.mlEnabled);
bool get enableDiscover => flagService.hasGrantedMLConsent;
void queueUpdate(String reason) {
_pendingUpdateReason.add(reason);

View File

@@ -45,7 +45,6 @@ import "package:photos/services/machine_learning/face_ml/face_filtering/face_fil
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/services/machine_learning/ml_computer.dart";
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/services/user_service.dart";
import "package:photos/states/location_screen_state.dart";
import "package:photos/ui/viewer/location/add_location_sheet.dart";
@@ -249,8 +248,7 @@ class SearchService {
Future<List<GenericSearchResult>> getMagicSectionResults(
BuildContext context,
) async {
if (userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled)) {
if (flagService.hasGrantedMLConsent) {
return magicCacheService.getMagicGenericSearchResult(context);
} else {
return <GenericSearchResult>[];

View File

@@ -1,141 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import "package:dio/dio.dart";
import "package:flutter/foundation.dart";
import 'package:logging/logging.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/notification_event.dart';
import "package:photos/service_locator.dart";
import 'package:photos/services/user_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserRemoteFlagService {
final Dio _enteDio;
late final _logger = Logger((UserRemoteFlagService).toString());
final SharedPreferences _prefs;
static const String recoveryVerificationFlag = "recoveryKeyVerified";
static const String mapEnabled = "mapEnabled";
static const String mlEnabled = "faceSearchEnabled";
static const String videoStreamingEnabled = "videoStreamingEnabled";
static const String needRecoveryKeyVerification =
"needRecoveryKeyVerification";
UserRemoteFlagService(this._enteDio, this._prefs) {
debugPrint("UserRemoteFlagService constructor");
}
bool shouldShowRecoveryVerification() {
if (!_prefs.containsKey(needRecoveryKeyVerification)) {
// fetch the status from remote
_refreshRecoveryVerificationFlag().ignore();
return false;
} else {
final bool shouldShow = _prefs.getBool(needRecoveryKeyVerification)!;
if (shouldShow) {
// refresh the status to check if user marked it as done on another device
_refreshRecoveryVerificationFlag().ignore();
}
return shouldShow;
}
}
bool getCachedBoolValue(String key) {
bool defaultValue = false;
if (key == mapEnabled) {
defaultValue = flagService.mapEnabled;
} else if (key == mlEnabled) {
defaultValue = flagService.hasGrantedMLConsent;
}
return _prefs.getBool(key) ?? defaultValue;
}
Future<bool> setBoolValue(String key, bool value) async {
await _updateKeyValue(key, value.toString());
return _prefs.setBool(key, value);
}
// markRecoveryVerificationAsDone is used to track if user has verified their
// recovery key in the past or not. This helps in avoid showing the same
// prompt to the user on re-install or signing into a different device
Future<void> markRecoveryVerificationAsDone() async {
await _updateKeyValue(recoveryVerificationFlag, true.toString());
await _prefs.setBool(needRecoveryKeyVerification, false);
}
Future<void> _refreshRecoveryVerificationFlag() async {
_logger.finest('refresh recovery key verification flag');
final remoteStatusValue =
await _getValue(recoveryVerificationFlag, "false");
final bool isNeedVerificationFlagSet =
_prefs.containsKey(needRecoveryKeyVerification);
if (remoteStatusValue.toLowerCase() == "true") {
await _prefs.setBool(needRecoveryKeyVerification, false);
// If the user verified on different device, then we should refresh
// the UI to dismiss the Notification.
if (isNeedVerificationFlagSet) {
Bus.instance.fire(NotificationEvent());
}
} else if (!isNeedVerificationFlagSet) {
// Verification is not done yet as remoteStatus is false and local flag to
// show notification isn't set. Set the flag to true if any active
// session is older than 1 day.
final activeSessions = await UserService.instance.getActiveSessions();
final int microSecondsInADay = const Duration(days: 1).inMicroseconds;
final bool anyActiveSessionOlderThanADay =
activeSessions.sessions.firstWhereOrNull(
(e) =>
(e.creationTime + microSecondsInADay) <
DateTime.now().microsecondsSinceEpoch,
) !=
null;
if (anyActiveSessionOlderThanADay) {
await _prefs.setBool(needRecoveryKeyVerification, true);
Bus.instance.fire(NotificationEvent());
} else {
// continue defaulting to no verification prompt
_logger.finest('No active session older than 1 day');
}
}
}
Future<String> _getValue(String key, String? defaultValue) async {
try {
final Map<String, dynamic> queryParams = {"key": key};
if (defaultValue != null) {
queryParams["defaultValue"] = defaultValue;
}
final response =
await _enteDio.get("/remote-store", queryParameters: queryParams);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected status code ${response.statusCode}");
}
return response.data["value"];
} catch (e) {
_logger.info("Error while fetching bool status for $key", e);
rethrow;
}
}
// _setBooleanFlag sets the corresponding flag on remote
// to mark recovery as completed
Future<void> _updateKeyValue(String key, String value) async {
try {
final response = await _enteDio.post(
"/remote-store/update",
data: {
"key": key,
"value": value,
},
);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected state");
}
} catch (e) {
_logger.warning("Failed to set flag for $key", e);
rethrow;
}
}
}

View File

@@ -40,7 +40,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
try {
await userRemoteFlagService.markRecoveryVerificationAsDone();
await flagService.setRecoveryKeyVerified(true);
} catch (e) {
await dialog.hide();
if (e is DioException && e.type == DioExceptionType.connectionError) {

View File

@@ -13,7 +13,6 @@ import "package:photos/models/preview/preview_item_status.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/preview_video_store.dart";
import 'package:photos/services/sync_service.dart';
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/theme/ente_theme.dart";
import 'package:photos/theme/text_style.dart';
import 'package:photos/ui/account/verify_recovery_page.dart';
@@ -43,8 +42,7 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
bool _showStatus = false;
bool _showErrorBanner = false;
bool _showMlBanner = !userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled) &&
bool _showMlBanner = !flagService.hasGrantedMLConsent &&
!localSettings.hasSeenMLEnablingBanner;
Error? _syncError;
@@ -81,8 +79,7 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
_notificationSubscription =
Bus.instance.on<NotificationEvent>().listen((event) {
if (mounted) {
_showMlBanner = !userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled) &&
_showMlBanner = !flagService.hasGrantedMLConsent &&
!localSettings.hasSeenMLEnablingBanner;
setState(() {});
}
@@ -115,7 +112,6 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
_subscription.cancel();
_notificationSubscription.cancel();
_previewSubscription.cancel();
super.dispose();
}
@@ -179,9 +175,7 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
),
)
: const SizedBox.shrink(),
userRemoteFlagService.shouldShowRecoveryVerification() &&
!_showErrorBanner &&
!_showMlBanner
_showVerificationBanner()
? Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12),
@@ -203,6 +197,17 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
],
);
}
// _showVerificationBanner after 3 days of installation
bool _showVerificationBanner() {
if (_showErrorBanner ||
_showErrorBanner ||
flagService.recoveryKeyVerified) {
return false;
}
final DateTime installTime = localSettings.getInstallDateTime();
return DateTime.now().difference(installTime).inDays >= 3;
}
}
class SyncStatusWidget extends StatefulWidget {

View File

@@ -2,15 +2,13 @@ import "package:flutter/cupertino.dart";
import "package:photos/generated/l10n.dart";
import 'package:photos/models/button_result.dart';
import "package:photos/service_locator.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/ui/components/buttons/button_widget.dart";
import "package:photos/ui/components/dialog_widget.dart";
import "package:photos/ui/components/models/button_type.dart";
import "package:photos/utils/toast_util.dart";
Future<bool> requestForMapEnable(BuildContext context) async {
const String flagName = UserRemoteFlagService.mapEnabled;
if (userRemoteFlagService.getCachedBoolValue(flagName)) {
if (flagService.mapEnabled) {
return true;
}
@@ -26,10 +24,7 @@ Future<bool> requestForMapEnable(BuildContext context) async {
labelText: S.of(context).enableMaps,
isInAlert: true,
onTap: () async {
await userRemoteFlagService.setBoolValue(
flagName,
true,
);
await flagService.setMapEnabled(true);
},
),
ButtonWidget(
@@ -52,5 +47,5 @@ Future<bool> requestForMapEnable(BuildContext context) async {
//For debugging.
void disableMap() {
userRemoteFlagService.setBoolValue(UserRemoteFlagService.mapEnabled, false);
flagService.setMapEnabled(false);
}

View File

@@ -3,7 +3,6 @@ import "package:photos/core/error-reporting/super_logging.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/preview_video_store.dart";
import "package:photos/services/user_remote_flag_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
@@ -101,19 +100,10 @@ class AdvancedSettingsScreen extends StatelessWidget {
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
trailingWidget: ToggleSwitchWidget(
value: () => userRemoteFlagService.getCachedBoolValue(
UserRemoteFlagService.mapEnabled,
),
value: () => flagService.mapEnabled,
onChanged: () async {
final isEnabled =
userRemoteFlagService.getCachedBoolValue(
UserRemoteFlagService.mapEnabled,
);
await userRemoteFlagService.setBoolValue(
UserRemoteFlagService.mapEnabled,
!isEnabled,
);
final isEnabled = flagService.mapEnabled;
await flagService.setMapEnabled(!isEnabled);
},
),
),

View File

@@ -11,7 +11,6 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import 'package:photos/services/machine_learning/ml_service.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
@@ -81,17 +80,12 @@ class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
},
),
trailingWidget: ToggleSwitchWidget(
value: () => userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled),
value: () => flagService.hasGrantedMLConsent,
onChanged: () async {
try {
final oldMlConsent = userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled);
final oldMlConsent = flagService.hasGrantedMLConsent;
final mlConsent = !oldMlConsent;
await userRemoteFlagService.setBoolValue(
UserRemoteFlagService.mlEnabled,
mlConsent,
);
await flagService.setMLConsent(mlConsent);
logger.info('ML consent turned ${mlConsent ? 'on' : 'off'}');
if (!mlConsent) {
MLService.instance.pauseIndexingAndClustering();

View File

@@ -3,7 +3,6 @@ import "package:photos/core/event_bus.dart";
import "package:photos/events/notification_event.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/common/web_page.dart";
import "package:photos/ui/components/buttons/button_widget.dart";
@@ -153,10 +152,7 @@ class _EnableMachineLearningConsentState
Future<void> enableMlConsent(BuildContext context) async {
try {
await userRemoteFlagService.setBoolValue(
UserRemoteFlagService.mlEnabled,
true,
);
await flagService.setMLConsent(true);
Bus.instance.fire(NotificationEvent());
Navigator.of(context).pop(true);
} catch (e) {

View File

@@ -12,7 +12,6 @@ import "package:photos/services/machine_learning/semantic_search/clip/clip_image
import "package:photos/services/machine_learning/semantic_search/clip/clip_text_encoder.dart";
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
import "package:photos/services/remote_assets_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/common/loading_widget.dart";
import "package:photos/ui/common/web_page.dart";
@@ -77,8 +76,7 @@ class _MachineLearningSettingsPageState
@override
Widget build(BuildContext context) {
final hasEnabled = userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled);
final hasEnabled = flagService.hasGrantedMLConsent;
return Scaffold(
body: CustomScrollView(
primary: false,
@@ -211,8 +209,7 @@ class _MachineLearningSettingsPageState
}
Future<void> toggleMlConsent() async {
final oldMlConsent = userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled);
final oldMlConsent = flagService.hasGrantedMLConsent;
// Go to consent page first if not enabled
if (!oldMlConsent) {
final result = await Navigator.push(
@@ -228,10 +225,7 @@ class _MachineLearningSettingsPageState
}
}
final mlConsent = !oldMlConsent;
await userRemoteFlagService.setBoolValue(
UserRemoteFlagService.mlEnabled,
mlConsent,
);
await flagService.setMLConsent(mlConsent);
if (!mlConsent) {
MLService.instance.pauseIndexingAndClustering();
unawaited(
@@ -248,8 +242,7 @@ class _MachineLearningSettingsPageState
}
Widget _getMlSettings(BuildContext context) {
final hasEnabled = userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled);
final hasEnabled = flagService.hasGrantedMLConsent;
if (!hasEnabled) {
return const SizedBox.shrink();
}

View File

@@ -19,7 +19,6 @@ import "package:photos/models/metadata/file_magic.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/file_magic_service.dart";
import "package:photos/services/filedata/filedata_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import "package:photos/ui/components/divider_widget.dart";
@@ -298,8 +297,7 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
]);
}
if (userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mlEnabled)) {
if (flagService.hasGrantedMLConsent) {
fileDetailsTiles.addAll([
FacesItemWidget(file),
const FileDetailsDivider(),

View File

@@ -11,7 +11,6 @@ import "package:photos/generated/l10n.dart";
import "package:photos/models/file/file.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/search_service.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/states/location_screen_state.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/buttons/chip_button_widget.dart";
@@ -183,8 +182,7 @@ class _InfoMapState extends State<InfoMap> {
@override
void initState() {
super.initState();
_hasEnabledMap = userRemoteFlagService
.getCachedBoolValue(UserRemoteFlagService.mapEnabled);
_hasEnabledMap = flagService.mapEnabled;
_fileLat = widget.file.location!.latitude!;
_fileLng = widget.file.location!.longitude!;

View File

@@ -10,7 +10,6 @@ import "package:photos/models/search/index_of_indexed_stack.dart";
import "package:photos/models/search/search_result.dart";
import "package:photos/models/search/search_types.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/user_remote_flag_service.dart";
import "package:photos/states/all_sections_examples_state.dart";
import "package:photos/ui/common/loading_widget.dart";
import "package:photos/ui/viewer/search/result/no_result_widget.dart";
@@ -119,9 +118,7 @@ class _AllSearchSectionsState extends State<AllSearchSections> {
itemBuilder: (context, index) {
switch (searchTypes[index]) {
case SectionType.face:
if (!userRemoteFlagService.getCachedBoolValue(
UserRemoteFlagService.mlEnabled,
)) {
if (!flagService.hasGrantedMLConsent) {
return const SizedBox.shrink();
}
return PeopleSection(

View File

@@ -55,6 +55,20 @@ class LocalSettings {
}
}
// getEstimatedInstallTimeInMs returns the time when the app was installed
// The time is stored in shared preferences and will be reset on logout
DateTime getInstallDateTime() {
if (_prefs.containsKey('ls.install_time')) {
return DateTime.fromMillisecondsSinceEpoch(
_prefs.getInt('ls.install_time')!,
);
} else {
final installTime = DateTime.now();
_prefs.setInt('ls.install_time', installTime.millisecondsSinceEpoch);
return installTime;
}
}
Future<void> setRateUsShownCount(int value) async {
await _prefs.setInt(kRateUsShownCount, value);
}

View File

@@ -26,12 +26,36 @@ class RemoteFlags {
required this.castUrl,
});
RemoteFlags copyWith({
bool? enableStripe,
bool? disableCFWorker,
bool? mapEnabled,
bool? faceSearchEnabled,
bool? recoveryKeyVerified,
bool? internalUser,
bool? betaUser,
bool? enableMobMultiPart,
String? castUrl,
}) {
return RemoteFlags(
enableStripe: enableStripe ?? this.enableStripe,
disableCFWorker: disableCFWorker ?? this.disableCFWorker,
mapEnabled: mapEnabled ?? this.mapEnabled,
faceSearchEnabled: faceSearchEnabled ?? this.faceSearchEnabled,
recoveryKeyVerified: recoveryKeyVerified ?? this.recoveryKeyVerified,
internalUser: internalUser ?? this.internalUser,
betaUser: betaUser ?? this.betaUser,
enableMobMultiPart: enableMobMultiPart ?? this.enableMobMultiPart,
castUrl: castUrl ?? this.castUrl,
);
}
static RemoteFlags defaultValue = RemoteFlags(
enableStripe: Platform.isAndroid,
disableCFWorker: false,
mapEnabled: false,
faceSearchEnabled: false,
recoveryKeyVerified: false,
recoveryKeyVerified: true,
internalUser: kDebugMode,
betaUser: kDebugMode,
enableMobMultiPart: false,

View File

@@ -1,5 +1,6 @@
// ignore_for_file: always_use_package_imports
import "dart:async";
import "dart:convert";
import "dart:developer";
import "dart:io";
@@ -13,10 +14,8 @@ import "model.dart";
class FlagService {
final SharedPreferences _prefs;
final Dio _enteDio;
late final bool _usingEnteEmail;
FlagService(this._prefs, this._enteDio) {
_usingEnteEmail = _prefs.getString("email")?.endsWith("@ente.io") ?? false;
Future.delayed(const Duration(seconds: 5), () {
_fetch();
});
@@ -39,25 +38,9 @@ class FlagService {
}
}
Future<void> _fetch() async {
try {
if (!_prefs.containsKey("token")) {
log("token not found, skip", name: "FlagService");
return;
}
log("fetching feature flags", name: "FlagService");
final response = await _enteDio.get("/remote-store/feature-flags");
final remoteFlags = RemoteFlags.fromMap(response.data);
await _prefs.setString("remote_flags", remoteFlags.toJson());
_flags = remoteFlags;
} catch (e) {
debugPrint("Failed to sync feature flags $e");
}
}
bool get disableCFWorker => flags.disableCFWorker;
bool get internalUser => flags.internalUser || _usingEnteEmail || kDebugMode;
bool get internalUser => flags.internalUser || kDebugMode;
bool get betaUser => flags.betaUser;
@@ -76,4 +59,70 @@ class FlagService {
bool get enableMobMultiPart => flags.enableMobMultiPart || internalUser;
String get castUrl => flags.castUrl;
Future<void> setMapEnabled(bool isEnabled) async {
await _updateKeyValue("mapEnabled", isEnabled.toString());
_updateFlags(flags.copyWith(mapEnabled: isEnabled));
}
Future<void> setMLConsent(bool isEnabled) async {
await _updateKeyValue("faceSearchEnabled", isEnabled.toString());
_updateFlags(flags.copyWith(faceSearchEnabled: isEnabled));
}
Future<void> setRecoveryKeyVerified(bool isVerified) async {
await _updateKeyValue("recoveryKeyVerified", isVerified.toString());
_updateFlags(flags.copyWith(recoveryKeyVerified: isVerified));
}
Completer<void>? _fetchCompleter;
Future<void> _fetch() async {
if (_fetchCompleter != null) {
await _fetchCompleter!.future;
return;
}
_fetchCompleter = Completer<void>();
try {
if (!_prefs.containsKey("token")) {
log("token not found, skip", name: "FlagService");
_fetchCompleter!.complete();
_fetchCompleter = null;
return;
}
log("fetching feature flags", name: "FlagService");
final response = await _enteDio.get("/remote-store/feature-flags");
final remoteFlags = RemoteFlags.fromMap(response.data);
await _prefs.setString("remote_flags", remoteFlags.toJson());
_flags = remoteFlags;
} catch (e) {
debugPrint("Failed to sync feature flags $e");
} finally {
_fetchCompleter!.complete();
_fetchCompleter = null;
}
}
Future<void> _updateKeyValue(String key, String value) async {
try {
final response = await _enteDio.post(
"/remote-store/update",
data: {
"key": key,
"value": value,
},
);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected state");
}
} catch (e) {
debugPrint("Failed to set flag for $key $e");
rethrow;
}
}
void _updateFlags(RemoteFlags flags) {
_flags = flags;
_prefs.setString("remote_flags", flags.toJson());
_fetch().ignore();
}
}