mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-05-17 22:24:48 +02:00
fixes offline -> online transition for tile cache re-fetching, also display of camera profiles only when enabled
This commit is contained in:
@@ -19,8 +19,18 @@ class CameraProviderWithCache extends ChangeNotifier {
|
||||
Timer? _debounceTimer;
|
||||
|
||||
/// Call this to get (quickly) all cached overlays for the given view.
|
||||
/// Filters by currently enabled profiles.
|
||||
List<OsmCameraNode> getCachedCamerasForBounds(LatLngBounds bounds) {
|
||||
return CameraCache.instance.queryByBounds(bounds);
|
||||
final allCameras = CameraCache.instance.queryByBounds(bounds);
|
||||
final enabledProfiles = AppState.instance.enabledProfiles;
|
||||
|
||||
// If no profiles are enabled, show no cameras
|
||||
if (enabledProfiles.isEmpty) return [];
|
||||
|
||||
// Filter cameras to only show those matching enabled profiles
|
||||
return allCameras.where((camera) {
|
||||
return _matchesAnyProfile(camera, enabledProfiles);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Call this when the map view changes (bounds/profiles), triggers async fetch
|
||||
@@ -59,4 +69,25 @@ class CameraProviderWithCache extends ChangeNotifier {
|
||||
CameraCache.instance.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Force refresh the display (useful when filters change but cache doesn't)
|
||||
void refreshDisplay() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Check if a camera matches any of the provided profiles
|
||||
bool _matchesAnyProfile(OsmCameraNode camera, List<CameraProfile> profiles) {
|
||||
for (final profile in profiles) {
|
||||
if (_cameraMatchesProfile(camera, profile)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check if a camera matches a specific profile (all profile tags must match)
|
||||
bool _cameraMatchesProfile(OsmCameraNode camera, CameraProfile profile) {
|
||||
for (final entry in profile.tags.entries) {
|
||||
if (camera.tags[entry.key] != entry.value) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
|
||||
import '../app_state.dart';
|
||||
import '../services/offline_area_service.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import '../models/camera_profile.dart';
|
||||
import 'debouncer.dart';
|
||||
import 'tile_provider_with_cache.dart';
|
||||
import 'camera_provider_with_cache.dart';
|
||||
@@ -40,6 +41,13 @@ class _MapViewState extends State<MapView> {
|
||||
LatLng? _currentLatLng;
|
||||
|
||||
late final CameraProviderWithCache _cameraProvider;
|
||||
|
||||
// Track profile changes to trigger camera refresh
|
||||
List<CameraProfile>? _lastEnabledProfiles;
|
||||
|
||||
// Track offline mode changes to trigger tile refresh
|
||||
bool? _lastOfflineMode;
|
||||
int _mapRebuildCounter = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -52,6 +60,7 @@ class _MapViewState extends State<MapView> {
|
||||
// Set up camera overlay caching
|
||||
_cameraProvider = CameraProviderWithCache.instance;
|
||||
_cameraProvider.addListener(_onCamerasUpdated);
|
||||
|
||||
// Ensure initial overlays are fetched
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_refreshCamerasFromProvider();
|
||||
@@ -137,13 +146,46 @@ class _MapViewState extends State<MapView> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to check if two profile lists are equal
|
||||
bool _profileListsEqual(List<CameraProfile> list1, List<CameraProfile> list2) {
|
||||
if (list1.length != list2.length) return false;
|
||||
// Compare by profile IDs since profiles are value objects
|
||||
final ids1 = list1.map((p) => p.id).toSet();
|
||||
final ids2 = list2.map((p) => p.id).toSet();
|
||||
return ids1.length == ids2.length && ids1.containsAll(ids2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
final session = appState.session;
|
||||
|
||||
// Only update cameras when map moves or profiles/mode actually change (not every build!)
|
||||
// _refreshCamerasFromProvider() is now only called from map movement and relevant change handlers.
|
||||
// Check if enabled profiles changed and refresh cameras if needed
|
||||
final currentEnabledProfiles = appState.enabledProfiles;
|
||||
if (_lastEnabledProfiles == null ||
|
||||
!_profileListsEqual(_lastEnabledProfiles!, currentEnabledProfiles)) {
|
||||
_lastEnabledProfiles = List.from(currentEnabledProfiles);
|
||||
// Refresh cameras when profiles change
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// Force display refresh first (for immediate UI update)
|
||||
_cameraProvider.refreshDisplay();
|
||||
// Then fetch new cameras for newly enabled profiles
|
||||
_refreshCamerasFromProvider();
|
||||
});
|
||||
}
|
||||
|
||||
// Check if offline mode changed and force complete map rebuild
|
||||
final currentOfflineMode = appState.offlineMode;
|
||||
if (_lastOfflineMode != null && _lastOfflineMode != currentOfflineMode) {
|
||||
// Offline mode changed - increment counter to force FlutterMap rebuild
|
||||
_mapRebuildCounter++;
|
||||
debugPrint('[MapView] Offline mode changed, forcing map rebuild #$_mapRebuildCounter');
|
||||
}
|
||||
_lastOfflineMode = currentOfflineMode;
|
||||
|
||||
// Seed add‑mode target once, after first controller center is available.
|
||||
if (session != null && session.target == null) {
|
||||
@@ -193,7 +235,7 @@ class _MapViewState extends State<MapView> {
|
||||
return Stack(
|
||||
children: [
|
||||
FlutterMap(
|
||||
key: ValueKey(appState.offlineMode),
|
||||
key: ValueKey('map_rebuild_$_mapRebuildCounter'),
|
||||
mapController: _controller,
|
||||
options: MapOptions(
|
||||
initialCenter: _currentLatLng ?? LatLng(37.7749, -122.4194),
|
||||
@@ -237,7 +279,7 @@ class _MapViewState extends State<MapView> {
|
||||
print('tileBuilder error: $e for tileImage: ${tileImage.toString()}');
|
||||
return tileWidget;
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
cameraLayers,
|
||||
// Built-in scale bar from flutter_map
|
||||
|
||||
@@ -9,13 +9,30 @@ class TileProviderWithCache extends TileProvider with ChangeNotifier {
|
||||
static Map<String, Uint8List> get tileCache => _tileCache;
|
||||
|
||||
bool _disposed = false;
|
||||
int _disposeCount = 0;
|
||||
|
||||
TileProviderWithCache();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposeCount++;
|
||||
|
||||
// If already disposed, just silently return (common during FlutterMap rebuilds)
|
||||
if (_disposed) {
|
||||
debugPrint('[TileProviderWithCache] Already disposed (call #$_disposeCount) - ignoring');
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('[TileProviderWithCache] Disposing (call #$_disposeCount)');
|
||||
_disposed = true;
|
||||
super.dispose();
|
||||
|
||||
// Safely call super.dispose() with error handling
|
||||
try {
|
||||
super.dispose();
|
||||
} catch (e) {
|
||||
debugPrint('[TileProviderWithCache] Error during disposal: $e');
|
||||
// Continue execution - disposal errors shouldn't crash the app
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -46,8 +63,8 @@ class TileProviderWithCache extends TileProvider with ChangeNotifier {
|
||||
);
|
||||
if (bytes.isNotEmpty) {
|
||||
_tileCache[key] = Uint8List.fromList(bytes);
|
||||
// Only notify listeners if not disposed
|
||||
if (!_disposed) {
|
||||
// Only notify listeners if not disposed and still mounted
|
||||
if (!_disposed && hasListeners) {
|
||||
notifyListeners(); // This updates any listening widgets
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user