From 8ed92dcd7e51b941584808f3cd05b4af1ce9b829 Mon Sep 17 00:00:00 2001 From: stopflock Date: Wed, 19 Nov 2025 13:32:40 -0600 Subject: [PATCH] Home screen respect safe areas in all orientations --- lib/dev_config.dart | 13 +++ lib/screens/home_screen.dart | 123 +++++++++++++++-------------- lib/widgets/compass_indicator.dart | 8 +- lib/widgets/map/map_overlays.dart | 25 +++--- lib/widgets/map_view.dart | 27 ++++--- 5 files changed, 113 insertions(+), 83 deletions(-) diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 82ed519..3d8582d 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -34,7 +34,20 @@ double bottomPositionFromButtonBar(double spacingAboveButtonBar, double safeArea return safeAreaBottom + kBottomButtonBarOffset + kButtonBarHeight + spacingAboveButtonBar; } +// Helper to get left positioning that accounts for safe area (for landscape mode) +double leftPositionWithSafeArea(double baseLeft, EdgeInsets safeArea) { + return baseLeft + safeArea.left; +} +// Helper to get right positioning that accounts for safe area (for landscape mode) +double rightPositionWithSafeArea(double baseRight, EdgeInsets safeArea) { + return baseRight + safeArea.right; +} + +// Helper to get top positioning that accounts for safe area +double topPositionWithSafeArea(double baseTop, EdgeInsets safeArea) { + return baseTop + safeArea.top; +} // Client name for OSM uploads ("created_by" tag) const String kClientName = 'DeFlock'; diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 5d6f3cc..b81fde6 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -708,71 +708,76 @@ class _HomeScreenState extends State with TickerProviderStateMixin { // Bottom button bar (restored to original) Align( alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom + kBottomButtonBarOffset, - left: 8, - right: 8, - ), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600), // Match typical sheet width - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.3), - blurRadius: 10, - offset: Offset(0, -2), - ) - ], + child: Builder( + builder: (context) { + final safeArea = MediaQuery.of(context).padding; + return Padding( + padding: EdgeInsets.only( + bottom: safeArea.bottom + kBottomButtonBarOffset, + left: leftPositionWithSafeArea(8, safeArea), + right: rightPositionWithSafeArea(8, safeArea), ), - margin: EdgeInsets.only(bottom: kBottomButtonBarOffset), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - child: Row( - children: [ - Expanded( - flex: 7, // 70% for primary action - child: AnimatedBuilder( - animation: LocalizationService.instance, - builder: (context, child) => ElevatedButton.icon( - icon: Icon(Icons.add_location_alt), - label: Text(LocalizationService.instance.tagNode), - onPressed: _openAddNodeSheet, - style: ElevatedButton.styleFrom( - minimumSize: Size(0, 48), - textStyle: TextStyle(fontSize: 16), - ), - ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), // Match typical sheet width + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Theme.of(context).shadowColor.withOpacity(0.3), + blurRadius: 10, + offset: Offset(0, -2), + ) + ], ), - ), - SizedBox(width: 12), - Expanded( - flex: 3, // 30% for secondary action - child: AnimatedBuilder( - animation: LocalizationService.instance, - builder: (context, child) => FittedBox( - fit: BoxFit.scaleDown, - child: ElevatedButton.icon( - icon: Icon(Icons.download_for_offline), - label: Text(LocalizationService.instance.download), - onPressed: () => showDialog( - context: context, - builder: (ctx) => DownloadAreaDialog(controller: _mapController.mapController), - ), - style: ElevatedButton.styleFrom( - minimumSize: Size(0, 48), - textStyle: TextStyle(fontSize: 16), + margin: EdgeInsets.only(bottom: kBottomButtonBarOffset), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + child: Row( + children: [ + Expanded( + flex: 7, // 70% for primary action + child: AnimatedBuilder( + animation: LocalizationService.instance, + builder: (context, child) => ElevatedButton.icon( + icon: Icon(Icons.add_location_alt), + label: Text(LocalizationService.instance.tagNode), + onPressed: _openAddNodeSheet, + style: ElevatedButton.styleFrom( + minimumSize: Size(0, 48), + textStyle: TextStyle(fontSize: 16), + ), + ), ), ), - ), + SizedBox(width: 12), + Expanded( + flex: 3, // 30% for secondary action + child: AnimatedBuilder( + animation: LocalizationService.instance, + builder: (context, child) => FittedBox( + fit: BoxFit.scaleDown, + child: ElevatedButton.icon( + icon: Icon(Icons.download_for_offline), + label: Text(LocalizationService.instance.download), + onPressed: () => showDialog( + context: context, + builder: (ctx) => DownloadAreaDialog(controller: _mapController.mapController), + ), + style: ElevatedButton.styleFrom( + minimumSize: Size(0, 48), + textStyle: TextStyle(fontSize: 16), + ), + ), + ), + ), + ), + ], ), ), - ], - ), - ), - ), + ), + ); + }, ), ), ], diff --git a/lib/widgets/compass_indicator.dart b/lib/widgets/compass_indicator.dart index 518aa6d..a5a38fb 100644 --- a/lib/widgets/compass_indicator.dart +++ b/lib/widgets/compass_indicator.dart @@ -11,10 +11,12 @@ import '../app_state.dart'; /// The compass appears in the top-right corner of the map and is disabled (non-interactive) when in follow+rotate mode. class CompassIndicator extends StatefulWidget { final AnimatedMapController mapController; + final EdgeInsets safeArea; const CompassIndicator({ super.key, required this.mapController, + required this.safeArea, }); @override @@ -46,9 +48,11 @@ class _CompassIndicatorState extends State { // Check if we're in follow+rotate mode (compass should be disabled) final isDisabled = appState.followMeMode == FollowMeMode.rotating; + final baseTop = (appState.uploadMode == UploadMode.sandbox || appState.uploadMode == UploadMode.simulate) ? 60 : 18; + return Positioned( - top: (appState.uploadMode == UploadMode.sandbox || appState.uploadMode == UploadMode.simulate) ? 60 : 18, - right: 16, + top: baseTop + widget.safeArea.top, + right: 16 + widget.safeArea.right, child: GestureDetector( onTap: isDisabled ? null : () { // Animate to north-up orientation diff --git a/lib/widgets/map/map_overlays.dart b/lib/widgets/map/map_overlays.dart index 9b8a676..9ca409e 100644 --- a/lib/widgets/map/map_overlays.dart +++ b/lib/widgets/map/map_overlays.dart @@ -51,13 +51,15 @@ class MapOverlays extends StatelessWidget { @override Widget build(BuildContext context) { + final safeArea = MediaQuery.of(context).padding; + return Stack( children: [ // MODE INDICATOR badge (top-right) if (uploadMode == UploadMode.sandbox || uploadMode == UploadMode.simulate) Positioned( - top: 18, - right: 14, + top: topPositionWithSafeArea(18, safeArea), + right: rightPositionWithSafeArea(14, safeArea), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( @@ -86,12 +88,13 @@ class MapOverlays extends StatelessWidget { // Compass indicator (top-right, below mode indicator) CompassIndicator( mapController: mapController, + safeArea: safeArea, ), - // Zoom indicator, positioned relative to button bar + // Zoom indicator, positioned relative to button bar with left safe area Positioned( - left: 10, - bottom: bottomPositionFromButtonBar(kZoomIndicatorSpacingAboveButtonBar, MediaQuery.of(context).padding.bottom), + left: leftPositionWithSafeArea(10, safeArea), + bottom: bottomPositionFromButtonBar(kZoomIndicatorSpacingAboveButtonBar, safeArea.bottom), child: Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( @@ -119,11 +122,11 @@ class MapOverlays extends StatelessWidget { ), ), - // Attribution overlay, positioned relative to button bar + // Attribution overlay, positioned relative to button bar with left safe area if (attribution != null) Positioned( - bottom: bottomPositionFromButtonBar(kAttributionSpacingAboveButtonBar, MediaQuery.of(context).padding.bottom), - left: 10, + bottom: bottomPositionFromButtonBar(kAttributionSpacingAboveButtonBar, safeArea.bottom), + left: leftPositionWithSafeArea(10, safeArea), child: GestureDetector( onTap: () => _showAttributionDialog(context, attribution!), child: Container( @@ -146,10 +149,10 @@ class MapOverlays extends StatelessWidget { ), ), - // Zoom and layer controls (bottom-right), positioned relative to button bar + // Zoom and layer controls (bottom-right), positioned relative to button bar with right safe area Positioned( - bottom: bottomPositionFromButtonBar(kZoomControlsSpacingAboveButtonBar, MediaQuery.of(context).padding.bottom), - right: 16, + bottom: bottomPositionFromButtonBar(kZoomControlsSpacingAboveButtonBar, safeArea.bottom), + right: rightPositionWithSafeArea(16, safeArea), child: Consumer( builder: (context, appState, child) { return Column( diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index 1c79586..86a70c3 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -661,17 +661,22 @@ class MapViewState extends State { selectedTileType: appState.selectedTileType, ), cameraLayers, - // Built-in scale bar from flutter_map, positioned relative to button bar - Scalebar( - alignment: Alignment.bottomLeft, - padding: EdgeInsets.only( - left: 8, - bottom: bottomPositionFromButtonBar(kScaleBarSpacingAboveButtonBar, MediaQuery.of(context).padding.bottom) - ), - textStyle: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), - lineColor: Colors.black, - strokeWidth: 3, - // backgroundColor removed in flutter_map >=8 (wrap in Container if needed) + // Built-in scale bar from flutter_map, positioned relative to button bar with safe area + Builder( + builder: (context) { + final safeArea = MediaQuery.of(context).padding; + return Scalebar( + alignment: Alignment.bottomLeft, + padding: EdgeInsets.only( + left: leftPositionWithSafeArea(8, safeArea), + bottom: bottomPositionFromButtonBar(kScaleBarSpacingAboveButtonBar, safeArea.bottom) + ), + textStyle: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), + lineColor: Colors.black, + strokeWidth: 3, + // backgroundColor removed in flutter_map >=8 (wrap in Container if needed) + ); + }, ), ], ),