add
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user