break up map_view

This commit is contained in:
stopflock
2025-08-21 19:15:59 -05:00
parent 4cc8929378
commit 1272eb9409
4 changed files with 298 additions and 199 deletions
+21 -199
View File
@@ -1,14 +1,6 @@
import 'dart:async';
import 'dart:math' as math;
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:http/io_client.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:latlong2/latlong.dart';
import 'package:geolocator/geolocator.dart';
import 'package:provider/provider.dart';
@@ -18,63 +10,12 @@ import '../services/map_data_provider.dart';
import '../services/offline_area_service.dart';
import '../models/osm_camera_node.dart';
import 'debouncer.dart';
import 'camera_tag_sheet.dart';
import 'tile_provider_with_cache.dart';
import 'camera_provider_with_cache.dart';
import 'package:flock_map_app/dev_config.dart';
// --- Smart marker widget for camera with single/double tap distinction
class _CameraMapMarker extends StatefulWidget {
final OsmCameraNode node;
final MapController mapController;
const _CameraMapMarker({required this.node, required this.mapController, Key? key}) : super(key: key);
@override
State<_CameraMapMarker> createState() => _CameraMapMarkerState();
}
class _CameraMapMarkerState extends State<_CameraMapMarker> {
Timer? _tapTimer;
// From dev_config.dart for build-time parameters
static const Duration tapTimeout = kMarkerTapTimeout;
void _onTap() {
_tapTimer = Timer(tapTimeout, () {
showModalBottomSheet(
context: context,
builder: (_) => CameraTagSheet(node: widget.node),
showDragHandle: true,
);
});
}
void _onDoubleTap() {
_tapTimer?.cancel();
widget.mapController.move(widget.node.coord, widget.mapController.camera.zoom + 1);
}
@override
void dispose() {
_tapTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Check if this is a pending upload
final isPending = widget.node.tags.containsKey('_pending_upload') &&
widget.node.tags['_pending_upload'] == 'true';
return GestureDetector(
onTap: _onTap,
onDoubleTap: _onDoubleTap,
child: Icon(
Icons.videocam,
color: isPending ? Colors.purple : Colors.orange,
),
);
}
}
import 'map/camera_markers.dart';
import 'map/direction_cones.dart';
import 'map/map_overlays.dart';
import '../dev_config.dart';
class MapView extends StatefulWidget {
final MapController controller;
@@ -229,39 +170,19 @@ class _MapViewState extends State<MapView> {
final cameras = (mapBounds != null)
? cameraProvider.getCachedCamerasForBounds(mapBounds)
: <OsmCameraNode>[];
final markers = <Marker>[
...cameras
.where((n) => n.coord.latitude != 0 || n.coord.longitude != 0)
.where((n) => n.coord.latitude.abs() <= 90 && n.coord.longitude.abs() <= 180)
.map((n) => Marker(
point: n.coord,
width: 24,
height: 24,
child: _CameraMapMarker(node: n, mapController: _controller),
)),
if (_currentLatLng != null)
Marker(
point: _currentLatLng!,
width: 16,
height: 16,
child: const Icon(Icons.my_location, color: Colors.blue),
),
];
final markers = CameraMarkersBuilder.buildCameraMarkers(
cameras: cameras,
mapController: _controller,
userLocation: _currentLatLng,
);
final overlays = DirectionConesBuilder.buildDirectionCones(
cameras: cameras,
zoom: zoom,
session: session,
);
final overlays = <Polygon>[
if (session != null && session.target != null)
_buildCone(session.target!, session.directionDegrees, zoom),
...cameras
.where((n) => n.hasDirection && n.directionDeg != null)
.where((n) => n.coord.latitude != 0 || n.coord.longitude != 0)
.where((n) => n.coord.latitude.abs() <= 90 && n.coord.longitude.abs() <= 180)
.map((n) => _buildCone(
n.coord,
n.directionDeg!,
zoom,
isPending: n.tags.containsKey('_pending_upload') && n.tags['_pending_upload'] == 'true',
)),
];
return Stack(
children: [
PolygonLayer(polygons: overlays),
@@ -332,113 +253,14 @@ class _MapViewState extends State<MapView> {
],
),
// MODE INDICATOR badge (top-right)
if (appState.uploadMode == UploadMode.sandbox || appState.uploadMode == UploadMode.simulate)
Positioned(
top: 18,
right: 14,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: appState.uploadMode == UploadMode.sandbox
? Colors.orange.withOpacity(0.90)
: Colors.deepPurple.withOpacity(0.80),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 5, offset: Offset(0,2)),
],
),
child: Text(
appState.uploadMode == UploadMode.sandbox
? 'SANDBOX MODE'
: 'SIMULATE',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13,
letterSpacing: 1.1,
),
),
),
),
// Zoom indicator, positioned above scale bar
Positioned(
left: 10,
bottom: kZoomIndicatorBottomOffset,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.52),
borderRadius: BorderRadius.circular(7),
),
child: Builder(
builder: (context) {
final zoom = _controller.camera.zoom;
return Text(
'Zoom: ${zoom.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
);
},
),
),
// All map overlays (mode indicator, zoom, attribution, add pin)
MapOverlays(
mapController: _controller,
uploadMode: appState.uploadMode,
session: session,
),
// Attribution overlay
Positioned(
bottom: kAttributionBottomOffset,
left: 10,
child: Container(
color: Colors.white70,
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
child: const Text(
'© OpenStreetMap and contributors',
style: TextStyle(fontSize: 11),
),
),
),
// Fixed pin when adding camera
if (session != null)
IgnorePointer(
child: Center(
child: Transform.translate(
offset: Offset(0, kAddPinYOffset),
child: Icon(Icons.place, size: 40, color: Colors.redAccent),
),
),
),
],
);
}
Polygon _buildCone(LatLng origin, double bearingDeg, double zoom, {bool isPending = false}) {
final halfAngle = kDirectionConeHalfAngle;
final length = kDirectionConeBaseLength * math.pow(2, 15 - zoom);
LatLng _project(double deg) {
final rad = deg * math.pi / 180;
final dLat = length * math.cos(rad);
final dLon =
length * math.sin(rad) / math.cos(origin.latitude * math.pi / 180);
return LatLng(origin.latitude + dLat, origin.longitude + dLon);
}
final left = _project(bearingDeg - halfAngle);
final right = _project(bearingDeg + halfAngle);
// Use purple color for pending uploads
final color = isPending ? Colors.purple : Colors.redAccent;
return Polygon(
points: [origin, left, right, origin],
color: color.withOpacity(0.25),
borderColor: color,
borderStrokeWidth: 1,
);
}
}