201 lines
6.3 KiB
Dart
201 lines
6.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../core/network/api_exception.dart';
|
|
import '../../core/theme/app_theme.dart';
|
|
import '../../models/video_item.dart';
|
|
import '../../providers/profile_provider.dart';
|
|
import '../../providers/upload_provider.dart';
|
|
import '../../services/video_service.dart';
|
|
import '../feed/widgets/feed_overlays.dart';
|
|
|
|
/// 编辑作品(《03-接口文档》5.12 / 5.13,仅作者):
|
|
/// 可改标题/简介/分类/标签;删除为服务端软删除,二次确认后立即下架。
|
|
class EditVideoScreen extends ConsumerStatefulWidget {
|
|
const EditVideoScreen({super.key, required this.video});
|
|
|
|
final VideoItem video;
|
|
|
|
@override
|
|
ConsumerState<EditVideoScreen> createState() => _EditVideoScreenState();
|
|
}
|
|
|
|
class _EditVideoScreenState extends ConsumerState<EditVideoScreen> {
|
|
late final TextEditingController _titleCtrl;
|
|
late final TextEditingController _descCtrl;
|
|
late final TextEditingController _tagsCtrl;
|
|
late int _categoryId;
|
|
bool _submitting = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final v = widget.video;
|
|
_titleCtrl = TextEditingController(text: v.title);
|
|
_descCtrl = TextEditingController(text: v.description);
|
|
_tagsCtrl = TextEditingController(text: v.tags.join(' '));
|
|
_categoryId = v.categoryId;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_titleCtrl.dispose();
|
|
_descCtrl.dispose();
|
|
_tagsCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
/// 空格/逗号/顿号分隔,去重,最多 5 个
|
|
List<String> _parseTags() => _tagsCtrl.text
|
|
.split(RegExp(r'[\s,,、]+'))
|
|
.map((e) => e.trim())
|
|
.where((e) => e.isNotEmpty)
|
|
.toSet()
|
|
.take(5)
|
|
.toList();
|
|
|
|
void _toast(String message) {
|
|
if (mounted) showTopToast(context, message);
|
|
}
|
|
|
|
Future<void> _save() async {
|
|
final title = _titleCtrl.text.trim();
|
|
if (title.isEmpty) {
|
|
_toast('请填写标题');
|
|
return;
|
|
}
|
|
setState(() => _submitting = true);
|
|
try {
|
|
await VideoService.updateVideo(
|
|
widget.video.videoId,
|
|
title: title,
|
|
description: _descCtrl.text.trim(),
|
|
categoryId: _categoryId,
|
|
tags: _parseTags(),
|
|
);
|
|
// 刷新我的作品列表,返回后展示最新信息
|
|
ref.invalidate(myVideosProvider);
|
|
_toast('已保存');
|
|
if (mounted) Navigator.of(context).maybePop();
|
|
} on ApiException catch (e) {
|
|
_toast(e.message);
|
|
} catch (_) {
|
|
_toast('网络异常,请稍后重试');
|
|
} finally {
|
|
if (mounted) setState(() => _submitting = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _confirmDelete() async {
|
|
final confirmed = await showDialog<bool>(
|
|
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) return;
|
|
|
|
setState(() => _submitting = true);
|
|
try {
|
|
await VideoService.deleteVideo(widget.video.videoId);
|
|
ref.invalidate(myVideosProvider);
|
|
_toast('已删除');
|
|
if (mounted) Navigator.of(context).maybePop();
|
|
} on ApiException catch (e) {
|
|
_toast(e.message);
|
|
} catch (_) {
|
|
_toast('网络异常,请稍后重试');
|
|
} finally {
|
|
if (mounted) setState(() => _submitting = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final categories = ref.watch(uploadCategoriesProvider);
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('编辑作品')),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
TextField(
|
|
controller: _titleCtrl,
|
|
enabled: !_submitting,
|
|
maxLength: 100,
|
|
decoration: const InputDecoration(labelText: '标题'),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextField(
|
|
controller: _descCtrl,
|
|
enabled: !_submitting,
|
|
maxLength: 500,
|
|
maxLines: 3,
|
|
decoration: const InputDecoration(labelText: '简介'),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text('分类', style: TextStyle(color: AppColors.textSecondary)),
|
|
const SizedBox(height: 8),
|
|
categories.when(
|
|
loading: () => const LinearProgressIndicator(),
|
|
error: (_, __) => const Text('分类加载失败',
|
|
style: TextStyle(color: AppColors.textWeak, fontSize: 12)),
|
|
data: (list) => Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: [
|
|
for (final c in list)
|
|
ChoiceChip(
|
|
label: Text(c.name),
|
|
selected: _categoryId == c.categoryId,
|
|
onSelected: _submitting
|
|
? null
|
|
: (_) => setState(() => _categoryId = c.categoryId),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: _tagsCtrl,
|
|
enabled: !_submitting,
|
|
decoration: const InputDecoration(
|
|
labelText: '标签',
|
|
hintText: '空格分隔,最多 5 个',
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton(
|
|
onPressed: _submitting ? null : _save,
|
|
style: ElevatedButton.styleFrom(minimumSize: const Size(0, 48)),
|
|
child: const Text('保存'),
|
|
),
|
|
const SizedBox(height: 8),
|
|
OutlinedButton(
|
|
onPressed: _submitting ? null : _confirmDelete,
|
|
style: OutlinedButton.styleFrom(
|
|
minimumSize: const Size(0, 48),
|
|
side: const BorderSide(color: AppColors.primary),
|
|
),
|
|
child: const Text('删除作品',
|
|
style: TextStyle(color: AppColors.primary)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|