From 641344271fba8eb4791210835b6d5ca26dc3b31f Mon Sep 17 00:00:00 2001 From: stopflock Date: Sun, 10 Aug 2025 00:53:01 -0500 Subject: [PATCH] fix appstate repeated instantiation --- lib/app_state.dart | 2 + .../profile_list_section.dart | 55 ++++++++++--------- .../queue_section.dart | 5 +- lib/services/map_data_provider.dart | 16 +++--- lib/services/offline_area_service.dart | 2 +- lib/widgets/map_view.dart | 14 +++-- lib/widgets/tile_provider_with_cache.dart | 6 +- 7 files changed, 55 insertions(+), 45 deletions(-) diff --git a/lib/app_state.dart b/lib/app_state.dart index 510ddf2..e9dde49 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -24,7 +24,9 @@ class AddCameraSession { // ------------------ AppState ------------------ class AppState extends ChangeNotifier { + static late AppState instance; AppState() { + instance = this; _init(); } diff --git a/lib/screens/settings_screen_sections/profile_list_section.dart b/lib/screens/settings_screen_sections/profile_list_section.dart index 94cbcee..00612f3 100644 --- a/lib/screens/settings_screen_sections/profile_list_section.dart +++ b/lib/screens/settings_screen_sections/profile_list_section.dart @@ -76,7 +76,7 @@ class ProfileListSection extends StatelessWidget { ), ); } else if (value == 'delete') { - _showDeleteProfileDialog(context, appState, p); + _showDeleteProfileDialog(context, p); } }, ), @@ -86,30 +86,31 @@ class ProfileListSection extends StatelessWidget { ); } - void _showDeleteProfileDialog(BuildContext context, AppState appState, CameraProfile profile) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Delete Profile'), - content: Text('Are you sure you want to delete "${profile.name}"?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - appState.deleteProfile(profile); - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Profile deleted')), - ); - }, - style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Delete'), - ), - ], - ), - ); - } +void _showDeleteProfileDialog(BuildContext context, CameraProfile profile) { + final appState = context.read(); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Delete Profile'), + content: Text('Are you sure you want to delete "${profile.name}"?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + appState.deleteProfile(profile); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Profile deleted')), + ); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Delete'), + ), + ], + ), + ); +} } diff --git a/lib/screens/settings_screen_sections/queue_section.dart b/lib/screens/settings_screen_sections/queue_section.dart index 3899965..6d82405 100644 --- a/lib/screens/settings_screen_sections/queue_section.dart +++ b/lib/screens/settings_screen_sections/queue_section.dart @@ -19,7 +19,7 @@ class QueueSection extends StatelessWidget { ? const Text('Sandbox mode – uploads go to OSM Sandbox') : const Text('Tap to view queue'), onTap: appState.pendingCount > 0 - ? () => _showQueueDialog(context, appState) + ? () => _showQueueDialog(context) : null, ), if (appState.pendingCount > 0) @@ -57,7 +57,8 @@ class QueueSection extends StatelessWidget { ); } - void _showQueueDialog(BuildContext context, AppState appState) { + void _showQueueDialog(BuildContext context) { + final appState = context.read(); showDialog( context: context, builder: (context) => AlertDialog( diff --git a/lib/services/map_data_provider.dart b/lib/services/map_data_provider.dart index 3c21a93..28c1e02 100644 --- a/lib/services/map_data_provider.dart +++ b/lib/services/map_data_provider.dart @@ -21,11 +21,11 @@ class MapDataProvider { factory MapDataProvider() => _instance; MapDataProvider._(); - AppState get _appState => AppState(); // Use singleton for now + // REMOVED: AppState get _appState => AppState(); - bool get isOfflineMode => _appState.offlineMode; + bool get isOfflineMode => AppState.instance.offlineMode; void setOfflineMode(bool enabled) { - _appState.setOfflineMode(enabled); + AppState.instance.setOfflineMode(enabled); } /// Fetch cameras from OSM/Overpass or local storage, depending on source/offline mode. @@ -35,9 +35,10 @@ class MapDataProvider { UploadMode uploadMode = UploadMode.production, MapSource source = MapSource.auto, }) async { - print('[MapDataProvider] getCameras called, source=$source, offlineMode=$isOfflineMode'); + final offline = AppState.instance.offlineMode; + print('[MapDataProvider] getCameras called, source=$source, offlineMode=$offline'); // Resolve source: - if (isOfflineMode && source != MapSource.local) { + if (offline && source != MapSource.local) { print('[MapDataProvider] BLOCKED by offlineMode for getCameras'); throw OfflineModeException("Cannot fetch remote cameras in offline mode."); } @@ -56,8 +57,9 @@ class MapDataProvider { required int y, MapSource source = MapSource.auto, }) async { - print('[MapDataProvider] getTile called for $z/$x/$y, source=$source, offlineMode=$isOfflineMode'); - if (isOfflineMode && source != MapSource.local) { + final offline = AppState.instance.offlineMode; + print('[MapDataProvider] getTile called for $z/$x/$y, source=$source, offlineMode=$offline'); + if (offline && source != MapSource.local) { print('[MapDataProvider] BLOCKED by offlineMode for $z/$x/$y'); throw OfflineModeException("Cannot fetch remote tiles in offline mode."); } diff --git a/lib/services/offline_area_service.dart b/lib/services/offline_area_service.dart index 6da76b8..dc92c70 100644 --- a/lib/services/offline_area_service.dart +++ b/lib/services/offline_area_service.dart @@ -247,7 +247,7 @@ class OfflineAreaService { if (!area.isPermanent) { final cameras = await camerasFromOverpass( bounds: bounds, - profiles: AppState().enabledProfiles, + profiles: AppState.instance.enabledProfiles, ); area.cameras = cameras; await saveCameras(cameras, directory); diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index 0e5a0c6..4b807b4 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -95,15 +95,16 @@ class _MapViewState extends State { List _lastProfileIds = []; UploadMode? _lastUploadMode; - void _maybeRefreshCameras(AppState appState) { + void _maybeRefreshCameras() { + final appState = context.read(); final currProfileIds = appState.enabledProfiles.map((p) => p.id).toList(); final currMode = appState.uploadMode; - if (_lastProfileIds.isEmpty || + if (_lastProfileIds.isEmpty || currProfileIds.length != _lastProfileIds.length || !_lastProfileIds.asMap().entries.every((entry) => currProfileIds[entry.key] == entry.value) || _lastUploadMode != currMode) { // If this is first load, or list/ids/mode changed, refetch - _debounce(() => _refreshCameras(appState)); + _debounce(_refreshCameras); _lastProfileIds = List.from(currProfileIds); _lastUploadMode = currMode; } @@ -149,7 +150,8 @@ class _MapViewState extends State { }); } - Future _refreshCameras(AppState appState) async { + Future _refreshCameras() async { + final appState = context.read(); LatLngBounds? bounds; try { bounds = _controller.camera.visibleBounds; @@ -181,7 +183,7 @@ class _MapViewState extends State { // Refetch only if profiles or mode changed // This avoids repeated fetches on every build // We track last seen values (local to the State class) - _maybeRefreshCameras(appState); + _maybeRefreshCameras(); // Seed add‑mode target once, after first controller center is available. if (session != null && session.target == null) { @@ -235,7 +237,7 @@ class _MapViewState extends State { if (session != null) { appState.updateSession(target: pos.center); } - _debounce(() => _refreshCameras(appState)); + _debounce(_refreshCameras); }, ), children: [ diff --git a/lib/widgets/tile_provider_with_cache.dart b/lib/widgets/tile_provider_with_cache.dart index 7100656..b91026f 100644 --- a/lib/widgets/tile_provider_with_cache.dart +++ b/lib/widgets/tile_provider_with_cache.dart @@ -4,13 +4,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter/scheduler.dart'; import '../services/map_data_provider.dart'; +import '../app_state.dart'; /// Singleton in-memory tile cache and async provider for custom tiles. class TileProviderWithCache extends TileProvider { static final Map _tileCache = {}; static Map get tileCache => _tileCache; final VoidCallback? onTileCacheUpdated; - TileProviderWithCache({this.onTileCacheUpdated}); @override @@ -29,7 +29,9 @@ class TileProviderWithCache extends TileProvider { // Don't fire multiple fetches for the same tile simultaneously if (_tileCache.containsKey(key)) return; try { - final bytes = await MapDataProvider().getTile(z: coords.z, x: coords.x, y: coords.y); + final bytes = await MapDataProvider().getTile( + z: coords.z, x: coords.x, y: coords.y, + ); if (bytes.isNotEmpty) { _tileCache[key] = Uint8List.fromList(bytes); print('[TileProviderWithCache] Cached tile $key, bytes=${bytes.length}');