fix appstate repeated instantiation

This commit is contained in:
stopflock
2025-08-10 00:53:01 -05:00
parent 6bf9556c65
commit 641344271f
7 changed files with 55 additions and 45 deletions
+2
View File
@@ -24,7 +24,9 @@ class AddCameraSession {
// ------------------ AppState ------------------
class AppState extends ChangeNotifier {
static late AppState instance;
AppState() {
instance = this;
_init();
}
@@ -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<AppState>();
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'),
),
],
),
);
}
}
@@ -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<AppState>();
showDialog(
context: context,
builder: (context) => AlertDialog(
+9 -7
View File
@@ -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.");
}
+1 -1
View File
@@ -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);
+8 -6
View File
@@ -95,15 +95,16 @@ class _MapViewState extends State<MapView> {
List<String> _lastProfileIds = [];
UploadMode? _lastUploadMode;
void _maybeRefreshCameras(AppState appState) {
void _maybeRefreshCameras() {
final appState = context.read<AppState>();
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<MapView> {
});
}
Future<void> _refreshCameras(AppState appState) async {
Future<void> _refreshCameras() async {
final appState = context.read<AppState>();
LatLngBounds? bounds;
try {
bounds = _controller.camera.visibleBounds;
@@ -181,7 +183,7 @@ class _MapViewState extends State<MapView> {
// 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 addmode target once, after first controller center is available.
if (session != null && session.target == null) {
@@ -235,7 +237,7 @@ class _MapViewState extends State<MapView> {
if (session != null) {
appState.updateSession(target: pos.center);
}
_debounce(() => _refreshCameras(appState));
_debounce(_refreshCameras);
},
),
children: [
+4 -2
View File
@@ -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<String, Uint8List> _tileCache = {};
static Map<String, Uint8List> 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}');