144 lines
5 KiB
Dart
144 lines
5 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:collection/collection.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:photos/core/event_bus.dart';
|
|
import 'package:photos/core/network/network.dart';
|
|
import 'package:photos/events/notification_event.dart';
|
|
import 'package:photos/services/user_service.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class UserRemoteFlagService {
|
|
final _enteDio = NetworkClient.instance.enteDio;
|
|
final _logger = Logger((UserRemoteFlagService).toString());
|
|
late SharedPreferences _prefs;
|
|
|
|
UserRemoteFlagService._privateConstructor();
|
|
|
|
static final UserRemoteFlagService instance =
|
|
UserRemoteFlagService._privateConstructor();
|
|
|
|
static const String recoveryVerificationFlag = "recoveryKeyVerified";
|
|
static const String mapEnabled = "mapEnabled";
|
|
static const String needRecoveryKeyVerification =
|
|
"needRecoveryKeyVerification";
|
|
|
|
Future<void> init() async {
|
|
_prefs = await SharedPreferences.getInstance();
|
|
}
|
|
|
|
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) {
|
|
return _prefs.getBool(key) ?? false;
|
|
}
|
|
|
|
Future<bool> getBoolValue(String key) async {
|
|
if (_prefs.containsKey(key)) {
|
|
return _prefs.getBool(key)!;
|
|
}
|
|
return _getValue(key, "false")
|
|
.then((value) => value.toLowerCase() == "true");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|