## 新增功能 - EPUB文件导入功能:支持文件选择、解析和存储 - 文件重复检测:避免重复导入同一文件 - 导入状态反馈:成功/失败消息提示 ## 模型扩展 - Book模型新增多作者支持(authors字段) - 新增章节数统计(chapterCount字段) - 新增语言标识(language字段) - 新增EPUB标识符(identifier字段) - 优化TypeAdapter序列化支持 ## 服务优化 - 新增EpubParserService:EPUB文件解析服务 - 改进DatabaseService:错误处理和数据迁移 - 优化BookRepository:调试日志和错误追踪 ## 依赖更新 - 新增epubx ^4.0.0:EPUB电子书解析库 - 更新pubspec.lock:同步依赖版本 ## UI改进 - AppHeader组件集成完整导入功能 - SafeArea适配:避免系统状态栏重叠 - 优化测试数据结构 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
314 lines
8.6 KiB
Dart
314 lines
8.6 KiB
Dart
import 'package:hive/hive.dart';
|
||
part 'book.g.dart';
|
||
|
||
// 电子书数据模型
|
||
// 这是Flutter中数据模型的标准写法,后续其他模型都可以参考这个结构
|
||
|
||
/// 书籍文件格式枚举
|
||
/// 使用enum提供类型安全的枚举值
|
||
@HiveType(typeId: 0)
|
||
enum BookFormat {
|
||
@HiveField(0)
|
||
epub, // EPUB格式电子书
|
||
@HiveField(1)
|
||
mobi, // Kindle MOBI格式
|
||
@HiveField(2)
|
||
txt, // 纯文本格式
|
||
@HiveField(3)
|
||
pdf // PDF格式
|
||
}
|
||
|
||
/// 阅读状态枚举
|
||
@HiveType(typeId: 1)
|
||
enum ReadingStatus {
|
||
@HiveField(0)
|
||
reading, // 阅读中
|
||
@HiveField(1)
|
||
completed, // 已完成
|
||
@HiveField(2)
|
||
pending // 待阅读
|
||
}
|
||
|
||
/// 书籍数据模型
|
||
///
|
||
/// 设计原则:
|
||
/// 1. 所有字段都是final(不可变性)
|
||
/// 2. 提供copyWith方法创建新对象
|
||
/// 3. 提供序列化和反序列化方法
|
||
/// 4. 使用空值安全语法
|
||
@HiveType(typeId: 2)
|
||
class Book {
|
||
/// 唯一标识符 - 使用UUID确保全局唯一
|
||
@HiveField(0)
|
||
final String id;
|
||
|
||
/// 书名 - 必需字段
|
||
@HiveField(1)
|
||
final String title;
|
||
|
||
/// 主要作者 - 用于显示的作者名称
|
||
@HiveField(2)
|
||
final String? author;
|
||
|
||
/// 完整作者列表 - 支持多个作者
|
||
@HiveField(16)
|
||
final List<String> authors;
|
||
|
||
/// 出版社 - 可选字段
|
||
@HiveField(3)
|
||
final String? publisher;
|
||
|
||
/// 书籍简介 - 可选字段
|
||
@HiveField(4)
|
||
final String? description;
|
||
|
||
/// 封面图片路径 - 可选字段
|
||
@HiveField(5)
|
||
final String? coverImagePath;
|
||
|
||
/// 文件路径 - 必需字段,指向实际的电子书文件
|
||
@HiveField(6)
|
||
final String filePath;
|
||
|
||
/// 文件格式 - 必需字段
|
||
@HiveField(7)
|
||
final BookFormat format;
|
||
|
||
/// 文件大小(字节)- 必需字段
|
||
@HiveField(8)
|
||
final int fileSize;
|
||
|
||
/// 添加到书架的时间 - 必需字段
|
||
@HiveField(9)
|
||
final DateTime addedDate;
|
||
|
||
/// 阅读状态 - 必需字段
|
||
@HiveField(10)
|
||
final ReadingStatus status;
|
||
|
||
/// 评分(1-5星)- 可选字段
|
||
@HiveField(11)
|
||
final double? rating;
|
||
|
||
/// 标签列表 - 使用空列表作为默认值,避免null指针
|
||
@HiveField(12)
|
||
final List<String> 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的空值安全特性
|
||
const Book({
|
||
required this.id, // 必需
|
||
required this.title, // 必需
|
||
this.author, // 可选
|
||
this.authors = const [], // 默认空列表,支持多作者
|
||
this.publisher, // 可选
|
||
this.description, // 可选
|
||
this.coverImagePath, // 可选
|
||
required this.filePath, // 必需
|
||
required this.format, // 必需
|
||
required this.fileSize, // 必需
|
||
required this.addedDate, // 必需
|
||
required this.status, // 必需
|
||
this.rating, // 可选
|
||
this.tags = const [], // 默认空列表,避免null
|
||
required this.totalPages, // 必需
|
||
this.chapterCount, // 可选章节数
|
||
this.language, // 可选语言
|
||
this.identifier, // 可选EPUB标识符
|
||
});
|
||
|
||
/// copyWith方法 - 创建对象的副本
|
||
///
|
||
/// 这是Flutter中不可变对象的标准模式
|
||
/// 当需要修改对象时,不是直接修改原对象,而是创建一个新对象
|
||
///
|
||
/// 使用示例:
|
||
/// ```dart
|
||
/// final newBook = book.copyWith(
|
||
/// title: "新书名",
|
||
/// status: ReadingStatus.completed,
|
||
/// );
|
||
/// ```
|
||
Book copyWith({
|
||
String? id,
|
||
String? title,
|
||
String? author,
|
||
List<String>? authors,
|
||
String? publisher,
|
||
String? description,
|
||
String? coverImagePath,
|
||
String? filePath,
|
||
BookFormat? format,
|
||
int? fileSize,
|
||
DateTime? addedDate,
|
||
ReadingStatus? status,
|
||
double? rating,
|
||
List<String>? 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,
|
||
filePath: filePath ?? this.filePath,
|
||
format: format ?? this.format,
|
||
fileSize: fileSize ?? this.fileSize,
|
||
addedDate: addedDate ?? this.addedDate,
|
||
status: status ?? this.status,
|
||
rating: rating ?? this.rating,
|
||
tags: tags ?? this.tags,
|
||
totalPages: totalPages ?? this.totalPages,
|
||
chapterCount: chapterCount ?? this.chapterCount,
|
||
language: language ?? this.language,
|
||
identifier: identifier ?? this.identifier,
|
||
);
|
||
}
|
||
|
||
/// toMap方法 - 将对象转换为Map
|
||
///
|
||
/// 这个方法用于:
|
||
/// 1. 本地存储(如Hive数据库)
|
||
/// 2. 网络传输(转换为JSON)
|
||
/// 3. 状态保存
|
||
Map<String, dynamic> toMap() {
|
||
return {
|
||
'id': id,
|
||
'title': title,
|
||
'author': author,
|
||
'authors': authors, // 新增作者列表
|
||
'publisher': publisher,
|
||
'description': description,
|
||
'coverImagePath': coverImagePath,
|
||
'filePath': filePath,
|
||
'format': format.name, // 枚举值转换为字符串
|
||
'fileSize': fileSize,
|
||
'addedDate': addedDate.toIso8601String(), // DateTime转换为字符串
|
||
'status': status.name,
|
||
'rating': rating,
|
||
'tags': tags, // List直接存储
|
||
'totalPages': totalPages,
|
||
'chapterCount': chapterCount, // 新增章节数
|
||
'language': language, // 新增语言
|
||
'identifier': identifier, // 新增EPUB标识符
|
||
};
|
||
}
|
||
|
||
/// fromMap构造函数 - 从Map创建Book对象
|
||
///
|
||
/// 这是toMap的逆操作,用于:
|
||
/// 1. 从本地存储中读取数据
|
||
/// 2. 从网络请求中解析数据
|
||
/// 3. 从保存的状态中恢复对象
|
||
factory Book.fromMap(Map<String, dynamic> map) {
|
||
return Book(
|
||
id: map['id'],
|
||
title: map['title'],
|
||
author: map['author'],
|
||
authors: List<String>.from(map['authors'] ?? []), // 新增作者列表,向后兼容
|
||
publisher: map['publisher'],
|
||
description: map['description'],
|
||
coverImagePath: map['coverImagePath'],
|
||
filePath: map['filePath'],
|
||
format: BookFormat.values.firstWhere(
|
||
// 字符串转换回枚举
|
||
(e) => e.name == map['format'],
|
||
),
|
||
fileSize: map['fileSize'],
|
||
addedDate: DateTime.parse(map['addedDate']),
|
||
status: ReadingStatus.values.firstWhere(
|
||
(e) => e.name == map['status'],
|
||
),
|
||
rating: map['rating'],
|
||
tags: List<String>.from(map['tags'] ?? []), // 确保返回List<String>
|
||
totalPages: map['totalPages'],
|
||
chapterCount: map['chapterCount'], // 新增章节数
|
||
language: map['language'], // 新增语言
|
||
identifier: map['identifier'], // 新增EPUB标识符
|
||
);
|
||
}
|
||
|
||
/// 创建一个新Book实例的工厂方法
|
||
///
|
||
/// 这是一个便利方法,用于创建基本的Book对象
|
||
/// 通常在用户导入新书籍时使用
|
||
factory Book.create({
|
||
required String title,
|
||
required String filePath,
|
||
required BookFormat format,
|
||
required int fileSize,
|
||
String? author,
|
||
List<String>? authors,
|
||
List<String>? 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 ?? [], // 默认空列表
|
||
);
|
||
}
|
||
|
||
/// 生成唯一ID的私有方法
|
||
static String _generateId() {
|
||
return '${DateTime.now().millisecondsSinceEpoch}_${(DateTime.now().microsecond).toString()}';
|
||
}
|
||
|
||
/// 重写toString方法,便于调试
|
||
@override
|
||
String toString() {
|
||
return 'Book(id: $id, title: $title, author: $author, format: $format)';
|
||
}
|
||
|
||
/// 重写equals和hashCode,用于对象比较
|
||
@override
|
||
bool operator ==(Object other) {
|
||
if (identical(this, other)) return true;
|
||
return other is Book && other.id == id;
|
||
}
|
||
|
||
@override
|
||
int get hashCode => id.hashCode;
|
||
}
|