import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/contracts/auth.dart'; import '../../core/contracts/library.dart'; import '../../providers/emby_headers_provider.dart'; import '../../providers/person_items_provider.dart'; import '../../providers/session_provider.dart'; import '../../shared/constants/layout_constants.dart'; import '../../shared/mappers/media_image_url.dart'; import '../../shared/utils/media_nav.dart'; import '../../shared/widgets/app_error_state.dart'; import '../../shared/widgets/app_loading_ring.dart'; import '../../shared/widgets/app_sliver_header.dart'; import '../../shared/widgets/empty_state.dart'; import '../../shared/widgets/media_poster_card.dart'; class PersonDetailPage extends ConsumerWidget { final String embyPersonId; const PersonDetailPage({super.key, required this.embyPersonId}); @override Widget build(BuildContext context, WidgetRef ref) { final session = ref.watch(activeSessionProvider); if (session == null) { return Scaffold( extendBodyBehindAppBar: true, body: CustomScrollView( slivers: [ AppSliverHeader(title: '演员', leading: _buildBackButton(context)), const SliverFillRemaining( hasScrollBody: false, child: _EmptyState(message: '请先登录服务器'), ), ], ), ); } final asyncDetail = ref.watch(personItemDetailProvider(embyPersonId)); final asyncItems = ref.watch(personItemsProvider(embyPersonId)); final imageHeaders = ref.watch(embyImageHeadersProvider).value; final personName = asyncDetail.value?.Name; final title = (personName != null && personName.isNotEmpty) ? '演员 $personName 的全部作品' : '演员'; final leadingAvatar = _personAvatar( context: context, session: session, person: asyncDetail.value, headers: imageHeaders, ); final Widget titleWidget = Row( children: [ if (leadingAvatar != null) ...[ leadingAvatar, const SizedBox(width: 12), ], Expanded( child: Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium, ), ), ], ); final Widget contentSliver = asyncItems.when( loading: () => const SliverFillRemaining( hasScrollBody: false, child: Center(child: AppLoadingRing()), ), error: (_, _) => SliverFillRemaining( hasScrollBody: false, child: AppErrorState( title: '查询失败', onRetry: () => ref.invalidate(personItemsProvider(embyPersonId)), ), ), data: (items) { if (items.isEmpty) { return const SliverFillRemaining( hasScrollBody: false, child: EmptyState(icon: Icons.movie_filter_outlined, title: '暂无作品'), ); } return SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), sliver: SliverGrid( gridDelegate: mediaGridDelegate, delegate: SliverChildBuilderDelegate((_, i) { final item = items[i]; return MediaPosterCard( item: item, width: double.infinity, imageUrl: EmbyImageUrl.primary( baseUrl: session.serverUrl, token: session.token, item: item, maxHeight: 360, ), preloadImageUrl: EmbyImageUrl.detailBanner( baseUrl: session.serverUrl, token: session.token, item: item, ), imageHeaders: imageHeaders, onTap: () => goMediaDetail(context, item, ref: ref), ); }, childCount: items.length), ), ); }, ); return Scaffold( extendBodyBehindAppBar: true, body: CustomScrollView( slivers: [ AppSliverHeader( titleWidget: titleWidget, leading: _buildBackButton(context), ), contentSliver, ], ), ); } Widget _buildBackButton(BuildContext context) { return IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { if (context.canPop()) { context.pop(); } else { context.go('/'); } }, ); } Widget? _personAvatar({ required BuildContext context, required AuthedSession session, required EmbyRawItem? person, required Map? headers, }) { if (person == null) return null; final hasImage = (person.PrimaryImageTag != null) || (person.ImageTags?['Primary'] != null); if (!hasImage) return null; final url = EmbyImageUrl.primaryById( baseUrl: session.serverUrl, token: session.token, itemId: person.Id, maxHeight: 80, ); return CircleAvatar( radius: 16, backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, backgroundImage: ResizeImage.resizeIfNeeded( 96, null, CachedNetworkImageProvider(url, headers: headers), ), ); } } class _EmptyState extends StatelessWidget { final String message; const _EmptyState({required this.message}); @override Widget build(BuildContext context) { return Center( child: Text( message, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 14, ), ), ); } }