generated from dellevin/template
结构重构
This commit is contained in:
71
lib/utils/reader/book_file_helper.dart
Normal file
71
lib/utils/reader/book_file_helper.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'dart:io';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// 阅读器文件路径管理
|
||||
class BookFileHelper {
|
||||
static final BookFileHelper instance = BookFileHelper._init();
|
||||
BookFileHelper._init();
|
||||
|
||||
String? _rootPath;
|
||||
|
||||
Future<String> get _root async {
|
||||
if (_rootPath != null) return _rootPath!;
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
_rootPath = p.join(dir.path, 'mooknote', 'book_file');
|
||||
await Directory(_rootPath!).create(recursive: true);
|
||||
return _rootPath!;
|
||||
}
|
||||
|
||||
Future<String> get bookFileRoot async => _root;
|
||||
|
||||
Future<String> get coverDir async {
|
||||
final root = await _root;
|
||||
final dir = p.join(root, 'cover');
|
||||
await Directory(dir).create(recursive: true);
|
||||
return dir;
|
||||
}
|
||||
|
||||
Future<String> bookDir(String bookId) async {
|
||||
final root = await _root;
|
||||
final dir = p.join(root, bookId);
|
||||
await Directory(dir).create(recursive: true);
|
||||
return dir;
|
||||
}
|
||||
|
||||
Future<String> bookFile(String bookId, String fileName) async {
|
||||
final dir = await bookDir(bookId);
|
||||
return p.join(dir, fileName);
|
||||
}
|
||||
|
||||
String? relativePath(String absolutePath) {
|
||||
if (_rootPath == null) return null;
|
||||
if (absolutePath.startsWith(_rootPath!)) {
|
||||
return absolutePath.substring(_rootPath!.length + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> absolutePath(String relativePath) async {
|
||||
final root = await _root;
|
||||
return p.join(root, relativePath);
|
||||
}
|
||||
|
||||
Future<void> deleteBookFiles(String bookId) async {
|
||||
final dir = await bookDir(bookId);
|
||||
if (await Directory(dir).exists()) {
|
||||
await Directory(dir).delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// 同步初始化(必须在使用 resolveAbsolutePath 前调用一次 bookFileRoot)
|
||||
Future<void> ensureInitialized() async {
|
||||
await _root;
|
||||
}
|
||||
|
||||
/// 根据相对路径解析绝对路径(调用前需确保已初始化)
|
||||
String resolveAbsolutePath(String relativePath) {
|
||||
if (_rootPath == null) return relativePath;
|
||||
return p.join(_rootPath!, relativePath);
|
||||
}
|
||||
}
|
||||
69
lib/utils/reader/reader_book_dao.dart
Normal file
69
lib/utils/reader/reader_book_dao.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/reader_book.dart';
|
||||
import 'database_helper.dart';
|
||||
|
||||
/// 阅读器书籍 DAO
|
||||
class ReaderBookDao {
|
||||
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
|
||||
|
||||
Future<T> _wrap<T>(String op, Future<T> Function() fn) async {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (e) {
|
||||
debugPrint('[ReaderBookDao] $op error: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ReaderBook>> getAllReaderBooks() => _wrap('getAllReaderBooks', () async {
|
||||
final db = await _dbHelper.database;
|
||||
final List<Map<String, dynamic>> maps = await db.query(
|
||||
'reader_books',
|
||||
where: 'is_deleted = ?',
|
||||
whereArgs: [0],
|
||||
orderBy: 'created_at DESC',
|
||||
);
|
||||
return List.generate(maps.length, (i) => ReaderBook.fromJson(maps[i]));
|
||||
});
|
||||
|
||||
Future<ReaderBook?> getReaderBookById(String id) => _wrap('getReaderBookById', () async {
|
||||
final db = await _dbHelper.database;
|
||||
final List<Map<String, dynamic>> maps = await db.query(
|
||||
'reader_books',
|
||||
where: 'id = ? AND is_deleted = ?',
|
||||
whereArgs: [id, 0],
|
||||
);
|
||||
if (maps.isEmpty) return null;
|
||||
return ReaderBook.fromJson(maps.first);
|
||||
});
|
||||
|
||||
Future<void> insertReaderBook(ReaderBook book) => _wrap('insertReaderBook', () async {
|
||||
final db = await _dbHelper.database;
|
||||
await db.insert('reader_books', book.toJson());
|
||||
});
|
||||
|
||||
Future<void> updateReaderBook(ReaderBook book) => _wrap('updateReaderBook', () async {
|
||||
final db = await _dbHelper.database;
|
||||
await db.update(
|
||||
'reader_books',
|
||||
book.toJson(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [book.id],
|
||||
);
|
||||
});
|
||||
|
||||
Future<void> deleteReaderBook(String id) => _wrap('deleteReaderBook', () async {
|
||||
final db = await _dbHelper.database;
|
||||
await db.update(
|
||||
'reader_books',
|
||||
{'is_deleted': 1, 'updated_at': DateTime.now().toIso8601String()},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
});
|
||||
|
||||
Future<void> permanentDeleteReaderBook(String id) => _wrap('permanentDeleteReaderBook', () async {
|
||||
final db = await _dbHelper.database;
|
||||
await db.delete('reader_books', where: 'id = ?', whereArgs: [id]);
|
||||
});
|
||||
}
|
||||
69
lib/utils/reader/reader_url_generator.dart
Normal file
69
lib/utils/reader/reader_url_generator.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'dart:convert';
|
||||
import '../service/book_server.dart';
|
||||
import 'color_converter.dart';
|
||||
|
||||
/// 生成 foliate-js 阅读器 URL
|
||||
String generateReaderUrl({
|
||||
required String fileUrl,
|
||||
String cfi = '',
|
||||
required String backgroundColor,
|
||||
required String textColor,
|
||||
bool isDarkMode = false,
|
||||
}) {
|
||||
final indexHtmlPath = 'http://127.0.0.1:${Server().port}/foliate-js/index.html';
|
||||
|
||||
final jsBg = convertDartColorToJs(backgroundColor);
|
||||
final jsTc = convertDartColorToJs(textColor);
|
||||
|
||||
final style = {
|
||||
'fontSize': 100, // 100 = base 100%
|
||||
'fontName': '',
|
||||
'fontPath': '',
|
||||
'fontWeight': 400,
|
||||
'letterSpacing': 0,
|
||||
'spacing': 1.6,
|
||||
'paragraphSpacing': 0.6,
|
||||
'textIndent': 2,
|
||||
'fontColor': '#$jsTc',
|
||||
'backgroundColor': '#$jsBg',
|
||||
'topMargin': 25,
|
||||
'bottomMargin': 25,
|
||||
'sideMargin': 15,
|
||||
'justify': true,
|
||||
'hyphenate': false,
|
||||
'pageTurnStyle': 'slide',
|
||||
'maxColumnCount': 1,
|
||||
'columnThreshold': 3,
|
||||
'writingMode': 'horizontal-tb',
|
||||
'textAlign': 'justify',
|
||||
'backgroundImage': '',
|
||||
'bgimgBlur': 0,
|
||||
'bgimgOpacity': 1.0,
|
||||
'bgimgFit': 'cover',
|
||||
'allowScript': false,
|
||||
'customCSS': '',
|
||||
'customCSSEnabled': false,
|
||||
'useBookStyles': true,
|
||||
'headingFontSize': 130,
|
||||
'codeHighlightTheme': 'atom-one-light',
|
||||
};
|
||||
|
||||
final readingRules = {
|
||||
'convertChineseMode': 'none',
|
||||
'bionicReadingMode': false,
|
||||
};
|
||||
|
||||
final params = {
|
||||
'importing': false,
|
||||
'url': fileUrl,
|
||||
'initialCfi': cfi,
|
||||
'style': style,
|
||||
'readingRules': readingRules,
|
||||
};
|
||||
|
||||
final queryParts = params.entries
|
||||
.map((e) => '${e.key}=${Uri.encodeComponent(jsonEncode(e.value))}')
|
||||
.join('&');
|
||||
|
||||
return '$indexHtmlPath?$queryParts';
|
||||
}
|
||||
Reference in New Issue
Block a user