mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-14 23:20:46 +02:00
more UX, stuff in settings, multiple areas, area set to visible map
This commit is contained in:
@@ -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 1–4 for seamless context.
|
- Always includes world tiles for zoom 1–4 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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 min‑zoom by area
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user