From 0c7c39e641b7204e08c9d7787c85df3d3cfd3a62 Mon Sep 17 00:00:00 2001 From: DelLevin-Home Date: Mon, 29 Jun 2026 01:13:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=92=E8=89=B2=E4=BF=A1=E6=81=AF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=88=B7=E6=96=B0=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/person_list_page.dart | 36 ++++++++++++--- lib/providers/app_provider.dart | 1 + lib/utils/database_helper.dart | 2 +- lib/widgets/genre_selector_page.dart | 65 +++++++++++++++++++++++++--- lib/widgets/text_input_panel.dart | 9 +++- 5 files changed, 98 insertions(+), 15 deletions(-) diff --git a/lib/pages/person_list_page.dart b/lib/pages/person_list_page.dart index 6452dd0..793e8de 100644 --- a/lib/pages/person_list_page.dart +++ b/lib/pages/person_list_page.dart @@ -18,6 +18,13 @@ class _PersonListPageState extends State { String _filter = 'all'; // all / 导演 / 编剧 / 主演 / 作者 String _searchQuery = ''; final _searchController = TextEditingController(); + bool _loading = false; + + @override + void initState() { + super.initState(); + _refresh(); + } @override void dispose() { @@ -25,6 +32,16 @@ class _PersonListPageState extends State { super.dispose(); } + Future _refresh() async { + setState(() => _loading = true); + final provider = context.read(); + await Future.wait([ + provider.loadMovies(), + provider.loadBooks(), + ]); + if (mounted) setState(() => _loading = false); + } + List<_PersonEntry> _buildPersons() { final provider = context.read(); final map = {}; @@ -61,7 +78,6 @@ class _PersonListPageState extends State { final colors = Theme.of(context).colorScheme; final allPersons = _buildPersons(); - // 过滤 var filtered = allPersons.where((p) { if (_filter != 'all' && !p.roles.contains(_filter)) return false; if (_searchQuery.isNotEmpty && !p.name.toLowerCase().contains(_searchQuery.toLowerCase())) return false; @@ -70,7 +86,17 @@ class _PersonListPageState extends State { return Scaffold( backgroundColor: colors.surface, - appBar: AppBar(title: const Text('角色信息')), + appBar: AppBar( + title: const Text('角色信息'), + actions: [ + IconButton( + icon: _loading + ? SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: colors.onSurface.withValues(alpha: 0.5))) + : const Icon(Icons.refresh), + onPressed: _loading ? null : _refresh, + ), + ], + ), body: Column( children: [ // 搜索栏 @@ -184,7 +210,6 @@ class _PersonListPageState extends State { '作者': const Color(0xFF7E57C2), }; final totalWorks = person.movies.length + person.books.length; - return ListTile( contentPadding: const EdgeInsets.symmetric(vertical: 4), leading: CircleAvatar( @@ -223,7 +248,7 @@ class _PersonListPageState extends State { } } -// ─── 人物详情页 ─── +// ─── 人物详情页(只读展示)─── class _PersonDetailPage extends StatelessWidget { final _PersonEntry person; @@ -239,7 +264,6 @@ class _PersonDetailPage extends StatelessWidget { '作者': const Color(0xFF7E57C2), }; - // 按类型分组作品 final movieItems = <_WorkItem>[]; final bookItems = <_WorkItem>[]; @@ -318,7 +342,6 @@ class _PersonDetailPage extends StatelessWidget { ), child: Row( children: [ - // 封面 ClipRRect( borderRadius: BorderRadius.circular(6), child: SizedBox( @@ -333,7 +356,6 @@ class _PersonDetailPage extends StatelessWidget { ), ), const SizedBox(width: 10), - // 标题 + 角色 Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface), diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index e858dec..073a11a 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -122,6 +122,7 @@ class AppProvider extends ChangeNotifier { notifyListeners(); } + // ─── 分页加载(供列表页触底加载使用)──────────────────────── static const int _pageSize = 20; diff --git a/lib/utils/database_helper.dart b/lib/utils/database_helper.dart index c8aa011..3d6344f 100644 --- a/lib/utils/database_helper.dart +++ b/lib/utils/database_helper.dart @@ -56,7 +56,7 @@ class DatabaseHelper { return await openDatabase( path, - version: 25, + version: 26, onCreate: _createDB, onUpgrade: _onUpgrade, ); diff --git a/lib/widgets/genre_selector_page.dart b/lib/widgets/genre_selector_page.dart index 7926e16..354fd6c 100644 --- a/lib/widgets/genre_selector_page.dart +++ b/lib/widgets/genre_selector_page.dart @@ -105,6 +105,47 @@ class _GenreSelectorPageState extends State { } } + void _editItem(int index, String oldValue) { + final editController = TextEditingController(text: oldValue); + showDialog( + context: context, + builder: (ctx) { + final colors = Theme.of(ctx).colorScheme; + return AlertDialog( + backgroundColor: colors.surface, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: Text('编辑', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)), + content: TextField( + controller: editController, + autofocus: true, + style: TextStyle(fontSize: 15, color: colors.onSurface), + cursorColor: colors.primary, + decoration: InputDecoration( + filled: true, + fillColor: colors.surfaceContainerHigh, + contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none), + focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: colors.primary, width: 1)), + ), + onSubmitted: (v) => Navigator.pop(ctx, v.trim()), + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))), + ElevatedButton( + onPressed: () => Navigator.pop(ctx, editController.text.trim()), + style: ElevatedButton.styleFrom(backgroundColor: colors.primary, foregroundColor: colors.onPrimary, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + child: const Text('确定'), + ), + ], + ); + }, + ).then((newValue) { + if (newValue != null && newValue.isNotEmpty && newValue != oldValue && mounted) { + setState(() => _selected[index] = newValue); + } + }); + } + @override Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; @@ -164,7 +205,8 @@ class _GenreSelectorPageState extends State { padding: EdgeInsets.zero, itemCount: _selected.length, itemBuilder: (_, i) { - final tag = _selected[_selected.length - 1 - i]; + final idx = _selected.length - 1 - i; + final tag = _selected[idx]; return Padding( padding: const EdgeInsets.only(bottom: 6), child: Container( @@ -176,10 +218,23 @@ class _GenreSelectorPageState extends State { dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 12), leading: Icon(Icons.check_circle, size: 20, color: colors.primary), - title: Text(tag, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface)), - trailing: GestureDetector( - onTap: () => _toggle(tag), - child: Icon(Icons.close, size: 18, color: colors.onSurface.withValues(alpha: 0.35)), + title: GestureDetector( + onTap: () => _editItem(idx, tag), + child: Text(tag, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface)), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () => _editItem(idx, tag), + child: Icon(Icons.edit, size: 16, color: colors.onSurface.withValues(alpha: 0.3)), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () => _toggle(tag), + child: Icon(Icons.close, size: 18, color: colors.onSurface.withValues(alpha: 0.35)), + ), + ], ), ), ), diff --git a/lib/widgets/text_input_panel.dart b/lib/widgets/text_input_panel.dart index 0a52cb6..0accd76 100644 --- a/lib/widgets/text_input_panel.dart +++ b/lib/widgets/text_input_panel.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; -/// 右侧滑入文本输入弹窗(单行编辑用,如影视名称、书籍名称) +/// 右侧滑入文本输入弹窗(单行/多行编辑用,如影视名称、书籍名称、简介) class TextInputPanel extends StatefulWidget { final String title; final String initialValue; final String hint; final TextInputType keyboardType; + final int maxLines; const TextInputPanel({ super.key, @@ -13,6 +14,7 @@ class TextInputPanel extends StatefulWidget { this.initialValue = '', this.hint = '', this.keyboardType = TextInputType.text, + this.maxLines = 1, }); static Future show({ @@ -21,6 +23,7 @@ class TextInputPanel extends StatefulWidget { String initialValue = '', String hint = '', TextInputType keyboardType = TextInputType.text, + int maxLines = 1, }) { return showGeneralDialog( context: context, @@ -40,6 +43,7 @@ class TextInputPanel extends StatefulWidget { initialValue: initialValue, hint: hint, keyboardType: keyboardType, + maxLines: maxLines, ), ), ); @@ -107,7 +111,8 @@ class _TextInputPanelState extends State { child: TextField( controller: _controller, autofocus: true, - keyboardType: widget.keyboardType, + keyboardType: widget.maxLines > 1 ? TextInputType.multiline : widget.keyboardType, + maxLines: widget.maxLines, style: TextStyle(fontSize: 15, color: colors.onSurface), cursorColor: colors.primary, decoration: InputDecoration(