import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../providers/session_provider.dart'; import '../../providers/sm_account_provider.dart'; import '../../providers/update_provider.dart'; import '../../shared/app_info.dart'; import '../../shared/theme/app_theme.dart'; import '../../shared/widgets/app_card.dart'; import '../../shared/widgets/app_loading_ring.dart'; import '../../shared/widgets/app_inline_alert.dart'; import '../../shared/widgets/app_sliver_header.dart'; import '../../shared/widgets/page_background.dart'; import '../../shared/widgets/server_avatar.dart'; import '../modules/modules_home_page.dart'; import '../settings/settings_page.dart'; class ProfilePage extends ConsumerWidget { const ProfilePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final activeSession = ref.watch(activeSessionProvider); final account = ref.watch(smAccountProvider).value; final appVersion = ref.watch(appVersionProvider).value ?? kSmPlayerVersionFallback; final updateState = ref.watch(updateProvider); return Scaffold( backgroundColor: Colors.transparent, extendBodyBehindAppBar: true, body: Stack( children: [ const Positioned.fill(child: PageBackground()), CustomScrollView( physics: const AlwaysScrollableScrollPhysics( parent: BouncingScrollPhysics(), ), slivers: [ const AppSliverHeader(title: '我的'), SliverPadding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 32), sliver: SliverList( delegate: SliverChildListDelegate.fixed([ _SearchEntry(onTap: () => context.push('/global-search')), const SizedBox(height: 12), _ServerAccountCard( serverName: activeSession?.serverName ?? '未连接服务器', userName: activeSession?.userName ?? '添加或登录 Emby 服务器', iconUrl: '', active: activeSession != null, onTap: () => context.go('/servers'), ), const SizedBox(height: 12), _SmAccountCard( account: account, onTap: () => context.push('/account-center'), ), const SizedBox(height: 18), const _SectionTitle('快捷入口'), _MenuCard( children: [ _ProfileMenuItem( icon: Icons.history_rounded, title: '观看历史', subtitle: '跨服务器继续观看', onTap: () => context.go('/history'), ), _ProfileMenuItem( icon: Icons.favorite_outline_rounded, title: '我的收藏', subtitle: '查看所有服务器收藏', onTap: () => context.go('/favorites-all'), ), _ProfileMenuItem( icon: Icons.new_releases_outlined, title: '最近添加', subtitle: '按服务器聚合最新内容', onTap: () => context.go('/recent'), ), ], ), const SizedBox(height: 18), const _SectionTitle('应用'), _MenuCard( children: [ _ProfileMenuItem( icon: Icons.extension_rounded, title: '模块中心', subtitle: '导入、配置与浏览 ForwardWidgets 模块', onTap: () => showModuleCenterSheet(context), ), _ProfileMenuItem( icon: Icons.settings_outlined, title: '设置', subtitle: '播放、账号与外观', onTap: () => showSettingsDialog(context), ), _ProfileMenuItem( icon: Icons.update_outlined, title: '检查更新', subtitle: _updateSubtitle(updateState, appVersion), trailing: updateState.status == UpdateStatus.checking ? const AppLoadingRing(size: 18) : null, onTap: updateState.status == UpdateStatus.checking || updateState.status == UpdateStatus.downloading || updateState.status == UpdateStatus.readyToInstall ? null : () => ref .read(updateProvider.notifier) .manualCheck(), ), ], ), if (updateState.status == UpdateStatus.error) ...[ const SizedBox(height: 12), AppInlineAlert( message: updateState.error ?? '检查更新失败', tone: AppAlertTone.error, ), ], ]), ), ), ], ), ], ), ); } String _updateSubtitle(UpdateState state, String appVersion) { return switch (state.status) { UpdateStatus.checking => '当前版本 $appVersion,正在检查', UpdateStatus.upToDate => '当前版本 $appVersion,已是最新版本', UpdateStatus.available => '发现新版本 ${state.info?.version ?? ''}', UpdateStatus.downloading => '正在下载更新', UpdateStatus.readyToInstall => '正在打开安装程序', UpdateStatus.error => state.error ?? '检查失败', UpdateStatus.idle => '当前版本 $appVersion', }; } } class _SearchEntry extends StatelessWidget { final VoidCallback onTap; const _SearchEntry({required this.onTap}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return AppCard( onTap: onTap, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ Icon(Icons.search_rounded, color: theme.colorScheme.primary), const SizedBox(width: 12), Expanded( child: Text( '搜索电影、剧集、演员', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.62), ), ), ), const Icon(Icons.chevron_right_rounded, size: 20), ], ), ); } } class _ServerAccountCard extends StatelessWidget { final String serverName; final String userName; final String iconUrl; final bool active; final VoidCallback onTap; const _ServerAccountCard({ required this.serverName, required this.userName, required this.iconUrl, required this.active, required this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return AppCard( onTap: onTap, padding: const EdgeInsets.all(16), child: Row( children: [ ServerAvatar( name: serverName, iconUrl: iconUrl, size: 52, isActive: active, showActiveBorder: true, ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( serverName, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: 3), Text( userName, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.58), ), ), ], ), ), const Icon(Icons.chevron_right_rounded), ], ), ); } } class _SmAccountCard extends StatelessWidget { final SmAccountState? account; final VoidCallback onTap; const _SmAccountCard({required this.account, required this.onTap}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final status = account?.status ?? SmAuthStatus.unauthenticated; final loggedIn = status == SmAuthStatus.authenticated; final title = loggedIn ? 'player 账号' : '登录 player 账号'; final subtitle = loggedIn ? (account?.email.isNotEmpty == true ? account!.email : '已登录') : '用于 TMDB、Trakt 等扩展服务'; return AppCard( onTap: onTap, padding: const EdgeInsets.all(16), child: Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: theme.colorScheme.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), ), child: Icon( loggedIn ? Icons.verified_user_outlined : Icons.person_add_alt_1, color: theme.colorScheme.primary, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: theme.textTheme.titleSmall), const SizedBox(height: 3), Text( subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.58), ), ), ], ), ), const Icon(Icons.chevron_right_rounded), ], ), ); } } class _SectionTitle extends StatelessWidget { final String text; const _SectionTitle(this.text); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(4, 0, 4, 8), child: Text( text, style: Theme.of(context).textTheme.labelLarge?.copyWith( color: context.appTokens.mutedForeground, fontWeight: FontWeight.w600, ), ), ); } } class _MenuCard extends StatelessWidget { final List children; const _MenuCard({required this.children}); @override Widget build(BuildContext context) { return AppCard( padding: EdgeInsets.zero, child: Column( children: [ for (var i = 0; i < children.length; i++) ...[ if (i > 0) Divider( height: 1, indent: 56, color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.08), ), children[i], ], ], ), ); } } class _ProfileMenuItem extends StatelessWidget { final IconData icon; final String title; final String? subtitle; final Widget? trailing; final VoidCallback? onTap; const _ProfileMenuItem({ required this.icon, required this.title, this.subtitle, this.trailing, this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), child: Row( children: [ Icon(icon, size: 22, color: theme.colorScheme.primary), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: theme.textTheme.bodyMedium), if (subtitle != null) ...[ const SizedBox(height: 2), Text( subtitle!, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withValues( alpha: 0.54, ), ), ), ], ], ), ), trailing ?? const Icon(Icons.chevron_right_rounded, size: 20), ], ), ), ); } }