fix: permission screen - location tap opens settings when permanently denied, battery dialog re-checks on done

This commit is contained in:
fiatcode 2026-04-30 16:21:07 +07:00
parent 650a6efeca
commit 6cbb7a2070
No known key found for this signature in database
3 changed files with 83 additions and 38 deletions

View file

@ -104,4 +104,16 @@ class LocationBridge {
return false;
}
}
static Future<bool> isBatteryOptimizationDisabled() async {
try {
final result = await _methodChannel.invokeMethod<bool>(
'isBatteryOptimizationDisabled',
);
return result ?? false;
} on PlatformException catch (e) {
debugPrint('Failed to check battery optimization: ${e.message}');
return false;
}
}
}

View file

@ -32,7 +32,6 @@ class _PermissionScreenState extends State<PermissionScreen> {
Future<void> _checkPermissions() async {
final location = await Permission.locationAlways.status;
final notification = await Permission.notification.status;
// Battery optimization check is Android-only
final batteryOptOut = await _isBatteryOptimizationDisabled();
if (mounted) {
@ -45,10 +44,12 @@ class _PermissionScreenState extends State<PermissionScreen> {
}
Future<bool> _isBatteryOptimizationDisabled() async {
// On Android, check if battery optimization is disabled for the app
// We approximate this - in production you'd use a native method
// For now, assume not granted until user says so
return false;
try {
final result = await LocationBridge.isBatteryOptimizationDisabled();
return result == true;
} catch (_) {
return false;
}
}
bool get _allGranted =>
@ -56,32 +57,39 @@ class _PermissionScreenState extends State<PermissionScreen> {
Future<void> _requestLocation() async {
final status = await Permission.locationAlways.request();
if (!status.isGranted && status.isPermanentlyDenied) {
// Permission permanently denied - open app settings
await openAppSettings();
}
if (mounted) {
setState(() => _locationGranted = status.isGranted);
final newStatus = await Permission.locationAlways.status;
setState(() => _locationGranted = newStatus.isGranted);
}
}
Future<void> _requestNotification() async {
final status = await Permission.notification.request();
if (!status.isGranted && status.isPermanentlyDenied) {
await openAppSettings();
}
if (mounted) {
setState(() => _notificationGranted = status.isGranted);
final newStatus = await Permission.notification.status;
setState(() => _notificationGranted = newStatus.isGranted);
}
}
Future<void> _requestBatteryOptOut() async {
final success = await LocationBridge.openBatteryOptimizationSettings();
if (success) {
// Show dialog asking user to confirm after disabling battery opt
if (mounted) _showBatteryOptDialog();
} else {
// Fallback to manual instructions dialog
if (mounted) _showBatteryOptDialog();
final opened = await LocationBridge.openBatteryOptimizationSettings();
if (mounted) {
// Show dialog after the settings screen opens
_showBatteryOptDialog(opened);
}
}
void _showBatteryOptDialog() {
void _showBatteryOptDialog(bool settingsOpened) {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF1a1a1a),
shape: RoundedRectangleBorder(
@ -98,10 +106,12 @@ class _PermissionScreenState extends State<PermissionScreen> {
color: Color(0xFFe0e0e0),
),
),
content: const Text(
'Please disable battery optimization for this app to ensure reliable background tracking.\n\n'
'In the next screen, find "Traccar Client" and set it to "Don\'t optimize" or "Unrestricted".',
style: TextStyle(
content: Text(
settingsOpened
? 'A settings screen has opened.\n\n'
'Find "Traccar Client" and set it to "Don\'t optimize" or "Unrestricted", then come back and tap DONE below.'
: 'Open Settings → Apps → Traccar Client → Battery and select "Don\'t optimize" or "Unrestricted", then come back and tap DONE below.',
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: Color(0xFF9e9e9e),
@ -109,9 +119,13 @@ class _PermissionScreenState extends State<PermissionScreen> {
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
onPressed: () {
Navigator.pop(ctx);
// Open app details settings as fallback
openAppSettings();
},
child: const Text(
'OPEN SETTINGS',
'OPEN APP SETTINGS',
style: TextStyle(
fontFamily: 'monospace',
fontSize: 11,
@ -122,12 +136,16 @@ class _PermissionScreenState extends State<PermissionScreen> {
),
),
TextButton(
onPressed: () {
onPressed: () async {
Navigator.pop(ctx);
setState(() => _batteryOptOut = true);
// Re-check battery optimization state via native
final disabled = await _isBatteryOptimizationDisabled();
if (mounted) {
setState(() => _batteryOptOut = disabled);
}
},
child: const Text(
'DONE',
'CHECK & DONE',
style: TextStyle(
fontFamily: 'monospace',
fontSize: 11,
@ -213,11 +231,11 @@ class _PermissionScreenState extends State<PermissionScreen> {
color: Color(0xFF161616),
border: Border(bottom: BorderSide(color: Color(0xFF2a2a2a), width: 1)),
),
child: Row(
child: const Row(
children: [
const Icon(Icons.shield, color: Color(0xFF00bcd4), size: 20),
const SizedBox(width: 12),
const Text(
Icon(Icons.shield, color: Color(0xFF00bcd4), size: 20),
SizedBox(width: 12),
Text(
'PERMISSIONS',
style: TextStyle(
fontFamily: 'monospace',
@ -245,18 +263,18 @@ class _PermissionScreenState extends State<PermissionScreen> {
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(0xFF00bcd4).withOpacity(0.15),
borderRadius: BorderRadius.circular(4),
decoration: const BoxDecoration(
color: Color(0xFF00bcd4),
borderRadius: BorderRadius.all(Radius.circular(4)),
),
child: const Icon(
Icons.info_outline,
color: Color(0xFF00bcd4),
color: Colors.white,
size: 20,
),
),
const SizedBox(width: 14),
const Expanded(
SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -420,7 +438,7 @@ class _PermissionScreenState extends State<PermissionScreen> {
_notificationGranted,
_batteryOptOut,
].where((x) => x).length;
final total = 3;
const total = 3;
final progress = granted / total;
return Column(
@ -428,9 +446,11 @@ class _PermissionScreenState extends State<PermissionScreen> {
children: [
Row(
children: [
const Icon(
Icons.check_circle_outline,
color: Color(0xFF00e676),
Icon(
granted == total
? Icons.check_circle
: Icons.check_circle_outline,
color: const Color(0xFF00e676),
size: 14,
),
const SizedBox(width: 8),