generated from dellevin/template
1
This commit is contained in:
@@ -12,6 +12,7 @@ import '../../utils/epub/reader_settings.dart';
|
|||||||
import '../../utils/epub/reader_models.dart';
|
import '../../utils/epub/reader_models.dart';
|
||||||
import '../../utils/epub/volume_control_service.dart';
|
import '../../utils/epub/volume_control_service.dart';
|
||||||
import '../../utils/epub/reader_dao.dart';
|
import '../../utils/epub/reader_dao.dart';
|
||||||
|
import '../../utils/toast_util.dart';
|
||||||
import 'book_session.dart';
|
import 'book_session.dart';
|
||||||
import 'reader_renderer.dart';
|
import 'reader_renderer.dart';
|
||||||
import 'control_panel.dart';
|
import 'control_panel.dart';
|
||||||
@@ -346,11 +347,7 @@ class _ReaderScreenState extends State<ReaderScreen>
|
|||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
// 删除已有书签
|
// 删除已有书签
|
||||||
await _readerDao.deleteBookmark(existing['id'] as int);
|
await _readerDao.deleteBookmark(existing['id'] as int);
|
||||||
if (mounted) {
|
if (mounted) ToastUtil.show(context, '已移除书签');
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('已移除书签'), duration: Duration(seconds: 1)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 添加书签
|
// 添加书签
|
||||||
final chapterTitle = bookSession.spine.isNotEmpty &&
|
final chapterTitle = bookSession.spine.isNotEmpty &&
|
||||||
@@ -374,11 +371,7 @@ class _ReaderScreenState extends State<ReaderScreen>
|
|||||||
'created_at': DateTime.now().toIso8601String(),
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
'updated_at': DateTime.now().toIso8601String(),
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
});
|
});
|
||||||
if (mounted) {
|
if (mounted) ToastUtil.show(context, '已添加书签');
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('已添加书签'), duration: Duration(seconds: 1)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await _loadBookmarks();
|
await _loadBookmarks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class _MdReaderTabPageState extends State<MdReaderTabPage> {
|
|||||||
|
|
||||||
Future<void> _init() async {
|
Future<void> _init() async {
|
||||||
final saved = UserPrefs().lastMdFolder;
|
final saved = UserPrefs().lastMdFolder;
|
||||||
if (saved != null && saved.isNotEmpty) {
|
if (saved != null && saved.isNotEmpty && saved.length > 3) {
|
||||||
final dir = Directory(saved);
|
final dir = Directory(saved);
|
||||||
if (await dir.exists()) {
|
if (await dir.exists()) {
|
||||||
_rootPath = saved;
|
_rootPath = saved;
|
||||||
@@ -239,8 +239,15 @@ class _MdReaderTabPageState extends State<MdReaderTabPage> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
final isPermissionError = e is FileSystemException && e.osError?.errorCode == 13;
|
||||||
|
if (isPermissionError) {
|
||||||
|
// 清除无效的保存路径
|
||||||
|
await UserPrefs().setLastMdFolder('');
|
||||||
|
_rootPath = null;
|
||||||
|
_currentPath = null;
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = '读取失败: $e';
|
_error = isPermissionError ? '权限不足,请重新选择目录' : '读取失败: $e';
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import '../providers/app_provider.dart';
|
|||||||
import '../utils/user_prefs.dart';
|
import '../utils/user_prefs.dart';
|
||||||
import '../utils/theme/app_theme.dart';
|
import '../utils/theme/app_theme.dart';
|
||||||
import '../utils/toast_util.dart';
|
import '../utils/toast_util.dart';
|
||||||
|
import '../utils/database_helper.dart';
|
||||||
import 'recycle_bin_page.dart';
|
import 'recycle_bin_page.dart';
|
||||||
import 'sync/backup_page.dart';
|
import 'sync/backup_page.dart';
|
||||||
import '../widgets/fade_in_local_image.dart';
|
import '../widgets/fade_in_local_image.dart';
|
||||||
@@ -989,7 +990,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
_buildActionItem(
|
_buildActionItem(
|
||||||
icon: Icons.cleaning_services_outlined,
|
icon: Icons.cleaning_services_outlined,
|
||||||
title: '清除缓存数据',
|
title: '清除缓存数据',
|
||||||
subtitle: '清理未在数据库中引用的图片文件',
|
subtitle: '清理未在数据库中引用的文件',
|
||||||
onTap: () => _showClearCacheDialog(context),
|
onTap: () => _showClearCacheDialog(context),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
@@ -1306,7 +1307,8 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
final colors = Theme.of(context).colorScheme;
|
final colors = Theme.of(context).colorScheme;
|
||||||
final provider = context.watch<AppProvider>();
|
final provider = context.watch<AppProvider>();
|
||||||
final currentIndex = provider.colorSchemeIndex;
|
final currentIndex = provider.colorSchemeIndex;
|
||||||
final label = currentIndex == -1 ? '莫奈取色' : AppTheme.colorSchemeNames[currentIndex];
|
final label =
|
||||||
|
currentIndex == -1 ? '莫奈取色' : AppTheme.colorSchemeNames[currentIndex];
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => _showColorSchemePicker(),
|
onTap: () => _showColorSchemePicker(),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -1319,8 +1321,12 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colors.surfaceContainerHighest,
|
color: colors.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(10)),
|
borderRadius: BorderRadius.circular(10)),
|
||||||
child: Icon(currentIndex == -1 ? Icons.auto_awesome : Icons.palette_outlined,
|
child: Icon(
|
||||||
color: colors.onSurface.withValues(alpha: 0.6), size: 18)),
|
currentIndex == -1
|
||||||
|
? Icons.auto_awesome
|
||||||
|
: Icons.palette_outlined,
|
||||||
|
color: colors.onSurface.withValues(alpha: 0.6),
|
||||||
|
size: 18)),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1445,13 +1451,17 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMonetOption(AppProvider provider, ColorScheme colors, BuildContext ctx) {
|
Widget _buildMonetOption(
|
||||||
|
AppProvider provider, ColorScheme colors, BuildContext ctx) {
|
||||||
final selected = provider.colorSchemeIndex == -1;
|
final selected = provider.colorSchemeIndex == -1;
|
||||||
final monetColor = AppTheme.monetColor;
|
final monetColor = AppTheme.monetColor;
|
||||||
final available = monetColor != null;
|
final available = monetColor != null;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: available
|
onTap: available
|
||||||
? () { provider.setColorScheme(-1); Navigator.pop(ctx); }
|
? () {
|
||||||
|
provider.setColorScheme(-1);
|
||||||
|
Navigator.pop(ctx);
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
@@ -1463,34 +1473,51 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: selected
|
color: selected
|
||||||
? (monetColor ?? colors.primary)
|
? (monetColor ?? colors.primary)
|
||||||
: available ? colors.outlineVariant : colors.outlineVariant.withValues(alpha: 0.5),
|
: available
|
||||||
|
? colors.outlineVariant
|
||||||
|
: colors.outlineVariant.withValues(alpha: 0.5),
|
||||||
width: selected ? 1.5 : 0.5),
|
width: selected ? 1.5 : 0.5),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 20, height: 20,
|
width: 20,
|
||||||
|
height: 20,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
gradient: monetColor != null
|
gradient: monetColor != null
|
||||||
? LinearGradient(colors: [monetColor, monetColor.withValues(alpha: 0.6)])
|
? LinearGradient(
|
||||||
|
colors: [monetColor, monetColor.withValues(alpha: 0.6)])
|
||||||
: null,
|
: null,
|
||||||
color: available ? null : colors.outlineVariant,
|
color: available ? null : colors.outlineVariant,
|
||||||
),
|
),
|
||||||
child: available ? null : Icon(Icons.auto_awesome, size: 12, color: colors.onSurface.withValues(alpha: 0.3)),
|
child: available
|
||||||
|
? null
|
||||||
|
: Icon(Icons.auto_awesome,
|
||||||
|
size: 12, color: colors.onSurface.withValues(alpha: 0.3)),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(
|
||||||
Text('莫奈取色', style: TextStyle(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontSize: 13,
|
children: [
|
||||||
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
Text('莫奈取色',
|
||||||
color: available ? colors.onSurface : colors.onSurface.withValues(alpha: 0.35))),
|
style: TextStyle(
|
||||||
Text(available ? '从系统壁纸自动提取配色' : '此设备不支持',
|
fontSize: 13,
|
||||||
style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.35))),
|
fontWeight:
|
||||||
]),
|
selected ? FontWeight.w600 : FontWeight.w500,
|
||||||
|
color: available
|
||||||
|
? colors.onSurface
|
||||||
|
: colors.onSurface.withValues(alpha: 0.35))),
|
||||||
|
Text(available ? '从系统壁纸自动提取配色' : '此设备不支持',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: colors.onSurface.withValues(alpha: 0.35))),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
if (selected) Icon(Icons.check_circle, size: 18, color: monetColor ?? colors.primary),
|
if (selected)
|
||||||
|
Icon(Icons.check_circle,
|
||||||
|
size: 18, color: monetColor ?? colors.primary),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1839,17 +1866,23 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
final dbImagePaths = await _getAllDbImagePaths(appProvider);
|
final dbImagePaths = await _getAllDbImagePaths(appProvider);
|
||||||
final deletedImages = await _cleanImageDirectory(dbImagePaths);
|
final deletedImages = await _cleanImageDirectory(dbImagePaths);
|
||||||
|
|
||||||
// 2. 清理临时目录缓存(分享海报、备份ZIP等)
|
// 2. 清理孤立的 epub_books 目录
|
||||||
|
final deletedEpubs = await _cleanOrphanedEpubBooks(appProvider);
|
||||||
|
|
||||||
|
// 3. 清理临时目录缓存(分享海报、备份ZIP等)
|
||||||
final deletedTemp = await _cleanTempDirectory();
|
final deletedTemp = await _cleanTempDirectory();
|
||||||
|
|
||||||
|
// 4. 清理空文件夹
|
||||||
|
final deletedEmptyDirs = await _cleanEmptyDirectories();
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
final total = deletedImages + deletedTemp;
|
final total = deletedImages + deletedEpubs + deletedTemp + deletedEmptyDirs;
|
||||||
if (total == 0) {
|
if (total == 0) {
|
||||||
ToastUtil.show(context, '没有需要清理的缓存');
|
ToastUtil.show(context, '没有需要清理的缓存');
|
||||||
} else {
|
} else {
|
||||||
ToastUtil.show(
|
ToastUtil.show(
|
||||||
context, '已清理 $deletedImages 个孤立图片,$deletedTemp 个临时文件');
|
context, '已清理 $deletedImages 个孤立图片,$deletedEpubs 个孤立电子书,$deletedTemp 个临时文件,$deletedEmptyDirs 个空文件夹');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1902,6 +1935,36 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
return deletedCount;
|
return deletedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 清理 epub_books 中孤立的目录(数据库中不存在的)
|
||||||
|
Future<int> _cleanOrphanedEpubBooks(AppProvider provider) async {
|
||||||
|
int deletedCount = 0;
|
||||||
|
try {
|
||||||
|
// 收集数据库中所有 reader_books 的 bookId
|
||||||
|
final db = await DatabaseHelper.instance.database;
|
||||||
|
final rows = await db.query('reader_books', columns: ['id']);
|
||||||
|
final dbIds = rows.map((r) => r['id'] as String).toSet();
|
||||||
|
|
||||||
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
|
final epubDir = Directory('${appDir.path}/epub_books');
|
||||||
|
if (!await epubDir.exists()) return 0;
|
||||||
|
|
||||||
|
await for (final entity in epubDir.list(followLinks: false)) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
final dirName = path.basename(entity.path);
|
||||||
|
if (!dbIds.contains(dirName)) {
|
||||||
|
try {
|
||||||
|
await entity.delete(recursive: true);
|
||||||
|
deletedCount++;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('清理 epub_books 目录失败: $e');
|
||||||
|
}
|
||||||
|
return deletedCount;
|
||||||
|
}
|
||||||
|
|
||||||
Future<int> _cleanTempDirectory() async {
|
Future<int> _cleanTempDirectory() async {
|
||||||
int deletedCount = 0;
|
int deletedCount = 0;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
@@ -1953,6 +2016,48 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
|
|
||||||
return deletedCount;
|
return deletedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 清理 images、epub_books、cache 下的空文件夹
|
||||||
|
Future<int> _cleanEmptyDirectories() async {
|
||||||
|
int deletedCount = 0;
|
||||||
|
try {
|
||||||
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
|
final cacheDir = await getApplicationCacheDirectory();
|
||||||
|
final dirs = [
|
||||||
|
Directory('${appDir.path}/images'),
|
||||||
|
Directory('${appDir.path}/epub_books'),
|
||||||
|
cacheDir,
|
||||||
|
];
|
||||||
|
for (final dir in dirs) {
|
||||||
|
if (!await dir.exists()) continue;
|
||||||
|
deletedCount += await _removeEmptyDirsRecursive(dir);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('清理空文件夹失败: $e');
|
||||||
|
}
|
||||||
|
return deletedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 递归删除空子文件夹(自底向上),不删除根目录本身
|
||||||
|
Future<int> _removeEmptyDirsRecursive(Directory dir) async {
|
||||||
|
int count = 0;
|
||||||
|
try {
|
||||||
|
final children = await dir.list(followLinks: false).toList();
|
||||||
|
for (final child in children) {
|
||||||
|
if (child is Directory) {
|
||||||
|
count += await _removeEmptyDirsRecursive(child);
|
||||||
|
final remaining = await child.list(followLinks: false).toList();
|
||||||
|
if (remaining.isEmpty) {
|
||||||
|
try {
|
||||||
|
await child.delete();
|
||||||
|
count++;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 主界面设置 ───
|
// ─── 主界面设置 ───
|
||||||
@@ -2478,63 +2583,98 @@ class _SidebarSettingsPageState extends State<SidebarSettingsPage> {
|
|||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildSectionHeader('信息模块'),
|
_buildSectionHeader('信息模块'),
|
||||||
_buildSwitchItem(Icons.calendar_today, '热力图', '显示创作活跃度热力图',
|
_buildSwitchItem(
|
||||||
_showHeatmap, (v) async {
|
Icons.calendar_today, '热力图', '显示创作活跃度热力图', _showHeatmap,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarHeatmap(v);
|
await _userPrefs.setShowSidebarHeatmap(v);
|
||||||
setState(() => _showHeatmap = v);
|
setState(() => _showHeatmap = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
_buildSwitchItem(Icons.schedule, '最近添加', '显示最近添加的记录',
|
height: 0.5,
|
||||||
_showRecent, (v) async {
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
|
_buildSwitchItem(Icons.schedule, '最近添加', '显示最近添加的记录', _showRecent,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarRecent(v);
|
await _userPrefs.setShowSidebarRecent(v);
|
||||||
setState(() => _showRecent = v);
|
setState(() => _showRecent = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
_buildSwitchItem(Icons.favorite_border, '统计', '与应用相遇的天数和数据概览',
|
height: 0.5,
|
||||||
_showEncounter, (v) async {
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
|
_buildSwitchItem(
|
||||||
|
Icons.favorite_border, '统计', '与应用相遇的天数和数据概览', _showEncounter,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarEncounter(v);
|
await _userPrefs.setShowSidebarEncounter(v);
|
||||||
setState(() => _showEncounter = v);
|
setState(() => _showEncounter = v);
|
||||||
}),
|
}),
|
||||||
_buildSectionHeader('快捷功能'),
|
_buildSectionHeader('快捷功能'),
|
||||||
_buildSwitchItem(Icons.explore_outlined, '漫步', '随机发现内容',
|
_buildSwitchItem(Icons.explore_outlined, '漫步', '随机发现内容', _showStroll,
|
||||||
_showStroll, (v) async {
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarStroll(v);
|
await _userPrefs.setShowSidebarStroll(v);
|
||||||
setState(() => _showStroll = v);
|
setState(() => _showStroll = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
_buildSwitchItem(Icons.calendar_month_outlined, '书影日历', '按日历查看记录',
|
height: 0.5,
|
||||||
_showCalendar, (v) async {
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
|
_buildSwitchItem(
|
||||||
|
Icons.calendar_month_outlined, '书影日历', '按日历查看记录', _showCalendar,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarCalendar(v);
|
await _userPrefs.setShowSidebarCalendar(v);
|
||||||
setState(() => _showCalendar = v);
|
setState(() => _showCalendar = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
_buildSwitchItem(Icons.people_outline, '角色信息', '管理影视和书籍中的角色',
|
height: 0.5,
|
||||||
_showPerson, (v) async {
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
|
_buildSwitchItem(
|
||||||
|
Icons.people_outline, '角色信息', '管理影视和书籍中的角色', _showPerson,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarPerson(v);
|
await _userPrefs.setShowSidebarPerson(v);
|
||||||
setState(() => _showPerson = v);
|
setState(() => _showPerson = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
_buildSwitchItem(Icons.label_outline, '标签管理', '管理所有标签',
|
height: 0.5,
|
||||||
_showTags, (v) async {
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
|
_buildSwitchItem(Icons.label_outline, '标签管理', '管理所有标签', _showTags,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarTags(v);
|
await _userPrefs.setShowSidebarTags(v);
|
||||||
setState(() => _showTags = v);
|
setState(() => _showTags = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
|
height: 0.5,
|
||||||
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
_buildSwitchItem(Icons.description_outlined, 'MD阅读', 'Markdown 文件阅读器',
|
_buildSwitchItem(Icons.description_outlined, 'MD阅读', 'Markdown 文件阅读器',
|
||||||
_showMdReader, (v) async {
|
_showMdReader, (v) async {
|
||||||
await _userPrefs.setShowSidebarMdReader(v);
|
await _userPrefs.setShowSidebarMdReader(v);
|
||||||
setState(() => _showMdReader = v);
|
setState(() => _showMdReader = v);
|
||||||
}),
|
}),
|
||||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
Divider(
|
||||||
_buildSwitchItem(Icons.auto_stories_outlined, 'EPUB阅读', 'EPUB 电子书阅读器',
|
height: 0.5,
|
||||||
_showEpub, (v) async {
|
indent: 24,
|
||||||
|
endIndent: 24,
|
||||||
|
color: colors.outlineVariant),
|
||||||
|
_buildSwitchItem(
|
||||||
|
Icons.auto_stories_outlined, 'EPUB阅读', 'EPUB 电子书阅读器', _showEpub,
|
||||||
|
(v) async {
|
||||||
await _userPrefs.setShowSidebarEpub(v);
|
await _userPrefs.setShowSidebarEpub(v);
|
||||||
setState(() => _showEpub = v);
|
setState(() => _showEpub = v);
|
||||||
}),
|
}),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
child: Text('关闭后对应功能将从侧边栏中隐藏。',
|
child: Text('关闭后对应功能将从侧边栏中隐藏。',
|
||||||
style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.3))),
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: colors.onSurface.withValues(alpha: 0.3))),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class BackupService {
|
|||||||
final bookReviews = await db.query('book_reviews');
|
final bookReviews = await db.query('book_reviews');
|
||||||
final bookExcerpts = await db.query('book_excerpts');
|
final bookExcerpts = await db.query('book_excerpts');
|
||||||
final tags = await db.query('tags');
|
final tags = await db.query('tags');
|
||||||
|
final readerBooks = await db.query('reader_books');
|
||||||
|
final bookAnnotations = await db.query('book_annotations');
|
||||||
|
final notePlus = await db.query('note_plus');
|
||||||
|
|
||||||
// 收集图片路径
|
// 收集图片路径
|
||||||
final imagePaths = <String>{};
|
final imagePaths = <String>{};
|
||||||
@@ -58,6 +61,10 @@ class BackupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (final rb in readerBooks) {
|
||||||
|
final p = rb['cover_path'] as String?;
|
||||||
|
if (p != null && p.isNotEmpty) imagePaths.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
final userPrefs = UserPrefs();
|
final userPrefs = UserPrefs();
|
||||||
final userInfo = {
|
final userInfo = {
|
||||||
@@ -85,6 +92,9 @@ class BackupService {
|
|||||||
'book_reviews': bookReviews,
|
'book_reviews': bookReviews,
|
||||||
'book_excerpts': bookExcerpts,
|
'book_excerpts': bookExcerpts,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
|
'reader_books': readerBooks,
|
||||||
|
'book_annotations': bookAnnotations,
|
||||||
|
'note_plus': notePlus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -304,15 +314,21 @@ class BackupService {
|
|||||||
final bookReviewsCols = await _getTableColumns(db, 'book_reviews');
|
final bookReviewsCols = await _getTableColumns(db, 'book_reviews');
|
||||||
final bookExcerptsCols = await _getTableColumns(db, 'book_excerpts');
|
final bookExcerptsCols = await _getTableColumns(db, 'book_excerpts');
|
||||||
final tagsCols = await _getTableColumns(db, 'tags');
|
final tagsCols = await _getTableColumns(db, 'tags');
|
||||||
|
final readerBooksCols = await _getTableColumns(db, 'reader_books');
|
||||||
|
final bookAnnotationsCols = await _getTableColumns(db, 'book_annotations');
|
||||||
|
final notePlusCols = await _getTableColumns(db, 'note_plus');
|
||||||
|
|
||||||
await db.transaction((txn) async {
|
await db.transaction((txn) async {
|
||||||
await txn.delete('movie_reviews');
|
await txn.delete('movie_reviews');
|
||||||
await txn.delete('movie_posters');
|
await txn.delete('movie_posters');
|
||||||
await txn.delete('book_reviews');
|
await txn.delete('book_reviews');
|
||||||
await txn.delete('book_excerpts');
|
await txn.delete('book_excerpts');
|
||||||
|
await txn.delete('book_annotations');
|
||||||
await txn.delete('movies');
|
await txn.delete('movies');
|
||||||
await txn.delete('books');
|
await txn.delete('books');
|
||||||
await txn.delete('notes');
|
await txn.delete('notes');
|
||||||
|
await txn.delete('reader_books');
|
||||||
|
await txn.delete('note_plus');
|
||||||
await txn.delete('tags');
|
await txn.delete('tags');
|
||||||
|
|
||||||
if (data.containsKey('movies')) {
|
if (data.containsKey('movies')) {
|
||||||
@@ -350,6 +366,21 @@ class BackupService {
|
|||||||
await txn.insert('book_excerpts', _convertToDbMapSafe(e, bookExcerptsCols));
|
await txn.insert('book_excerpts', _convertToDbMapSafe(e, bookExcerptsCols));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('reader_books')) {
|
||||||
|
for (final rb in data['reader_books'] as List) {
|
||||||
|
await txn.insert('reader_books', _updateImagePath(_convertToDbMapSafe(rb, readerBooksCols), 'cover_path', imagePathMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.containsKey('book_annotations')) {
|
||||||
|
for (final a in data['book_annotations'] as List) {
|
||||||
|
await txn.insert('book_annotations', _convertToDbMapSafe(a, bookAnnotationsCols));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.containsKey('note_plus')) {
|
||||||
|
for (final np in data['note_plus'] as List) {
|
||||||
|
await txn.insert('note_plus', _convertToDbMapSafe(np, notePlusCols));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (data.containsKey('tags')) {
|
if (data.containsKey('tags')) {
|
||||||
for (final t in data['tags'] as List) {
|
for (final t in data['tags'] as List) {
|
||||||
final map = _convertToDbMapSafe(t, tagsCols);
|
final map = _convertToDbMapSafe(t, tagsCols);
|
||||||
@@ -421,15 +452,21 @@ class BackupService {
|
|||||||
final bookReviewsCols = await _getTableColumns(db, 'book_reviews');
|
final bookReviewsCols = await _getTableColumns(db, 'book_reviews');
|
||||||
final bookExcerptsCols = await _getTableColumns(db, 'book_excerpts');
|
final bookExcerptsCols = await _getTableColumns(db, 'book_excerpts');
|
||||||
final tagsCols = await _getTableColumns(db, 'tags');
|
final tagsCols = await _getTableColumns(db, 'tags');
|
||||||
|
final readerBooksCols = await _getTableColumns(db, 'reader_books');
|
||||||
|
final bookAnnotationsCols = await _getTableColumns(db, 'book_annotations');
|
||||||
|
final notePlusCols = await _getTableColumns(db, 'note_plus');
|
||||||
|
|
||||||
await db.transaction((txn) async {
|
await db.transaction((txn) async {
|
||||||
await txn.delete('movie_reviews');
|
await txn.delete('movie_reviews');
|
||||||
await txn.delete('movie_posters');
|
await txn.delete('movie_posters');
|
||||||
await txn.delete('book_reviews');
|
await txn.delete('book_reviews');
|
||||||
await txn.delete('book_excerpts');
|
await txn.delete('book_excerpts');
|
||||||
|
await txn.delete('book_annotations');
|
||||||
await txn.delete('movies');
|
await txn.delete('movies');
|
||||||
await txn.delete('books');
|
await txn.delete('books');
|
||||||
await txn.delete('notes');
|
await txn.delete('notes');
|
||||||
|
await txn.delete('reader_books');
|
||||||
|
await txn.delete('note_plus');
|
||||||
await txn.delete('tags'); // 修复: 之前漏删 tags 表
|
await txn.delete('tags'); // 修复: 之前漏删 tags 表
|
||||||
|
|
||||||
if (data.containsKey('movies')) {
|
if (data.containsKey('movies')) {
|
||||||
@@ -467,6 +504,21 @@ class BackupService {
|
|||||||
await txn.insert('book_excerpts', _convertToDbMapSafe(e, bookExcerptsCols));
|
await txn.insert('book_excerpts', _convertToDbMapSafe(e, bookExcerptsCols));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('reader_books')) {
|
||||||
|
for (final rb in data['reader_books'] as List) {
|
||||||
|
await txn.insert('reader_books', _updateImagePath(_convertToDbMapSafe(rb, readerBooksCols), 'cover_path', imagePathMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.containsKey('book_annotations')) {
|
||||||
|
for (final a in data['book_annotations'] as List) {
|
||||||
|
await txn.insert('book_annotations', _convertToDbMapSafe(a, bookAnnotationsCols));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.containsKey('note_plus')) {
|
||||||
|
for (final np in data['note_plus'] as List) {
|
||||||
|
await txn.insert('note_plus', _convertToDbMapSafe(np, notePlusCols));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (data.containsKey('tags')) {
|
if (data.containsKey('tags')) {
|
||||||
for (final t in data['tags'] as List) {
|
for (final t in data['tags'] as List) {
|
||||||
final map = _convertToDbMapSafe(t, tagsCols);
|
final map = _convertToDbMapSafe(t, tagsCols);
|
||||||
@@ -560,6 +612,9 @@ class BackupService {
|
|||||||
if (data.containsKey('book_reviews')) stats['书评'] = (data['book_reviews'] as List).length;
|
if (data.containsKey('book_reviews')) stats['书评'] = (data['book_reviews'] as List).length;
|
||||||
if (data.containsKey('book_excerpts')) stats['书摘'] = (data['book_excerpts'] as List).length;
|
if (data.containsKey('book_excerpts')) stats['书摘'] = (data['book_excerpts'] as List).length;
|
||||||
if (data.containsKey('tags')) stats['标签'] = (data['tags'] as List).length;
|
if (data.containsKey('tags')) stats['标签'] = (data['tags'] as List).length;
|
||||||
|
if (data.containsKey('reader_books')) stats['阅读'] = (data['reader_books'] as List).length;
|
||||||
|
if (data.containsKey('book_annotations')) stats['批注'] = (data['book_annotations'] as List).length;
|
||||||
|
if (data.containsKey('note_plus')) stats['高级笔记'] = (data['note_plus'] as List).length;
|
||||||
if (imageCount > 0) stats['图片'] = imageCount;
|
if (imageCount > 0) stats['图片'] = imageCount;
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user