fix: permission screen - location tap opens settings when permanently denied, battery dialog re-checks on done
This commit is contained in:
parent
650a6efeca
commit
6cbb7a2070
3 changed files with 83 additions and 38 deletions
|
|
@ -90,6 +90,9 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||||
"openBatteryOptimizationSettings" -> {
|
"openBatteryOptimizationSettings" -> {
|
||||||
openBatteryOptimizationSettings(result)
|
openBatteryOptimizationSettings(result)
|
||||||
}
|
}
|
||||||
|
"isBatteryOptimizationDisabled" -> {
|
||||||
|
result.success(isBatteryOptimizationDisabled())
|
||||||
|
}
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -215,4 +218,14 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isBatteryOptimizationDisabled(): Boolean {
|
||||||
|
return try {
|
||||||
|
val powerManager = context?.getSystemService(android.content.Context.POWER_SERVICE) as? android.os.PowerManager
|
||||||
|
val packageName = context?.packageName ?: ""
|
||||||
|
powerManager?.isIgnoringBatteryOptimizations(packageName) == true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,4 +104,16 @@ class LocationBridge {
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
Future<void> _checkPermissions() async {
|
Future<void> _checkPermissions() async {
|
||||||
final location = await Permission.locationAlways.status;
|
final location = await Permission.locationAlways.status;
|
||||||
final notification = await Permission.notification.status;
|
final notification = await Permission.notification.status;
|
||||||
// Battery optimization check is Android-only
|
|
||||||
final batteryOptOut = await _isBatteryOptimizationDisabled();
|
final batteryOptOut = await _isBatteryOptimizationDisabled();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -45,10 +44,12 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _isBatteryOptimizationDisabled() async {
|
Future<bool> _isBatteryOptimizationDisabled() async {
|
||||||
// On Android, check if battery optimization is disabled for the app
|
try {
|
||||||
// We approximate this - in production you'd use a native method
|
final result = await LocationBridge.isBatteryOptimizationDisabled();
|
||||||
// For now, assume not granted until user says so
|
return result == true;
|
||||||
return false;
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _allGranted =>
|
bool get _allGranted =>
|
||||||
|
|
@ -56,32 +57,39 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
|
|
||||||
Future<void> _requestLocation() async {
|
Future<void> _requestLocation() async {
|
||||||
final status = await Permission.locationAlways.request();
|
final status = await Permission.locationAlways.request();
|
||||||
|
if (!status.isGranted && status.isPermanentlyDenied) {
|
||||||
|
// Permission permanently denied - open app settings
|
||||||
|
await openAppSettings();
|
||||||
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _locationGranted = status.isGranted);
|
final newStatus = await Permission.locationAlways.status;
|
||||||
|
setState(() => _locationGranted = newStatus.isGranted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _requestNotification() async {
|
Future<void> _requestNotification() async {
|
||||||
final status = await Permission.notification.request();
|
final status = await Permission.notification.request();
|
||||||
|
if (!status.isGranted && status.isPermanentlyDenied) {
|
||||||
|
await openAppSettings();
|
||||||
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _notificationGranted = status.isGranted);
|
final newStatus = await Permission.notification.status;
|
||||||
|
setState(() => _notificationGranted = newStatus.isGranted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _requestBatteryOptOut() async {
|
Future<void> _requestBatteryOptOut() async {
|
||||||
final success = await LocationBridge.openBatteryOptimizationSettings();
|
final opened = await LocationBridge.openBatteryOptimizationSettings();
|
||||||
if (success) {
|
if (mounted) {
|
||||||
// Show dialog asking user to confirm after disabling battery opt
|
// Show dialog after the settings screen opens
|
||||||
if (mounted) _showBatteryOptDialog();
|
_showBatteryOptDialog(opened);
|
||||||
} else {
|
|
||||||
// Fallback to manual instructions dialog
|
|
||||||
if (mounted) _showBatteryOptDialog();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showBatteryOptDialog() {
|
void _showBatteryOptDialog(bool settingsOpened) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
backgroundColor: const Color(0xFF1a1a1a),
|
backgroundColor: const Color(0xFF1a1a1a),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|
@ -98,10 +106,12 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
color: Color(0xFFe0e0e0),
|
color: Color(0xFFe0e0e0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content: const Text(
|
content: Text(
|
||||||
'Please disable battery optimization for this app to ensure reliable background tracking.\n\n'
|
settingsOpened
|
||||||
'In the next screen, find "Traccar Client" and set it to "Don\'t optimize" or "Unrestricted".',
|
? 'A settings screen has opened.\n\n'
|
||||||
style: TextStyle(
|
'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',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Color(0xFF9e9e9e),
|
color: Color(0xFF9e9e9e),
|
||||||
|
|
@ -109,9 +119,13 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () {
|
||||||
|
Navigator.pop(ctx);
|
||||||
|
// Open app details settings as fallback
|
||||||
|
openAppSettings();
|
||||||
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'OPEN SETTINGS',
|
'OPEN APP SETTINGS',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
|
|
@ -122,12 +136,16 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.pop(ctx);
|
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(
|
child: const Text(
|
||||||
'DONE',
|
'CHECK & DONE',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
|
|
@ -213,11 +231,11 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
color: Color(0xFF161616),
|
color: Color(0xFF161616),
|
||||||
border: Border(bottom: BorderSide(color: Color(0xFF2a2a2a), width: 1)),
|
border: Border(bottom: BorderSide(color: Color(0xFF2a2a2a), width: 1)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: const Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.shield, color: Color(0xFF00bcd4), size: 20),
|
Icon(Icons.shield, color: Color(0xFF00bcd4), size: 20),
|
||||||
const SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
const Text(
|
Text(
|
||||||
'PERMISSIONS',
|
'PERMISSIONS',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
|
|
@ -245,18 +263,18 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
Container(
|
Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: const Color(0xFF00bcd4).withOpacity(0.15),
|
color: Color(0xFF00bcd4),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.info_outline,
|
Icons.info_outline,
|
||||||
color: Color(0xFF00bcd4),
|
color: Colors.white,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
SizedBox(width: 14),
|
||||||
const Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -420,7 +438,7 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
_notificationGranted,
|
_notificationGranted,
|
||||||
_batteryOptOut,
|
_batteryOptOut,
|
||||||
].where((x) => x).length;
|
].where((x) => x).length;
|
||||||
final total = 3;
|
const total = 3;
|
||||||
final progress = granted / total;
|
final progress = granted / total;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
|
@ -428,9 +446,11 @@ class _PermissionScreenState extends State<PermissionScreen> {
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
Icon(
|
||||||
Icons.check_circle_outline,
|
granted == total
|
||||||
color: Color(0xFF00e676),
|
? Icons.check_circle
|
||||||
|
: Icons.check_circle_outline,
|
||||||
|
color: const Color(0xFF00e676),
|
||||||
size: 14,
|
size: 14,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue