import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'bridge/location_bridge.dart'; import 'preferences.dart'; import 'main_screen.dart'; class PermissionScreen extends StatefulWidget { const PermissionScreen({super.key}); @override State createState() => _PermissionScreenState(); } class _PermissionScreenState extends State { bool _locationGranted = false; bool _notificationGranted = false; bool _batteryOptOut = false; @override void initState() { super.initState(); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Color(0xFF0d0d0d), statusBarIconBrightness: Brightness.light, ), ); _checkPermissions(); } Future _checkPermissions() async { final location = await Permission.locationAlways.status; final notification = await Permission.notification.status; final batteryOptOut = await _isBatteryOptimizationDisabled(); if (mounted) { setState(() { _locationGranted = location.isGranted; _notificationGranted = notification.isGranted; _batteryOptOut = batteryOptOut; }); } } Future _isBatteryOptimizationDisabled() async { try { final result = await LocationBridge.isBatteryOptimizationDisabled(); return result == true; } catch (_) { return false; } } bool get _allGranted => _locationGranted && _notificationGranted && _batteryOptOut; Future _requestLocation() async { // Check current status first var status = await Permission.locationAlways.status; if (status.isGranted) { if (mounted) setState(() => _locationGranted = true); return; } // On Android, request locationWhenInUse first before locationAlways var whenInUse = await Permission.locationWhenInUse.status; if (!whenInUse.isGranted) { whenInUse = await Permission.locationWhenInUse.request(); } // Now request locationAlways (background permission) status = await Permission.locationAlways.request(); if (!status.isGranted && status.isPermanentlyDenied) { await openAppSettings(); } final finalStatus = await Permission.locationAlways.status; if (mounted) { setState(() => _locationGranted = finalStatus.isGranted); } } Future _requestNotification() async { var status = await Permission.notification.status; if (status.isGranted) { if (mounted) setState(() => _notificationGranted = true); return; } status = await Permission.notification.request(); if (!status.isGranted && status.isPermanentlyDenied) { await openAppSettings(); } final finalStatus = await Permission.notification.status; if (mounted) { setState(() => _notificationGranted = finalStatus.isGranted); } } Future _requestBatteryOptOut() async { final opened = await LocationBridge.openBatteryOptimizationSettings(); if (mounted) { _showBatteryOptDialog(opened); } } void _showBatteryOptDialog(bool settingsOpened) { showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( backgroundColor: const Color(0xFF1a1a1a), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), side: const BorderSide(color: Color(0xFF2a2a2a)), ), title: const Text( 'BATTERY OPTIMIZATION', style: TextStyle( fontFamily: 'monospace', fontSize: 14, fontWeight: FontWeight.w700, letterSpacing: 2, color: Color(0xFFe0e0e0), ), ), content: Text( settingsOpened ? 'A settings screen has opened.\n\n' 'Find "TracPulse" and set it to "Don\'t optimize" or "Unrestricted", then come back and tap CHECK & DONE.' : 'Open Settings → Apps → TracPulse → Battery and select "Don\'t optimize" or "Unrestricted", then come back and tap CHECK & DONE.', style: const TextStyle( fontFamily: 'monospace', fontSize: 12, color: Color(0xFF9e9e9e), ), ), actions: [ TextButton( onPressed: () { Navigator.pop(ctx); openAppSettings(); }, child: const Text( 'OPEN APP SETTINGS', style: TextStyle( fontFamily: 'monospace', fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFF00bcd4), ), ), ), TextButton( onPressed: () async { Navigator.pop(ctx); final disabled = await _isBatteryOptimizationDisabled(); if (mounted) { setState(() => _batteryOptOut = disabled); } }, child: const Text( 'CHECK & DONE', style: TextStyle( fontFamily: 'monospace', fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFF00e676), ), ), ), ], ), ); } Future _complete() async { await Preferences.setPermissionsGranted(true); if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const MainScreen()), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0d0d0d), body: SafeArea( child: Column( children: [ _buildHeader(), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildIntroCard(), const SizedBox(height: 20), _buildPermissionItem( icon: Icons.location_on, title: 'LOCATION', subtitle: 'All the time access', granted: _locationGranted, onRequest: _requestLocation, ), const SizedBox(height: 12), _buildPermissionItem( icon: Icons.notifications, title: 'NOTIFICATION', subtitle: 'Show sync & tracking alerts', granted: _notificationGranted, onRequest: _requestNotification, ), const SizedBox(height: 12), _buildPermissionItem( icon: Icons.battery_full, title: 'BATTERY', subtitle: 'Disable optimization for reliability', granted: _batteryOptOut, onRequest: _requestBatteryOptOut, ), const SizedBox(height: 32), _buildStatusSummary(), const SizedBox(height: 24), _buildContinueButton(), const SizedBox(height: 20), ], ), ), ), ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), decoration: const BoxDecoration( color: Color(0xFF161616), border: Border(bottom: BorderSide(color: Color(0xFF2a2a2a), width: 1)), ), child: const Row( children: [ Icon(Icons.shield, color: Color(0xFF00bcd4), size: 20), SizedBox(width: 12), Text( 'PERMISSIONS', style: TextStyle( fontFamily: 'monospace', fontSize: 16, fontWeight: FontWeight.w700, letterSpacing: 3, color: Color(0xFFe0e0e0), ), ), ], ), ); } Widget _buildIntroCard() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF161616), borderRadius: BorderRadius.circular(4), border: Border.all(color: const Color(0xFF2a2a2a), width: 1), ), child: Row( children: [ Container( width: 40, height: 40, decoration: const BoxDecoration( color: Color(0xFF00bcd4), borderRadius: BorderRadius.all(Radius.circular(4)), ), child: const Icon( Icons.info_outline, color: Colors.white, size: 20, ), ), const SizedBox(width: 14), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Required Permissions', style: TextStyle( fontFamily: 'monospace', fontSize: 13, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFFe0e0e0), ), ), SizedBox(height: 4), Text( 'TracPulse needs these permissions to track your location reliably in the background.', style: TextStyle( fontFamily: 'monospace', fontSize: 11, color: Color(0xFF757575), ), ), ], ), ), ], ), ); } Widget _buildPermissionItem({ required IconData icon, required String title, required String subtitle, required bool granted, required VoidCallback onRequest, }) { return Material( color: const Color(0xFF161616), borderRadius: BorderRadius.circular(4), child: InkWell( onTap: granted ? null : onRequest, borderRadius: BorderRadius.circular(4), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all( color: granted ? const Color(0xFF00e676).withValues(alpha: 0.3) : const Color(0xFF2a2a2a), width: 1, ), ), child: Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: granted ? const Color(0xFF00e676).withValues(alpha: 0.15) : const Color(0xFF2a2a2a), borderRadius: BorderRadius.circular(4), ), child: Icon( icon, color: granted ? const Color(0xFF00e676) : const Color(0xFF616161), size: 18, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontFamily: 'monospace', fontSize: 12, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFFe0e0e0), ), ), const SizedBox(height: 2), Text( subtitle, style: const TextStyle( fontFamily: 'monospace', fontSize: 10, color: Color(0xFF616161), ), ), ], ), ), if (granted) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: const Color(0xFF00e676).withValues(alpha: 0.15), borderRadius: BorderRadius.circular(3), border: Border.all( color: const Color(0xFF00e676).withValues(alpha: 0.4), width: 1, ), ), child: const Text( 'GRANTED', style: TextStyle( fontFamily: 'monospace', fontSize: 9, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFF00e676), ), ), ) else Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: const Color(0xFF00bcd4).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(3), border: Border.all( color: const Color(0xFF00bcd4).withValues(alpha: 0.4), width: 1, ), ), child: const Text( 'TAP TO GRANT', style: TextStyle( fontFamily: 'monospace', fontSize: 9, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFF00bcd4), ), ), ), ], ), ), ), ); } Widget _buildStatusSummary() { final granted = [ _locationGranted, _notificationGranted, _batteryOptOut, ].where((x) => x).length; const total = 3; final progress = granted / total; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( granted == total ? Icons.check_circle : Icons.check_circle_outline, color: const Color(0xFF00e676), size: 14, ), const SizedBox(width: 8), Text( '$granted / $total GRANTED', style: const TextStyle( fontFamily: 'monospace', fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFF00e676), ), ), ], ), const SizedBox(height: 10), Container( height: 4, decoration: BoxDecoration( color: const Color(0xFF2a2a2a), borderRadius: BorderRadius.circular(2), ), child: FractionallySizedBox( alignment: Alignment.centerLeft, widthFactor: progress, child: Container( decoration: BoxDecoration( color: const Color(0xFF00e676), borderRadius: BorderRadius.circular(2), ), ), ), ), ], ); } Widget _buildContinueButton() { return Material( color: _allGranted ? const Color(0xFF00e676).withValues(alpha: 0.15) : const Color(0xFF2a2a2a), borderRadius: BorderRadius.circular(4), child: InkWell( onTap: _allGranted ? _complete : null, borderRadius: BorderRadius.circular(4), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all( color: _allGranted ? const Color(0xFF00e676).withValues(alpha: 0.4) : const Color(0xFF2a2a2a), width: 1, ), ), child: Center( child: Text( _allGranted ? 'CONTINUE' : 'GRANT ALL PERMISSIONS', style: TextStyle( fontFamily: 'monospace', fontSize: 13, fontWeight: FontWeight.w700, letterSpacing: 2, color: _allGranted ? const Color(0xFF00e676) : const Color(0xFF616161), ), ), ), ), ), ); } }