From 2d0dc7fd6657c3d15b16a29851f564d6a924835d Mon Sep 17 00:00:00 2001 From: stopflock Date: Tue, 26 Aug 2025 23:46:38 -0500 Subject: [PATCH] ternary follow me --- lib/screens/home_screen.dart | 59 +++++++++++++++++++++++++++++++----- lib/widgets/map_view.dart | 37 ++++++++++++++-------- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 186b732..6424137 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -10,6 +10,12 @@ import '../widgets/add_camera_sheet.dart'; import '../widgets/camera_provider_with_cache.dart'; import '../widgets/download_area_dialog.dart'; +enum FollowMeMode { + off, // No following + northUp, // Follow position, keep north up + rotating, // Follow position and rotation +} + class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -21,11 +27,44 @@ class _HomeScreenState extends State { final GlobalKey _scaffoldKey = GlobalKey(); final GlobalKey _mapViewKey = GlobalKey(); final MapController _mapController = MapController(); - bool _followMe = true; + FollowMeMode _followMeMode = FollowMeMode.northUp; + + String _getFollowMeTooltip() { + switch (_followMeMode) { + case FollowMeMode.off: + return 'Enable follow-me (north up)'; + case FollowMeMode.northUp: + return 'Enable follow-me (rotating)'; + case FollowMeMode.rotating: + return 'Disable follow-me'; + } + } + + IconData _getFollowMeIcon() { + switch (_followMeMode) { + case FollowMeMode.off: + return Icons.gps_off; + case FollowMeMode.northUp: + return Icons.gps_fixed; + case FollowMeMode.rotating: + return Icons.navigation; + } + } + + FollowMeMode _getNextFollowMeMode() { + switch (_followMeMode) { + case FollowMeMode.off: + return FollowMeMode.northUp; + case FollowMeMode.northUp: + return FollowMeMode.rotating; + case FollowMeMode.rotating: + return FollowMeMode.off; + } + } void _openAddCameraSheet() { // Disable follow-me when adding a camera so the map doesn't jump around - setState(() => _followMe = false); + setState(() => _followMeMode = FollowMeMode.off); final appState = context.read(); appState.startAddSession(); @@ -50,12 +89,14 @@ class _HomeScreenState extends State { title: const Text('Flock Map'), actions: [ IconButton( - tooltip: _followMe ? 'Disable follow‑me' : 'Enable follow‑me', - icon: Icon(_followMe ? Icons.gps_fixed : Icons.gps_off), + tooltip: _getFollowMeTooltip(), + icon: Icon(_getFollowMeIcon()), onPressed: () { - setState(() => _followMe = !_followMe); + setState(() { + _followMeMode = _getNextFollowMeMode(); + }); // If enabling follow-me, retry location init in case permission was granted - if (_followMe) { + if (_followMeMode != FollowMeMode.off) { _mapViewKey.currentState?.retryLocationInit(); } }, @@ -71,9 +112,11 @@ class _HomeScreenState extends State { MapView( key: _mapViewKey, controller: _mapController, - followMe: _followMe, + followMeMode: _followMeMode, onUserGesture: () { - if (_followMe) setState(() => _followMe = false); + if (_followMeMode != FollowMeMode.off) { + setState(() => _followMeMode = FollowMeMode.off); + } }, ), Align( diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index 83911b1..64c47c4 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -22,16 +22,18 @@ import 'map/map_overlays.dart'; import 'network_status_indicator.dart'; import '../dev_config.dart'; +import '../screens/home_screen.dart' show FollowMeMode; + class MapView extends StatefulWidget { final MapController controller; const MapView({ super.key, required this.controller, - required this.followMe, + required this.followMeMode, required this.onUserGesture, }); - final bool followMe; + final FollowMeMode followMeMode; final VoidCallback onUserGesture; @override @@ -135,12 +137,16 @@ class MapViewState extends State { @override void didUpdateWidget(covariant MapView oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.followMe && !oldWidget.followMe && _currentLatLng != null) { + if (widget.followMeMode != FollowMeMode.off && + oldWidget.followMeMode == FollowMeMode.off && + _currentLatLng != null) { // Use smooth animation when follow me is first enabled - _controller.animatedMove( - _currentLatLng!, - _controller.camera.zoom, - ); + if (widget.followMeMode == FollowMeMode.northUp) { + _controller.move(_currentLatLng!, _controller.camera.zoom); + } else if (widget.followMeMode == FollowMeMode.rotating) { + // When switching to rotating mode, reset to north-up first, then let GPS handle rotation + _controller.moveAndRotate(_currentLatLng!, _controller.camera.zoom, 0.0); + } } } @@ -160,15 +166,20 @@ class MapViewState extends State { Geolocator.getPositionStream(locationSettings: locationSettings).listen((Position position) { final latLng = LatLng(position.latitude, position.longitude); setState(() => _currentLatLng = latLng); - if (widget.followMe) { + + if (widget.followMeMode != FollowMeMode.off) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { try { - // Use smooth animation instead of instant jump - _controller.animatedMove( - latLng, - _controller.camera.zoom, - ); + if (widget.followMeMode == FollowMeMode.northUp) { + // Follow position only, keep current rotation + _controller.move(latLng, _controller.camera.zoom); + } else if (widget.followMeMode == FollowMeMode.rotating) { + // Follow position and rotation based on heading + final heading = position.heading; + final rotation = heading.isNaN ? 0.0 : -heading; // Convert to map rotation + _controller.moveAndRotate(latLng, _controller.camera.zoom, rotation); + } } catch (e) { debugPrint('MapController not ready yet: $e'); }