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/network/api_exception.dart'; import '../../core/theme/app_theme.dart'; import '../../models/user_profile.dart'; import '../../models/video_item.dart'; import '../../providers/profile_provider.dart'; import '../../services/user_service.dart'; import '../../services/video_service.dart'; import '../feed/widgets/feed_overlays.dart'; /// 我的作品 / 我的收藏(双列网格,结构相同仅数据源不同) class VideoGridScreen extends ConsumerWidget { VideoGridScreen.works({super.key}) : title = '我的作品', provider = myVideosProvider, editable = true; VideoGridScreen.favorites({super.key}) : title = '我的收藏', provider = myFavoritesProvider, editable = false; final String title; final AutoDisposeFutureProvider provider; /// 我的作品可长按管理(编辑/删除),收藏不可 final bool editable; @override Widget build(BuildContext context, WidgetRef ref) { final asyncPage = ref.watch(provider); return Scaffold( appBar: AppBar(title: Text(title)), body: asyncPage.when( loading: () => const Center( child: CircularProgressIndicator(color: AppColors.primary), ), error: (_, __) => Center( child: TextButton( onPressed: () => ref.invalidate(provider), child: const Text('加载失败,点击重试', style: TextStyle(color: AppColors.primary)), ), ), data: (page) => page.items.isEmpty ? const Center( child: Text('暂无内容', style: TextStyle(color: AppColors.textSecondary)), ) : GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 0.68, ), itemCount: page.items.length, itemBuilder: (_, index) => _GridCard(item: page.items[index], editable: editable), ), ), ); } } class _GridCard extends ConsumerWidget { const _GridCard({required this.item, this.editable = false}); final VideoItem item; /// 长按弹出管理菜单(仅我的作品) final bool editable; /// 我的作品卡片点击:仅已发布可播放,其余状态提示原因 void _onTap(BuildContext context) { if (item.status == -1 || item.status == 2) { context.push('/search/play', extra: item); return; } showTopToast( context, item.status == 3 && item.rejectReason.isNotEmpty ? '审核拒绝:${item.rejectReason}' : '视频${item.statusText},暂不可播放', ); } void _showManageSheet(BuildContext context, WidgetRef ref) { showModalBottomSheet( context: context, backgroundColor: AppColors.surface, builder: (sheetCtx) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.edit_outlined), title: const Text('编辑信息'), onTap: () { Navigator.pop(sheetCtx); context.push('/mine/works/edit', extra: item); }, ), ListTile( leading: const Icon(Icons.delete_outline, color: AppColors.primary), title: const Text('删除作品', style: TextStyle(color: AppColors.primary)), onTap: () { Navigator.pop(sheetCtx); _confirmDelete(context, ref); }, ), ], ), ), ); } Future _confirmDelete(BuildContext context, WidgetRef ref) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: AppColors.surface, title: const Text('删除作品'), content: const Text('删除后作品立即下架且不可恢复,确定删除?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('取消', style: TextStyle(color: AppColors.textSecondary)), ), TextButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('删除', style: TextStyle(color: AppColors.primary)), ), ], ), ); if (confirmed != true || !context.mounted) return; try { await VideoService.deleteVideo(item.videoId); ref.invalidate(myVideosProvider); if (context.mounted) showTopToast(context, '已删除'); } on ApiException catch (e) { if (context.mounted) showTopToast(context, e.message); } catch (_) { if (context.mounted) showTopToast(context, '网络异常,请稍后重试'); } } @override Widget build(BuildContext context, WidgetRef ref) { return GestureDetector( onTap: () => _onTap(context), onLongPress: editable ? () => _showManageSheet(context, ref) : null, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Stack( fit: StackFit.expand, children: [ Container( color: AppColors.surface, child: item.coverUrl.isNotEmpty ? CachedNetworkImage( imageUrl: item.coverUrl, fit: BoxFit.cover, errorWidget: (_, __, ___) => const Icon( Icons.play_circle_outline, color: AppColors.textWeak, size: 40, ), ) : const Icon( Icons.play_circle_outline, color: AppColors.textWeak, size: 40, ), ), // 作品状态角标(我的作品:待审核/拒绝/下架等) if (item.statusText.isNotEmpty) Positioned( left: 6, top: 6, child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: item.status == 3 ? AppColors.primary : item.status == 2 ? AppColors.secondary : Colors.orange, borderRadius: BorderRadius.circular(4), ), child: Text( item.statusText, style: const TextStyle( color: Colors.black, fontSize: 10, fontWeight: FontWeight.w700, ), ), ), ), Positioned( left: 0, right: 0, bottom: 0, child: Container( color: Colors.black54, padding: const EdgeInsets.all(6), child: Text( item.title.isNotEmpty ? item.title : '未命名视频', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12), ), ), ), ], ), ), ); } } /// 观看历史(列表:封面缩略图 + 标题 + 观看时间 + 计费方式) class HistoryScreen extends ConsumerWidget { const HistoryScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final asyncList = ref.watch(myHistoryProvider); return Scaffold( appBar: AppBar(title: const Text('观看历史')), body: asyncList.when( loading: () => const Center( child: CircularProgressIndicator(color: AppColors.primary), ), error: (_, __) => Center( child: TextButton( onPressed: () => ref.invalidate(myHistoryProvider), child: const Text('加载失败,点击重试', style: TextStyle(color: AppColors.primary)), ), ), data: (items) => items.isEmpty ? const Center( child: Text('暂无观看记录', style: TextStyle(color: AppColors.textSecondary)), ) : ListView.builder( itemCount: items.length, itemBuilder: (_, index) => _HistoryTile(item: items[index]), ), ), ); } } class _HistoryTile extends StatelessWidget { const _HistoryTile({required this.item}); final HistoryItem item; @override Widget build(BuildContext context) { return ListTile( onTap: () => context.push('/search/play', extra: item.video), leading: ClipRRect( borderRadius: BorderRadius.circular(6), child: Container( width: 72, height: 48, color: AppColors.surface, child: item.video.coverUrl.isNotEmpty ? CachedNetworkImage( imageUrl: item.video.coverUrl, fit: BoxFit.cover, errorWidget: (_, __, ___) => const Icon(Icons.play_circle_outline, size: 24), ) : const Icon(Icons.play_circle_outline, size: 24), ), ), title: Text( item.video.title.isNotEmpty ? item.video.title : '未命名视频', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 14), ), subtitle: Text( '${item.watchedAt.length >= 16 ? item.watchedAt.substring(5, 16) : item.watchedAt} · ${item.costLabel}', style: const TextStyle(color: AppColors.textWeak, fontSize: 12), ), ); } }