Files
meshcore-sar_android/lib/widgets/permission_request_dialog.dart
Janez T c50a260263 feat: Implement command queue for BLE command handling
- Added BleCommandQueue to manage command serialization and responses in BleCommandSender.
- Updated writeData, writeDataAndWaitForAck, and writeDataAndWaitForResponse methods to utilize the command queue.
- Enhanced BleResponseHandler to complete commands based on responses received from the BLE device.
- Introduced new commands for channel management, including getChannel and setChannel.
- Created LocationTrailLayer and TrailControls widgets for displaying and managing location trails on the map.
- Added PermissionRequestDialog to handle location permission requests on app startup.
- Updated LocationTrackingService to allow GPS tracking without a BLE connection.
2025-10-18 21:12:02 +02:00

211 lines
6.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
/// Dialog that requests location permissions on app startup
class PermissionRequestDialog extends StatefulWidget {
final VoidCallback onPermissionsGranted;
final VoidCallback? onPermissionsDenied;
const PermissionRequestDialog({
super.key,
required this.onPermissionsGranted,
this.onPermissionsDenied,
});
@override
State<PermissionRequestDialog> createState() => _PermissionRequestDialogState();
}
class _PermissionRequestDialogState extends State<PermissionRequestDialog> {
bool _isRequesting = false;
String? _errorMessage;
@override
void initState() {
super.initState();
// Automatically check and request permissions when dialog opens
_checkAndRequestPermissions();
}
Future<void> _checkAndRequestPermissions() async {
if (_isRequesting) return;
setState(() {
_isRequesting = true;
_errorMessage = null;
});
try {
// Check if location service is enabled
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
setState(() {
_errorMessage = 'Location services are disabled. Please enable location services in your device settings.';
_isRequesting = false;
});
return;
}
// Check current permission
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
// Request permission
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.denied) {
setState(() {
_errorMessage = 'Location permission denied. This app requires location access to track your position and share it with your team.';
_isRequesting = false;
});
widget.onPermissionsDenied?.call();
return;
}
if (permission == LocationPermission.deniedForever) {
setState(() {
_errorMessage = 'Location permission permanently denied. Please enable location access in your device settings.';
_isRequesting = false;
});
widget.onPermissionsDenied?.call();
return;
}
// Permission granted!
setState(() {
_isRequesting = false;
});
// Close dialog and notify parent
if (mounted) {
Navigator.of(context).pop();
widget.onPermissionsGranted();
}
} catch (e) {
setState(() {
_errorMessage = 'Error requesting permissions: $e';
_isRequesting = false;
});
}
}
@override
Widget build(BuildContext context) {
return PopScope(
// Prevent dismissing dialog by tapping outside
canPop: false,
child: AlertDialog(
title: Row(
children: [
Icon(
Icons.location_on,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 12),
const Text('Location Permission'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'MeshCore SAR needs access to your location to:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
_buildPermissionReason(
icon: Icons.track_changes,
text: 'Track your position during search and rescue operations',
),
const SizedBox(height: 8),
_buildPermissionReason(
icon: Icons.share_location,
text: 'Share your location with team members via mesh network',
),
const SizedBox(height: 8),
_buildPermissionReason(
icon: Icons.map,
text: 'Display your location and trail on the map',
),
if (_errorMessage != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.red.withValues(alpha: 0.3)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.error_outline, color: Colors.red, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
_errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 12),
),
),
],
),
),
],
if (_isRequesting) ...[
const SizedBox(height: 16),
const Center(
child: CircularProgressIndicator(),
),
],
],
),
actions: [
if (_errorMessage != null && !_isRequesting)
TextButton(
onPressed: () {
Navigator.of(context).pop();
widget.onPermissionsDenied?.call();
},
child: const Text('Skip'),
),
if (_errorMessage != null && !_isRequesting)
ElevatedButton(
onPressed: () async {
// Open app settings
await Geolocator.openLocationSettings();
},
child: const Text('Open Settings'),
),
if (_errorMessage != null && !_isRequesting &&
!_errorMessage!.contains('permanently denied'))
ElevatedButton(
onPressed: _checkAndRequestPermissions,
child: const Text('Retry'),
),
],
),
);
}
Widget _buildPermissionReason({
required IconData icon,
required String text,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 20, color: Colors.grey),
const SizedBox(width: 8),
Expanded(
child: Text(
text,
style: const TextStyle(fontSize: 14),
),
),
],
);
}
}