import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:im_app/features/settings/presentation/settings_view_model.dart'; /// 我的页面(原设置页) /// /// 结构: /// ┌─ 个人资料卡 ──────────────────────────────────────────┐ /// │ 头像 昵称 │ /// │ 手机号(掩码) UID: xxx │ /// └──────────────────────────────────────────────────────┘ /// 偏好设置 → 主题 / 语言 / 通知 /// 工具 → 黑名单 / 网络诊断 /// 关于 → 关于本应用 /// [退出登录] class SettingsPage extends ConsumerWidget { const SettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(settingsViewModelProvider); final vm = ref.read(settingsViewModelProvider.notifier); return Scaffold( backgroundColor: Theme.of(context).colorScheme.surfaceContainerLowest, body: CustomScrollView( slivers: [ SliverAppBar.large( title: const Text('我的'), automaticallyImplyLeading: false, ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: _ProfileCard(state: state, onTap: () => vm.navigateToEditProfile(context)), ), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _SectionLabel('偏好设置'), _SettingsCard( items: [ _RowConfig( icon: Icons.palette_outlined, title: '主题', onTap: () => vm.navigateToTheme(context), ), _RowConfig( icon: Icons.language, title: '语言', onTap: () => vm.navigateToLanguage(context), ), _RowConfig( icon: Icons.notifications_outlined, title: '通知', onTap: () {}, // TODO: 通知设置页 ), ], ), const SizedBox(height: 16), _SectionLabel('工具'), _SettingsCard( items: [ _RowConfig( icon: Icons.folder_outlined, title: '聊天文件夹', onTap: () {}, // TODO: Issue #11 ), _RowConfig( icon: Icons.block, title: '黑名单', onTap: () => vm.navigateToBlocklist(context), ), _RowConfig( icon: Icons.network_check, title: '网络诊断', onTap: () => vm.navigateToNetworkDiagnostics(context), ), ], ), const SizedBox(height: 16), _SectionLabel('关于'), _SettingsCard( items: [ _RowConfig( icon: Icons.info_outline, title: '关于本应用', onTap: () => vm.navigateToAbout(context), ), ], ), const SizedBox(height: 24), _LogoutButton( isLoading: state.isLoggingOut, onTap: () => _confirmLogout(context, ref), ), const SizedBox(height: 32), ], ), ), ), ], ), ); } Future _confirmLogout(BuildContext context, WidgetRef ref) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('退出登录'), content: const Text('确定要退出当前账号吗?'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.of(ctx).pop(true), style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('退出'), ), ], ), ); if (confirmed == true) { ref.read(settingsViewModelProvider.notifier).logout(); } } } // ── 个人资料卡 ───────────────────────────────────────────────────────────────── class _ProfileCard extends StatelessWidget { const _ProfileCard({required this.state, required this.onTap}); final SettingsState state; final VoidCallback onTap; @override Widget build(BuildContext context) { return Card( child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ _Avatar(avatarUrl: state.avatarUrl, isLoading: state.isLoading), const SizedBox(width: 16), Expanded( child: state.isLoading ? _ProfileSkeleton() : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( state.nickname.isEmpty ? '加载中…' : state.nickname, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), Text( state.maskedContact.isNotEmpty ? state.maskedContact : 'UID: ${state.uid}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey, ), ), if (state.maskedContact.isNotEmpty && state.uid > 0) Text( 'UID: ${state.uid}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey, ), ), ], ), ), const Icon(Icons.chevron_right, color: Colors.grey), ], ), ), ), ); } } class _Avatar extends StatelessWidget { const _Avatar({required this.avatarUrl, required this.isLoading}); final String? avatarUrl; final bool isLoading; @override Widget build(BuildContext context) { if (isLoading) { return const CircleAvatar( radius: 28, child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ); } if (avatarUrl != null && avatarUrl!.isNotEmpty) { return CircleAvatar( radius: 28, backgroundImage: NetworkImage(avatarUrl!), ); } return const CircleAvatar( radius: 28, child: Icon(Icons.person, size: 28), ); } } class _ProfileSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 16, width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 8), Container( height: 12, width: 80, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), ), ], ); } } // ── 设置分组 ─────────────────────────────────────────────────────────────────── class _SectionLabel extends StatelessWidget { const _SectionLabel(this.text); final String text; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 4, bottom: 8), child: Text( text, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w600, ), ), ); } } class _RowConfig { const _RowConfig({ required this.icon, required this.title, this.subtitle, required this.onTap, }); final IconData icon; final String title; final String? subtitle; final VoidCallback onTap; } class _SettingsCard extends StatelessWidget { const _SettingsCard({required this.items}); final List<_RowConfig> items; @override Widget build(BuildContext context) { return Card( child: Column( children: [ for (int i = 0; i < items.length; i++) ...[ if (i > 0) Divider( height: 1, indent: 52, color: Theme.of(context).dividerColor.withOpacity(0.5), ), _SettingsRow(config: items[i]), ], ], ), ); } } class _SettingsRow extends StatelessWidget { const _SettingsRow({required this.config}); final _RowConfig config; @override Widget build(BuildContext context) { return ListTile( leading: Icon(config.icon, size: 22), title: Text(config.title), subtitle: config.subtitle != null ? Text(config.subtitle!) : null, trailing: const Icon(Icons.chevron_right, size: 18, color: Colors.grey), onTap: config.onTap, ); } } // ── 退出登录 ─────────────────────────────────────────────────────────────────── class _LogoutButton extends StatelessWidget { const _LogoutButton({required this.isLoading, required this.onTap}); final bool isLoading; final VoidCallback onTap; @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, child: FilledButton( onPressed: isLoading ? null : onTap, style: FilledButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), ) : const Text('退出登录', style: TextStyle(fontSize: 16)), ), ); } }