优化调整

This commit is contained in:
DelLevin-Home
2026-03-21 12:57:40 +08:00
parent bf54e1082a
commit cc4c908c86
13 changed files with 832 additions and 126 deletions

View File

@@ -84,10 +84,12 @@ class _MovieFormPageState extends State<MovieFormPage> {
@override
void dispose() {
// 先 dispose controllers
_titleController.dispose();
_summaryController.dispose();
_ratingController.dispose();
_tagControllers.values.forEach((c) => c.dispose());
// 最后调用 super.dispose
super.dispose();
}
@@ -126,6 +128,195 @@ class _MovieFormPageState extends State<MovieFormPage> {
);
}
/// 显示快捷添加对话框
void _showQuickAddDialog() {
final textController = TextEditingController();
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: const Text(
'快捷添加',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'请输入豆瓣影视链接:',
style: TextStyle(
fontSize: 14,
color: Color(0xFF666666),
),
),
const SizedBox(height: 12),
TextField(
controller: textController,
autofocus: true,
decoration: InputDecoration(
hintText: 'https://m.douban.com/subject/...',
hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFCCCCCC)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFFE5E5E5)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFFE5E5E5)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF1A1A1A)),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
onSubmitted: (url) async {
if (url.trim().isNotEmpty) {
// 先移除焦点
FocusManager.instance.primaryFocus?.unfocus();
await Future.delayed(const Duration(milliseconds: 50));
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop(url);
}
}
},
),
],
),
actions: [
TextButton(
onPressed: () async {
// 先移除焦点,等待一帧确保焦点已释放
FocusManager.instance.primaryFocus?.unfocus();
await Future.delayed(const Duration(milliseconds: 50));
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop(null);
}
},
child: const Text(
'取消',
style: TextStyle(color: Color(0xFF999999)),
),
),
TextButton(
onPressed: () async {
final url = textController.text.trim();
if (url.isNotEmpty) {
// 先移除焦点
FocusManager.instance.primaryFocus?.unfocus();
await Future.delayed(const Duration(milliseconds: 50));
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop(url);
}
}
},
child: const Text(
'确定',
style: TextStyle(color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600),
),
),
],
),
).then((result) {
// 延迟 dispose确保 widget tree 已释放 controller
WidgetsBinding.instance.addPostFrameCallback((_) {
textController.dispose();
});
// 处理结果
if (result != null && result is String && result.isNotEmpty) {
_openDoubanWebView(result);
}
});
}
/// 打开豆瓣WebView页面
Future<void> _openDoubanWebView(String url) async {
// 导航到WebView页面并等待返回结果
final result = await Navigator.pushNamed(
context,
'/douban-webview',
arguments: url,
);
// 处理返回的影视信息
if (result != null && result is Map<String, dynamic>) {
_fillMovieInfo(result);
}
}
/// 填充影视信息到表单
void _fillMovieInfo(Map<String, dynamic> info) {
setState(() {
// 填充标题
if (info['title'] != null && info['title'].toString().isNotEmpty) {
_titleController.text = info['title'].toString();
}
// 填充评分
if (info['rating'] != null && info['rating'].toString().isNotEmpty) {
_ratingController.text = info['rating'].toString();
}
// 填充导演
if (info['director'] != null && info['director'].toString().isNotEmpty) {
_directors = [info['director'].toString()];
}
// 填充编剧
if (info['writers'] != null && info['writers'] is List) {
_writers = (info['writers'] as List).map((w) => w.toString()).toList();
}
// 填充演员
if (info['actors'] != null && info['actors'] is List) {
_actors = (info['actors'] as List).map((a) => a.toString()).toList();
}
// 填充类型
if (info['genres'] != null && info['genres'].toString().isNotEmpty) {
_genres = info['genres'].toString().split(',').map((g) => g.trim()).toList();
}
// 填充别名
if (info['alternateTitles'] != null && info['alternateTitles'] is List) {
_alternateTitles = (info['alternateTitles'] as List).map((t) => t.toString()).toList();
}
// 填充简介
if (info['summary'] != null && info['summary'].toString().isNotEmpty) {
_summaryController.text = info['summary'].toString();
}
// 填充上映日期
if (info['releaseDate'] != null && info['releaseDate'].toString().isNotEmpty) {
final dateStr = info['releaseDate'].toString();
// 尝试解析日期
try {
// 处理格式如 "2023-01-01(中国大陆)"
final cleanDate = dateStr.split('(')[0].trim();
_releaseDate = DateTime.parse(cleanDate);
} catch (e) {
// 解析失败则忽略
}
}
});
// 显示成功提示
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已自动填充影视信息')),
);
}
}
@override
Widget build(BuildContext context) {
final isEdit = widget.movie != null;
@@ -135,6 +326,13 @@ class _MovieFormPageState extends State<MovieFormPage> {
appBar: AppBar(
title: Text(isEdit ? '编辑影视' : '添加影视'),
actions: [
// 快捷添加按钮(仅添加模式显示)
if (!isEdit)
_buildActionButton(
icon: Icons.auto_fix_high_outlined,
onPressed: _showQuickAddDialog,
tooltip: '快捷添加',
),
// 保存按钮
_buildActionButton(
icon: Icons.save_outlined,
@@ -765,7 +963,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
return Column(
children: [
GestureDetector(
onTap: _pickCover,
onTap: _showCoverOptions,
child: Container(
width: 140,
height: 200,
@@ -818,6 +1016,107 @@ class _MovieFormPageState extends State<MovieFormPage> {
);
}
/// 显示封面选择选项
void _showCoverOptions() {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) => SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 顶部指示条
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 20),
// 标题
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'添加海报',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
),
),
),
const SizedBox(height: 16),
// 本地图片选项
_buildCoverOption(
icon: Icons.photo_library_outlined,
title: '从相册选择',
onTap: () {
Navigator.pop(context);
_pickCover();
},
),
],
),
),
),
);
}
/// 构建封面选项
Widget _buildCoverOption({
required IconData icon,
required String title,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: 22,
color: const Color(0xFF666666),
),
),
const SizedBox(width: 16),
Text(
title,
style: const TextStyle(
fontSize: 16,
color: Color(0xFF1A1A1A),
),
),
const Spacer(),
const Icon(
Icons.chevron_right,
color: Color(0xFFCCCCCC),
size: 20,
),
],
),
),
);
}
Widget _buildCoverPlaceholder() {
return const Column(
mainAxisAlignment: MainAxisAlignment.center,