代码优化,epub阅读标签

This commit is contained in:
DelLevin-Home
2026-06-28 02:25:07 +08:00
parent c810a1a381
commit 5f434ae3fa
35 changed files with 2110 additions and 525 deletions

View File

@@ -22,9 +22,9 @@ class EpubService {
// 复制 EPUB 到永久存储FilePicker 临时文件会被清理)
final appDir = await getApplicationDocumentsDirectory();
final booksDir = Directory(p.join(appDir.path, 'epub_books'));
if (!await booksDir.exists()) await booksDir.create(recursive: true);
final permanentPath = p.join(booksDir.path, '$bookId.epub');
final bookDir = Directory(p.join(appDir.path, 'epub_books', bookId));
if (!await bookDir.exists()) await bookDir.create(recursive: true);
final permanentPath = p.join(bookDir.path, 'book.epub');
await File(sourcePath).copy(permanentPath);
// 从永久副本解析
@@ -97,9 +97,9 @@ class EpubService {
final coverFile = File(p.join(extractDir, coverRelPath));
if (!await coverFile.exists()) return null;
// 保存到应用文档目录
// 保存到 epub_books/{bookId}/ 目录
final appDir = await getApplicationDocumentsDirectory();
final coverDir = p.join(appDir.path, 'images', 'books', bookId);
final coverDir = p.join(appDir.path, 'epub_books', bookId);
await Directory(coverDir).create(recursive: true);
final ext = p.extension(coverFile.path).toLowerCase();
final destPath = p.join(coverDir, 'cover$ext');
@@ -142,19 +142,11 @@ class EpubService {
if (await dir.exists()) await dir.delete(recursive: true);
} catch (_) {}
// 清理封面
// 清理 epub_books/{bookId}/ 目录epub + 封面
try {
final appDir = await getApplicationDocumentsDirectory();
final coverDir = p.join(appDir.path, 'images', 'books', bookId);
final dir = Directory(coverDir);
if (await dir.exists()) await dir.delete(recursive: true);
} catch (_) {}
// 清理永久 EPUB 文件
try {
final appDir = await getApplicationDocumentsDirectory();
final epubFile = File(p.join(appDir.path, 'epub_books', '$bookId.epub'));
if (await epubFile.exists()) await epubFile.delete();
final bookDir = Directory(p.join(appDir.path, 'epub_books', bookId));
if (await bookDir.exists()) await bookDir.delete(recursive: true);
} catch (_) {}
// 软删除数据库记录

View File

@@ -1,6 +1,35 @@
import 'package:flutter/material.dart';
import 'reader_scripts.dart';
/// 阅读器主题预设
class ReaderThemePreset {
final String name;
final Color surface;
final Color onSurface;
final bool isDark;
const ReaderThemePreset({
required this.name,
required this.surface,
required this.onSurface,
this.isDark = false,
});
}
class ReaderThemePresets {
static const List<ReaderThemePreset> presets = [
ReaderThemePreset(name: '跟随App', surface: Colors.white, onSurface: Colors.black),
ReaderThemePreset(name: '纯白', surface: Color(0xFFFFFFFF), onSurface: Color(0xFF1A1A1A)),
ReaderThemePreset(name: '护眼', surface: Color(0xFFF4ECD8), onSurface: Color(0xFF5B4636)),
ReaderThemePreset(name: '抹茶', surface: Color(0xFFF6FBF5), onSurface: Color(0xFF2E3E2E)),
ReaderThemePreset(name: '樱花', surface: Color(0xFFFFF8F8), onSurface: Color(0xFF4A2030)),
ReaderThemePreset(name: '午夜蓝', surface: Color(0xFFF7F9FC), onSurface: Color(0xFF1A2A3A)),
ReaderThemePreset(name: '深色', surface: Color(0xFF191919), onSurface: Color(0xFFD4D4D4), isDark: true),
ReaderThemePreset(name: '深色护眼', surface: Color(0xFF1C1A18), onSurface: Color(0xFFC8B8A0), isDark: true),
ReaderThemePreset(name: '咖啡', surface: Color(0xFFFCF8F3), onSurface: Color(0xFF3E2E1E)),
];
}
class EpubTheme {
final double zoom;
final bool shouldOverrideTextColor;

View File

@@ -133,4 +133,32 @@ class ReaderDao {
final db = await _db.database;
return db.delete('book_annotations', where: 'id = ?', whereArgs: [id]);
}
// ─── bookmarks ──────────────────────────────────────────────────
/// 获取某本书的所有书签
Future<List<Map<String, dynamic>>> getBookmarksByBookId(String bookId) async {
final db = await _db.database;
return db.query(
'book_annotations',
where: 'book_id = ? AND type = ?',
whereArgs: [bookId, 'bookmark'],
orderBy: 'created_at DESC',
);
}
/// 插入书签
Future<int> insertBookmark(Map<String, dynamic> bookmark) async {
final db = await _db.database;
return db.insert('book_annotations', {
...bookmark,
'type': 'bookmark',
});
}
/// 删除书签
Future<int> deleteBookmark(int id) async {
final db = await _db.database;
return db.delete('book_annotations', where: 'id = ?', whereArgs: [id]);
}
}

View File

@@ -28,6 +28,15 @@ class ReaderSettings {
/// When true, volume up/down keys turn pages in the reader.
final bool volumeKeyTurnsPage;
/// Reader theme preset index (0 = follow app, 1-8 = presets, 9 = custom).
final int themeIndex;
/// Custom background color (ARGB int), used when themeIndex == 9.
final int customBgColor;
/// Custom text color (ARGB int), used when themeIndex == 9.
final int customTextColor;
const ReaderSettings({
this.zoom = 1.0,
this.followAppTheme = true,
@@ -40,6 +49,9 @@ class ReaderSettings {
this.fontFileName,
this.overrideFontFamily = false,
this.volumeKeyTurnsPage = false,
this.themeIndex = 0,
this.customBgColor = 0xFFFFFFFF,
this.customTextColor = 0xFF1A1A1A,
});
// Sentinel: lets copyWith(fontFileName: null) mean "set to null" rather than
@@ -58,6 +70,9 @@ class ReaderSettings {
Object? fontFileName = _kUnset,
bool? overrideFontFamily,
bool? volumeKeyTurnsPage,
int? themeIndex,
int? customBgColor,
int? customTextColor,
}) {
return ReaderSettings(
zoom: zoom ?? this.zoom,
@@ -73,21 +88,78 @@ class ReaderSettings {
: fontFileName as String?,
overrideFontFamily: overrideFontFamily ?? this.overrideFontFamily,
volumeKeyTurnsPage: volumeKeyTurnsPage ?? this.volumeKeyTurnsPage,
themeIndex: themeIndex ?? this.themeIndex,
customBgColor: customBgColor ?? this.customBgColor,
customTextColor: customTextColor ?? this.customTextColor,
);
}
EpubTheme toEpubTheme(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
ColorScheme colorScheme;
bool shouldOverride = true;
if (themeIndex == 0) {
// 跟随 App 主题
colorScheme = Theme.of(context).colorScheme;
} else {
Color bg;
Color text;
bool isDark;
if (themeIndex == 9) {
// 自定义颜色
bg = Color(customBgColor);
text = Color(customTextColor);
isDark = ThemeData.estimateBrightnessForColor(bg) == Brightness.dark;
} else if (themeIndex >= 1 &&
themeIndex <= ReaderThemePresets.presets.length) {
final preset = ReaderThemePresets.presets[themeIndex];
bg = preset.surface;
text = preset.onSurface;
isDark = preset.isDark;
} else {
colorScheme = Theme.of(context).colorScheme;
return EpubTheme(
zoom: zoom,
shouldOverrideTextColor: true,
colorScheme: colorScheme,
padding: EdgeInsets.only(
top: marginTop, bottom: marginBottom,
left: marginLeft, right: marginRight,
),
fontFileName: fontFileName,
overrideFontFamily: overrideFontFamily,
);
}
colorScheme = ColorScheme(
brightness: isDark ? Brightness.dark : Brightness.light,
primary: text,
onPrimary: bg,
secondary: text,
onSecondary: bg,
error: const Color(0xFFDC2626),
onError: bg,
surface: bg,
onSurface: text,
surfaceContainerHighest: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFF0F0F0),
surfaceContainerHigh: isDark ? const Color(0xFF222222) : const Color(0xFFFAFAFA),
surfaceContainer: isDark ? const Color(0xFF1E1E1E) : const Color(0xFFF5F5F5),
surfaceContainerLow: isDark ? const Color(0xFF1A1A1A) : const Color(0xFFFAFAFA),
outline: isDark ? const Color(0xFF444444) : const Color(0xFFCCCCCC),
outlineVariant: isDark ? const Color(0xFF333333) : const Color(0xFFE5E5E5),
onSurfaceVariant: isDark ? const Color(0xFFAAAAAA) : const Color(0xFF666666),
primaryContainer: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFF0F0F0),
);
}
return EpubTheme(
zoom: zoom,
shouldOverrideTextColor: true,
shouldOverrideTextColor: shouldOverride,
colorScheme: colorScheme,
padding: EdgeInsets.only(
top: marginTop,
bottom: marginBottom,
left: marginLeft,
right: marginRight,
top: marginTop, bottom: marginBottom,
left: marginLeft, right: marginRight,
),
fontFileName: fontFileName,
overrideFontFamily: overrideFontFamily,
@@ -115,6 +187,9 @@ class ReaderSettings {
}
await prefs.setBool('${_kPrefix}overrideFontFamily', overrideFontFamily);
await prefs.setBool('${_kPrefix}volumeKeyTurnsPage', volumeKeyTurnsPage);
await prefs.setInt('${_kPrefix}themeIndex', themeIndex);
await prefs.setInt('${_kPrefix}customBgColor', customBgColor);
await prefs.setInt('${_kPrefix}customTextColor', customTextColor);
}
static Future<ReaderSettings> load() async {