readful/lib/models/book.dart
ddshi bef0de5909 feat: 完成文件导入功能和Book模型扩展
## 新增功能
- 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>
2025-12-04 20:34:16 +08:00

314 lines
8.6 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}