more UX, stuff in settings, multiple areas, area set to visible map

This commit is contained in:
stopflock
2025-08-06 20:08:39 -05:00
parent 338dd58bb0
commit db93d83efe
3 changed files with 48 additions and 14 deletions
+1 -2
View File
@@ -20,14 +20,13 @@ A Flutter app for mapping and tagging ALPR-style cameras (and other surveillance
## Offline Areas *(IN PROGRESS)* ## Offline Areas *(IN PROGRESS)*
- Download any map area for offline use! Uses OSM raster tile cache and Overpass-surveillance cameras. - Download any map area for offline use! Uses OSM raster tile cache and Overpass-surveillance cameras.
- Each area download: - Each area download:
- Selects the current map region with a dynamic minimum zoom. - Selects the current map region with a dynamic minimum zoom (calculated as the highest zoom level at which a single tile covers the selected area)
- Lets user pick the max zoom, shows real tile/storage estimate. - Lets user pick the max zoom, shows real tile/storage estimate.
- Always includes world tiles for zoom 14 for seamless context. - Always includes world tiles for zoom 14 for seamless context.
- Downloads **all** camera points in area (not just top 250) for offline visibility. - Downloads **all** camera points in area (not just top 250) for offline visibility.
- Status, progress, and detailed area management appear in Settings: - Status, progress, and detailed area management appear in Settings:
- Cancel, retry, and delete areas (in UI now) - Cancel, retry, and delete areas (in UI now)
- Storage/camera breakdown per area (coming soon) - Storage/camera breakdown per area (coming soon)
- After area download: future updates will allow full offline map and camera viewing/queuing.
--- ---
+43 -11
View File
@@ -3,6 +3,8 @@ import 'package:provider/provider.dart';
import '../app_state.dart'; import '../app_state.dart';
import '../widgets/map_view.dart'; import '../widgets/map_view.dart';
import 'package:flutter_map/flutter_map.dart';
import '../services/offline_area_service.dart';
import '../widgets/add_camera_sheet.dart'; import '../widgets/add_camera_sheet.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
@@ -14,6 +16,7 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final MapController _mapController = MapController();
bool _followMe = true; bool _followMe = true;
void _openAddCameraSheet() { void _openAddCameraSheet() {
@@ -47,6 +50,7 @@ class _HomeScreenState extends State<HomeScreen> {
], ],
), ),
body: MapView( body: MapView(
controller: _mapController,
followMe: _followMe, followMe: _followMe,
onUserGesture: () { onUserGesture: () {
if (_followMe) setState(() => _followMe = false); if (_followMe) setState(() => _followMe = false);
@@ -67,7 +71,7 @@ class _HomeScreenState extends State<HomeScreen> {
FloatingActionButton.extended( FloatingActionButton.extended(
onPressed: () => showDialog( onPressed: () => showDialog(
context: context, context: context,
builder: (ctx) => const DownloadAreaDialog(), builder: (ctx) => DownloadAreaDialog(controller: _mapController),
), ),
icon: const Icon(Icons.download_for_offline), icon: const Icon(Icons.download_for_offline),
label: const Text('Download'), label: const Text('Download'),
@@ -82,9 +86,9 @@ class _HomeScreenState extends State<HomeScreen> {
} }
// --- Download area dialog --- // --- Download area dialog ---
class DownloadAreaDialog extends StatefulWidget { class DownloadAreaDialog extends StatefulWidget {
const DownloadAreaDialog({super.key}); final MapController controller;
const DownloadAreaDialog({super.key, required this.controller});
@override @override
State<DownloadAreaDialog> createState() => _DownloadAreaDialogState(); State<DownloadAreaDialog> createState() => _DownloadAreaDialogState();
@@ -147,19 +151,47 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
child: const Text('Cancel'), child: const Text('Cancel'),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () async {
// Real download to be implemented in later stages. try {
Navigator.pop(context); final bounds = widget.controller.camera.visibleBounds;
ScaffoldMessenger.of(context).showSnackBar( final maxZoom = _zoom.toInt();
const SnackBar( final minZoom = _findDynamicMinZoom(bounds);
content: Text('Download started (stub only)'), final id = DateTime.now().toIso8601String().replaceAll(':', '-');
), final dir = '/tmp/offline_areas/$id';
);
await OfflineAreaService().downloadArea(
id: id,
bounds: bounds,
minZoom: minZoom,
maxZoom: maxZoom,
directory: dir,
onProgress: (progress) {},
onComplete: (status) {},
);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Download started!'),
),
);
} catch (e) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to start download: $e'),
),
);
}
}, },
child: const Text('Download'), child: const Text('Download'),
), ),
], ],
); );
} }
int _findDynamicMinZoom(LatLngBounds bounds) {
// For now, just pick 12 as min; can implement dynamic minzoom by area
return 12;
}
} }
+4 -1
View File
@@ -16,8 +16,10 @@ import 'debouncer.dart';
import 'camera_tag_sheet.dart'; import 'camera_tag_sheet.dart';
class MapView extends StatefulWidget { class MapView extends StatefulWidget {
final MapController controller;
const MapView({ const MapView({
super.key, super.key,
required this.controller,
required this.followMe, required this.followMe,
required this.onUserGesture, required this.onUserGesture,
}); });
@@ -30,7 +32,7 @@ class MapView extends StatefulWidget {
} }
class _MapViewState extends State<MapView> { class _MapViewState extends State<MapView> {
final MapController _controller = MapController(); late final MapController _controller;
final OverpassService _overpass = OverpassService(); final OverpassService _overpass = OverpassService();
final Debouncer _debounce = Debouncer(const Duration(milliseconds: 500)); final Debouncer _debounce = Debouncer(const Duration(milliseconds: 500));
@@ -58,6 +60,7 @@ class _MapViewState extends State<MapView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller = widget.controller;
_initLocation(); _initLocation();
} }