import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../services/sensor_fusion_service.dart'; import '../../services/map/map_provider_interface.dart'; import '../../services/map/map_providers.dart'; import '../../services/alerts/road_alert_models.dart'; import '../../services/alerts/road_alert_providers.dart'; import '../../services/alerts/road_alert_service.dart'; import '../../database/database.dart'; import '../../core/app_info.dart'; class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @override ConsumerState createState() => _SettingsScreenState(); } class _SettingsScreenState extends ConsumerState { late TextEditingController _tomtomKeyController; late TextEditingController _communityKeyController; bool _isTestingTomTom = false; bool _isTestingCommunity = false; @override void initState() { super.initState(); _tomtomKeyController = TextEditingController( text: ref.read(tomtomApiKeyProvider), ); _communityKeyController = TextEditingController( text: ref.read(communityApiKeyProvider), ); } @override void dispose() { _tomtomKeyController.dispose(); _communityKeyController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final mapType = ref.watch(mapProviderTypeNotifierProvider); final roadAlertsEnabled = ref.watch(roadAlertsEnabledProvider); final audioAlertsEnabled = ref.watch(audioAlertsEnabledProvider); final alertRadiusMeters = ref.watch(alertRadiusMetersProvider); final enabledCategories = ref.watch(enabledCategoriesProvider); final tomtomStatus = ref.watch(tomtomStatusProvider); final communityStatus = ref.watch(communityStatusProvider); return Scaffold( appBar: AppBar(title: const Text('Settings')), body: ListView( padding: const EdgeInsets.all(16.0), children: [ _buildSectionHeader('Maps & Navigation Engine'), const SizedBox(height: 12), Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Icon(Icons.map, color: Colors.cyanAccent, size: 20), SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Active Map Engine', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), SizedBox(height: 2), Text( 'Select map rendering backend for live map, route preview, and replay.', style: TextStyle( fontSize: 12, color: Colors.white70, height: 1.3, ), ), ], ), ), ], ), const SizedBox(height: 16), SingleChildScrollView( scrollDirection: Axis.horizontal, child: SegmentedButton( segments: const [ ButtonSegment( value: AppMapType.openStreetMap, label: Text('OpenStreetMap'), icon: Icon(Icons.public), ), ButtonSegment( value: AppMapType.googleMaps, label: Text('Google Maps'), icon: Icon(Icons.map_outlined), ), ], selected: {mapType}, onSelectionChanged: (Set selection) { ref .read(mapProviderTypeNotifierProvider.notifier) .setMapProviderType(selection.first); }, ), ), ], ), ), ), const SizedBox(height: 24), _buildSectionHeader('Road Alerts & Incidents'), const SizedBox(height: 12), Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SwitchListTile( activeThumbColor: Colors.cyanAccent, contentPadding: const EdgeInsets.symmetric( horizontal: 12.0, vertical: 0.0, ), title: const Text( 'Road Alerts', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), subtitle: const Text( 'Display real-time police, hazards, and traffic incidents on map.', style: TextStyle(fontSize: 11), ), value: roadAlertsEnabled, onChanged: (val) { ref .read(roadAlertsEnabledProvider.notifier) .setEnabled(val); }, ), const Divider(), SwitchListTile( activeThumbColor: Colors.cyanAccent, contentPadding: const EdgeInsets.symmetric( horizontal: 12.0, vertical: 0.0, ), title: const Text( 'Audio Warnings (TTS)', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), subtitle: const Text( 'Announce approaching hazards and enforcement over helmet headset.', style: TextStyle(fontSize: 11), ), value: audioAlertsEnabled, onChanged: roadAlertsEnabled ? (val) { ref .read(audioAlertsEnabledProvider.notifier) .setEnabled(val); } : null, ), const Divider(), Padding( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Alert Radius', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), SizedBox(height: 4), Text( 'Distance to detect upcoming incidents', style: TextStyle( fontSize: 11, color: Colors.white70, ), ), ], ), ), const SizedBox(width: 12), DropdownButton( value: alertRadiusMeters, items: const [ DropdownMenuItem( value: 152.4, child: Text('500 ft'), ), DropdownMenuItem( value: 304.8, child: Text('1,000 ft'), ), DropdownMenuItem( value: 402.3, child: Text('1/4 Mile'), ), DropdownMenuItem( value: 804.7, child: Text('1/2 Mile'), ), DropdownMenuItem( value: 1609.3, child: Text('1 Mile'), ), ], onChanged: roadAlertsEnabled ? (val) { if (val != null) { ref .read( alertRadiusMetersProvider.notifier, ) .setRadius(val); } } : null, ), ], ), ), const Divider(), const Padding( padding: EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0, ), child: Text( 'Alert Categories', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), ), ...RoadAlertCategory.values.map((cat) { final isChecked = enabledCategories.contains(cat); return CheckboxListTile( activeColor: Colors.cyanAccent, checkColor: Colors.black, dense: true, visualDensity: VisualDensity.compact, contentPadding: const EdgeInsets.symmetric( horizontal: 12.0, vertical: 0.0, ), title: Text( cat.displayName, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), ), secondary: Icon(cat.icon, color: cat.color, size: 20), value: isChecked, onChanged: roadAlertsEnabled ? (val) { ref .read(enabledCategoriesProvider.notifier) .toggleCategory(cat, val ?? true); } : null, ); }), ], ), ), ), const SizedBox(height: 16), // Community Traffic Provider Card Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildProviderHeader( icon: Icons.local_police, iconColor: Colors.blueAccent, title: 'Community Traffic Provider', subtitle: 'Supplies Police, Speed Cameras, and Crowd Hazards.', status: communityStatus, ), const SizedBox(height: 16), TextField( controller: _communityKeyController, decoration: const InputDecoration( labelText: 'Community API Key', hintText: 'Enter API Key for Community Traffic Feed', helperText: 'Supplies Police, Speed Cameras, and Crowd Hazards.', helperMaxLines: 2, border: OutlineInputBorder(), isDense: true, ), style: const TextStyle(fontSize: 14), onChanged: (val) { ref.read(communityApiKeyProvider.notifier).setApiKey(val); }, ), const SizedBox(height: 12), Align( alignment: Alignment.centerRight, child: ElevatedButton.icon( onPressed: _isTestingCommunity ? null : () async { setState(() => _isTestingCommunity = true); final messenger = ScaffoldMessenger.of(context); final provider = ref.read( communityProviderInstance, ); final status = await provider.testConnection( _communityKeyController.text, ); ref.read(communityStatusProvider.notifier).state = status; setState(() => _isTestingCommunity = false); if (mounted) { String msg = ''; if (status == RoadAlertProviderStatus.connected) { msg = 'Community API Connected successfully!'; } else if (status == RoadAlertProviderStatus.authError) { msg = 'Authentication Error: Invalid Key.'; } else if (status == RoadAlertProviderStatus.notConfigured) { msg = 'Key is empty.'; } else if (status == RoadAlertProviderStatus.networkError) { msg = 'Network Error: Failed to connect to API.'; } else if (status == RoadAlertProviderStatus.rateLimited) { msg = 'Rate Limited: Too many requests.'; } else { msg = 'Status: ${status.name}'; } messenger.showSnackBar( SnackBar(content: Text(msg)), ); } }, icon: _isTestingCommunity ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sync), label: const Text('Test Connection'), ), ), ], ), ), ), const SizedBox(height: 16), // TomTom Provider API Key Card Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildProviderHeader( icon: Icons.security, iconColor: Colors.cyanAccent, title: 'TomTom Traffic Provider', subtitle: 'Supplies Accidents, Hazards, Closures & Construction data.', status: tomtomStatus, ), const SizedBox(height: 16), TextField( controller: _tomtomKeyController, decoration: const InputDecoration( labelText: 'TomTom API Key', hintText: 'Enter your free TomTom API Key', helperText: 'Supplies Accidents, Hazards, Closures & Construction data.', helperMaxLines: 2, border: OutlineInputBorder(), isDense: true, ), style: const TextStyle(fontSize: 14), onChanged: (val) { ref.read(tomtomApiKeyProvider.notifier).setApiKey(val); }, ), const SizedBox(height: 12), Align( alignment: Alignment.centerRight, child: ElevatedButton.icon( onPressed: _isTestingTomTom ? null : () async { setState(() => _isTestingTomTom = true); final messenger = ScaffoldMessenger.of(context); final provider = ref.read(tomtomProviderInstance); final status = await provider.testConnection( _tomtomKeyController.text, ); ref.read(tomtomStatusProvider.notifier).state = status; setState(() => _isTestingTomTom = false); if (mounted) { String msg = ''; if (status == RoadAlertProviderStatus.connected) { msg = 'TomTom API Connected successfully!'; } else if (status == RoadAlertProviderStatus.authError) { msg = 'Authentication Error: Invalid Key.'; } else if (status == RoadAlertProviderStatus.notConfigured) { msg = 'Key is empty.'; } else if (status == RoadAlertProviderStatus.networkError) { msg = 'Network Error: Failed to connect to API.'; } else if (status == RoadAlertProviderStatus.rateLimited) { msg = 'Rate Limited: Too many requests.'; } else { msg = 'Status: ${status.name}'; } messenger.showSnackBar( SnackBar(content: Text(msg)), ); } }, icon: _isTestingTomTom ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sync), label: const Text('Test Connection'), ), ), ], ), ), ), // Developer Test Mode Section (Debug Mode Only) if (kDebugMode) ...[ const SizedBox(height: 32), const Text( 'Developer Options', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.orangeAccent, ), ), const SizedBox(height: 16), Card( color: Colors.orange.withValues(alpha: 0.1), shape: RoundedRectangleBorder( side: const BorderSide(color: Colors.orangeAccent), borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row( children: [ Icon( Icons.bug_report, color: Colors.orangeAccent, size: 20, ), SizedBox(width: 10), Text( 'Road Alert Test Mode', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.orangeAccent, ), ), ], ), const SizedBox(height: 8), const Text( 'Generate simulated test alerts for real-device testing (Debug Build Only).', style: TextStyle(fontSize: 12, color: Colors.white70), ), const SizedBox(height: 16), Wrap( spacing: 8, runSpacing: 8, children: [ ElevatedButton.icon( icon: const Icon( Icons.local_police, color: Colors.blueAccent, size: 18, ), label: const Text('Test Police (100m)'), onPressed: () { final alert = RoadAlert( id: 'sim_police', providerId: 'mock', category: RoadAlertCategory.police, description: 'Simulated police alert', latitude: 0, longitude: 0, distanceFromUser: 100.0, reportedAt: DateTime.now(), lastUpdated: DateTime.now(), source: 'Test', ); ref.read(roadAlertServiceProvider).simulateAlert(alert); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Simulated Police Alert sent to Dashboard.')), ); }, ), ElevatedButton.icon( icon: const Icon( Icons.warning, color: Colors.orangeAccent, size: 18, ), label: const Text('Test Hazard (250m)'), onPressed: () { final alert = RoadAlert( id: 'sim_hazard', providerId: 'mock', category: RoadAlertCategory.hazard, description: 'Simulated hazard alert', latitude: 0, longitude: 0, distanceFromUser: 250.0, reportedAt: DateTime.now(), lastUpdated: DateTime.now(), source: 'Test', ); ref.read(roadAlertServiceProvider).simulateAlert(alert); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Simulated Hazard Alert sent to Dashboard.')), ); }, ), ], ), ], ), ), ), ], const SizedBox(height: 24), _buildSectionHeader('Sensors'), const SizedBox(height: 12), Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon( Icons.screen_rotation, color: Colors.cyanAccent, size: 20, ), const SizedBox(width: 10), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Calibrate Mount Angle', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), SizedBox(height: 2), Text( 'Place the motorcycle perfectly upright and tap this to zero the lean angle sensor.', style: TextStyle( fontSize: 12, color: Colors.white70, height: 1.3, ), ), ], ), ), ], ), const SizedBox(height: 16), Align( alignment: Alignment.centerRight, child: ElevatedButton( onPressed: () async { ref .read(sensorFusionServiceProvider) .calibrateUpright(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Sensors Calibrated!'), ), ); } }, child: const Text('Calibrate'), ), ), ], ), ), ), const SizedBox(height: 24), _buildSectionHeader('Data'), const SizedBox(height: 12), Card( clipBehavior: Clip.antiAlias, child: InkWell( onTap: () => context.go('/settings/diagnostics'), child: const Padding( padding: EdgeInsets.all(16.0), child: Row( children: [ Icon( Icons.monitor_heart, color: Colors.cyanAccent, size: 20, ), SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sensor Diagnostics', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), SizedBox(height: 2), Text( 'View raw IMU and GPS values.', style: TextStyle( fontSize: 12, color: Colors.white70, ), ), ], ), ), Icon(Icons.chevron_right, color: Colors.white54, size: 20), ], ), ), ), ), const SizedBox(height: 16), Card( clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon( Icons.delete_forever, color: Colors.redAccent, size: 20, ), const SizedBox(width: 10), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Clear Ride History', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), SizedBox(height: 2), Text( 'Permanently delete all recorded rides and performance runs.', style: TextStyle( fontSize: 12, color: Colors.white70, height: 1.3, ), ), ], ), ), ], ), const SizedBox(height: 16), Align( alignment: Alignment.centerRight, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red.withValues(alpha: 0.2), ), onPressed: () async { final db = ref.read(databaseProvider); await db.delete(db.rides).go(); await db.delete(db.performanceRuns).go(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('All data cleared.')), ); } }, child: const Text( 'Clear', style: TextStyle(color: Colors.redAccent), ), ), ), ], ), ), ), const SizedBox(height: 24), _AppVersionFooter(), ], ), ); } Widget _buildSectionHeader(String title) { return Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 0.4, color: Colors.cyanAccent, ), ); } Widget _buildProviderHeader({ required IconData icon, required Color iconColor, required String title, required String subtitle, required RoadAlertProviderStatus status, }) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, color: iconColor, size: 20), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), softWrap: true, ), const SizedBox(height: 2), Text( subtitle, style: const TextStyle( fontSize: 12, color: Colors.white70, height: 1.3, ), softWrap: true, ), const SizedBox(height: 8), Align( alignment: Alignment.centerLeft, child: _buildStatusBadge(status), ), ], ), ), ], ); } Widget _buildStatusBadge(RoadAlertProviderStatus status) { String label; Color color; switch (status) { case RoadAlertProviderStatus.connected: label = 'Connected'; color = Colors.greenAccent; break; case RoadAlertProviderStatus.configured: label = 'Configured'; color = Colors.cyanAccent; break; case RoadAlertProviderStatus.authError: label = 'Auth Error'; color = Colors.redAccent; break; case RoadAlertProviderStatus.rateLimited: label = 'Rate Limited'; color = Colors.orangeAccent; break; case RoadAlertProviderStatus.networkError: label = 'Network Error'; color = Colors.amberAccent; break; case RoadAlertProviderStatus.notConfigured: label = 'Not Set'; color = Colors.white38; break; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), border: Border.all(color: color), borderRadius: BorderRadius.circular(8), ), child: Text( label, style: TextStyle( color: color, fontSize: 11, fontWeight: FontWeight.bold, ), ), ); } } /// Read-only footer that surfaces the app's build version inside Settings. /// Resolves the version asynchronously via [AppInfo]; shows a placeholder /// while loading and the literal fallback string if the platform call fails. class _AppVersionFooter extends StatefulWidget { const _AppVersionFooter(); @override State<_AppVersionFooter> createState() => _AppVersionFooterState(); } class _AppVersionFooterState extends State<_AppVersionFooter> { late Future _versionFuture; @override void initState() { super.initState(); _versionFuture = AppInfo.versionString(); } @override Widget build(BuildContext context) { return Center( child: FutureBuilder( future: _versionFuture, builder: (context, snapshot) { final text = snapshot.data ?? AppInfo.fallbackVersion; return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( 'Motorcycle Dash v$text', textAlign: TextAlign.center, style: const TextStyle( color: Colors.white38, fontSize: 12, letterSpacing: 0.4, ), ), ); }, ), ); } }