371 lines
13 KiB
Dart
371 lines
13 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:math';
|
|
|
|
import 'package:aves/app_flavor.dart';
|
|
import 'package:aves/model/device.dart';
|
|
import 'package:aves/model/filters/favourite.dart';
|
|
import 'package:aves/model/filters/mime.dart';
|
|
import 'package:aves/model/settings/defaults.dart';
|
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
|
import 'package:aves/model/settings/modules/app.dart';
|
|
import 'package:aves/model/settings/modules/collection.dart';
|
|
import 'package:aves/model/settings/modules/debug.dart';
|
|
import 'package:aves/model/settings/modules/display.dart';
|
|
import 'package:aves/model/settings/modules/info.dart';
|
|
import 'package:aves/model/settings/modules/navigation.dart';
|
|
import 'package:aves/model/settings/modules/privacy.dart';
|
|
import 'package:aves/model/settings/modules/screen_saver.dart';
|
|
import 'package:aves/model/settings/modules/search.dart';
|
|
import 'package:aves/model/settings/modules/slideshow.dart';
|
|
import 'package:aves/model/settings/modules/viewer.dart';
|
|
import 'package:aves/model/settings/modules/widget.dart';
|
|
import 'package:aves/ref/bursts.dart';
|
|
import 'package:aves/services/accessibility_service.dart';
|
|
import 'package:aves/services/common/services.dart';
|
|
import 'package:aves_map/aves_map.dart';
|
|
import 'package:aves_model/aves_model.dart';
|
|
import 'package:aves_utils/aves_utils.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:latlong2/latlong.dart';
|
|
|
|
final Settings settings = Settings._private();
|
|
|
|
class Settings
|
|
with
|
|
ChangeNotifier,
|
|
SettingsAccess,
|
|
SearchSettings,
|
|
AppSettings,
|
|
CollectionSettings,
|
|
DebugSettings,
|
|
DisplaySettings,
|
|
InfoSettings,
|
|
NavigationSettings,
|
|
PrivacySettings,
|
|
ScreenSaverSettings,
|
|
WidgetSettings {
|
|
final Set<StreamSubscription> _subscriptions = {};
|
|
final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change');
|
|
final StreamController<SettingsChangedEvent> _updateStreamController = StreamController.broadcast();
|
|
final StreamController<SettingsChangedEvent> _updateTileExtentStreamController = StreamController.broadcast();
|
|
|
|
@override
|
|
Stream<SettingsChangedEvent> get updateStream => _updateStreamController.stream;
|
|
|
|
Stream<SettingsChangedEvent> get updateTileExtentStream => _updateTileExtentStreamController.stream;
|
|
|
|
@override
|
|
bool get initialized => store.initialized;
|
|
|
|
@override
|
|
SettingsStore get store => settingsStore;
|
|
|
|
Settings._private() {
|
|
if (kFlutterMemoryAllocationsEnabled) ChangeNotifier.maybeDispatchObjectCreation(this);
|
|
}
|
|
|
|
Future<void> init({
|
|
required bool monitorPlatformSettings,
|
|
required bool shouldSanitize,
|
|
}) async {
|
|
await store.init();
|
|
resetAppliedLocale();
|
|
_unregister();
|
|
_register(monitorPlatformSettings);
|
|
initAppSettings();
|
|
if (shouldSanitize) {
|
|
await sanitize();
|
|
}
|
|
}
|
|
|
|
void _unregister() {
|
|
//albumGrouping.removeListener(saveAlbumGroups);
|
|
//tagGrouping.removeListener(saveTagGroups);
|
|
_subscriptions
|
|
..forEach((sub) => sub.cancel())
|
|
..clear();
|
|
}
|
|
|
|
void _register(bool monitorPlatformSettings) {
|
|
// 🔥 album grouping rimosso
|
|
// 🔥 dynamic albums rimossi
|
|
// 🔥 tag grouping: nessun salvataggio gruppi
|
|
|
|
// ascolta SOLO i cambiamenti del grouping TAG (versione minimal)
|
|
_subscriptions.add(
|
|
tagGrouping.eventBus.on<GroupUriChangedEvent>().listen(_onGroupingChange),
|
|
);
|
|
|
|
// mantieni il monitoraggio delle impostazioni di sistema
|
|
if (monitorPlatformSettings) {
|
|
_subscriptions.add(
|
|
_platformSettingsChangeChannel
|
|
.receiveBroadcastStream()
|
|
.listen((event) => _onPlatformSettingsChanged(event as Map?)),
|
|
);
|
|
}
|
|
}
|
|
|
|
void _onGroupingChange(GroupUriChangedEvent event) {
|
|
// nuovo modello: nessun aggiornamento necessario
|
|
}
|
|
|
|
Future<void> reload() => store.reload();
|
|
|
|
Future<void> reset({required bool includeInternalKeys}) async {
|
|
if (includeInternalKeys) {
|
|
await store.clear();
|
|
} else {
|
|
await Future.forEach<String>(store.getKeys().whereNot(SettingKeys.isInternalKey), store.remove);
|
|
}
|
|
}
|
|
|
|
Future<void> setContextualDefaults(AppFlavor flavor) async {
|
|
// performance
|
|
final performanceClass = await deviceService.getPerformanceClass();
|
|
enableBlurEffect = performanceClass >= 29;
|
|
|
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
|
final manufacturer = androidInfo.manufacturer.toLowerCase();
|
|
final pattern = BurstPatterns.byManufacturer[manufacturer];
|
|
collectionBurstPatterns = pattern != null ? [pattern] : [];
|
|
|
|
// availability
|
|
if (flavor.hasMapStyleDefault) {
|
|
final defaultMapStyle = mobileServices.defaultMapStyle;
|
|
if (defaultMapStyle != null && mobileServices.mapStyles.contains(defaultMapStyle)) {
|
|
mapStyle = defaultMapStyle;
|
|
} else {
|
|
final styles = EntryMapStyles.baseStyles;
|
|
mapStyle = styles[Random().nextInt(styles.length)];
|
|
}
|
|
}
|
|
|
|
if (settings.useTvLayout) {
|
|
applyTvSettings();
|
|
}
|
|
}
|
|
|
|
void applyTvSettings() {
|
|
themeBrightness = AvesThemeBrightness.dark;
|
|
maxBrightness = MaxBrightness.never;
|
|
mustBackTwiceToExit = false;
|
|
// address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality
|
|
drawerTypeBookmarks = [
|
|
null,
|
|
MimeFilter.video,
|
|
FavouriteFilter.instance,
|
|
];
|
|
drawerPageBookmarks = [
|
|
SearchPage.routeName,
|
|
];
|
|
bottomNavigationActions = [];
|
|
showOverlayOnOpening = false;
|
|
showOverlayMinimap = false;
|
|
showOverlayZoomLevel = false;
|
|
showOverlayThumbnailPreview = false;
|
|
viewerGestureSideTapNext = false;
|
|
viewerUseCutout = true;
|
|
enableBin = false;
|
|
showPinchGestureAlternatives = true;
|
|
resetShowTitleQuery();
|
|
}
|
|
|
|
Future<void> sanitize() async {
|
|
if (timeToTakeAction == AccessibilityTimeout.system &&
|
|
!await AccessibilityService.hasRecommendedTimeouts()) {
|
|
set(SettingKeys.timeToTakeActionKey, null);
|
|
}
|
|
|
|
if (viewerUseCutout != SettingsDefaults.viewerUseCutout &&
|
|
!await windowService.isCutoutAware()) {
|
|
set(SettingKeys.viewerUseCutoutKey, null);
|
|
}
|
|
|
|
collectionBurstPatterns =
|
|
collectionBurstPatterns.where(BurstPatterns.options.contains).toList();
|
|
}
|
|
|
|
// tag editor
|
|
|
|
bool get tagEditorCurrentFilterSectionExpanded => getBool(SettingKeys.tagEditorCurrentFilterSectionExpandedKey) ?? SettingsDefaults.tagEditorCurrentFilterSectionExpanded;
|
|
|
|
set tagEditorCurrentFilterSectionExpanded(bool newValue) => set(SettingKeys.tagEditorCurrentFilterSectionExpandedKey, newValue);
|
|
|
|
String? get tagEditorExpandedSection => getString(SettingKeys.tagEditorExpandedSectionKey);
|
|
|
|
set tagEditorExpandedSection(String? newValue) => set(SettingKeys.tagEditorExpandedSectionKey, newValue);
|
|
|
|
// converter
|
|
|
|
String get convertMimeType => getString(SettingKeys.convertMimeTypeKey) ?? SettingsDefaults.convertMimeType;
|
|
|
|
set convertMimeType(String newValue) => set(SettingKeys.convertMimeTypeKey, newValue);
|
|
|
|
int get convertQuality => getInt(SettingKeys.convertQualityKey) ?? SettingsDefaults.convertQuality;
|
|
|
|
set convertQuality(int newValue) => set(SettingKeys.convertQualityKey, newValue);
|
|
|
|
bool get convertWriteMetadata => getBool(SettingKeys.convertWriteMetadataKey) ?? SettingsDefaults.convertWriteMetadata;
|
|
|
|
set convertWriteMetadata(bool newValue) => set(SettingKeys.convertWriteMetadataKey, newValue);
|
|
|
|
// map
|
|
|
|
EntryMapStyle? get mapStyle {
|
|
var preferred = getString(SettingKeys.mapStyleKey);
|
|
|
|
// backward compatibility with definition as enum
|
|
const oldEnumPrefix = 'EntryMapStyle.';
|
|
if (preferred != null && preferred.startsWith(oldEnumPrefix)) {
|
|
preferred = preferred.substring(oldEnumPrefix.length);
|
|
if (preferred.isEmpty) preferred = null;
|
|
}
|
|
|
|
if (preferred == null) return null;
|
|
|
|
final styles = [...availability.mapStyles, ...customMapStyles];
|
|
return styles.firstWhereOrNull((v) => v.key == preferred) ?? styles.first;
|
|
}
|
|
|
|
set mapStyle(EntryMapStyle? newValue) => set(SettingKeys.mapStyleKey, newValue?.key);
|
|
|
|
LatLng? get mapDefaultCenter {
|
|
final json = getString(SettingKeys.mapDefaultCenterKey);
|
|
return json != null ? LatLng.fromJson(jsonDecode(json)) : null;
|
|
}
|
|
|
|
set mapDefaultCenter(LatLng? newValue) => set(SettingKeys.mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null);
|
|
|
|
Set<EntryMapStyle> get customMapStyles => (getStringList(SettingKeys.customMapStylesKey) ?? []).map(EntryMapStyle.fromJson).nonNulls.toSet();
|
|
|
|
set customMapStyles(Set<EntryMapStyle> newValue) => set(SettingKeys.customMapStylesKey, newValue.map((filter) => filter.toJson()).toList());
|
|
|
|
// bin
|
|
|
|
bool get enableBin => getBool(SettingKeys.enableBinKey) ?? SettingsDefaults.enableBin;
|
|
|
|
set enableBin(bool newValue) => set(SettingKeys.enableBinKey, newValue);
|
|
|
|
// accessibility
|
|
|
|
bool get showPinchGestureAlternatives => getBool(SettingKeys.showPinchGestureAlternativesKey) ?? SettingsDefaults.showPinchGestureAlternatives;
|
|
|
|
set showPinchGestureAlternatives(bool newValue) => set(SettingKeys.showPinchGestureAlternativesKey, newValue);
|
|
|
|
AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(SettingKeys.accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);
|
|
|
|
bool get animate => accessibilityAnimations.animate;
|
|
|
|
set accessibilityAnimations(AccessibilityAnimations newValue) => set(SettingKeys.accessibilityAnimationsKey, newValue.toString());
|
|
|
|
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(SettingKeys.timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
|
|
|
|
set timeToTakeAction(AccessibilityTimeout newValue) => set(SettingKeys.timeToTakeActionKey, newValue.toString());
|
|
|
|
// platform settings
|
|
|
|
void _onPlatformSettingsChanged(Map? fields) {
|
|
fields?.forEach((key, value) {
|
|
switch (key) {
|
|
case SettingKeys.platformAccelerometerRotationKey:
|
|
if (value is num) {
|
|
isRotationLocked = value == 0;
|
|
}
|
|
case SettingKeys.platformTransitionAnimationScaleKey:
|
|
if (value is num) {
|
|
areAnimationsRemoved = value == 0;
|
|
}
|
|
case SettingKeys.platformLongPressTimeoutMillisKey:
|
|
if (value is num) {
|
|
longPressTimeoutMillis = value.toInt();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
bool get isRotationLocked => getBool(SettingKeys.platformAccelerometerRotationKey) ?? SettingsDefaults.isRotationLocked;
|
|
|
|
set isRotationLocked(bool newValue) => set(SettingKeys.platformAccelerometerRotationKey, newValue);
|
|
|
|
bool get areAnimationsRemoved => getBool(SettingKeys.platformTransitionAnimationScaleKey) ?? SettingsDefaults.areAnimationsRemoved;
|
|
|
|
set areAnimationsRemoved(bool newValue) => set(SettingKeys.platformTransitionAnimationScaleKey, newValue);
|
|
|
|
Duration get longPressTimeout => Duration(milliseconds: getInt(SettingKeys.platformLongPressTimeoutMillisKey) ?? kLongPressTimeout.inMilliseconds);
|
|
|
|
set longPressTimeoutMillis(int newValue) => set(SettingKeys.platformLongPressTimeoutMillisKey, newValue);
|
|
|
|
// import/export
|
|
|
|
Map<String, dynamic> export() => Map.fromEntries(
|
|
store.getKeys().whereNot(SettingKeys.isInternalKey).map((k) => MapEntry(k, store.get(k))),
|
|
);
|
|
|
|
Future<void> import(Object jsonMap) async {
|
|
if (jsonMap is! Map) {
|
|
debugPrint('failed to import settings for jsonMap=$jsonMap');
|
|
return;
|
|
}
|
|
|
|
// reset per ripristinare i default
|
|
await reset(includeInternalKeys: false);
|
|
|
|
jsonMap.cast<String, Object?>().forEach((key, newValue) {
|
|
final oldValue = store.get(key);
|
|
|
|
// null → rimuovi
|
|
if (newValue == null) {
|
|
store.remove(key);
|
|
}
|
|
|
|
// double
|
|
else if (key.startsWith(SettingKeys.tileExtentPrefixKey)) {
|
|
if (newValue is double) store.setDouble(key, newValue);
|
|
}
|
|
|
|
// string
|
|
else if (key.startsWith(SettingKeys.tileLayoutPrefixKey)) {
|
|
if (newValue is String) store.setString(key, newValue);
|
|
}
|
|
|
|
// bool
|
|
else if (key.startsWith(SettingKeys.showTitleQueryPrefixKey)) {
|
|
if (newValue is bool) store.setBool(key, newValue);
|
|
}
|
|
|
|
// 🔥 gestione compatta dei tipi base
|
|
else if (newValue is int) {
|
|
store.setInt(key, newValue);
|
|
} else if (newValue is double) {
|
|
store.setDouble(key, newValue);
|
|
} else if (newValue is bool) {
|
|
store.setBool(key, newValue);
|
|
} else if (newValue is String) {
|
|
store.setString(key, newValue);
|
|
} else if (newValue is List) {
|
|
store.setStringList(key, newValue.cast<String>());
|
|
}
|
|
|
|
// notifica se cambiato
|
|
if (oldValue != newValue) {
|
|
notifyKeyChange(key, oldValue, newValue);
|
|
}
|
|
});
|
|
|
|
await sanitize();
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void notifyKeyChange(String key, Object? oldValue, Object? newValue) {
|
|
_updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue));
|
|
if (key.startsWith(SettingKeys.tileExtentPrefixKey)) {
|
|
_updateTileExtentStreamController.add(SettingsChangedEvent(key, oldValue, newValue));
|
|
}
|
|
}
|
|
}
|