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

View File

@@ -20,14 +20,13 @@ A Flutter app for mapping and tagging ALPR-style cameras (and other surveillance
## Offline Areas *(IN PROGRESS)*
- Download any map area for offline use! Uses OSM raster tile cache and Overpass-surveillance cameras.
- 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.
- Always includes world tiles for zoom 14 for seamless context.
- Downloads **all** camera points in area (not just top 250) for offline visibility.
- Status, progress, and detailed area management appear in Settings:
- Cancel, retry, and delete areas (in UI now)
- Storage/camera breakdown per area (coming soon)
- After area download: future updates will allow full offline map and camera viewing/queuing.
---

View File

@@ -3,6 +3,8 @@ import 'package:provider/provider.dart';
import '../app_state.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';
class HomeScreen extends StatefulWidget {
@@ -14,6 +16,7 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final MapController _mapController = MapController();
bool _followMe = true;
void _openAddCameraSheet() {
@@ -47,6 +50,7 @@ class _HomeScreenState extends State<HomeScreen> {
],
),
body: MapView(
controller: _mapController,
followMe: _followMe,
onUserGesture: () {
if (_followMe) setState(() => _followMe = false);
@@ -67,7 +71,7 @@ class _HomeScreenState extends State<HomeScreen> {
FloatingActionButton.extended(
onPressed: () => showDialog(
context: context,
builder: (ctx) => const DownloadAreaDialog(),
builder: (ctx) => DownloadAreaDialog(controller: _mapController),
),
icon: const Icon(Icons.download_for_offline),
label: const Text('Download'),
@@ -82,9 +86,9 @@ class _HomeScreenState extends State<HomeScreen> {
}
// --- Download area dialog ---
class DownloadAreaDialog extends StatefulWidget {
const DownloadAreaDialog({super.key});
final MapController controller;
const DownloadAreaDialog({super.key, required this.controller});
@override
State<DownloadAreaDialog> createState() => _DownloadAreaDialogState();
@@ -147,19 +151,47 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
// Real download to be implemented in later stages.
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Download started (stub only)'),
),
);
onPressed: () async {
try {
final bounds = widget.controller.camera.visibleBounds;
final maxZoom = _zoom.toInt();
final minZoom = _findDynamicMinZoom(bounds);
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'),
),
],
);
}
int _findDynamicMinZoom(LatLngBounds bounds) {
// For now, just pick 12 as min; can implement dynamic minzoom by area
return 12;
}
}

View File

@@ -16,8 +16,10 @@ import 'debouncer.dart';
import 'camera_tag_sheet.dart';
class MapView extends StatefulWidget {
final MapController controller;
const MapView({
super.key,
required this.controller,
required this.followMe,
required this.onUserGesture,
});
@@ -30,7 +32,7 @@ class MapView extends StatefulWidget {
}
class _MapViewState extends State<MapView> {
final MapController _controller = MapController();
late final MapController _controller;
final OverpassService _overpass = OverpassService();
final Debouncer _debounce = Debouncer(const Duration(milliseconds: 500));
@@ -58,6 +60,7 @@ class _MapViewState extends State<MapView> {
@override
void initState() {
super.initState();
_controller = widget.controller;
_initLocation();
}