import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../core/app_version.dart'; import '../../core/display_controller.dart'; import '../../core/theme/accent_choice.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/theme_controller.dart'; import '../../core/theme/theme_preference.dart'; import '../../core/widgets/app_icon.dart'; import '../../core/widgets/pressable.dart'; import '../../core/widgets/surface_card.dart'; import '../../data/models/slot_type.dart'; import '../apps/app_picker_sheet.dart'; import '../home/launcher_controller.dart'; import '../home/system_controller.dart'; import '../jellyfin/jellyfin_playback_controller.dart'; import 'update_controller.dart'; import '../lock/lock_controller.dart'; import '../lock/pin_setup_screen.dart'; import 'jellyfin_settings_card.dart'; import 'obd_settings_card.dart'; import 'settings_widgets.dart'; import 'speed_settings_card.dart'; import 'temperature_settings_card.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key, this.playback}); final JellyfinPlaybackController? playback; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); final themeController = context.watch(); final display = context.watch(); final launcher = context.watch(); final system = context.watch(); final update = context.watch(); final lock = context.watch(); return Scaffold( backgroundColor: colors.canvas, body: Container( decoration: homeBackground(colors, Theme.of(context).brightness), child: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(32, 22, 32, 32), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 980), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Pressable( onTap: () => Navigator.of(context).pop(), semanticLabel: 'Back', pressedScale: 0.92, child: Container( width: 46, height: 46, decoration: BoxDecoration( color: colors.surface, shape: BoxShape.circle, border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon( 'assets/icons/arrow-left.svg', size: 20, color: colors.ink, ), ), ), const SizedBox(width: 18), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Settings', style: theme.textTheme.headlineLarge, ), const SizedBox(height: 2), Text( 'Theme, media, car data and access', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, ), ), ], ), ], ), const SizedBox(height: 32), _SectionTitle( title: 'General', colors: colors, theme: theme, ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 22, 26, 18), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('Theme', style: theme.textTheme.titleMedium), const Spacer(), _ThemeSelector( selected: themeController.preference, onSelected: themeController.setPreference, ), ], ), const SizedBox(height: 8), Text( themeController.preference == ThemePreference.automatic ? 'Switches automatically at the times below.' : themeController.preference == ThemePreference.dark ? 'Always dark.' : 'Always light.', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, ), ), const SizedBox(height: 8), Divider(color: colors.border, height: 24), Row( children: [ Text( 'Accent', style: theme.textTheme.titleMedium, ), const Spacer(), Text( themeController.accent.label, style: theme.textTheme.labelMedium?.copyWith( color: colors.inkMuted, ), ), ], ), const SizedBox(height: 10), _AccentSelector( selected: themeController.accent, onSelected: (choice) { themeController.setAccent(choice); }, ), if (themeController.preference == ThemePreference.automatic) ...[ const SizedBox(height: 8), Divider(color: colors.border, height: 24), _ActionRow( iconAsset: 'assets/icons/moon.svg', title: 'Dark from', value: _formatMinutes( context, themeController.nightStartMinutes, ), onTap: () => _pickTime( context, themeController, start: true, ), ), const SizedBox(height: 2), _ActionRow( iconAsset: 'assets/icons/sun.svg', title: 'Light from', value: _formatMinutes( context, themeController.nightEndMinutes, ), onTap: () => _pickTime( context, themeController, start: false, ), ), ], Divider(color: colors.border, height: 24), _BatteryToggleRow( value: themeController.showBattery, onChanged: (value) { themeController.setShowBattery(value); }, ), Divider(color: colors.border, height: 24), _KeepScreenOnRow( value: display.keepScreenOn, onChanged: display.setKeepScreenOn, ), Divider(color: colors.border, height: 30), Row( children: [ Text( 'Home buttons', style: theme.textTheme.titleMedium, ), const Spacer(), Text( '${launcher.homeButtonCount} / ' '${LauncherController.maxHomeButtons}', style: theme.textTheme.labelMedium?.copyWith( color: colors.inkMuted, ), ), ], ), const SizedBox(height: 3), Text( 'Turn one off to hide it. All apps is always shown; Settings lives in the top-right status area.', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, ), ), const SizedBox(height: 4), for (final slot in SlotType.values) _ShortcutToggleRow( slot: slot, launcher: launcher, canToggle: slot.isAppSlot || launcher.jellyfinConfigured, onPick: slot.isAppSlot ? () => showAppPicker(context, slot) : null, subtitle: slot == SlotType.jellyfin ? launcher.jellyfinConfigured ? 'Music library' : 'Configure Jellyfin first' : null, ), Divider(color: colors.border, height: 30), _HomeRoleRow(launcher: launcher), ], ), ), const SizedBox(height: 30), _SectionTitle(title: 'Media', colors: colors, theme: theme), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 20, 26, 20), child: _NowPlayingAccessRow(system: system), ), const SizedBox(height: 30), _SectionTitle( title: 'Jellyfin', colors: colors, theme: theme, ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 22, 26, 20), child: JellyfinSettingsCard(playback: playback), ), const SizedBox(height: 30), _SectionTitle( title: 'OBD-II', colors: colors, theme: theme, ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 22, 26, 20), child: const ObdSettingsCard(), ), const SizedBox(height: 30), _SectionTitle(title: 'Speed', colors: colors, theme: theme), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 22, 26, 20), child: const SpeedSettingsCard(), ), const SizedBox(height: 30), _SectionTitle( title: 'Temperature', colors: colors, theme: theme, ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 22, 26, 20), child: const TemperatureSettingsCard(), ), const SizedBox(height: 30), _SectionTitle( title: 'Security', colors: colors, theme: theme, ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 22, 26, 22), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: colors.accentSoft, borderRadius: BorderRadius.circular(14), ), alignment: Alignment.center, child: AppIcon( 'assets/icons/lock.svg', size: 20, color: colors.accent, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'PIN protection', style: theme.textTheme.titleMedium, ), const SizedBox(height: 2), Text( lock.pinSet ? 'On' : 'Off', style: theme.textTheme.bodySmall ?.copyWith( color: lock.pinSet ? colors.accent : colors.inkMuted, fontWeight: FontWeight.w600, ), ), ], ), ), if (!lock.pinSet) _SmallButton( label: 'Set PIN', onTap: () => _openPinSetup(context, PinSetupMode.set), ), ], ), if (lock.pinSet) ...[ const SizedBox(height: 10), Divider(color: colors.border, height: 24), _ActionRow( iconAsset: 'assets/icons/lock.svg', title: 'Change PIN', trailing: const _Chevron(), onTap: () => _openPinSetup(context, PinSetupMode.change), ), _ActionRow( iconAsset: 'assets/icons/x.svg', title: 'Remove PIN', trailing: const _Chevron(), onTap: () => _openPinSetup(context, PinSetupMode.remove), ), ], const SizedBox(height: 16), Text( 'The PIN is requested only when CarDash starts. It does not lock ' 'again when you switch apps or turn the screen off. It protects the ' 'launcher, not the device itself.', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, height: 1.5, ), ), ], ), ), const SizedBox(height: 30), _SectionTitle( title: 'Updates', colors: colors, theme: theme, ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.fromLTRB(26, 20, 26, 20), child: _UpdateRow(update: update), ), const SizedBox(height: 28), Center( child: Text( 'CarDash $appVersion · Android 10+', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkFaint, ), ), ), ], ), ), ), ), ), ), ); } Future _openPinSetup(BuildContext context, PinSetupMode mode) async { final result = await Navigator.of( context, ).push(MaterialPageRoute(builder: (_) => PinSetupScreen(mode: mode))); if (result != true || !context.mounted) return; final message = switch (mode) { PinSetupMode.set => 'PIN set. It will be requested the next time CarDash starts.', PinSetupMode.change => 'PIN changed.', PinSetupMode.remove => 'PIN removed.', }; ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } Future _pickTime( BuildContext context, ThemeController controller, { required bool start, }) async { final minutes = start ? controller.nightStartMinutes : controller.nightEndMinutes; final picked = await showTimePicker( context: context, initialTime: TimeOfDay(hour: minutes ~/ 60, minute: minutes % 60), helpText: start ? 'Dark from' : 'Light from', builder: (context, child) { final theme = Theme.of(context); return Theme( data: theme.copyWith( timePickerTheme: TimePickerThemeData( backgroundColor: theme.colorScheme.surface, dialBackgroundColor: theme.colorScheme.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(28), ), ), ), child: child ?? const SizedBox.shrink(), ); }, ); if (picked == null || !context.mounted) return; final newValue = picked.hour * 60 + picked.minute; await controller.setNightWindow( startMinutes: start ? newValue : controller.nightStartMinutes, endMinutes: start ? controller.nightEndMinutes : newValue, ); } String _formatMinutes(BuildContext context, int minutes) { return TimeOfDay(hour: minutes ~/ 60, minute: minutes % 60).format(context); } } class _SectionTitle extends StatelessWidget { const _SectionTitle({ required this.title, required this.colors, required this.theme, }); final String title; final AppColors colors; final ThemeData theme; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 4), child: Text( title, style: theme.textTheme.titleLarge?.copyWith(color: colors.inkMuted), ), ); } } class _ThemeSelector extends StatelessWidget { const _ThemeSelector({required this.selected, required this.onSelected}); final ThemePreference selected; final ValueChanged onSelected; @override Widget build(BuildContext context) { final colors = context.colors; return Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: colors.surfaceStrong, borderRadius: BorderRadius.circular(999), border: Border.all(color: colors.border), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ for (final preference in ThemePreference.values) Pressable( onTap: () => onSelected(preference), semanticLabel: 'Theme ${preference.storageValue}', pressedScale: 0.95, child: AnimatedContainer( duration: const Duration(milliseconds: 160), curve: Curves.easeOutCubic, padding: const EdgeInsets.symmetric( horizontal: 18, vertical: 9, ), decoration: BoxDecoration( color: selected == preference ? colors.accent : Colors.transparent, borderRadius: BorderRadius.circular(999), ), child: Text( switch (preference) { ThemePreference.light => 'Light', ThemePreference.dark => 'Dark', ThemePreference.automatic => 'Auto', }, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: selected == preference ? colors.accentInk : colors.inkMuted, fontWeight: FontWeight.w600, ), ), ), ), ], ), ); } } class _AccentSelector extends StatelessWidget { const _AccentSelector({required this.selected, required this.onSelected}); final AccentChoice selected; final ValueChanged onSelected; @override Widget build(BuildContext context) { final colors = context.colors; final brightness = Theme.of(context).brightness; return Wrap( spacing: 10, runSpacing: 10, children: [ for (final choice in AccentChoice.values) Semantics( label: '${choice.label} accent', selected: choice == selected, button: true, child: Material( color: Colors.transparent, shape: const CircleBorder(), child: InkWell( customBorder: const CircleBorder(), onTap: () => onSelected(choice), child: Padding( padding: const EdgeInsets.all(3), child: AnimatedContainer( duration: const Duration(milliseconds: 180), width: 30, height: 30, decoration: BoxDecoration( shape: BoxShape.circle, color: choice.accentFor(brightness), border: Border.all( color: choice == selected ? colors.ink : colors.border, width: choice == selected ? 2 : 1, ), ), alignment: Alignment.center, child: choice == selected ? Icon( Icons.check_rounded, size: 16, color: choice.inkFor(brightness), ) : null, ), ), ), ), ), ], ); } } class _KeepScreenOnRow extends StatelessWidget { const _KeepScreenOnRow({required this.value, required this.onChanged}); final bool value; final ValueChanged onChanged; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); return Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: colors.surfaceStrong, borderRadius: BorderRadius.circular(13), border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon( 'assets/icons/clock.svg', size: 18, color: colors.inkMuted, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Prevent CarDash from sleeping', style: theme.textTheme.bodyLarge, ), const SizedBox(height: 2), Text( 'Keep the display on while CarDash is in front. Turn off to use the system screen timeout.', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, ), ), ], ), ), const SizedBox(width: 8), SettingsSwitch( value: value, semanticsLabel: value ? 'Allow the display to sleep' : 'Prevent CarDash from sleeping', onChanged: onChanged, ), ], ); } } class _BatteryToggleRow extends StatelessWidget { const _BatteryToggleRow({required this.value, required this.onChanged}); final bool value; final ValueChanged onChanged; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); return Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: colors.surfaceStrong, borderRadius: BorderRadius.circular(13), border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon( 'assets/icons/battery-charging.svg', size: 18, color: colors.inkMuted, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Battery & charging', style: theme.textTheme.bodyLarge), const SizedBox(height: 2), Text( 'Show the battery level and charging bolt.', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, ), ), ], ), ), const SizedBox(width: 8), SettingsSwitch( value: value, semanticsLabel: value ? 'Hide battery and charging status' : 'Show battery and charging status', onChanged: onChanged, ), ], ); } } class _UpdateRow extends StatelessWidget { const _UpdateRow({required this.update}); final UpdateController update; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); final available = update.updateAvailable; final status = update.installing ? 'Downloading update…' : update.checking ? 'Checking for updates…' : available ? 'Version ${update.latestVersion} is available.' : update.latestVersion != null ? 'CarDash ${update.currentVersion} is up to date.' : 'Current version ${update.currentVersion}.'; final detail = update.installMessage ?? update.error; return Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: available ? colors.accentSoft : colors.surfaceStrong, borderRadius: BorderRadius.circular(14), border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon( available ? 'assets/icons/cloud.svg' : 'assets/icons/check.svg', size: 20, color: available ? colors.accent : colors.inkMuted, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('CarDash updates', style: theme.textTheme.titleMedium), const SizedBox(height: 2), Text( status, style: theme.textTheme.bodySmall?.copyWith( color: available ? colors.accent : colors.inkMuted, ), ), if (detail != null) ...[ const SizedBox(height: 2), Text( detail, style: theme.textTheme.bodySmall?.copyWith( color: colors.inkFaint, ), ), ], ], ), ), const SizedBox(width: 10), if (update.checking || update.installing) SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: colors.accent, ), ) else if (available) _SmallButton(label: 'Update', onTap: update.installUpdate) else _SmallButton(label: 'Check', onTap: update.check), ], ); } } class _NowPlayingAccessRow extends StatelessWidget { const _NowPlayingAccessRow({required this.system}); final SystemController system; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); final granted = system.hasMediaAccess; return Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: colors.accentSoft, borderRadius: BorderRadius.circular(14), ), alignment: Alignment.center, child: AppIcon( 'assets/icons/music.svg', size: 20, color: colors.accent, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Now playing', style: theme.textTheme.titleMedium), const SizedBox(height: 2), Text( granted ? 'Notification access is on. Song title and progress are read-only.' : 'Allow notification access to show the song title and progress. No notification content is read.', style: theme.textTheme.bodySmall?.copyWith( color: colors.inkMuted, ), ), ], ), ), const SizedBox(width: 12), if (granted) Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_rounded, size: 17, color: colors.accent), const SizedBox(width: 6), Text( 'Granted', style: theme.textTheme.labelMedium?.copyWith( color: colors.accent, ), ), ], ) else TextButton( onPressed: () async { final opened = await system.openMediaAccessSettings(); if (!opened && context.mounted) { final messenger = ScaffoldMessenger.of(context); messenger.hideCurrentSnackBar(); messenger.showSnackBar( const SnackBar( content: Text( 'Could not open notification access settings.', ), ), ); } }, style: TextButton.styleFrom(foregroundColor: colors.accent), child: const Text('Allow'), ), ], ); } } class _ActionRow extends StatelessWidget { const _ActionRow({ required this.iconAsset, required this.title, this.value, this.trailing, this.onTap, }); final String iconAsset; final String title; final String? value; final Widget? trailing; final VoidCallback? onTap; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); final content = Padding( padding: const EdgeInsets.symmetric(vertical: 7), child: Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: colors.surfaceStrong, borderRadius: BorderRadius.circular(13), border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon(iconAsset, size: 18, color: colors.inkMuted), ), const SizedBox(width: 14), Expanded(child: Text(title, style: theme.textTheme.bodyLarge)), if (value != null) Text( value!, style: theme.textTheme.labelLarge?.copyWith( color: colors.inkMuted, ), ), if (trailing != null) ...[const SizedBox(width: 8), trailing!], ], ), ); if (onTap == null) return content; return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: onTap, child: content, ), ); } } class _ShortcutToggleRow extends StatelessWidget { const _ShortcutToggleRow({ required this.slot, required this.launcher, this.onPick, this.subtitle, this.canToggle = true, }); final SlotType slot; final LauncherController launcher; final VoidCallback? onPick; final String? subtitle; final bool canToggle; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); final enabled = launcher.isSlotEnabled(slot); final assignment = launcher.assignmentFor(slot); final label = subtitle ?? assignment?.label ?? 'Select app'; final showPickPrompt = canToggle && slot.isAppSlot && assignment == null; return Row( children: [ Expanded( child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: canToggle ? onPick : null, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Opacity( opacity: enabled ? 1 : (canToggle ? 0.45 : 0.3), child: Container( width: 38, height: 38, decoration: BoxDecoration( color: colors.surfaceStrong, borderRadius: BorderRadius.circular(13), border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon( slot.iconAsset, size: 18, color: colors.inkMuted, ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( slot.label, style: theme.textTheme.bodyLarge?.copyWith( color: enabled ? colors.ink : colors.inkFaint, ), ), const SizedBox(height: 2), Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: !canToggle || !enabled ? colors.inkFaint : showPickPrompt ? colors.accent : colors.inkMuted, fontWeight: showPickPrompt ? FontWeight.w600 : FontWeight.w400, ), ), ], ), ), ], ), ), ), ), ), const SizedBox(width: 8), SettingsSwitch( value: enabled, enabled: canToggle, semanticsLabel: !canToggle ? '${slot.label} is not configured' : enabled ? 'Hide ${slot.label} on Home' : 'Show ${slot.label} on Home', onChanged: (value) async { final error = await launcher.setSlotEnabled(slot, value); if (error != null && context.mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(error))); } }, ), ], ); } } class _Chevron extends StatelessWidget { const _Chevron(); @override Widget build(BuildContext context) { return AppIcon( 'assets/icons/chevron-right.svg', size: 18, color: context.colors.inkFaint, ); } } class _SmallButton extends StatelessWidget { const _SmallButton({required this.label, required this.onTap}); final String label; final VoidCallback onTap; @override Widget build(BuildContext context) { final colors = context.colors; return Pressable( onTap: onTap, semanticLabel: label, pressedScale: 0.95, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: colors.accent, borderRadius: BorderRadius.circular(999), ), child: Text( label, style: Theme.of(context).textTheme.labelMedium ?.copyWith(color: colors.accentInk, fontWeight: FontWeight.w600), ), ), ); } } class _HomeRoleRow extends StatelessWidget { const _HomeRoleRow({required this.launcher}); final LauncherController launcher; @override Widget build(BuildContext context) { final colors = context.colors; final theme = Theme.of(context); final isDefault = launcher.isDefaultHome; return Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: colors.surfaceStrong, borderRadius: BorderRadius.circular(13), border: Border.all(color: colors.border), ), alignment: Alignment.center, child: AppIcon( 'assets/icons/smartphone.svg', size: 18, color: colors.inkMuted, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Home app', style: theme.textTheme.bodyLarge), const SizedBox(height: 2), Text( launcher.defaultHomeKnown ? (isDefault ? 'CarDash is your home app' : 'Not set') : 'Checking…', style: theme.textTheme.bodySmall?.copyWith( color: isDefault ? colors.accent : colors.inkMuted, fontWeight: isDefault ? FontWeight.w600 : FontWeight.w400, ), ), ], ), ), if (launcher.defaultHomeKnown && !isDefault) _SmallButton( label: 'Set as default', onTap: () async { final launched = await launcher.requestDefaultHome(); if (!launched && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Could not open the home app settings.'), ), ); } }, ) else if (isDefault) Icon(Icons.check_circle_rounded, color: colors.accent, size: 22), ], ); } }