import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; import '../../providers/auth_provider.dart'; import '../../providers/upload_provider.dart'; import '../../services/upload_service.dart'; import '../feed/widgets/feed_overlays.dart'; /// 发布作品页(《03-接口文档》5.5/5.6): /// 三步:选视频 → 填信息(标题/描述/分类/标签) → 发布结果(进我的作品查看审核状态) class UploadScreen extends ConsumerStatefulWidget { const UploadScreen({super.key}); @override ConsumerState createState() => _UploadScreenState(); } class _UploadScreenState extends ConsumerState { final _titleCtrl = TextEditingController(); final _descCtrl = TextEditingController(); final _tagsCtrl = TextEditingController(); int? _categoryId; @override void dispose() { _titleCtrl.dispose(); _descCtrl.dispose(); _tagsCtrl.dispose(); super.dispose(); } /// 解析标签输入:空格/逗号/顿号分隔,去重,最多 5 个 List _parseTags() { return _tagsCtrl.text .split(RegExp(r'[\s,,、]+')) .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toSet() .take(5) .toList(); } void _submit() { final title = _titleCtrl.text.trim(); if (title.isEmpty) { showTopToast(context, '请填写标题'); return; } if (_categoryId == null) { showTopToast(context, '请选择分类'); return; } ref.read(uploadProvider.notifier).submit( title: title, description: _descCtrl.text.trim(), categoryId: _categoryId!, tags: _parseTags(), ); } @override Widget build(BuildContext context) { final isLoggedIn = ref.watch(authProvider).isLoggedIn; if (!isLoggedIn) { return Scaffold( appBar: AppBar(title: const Text('发布作品')), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.video_call_outlined, size: 64, color: AppColors.textWeak), const SizedBox(height: 12), const Text('登录后发布你的作品', style: TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 16), ElevatedButton( onPressed: () => context.push('/login'), style: ElevatedButton.styleFrom(minimumSize: const Size(160, 44)), child: const Text('去登录 / 注册'), ), ], ), ), ); } // 发布失败:停留表单并提示服务端文案 ref.listen(uploadProvider, (prev, next) { if (next.error.isNotEmpty && next.error != prev?.error) { showTopToast(context, next.error); } }); final state = ref.watch(uploadProvider); return Scaffold( appBar: AppBar(title: const Text('发布作品')), body: switch (state.step) { UploadStep.pick => _PickStep( onPick: () => ref.read(uploadProvider.notifier).pickVideo(), ), UploadStep.edit || UploadStep.publishing => _EditStep( state: state, titleCtrl: _titleCtrl, descCtrl: _descCtrl, tagsCtrl: _tagsCtrl, categoryId: _categoryId, onCategoryChanged: (id) => setState(() => _categoryId = id), onReselect: () => ref.read(uploadProvider.notifier).reset(), onSubmit: _submit, ), UploadStep.done => _DoneStep(result: state.result), }, ); } } /// 第一步:选择视频 class _PickStep extends StatelessWidget { const _PickStep({required this.onPick}); final VoidCallback onPick; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cloud_upload_outlined, size: 72, color: AppColors.textWeak), const SizedBox(height: 16), const Text('选择相册中的视频', style: TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 4), const Text('支持 mp4 / mov 等常见格式,不超过 500MB', style: TextStyle(color: AppColors.textWeak, fontSize: 12)), const SizedBox(height: 24), ElevatedButton( onPressed: onPick, style: ElevatedButton.styleFrom(minimumSize: const Size(180, 48)), child: const Text('选择视频'), ), ], ), ); } } /// 第二步:填写作品信息(发布中显示进度) class _EditStep extends ConsumerWidget { const _EditStep({ required this.state, required this.titleCtrl, required this.descCtrl, required this.tagsCtrl, required this.categoryId, required this.onCategoryChanged, required this.onReselect, required this.onSubmit, }); final UploadState state; final TextEditingController titleCtrl; final TextEditingController descCtrl; final TextEditingController tagsCtrl; final int? categoryId; final ValueChanged onCategoryChanged; final VoidCallback onReselect; final VoidCallback onSubmit; @override Widget build(BuildContext context, WidgetRef ref) { final publishing = state.publishing; final fileName = state.file?.name ?? ''; final sizeMB = (state.fileSize / 1024 / 1024).toStringAsFixed(1); final asyncCategories = ref.watch(uploadCategoriesProvider); return ListView( padding: const EdgeInsets.all(16), children: [ // 已选视频信息 Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.videocam_outlined, color: AppColors.textSecondary), const SizedBox(width: 8), Expanded( child: Text( '$fileName($sizeMB MB)', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13), ), ), if (!publishing) TextButton( onPressed: onReselect, child: const Text('重新选择')), ], ), ), const SizedBox(height: 16), TextField( controller: titleCtrl, enabled: !publishing, maxLength: 100, decoration: const InputDecoration( labelText: '标题', hintText: '给你的作品起个标题(必填)', ), ), const SizedBox(height: 8), TextField( controller: descCtrl, enabled: !publishing, maxLength: 500, maxLines: 3, decoration: const InputDecoration( labelText: '简介', hintText: '补充介绍一下(选填)', ), ), const SizedBox(height: 8), const Text('分类', style: TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 8), asyncCategories.when( loading: () => const LinearProgressIndicator(), error: (_, __) => const Text('分类加载失败', style: TextStyle(color: AppColors.textWeak, fontSize: 12)), data: (categories) => Wrap( spacing: 8, runSpacing: 8, children: [ for (final c in categories) ChoiceChip( label: Text(c.name), selected: categoryId == c.categoryId, onSelected: publishing ? null : (_) => onCategoryChanged(c.categoryId), ), ], ), ), const SizedBox(height: 16), TextField( controller: tagsCtrl, enabled: !publishing, decoration: const InputDecoration( labelText: '标签', hintText: '空格分隔,最多 5 个(选填)', ), ), const SizedBox(height: 24), if (publishing) ...[ LinearProgressIndicator(value: state.progress / 100), const SizedBox(height: 8), Text( state.progress < 100 ? '上传中 ${state.progress}%' : '发布中…', textAlign: TextAlign.center, style: const TextStyle(color: AppColors.textSecondary, fontSize: 12), ), ] else ElevatedButton( onPressed: onSubmit, style: ElevatedButton.styleFrom(minimumSize: const Size(0, 48)), child: const Text('发布'), ), ], ); } } /// 第三步:发布结果(引导去我的作品查看审核进度) class _DoneStep extends StatelessWidget { const _DoneStep({required this.result}); final PublishResult? result; @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check_circle_outline, size: 72, color: AppColors.secondary), const SizedBox(height: 16), const Text('发布成功', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), const SizedBox(height: 8), Text( '当前状态:${result?.statusText ?? '待审核'},审核通过后自动上架', textAlign: TextAlign.center, style: const TextStyle(color: AppColors.textSecondary, fontSize: 13), ), const SizedBox(height: 24), ElevatedButton( onPressed: () => context.pushReplacement('/mine/works'), style: ElevatedButton.styleFrom(minimumSize: const Size(180, 44)), child: const Text('查看我的作品'), ), TextButton( onPressed: () => Navigator.of(context).maybePop(), child: const Text('返回', style: TextStyle(color: AppColors.textSecondary)), ), ], ), ), ); } }