mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 00:40:28 +00:00
Add flutter_compass and shared_preferences_foundation dependencies; update Info.plist for motion usage description
This commit is contained in:
@@ -37,6 +37,8 @@ PODS:
|
|||||||
- flutter_blue_plus_darwin (0.0.2):
|
- flutter_blue_plus_darwin (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- flutter_compass (0.0.1):
|
||||||
|
- Flutter
|
||||||
- geolocator_apple (1.2.0):
|
- geolocator_apple (1.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -56,18 +58,23 @@ PODS:
|
|||||||
- SDWebImage/Core (5.21.3)
|
- SDWebImage/Core (5.21.3)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||||
|
- flutter_compass (from `.symlinks/plugins/flutter_compass/ios`)
|
||||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||||
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
|
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
@@ -84,6 +91,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_blue_plus_darwin:
|
flutter_blue_plus_darwin:
|
||||||
:path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin"
|
:path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin"
|
||||||
|
flutter_compass:
|
||||||
|
:path: ".symlinks/plugins/flutter_compass/ios"
|
||||||
geolocator_apple:
|
geolocator_apple:
|
||||||
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||||
objectbox_flutter_libs:
|
objectbox_flutter_libs:
|
||||||
@@ -96,6 +105,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
@@ -103,6 +114,7 @@ SPEC CHECKSUMS:
|
|||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||||
|
flutter_compass: b236ab69b61545cce89fd58527f401a7587d5cc1
|
||||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||||
ObjectBox: 7da4aceb5013d041bfafdbc6d744a26918b09757
|
ObjectBox: 7da4aceb5013d041bfafdbc6d744a26918b09757
|
||||||
objectbox_flutter_libs: 09b1dec1b4cd27bf1a5f9bae7ccaa7e43588bf31
|
objectbox_flutter_libs: 09b1dec1b4cd27bf1a5f9bae7ccaa7e43588bf31
|
||||||
@@ -111,6 +123,7 @@ SPEC CHECKSUMS:
|
|||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
|
|
||||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
||||||
|
|||||||
@@ -57,5 +57,7 @@
|
|||||||
<string>MeshCore SAR needs precise location for accurate positioning in SAR operations</string>
|
<string>MeshCore SAR needs precise location for accurate positioning in SAR operations</string>
|
||||||
<key>NSLocationDefaultAccuracyReduced</key>
|
<key>NSLocationDefaultAccuracyReduced</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>NSMotionUsageDescription</key>
|
||||||
|
<string>MeshCore SAR needs access to the compass to show your heading direction on the map</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class MapTab extends StatefulWidget {
|
|||||||
State<MapTab> createState() => _MapTabState();
|
State<MapTab> createState() => _MapTabState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MapTabState extends State<MapTab> {
|
class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||||
final MapController _mapController = MapController();
|
final MapController _mapController = MapController();
|
||||||
final TileCacheService _tileCache = TileCacheService();
|
final TileCacheService _tileCache = TileCacheService();
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
@@ -39,10 +39,17 @@ class _MapTabState extends State<MapTab> {
|
|||||||
StreamSubscription<Position>? _positionStreamSubscription;
|
StreamSubscription<Position>? _positionStreamSubscription;
|
||||||
StreamSubscription<CompassEvent>? _compassStreamSubscription;
|
StreamSubscription<CompassEvent>? _compassStreamSubscription;
|
||||||
|
|
||||||
|
// Saved map position (loaded from SharedPreferences)
|
||||||
|
LatLng? _savedMapCenter;
|
||||||
|
double? _savedMapZoom;
|
||||||
|
|
||||||
// Default center point (will be updated based on markers)
|
// Default center point (will be updated based on markers)
|
||||||
static const LatLng _defaultCenter = LatLng(46.0569, 14.5058); // Ljubljana, Slovenia
|
static const LatLng _defaultCenter = LatLng(46.0569, 14.5058); // Ljubljana, Slovenia
|
||||||
static const double _defaultZoom = 13.0;
|
static const double _defaultZoom = 13.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -59,35 +66,52 @@ class _MapTabState extends State<MapTab> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _startCompassTracking() {
|
void _startCompassTracking() {
|
||||||
// Start listening to compass events
|
final compassStream = FlutterCompass.events;
|
||||||
_compassStreamSubscription = FlutterCompass.events?.listen((CompassEvent event) {
|
if (compassStream == null) {
|
||||||
if (mounted && event.heading != null) {
|
return;
|
||||||
setState(() {
|
}
|
||||||
_compassHeading = event.heading;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rotate map if rotation mode is enabled and we have compass heading
|
// Start listening to compass events
|
||||||
if (_rotateMarkerWithHeading && event.heading != null) {
|
_compassStreamSubscription = compassStream.listen(
|
||||||
debugPrint('Rotating map to compass heading: ${event.heading}');
|
(CompassEvent event) {
|
||||||
// Use rotateAroundPoint to set absolute rotation
|
if (mounted && event.heading != null) {
|
||||||
final camera = _mapController.camera;
|
setState(() {
|
||||||
_mapController.moveAndRotate(
|
_compassHeading = event.heading;
|
||||||
camera.center,
|
});
|
||||||
camera.zoom,
|
|
||||||
-event.heading!,
|
// Rotate map if rotation mode is enabled and we have compass heading
|
||||||
);
|
if (_rotateMarkerWithHeading && event.heading != null) {
|
||||||
|
// Use moveAndRotate to set absolute rotation
|
||||||
|
final camera = _mapController.camera;
|
||||||
|
_mapController.moveAndRotate(
|
||||||
|
camera.center,
|
||||||
|
camera.zoom,
|
||||||
|
-event.heading!,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
// Load last map position if available
|
||||||
|
final lastLat = prefs.getDouble('map_last_latitude');
|
||||||
|
final lastLon = prefs.getDouble('map_last_longitude');
|
||||||
|
final lastZoom = prefs.getDouble('map_last_zoom');
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_showLegend = prefs.getBool('map_show_legend') ?? false;
|
_showLegend = prefs.getBool('map_show_legend') ?? false;
|
||||||
_rotateMarkerWithHeading = prefs.getBool('map_rotate_with_heading') ?? false;
|
_rotateMarkerWithHeading = prefs.getBool('map_rotate_with_heading') ?? false;
|
||||||
_gpsUpdateDistance = prefs.getDouble('map_gps_update_distance') ?? 3.0;
|
_gpsUpdateDistance = prefs.getDouble('map_gps_update_distance') ?? 3.0;
|
||||||
|
|
||||||
|
// Store saved position for use in build
|
||||||
|
if (lastLat != null && lastLon != null && lastZoom != null) {
|
||||||
|
_savedMapCenter = LatLng(lastLat, lastLon);
|
||||||
|
_savedMapZoom = lastZoom;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +123,14 @@ class _MapTabState extends State<MapTab> {
|
|||||||
await prefs.setDouble('map_gps_update_distance', _gpsUpdateDistance);
|
await prefs.setDouble('map_gps_update_distance', _gpsUpdateDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _saveMapPosition() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final camera = _mapController.camera;
|
||||||
|
await prefs.setDouble('map_last_latitude', camera.center.latitude);
|
||||||
|
await prefs.setDouble('map_last_longitude', camera.center.longitude);
|
||||||
|
await prefs.setDouble('map_last_zoom', camera.zoom);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _requestLocationPermission() async {
|
Future<void> _requestLocationPermission() async {
|
||||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
if (!serviceEnabled) {
|
if (!serviceEnabled) {
|
||||||
@@ -142,8 +174,6 @@ class _MapTabState extends State<MapTab> {
|
|||||||
),
|
),
|
||||||
).listen((Position position) {
|
).listen((Position position) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
debugPrint('Position update - Heading: ${position.heading}, Speed: ${position.speed}');
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentPosition = position;
|
_currentPosition = position;
|
||||||
});
|
});
|
||||||
@@ -151,7 +181,6 @@ class _MapTabState extends State<MapTab> {
|
|||||||
// Rotate map if rotation mode is enabled and heading is available
|
// Rotate map if rotation mode is enabled and heading is available
|
||||||
// Heading of -1.0 means heading is unavailable
|
// Heading of -1.0 means heading is unavailable
|
||||||
if (_rotateMarkerWithHeading && position.heading != null && position.heading >= 0) {
|
if (_rotateMarkerWithHeading && position.heading != null && position.heading >= 0) {
|
||||||
debugPrint('Rotating map to heading: ${position.heading}');
|
|
||||||
final camera = _mapController.camera;
|
final camera = _mapController.camera;
|
||||||
_mapController.moveAndRotate(
|
_mapController.moveAndRotate(
|
||||||
camera.center,
|
camera.center,
|
||||||
@@ -195,6 +224,9 @@ class _MapTabState extends State<MapTab> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// Save map position before disposing
|
||||||
|
_saveMapPosition();
|
||||||
|
|
||||||
final mapProvider = context.read<MapProvider>();
|
final mapProvider = context.read<MapProvider>();
|
||||||
mapProvider.removeListener(_handleMapNavigation);
|
mapProvider.removeListener(_handleMapNavigation);
|
||||||
_positionStreamSubscription?.cancel();
|
_positionStreamSubscription?.cancel();
|
||||||
@@ -441,8 +473,8 @@ class _MapTabState extends State<MapTab> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _DetailedCompassDialog(
|
builder: (context) => _DetailedCompassDialog(
|
||||||
currentPosition: _currentPosition,
|
initialPosition: _currentPosition,
|
||||||
currentHeading: _currentHeading,
|
initialHeading: _currentHeading,
|
||||||
contacts: contacts,
|
contacts: contacts,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -480,6 +512,7 @@ class _MapTabState extends State<MapTab> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context); // Required for AutomaticKeepAliveClientMixin
|
||||||
return Consumer2<ContactsProvider, MessagesProvider>(
|
return Consumer2<ContactsProvider, MessagesProvider>(
|
||||||
builder: (context, contactsProvider, messagesProvider, child) {
|
builder: (context, contactsProvider, messagesProvider, child) {
|
||||||
final contactsWithLocation = contactsProvider.chatContactsWithLocation;
|
final contactsWithLocation = contactsProvider.chatContactsWithLocation;
|
||||||
@@ -493,13 +526,20 @@ class _MapTabState extends State<MapTab> {
|
|||||||
? FlutterMap(
|
? FlutterMap(
|
||||||
mapController: _mapController,
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
initialCenter: center,
|
// Use saved position if available, otherwise use calculated center
|
||||||
initialZoom: _defaultZoom,
|
initialCenter: _savedMapCenter ?? center,
|
||||||
|
initialZoom: _savedMapZoom ?? _defaultZoom,
|
||||||
minZoom: 5,
|
minZoom: 5,
|
||||||
maxZoom: 18,
|
maxZoom: 18,
|
||||||
interactionOptions: const InteractionOptions(
|
interactionOptions: const InteractionOptions(
|
||||||
flags: InteractiveFlag.all,
|
flags: InteractiveFlag.all,
|
||||||
),
|
),
|
||||||
|
onMapEvent: (event) {
|
||||||
|
// Save map position when user stops panning/zooming
|
||||||
|
if (event is MapEventMoveEnd || event is MapEventScrollWheelZoom) {
|
||||||
|
_saveMapPosition();
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
TileLayer(
|
||||||
@@ -875,19 +915,80 @@ class _CompassRosePainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Detailed Compass Dialog
|
// Detailed Compass Dialog
|
||||||
class _DetailedCompassDialog extends StatelessWidget {
|
class _DetailedCompassDialog extends StatefulWidget {
|
||||||
final Position? currentPosition;
|
final Position? initialPosition;
|
||||||
final double? currentHeading;
|
final double? initialHeading;
|
||||||
final List<Contact> contacts;
|
final List<Contact> contacts;
|
||||||
|
|
||||||
const _DetailedCompassDialog({
|
const _DetailedCompassDialog({
|
||||||
required this.currentPosition,
|
required this.initialPosition,
|
||||||
required this.currentHeading,
|
required this.initialHeading,
|
||||||
required this.contacts,
|
required this.contacts,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_DetailedCompassDialog> createState() => _DetailedCompassDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetailedCompassDialogState extends State<_DetailedCompassDialog> {
|
||||||
|
double? _currentHeading;
|
||||||
|
Position? _currentPosition;
|
||||||
|
StreamSubscription<CompassEvent>? _compassSubscription;
|
||||||
|
StreamSubscription<Position>? _positionSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentHeading = widget.initialHeading;
|
||||||
|
_currentPosition = widget.initialPosition;
|
||||||
|
|
||||||
|
// Subscribe to compass updates
|
||||||
|
final compassStream = FlutterCompass.events;
|
||||||
|
if (compassStream != null) {
|
||||||
|
_compassSubscription = compassStream.listen((event) {
|
||||||
|
if (mounted && event.heading != null) {
|
||||||
|
setState(() {
|
||||||
|
_currentHeading = event.heading;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to position updates
|
||||||
|
_positionSubscription = Geolocator.getPositionStream(
|
||||||
|
locationSettings: const LocationSettings(
|
||||||
|
accuracy: LocationAccuracy.best,
|
||||||
|
distanceFilter: 1,
|
||||||
|
),
|
||||||
|
).listen((position) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_currentPosition = position;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_compassSubscription?.cancel();
|
||||||
|
_positionSubscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current heading (prefer compass over GPS)
|
||||||
|
double? get currentHeading {
|
||||||
|
if (_currentHeading != null) return _currentHeading;
|
||||||
|
if (_currentPosition?.heading != null && _currentPosition!.heading >= 0) {
|
||||||
|
return _currentPosition!.heading;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final heading = currentHeading;
|
||||||
|
final position = _currentPosition;
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -917,51 +1018,51 @@ class _DetailedCompassDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Heading and Elevation info
|
// Heading and Elevation info
|
||||||
_buildInfoRow(context),
|
_buildInfoRow(context, heading, position),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// Large compass
|
// Large compass
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
child: _DetailedCompassPainter(
|
child: _DetailedCompassPainter(
|
||||||
heading: currentHeading ?? 0,
|
heading: heading ?? 0,
|
||||||
hasHeading: currentHeading != null,
|
hasHeading: heading != null,
|
||||||
currentPosition: currentPosition,
|
currentPosition: position,
|
||||||
contacts: contacts,
|
contacts: widget.contacts,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Contacts list
|
// Contacts list
|
||||||
if (contacts.isNotEmpty) _buildContactsList(context),
|
if (widget.contacts.isNotEmpty) _buildContactsList(context, heading, position),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInfoRow(BuildContext context) {
|
Widget _buildInfoRow(BuildContext context, double? heading, Position? position) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoCard(
|
_buildInfoCard(
|
||||||
context,
|
context,
|
||||||
'Heading',
|
'Heading',
|
||||||
currentHeading != null ? '${currentHeading!.round()}°' : '--',
|
heading != null ? '${heading.round()}°' : '--',
|
||||||
Icons.explore,
|
Icons.explore,
|
||||||
),
|
),
|
||||||
_buildInfoCard(
|
_buildInfoCard(
|
||||||
context,
|
context,
|
||||||
'Elevation',
|
'Elevation',
|
||||||
currentPosition?.altitude != null
|
position?.altitude != null
|
||||||
? '${currentPosition!.altitude.round()}m'
|
? '${position!.altitude.round()}m'
|
||||||
: '--',
|
: '--',
|
||||||
Icons.terrain,
|
Icons.terrain,
|
||||||
),
|
),
|
||||||
_buildInfoCard(
|
_buildInfoCard(
|
||||||
context,
|
context,
|
||||||
'Accuracy',
|
'Accuracy',
|
||||||
currentPosition?.accuracy != null
|
position?.accuracy != null
|
||||||
? '±${currentPosition!.accuracy.round()}m'
|
? '±${position!.accuracy.round()}m'
|
||||||
: '--',
|
: '--',
|
||||||
Icons.gps_fixed,
|
Icons.gps_fixed,
|
||||||
),
|
),
|
||||||
@@ -989,25 +1090,25 @@ class _DetailedCompassDialog extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContactsList(BuildContext context) {
|
Widget _buildContactsList(BuildContext context, double? heading, Position? position) {
|
||||||
if (currentPosition == null) {
|
if (position == null) {
|
||||||
return const Text('Location unavailable');
|
return const Text('Location unavailable');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate bearings and distances
|
// Calculate bearings and distances
|
||||||
final contactsWithBearing = contacts.map((contact) {
|
final contactsWithBearing = widget.contacts.map((contact) {
|
||||||
if (contact.displayLocation == null) return null;
|
if (contact.displayLocation == null) return null;
|
||||||
|
|
||||||
final bearing = _calculateBearing(
|
final bearing = _calculateBearing(
|
||||||
currentPosition!.latitude,
|
position.latitude,
|
||||||
currentPosition!.longitude,
|
position.longitude,
|
||||||
contact.displayLocation!.latitude,
|
contact.displayLocation!.latitude,
|
||||||
contact.displayLocation!.longitude,
|
contact.displayLocation!.longitude,
|
||||||
);
|
);
|
||||||
|
|
||||||
final distance = _calculateDistance(
|
final distance = _calculateDistance(
|
||||||
currentPosition!.latitude,
|
position.latitude,
|
||||||
currentPosition!.longitude,
|
position.longitude,
|
||||||
contact.displayLocation!.latitude,
|
contact.displayLocation!.latitude,
|
||||||
contact.displayLocation!.longitude,
|
contact.displayLocation!.longitude,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user