角色信息添加刷新按钮

This commit is contained in:
DelLevin-Home
2026-06-29 01:13:52 +08:00
parent ede0b10b19
commit 0c7c39e641
5 changed files with 98 additions and 15 deletions

View File

@@ -18,6 +18,13 @@ class _PersonListPageState extends State<PersonListPage> {
String _filter = 'all'; // all / 导演 / 编剧 / 主演 / 作者 String _filter = 'all'; // all / 导演 / 编剧 / 主演 / 作者
String _searchQuery = ''; String _searchQuery = '';
final _searchController = TextEditingController(); final _searchController = TextEditingController();
bool _loading = false;
@override
void initState() {
super.initState();
_refresh();
}
@override @override
void dispose() { void dispose() {
@@ -25,6 +32,16 @@ class _PersonListPageState extends State<PersonListPage> {
super.dispose(); super.dispose();
} }
Future<void> _refresh() async {
setState(() => _loading = true);
final provider = context.read<AppProvider>();
await Future.wait([
provider.loadMovies(),
provider.loadBooks(),
]);
if (mounted) setState(() => _loading = false);
}
List<_PersonEntry> _buildPersons() { List<_PersonEntry> _buildPersons() {
final provider = context.read<AppProvider>(); final provider = context.read<AppProvider>();
final map = <String, _PersonEntry>{}; final map = <String, _PersonEntry>{};
@@ -61,7 +78,6 @@ class _PersonListPageState extends State<PersonListPage> {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
final allPersons = _buildPersons(); final allPersons = _buildPersons();
// 过滤
var filtered = allPersons.where((p) { var filtered = allPersons.where((p) {
if (_filter != 'all' && !p.roles.contains(_filter)) return false; if (_filter != 'all' && !p.roles.contains(_filter)) return false;
if (_searchQuery.isNotEmpty && !p.name.toLowerCase().contains(_searchQuery.toLowerCase())) return false; if (_searchQuery.isNotEmpty && !p.name.toLowerCase().contains(_searchQuery.toLowerCase())) return false;
@@ -70,7 +86,17 @@ class _PersonListPageState extends State<PersonListPage> {
return Scaffold( return Scaffold(
backgroundColor: colors.surface, 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( body: Column(
children: [ children: [
// 搜索栏 // 搜索栏
@@ -184,7 +210,6 @@ class _PersonListPageState extends State<PersonListPage> {
'作者': const Color(0xFF7E57C2), '作者': const Color(0xFF7E57C2),
}; };
final totalWorks = person.movies.length + person.books.length; final totalWorks = person.movies.length + person.books.length;
return ListTile( return ListTile(
contentPadding: const EdgeInsets.symmetric(vertical: 4), contentPadding: const EdgeInsets.symmetric(vertical: 4),
leading: CircleAvatar( leading: CircleAvatar(
@@ -223,7 +248,7 @@ class _PersonListPageState extends State<PersonListPage> {
} }
} }
// ─── 人物详情页 ─── // ─── 人物详情页(只读展示)───
class _PersonDetailPage extends StatelessWidget { class _PersonDetailPage extends StatelessWidget {
final _PersonEntry person; final _PersonEntry person;
@@ -239,7 +264,6 @@ class _PersonDetailPage extends StatelessWidget {
'作者': const Color(0xFF7E57C2), '作者': const Color(0xFF7E57C2),
}; };
// 按类型分组作品
final movieItems = <_WorkItem>[]; final movieItems = <_WorkItem>[];
final bookItems = <_WorkItem>[]; final bookItems = <_WorkItem>[];
@@ -318,7 +342,6 @@ class _PersonDetailPage extends StatelessWidget {
), ),
child: Row( child: Row(
children: [ children: [
// 封面
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
child: SizedBox( child: SizedBox(
@@ -333,7 +356,6 @@ class _PersonDetailPage extends StatelessWidget {
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
// 标题 + 角色
Expanded( Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(item.title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface), Text(item.title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface),

View File

@@ -122,6 +122,7 @@ class AppProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
// ─── 分页加载(供列表页触底加载使用)──────────────────────── // ─── 分页加载(供列表页触底加载使用)────────────────────────
static const int _pageSize = 20; static const int _pageSize = 20;

View File

@@ -56,7 +56,7 @@ class DatabaseHelper {
return await openDatabase( return await openDatabase(
path, path,
version: 25, version: 26,
onCreate: _createDB, onCreate: _createDB,
onUpgrade: _onUpgrade, onUpgrade: _onUpgrade,
); );

View File

@@ -105,6 +105,47 @@ class _GenreSelectorPageState extends State<GenreSelectorPage> {
} }
} }
void _editItem(int index, String oldValue) {
final editController = TextEditingController(text: oldValue);
showDialog<String>(
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
@@ -164,7 +205,8 @@ class _GenreSelectorPageState extends State<GenreSelectorPage> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: _selected.length, itemCount: _selected.length,
itemBuilder: (_, i) { itemBuilder: (_, i) {
final tag = _selected[_selected.length - 1 - i]; final idx = _selected.length - 1 - i;
final tag = _selected[idx];
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 6), padding: const EdgeInsets.only(bottom: 6),
child: Container( child: Container(
@@ -176,10 +218,23 @@ class _GenreSelectorPageState extends State<GenreSelectorPage> {
dense: true, dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 12), contentPadding: const EdgeInsets.symmetric(horizontal: 12),
leading: Icon(Icons.check_circle, size: 20, color: colors.primary), leading: Icon(Icons.check_circle, size: 20, color: colors.primary),
title: Text(tag, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface)), title: GestureDetector(
trailing: GestureDetector( onTap: () => _editItem(idx, tag),
onTap: () => _toggle(tag), child: Text(tag, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface)),
child: Icon(Icons.close, size: 18, color: colors.onSurface.withValues(alpha: 0.35)), ),
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)),
),
],
), ),
), ),
), ),

View File

@@ -1,11 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// 右侧滑入文本输入弹窗(单行编辑用,如影视名称、书籍名称) /// 右侧滑入文本输入弹窗(单行/多行编辑用,如影视名称、书籍名称、简介
class TextInputPanel extends StatefulWidget { class TextInputPanel extends StatefulWidget {
final String title; final String title;
final String initialValue; final String initialValue;
final String hint; final String hint;
final TextInputType keyboardType; final TextInputType keyboardType;
final int maxLines;
const TextInputPanel({ const TextInputPanel({
super.key, super.key,
@@ -13,6 +14,7 @@ class TextInputPanel extends StatefulWidget {
this.initialValue = '', this.initialValue = '',
this.hint = '', this.hint = '',
this.keyboardType = TextInputType.text, this.keyboardType = TextInputType.text,
this.maxLines = 1,
}); });
static Future<String?> show({ static Future<String?> show({
@@ -21,6 +23,7 @@ class TextInputPanel extends StatefulWidget {
String initialValue = '', String initialValue = '',
String hint = '', String hint = '',
TextInputType keyboardType = TextInputType.text, TextInputType keyboardType = TextInputType.text,
int maxLines = 1,
}) { }) {
return showGeneralDialog<String>( return showGeneralDialog<String>(
context: context, context: context,
@@ -40,6 +43,7 @@ class TextInputPanel extends StatefulWidget {
initialValue: initialValue, initialValue: initialValue,
hint: hint, hint: hint,
keyboardType: keyboardType, keyboardType: keyboardType,
maxLines: maxLines,
), ),
), ),
); );
@@ -107,7 +111,8 @@ class _TextInputPanelState extends State<TextInputPanel> {
child: TextField( child: TextField(
controller: _controller, controller: _controller,
autofocus: true, autofocus: true,
keyboardType: widget.keyboardType, keyboardType: widget.maxLines > 1 ? TextInputType.multiline : widget.keyboardType,
maxLines: widget.maxLines,
style: TextStyle(fontSize: 15, color: colors.onSurface), style: TextStyle(fontSize: 15, color: colors.onSurface),
cursorColor: colors.primary, cursorColor: colors.primary,
decoration: InputDecoration( decoration: InputDecoration(