aves_mio0.31/a.dart
2026-07-18 13:39:22 +02:00

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));
}
}
}