import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../auth/data/providers/auth_provider.dart'; import '../providers/settings_providers.dart'; class SettingsPage extends ConsumerStatefulWidget { const SettingsPage({super.key}); @override ConsumerState createState() => _SettingsPageState(); } class _SettingsPageState extends ConsumerState { @override void initState() { super.initState(); Future.microtask(() { ref.read(accountProfileProvider.notifier).loadProfile(); }); } @override Widget build(BuildContext context) { final settings = ref.watch(settingsProvider); final profile = ref.watch(accountProfileProvider); final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; final cardColor = isDark ? AppColors.surface : Colors.white; final subtitleColor = isDark ? AppColors.textSecondary : Colors.grey[600]; return Scaffold( appBar: AppBar(title: const Text('设置')), body: ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), children: [ // ===== Profile Card (large, like WeChat/Feishu) ===== _buildProfileCard(profile, theme, cardColor, subtitleColor), const SizedBox(height: 24), // ===== General Group ===== _SettingsGroup( cardColor: cardColor, children: [ _SettingsRow( icon: Icons.palette_outlined, iconBg: const Color(0xFF6366F1), title: '外观主题', trailing: _ThemeLabel(mode: settings.themeMode), onTap: () => _showThemePicker(settings.themeMode), ), _SettingsRow( icon: Icons.language, iconBg: const Color(0xFF3B82F6), title: '语言', trailing: Text('简体中文', style: TextStyle(color: subtitleColor, fontSize: 14)), ), ], ), const SizedBox(height: 24), // ===== Notifications Group ===== _SettingsGroup( cardColor: cardColor, children: [ _SettingsToggleRow( icon: Icons.notifications_outlined, iconBg: const Color(0xFFEF4444), title: '推送通知', value: settings.notificationsEnabled, onChanged: (v) => ref.read(settingsProvider.notifier).setNotificationsEnabled(v), ), _SettingsToggleRow( icon: Icons.volume_up_outlined, iconBg: const Color(0xFFF59E0B), title: '提示音', value: settings.soundEnabled, onChanged: settings.notificationsEnabled ? (v) => ref.read(settingsProvider.notifier).setSoundEnabled(v) : null, ), _SettingsToggleRow( icon: Icons.vibration, iconBg: const Color(0xFF22C55E), title: '震动反馈', value: settings.hapticFeedback, onChanged: settings.notificationsEnabled ? (v) => ref.read(settingsProvider.notifier).setHapticFeedback(v) : null, ), ], ), const SizedBox(height: 24), // ===== Security Group ===== _SettingsGroup( cardColor: cardColor, children: [ _SettingsRow( icon: Icons.lock_outline, iconBg: const Color(0xFF8B5CF6), title: '修改密码', onTap: _showChangePasswordSheet, ), ], ), const SizedBox(height: 24), // ===== About Group ===== _SettingsGroup( cardColor: cardColor, children: [ _SettingsRow( icon: Icons.info_outline, iconBg: const Color(0xFF64748B), title: '版本', trailing: Text('v1.0.0', style: TextStyle(color: subtitleColor, fontSize: 14)), ), if (settings.selectedTenantName != null) _SettingsRow( icon: Icons.business_outlined, iconBg: const Color(0xFF0EA5E9), title: '租户', trailing: Text(settings.selectedTenantName!, style: TextStyle(color: subtitleColor, fontSize: 14)), ), ], ), const SizedBox(height: 32), // ===== Logout ===== SizedBox( width: double.infinity, child: OutlinedButton( onPressed: _confirmLogout, style: OutlinedButton.styleFrom( foregroundColor: AppColors.error, side: const BorderSide(color: AppColors.error, width: 1), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: const Text('退出登录', style: TextStyle(fontSize: 16)), ), ), const SizedBox(height: 48), ], ), ); } // ---- Profile Card (top) --------------------------------------------------- Widget _buildProfileCard( AccountProfile profile, ThemeData theme, Color cardColor, Color? subtitleColor) { final initial = profile.displayName.isNotEmpty ? profile.displayName[0].toUpperCase() : '?'; return Card( color: cardColor, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () => _showEditNameDialog(profile.displayName), child: Padding( padding: const EdgeInsets.all(20), child: Row( children: [ // Avatar Container( width: 60, height: 60, decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF6366F1), Color(0xFF818CF8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), ), child: Center( child: Text( initial, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 16), // Name & email Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( profile.displayName.isNotEmpty ? profile.displayName : '加载中...', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, fontSize: 18, ), ), const SizedBox(height: 4), Text( profile.email.isNotEmpty ? profile.email : ' ', style: TextStyle(color: subtitleColor, fontSize: 14), ), ], ), ), Icon(Icons.chevron_right, color: subtitleColor), ], ), ), ), ); } // ---- Theme Picker --------------------------------------------------------- void _showThemePicker(ThemeMode current) { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) { return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 12), Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[400], borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), Text('选择主题', style: Theme.of(ctx).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, )), const SizedBox(height: 8), _ThemeOption( icon: Icons.dark_mode, label: '深色模式', selected: current == ThemeMode.dark, onTap: () { ref.read(settingsProvider.notifier).setThemeMode(ThemeMode.dark); Navigator.pop(ctx); }, ), _ThemeOption( icon: Icons.light_mode, label: '浅色模式', selected: current == ThemeMode.light, onTap: () { ref.read(settingsProvider.notifier).setThemeMode(ThemeMode.light); Navigator.pop(ctx); }, ), _ThemeOption( icon: Icons.settings_brightness, label: '跟随系统', selected: current == ThemeMode.system, onTap: () { ref.read(settingsProvider.notifier).setThemeMode(ThemeMode.system); Navigator.pop(ctx); }, ), const SizedBox(height: 16), ], ), ); }, ); } // ---- Edit Name Dialog ----------------------------------------------------- void _showEditNameDialog(String currentName) { final controller = TextEditingController(text: currentName); showDialog( context: context, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: const Text('修改显示名称'), content: TextField( controller: controller, autofocus: true, decoration: InputDecoration( labelText: '显示名称', hintText: '输入新的显示名称', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), ), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('取消'), ), FilledButton( onPressed: () async { final name = controller.text.trim(); if (name.isEmpty) return; Navigator.of(ctx).pop(); final success = await ref .read(accountProfileProvider.notifier) .updateDisplayName(name); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(success ? '名称已更新' : '更新失败')), ); } }, child: const Text('保存'), ), ], ), ); } // ---- Change Password Sheet ------------------------------------------------ void _showChangePasswordSheet() { final currentCtrl = TextEditingController(); final newCtrl = TextEditingController(); final confirmCtrl = TextEditingController(); showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) => Padding( padding: EdgeInsets.fromLTRB( 24, 24, 24, MediaQuery.of(ctx).viewInsets.bottom + 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[400], borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 16), Text('修改密码', style: Theme.of(ctx) .textTheme .titleLarge ?.copyWith(fontWeight: FontWeight.w600)), const SizedBox(height: 20), TextField( controller: currentCtrl, obscureText: true, decoration: InputDecoration( labelText: '当前密码', prefixIcon: const Icon(Icons.lock_outline), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 14), TextField( controller: newCtrl, obscureText: true, decoration: InputDecoration( labelText: '新密码', prefixIcon: const Icon(Icons.lock_reset), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 14), TextField( controller: confirmCtrl, obscureText: true, decoration: InputDecoration( labelText: '确认新密码', prefixIcon: const Icon(Icons.lock_reset), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 24), FilledButton( style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), onPressed: () async { if (newCtrl.text != confirmCtrl.text) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('两次输入的密码不一致')), ); return; } if (newCtrl.text.length < 6) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('新密码至少6个字符')), ); return; } Navigator.of(ctx).pop(); final result = await ref .read(accountProfileProvider.notifier) .changePassword( currentPassword: currentCtrl.text, newPassword: newCtrl.text, ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(result.success ? '密码已修改' : result.message ?? '修改失败'), ), ); } }, child: const Text('确认修改', style: TextStyle(fontSize: 16)), ), ], ), ), ); } // ---- Confirm Logout ------------------------------------------------------- void _confirmLogout() async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: const Text('退出登录'), content: const Text('确定要退出登录吗?'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('取消'), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: AppColors.error), onPressed: () => Navigator.of(ctx).pop(true), child: const Text('退出'), ), ], ), ); if (confirmed == true) { await ref.read(authStateProvider.notifier).logout(); if (mounted) context.go('/login'); } } } // ============================================================================= // Reusable settings widgets // ============================================================================= /// A group of settings rows wrapped in a rounded card. class _SettingsGroup extends StatelessWidget { final Color cardColor; final List children; const _SettingsGroup({required this.cardColor, required this.children}); @override Widget build(BuildContext context) { return Card( color: cardColor, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), clipBehavior: Clip.antiAlias, child: Column( children: [ for (int i = 0; i < children.length; i++) ...[ children[i], if (i < children.length - 1) Divider( height: 1, indent: 56, color: Theme.of(context).dividerColor.withAlpha(80), ), ], ], ), ); } } /// A single settings row with icon, title, optional trailing, and tap action. class _SettingsRow extends StatelessWidget { final IconData icon; final Color iconBg; final String title; final Widget? trailing; final VoidCallback? onTap; const _SettingsRow({ required this.icon, required this.iconBg, required this.title, this.trailing, this.onTap, }); @override Widget build(BuildContext context) { return ListTile( leading: Container( width: 32, height: 32, decoration: BoxDecoration( color: iconBg, borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: Colors.white, size: 18), ), title: Text(title), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (trailing != null) trailing!, if (onTap != null) ...[ const SizedBox(width: 4), Icon(Icons.chevron_right, size: 20, color: Theme.of(context).hintColor), ], ], ), onTap: onTap, ); } } /// A settings row with a switch toggle. class _SettingsToggleRow extends StatelessWidget { final IconData icon; final Color iconBg; final String title; final bool value; final ValueChanged? onChanged; const _SettingsToggleRow({ required this.icon, required this.iconBg, required this.title, required this.value, this.onChanged, }); @override Widget build(BuildContext context) { return SwitchListTile( secondary: Container( width: 32, height: 32, decoration: BoxDecoration( color: iconBg, borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: Colors.white, size: 18), ), title: Text(title), value: value, onChanged: onChanged, ); } } /// Label showing the current theme mode. class _ThemeLabel extends StatelessWidget { final ThemeMode mode; const _ThemeLabel({required this.mode}); @override Widget build(BuildContext context) { final label = switch (mode) { ThemeMode.dark => '深色', ThemeMode.light => '浅色', ThemeMode.system => '跟随系统', }; return Text(label, style: TextStyle(color: Theme.of(context).hintColor, fontSize: 14)); } } /// Theme option in the bottom sheet picker. class _ThemeOption extends StatelessWidget { final IconData icon; final String label; final bool selected; final VoidCallback onTap; const _ThemeOption({ required this.icon, required this.label, required this.selected, required this.onTap, }); @override Widget build(BuildContext context) { return ListTile( leading: Icon(icon, color: selected ? Theme.of(context).colorScheme.primary : null), title: Text(label, style: TextStyle( fontWeight: selected ? FontWeight.w600 : FontWeight.normal, color: selected ? Theme.of(context).colorScheme.primary : null, )), trailing: selected ? Icon(Icons.check_circle, color: Theme.of(context).colorScheme.primary) : null, onTap: onTap, ); } }