import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'aws_map_provider.dart'; import 'google_map_provider.dart'; import 'map_provider_interface.dart'; import 'open_street_map_provider.dart'; final mapConfigProvider = Provider((ref) { final type = ref.watch(mapProviderTypeNotifierProvider); if (type == AppMapType.openStreetMap) { return const MapConfig(); } final aws = ref.watch(awsMapProviderInstance); return MapConfig.awsLocation(apiKey: aws.apiKey, region: aws.region); }); class MapProviderTypeNotifier extends StateNotifier { static const String _prefKey = 'selected_map_provider_type'; MapProviderTypeNotifier() : super(AppMapType.awsMaps) { _loadFromPrefs(); } Future _loadFromPrefs() async { try { final prefs = await SharedPreferences.getInstance(); final savedVal = prefs.getString(_prefKey); if (savedVal == 'googleMaps') { state = AppMapType.googleMaps; } else if (savedVal == 'openStreetMap') { state = AppMapType.openStreetMap; } else if (savedVal == 'awsMaps') { state = AppMapType.awsMaps; } } catch (_) {} } Future setMapProviderType(AppMapType type) async { state = type; try { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_prefKey, type.name); } catch (_) {} } } final mapProviderTypeNotifierProvider = StateNotifierProvider((ref) { return MapProviderTypeNotifier(); }); final openStreetMapProviderInstance = Provider((ref) { return OpenStreetMapProvider(); }); final googleMapProviderInstance = Provider((ref) { return GoogleMapProvider(); }); final awsMapProviderInstance = Provider((ref) { return AwsMapProvider(); }); final activeMapProvider = Provider((ref) { final type = ref.watch(mapProviderTypeNotifierProvider); switch (type) { case AppMapType.openStreetMap: return ref.watch(openStreetMapProviderInstance); case AppMapType.googleMaps: return ref.watch(googleMapProviderInstance); case AppMapType.awsMaps: return ref.watch(awsMapProviderInstance); } }); class MapFollowNotifier extends StateNotifier { MapFollowNotifier() : super(true); void setFollow(bool follow) { state = follow; } void userInteracted() { state = false; } void recenter() { state = true; } } final mapFollowStateProvider = StateNotifierProvider((ref) { return MapFollowNotifier(); }); /// Map orientation mode: North Up vs Heading Up. enum MapOrientation { northUp, headingUp; String get displayName => this == northUp ? 'North Up' : 'Heading Up'; } /// Computes the shortest angular difference in degrees from [from] to [to]. /// Result is in range (-180.0, 180.0]. /// Handles circular transitions across the 0°/360° discontinuity: /// e.g. from 359° to 1° yields +2°, not -358°. double shortestAngleDiff(double from, double to) { return ((to - from + 540.0) % 360.0) - 180.0; } /// Normalizes an angle in degrees into [0.0, 360.0). double normalizeAngle(double angle) { return (angle % 360.0 + 360.0) % 360.0; } class MapOrientationNotifier extends StateNotifier { static const String _prefKey = 'selected_map_orientation'; MapOrientationNotifier() : super(MapOrientation.northUp) { _loadFromPrefs(); } Future _loadFromPrefs() async { try { final prefs = await SharedPreferences.getInstance(); final savedVal = prefs.getString(_prefKey); if (savedVal == 'headingUp') { state = MapOrientation.headingUp; } else if (savedVal == 'northUp') { state = MapOrientation.northUp; } } catch (_) {} } Future toggle() async { final next = state == MapOrientation.northUp ? MapOrientation.headingUp : MapOrientation.northUp; state = next; try { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_prefKey, next.name); } catch (_) {} } Future setOrientation(MapOrientation orientation) async { state = orientation; try { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_prefKey, orientation.name); } catch (_) {} } } final mapOrientationProvider = StateNotifierProvider((ref) { return MapOrientationNotifier(); });