enum UnitSystem { metric, imperial } extension UnitSystemValue on UnitSystem { String get value { switch (this) { case UnitSystem.imperial: return 'imperial'; case UnitSystem.metric: return 'metric'; } } } class AppSettings { static const Object _unset = Object(); final bool clearPathOnMaxRetry; final bool mapShowRepeaters; final bool mapShowChatNodes; final bool mapShowOtherNodes; final double mapTimeFilterHours; // 0 = all time final bool mapKeyPrefixEnabled; final String mapKeyPrefix; final bool mapShowMarkers; final bool mapShowGuessedLocations; final bool enableMessageTracing; final Map? mapCacheBounds; final int mapCacheMinZoom; final int mapCacheMaxZoom; final bool notificationsEnabled; final bool notifyOnNewMessage; final bool notifyOnNewChannelMessage; final bool notifyOnNewAdvert; final bool autoRouteRotationEnabled; final String themeMode; final String? languageOverride; // null = system default final bool appDebugLogEnabled; final Map batteryChemistryByDeviceId; final Map batteryChemistryByRepeaterId; final UnitSystem unitSystem; final Set mutedChannels; AppSettings({ this.clearPathOnMaxRetry = false, this.mapShowRepeaters = true, this.mapShowChatNodes = true, this.mapShowOtherNodes = true, this.mapTimeFilterHours = 0, // Default to all time this.mapKeyPrefixEnabled = false, this.mapKeyPrefix = '', this.mapShowMarkers = true, this.mapShowGuessedLocations = true, this.enableMessageTracing = false, this.mapCacheBounds, this.mapCacheMinZoom = 10, this.mapCacheMaxZoom = 15, this.notificationsEnabled = true, this.notifyOnNewMessage = true, this.notifyOnNewChannelMessage = true, this.notifyOnNewAdvert = true, this.autoRouteRotationEnabled = false, this.themeMode = 'system', this.languageOverride, this.appDebugLogEnabled = false, Map? batteryChemistryByDeviceId, Map? batteryChemistryByRepeaterId, this.unitSystem = UnitSystem.metric, Set? mutedChannels, }) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {}, batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {}, mutedChannels = mutedChannels ?? {}; Map toJson() { return { 'clear_path_on_max_retry': clearPathOnMaxRetry, 'map_show_repeaters': mapShowRepeaters, 'map_show_chat_nodes': mapShowChatNodes, 'map_show_other_nodes': mapShowOtherNodes, 'map_time_filter_hours': mapTimeFilterHours, 'map_key_prefix_enabled': mapKeyPrefixEnabled, 'map_key_prefix': mapKeyPrefix, 'map_show_markers': mapShowMarkers, 'map_show_guessed_locations': mapShowGuessedLocations, 'enable_message_tracing': enableMessageTracing, 'map_cache_bounds': mapCacheBounds, 'map_cache_min_zoom': mapCacheMinZoom, 'map_cache_max_zoom': mapCacheMaxZoom, 'notifications_enabled': notificationsEnabled, 'notify_on_new_message': notifyOnNewMessage, 'notify_on_new_channel_message': notifyOnNewChannelMessage, 'notify_on_new_advert': notifyOnNewAdvert, 'auto_route_rotation_enabled': autoRouteRotationEnabled, 'theme_mode': themeMode, 'language_override': languageOverride, 'app_debug_log_enabled': appDebugLogEnabled, 'battery_chemistry_by_device_id': batteryChemistryByDeviceId, 'battery_chemistry_by_repeater_id': batteryChemistryByRepeaterId, 'unit_system': unitSystem.value, 'muted_channels': mutedChannels.toList(), }; } factory AppSettings.fromJson(Map json) { UnitSystem parseUnitSystem(dynamic value) { if (value is String && value.toLowerCase() == 'imperial') { return UnitSystem.imperial; } return UnitSystem.metric; } return AppSettings( clearPathOnMaxRetry: json['clear_path_on_max_retry'] as bool? ?? false, mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true, mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true, mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true, mapTimeFilterHours: (json['map_time_filter_hours'] as num?)?.toDouble() ?? 0, mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false, mapKeyPrefix: json['map_key_prefix'] as String? ?? '', mapShowMarkers: json['map_show_markers'] as bool? ?? true, mapShowGuessedLocations: json['map_show_guessed_locations'] as bool? ?? true, enableMessageTracing: json['enable_message_tracing'] as bool? ?? false, mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map( (key, value) => MapEntry(key.toString(), (value as num).toDouble()), ), mapCacheMinZoom: json['map_cache_min_zoom'] as int? ?? 10, mapCacheMaxZoom: json['map_cache_max_zoom'] as int? ?? 15, notificationsEnabled: json['notifications_enabled'] as bool? ?? true, notifyOnNewMessage: json['notify_on_new_message'] as bool? ?? true, notifyOnNewChannelMessage: json['notify_on_new_channel_message'] as bool? ?? true, notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true, autoRouteRotationEnabled: json['auto_route_rotation_enabled'] as bool? ?? false, themeMode: json['theme_mode'] as String? ?? 'system', languageOverride: json['language_override'] as String?, appDebugLogEnabled: json['app_debug_log_enabled'] as bool? ?? false, batteryChemistryByDeviceId: (json['battery_chemistry_by_device_id'] as Map?)?.map( (key, value) => MapEntry(key.toString(), value.toString()), ) ?? {}, batteryChemistryByRepeaterId: (json['battery_chemistry_by_repeater_id'] as Map?)?.map( (key, value) => MapEntry(key.toString(), value.toString()), ) ?? {}, unitSystem: parseUnitSystem(json['unit_system']), mutedChannels: ((json['muted_channels'] as List?) ?.map((e) => e.toString()) .toSet()) ?? {}, ); } AppSettings copyWith({ bool? clearPathOnMaxRetry, bool? mapShowRepeaters, bool? mapShowChatNodes, bool? mapShowOtherNodes, double? mapTimeFilterHours, bool? mapKeyPrefixEnabled, String? mapKeyPrefix, bool? mapShowMarkers, bool? mapShowGuessedLocations, bool? enableMessageTracing, Object? mapCacheBounds = _unset, int? mapCacheMinZoom, int? mapCacheMaxZoom, bool? notificationsEnabled, bool? notifyOnNewMessage, bool? notifyOnNewChannelMessage, bool? notifyOnNewAdvert, bool? autoRouteRotationEnabled, String? themeMode, Object? languageOverride = _unset, bool? appDebugLogEnabled, Map? batteryChemistryByDeviceId, Map? batteryChemistryByRepeaterId, UnitSystem? unitSystem, Set? mutedChannels, }) { return AppSettings( clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry, mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters, mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes, mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes, mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours, mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled, mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix, mapShowMarkers: mapShowMarkers ?? this.mapShowMarkers, mapShowGuessedLocations: mapShowGuessedLocations ?? this.mapShowGuessedLocations, enableMessageTracing: enableMessageTracing ?? this.enableMessageTracing, mapCacheBounds: mapCacheBounds == _unset ? this.mapCacheBounds : mapCacheBounds as Map?, mapCacheMinZoom: mapCacheMinZoom ?? this.mapCacheMinZoom, mapCacheMaxZoom: mapCacheMaxZoom ?? this.mapCacheMaxZoom, notificationsEnabled: notificationsEnabled ?? this.notificationsEnabled, notifyOnNewMessage: notifyOnNewMessage ?? this.notifyOnNewMessage, notifyOnNewChannelMessage: notifyOnNewChannelMessage ?? this.notifyOnNewChannelMessage, notifyOnNewAdvert: notifyOnNewAdvert ?? this.notifyOnNewAdvert, autoRouteRotationEnabled: autoRouteRotationEnabled ?? this.autoRouteRotationEnabled, themeMode: themeMode ?? this.themeMode, languageOverride: languageOverride == _unset ? this.languageOverride : languageOverride as String?, appDebugLogEnabled: appDebugLogEnabled ?? this.appDebugLogEnabled, batteryChemistryByDeviceId: batteryChemistryByDeviceId ?? this.batteryChemistryByDeviceId, batteryChemistryByRepeaterId: batteryChemistryByRepeaterId ?? this.batteryChemistryByRepeaterId, unitSystem: unitSystem ?? this.unitSystem, mutedChannels: mutedChannels ?? this.mutedChannels, ); } }