import 'dart:developer' as developer; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tatlock_ui/core/auth/auth_provider.dart'; import 'package:tatlock_ui/core/theme/theme_provider.dart'; import 'package:tatlock_ui/routing/room_registry.dart'; /// Settings page with user preferences. class SettingsPage extends ConsumerWidget { const SettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(authProvider); final currentTheme = ref.watch(themeProvider); final colorScheme = Theme.of(context).colorScheme; final textTheme = Theme.of(context).textTheme; return Scaffold( body: authState.when( data: (auth) => SingleChildScrollView( padding: const EdgeInsets.all(24), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 600), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Page header Text( 'Settings', style: textTheme.headlineMedium, ), const SizedBox(height: 32), // Appearance section _SectionHeader(title: 'Appearance'), const SizedBox(height: 8), Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon( _themeIcon(currentTheme), color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Theme', style: textTheme.titleMedium), Text( 'Choose your preferred color scheme', style: textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ), DropdownButton( value: currentTheme, underline: const SizedBox(), onChanged: (value) { if (value != null) { _updateTheme(ref, value); } }, items: const [ DropdownMenuItem( value: ThemeSetting.system, child: Text('System'), ), DropdownMenuItem( value: ThemeSetting.light, child: Text('Light'), ), DropdownMenuItem( value: ThemeSetting.dark, child: Text('Dark'), ), ], ), ], ), ), ), const SizedBox(height: 24), // Navigation section _SectionHeader(title: 'Navigation'), const SizedBox(height: 8), Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon( Icons.home_outlined, color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Default Room', style: textTheme.titleMedium, ), Text( 'Room to show when app opens', style: textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ), DropdownButton( value: auth.preferences?.defaultRoom ?? 'front-hall', underline: const SizedBox(), onChanged: (value) { if (value != null) { _updateDefaultRoom(ref, value); } }, items: roomRegistry.all .map( (room) => DropdownMenuItem( value: room.id, child: Text(room.label), ), ) .toList(), ), ], ), ), ), const SizedBox(height: 24), // Account section _SectionHeader(title: 'Account'), const SizedBox(height: 8), Card( child: Column( children: [ _AccountInfoTile( icon: Icons.person_outline, label: 'Name', value: auth.userName ?? 'Not available', ), const Divider(height: 1), _AccountInfoTile( icon: Icons.email_outlined, label: 'Email', value: auth.userEmail ?? 'Not available', ), if (auth.roles.isNotEmpty) ...[ const Divider(height: 1), _AccountInfoTile( icon: Icons.shield_outlined, label: 'Roles', value: auth.roles.map((r) => r.name).join(', '), ), ], ], ), ), ], ), ), ), ), loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline, size: 48, color: colorScheme.error), const SizedBox(height: 16), Text( 'Failed to load settings', style: textTheme.titleMedium, ), const SizedBox(height: 8), Text( error.toString(), style: textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ), ), ); } IconData _themeIcon(ThemeSetting theme) { return switch (theme) { ThemeSetting.system => Icons.brightness_auto, ThemeSetting.light => Icons.light_mode, ThemeSetting.dark => Icons.dark_mode, }; } Future _updateTheme(WidgetRef ref, ThemeSetting setting) async { // Update local theme immediately for instant UI response await ref.read(themeProvider.notifier).setSetting(setting); // Sync to backend try { await ref.read(authProvider.notifier).updatePreferences( theme: setting.name, ); } catch (e) { developer.log('Failed to sync theme preference: $e', name: 'settings'); } } Future _updateDefaultRoom(WidgetRef ref, String room) async { try { await ref.read(authProvider.notifier).updatePreferences( defaultRoom: room, ); } catch (e) { developer.log('Failed to sync default room: $e', name: 'settings'); } } } /// Section header widget. class _SectionHeader extends StatelessWidget { const _SectionHeader({required this.title}); final String title; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final textTheme = Theme.of(context).textTheme; return Text( title.toUpperCase(), style: textTheme.labelMedium?.copyWith( color: colorScheme.primary, letterSpacing: 1.0, fontWeight: FontWeight.w600, ), ); } } /// Account info tile widget. class _AccountInfoTile extends StatelessWidget { const _AccountInfoTile({ required this.icon, required this.label, required this.value, }); final IconData icon; final String label; final String value; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final textTheme = Theme.of(context).textTheme; return Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon(icon, color: colorScheme.onSurfaceVariant), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, )), const SizedBox(height: 2), Text(value, style: textTheme.titleMedium), ], ), ), ], ), ); } }