From 5c2bfbc76ed020776c3f855a6ad3e74beb643ac6 Mon Sep 17 00:00:00 2001 From: stopflock Date: Fri, 29 Aug 2025 20:09:42 -0500 Subject: [PATCH] Adjust map view when adding/editing to account for bottom sheet --- lib/screens/home_screen.dart | 46 +++++++++++++++++++++++--- lib/widgets/add_node_sheet.dart | 26 ++++++++++++--- lib/widgets/edit_node_sheet.dart | 28 ++++++++++++---- lib/widgets/map/map_overlays.dart | 13 +------- lib/widgets/map_view.dart | 40 ++++++++++++++++++---- lib/widgets/measured_sheet.dart | 55 +++++++++++++++++++++++++++++++ 6 files changed, 175 insertions(+), 33 deletions(-) create mode 100644 lib/widgets/measured_sheet.dart diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index af4d69c..8edde59 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -11,6 +11,7 @@ import '../widgets/add_node_sheet.dart'; import '../widgets/edit_node_sheet.dart'; import '../widgets/camera_provider_with_cache.dart'; import '../widgets/download_area_dialog.dart'; +import '../widgets/measured_sheet.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -24,6 +25,10 @@ class _HomeScreenState extends State with TickerProviderStateMixin { final GlobalKey _mapViewKey = GlobalKey(); late final AnimatedMapController _mapController; bool _editSheetShown = false; + + // Track sheet heights for map padding + double _addSheetHeight = 0.0; + double _editSheetHeight = 0.0; @override void initState() { @@ -78,9 +83,23 @@ class _HomeScreenState extends State with TickerProviderStateMixin { appState.startAddSession(); final session = appState.session!; // guaranteed non‑null now - _scaffoldKey.currentState!.showBottomSheet( - (ctx) => AddNodeSheet(session: session), + final controller = _scaffoldKey.currentState!.showBottomSheet( + (ctx) => MeasuredSheet( + onHeightChanged: (height) { + setState(() { + _addSheetHeight = height; + }); + }, + child: AddNodeSheet(session: session), + ), ); + + // Reset height when sheet is dismissed + controller.closed.then((_) { + setState(() { + _addSheetHeight = 0.0; + }); + }); } void _openEditNodeSheet() { @@ -90,9 +109,23 @@ class _HomeScreenState extends State with TickerProviderStateMixin { final session = appState.editSession!; // should be non-null when this is called - _scaffoldKey.currentState!.showBottomSheet( - (ctx) => EditNodeSheet(session: session), + final controller = _scaffoldKey.currentState!.showBottomSheet( + (ctx) => MeasuredSheet( + onHeightChanged: (height) { + setState(() { + _editSheetHeight = height; + }); + }, + child: EditNodeSheet(session: session), + ), ); + + // Reset height when sheet is dismissed + controller.closed.then((_) { + setState(() { + _editSheetHeight = 0.0; + }); + }); } @override @@ -107,6 +140,10 @@ class _HomeScreenState extends State with TickerProviderStateMixin { _editSheetShown = false; } + // Calculate bottom padding for map (90% of active sheet height) + final activeSheetHeight = _addSheetHeight > 0 ? _addSheetHeight : _editSheetHeight; + final mapBottomPadding = activeSheetHeight * 0.9; + return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => CameraProviderWithCache()), @@ -142,6 +179,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { key: _mapViewKey, controller: _mapController, followMeMode: appState.followMeMode, + bottomPadding: mapBottomPadding, onUserGesture: () { if (appState.followMeMode != FollowMeMode.off) { appState.setFollowMeMode(FollowMeMode.off); diff --git a/lib/widgets/add_node_sheet.dart b/lib/widgets/add_node_sheet.dart index 88e3abf..f7b8e83 100644 --- a/lib/widgets/add_node_sheet.dart +++ b/lib/widgets/add_node_sheet.dart @@ -29,7 +29,7 @@ class AddNodeSheet extends StatelessWidget { } final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList(); - final allowSubmit = submittableProfiles.isNotEmpty && session.profile.isSubmittable; + final allowSubmit = appState.isLoggedIn && submittableProfiles.isNotEmpty && session.profile.isSubmittable; void _openRefineTags() async { final result = await Navigator.push( @@ -90,7 +90,7 @@ class AddNodeSheet extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ Icon(Icons.info_outline, color: Colors.grey, size: 16), SizedBox(width: 6), Expanded( @@ -102,11 +102,27 @@ class AddNodeSheet extends StatelessWidget { ], ), ), - if (submittableProfiles.isEmpty) + if (!appState.isLoggedIn) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ + Icon(Icons.info_outline, color: Colors.red, size: 20), + SizedBox(width: 6), + Expanded( + child: Text( + 'You must be logged in to submit new nodes. Please log in via Settings.', + style: TextStyle(color: Colors.red, fontSize: 13), + ), + ), + ], + ), + ) + else if (submittableProfiles.isEmpty) + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), + child: Row( + children: [ Icon(Icons.info_outline, color: Colors.red, size: 20), SizedBox(width: 6), Expanded( @@ -122,7 +138,7 @@ class AddNodeSheet extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ Icon(Icons.info_outline, color: Colors.orange, size: 20), SizedBox(width: 6), Expanded( diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index 0b07b88..de93814 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -31,7 +31,7 @@ class EditNodeSheet extends StatelessWidget { final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList(); final isSandboxMode = appState.uploadMode == UploadMode.sandbox; - final allowSubmit = submittableProfiles.isNotEmpty && session.profile.isSubmittable && !isSandboxMode; + final allowSubmit = appState.isLoggedIn && submittableProfiles.isNotEmpty && session.profile.isSubmittable && !isSandboxMode; void _openRefineTags() async { final result = await Navigator.push( @@ -97,7 +97,7 @@ class EditNodeSheet extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ Icon(Icons.info_outline, color: Colors.grey, size: 16), SizedBox(width: 6), Expanded( @@ -109,11 +109,27 @@ class EditNodeSheet extends StatelessWidget { ], ), ), - if (isSandboxMode) + if (!appState.isLoggedIn) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ + Icon(Icons.info_outline, color: Colors.red, size: 20), + SizedBox(width: 6), + Expanded( + child: Text( + 'You must be logged in to edit nodes. Please log in via Settings.', + style: TextStyle(color: Colors.red, fontSize: 13), + ), + ), + ], + ), + ) + else if (isSandboxMode) + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), + child: Row( + children: [ Icon(Icons.info_outline, color: Colors.blue, size: 20), SizedBox(width: 6), Expanded( @@ -129,7 +145,7 @@ class EditNodeSheet extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ Icon(Icons.info_outline, color: Colors.red, size: 20), SizedBox(width: 6), Expanded( @@ -145,7 +161,7 @@ class EditNodeSheet extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( - children: const [ + children: [ Icon(Icons.info_outline, color: Colors.orange, size: 20), SizedBox(width: 6), Expanded( diff --git a/lib/widgets/map/map_overlays.dart b/lib/widgets/map/map_overlays.dart index c0d8329..3d40eb9 100644 --- a/lib/widgets/map/map_overlays.dart +++ b/lib/widgets/map/map_overlays.dart @@ -132,18 +132,7 @@ class MapOverlays extends StatelessWidget { ), ), - // Fixed pin when adding or editing camera - if (session != null || editSession != null) - IgnorePointer( - child: Center( - child: Transform.translate( - offset: const Offset(0, kAddPinYOffset), - child: CameraIcon( - type: editSession != null ? CameraIconType.editing : CameraIconType.mock - ), - ), - ), - ), + ], ); } diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index 9299220..0d022a7 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -12,6 +12,7 @@ import '../models/node_profile.dart'; import '../models/tile_provider.dart'; import 'debouncer.dart'; import 'camera_provider_with_cache.dart'; +import 'camera_icon.dart'; import 'map/camera_markers.dart'; import 'map/direction_cones.dart'; import 'map/map_overlays.dart'; @@ -30,10 +31,12 @@ class MapView extends StatefulWidget { required this.controller, required this.followMeMode, required this.onUserGesture, + this.bottomPadding = 0.0, }); final FollowMeMode followMeMode; final VoidCallback onUserGesture; + final double bottomPadding; @override State createState() => MapViewState(); @@ -244,11 +247,31 @@ class MapViewState extends State { // Build edit lines connecting original cameras to their edited positions final editLines = _buildEditLines(cameras); + // Build center marker for add/edit sessions + final centerMarkers = []; + if (session != null || editSession != null) { + try { + final center = _controller.mapController.camera.center; + centerMarkers.add( + Marker( + point: center, + width: kCameraIconDiameter, + height: kCameraIconDiameter, + child: CameraIcon( + type: editSession != null ? CameraIconType.editing : CameraIconType.mock, + ), + ), + ); + } catch (_) { + // Controller not ready yet + } + } + return Stack( children: [ PolygonLayer(polygons: overlays), if (editLines.isNotEmpty) PolylineLayer(polylines: editLines), - MarkerLayer(markers: markers), + MarkerLayer(markers: [...markers, ...centerMarkers]), ], ); } @@ -256,11 +279,15 @@ class MapViewState extends State { return Stack( children: [ - FlutterMap( - key: ValueKey('map_${appState.offlineMode}_${appState.selectedTileType?.id ?? 'none'}_${_tileManager.mapRebuildKey}'), - mapController: _controller.mapController, - options: MapOptions( - initialCenter: _gpsController.currentLocation ?? _positionManager.initialLocation ?? LatLng(37.7749, -122.4194), + AnimatedPadding( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + padding: EdgeInsets.only(bottom: widget.bottomPadding), + child: FlutterMap( + key: ValueKey('map_${appState.offlineMode}_${appState.selectedTileType?.id ?? 'none'}_${_tileManager.mapRebuildKey}'), + mapController: _controller.mapController, + options: MapOptions( + initialCenter: _gpsController.currentLocation ?? _positionManager.initialLocation ?? LatLng(37.7749, -122.4194), initialZoom: _positionManager.initialZoom ?? 15, maxZoom: 19, onPositionChanged: (pos, gesture) { @@ -315,6 +342,7 @@ class MapViewState extends State { // backgroundColor removed in flutter_map >=8 (wrap in Container if needed) ), ], + ), ), // All map overlays (mode indicator, zoom, attribution, add pin) diff --git a/lib/widgets/measured_sheet.dart b/lib/widgets/measured_sheet.dart new file mode 100644 index 0000000..1ed2bab --- /dev/null +++ b/lib/widgets/measured_sheet.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +/// Wrapper widget that measures its child's height and reports changes via callback +class MeasuredSheet extends StatefulWidget { + final Widget child; + final ValueChanged onHeightChanged; + + const MeasuredSheet({ + super.key, + required this.child, + required this.onHeightChanged, + }); + + @override + State createState() => _MeasuredSheetState(); +} + +class _MeasuredSheetState extends State { + final GlobalKey _key = GlobalKey(); + double _lastHeight = 0.0; + + @override + void initState() { + super.initState(); + // Schedule height measurement after first frame + WidgetsBinding.instance.addPostFrameCallback(_measureHeight); + } + + void _measureHeight(Duration _) { + final renderBox = _key.currentContext?.findRenderObject() as RenderBox?; + if (renderBox != null) { + final height = renderBox.size.height; + if (height != _lastHeight) { + _lastHeight = height; + widget.onHeightChanged(height); + } + } + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: (notification) { + WidgetsBinding.instance.addPostFrameCallback(_measureHeight); + return true; + }, + child: SizeChangedLayoutNotifier( + child: Container( + key: _key, + child: widget.child, + ), + ), + ); + } +} \ No newline at end of file