generated from dellevin/template
角色信息添加刷新按钮
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ class AppProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ─── 分页加载(供列表页触底加载使用)────────────────────────
|
// ─── 分页加载(供列表页触底加载使用)────────────────────────
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user