diff --git a/lib/components/app_header.dart b/lib/components/app_header.dart index 205383e..af9e729 100644 --- a/lib/components/app_header.dart +++ b/lib/components/app_header.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import '../pages/search_page.dart'; +import '../services/epub_parser_service.dart'; +import '../services/book_repository.dart'; +import 'package:file_picker/file_picker.dart'; /// 应用顶部导航组件 /// /// 可复用的顶部导航栏组件,集成在搜索栏内的导入按钮。 /// 提供统一的搜索和文件导入功能,支持主题自适应。 -class AppHeader extends StatelessWidget { +class AppHeader extends StatefulWidget { /// 标题文本(可选),某些页面可以显示标题而非搜索栏 final String? title; @@ -30,6 +33,14 @@ class AppHeader extends StatelessWidget { this.searchHint = '搜索书名或内容...', }); + @override + State createState() => _AppHeaderState(); +} + +class _AppHeaderState extends State { + final EpubParserService _epubParserService = EpubParserService(); + final BookRepository _bookRepository = BookRepository(); + /// 构建组件的UI结构 @override Widget build(BuildContext context) { @@ -48,9 +59,9 @@ class AppHeader extends StatelessWidget { child: Row( children: [ // 标题区域(条件渲染) - if (title != null) ...[ + if (widget.title != null) ...[ Text( - title!, + widget.title!, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w600, ), @@ -59,7 +70,7 @@ class AppHeader extends StatelessWidget { ], // 搜索栏区域(占据剩余空间) - if (showSearchBar) ...[ + if (widget.showSearchBar) ...[ Expanded( child: _buildSearchBar(context), ), @@ -84,7 +95,7 @@ class AppHeader extends StatelessWidget { color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(22), - onTap: onSearchPressed ?? () => _navigateToSearchPage(context), + onTap: widget.onSearchPressed ?? () => _navigateToSearchPage(context), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -97,7 +108,7 @@ class AppHeader extends StatelessWidget { const SizedBox(width: 8), Expanded( child: Text( - searchHint, + widget.searchHint, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context) .colorScheme @@ -128,7 +139,7 @@ class AppHeader extends StatelessWidget { color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(16), - onTap: onImportPressed, + onTap: widget.onImportPressed ?? () => _importEpubFile(), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -147,13 +158,76 @@ class AppHeader extends StatelessWidget { ), ); } + + /// 导航到搜索页面 + void _navigateToSearchPage(BuildContext context) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const SearchPage(), + ), + ); + } + + /// 导入EPUB文件 + Future _importEpubFile() async { + try { + // 1. 使用FilePicker选择EPUB文件 + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['epub'], + ); + + if (result != null && result.files.single.path != null) { + final filePath = result.files.single.path!; + + // 2. 检查文件是否已存在 + final existingBooks = await _bookRepository.getAllBooks(); + final isDuplicate = existingBooks.any((book) => book.filePath == filePath); + + if (isDuplicate) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('该文件已经导入过了'), + backgroundColor: Colors.orange, + ), + ); + } + return; + } + + // 3. 解析EPUB文件 + final epubBook = await _epubParserService.parseEpubFile(filePath); + + // 4. 提取元数据并创建Book对象 + final book = await _epubParserService.extractBookMetadata(epubBook, filePath); + + // 5. 保存到数据库 + await _bookRepository.addBook(book); + + // 6. 显示成功消息 + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('成功导入: ${book.title}'), + backgroundColor: Colors.green, + duration: const Duration(seconds: 2), + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('导入失败: $e'), + backgroundColor: Colors.red, + duration: const Duration(seconds: 3), + ), + ); + } + } + } } -// 添加这个新方法 -void _navigateToSearchPage(BuildContext context) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const SearchPage(), - ), - ); -} \ No newline at end of file + diff --git a/lib/main.dart b/lib/main.dart index e650905..25bad72 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,9 +37,11 @@ void main() async { // 数据库包含:书籍、书架、书签、高亮批注等数据 try { await DatabaseService.instance.init(); - print('✅ Hive数据库初始化成功'); + print('✅ 数据库初始化完成,启动应用'); } catch (e) { print('❌ 数据库初始化失败: $e'); + print('🚨 应用将退出,因为数据库初始化是必须的'); + return; } // 启动Readful应用 @@ -285,66 +287,61 @@ class ReadfulApp extends StatelessWidget { // 创建测试数据 List _createTestData() { return [ - Book( - id: '1', + Book.create( title: 'Flutter开发实战', - author: '张三', - description: '一本关于Flutter移动开发的实战指南', - format: BookFormat.epub, filePath: '/path/to/flutter.epub', - fileSize: 2048000, - addedDate: DateTime.now().subtract(const Duration(days: 30)), - status:ReadingStatus.reading, - totalPages: 100 - ), - Book( - id: '2', - title: 'Dart编程语言', - author: '李四', - description: '深入理解Dart编程语言的核心概念', format: BookFormat.epub, + fileSize: 2048000, + author: '张三', + totalPages: 100, + ).copyWith( + addedDate: DateTime.now().subtract(const Duration(days: 30)), + status: ReadingStatus.reading, + ), + Book.create( + title: 'Dart编程语言', filePath: '/path/to/dart.epub', + format: BookFormat.epub, fileSize: 1536000, + author: '李四', + totalPages: 333, + ).copyWith( addedDate: DateTime.now().subtract(const Duration(days: 60)), status: ReadingStatus.completed, - totalPages: 333 ), - Book( - id: '3', + Book.create( title: '移动应用设计指南', - author: '王五', - description: '移动应用UI/UX设计的最佳实践', - format: BookFormat.pdf, filePath: '/path/to/design.pdf', + format: BookFormat.pdf, fileSize: 5120000, - addedDate: DateTime.now().subtract(const Duration(days: 10)), + author: '王五', + totalPages: 1000, tags: ['设计', '移动应用', 'UI'], + ).copyWith( + addedDate: DateTime.now().subtract(const Duration(days: 10)), status: ReadingStatus.pending, - totalPages: 1000 ), - Book( - id: '4', + Book.create( title: '人工智能简史', - author: null, // 测试作者为null的情况 - description: '人工智能发展历程的详细介绍', - format: BookFormat.mobi, - filePath: '/path/to/ai.mobi', - fileSize: 1024000, + filePath: '/path/to/ai_history.epub', + format: BookFormat.epub, + fileSize: 3072000, + // author: null, // 测试作者为null的情况 + totalPages: 192, + ).copyWith( addedDate: DateTime.now().subtract(const Duration(days: 5)), status: ReadingStatus.reading, - totalPages: 192 ), - Book( - id: '5', + Book.create( title: '算法与数据结构', - author: '赵六', - description: '程序员必备的算法和数据结构知识', - format: BookFormat.epub, filePath: '/path/to/algorithm.epub', + format: BookFormat.epub, fileSize: 3072000, + author: '赵六', + totalPages: 250, + ).copyWith( addedDate: DateTime.now().subtract(const Duration(days: 20)), status: ReadingStatus.completed, - totalPages: 250 ), ]; } diff --git a/lib/models/book.dart b/lib/models/book.dart index 73bd938..86c50c5 100644 --- a/lib/models/book.dart +++ b/lib/models/book.dart @@ -46,10 +46,14 @@ class Book { @HiveField(1) final String title; - /// 作者 - 可为null(因为有些书籍可能没有作者信息) + /// 主要作者 - 用于显示的作者名称 @HiveField(2) final String? author; + /// 完整作者列表 - 支持多个作者 + @HiveField(16) + final List authors; + /// 出版社 - 可选字段 @HiveField(3) final String? publisher; @@ -90,10 +94,26 @@ class Book { @HiveField(12) final List tags; - /// 总页数 - 必需字段 + /// 总页数 - 不同格式的含义不同 + /// PDF: 实际页数 + /// EPUB: 使用章节数 + /// TXT: 总行数 + /// MOBI: 位置总数 @HiveField(13) final int totalPages; + /// 章节数量(适用于EPUB等流式格式) + @HiveField(17) + final int? chapterCount; + + /// 语言(如'zh-CN', 'en-US'等) + @HiveField(18) + final String? language; + + /// EPUB唯一标识符 + @HiveField(19) + final String? identifier; + /// 构造函数 /// /// 使用required关键字标记必需字段,这是Flutter的空值安全特性 @@ -101,6 +121,7 @@ class Book { required this.id, // 必需 required this.title, // 必需 this.author, // 可选 + this.authors = const [], // 默认空列表,支持多作者 this.publisher, // 可选 this.description, // 可选 this.coverImagePath, // 可选 @@ -112,6 +133,9 @@ class Book { this.rating, // 可选 this.tags = const [], // 默认空列表,避免null required this.totalPages, // 必需 + this.chapterCount, // 可选章节数 + this.language, // 可选语言 + this.identifier, // 可选EPUB标识符 }); /// copyWith方法 - 创建对象的副本 @@ -130,6 +154,7 @@ class Book { String? id, String? title, String? author, + List? authors, String? publisher, String? description, String? coverImagePath, @@ -141,11 +166,15 @@ class Book { double? rating, List? tags, int? totalPages, + int? chapterCount, + String? language, + String? identifier, }) { return Book( id: id ?? this.id, // 如果id不为null就使用新值,否则保持原值 title: title ?? this.title, author: author ?? this.author, + authors: authors ?? this.authors, publisher: publisher ?? this.publisher, description: description ?? this.description, coverImagePath: coverImagePath ?? this.coverImagePath, @@ -157,6 +186,9 @@ class Book { rating: rating ?? this.rating, tags: tags ?? this.tags, totalPages: totalPages ?? this.totalPages, + chapterCount: chapterCount ?? this.chapterCount, + language: language ?? this.language, + identifier: identifier ?? this.identifier, ); } @@ -171,6 +203,7 @@ class Book { 'id': id, 'title': title, 'author': author, + 'authors': authors, // 新增作者列表 'publisher': publisher, 'description': description, 'coverImagePath': coverImagePath, @@ -182,6 +215,9 @@ class Book { 'rating': rating, 'tags': tags, // List直接存储 'totalPages': totalPages, + 'chapterCount': chapterCount, // 新增章节数 + 'language': language, // 新增语言 + 'identifier': identifier, // 新增EPUB标识符 }; } @@ -196,6 +232,7 @@ class Book { id: map['id'], title: map['title'], author: map['author'], + authors: List.from(map['authors'] ?? []), // 新增作者列表,向后兼容 publisher: map['publisher'], description: map['description'], coverImagePath: map['coverImagePath'], @@ -212,6 +249,9 @@ class Book { rating: map['rating'], tags: List.from(map['tags'] ?? []), // 确保返回List totalPages: map['totalPages'], + chapterCount: map['chapterCount'], // 新增章节数 + language: map['language'], // 新增语言 + identifier: map['identifier'], // 新增EPUB标识符 ); } @@ -225,18 +265,28 @@ class Book { required BookFormat format, required int fileSize, String? author, + List? authors, + List? tags, int totalPages = 0, + int? chapterCount, + String? language, + String? identifier, }) { return Book( id: _generateId(), // 生成唯一ID title: title, author: author, + authors: authors ?? [], // 默认空列表 filePath: filePath, format: format, fileSize: fileSize, totalPages: totalPages, + chapterCount: chapterCount, + language: language, + identifier: identifier, addedDate: DateTime.now(), // 当前时间作为添加时间 status: ReadingStatus.pending, // 默认状态为待阅读 + tags: tags ?? [], // 默认空列表 ); } diff --git a/lib/models/book.g.dart b/lib/models/book.g.dart index c0848ae..617d358 100644 --- a/lib/models/book.g.dart +++ b/lib/models/book.g.dart @@ -20,6 +20,7 @@ class BookAdapter extends TypeAdapter { id: fields[0] as String, title: fields[1] as String, author: fields[2] as String?, + authors: (fields[16] as List).cast(), publisher: fields[3] as String?, description: fields[4] as String?, coverImagePath: fields[5] as String?, @@ -31,19 +32,24 @@ class BookAdapter extends TypeAdapter { rating: fields[11] as double?, tags: (fields[12] as List).cast(), totalPages: fields[13] as int, + chapterCount: fields[17] as int?, + language: fields[18] as String?, + identifier: fields[19] as String?, ); } @override void write(BinaryWriter writer, Book obj) { writer - ..writeByte(14) + ..writeByte(18) ..writeByte(0) ..write(obj.id) ..writeByte(1) ..write(obj.title) ..writeByte(2) ..write(obj.author) + ..writeByte(16) + ..write(obj.authors) ..writeByte(3) ..write(obj.publisher) ..writeByte(4) @@ -65,7 +71,13 @@ class BookAdapter extends TypeAdapter { ..writeByte(12) ..write(obj.tags) ..writeByte(13) - ..write(obj.totalPages); + ..write(obj.totalPages) + ..writeByte(17) + ..write(obj.chapterCount) + ..writeByte(18) + ..write(obj.language) + ..writeByte(19) + ..write(obj.identifier); } @override diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 64a2095..307185f 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -18,17 +18,17 @@ class HomePage extends StatelessWidget { body: Column( children: [ // 顶部导航组件 - SafeArea( + const SafeArea( bottom: false, // 只处理顶部安全区域 child: AppHeader( // onSearchPressed: () { // // TODO: 实现搜索功能 // print('搜索按钮被点击'); // }, - onImportPressed: () { - // TODO: 实现导入功能 - print('导入按钮被点击'); - }, + // onImportPressed: () { + // // TODO: 实现导入功能 + // print('导入按钮被点击'); + // }, ), ), diff --git a/lib/services/book_repository.dart b/lib/services/book_repository.dart index d9ccb79..d383d25 100644 --- a/lib/services/book_repository.dart +++ b/lib/services/book_repository.dart @@ -8,10 +8,14 @@ class BookRepository { Future> getAllBooks() async { // 实现获取所有书籍的逻辑 try { + print('🔍 开始获取所有书籍...'); final booksBox = _databaseService.getBooksBox(); - return booksBox.values.toList(); + final books = booksBox.values.toList(); + print('📚 成功获取 ${books.length} 本书籍'); + return books; } catch (e) { print('❌ 获取所有书籍失败: $e'); + print('❌ 错误详情: ${StackTrace.current}'); rethrow; } } diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart index b89c43d..fb4766a 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -21,8 +21,11 @@ class DatabaseService { try { // 1. 获取应用文档目录 final appDocumentDir = await getApplicationDocumentsDirectory(); + print('📁 应用文档目录: ${appDocumentDir.path}'); + // 2. 初始化Hive await Hive.initFlutter(appDocumentDir.path); + print('✅ Hive初始化完成'); // 3. 注册枚举TypeAdapter(使用内置的枚举适配器) Hive.registerAdapter(BookFormatAdapter()); @@ -30,24 +33,43 @@ class DatabaseService { Hive.registerAdapter(BookshelfTypeAdapter()); Hive.registerAdapter(HighlightColorAdapter()); Hive.registerAdapter(AnnotationTypeAdapter()); - + print('✅ 枚举TypeAdapter注册完成'); // 4. 注册模型TypeAdapter Hive.registerAdapter(BookAdapter()); Hive.registerAdapter(BookshelfAdapter()); Hive.registerAdapter(BookmarkAdapter()); Hive.registerAdapter(HighlightAdapter()); - + print('✅ 模型TypeAdapter注册完成'); - // 5. 打开Box - _booksBox = await Hive.openBox('books'); - _bookshelvesBox = await Hive.openBox('bookshelves'); - _bookmarksBox = await Hive.openBox('bookmarks'); - _highlightsBox = await Hive.openBox('highlights'); + // 5. 打开Box(如果存在数据不兼容,强制清空) + try { + _booksBox = await Hive.openBox('books'); + _bookshelvesBox = await Hive.openBox('bookshelves'); + _bookmarksBox = await Hive.openBox('bookmarks'); + _highlightsBox = await Hive.openBox('highlights'); + } catch (e) { + print('⚠️ Box打开失败,尝试清空数据: $e'); + await Hive.deleteBoxFromDisk('books'); + await Hive.deleteBoxFromDisk('bookshelves'); + await Hive.deleteBoxFromDisk('bookmarks'); + await Hive.deleteBoxFromDisk('highlights'); + + // 重新打开空的Box + _booksBox = await Hive.openBox('books'); + _bookshelvesBox = await Hive.openBox('bookshelves'); + _bookmarksBox = await Hive.openBox('bookmarks'); + _highlightsBox = await Hive.openBox('highlights'); + print('✅ 数据库清空并重新创建成功'); + } + + print('✅ 所有Box打开成功'); + print('📚 当前书籍数量: ${_booksBox.length}'); print('✅ Hive数据库初始化成功'); } catch (e) { print('❌ Hive数据库初始化失败: $e'); + print('❌ 错误堆栈: ${StackTrace.current}'); rethrow; // 重新抛出异常,让调用者知道初始化失败 } } diff --git a/lib/services/epub_parser_service.dart b/lib/services/epub_parser_service.dart new file mode 100644 index 0000000..cc032be --- /dev/null +++ b/lib/services/epub_parser_service.dart @@ -0,0 +1,55 @@ +import 'package:epubx/epubx.dart'; +import '../models/book.dart'; +import 'dart:io'; + +//解析epub文件的服务类 +class EpubParserService { + Future parseEpubFile(String filePath) async { + try { + final bytes = await File(filePath).readAsBytes(); + return await EpubReader.readBook(bytes); + } catch (e) { + throw Exception('EPUB解析失败: $e'); + } + } + + Future extractBookMetadata(EpubBook epubBook, String filePath) async { + try { + // 基本信息 + final title = epubBook.Title ?? '未知标题'; + + // 处理作者列表 + final authorList = epubBook.AuthorList?.map((author) => author.toString()).toList() ?? []; + final primaryAuthor = authorList.isNotEmpty ? authorList.first : '未知作者'; + + // 计算章节数 + final chapters = epubBook.Chapters ?? []; + final chapterCount = chapters.isNotEmpty ? chapters.length : null; + + // 如果有章节数,使用章节数作为totalPages + final totalPages = chapterCount ?? 0; + + return Book.create( + title: title, + filePath: filePath, + format: BookFormat.epub, + fileSize: await File(filePath).length(), + author: primaryAuthor, + authors: authorList, + chapterCount: chapterCount, + totalPages: totalPages, + ); + } catch (e) { + throw Exception('提取元数据失败: $e'); + } + } + + Future saveCoverImage(EpubBook epubBook, String bookId) async { + // 如果没有封面图片,返回null + if (epubBook.CoverImage == null) return null; + + // TODO: 这里需要实现保存封面图片的逻辑 + // 将封面图片保存到本地存储,并返回文件路径 + return null; // 暂时返回null,后续完善 + } +} diff --git a/pubspec.lock b/pubspec.lock index 46ea81d..7b509be 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "6.2.0" + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.6.1" args: dependency: transitive description: @@ -177,6 +185,14 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "2.3.6" + epubx: + dependency: "direct main" + description: + name: epubx + sha256: "0ab9354efa177c4be52c46f857bc15bf83f83a92667fb673465c8f89fca26db3" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "4.0.0" fake_async: dependency: transitive description: @@ -312,6 +328,14 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.3.0" io: dependency: transitive description: @@ -456,6 +480,14 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -504,6 +536,14 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "1.4.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.2.2" shared_preferences: dependency: "direct main" description: @@ -717,6 +757,14 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 98e98f5..03a672b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,9 @@ dependencies: # 状态管理(后续使用) provider: ^6.0.5 # Provider状态管理 + # 电子书解析 + epubx: ^4.0.0 # EPUB电子书解析库 + #存储 shared_preferences: ^2.2.2 # 本地存储简单数据