generated from dellevin/template
1
This commit is contained in:
@@ -263,7 +263,7 @@ class _BookFormPageState extends State<BookFormPage> {
|
|||||||
icon: Icons.category_outlined,
|
icon: Icons.category_outlined,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final provider = context.read<AppProvider>();
|
final provider = context.read<AppProvider>();
|
||||||
final tags = await provider.getTags('book_genre');
|
final tags = await provider.getTags('book_genre', excludeHidden: true);
|
||||||
final existingNames = tags.map((t) => t['name'] as String).toList();
|
final existingNames = tags.map((t) => t['name'] as String).toList();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_showMultiValueDialog(
|
_showMultiValueDialog(
|
||||||
|
|||||||
@@ -539,7 +539,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
|||||||
icon: Icons.category_outlined,
|
icon: Icons.category_outlined,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final provider = context.read<AppProvider>();
|
final provider = context.read<AppProvider>();
|
||||||
final tags = await provider.getTags('movie_genre');
|
final tags = await provider.getTags('movie_genre', excludeHidden: true);
|
||||||
final existingNames = tags.map((t) => t['name'] as String).toList();
|
final existingNames = tags.map((t) => t['name'] as String).toList();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_showMultiValueDialog(
|
_showMultiValueDialog(
|
||||||
|
|||||||
@@ -267,20 +267,22 @@ class _TagManagementPageState extends State<TagManagementPage> {
|
|||||||
final count = _usageCounts[name] ?? 0;
|
final count = _usageCounts[name] ?? 0;
|
||||||
final tagId = tag['id'] as String;
|
final tagId = tag['id'] as String;
|
||||||
final isNew = tagId == _newlyAddedTagId;
|
final isNew = tagId == _newlyAddedTagId;
|
||||||
|
final isHidden = (tag['is_hidden'] as int?) == 1;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
key: ValueKey(tagId),
|
key: ValueKey(tagId),
|
||||||
onTap: () => _showTagMenu(tag),
|
onTap: () => _showTagMenu(tag),
|
||||||
onLongPress: () => _showTagMenu(tag),
|
onLongPress: () => _showTagMenu(tag),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: isHidden ? 0.4 : 1.0,
|
||||||
child: isNew
|
child: isNew
|
||||||
? _NewTagHighlight(
|
? _NewTagHighlight(child: _tagChipContent(name, count, colors, isHidden: isHidden))
|
||||||
child: _tagChipContent(name, count, colors),
|
: _tagChipContent(name, count, colors, isHidden: isHidden),
|
||||||
)
|
),
|
||||||
: _tagChipContent(name, count, colors),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _tagChipContent(String name, int count, ColorScheme colors) {
|
Widget _tagChipContent(String name, int count, ColorScheme colors, {bool isHidden = false}) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.only(left: 14, right: 10, top: 8, bottom: 8),
|
padding: const EdgeInsets.only(left: 14, right: 10, top: 8, bottom: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -293,7 +295,10 @@ class _TagManagementPageState extends State<TagManagementPage> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(name, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface)),
|
Text(name, style: TextStyle(
|
||||||
|
fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface,
|
||||||
|
decoration: isHidden ? TextDecoration.lineThrough : null,
|
||||||
|
)),
|
||||||
if (count > 0) ...[
|
if (count > 0) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Container(
|
Container(
|
||||||
@@ -341,6 +346,7 @@ class _TagManagementPageState extends State<TagManagementPage> {
|
|||||||
void _showTagMenu(Map<String, dynamic> tag) {
|
void _showTagMenu(Map<String, dynamic> tag) {
|
||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
final name = tag['name'] as String;
|
final name = tag['name'] as String;
|
||||||
|
final isHidden = (tag['is_hidden'] as int?) == 1;
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -361,9 +367,28 @@ class _TagManagementPageState extends State<TagManagementPage> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||||
decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(12)),
|
decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(12)),
|
||||||
child: Text(name, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: Text(name, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface))),
|
||||||
|
if (isHidden) Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(color: colors.outlineVariant, borderRadius: BorderRadius.circular(4)),
|
||||||
|
child: Text('已隐藏', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.5))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
_menuAction(
|
||||||
|
isHidden ? Icons.visibility_outlined : Icons.visibility_off_outlined,
|
||||||
|
isHidden ? '取消隐藏' : '隐藏',
|
||||||
|
colors,
|
||||||
|
() async {
|
||||||
|
Navigator.pop(ctx);
|
||||||
|
await context.read<AppProvider>().toggleTagHidden(tag['id'] as String);
|
||||||
|
await _loadTags(_currentType);
|
||||||
|
},
|
||||||
|
),
|
||||||
_menuAction(Icons.edit_outlined, '重命名', colors, () {
|
_menuAction(Icons.edit_outlined, '重命名', colors, () {
|
||||||
Navigator.pop(ctx);
|
Navigator.pop(ctx);
|
||||||
_showRenameDialog(tag);
|
_showRenameDialog(tag);
|
||||||
|
|||||||
@@ -653,11 +653,16 @@ class AppProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
// ========== 标签管理方法 ==========
|
// ========== 标签管理方法 ==========
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getTags(String type) async {
|
Future<List<Map<String, dynamic>>> getTags(String type, {bool excludeHidden = false}) async {
|
||||||
if (_useRemote) {
|
if (_useRemote) {
|
||||||
return await ServerDataService.instance.getTags(type.isEmpty ? null : type);
|
return await ServerDataService.instance.getTags(type.isEmpty ? null : type);
|
||||||
}
|
}
|
||||||
return await _tagDao.getTagsByType(type);
|
return await _tagDao.getTagsByType(type, excludeHidden: excludeHidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleTagHidden(String tagId) async {
|
||||||
|
await _tagDao.toggleHidden(tagId);
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> addTag(String name, String type) async {
|
Future<String> addTag(String name, String type) async {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class DatabaseHelper {
|
|||||||
|
|
||||||
return await openDatabase(
|
return await openDatabase(
|
||||||
path,
|
path,
|
||||||
version: 16,
|
version: 17,
|
||||||
onCreate: _createDB,
|
onCreate: _createDB,
|
||||||
onUpgrade: _onUpgrade,
|
onUpgrade: _onUpgrade,
|
||||||
);
|
);
|
||||||
@@ -140,6 +140,9 @@ class DatabaseHelper {
|
|||||||
await db.execute('ALTER TABLE books ADD COLUMN cover_offset REAL DEFAULT 0');
|
await db.execute('ALTER TABLE books ADD COLUMN cover_offset REAL DEFAULT 0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 17) {
|
||||||
|
await db.execute('ALTER TABLE tags ADD COLUMN is_hidden INTEGER NOT NULL DEFAULT 0');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 升级books表到V11(添加ISBN和出版时间字段)
|
/// 升级books表到V11(添加ISBN和出版时间字段)
|
||||||
@@ -207,6 +210,7 @@ class DatabaseHelper {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
|
is_hidden INTEGER NOT NULL DEFAULT 0,
|
||||||
UNIQUE(name, type)
|
UNIQUE(name, type)
|
||||||
)
|
)
|
||||||
''');
|
''');
|
||||||
@@ -631,6 +635,7 @@ class DatabaseHelper {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
|
is_hidden INTEGER NOT NULL DEFAULT 0,
|
||||||
UNIQUE(name, type)
|
UNIQUE(name, type)
|
||||||
)
|
)
|
||||||
''');
|
''');
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ class TagDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 获取指定类型的所有标签,按名称排序
|
/// 获取指定类型的所有标签,按名称排序
|
||||||
Future<List<Map<String, dynamic>>> getTagsByType(String type) => _wrap('getTagsByType', () async {
|
/// [excludeHidden] 为 true 时,过滤掉隐藏标签
|
||||||
|
Future<List<Map<String, dynamic>>> getTagsByType(String type, {bool excludeHidden = false}) => _wrap('getTagsByType', () async {
|
||||||
final db = await _dbHelper.database;
|
final db = await _dbHelper.database;
|
||||||
|
final where = excludeHidden ? 'type = ? AND is_hidden = 0' : 'type = ?';
|
||||||
return await db.query('tags',
|
return await db.query('tags',
|
||||||
where: 'type = ?',
|
where: where,
|
||||||
whereArgs: [type],
|
whereArgs: [type],
|
||||||
orderBy: 'name ASC');
|
orderBy: 'name ASC');
|
||||||
});
|
});
|
||||||
@@ -122,6 +124,12 @@ class TagDao {
|
|||||||
await db.delete('tags', where: 'id = ?', whereArgs: [tagId]);
|
await db.delete('tags', where: 'id = ?', whereArgs: [tagId]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// 切换标签的隐藏状态
|
||||||
|
Future<void> toggleHidden(String tagId) => _wrap('toggleHidden', () async {
|
||||||
|
final db = await _dbHelper.database;
|
||||||
|
await db.rawUpdate('UPDATE tags SET is_hidden = 1 - is_hidden WHERE id = ?', [tagId]);
|
||||||
|
});
|
||||||
|
|
||||||
/// 确保标签存在(用于替换操作)
|
/// 确保标签存在(用于替换操作)
|
||||||
Future<void> _ensureTagExists(Transaction txn, String name, String type) async {
|
Future<void> _ensureTagExists(Transaction txn, String name, String type) async {
|
||||||
final existing = await txn.query('tags',
|
final existing = await txn.query('tags',
|
||||||
|
|||||||
Reference in New Issue
Block a user