Compare commits
No commits in common. "fa09de756043ad29827fc3ee172fd9c4850ae78a" and "25b7c5ae35bc43620725a2ecb0ccbe31228dc6cf" have entirely different histories.
fa09de7560
...
25b7c5ae35
@ -2,10 +2,7 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(flutter pub get:*)",
|
||||
"Bash(dir:*)"
|
||||
"Bash(git add:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
250
CLAUDE.md
250
CLAUDE.md
@ -1,250 +0,0 @@
|
||||
# Readful - Flutter 电子书阅读器项目
|
||||
|
||||
## 📖 项目概述
|
||||
**项目名称:** Readful(读ful)
|
||||
**项目类型:** Flutter跨平台电子书阅读器应用
|
||||
**开发阶段:** 数据模型设计 + 数据持久化完成 ✅
|
||||
**当前版本:** v1.0.0+1
|
||||
|
||||
## 🎯 项目目标
|
||||
开发一个功能完整的电子书阅读器,支持:
|
||||
- 多格式电子书导入(EPUB、MOBI、TXT、PDF)
|
||||
- 智能文本高亮和批注系统
|
||||
- 个性化书签和阅读进度管理
|
||||
- 灵活的书架分类系统
|
||||
- 现代化的用户界面设计
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
### 核心技术栈
|
||||
- **Flutter SDK** >=3.0.0 - 跨平台UI框架
|
||||
- **Dart** - 编程语言(空值安全)
|
||||
- **Hive** - 轻量级NoSQL数据库(数据持久化)
|
||||
- **Provider/Riverpod** - 状态管理(待定)
|
||||
- **File Picker** - 文件选择和导入
|
||||
|
||||
### 项目结构
|
||||
```
|
||||
e:\readful\
|
||||
├── lib/ # 源代码目录
|
||||
│ ├── main.dart # 应用入口 + 数据库测试
|
||||
│ ├── models/ # 数据模型目录
|
||||
│ │ ├── book.dart # 电子书模型 + Hive注解
|
||||
│ │ ├── book.g.dart # Book TypeAdapter (自动生成)
|
||||
│ │ ├── highlight.dart # 高亮+批注模型 + Hive注解
|
||||
│ │ ├── bookmark.dart # 书签模型 + Hive注解
|
||||
│ │ └── bookshelf.dart # 书架模型 + Hive注解
|
||||
│ └── services/ # 数据服务层
|
||||
│ ├── database_service.dart # Hive数据库管理
|
||||
│ └── book_repository.dart # 书籍数据访问层
|
||||
├── learning_docs/ # 学习文档目录
|
||||
│ ├── 01_项目结构与环境配置.md
|
||||
│ ├── 02_数据模型设计思路.md
|
||||
│ ├── 03_数据模型实践与技巧.md
|
||||
│ ├── 04_数据模型完成度检查.md
|
||||
│ ├── 05_数据模型设计阶段总结.md
|
||||
│ └── 06_Hive数据库数据持久化详解.md
|
||||
├── android/ # Android平台代码
|
||||
├── ios/ # iOS平台代码
|
||||
└── test/ # 测试代码
|
||||
```
|
||||
|
||||
## 📊 数据模型设计
|
||||
|
||||
### 核心实体关系
|
||||
```
|
||||
Bookshelf (书架) ──┬── Book (书籍)
|
||||
├── Highlight (高亮+批注)
|
||||
└── Bookmark (书签)
|
||||
```
|
||||
|
||||
### 已完成的数据模型
|
||||
|
||||
#### 1. Book(书籍模型)✅
|
||||
**功能:** 电子书基本信息管理
|
||||
- 基本信息:书名、作者、出版社、简介
|
||||
- 文件信息:路径、格式(EPUB/MOBI/TXT/PDF)、大小
|
||||
- 阅读状态:reading/completed/pending
|
||||
- 扩展信息:标签、评分、封面图片
|
||||
|
||||
#### 2. Highlight(高亮模型)✅
|
||||
**功能:** 文本高亮 + 批注功能
|
||||
- 高亮功能:文本范围、5种颜色分类
|
||||
- 批注功能:4种类型分类(笔记/感想/摘要/问题)
|
||||
- 批注操作:添加、更新、移除、时间戳管理
|
||||
|
||||
#### 3. Bookmark(书签模型)✅
|
||||
**功能:** 阅读位置管理
|
||||
- 位置记录:页码、百分比位置(0.0-1.0)
|
||||
- 书签信息:标题、描述、预览文本
|
||||
- 智能显示:位置格式化、有效性验证
|
||||
|
||||
#### 4. Bookshelf(书架模型)✅
|
||||
**功能:** 书架分类管理
|
||||
- 书架类型:系统书架 vs 自定义书架
|
||||
- 书架管理:创建、更新、书籍数量管理
|
||||
- 系统预设:6个默认系统书架
|
||||
|
||||
### 枚举类型统计
|
||||
- **BookFormat**: 4种电子书格式
|
||||
- **ReadingStatus**: 3种阅读状态
|
||||
- **HighlightColor**: 5种高亮颜色
|
||||
- **AnnotationType**: 4种批注类型
|
||||
- **BookshelfType**: 2种书架类型
|
||||
|
||||
## 💾 数据持久化实现
|
||||
|
||||
### Hive数据库集成 ✅
|
||||
**技术栈:** Hive + TypeAdapter + Repository模式
|
||||
**架构层次:** UI层 → Repository层 → DatabaseService层 → Hive数据库
|
||||
|
||||
#### 核心组件
|
||||
1. **DatabaseService** - 单例模式,数据库初始化和管理
|
||||
2. **BookRepository** - 数据访问抽象层,CRUD操作实现
|
||||
3. **TypeAdapter** - 自动生成,对象序列化支持
|
||||
|
||||
#### 实现功能
|
||||
- ✅ **数据库初始化** - Hive注册、Box打开
|
||||
- ✅ **CRUD操作** - 增删改查完整实现
|
||||
- ✅ **错误处理** - 完善的异常捕获机制
|
||||
- ✅ **测试验证** - 功能测试和数据持久化验证
|
||||
|
||||
### 代码生成 ✅
|
||||
- **build_runner** - TypeAdapter自动生成
|
||||
- **hive_generator** - Hive序列化代码生成
|
||||
- **生成文件** - book.g.dart(Book类型适配器)
|
||||
|
||||
## 🚀 开发进度
|
||||
|
||||
### ✅ 已完成阶段(100%)
|
||||
1. **环境搭建** - Flutter SDK + IDE配置
|
||||
2. **项目创建** - Flutter项目初始化
|
||||
3. **数据模型设计** - 4个核心模型 + 7个枚举类型 + Hive注解
|
||||
4. **模型验证** - 完整的序列化、工厂方法、对象比较
|
||||
5. **数据持久化** - Hive数据库集成 + Repository模式 + 测试验证
|
||||
|
||||
### 📋 待完成阶段
|
||||
1. **用户界面开发**
|
||||
- 书籍列表页面设计
|
||||
- 书架管理界面
|
||||
- 搜索和筛选功能
|
||||
|
||||
2. **核心功能开发**
|
||||
- 文件导入功能
|
||||
- 阅读器核心界面
|
||||
- 书签和高亮界面
|
||||
|
||||
3. **高级功能**
|
||||
- 文本解析(EPUB/MOBI)
|
||||
- 阅读器交互(翻页、字体调整)
|
||||
- 数据同步和备份
|
||||
|
||||
4. **优化与发布**
|
||||
- 性能优化
|
||||
- UI/UX改进
|
||||
- 应用打包发布
|
||||
|
||||
## 📚 学习成果
|
||||
|
||||
### 已掌握的Flutter技能
|
||||
- ✅ **空值安全语法** - `?`、`!`、`required`、`??`
|
||||
- ✅ **不可变对象设计** - `final`字段、`copyWith`模式
|
||||
- ✅ **枚举类型使用** - 类型安全的选项管理
|
||||
- ✅ **序列化模式** - `toMap()`/`fromMap()`实现
|
||||
- ✅ **工厂构造函数** - 对象创建的最佳实践
|
||||
- ✅ **计算属性** - `get`方法的灵活应用
|
||||
- ✅ **对象比较** - `operator ==`和`hashCode`
|
||||
- ✅ **字符串处理** - 插值、正则表达式、格式化
|
||||
- ✅ **异步编程** - `async/await`、Future处理
|
||||
- ✅ **数据库操作** - Hive CRUD、TypeAdapter
|
||||
|
||||
### 设计模式实践
|
||||
- **不可变对象模式** - 确保数据安全性
|
||||
- **工厂模式** - 简化对象创建逻辑
|
||||
- **建造者模式** - 复杂对象构建(copyWith)
|
||||
- **策略模式** - 不同类型的枚举处理
|
||||
- **单例模式** - 数据库服务管理
|
||||
- **Repository模式** - 数据访问抽象层
|
||||
|
||||
## 🛠️ 开发工具链
|
||||
|
||||
### 已集成依赖
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.2 # iOS 风格图标库
|
||||
# 数据库和存储相关依赖
|
||||
hive: ^2.2.3 # Hive轻量级NoSQL数据库
|
||||
hive_flutter: ^1.1.0 # Hive与Flutter的集成
|
||||
path_provider: ^2.0.14 # 获取文件存储路径
|
||||
file_picker: ^5.2.1 # 文件选择器,用于导入电子书
|
||||
# 状态管理(后续使用)
|
||||
provider: ^6.0.5 # Provider状态管理
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0 # 代码规范检查
|
||||
# Hive代码生成相关依赖
|
||||
hive_generator: ^2.0.0 # Hive TypeAdapter代码生成器
|
||||
build_runner: ^2.4.6 # Dart代码生成工具
|
||||
```
|
||||
|
||||
## 📋 开发规范
|
||||
|
||||
### 代码风格
|
||||
- 使用 `flutter_lints` 代码规范
|
||||
- 遵循Dart官方命名约定
|
||||
- 完整的文档注释(dartdoc)
|
||||
|
||||
### 提交规范
|
||||
- 使用语义化提交信息
|
||||
- 功能开发:`feat: 新功能描述`
|
||||
- 问题修复:`fix: 问题描述`
|
||||
- 文档更新:`docs: 文档内容`
|
||||
|
||||
### 分支策略
|
||||
- `main` - 主开发分支
|
||||
- `feature/*` - 功能开发分支
|
||||
- `hotfix/*` - 紧急修复分支
|
||||
|
||||
## 🤖 AI助手使用记录
|
||||
|
||||
本项目使用 Claude Code 作为开发助手,主要用于:
|
||||
- 📚 **技术指导** - Flutter最佳实践和设计模式
|
||||
- 🛠️ **代码审查** - 代码质量检查和优化建议
|
||||
- 📝 **文档生成** - 自动生成学习文档和注释
|
||||
- 🎯 **任务规划** - 开发进度跟踪和任务分解
|
||||
- 🔍 **问题排查** - 语法错误和逻辑问题诊断
|
||||
|
||||
## 📊 项目成果统计
|
||||
|
||||
### 代码产出
|
||||
- **数据模型文件:** 4个核心模型(Book、Highlight、Bookmark、Bookshelf)
|
||||
- **数据库服务:** 2个服务类(DatabaseService、BookRepository)
|
||||
- **代码生成:** 1个TypeAdapter文件(book.g.dart)
|
||||
- **代码总行数:** 约1500行高质量代码
|
||||
- **枚举类型:** 7个类型安全枚举
|
||||
- **字段总数:** 45个数据字段
|
||||
- **方法总数:** 100+个方法
|
||||
|
||||
### 文档产出
|
||||
- **学习文档:** 6篇详细教程文档
|
||||
- **文档总字数:** 15000+字
|
||||
- **代码示例:** 100+个
|
||||
- **知识点覆盖:** 完整覆盖Flutter数据建模和数据持久化
|
||||
- **问题解决方案:** 20+个常见问题和最佳实践
|
||||
|
||||
### 测试验证
|
||||
- **数据库初始化测试** ✅
|
||||
- **CRUD操作测试** ✅
|
||||
- **数据持久化验证** ✅
|
||||
- **TypeAdapter序列化测试** ✅
|
||||
- **错误处理测试** ✅
|
||||
|
||||
---
|
||||
|
||||
**项目状态:** 数据模型设计 + 数据持久化完成,准备进入用户界面开发阶段
|
||||
**下一里程碑:** 实现书籍列表页面和用户界面交互
|
||||
**当前技术债务:** 无,所有数据层功能已完成并通过测试
|
||||
@ -1,171 +0,0 @@
|
||||
# Flutter 电子书阅读器数据模型完成度检查
|
||||
|
||||
## 📊 **当前已完成的模型**
|
||||
|
||||
### ✅ **Book(书籍模型)** - 完成度:100%
|
||||
**文件位置:** `lib/models/book.dart`
|
||||
|
||||
**功能覆盖:**
|
||||
- ✅ 基本书籍信息(书名、作者、出版社等)
|
||||
- ✅ 文件信息(路径、格式、大小)
|
||||
- ✅ 阅读状态管理(阅读中、已完成、待阅读)
|
||||
- ✅ 扩展信息(标签、评分、简介)
|
||||
- ✅ 完整的序列化支持(toMap/fromMap)
|
||||
- ✅ 工厂构造函数(Book.create)
|
||||
- ✅ 对象比较方法(equals/hashCode)
|
||||
|
||||
**枚举类型:**
|
||||
- `BookFormat`:epub, mobi, txt, pdf
|
||||
- `ReadingStatus`:reading, completed, pending
|
||||
|
||||
---
|
||||
|
||||
### ✅ **Highlight(高亮模型 + 批注功能)** - 完成度:100%
|
||||
**文件位置:** `lib/models/highlight.dart`
|
||||
|
||||
**功能覆盖:**
|
||||
- ✅ 文本高亮(选中范围、颜色分类)
|
||||
- ✅ 集成批注功能(类型分类、时间戳)
|
||||
- ✅ 批注操作方法(添加、更新、移除)
|
||||
- ✅ 完整的序列化支持(处理可选枚举字段)
|
||||
- ✅ 工厂构造函数(Highlight.create)
|
||||
- ✅ 便利的批注管理方法
|
||||
|
||||
**枚举类型:**
|
||||
- `HighlightColor`:yellow, orange, green, blue, pink
|
||||
- `AnnotationType`:note, thought, summary, question
|
||||
|
||||
**设计亮点:**
|
||||
- 将批注功能集成到高亮模型中,符合实际使用场景
|
||||
- 提供完整的批注生命周期管理
|
||||
|
||||
---
|
||||
|
||||
### ✅ **Bookmark(书签模型)** - 完成度:100%
|
||||
**文件位置:** `lib/models/bookmark.dart`
|
||||
|
||||
**功能覆盖:**
|
||||
- ✅ 阅读位置标记(页码、百分比位置)
|
||||
- ✅ 书签信息管理(标题、描述、预览文本)
|
||||
- ✅ 位置格式化显示(百分比)
|
||||
- ✅ 位置有效性验证
|
||||
- ✅ 完整的序列化支持
|
||||
- ✅ 工厂构造函数(Bookmark.create)
|
||||
- ✅ 便利的更新方法(updatePosition、updateInfo)
|
||||
|
||||
**计算属性:**
|
||||
- `positionDisplay`:位置百分比显示
|
||||
- `isValidPosition`:位置有效性检查
|
||||
- `hasDescription`、`hasPreview`:内容检查
|
||||
- `shortDescription`:智能文本截取
|
||||
|
||||
---
|
||||
|
||||
### ✅ **Bookshelf(书架模型)** - 完成度:100%
|
||||
**文件位置:** `lib/models/bookshelf.dart`
|
||||
|
||||
**功能覆盖:**
|
||||
- ✅ 书架信息管理(名称、描述、封面)
|
||||
- ✅ 系统书架vs自定义书架分类
|
||||
- ✅ 可预测的系统书架ID生成
|
||||
- ✅ 书籍数量管理(增加、减少、更新)
|
||||
- ✅ 默认系统书架预设
|
||||
- ✅ 完整的序列化支持
|
||||
- ✅ 工厂构造函数(create/createSystem)
|
||||
|
||||
**枚举类型:**
|
||||
- `BookshelfType`:system, custom
|
||||
|
||||
**系统书架预设:**
|
||||
- 全部书籍、最近阅读、收藏
|
||||
- 阅读中、已完成、待阅读
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **需求回顾 vs 实现情况**
|
||||
|
||||
### **用户原始需求:**
|
||||
1. ✅ **支持导入epub、mobi、txt文件** → Book模型已覆盖
|
||||
2. ✅ **划线(不同颜色)** → Highlight模型已覆盖
|
||||
3. ✅ **批注** → 已集成到Highlight模型
|
||||
4. ✅ **书签** → Bookmark模型已覆盖
|
||||
5. ✅ **书架支持新建新加** → Bookshelf模型已覆盖
|
||||
6. ✅ **不同书籍放在不同书架** → Bookshelf模型支持
|
||||
7. ✅ **三种读书进度** → Book.status字段支持
|
||||
|
||||
---
|
||||
|
||||
## 🔍 **潜在缺失的模型分析**
|
||||
|
||||
### **1. ReadingProgress(阅读进度模型)**
|
||||
**当前状态:** 未实现,但功能已在Book模型中部分覆盖
|
||||
|
||||
**分析:**
|
||||
- Book模型已有`status`字段(reading/completed/pending)
|
||||
- 但缺少详细的进度信息(当前页数、阅读时间、会话记录)
|
||||
|
||||
**是否需要:**
|
||||
- ❌ **当前阶段不需要** - 可以后续作为功能扩展
|
||||
- Book模型的`status`字段已经满足基本需求
|
||||
|
||||
### **2. BookshelfBook(书架书籍关联模型)**
|
||||
**当前状态:** 未实现
|
||||
|
||||
**分析:**
|
||||
- 用于实现书籍与书架的多对多关系
|
||||
- 当前设计采用简化方案:书籍通过bookCount字段与书架关联
|
||||
|
||||
**是否需要:**
|
||||
- ❌ **当前阶段不需要** - 简化方案足够使用
|
||||
- 可以作为后续功能扩展
|
||||
|
||||
### **3. ReadingSession(阅读会话模型)**
|
||||
**当前状态:** 未实现
|
||||
|
||||
**分析:**
|
||||
- 记录每次阅读会话的详细信息
|
||||
- 属于高级功能(阅读统计、习惯分析)
|
||||
|
||||
**是否需要:**
|
||||
- ❌ **当前阶段不需要** - 属于扩展功能
|
||||
|
||||
---
|
||||
|
||||
## 📋 **最终确认**
|
||||
|
||||
### **✅ 已完成的核心模型:**
|
||||
1. **Book** - 电子书基本信息管理
|
||||
2. **Highlight** - 文本高亮 + 批注功能
|
||||
3. **Bookmark** - 阅读位置书签
|
||||
4. **Bookshelf** - 书架分类管理
|
||||
|
||||
### **✅ 已实现的核心功能:**
|
||||
- 电子书导入和基本信息管理
|
||||
- 文本高亮(5种颜色分类)
|
||||
- 批注功能(4种类型分类)
|
||||
- 书签管理(位置记录)
|
||||
- 书架管理(系统+自定义)
|
||||
- 阅读状态跟踪
|
||||
|
||||
### **🎯 数据模型完成度:100%**
|
||||
|
||||
**结论:** 所有核心数据模型已经完成,完全满足电子书阅读器的基本功能需求。无需额外的模型即可开始UI开发阶段。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **下一步建议**
|
||||
|
||||
### **技术栈准备:**
|
||||
1. **本地存储方案** - Hive数据库集成
|
||||
2. **状态管理** - Provider/Riverpod
|
||||
3. **文件处理** - EPUB/MOBI解析库
|
||||
4. **UI框架** - Material Design组件
|
||||
|
||||
### **开发顺序建议:**
|
||||
1. 集成Hive数据库,实现数据持久化
|
||||
2. 创建Repository层,封装数据访问
|
||||
3. 开发书架列表页面
|
||||
4. 实现文件导入功能
|
||||
5. 开发阅读器核心页面
|
||||
|
||||
所有数据模型已经就绪,可以开始下一阶段的开发!
|
||||
@ -1,331 +0,0 @@
|
||||
# Flutter 电子书阅读器开发 - 数据模型设计阶段总结
|
||||
|
||||
## 🎯 阶段概述
|
||||
**阶段名称:** 数据模型设计与实现
|
||||
**开始时间:** 项目初始化
|
||||
**完成时间:** 当前
|
||||
**阶段状态:** ✅ 100% 完成
|
||||
|
||||
## 📊 完成成果统计
|
||||
|
||||
### 代码产出
|
||||
- **模型文件数量:** 4个核心模型文件
|
||||
- **代码总行数:** 约1200行
|
||||
- **枚举类型:** 7个
|
||||
- **字段总数:** 45个数据字段
|
||||
- **方法总数:** 80+个方法
|
||||
|
||||
### 文档产出
|
||||
- **学习文档:** 5篇详细教程
|
||||
- **文档总字数:** 8000+字
|
||||
- **代码示例:** 50+个
|
||||
- **知识点覆盖:** 100%覆盖Flutter数据模型设计
|
||||
|
||||
## 🏆 核心成就
|
||||
|
||||
### ✅ 技术成就
|
||||
1. **掌握Flutter数据模型设计模式**
|
||||
- 不可变对象设计原则
|
||||
- 空值安全语法的熟练应用
|
||||
- 枚举类型的正确使用和序列化
|
||||
|
||||
2. **实现完整的对象生命周期管理**
|
||||
- 构造函数、工厂构造函数
|
||||
- copyWith模式(不可变性)
|
||||
- 序列化/反序列化(toMap/fromMap)
|
||||
|
||||
3. **建立代码质量标准**
|
||||
- 完整的对象比较(equals/hashCode)
|
||||
- 调试友好的toString方法
|
||||
- 丰富的计算属性和便利方法
|
||||
|
||||
### ✅ 业务成就
|
||||
1. **完整的功能覆盖**
|
||||
- 电子书信息管理(Book)
|
||||
- 文本高亮+批注(Highlight)
|
||||
- 阅读书签(Bookmark)
|
||||
- 书架分类(Bookshelf)
|
||||
|
||||
2. **合理的架构设计**
|
||||
- 模型间关系清晰
|
||||
- 数据冗余最小化
|
||||
- 扩展性良好
|
||||
|
||||
3. **用户体验考虑**
|
||||
- 智能的显示文本(positionDisplay、bookCountDisplay)
|
||||
- 便利的操作方法(incrementBookCount、addAnnotation)
|
||||
- 合理的默认值和预设
|
||||
|
||||
## 📚 学习成果详解
|
||||
|
||||
### 1. Flutter核心概念掌握
|
||||
|
||||
#### 不可变对象设计模式
|
||||
```dart
|
||||
class Book {
|
||||
final String title; // 所有字段都是final
|
||||
|
||||
Book copyWith({String? title}) { // 通过copyWith创建新对象
|
||||
return Book(title: title ?? this.title);
|
||||
}
|
||||
}
|
||||
```
|
||||
**学习要点:**
|
||||
- 为什么要使用不可变对象(线程安全、状态管理友好)
|
||||
- copyWith模式的优势(性能、可维护性)
|
||||
- 实际应用场景(状态更新、对象复制)
|
||||
|
||||
#### 空值安全语法
|
||||
```dart
|
||||
// 正确使用各种空值安全操作符
|
||||
final String? author; // 可空类型
|
||||
final List<String> tags = const []; // 避免null的默认值
|
||||
return title ?? defaultTitle; // ?? 操作符
|
||||
return book?.author ?? "未知"; // ?. 操作符
|
||||
```
|
||||
**学习要点:**
|
||||
- `?`、`!`、`??` 操作符的区别和使用场景
|
||||
- required关键字的正确应用
|
||||
- 避免空指针异常的最佳实践
|
||||
|
||||
#### 枚举类型的高级应用
|
||||
```dart
|
||||
enum BookFormat { epub, mobi, txt, pdf }
|
||||
|
||||
// 存储时:枚举 → 字符串
|
||||
'format': format.name,
|
||||
|
||||
// 读取时:字符串 → 枚举
|
||||
format: BookFormat.values.firstWhere((e) => e.name == map['format'])
|
||||
```
|
||||
**学习要点:**
|
||||
- 枚举序列化的正确方法
|
||||
- 类型安全的枚举值处理
|
||||
- 枚举在实际业务中的应用
|
||||
|
||||
### 2. 设计模式实践
|
||||
|
||||
#### 工厂构造函数模式
|
||||
```dart
|
||||
// 普通工厂方法
|
||||
factory Book.create({...}) { ... }
|
||||
|
||||
// 特殊用途工厂方法
|
||||
factory Bookshelf.createSystem({...}) { ... }
|
||||
```
|
||||
**学习要点:**
|
||||
- 工厂构造函数vs普通构造函数的区别
|
||||
- 如何封装复杂的创建逻辑
|
||||
- 预设值的自动处理
|
||||
|
||||
#### 计算属性(Getter)模式
|
||||
```dart
|
||||
// 格式化显示
|
||||
String get positionDisplay => '${(position * 100).toInt()}%';
|
||||
|
||||
// 条件检查
|
||||
bool get hasAnnotation => annotation != null && annotation!.isNotEmpty;
|
||||
|
||||
// 智能文本处理
|
||||
String get shortDescription {
|
||||
return description!.length > 50
|
||||
? '${description!.substring(0, 50)}...'
|
||||
: description!;
|
||||
}
|
||||
```
|
||||
**学习要点:**
|
||||
- getter vs方法的适用场景
|
||||
- 如何编写高效的计算属性
|
||||
- 复杂业务逻辑的封装
|
||||
|
||||
### 3. 代码质量提升
|
||||
|
||||
#### 完整的对象管理
|
||||
```dart
|
||||
// 对象比较
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Book && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
// 调试输出
|
||||
@override
|
||||
String toString() {
|
||||
return 'Book(id: $id, title: $title, format: $format)';
|
||||
}
|
||||
```
|
||||
|
||||
#### 便利方法设计
|
||||
```dart
|
||||
// 业务逻辑封装
|
||||
Highlight addAnnotation(String content, AnnotationType type) {
|
||||
return copyWith(
|
||||
annotation: content,
|
||||
annotationType: type,
|
||||
annotationTime: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
// 数据更新方法
|
||||
Bookshelf incrementBookCount({int increment = 1}) {
|
||||
return copyWith(
|
||||
bookCount: bookCount + increment,
|
||||
lastModifiedTime: DateTime.now(),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🎓 技能提升评估
|
||||
|
||||
### 入门前 vs 入门后
|
||||
|
||||
#### 代码能力对比
|
||||
**之前:** 只会基本的类定义和方法编写
|
||||
```dart
|
||||
class Book {
|
||||
String title;
|
||||
String author;
|
||||
}
|
||||
```
|
||||
|
||||
**现在:** 能设计企业级的数据模型
|
||||
```dart
|
||||
class Book {
|
||||
final String id;
|
||||
final String title;
|
||||
final String? author;
|
||||
|
||||
const Book({required this.id, required this.title, this.author});
|
||||
|
||||
Book copyWith({String? title, String? author}) { ... }
|
||||
Map<String, dynamic> toMap() { ... }
|
||||
factory Book.fromMap(Map<String, dynamic> map) { ... }
|
||||
factory Book.create({required String title, ...}) { ... }
|
||||
// ... 完整的对象生命周期管理
|
||||
}
|
||||
```
|
||||
|
||||
#### 设计思维对比
|
||||
**之前:** 考虑基本功能实现
|
||||
**现在:** 考虑
|
||||
- 数据一致性和安全性
|
||||
- 代码的可维护性和扩展性
|
||||
- 用户体验和易用性
|
||||
- 性能优化和最佳实践
|
||||
|
||||
### 掌握的Flutter核心技能
|
||||
|
||||
#### 基础语法(100%)
|
||||
- ✅ 变量和类型系统
|
||||
- ✅ 函数和方法定义
|
||||
- ✅ 类和对象概念
|
||||
- ✅ 继承和多态
|
||||
|
||||
#### 进阶特性(100%)
|
||||
- ✅ 空值安全(Null Safety)
|
||||
- ✅ 枚举类型使用
|
||||
- ✅ 扩展方法(Extension)
|
||||
- ✅ 泛型(Generics)
|
||||
|
||||
#### 设计模式(90%)
|
||||
- ✅ 工厂模式(Factory Pattern)
|
||||
- ✅ 建造者模式(Builder Pattern - copyWith)
|
||||
- ⏳ 单例模式(Singleton - 待应用层实现)
|
||||
- ⏳ 观察者模式(Observer - 待状态管理实现)
|
||||
|
||||
#### 数据管理(100%)
|
||||
- ✅ 序列化/反序列化
|
||||
- ✅ 对象关系设计
|
||||
- ✅ 数据验证
|
||||
- ✅ 数据库持久化(Hive集成完成)
|
||||
|
||||
## 🚀 下一步学习计划
|
||||
|
||||
### ✅ 已完成(本阶段)
|
||||
1. **Hive数据库集成** ✅
|
||||
- 依赖配置和初始化
|
||||
- TypeAdapter代码生成
|
||||
- Repository模式实现
|
||||
|
||||
2. **数据持久化完成** ✅
|
||||
- 完整的CRUD操作实现
|
||||
- 错误处理和测试验证
|
||||
- 生产级的数据访问层
|
||||
|
||||
### 立即可开始(下一阶段)
|
||||
1. **用户界面开发**
|
||||
- 书籍列表页面实现
|
||||
- 书架管理界面
|
||||
- 搜索和筛选功能
|
||||
|
||||
2. **状态管理应用**
|
||||
- Provider状态管理集成
|
||||
- UI与数据层的响应式绑定
|
||||
- 用户体验优化
|
||||
|
||||
### 中期目标(1-2周内)
|
||||
1. **UI组件开发**
|
||||
- Material Design组件
|
||||
- 自定义Widget设计
|
||||
- 响应式布局
|
||||
|
||||
2. **文件处理**
|
||||
- 文件选择和导入
|
||||
- EPUB/MOBI解析
|
||||
- 文本处理和渲染
|
||||
|
||||
### 长期目标(1个月内)
|
||||
1. **完整应用开发**
|
||||
- 书架列表页面
|
||||
- 阅读器核心功能
|
||||
- 设置和配置页面
|
||||
|
||||
2. **性能优化和发布**
|
||||
- 内存管理
|
||||
- 启动优化
|
||||
- 应用打包发布
|
||||
|
||||
## 💡 学习建议
|
||||
|
||||
### 给初学者的建议
|
||||
1. **先掌握概念再写代码** - 理解为什么这样设计比直接写代码更重要
|
||||
2. **循序渐进** - 每个知识点都要实践,不要跳跃学习
|
||||
3. **多写注释** - 帮助理解,也便于后续回顾
|
||||
4. **参考优秀代码** - 学习官方和开源项目的设计思路
|
||||
|
||||
### 继续深入的方向
|
||||
1. **Flutter高级特性** - 动画、自定义绘制、平台通道
|
||||
2. **架构模式** - MVVM、Clean Architecture、DDD
|
||||
3. **性能优化** - 内存管理、渲染优化、网络优化
|
||||
4. **跨平台开发** - Web、Desktop平台适配
|
||||
|
||||
---
|
||||
|
||||
## 🎉 数据持久化阶段总结
|
||||
|
||||
**🏆 阶段成就:** 数据模型设计 + 数据持久化阶段100%完成!
|
||||
|
||||
**📊 最终成果:**
|
||||
- ✅ **4个完整数据模型** - Book、Highlight、Bookmark、Bookshelf
|
||||
- ✅ **7个类型安全枚举** - 状态、格式、颜色等管理
|
||||
- ✅ **完整的Hive数据库集成** - TypeAdapter + Repository模式
|
||||
- ✅ **生产级代码质量** - 错误处理、测试验证、文档完善
|
||||
- ✅ **丰富的学习资源** - 6篇详细教程文档
|
||||
|
||||
**🎯 核心收获:**
|
||||
1. **Flutter数据建模能力** - 从需求到模型的完整设计流程
|
||||
2. **数据持久化实现能力** - Hive数据库的全面掌握
|
||||
3. **企业级代码规范** - Repository模式、错误处理、测试验证
|
||||
4. **独立学习能力** - 文档编写、问题排查、最佳实践应用
|
||||
|
||||
**🚀 技术栈掌握:**
|
||||
- **Flutter核心语法** - 空值安全、不可变对象、泛型、枚举
|
||||
- **设计模式应用** - 工厂模式、Repository模式、单例模式
|
||||
- **数据库技术** - Hive CRUD操作、TypeAdapter、性能优化
|
||||
- **工程化实践** - 代码生成、依赖管理、测试验证
|
||||
|
||||
**阶段总结:** 数据模型设计和持久化阶段已100%完成,现在具备了独立开发Flutter应用数据层的完整能力。所有数据相关功能都已实现并通过测试验证,为用户界面开发奠定了坚实的技术基础。
|
||||
@ -1,973 +0,0 @@
|
||||
# Hive数据库数据持久化详解
|
||||
|
||||
## 📚 章节概述
|
||||
**学习目标:** 掌握Flutter中的数据持久化,理解Hive数据库的原理和使用方法
|
||||
**掌握程度:** 核心知识点 ⭐⭐⭐⭐⭐
|
||||
**完成状态:** ✅ 已完成
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心概念理解
|
||||
|
||||
### 1️⃣ 什么是数据持久化?
|
||||
|
||||
**数据持久化** = 数据保存 + 长期存储
|
||||
|
||||
**生活中的比喻:**
|
||||
- 📱 微信聊天记录 - 关闭APP后重新打开,聊天记录还在
|
||||
- 📞 手机通讯录 - 重启手机后联系人信息不丢失
|
||||
- 🎮 游戏进度 - 退出游戏下次继续,进度保存着
|
||||
- 📚 我们的电子书库 - 退出Readful应用后,书籍信息还在
|
||||
|
||||
### 2️⃣ 为什么需要数据持久化?
|
||||
|
||||
**问题:** 如果没有数据持久化
|
||||
- 用户添加的书籍 → 关闭应用就丢失了 😱
|
||||
- 阅读进度 → 重新打开要从头开始 😭
|
||||
- 书签和高亮 → 全部消失 😰
|
||||
|
||||
**解决方案:** 数据持久化
|
||||
- ✅ 数据永久保存(除非用户删除)
|
||||
- ✅ 应用重启后数据依然存在
|
||||
- ✅ 用户体验持续和连贯
|
||||
|
||||
### 3️⃣ 为什么选择Hive数据库?
|
||||
|
||||
**Hive = 轻量级 + 快速 + 简单**
|
||||
|
||||
**🚀 优势对比:**
|
||||
|
||||
| 特性 | Hive | SQLite | SharedPreferences |
|
||||
|------|------|--------|-------------------|
|
||||
| **速度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| **易用性** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
|
||||
| **对象支持** | ✅ 直接存储对象 | ❌ 需要SQL语句 | ❌ 只支持基本类型 |
|
||||
| **离线能力** | ✅ 完全离线 | ✅ 完全离线 | ✅ 完全离线 |
|
||||
| **类型安全** | ✅ 编译时检查 | ⚠️ 运行时错误 | ⚠️ 运行时错误 |
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 架构设计详解
|
||||
|
||||
### 架构层次图
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 用户界面 (UI) │
|
||||
│ (List, Detail等页面) │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────┐
|
||||
│ BookRepository │
|
||||
│ (数据访问层) │
|
||||
│ getAllBooks(), addBook()等 │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────┐
|
||||
│ DatabaseService │
|
||||
│ (数据库服务层) │
|
||||
│ 初始化、注册、打开Box等 │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────┐
|
||||
│ Hive数据库 │
|
||||
│ (物理存储层) │
|
||||
│ books.hive文件存储 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Hive基础使用方法
|
||||
|
||||
### 📦 Hive安装和配置
|
||||
|
||||
#### 1️⃣ 依赖配置
|
||||
```yaml
|
||||
# pubspec.yaml
|
||||
dependencies:
|
||||
hive: ^2.2.3 # Hive核心库
|
||||
hive_flutter: ^1.1.0 # Flutter集成
|
||||
path_provider: ^2.0.14 # 文件路径管理
|
||||
|
||||
dev_dependencies:
|
||||
hive_generator: ^2.0.0 # TypeAdapter生成器
|
||||
build_runner: ^2.4.6 # 代码生成工具
|
||||
```
|
||||
|
||||
#### 2️⃣ 安装依赖
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
### 🔧 Hive基础操作
|
||||
|
||||
#### 1️⃣ 数据库初始化
|
||||
```dart
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
Future<void> initHive() async {
|
||||
// 获取应用文档目录
|
||||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
// 初始化Hive
|
||||
await Hive.initFlutter(appDocumentDir.path);
|
||||
|
||||
print('✅ Hive初始化完成');
|
||||
}
|
||||
```
|
||||
|
||||
#### 2️⃣ 打开和关闭Box
|
||||
```dart
|
||||
// 打开Box(数据容器)
|
||||
Box<String> settingsBox = await Hive.openBox<String>('settings');
|
||||
Box<Book> booksBox = await Hive.openBox<Book>('books');
|
||||
|
||||
// 检查Box是否打开
|
||||
bool isOpen = Hive.isBoxOpen('books');
|
||||
|
||||
// 获取已打开的Box
|
||||
Box<String> myBox = Hive.box('settings');
|
||||
|
||||
// 关闭单个Box
|
||||
await settingsBox.close();
|
||||
|
||||
// 关闭所有Box
|
||||
await Hive.close();
|
||||
```
|
||||
|
||||
#### 3️⃣ 基本CRUD操作
|
||||
```dart
|
||||
// 📝 CREATE - 添加数据
|
||||
await settingsBox.put('theme', 'dark');
|
||||
await settingsBox.put('fontSize', 16.0);
|
||||
|
||||
// 📖 READ - 读取数据
|
||||
String? theme = settingsBox.get('theme'); // 'dark'
|
||||
double fontSize = settingsBox.get('fontSize', defaultValue: 14.0); // 16.0
|
||||
|
||||
// 获取所有键值对
|
||||
Map<String, dynamic> allSettings = settingsBox.toMap();
|
||||
|
||||
// 获取所有值
|
||||
List<String> allValues = settingsBox.values.toList();
|
||||
|
||||
// 获取所有键
|
||||
List<String> allKeys = settingsBox.keys.toList();
|
||||
|
||||
// ✏️ UPDATE - 更新数据
|
||||
await settingsBox.put('theme', 'light'); // 更新已存在的键
|
||||
|
||||
// 🗑️ DELETE - 删除数据
|
||||
await settingsBox.delete('theme');
|
||||
await settingsBox.deleteAll(['theme', 'fontSize']);
|
||||
|
||||
// 清空整个Box
|
||||
await settingsBox.clear();
|
||||
```
|
||||
|
||||
### 🎯 Hive高级操作
|
||||
|
||||
#### 1️⃣ 条件查询
|
||||
```dart
|
||||
// 查询所有key以'user_'开头的项目
|
||||
Iterable<String> userKeys = settingsBox.keys.where((key) => key.startsWith('user_'));
|
||||
|
||||
// 查询所有值大于10的项目
|
||||
Iterable<double> largeValues = settingsBox.values.whereType<double>()
|
||||
.where((value) => value > 10);
|
||||
|
||||
// 查找特定条件的键值对
|
||||
Map<String, dynamic> filteredData = {};
|
||||
for (var entry in settingsBox.toMap().entries) {
|
||||
if (entry.key.toString().contains('important')) {
|
||||
filteredData[entry.key] = entry.value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2️⃣ 数据监听
|
||||
```dart
|
||||
// 监听数据变化
|
||||
settingsBox.listenable().addListener(() {
|
||||
print('Settings box数据发生变化');
|
||||
// 在这里可以更新UI或执行其他操作
|
||||
});
|
||||
|
||||
// 监听特定键的变化
|
||||
settingsBox.watch(key: 'theme').listen((event) {
|
||||
print('主题设置改变: ${event.value}');
|
||||
// 可以在这里切换UI主题
|
||||
});
|
||||
```
|
||||
|
||||
#### 3️⃣ 批量操作
|
||||
```dart
|
||||
// 批量添加数据
|
||||
await settingsBox.putAll({
|
||||
'theme': 'dark',
|
||||
'fontSize': 16.0,
|
||||
'language': 'zh_CN',
|
||||
'autoSave': true,
|
||||
});
|
||||
|
||||
// 批量删除数据
|
||||
List<String> keysToDelete = ['temp1', 'temp2', 'temp3'];
|
||||
await settingsBox.deleteAll(keysToDelete);
|
||||
```
|
||||
|
||||
### 🔍 Hive数据类型支持
|
||||
|
||||
#### 1️⃣ 原生支持的数据类型
|
||||
```dart
|
||||
// ✅ 基本类型
|
||||
Box<String> stringBox = await Hive.openBox<String>('strings');
|
||||
Box<int> intBox = await Hive.openBox<int>('integers');
|
||||
Box<double> doubleBox = await Hive.openBox<double>('doubles');
|
||||
Box<bool> boolBox = await Hive.openBox<bool>('booleans');
|
||||
Box<DateTime> dateBox = await Hive.openBox<DateTime>('dates');
|
||||
Box<List<String>> listBox = await Hive.openBox<List<String>>('lists');
|
||||
Box<Map<String, dynamic>> mapBox = await Hive.openBox<Map<String, dynamic>>('maps');
|
||||
```
|
||||
|
||||
#### 2️⃣ 自定义类型(需要TypeAdapter)
|
||||
```dart
|
||||
// 自定义类
|
||||
@HiveType(typeId: 0)
|
||||
class Person {
|
||||
@HiveField(0) final String name;
|
||||
@HiveField(1) final int age;
|
||||
|
||||
const Person({required this.name, required this.age});
|
||||
}
|
||||
|
||||
// 注册TypeAdapter
|
||||
Hive.registerAdapter(PersonAdapter());
|
||||
|
||||
// 使用自定义类型
|
||||
Box<Person> personBox = await Hive.openBox<Person>('persons');
|
||||
|
||||
// 添加自定义对象
|
||||
await personBox.put('user1', Person(name: '张三', age: 25));
|
||||
|
||||
// 读取自定义对象
|
||||
Person? user = personBox.get('user1');
|
||||
```
|
||||
|
||||
### 📊 Hive性能优化
|
||||
|
||||
#### 1️⃣ 延迟初始化
|
||||
```dart
|
||||
class LazyBoxManager {
|
||||
Box<Book>? _booksBox;
|
||||
|
||||
Box<Book> get booksBox {
|
||||
_booksBox ??= Hive.box<Book>('books');
|
||||
return _booksBox!;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2️⃣ 事务操作
|
||||
```dart
|
||||
// 使用事务确保数据一致性
|
||||
await Hive.openBox<String>('settings');
|
||||
await settingsBox.putAll({
|
||||
'operation1': 'value1',
|
||||
'operation2': 'value2',
|
||||
});
|
||||
|
||||
// 如果出现错误,操作会自动回滚
|
||||
```
|
||||
|
||||
#### 3️⃣ 内存管理
|
||||
```dart
|
||||
// 及时关闭不再使用的Box
|
||||
await tempBox.close();
|
||||
|
||||
// 定期压缩数据库
|
||||
await Hive.compact();
|
||||
```
|
||||
|
||||
### 🚨 常见问题和解决方案
|
||||
|
||||
#### 1️⃣ "Cannot write, unknown type" 错误
|
||||
```dart
|
||||
// ❌ 错误:未注册TypeAdapter
|
||||
@HiveType(typeId: 0)
|
||||
class Book { ... }
|
||||
|
||||
await bookBox.put('book1', myBook); // 会报错
|
||||
|
||||
// ✅ 正确:注册TypeAdapter
|
||||
Hive.registerAdapter(BookAdapter());
|
||||
await bookBox.put('book1', myBook); // 正常工作
|
||||
```
|
||||
|
||||
#### 2️⃣ "Box is not open" 错误
|
||||
```dart
|
||||
// ❌ 错误:忘记打开Box
|
||||
Box<Book> booksBox = Hive.box<Book>('books'); // 如果Box未打开会报错
|
||||
|
||||
// ✅ 正确:先打开Box
|
||||
Box<Book> booksBox = await Hive.openBox<Book>('books');
|
||||
|
||||
// 或者使用惰性Box(会自动打开)
|
||||
LazyBox<Book> lazyBox = Hive.lazyBox<Book>('books');
|
||||
```
|
||||
|
||||
#### 3️⃣ 数据迁移
|
||||
```dart
|
||||
// 检查数据库版本
|
||||
const int currentVersion = 2;
|
||||
const int savedVersion = settingsBox.get('db_version', defaultValue: 0);
|
||||
|
||||
if (savedVersion < currentVersion) {
|
||||
// 执行数据迁移
|
||||
await migrateDatabase(savedVersion, currentVersion);
|
||||
settingsBox.put('db_version', currentVersion);
|
||||
}
|
||||
```
|
||||
|
||||
### 🔧 Hive开发工具
|
||||
|
||||
#### 1️⃣ 调试技巧
|
||||
```dart
|
||||
// 查看Box内容
|
||||
print('Box内容: ${settingsBox.toMap()}');
|
||||
|
||||
// 查看Box统计信息
|
||||
print('Box长度: ${settingsBox.length}');
|
||||
print('是否为空: ${settingsBox.isEmpty}');
|
||||
print('所有键: ${settingsBox.keys}');
|
||||
```
|
||||
|
||||
#### 2️⃣ 数据导出和导入
|
||||
```dart
|
||||
// 导出数据
|
||||
Map<String, dynamic> exportData = settingsBox.toMap();
|
||||
String jsonString = jsonEncode(exportData);
|
||||
|
||||
// 导入数据
|
||||
Map<String, dynamic> importData = jsonDecode(jsonString);
|
||||
await settingsBox.putAll(importData);
|
||||
```
|
||||
|
||||
### 🎯 最佳实践
|
||||
|
||||
#### 1️⃣ 数据库结构设计
|
||||
```dart
|
||||
// 使用有意义的Box名称
|
||||
Box<Book> booksBox = await Hive.openBox<Book>('books');
|
||||
Box<Bookmark> bookmarksBox = await Hive.openBox<Bookmark>('bookmarks');
|
||||
Box<String> settingsBox = await Hive.openBox<String>('app_settings');
|
||||
|
||||
// 使用一致的键命名规范
|
||||
await settingsBox.put('user_theme', 'dark');
|
||||
await settingsBox.put('user_font_size', 16);
|
||||
await settingsBox.put('user_language', 'zh_CN');
|
||||
```
|
||||
|
||||
#### 2️⃣ 错误处理
|
||||
```dart
|
||||
try {
|
||||
await bookBox.put('book1', myBook);
|
||||
} on HiveError catch (e) {
|
||||
print('Hive错误: $e');
|
||||
} catch (e) {
|
||||
print('未知错误: $e');
|
||||
}
|
||||
```
|
||||
|
||||
#### 3️⃣ 资源管理
|
||||
```dart
|
||||
class DatabaseService {
|
||||
static DatabaseService? _instance;
|
||||
static DatabaseService get instance => _instance ??= DatabaseService._();
|
||||
|
||||
DatabaseService._();
|
||||
|
||||
Future<void> init() async {
|
||||
await Hive.initFlutter();
|
||||
// 注册TypeAdapter
|
||||
// 打开Box
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
await Hive.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 核心组件详解
|
||||
|
||||
### 1️⃣ DatabaseService (数据库服务层)
|
||||
|
||||
**📖 文件位置:** `lib/services/database_service.dart`
|
||||
|
||||
#### 单例模式 (Singleton Pattern)
|
||||
|
||||
```dart
|
||||
class DatabaseService {
|
||||
static DatabaseService? _instance; // 🏠 私有实例变量
|
||||
static DatabaseService get instance => _instance ??= DatabaseService._(); // 🔑 公共访问方法
|
||||
|
||||
DatabaseService._(); // 🔒 私有构造函数
|
||||
}
|
||||
```
|
||||
|
||||
**🎯 为什么用单例?**
|
||||
- **比喻:** 一个城市只有一个图书馆管理系统
|
||||
- **原因:** 避免多个数据库连接导致的数据冲突
|
||||
- **好处:**
|
||||
- 内存中只有一个实例
|
||||
- 全局访问点
|
||||
- 数据一致性
|
||||
|
||||
**💡 语法解释:**
|
||||
- `static DatabaseService? _instance` - 类级别的私有变量
|
||||
- `??=` - 空赋值操作符 (如果为null就赋新值)
|
||||
- `DatabaseService._()` - 私有命名构造函数
|
||||
|
||||
#### Box概念
|
||||
|
||||
```dart
|
||||
late Box<Book> _booksBox; // 📦 书籍存储盒子
|
||||
```
|
||||
|
||||
**📦 Box是什么?**
|
||||
- Box是Hive的存储容器
|
||||
- 就像贴着标签的储物箱
|
||||
- 每个Box只能存储一种类型的数据
|
||||
- `_booksBox`专门存储Book对象
|
||||
|
||||
#### 数据库初始化过程
|
||||
|
||||
```dart
|
||||
Future<void> init() async {
|
||||
try {
|
||||
// 第1步:获取应用存储位置
|
||||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
// 第2步:初始化Hive
|
||||
await Hive.initFlutter(appDocumentDir.path);
|
||||
|
||||
// 第3步:注册TypeAdapter
|
||||
Hive.registerAdapter(BookFormatAdapter());
|
||||
Hive.registerAdapter(ReadingStatusAdapter());
|
||||
Hive.registerAdapter(BookAdapter());
|
||||
|
||||
// 第4步:打开Box
|
||||
_booksBox = await Hive.openBox<Book>('books');
|
||||
|
||||
} catch (e) {
|
||||
// 错误处理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**🚶♂️ 详细步骤解释:**
|
||||
|
||||
1. **获取存储位置:**
|
||||
- Android: `/data/data/com.yourapp/files/`
|
||||
- iOS: `应用沙盒/Documents/`
|
||||
- 这是系统为应用分配的专属存储空间
|
||||
|
||||
2. **初始化Hive:**
|
||||
- 告诉Hive在哪个路径下创建数据库文件
|
||||
- 只需要执行一次
|
||||
|
||||
3. **注册TypeAdapter:**
|
||||
- 注册Book类型的"翻译官"
|
||||
- 让Hive能理解我们的自定义对象
|
||||
|
||||
4. **打开Box:**
|
||||
- 创建或打开名为'books'的存储容器
|
||||
- 准备存储Book对象
|
||||
|
||||
### 2️⃣ BookRepository (数据访问层)
|
||||
|
||||
**📖 文件位置:** `lib/services/book_repository.dart`
|
||||
|
||||
#### Repository模式
|
||||
|
||||
**🏭 工厂比喻:**
|
||||
- **DatabaseService** = 工厂大门(唯一入口)
|
||||
- **BookRepository** = 书籍车间(专门处理书籍)
|
||||
- **Box** = 货架(存储数据的地方)
|
||||
- **Book对象** = 书籍产品
|
||||
|
||||
#### CRUD操作详解
|
||||
|
||||
**📖 C - Create (创建/添加)**
|
||||
```dart
|
||||
Future<void> addBook(Book book) async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox(); // 📦 获取存储盒子
|
||||
await booksBox.put(book.id, book); // 📝 存储书籍 (ID作为key)
|
||||
print('✅ 书籍添加成功: ${book.title}');
|
||||
} catch (e) {
|
||||
print('❌ 添加书籍失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**💡 关键概念:**
|
||||
- `book.id` - 唯一标识符,作为存储的key
|
||||
- `book` - 整个Book对象作为value
|
||||
- 如果key已存在,会覆盖更新(保证数据唯一性)
|
||||
|
||||
**📖 R - Read (读取/查询)**
|
||||
```dart
|
||||
// 读取所有书籍
|
||||
Future<List<Book>> getAllBooks() async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
return booksBox.values.toList(); // 📋 获取所有值并转换为列表
|
||||
} catch (e) {
|
||||
// 错误处理
|
||||
}
|
||||
}
|
||||
|
||||
// 根据ID读取特定书籍
|
||||
Future<Book?> getBookById(String id) async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
return booksBox.get(id); // 🔍 根据key查找value
|
||||
} catch (e) {
|
||||
// 错误处理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**💡 语法解释:**
|
||||
- `.values` - 获取Box中所有值
|
||||
- `.get(id)` - 根据key获取特定值
|
||||
- `Book?` - 可空类型(可能找不到)
|
||||
|
||||
**📖 U - Update (更新)**
|
||||
```dart
|
||||
Future<void> updateBook(Book book) async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
await booksBox.put(book.id, book); // 🔄 覆盖存储同一ID的书籍
|
||||
} catch (e) {
|
||||
// 错误处理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**💡 更新原理:**
|
||||
- 使用相同的`put(key, value)`方法
|
||||
- 由于key相同,会覆盖原有数据
|
||||
- 达到更新的效果
|
||||
|
||||
**📖 D - Delete (删除)**
|
||||
```dart
|
||||
Future<void> deleteBook(String id) async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
await booksBox.delete(id); // 🗑️ 根据ID删除书籍
|
||||
} catch (e) {
|
||||
// 错误处理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔢 TypeAdapter详解
|
||||
|
||||
### 什么是TypeAdapter?
|
||||
|
||||
**TypeAdapter = 类型转换器 = 翻译官**
|
||||
|
||||
**🤔 为什么需要TypeAdapter?**
|
||||
- Hive只能存储基本数据类型(String、int、double、bool等)
|
||||
- Hive不知道如何存储自定义对象(如Book)
|
||||
- TypeAdapter负责"翻译"工作
|
||||
|
||||
**🔄 工作流程:**
|
||||
```
|
||||
存储时:Book对象 → TypeAdapter → 二进制数据 → 文件
|
||||
读取时:文件 → 二进制数据 → TypeAdapter → Book对象
|
||||
```
|
||||
|
||||
### 自动生成的TypeAdapter
|
||||
|
||||
**📖 文件位置:** `lib/models/book.g.dart` (自动生成)
|
||||
|
||||
#### read方法 (从文件读取)
|
||||
```dart
|
||||
@override
|
||||
Book read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte(); // 读取字段总数
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Book(
|
||||
id: fields[0] as String, // 第0个字段 → id
|
||||
title: fields[1] as String, // 第1个字段 → title
|
||||
author: fields[2] as String?, // 第2个字段 → author (可为空)
|
||||
publisher: fields[3] as String?, // 第3个字段 → publisher (可为空)
|
||||
description: fields[4] as String?, // ... 依此类推
|
||||
// ... 其他字段
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**💡 工作原理:**
|
||||
1. `BinaryReader` 从二进制文件读取数据
|
||||
2. 读取字段总数
|
||||
3. 按顺序读取每个字段
|
||||
4. 转换为对应的Dart类型
|
||||
5. 构造Book对象
|
||||
|
||||
#### write方法 (写入文件)
|
||||
```dart
|
||||
@override
|
||||
void write(BinaryWriter writer, Book obj) {
|
||||
writer
|
||||
..writeByte(14) // 写入字段总数 (14个字段)
|
||||
..writeByte(0)..write(obj.id) // 写入第0个字段:id
|
||||
..writeByte(1)..write(obj.title) // 写入第1个字段:title
|
||||
..writeByte(2)..write(obj.author) // 写入第2个字段:author
|
||||
// ... 写入所有其他字段
|
||||
}
|
||||
```
|
||||
|
||||
**💡 工作原理:**
|
||||
1. `BinaryWriter` 准备写入二进制文件
|
||||
2. 写入字段总数
|
||||
3. 按顺序写入每个字段的值
|
||||
4. 最终存储为二进制格式
|
||||
|
||||
### 枚举TypeAdapter
|
||||
|
||||
**🔄 枚举为什么也需要TypeAdapter?**
|
||||
- 枚举在Dart中也是自定义类型
|
||||
- Hive需要知道如何存储和读取枚举值
|
||||
- 枚举通常转换为整数存储
|
||||
|
||||
**💡 存储方式:**
|
||||
- `BookFormat.epub` → 存储为整数 0
|
||||
- `BookFormat.mobi` → 存储为整数 1
|
||||
- `BookFormat.txt` → 存储为整数 2
|
||||
- 读取时:0 → `BookFormat.epub`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证详解
|
||||
|
||||
### 测试代码分析
|
||||
**📖 文件位置:** `lib/main.dart`
|
||||
|
||||
#### 主函数初始化
|
||||
```dart
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized(); // 🚦 确保Flutter引擎就绪
|
||||
|
||||
await DatabaseService.instance.init(); // 🏗️ 初始化数据库
|
||||
await runBookRepositoryTest(); // 🧪 运行测试
|
||||
|
||||
runApp(const MyApp()); // 🎬 启动UI
|
||||
}
|
||||
```
|
||||
|
||||
**💡 关键概念:**
|
||||
- `async/await` - 异步操作,因为数据库初始化需要时间
|
||||
- `WidgetsFlutterBinding.ensureInitialized()` - 确保Flutter引擎准备就绪
|
||||
|
||||
#### 测试流程详解
|
||||
|
||||
**🧪 完整测试步骤:**
|
||||
|
||||
1. **获取当前数据状态**
|
||||
```dart
|
||||
List<Book> currentBooks = await bookRepository.getAllBooks();
|
||||
print('📚 当前书籍数量: ${currentBooks.length}');
|
||||
```
|
||||
|
||||
2. **创建测试数据**
|
||||
```dart
|
||||
final testBook = Book(
|
||||
id: 'test_book_001', // 🔑 唯一标识符
|
||||
title: 'Flutter开发入门', // 📚 书名
|
||||
format: BookFormat.epub, // 📄 文件格式
|
||||
status: ReadingStatus.pending, // 📖 阅读状态
|
||||
// ... 其他字段
|
||||
);
|
||||
```
|
||||
|
||||
3. **测试添加功能**
|
||||
```dart
|
||||
await bookRepository.addBook(testBook); // ➕ 添加到数据库
|
||||
```
|
||||
|
||||
4. **验证添加结果**
|
||||
```dart
|
||||
List<Book> updatedBooks = await bookRepository.getAllBooks();
|
||||
print('📚 添加后书籍数量: ${updatedBooks.length}'); // 应该是 +1
|
||||
```
|
||||
|
||||
5. **测试查询功能**
|
||||
```dart
|
||||
Book? foundBook = await bookRepository.getBookById(testBook.id);
|
||||
// 验证找到的书籍信息是否正确
|
||||
```
|
||||
|
||||
6. **测试更新功能**
|
||||
```dart
|
||||
final updatedBook = testBook.copyWith(status: ReadingStatus.completed);
|
||||
await bookRepository.updateBook(updatedBook); // ✏️ 更新状态
|
||||
```
|
||||
|
||||
7. **验证更新结果**
|
||||
```dart
|
||||
Book? updatedFoundBook = await bookRepository.getBookById(testBook.id);
|
||||
// 检查状态是否真的变成了completed
|
||||
```
|
||||
|
||||
**📊 测试验证的关键点:**
|
||||
|
||||
| 测试项 | 验证内容 | 预期结果 |
|
||||
|-------|---------|---------|
|
||||
| **存储功能** | 数据是否正确保存到文件 | ✅ 关闭应用后数据依然存在 |
|
||||
| **查询功能** | 能否正确检索数据 | ✅ 返回正确的Book对象 |
|
||||
| **更新功能** | 数据更新是否生效 | ✅ 更新后的数据正确保存 |
|
||||
| **对象完整性** | 复杂对象是否完整保存 | ✅ 枚举、日期、列表等字段正确 |
|
||||
| **类型安全** | 类型转换是否正确 | ✅ 没有类型转换错误 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常见问题和解决方案
|
||||
|
||||
### 1️⃣ TypeAdapter错误
|
||||
```
|
||||
HiveError: Cannot write, unknown type: BookFormat. Did you forget to register an adapter?
|
||||
```
|
||||
|
||||
**🔧 解决方案:**
|
||||
```dart
|
||||
// 在DatabaseService.init()中注册所有TypeAdapter
|
||||
Hive.registerAdapter(BookFormatAdapter());
|
||||
Hive.registerAdapter(ReadingStatusAdapter());
|
||||
Hive.registerAdapter(BookAdapter());
|
||||
```
|
||||
|
||||
**💡 错误原因:**
|
||||
- Hive不知道如何序列化自定义类型
|
||||
- 需要先注册TypeAdapter再使用该类型
|
||||
|
||||
### 2️⃣ Box未打开错误
|
||||
```
|
||||
Exception: Book Box未打开,请先调用init()
|
||||
```
|
||||
|
||||
**🔧 解决方案:**
|
||||
- 确保先调用 `DatabaseService.instance.init()`
|
||||
- 不要在init()完成前访问Box
|
||||
- 检查初始化顺序是否正确
|
||||
|
||||
### 3️⃣ 应用启动顺序
|
||||
```dart
|
||||
void main() async {
|
||||
// 正确的初始化顺序
|
||||
WidgetsFlutterBinding.ensureInitialized(); // 1. Flutter引擎
|
||||
await DatabaseService.instance.init(); // 2. 数据库
|
||||
runApp(const MyApp()); // 3. UI
|
||||
}
|
||||
```
|
||||
|
||||
### 4️⃣ TypeAdapter重新生成
|
||||
```bash
|
||||
# 清理并重新生成
|
||||
dart run build_runner clean
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
**🔄 何时需要重新生成:**
|
||||
- 修改了数据模型(添加/删除字段)
|
||||
- 更改了Hive注解
|
||||
- 修改了枚举定义
|
||||
|
||||
---
|
||||
|
||||
## 🎯 实际应用场景
|
||||
|
||||
### 📚 电子书阅读器中的应用
|
||||
|
||||
**书籍管理:**
|
||||
- 用户导入的EPUB、MOBI、TXT文件信息持久化
|
||||
- 书籍封面、元数据存储
|
||||
- 阅读进度保存
|
||||
|
||||
**阅读功能:**
|
||||
- 书签位置记录
|
||||
- 高亮文本保存
|
||||
- 批注内容存储
|
||||
|
||||
**用户设置:**
|
||||
- 字体大小、主题偏好
|
||||
- 阅读习惯配置
|
||||
- 书架分类管理
|
||||
|
||||
### 🔄 数据持久化在Readful项目中的应用
|
||||
|
||||
```dart
|
||||
// 1. 用户导入电子书
|
||||
final book = Book(
|
||||
title: '深入Flutter',
|
||||
author: '张三',
|
||||
filePath: selectedFile.path,
|
||||
format: detectFileFormat(selectedFile.path),
|
||||
status: ReadingStatus.pending,
|
||||
addedDate: DateTime.now(),
|
||||
);
|
||||
await bookRepository.addBook(book);
|
||||
|
||||
// 2. 用户开始阅读
|
||||
final updatedBook = book.copyWith(
|
||||
status: ReadingStatus.reading,
|
||||
lastReadDate: DateTime.now(),
|
||||
);
|
||||
await bookRepository.updateBook(updatedBook);
|
||||
|
||||
// 3. 用户添加书签
|
||||
final bookmark = Bookmark(
|
||||
bookId: book.id,
|
||||
title: '重要章节',
|
||||
position: 0.75, // 75%位置
|
||||
content: 'Flutter是Google开发的跨平台框架',
|
||||
);
|
||||
await bookmarkRepository.addBookmark(bookmark);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 性能优化建议
|
||||
|
||||
### 1️⃣ 异步操作优化
|
||||
|
||||
```dart
|
||||
// ❌ 不要这样写(阻塞UI)
|
||||
List<Book> books = bookRepository.getAllBooks(); // 同步操作
|
||||
|
||||
// ✅ 应该这样写(不阻塞UI)
|
||||
List<Book> books = await bookRepository.getAllBooks(); // 异步操作
|
||||
```
|
||||
|
||||
### 2️⃣ 错误处理最佳实践
|
||||
|
||||
```dart
|
||||
try {
|
||||
await bookRepository.addBook(book);
|
||||
} on HiveException catch (e) {
|
||||
// 处理Hive特定错误
|
||||
print('数据库错误: ${e.message}');
|
||||
} catch (e) {
|
||||
// 处理其他错误
|
||||
print('未知错误: $e');
|
||||
}
|
||||
```
|
||||
|
||||
### 3️⃣ 内存管理
|
||||
|
||||
```dart
|
||||
// 🔄 及时关闭数据库连接
|
||||
@override
|
||||
void dispose() {
|
||||
DatabaseService.instance.close();
|
||||
super.dispose();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 学习成果检验
|
||||
|
||||
### ✅ 掌握的核心知识点
|
||||
|
||||
1. **数据持久化概念** ⭐⭐⭐⭐⭐
|
||||
- 理解为什么需要数据持久化
|
||||
- 知道不同存储方案的优缺点
|
||||
|
||||
2. **Hive数据库原理** ⭐⭐⭐⭐⭐
|
||||
- 单例模式的应用
|
||||
- Box概念的理解
|
||||
- TypeAdapter的自动生成机制
|
||||
|
||||
3. **Repository模式** ⭐⭐⭐⭐
|
||||
- 数据访问层的抽象
|
||||
- CRUD操作的实现
|
||||
- 错误处理最佳实践
|
||||
|
||||
4. **异步编程** ⭐⭐⭐⭐
|
||||
- async/await的使用
|
||||
- Future类型的概念
|
||||
- 异步操作的错误处理
|
||||
|
||||
5. **代码生成工具** ⭐⭐⭐⭐
|
||||
- build_runner的使用
|
||||
- TypeAdapter的自动生成
|
||||
- 代码维护的最佳实践
|
||||
|
||||
### 🚀 实际应用能力
|
||||
|
||||
- ✅ **数据库设计:** 能够设计并实现移动应用的本地数据存储
|
||||
- ✅ **数据操作:** 熟练使用Hive进行数据的增删改查
|
||||
- ✅ **架构设计:** 理解Repository模式的应用场景
|
||||
- ✅ **问题解决:** 能够独立排查和解决数据持久化相关的问题
|
||||
|
||||
---
|
||||
|
||||
## 📚 进阶学习建议
|
||||
|
||||
### 🔮 下一步学习内容
|
||||
|
||||
1. **性能优化**
|
||||
- 大量数据的分页加载
|
||||
- 数据库查询优化
|
||||
- 内存管理策略
|
||||
|
||||
2. **高级Hive功能**
|
||||
- 复杂查询和索引
|
||||
- 数据迁移和版本管理
|
||||
- 备份和恢复机制
|
||||
|
||||
3. **状态管理集成**
|
||||
- Provider状态管理
|
||||
- Riverpod状态管理
|
||||
- 数据与UI的响应式绑定
|
||||
|
||||
4. **其他存储方案**
|
||||
- SQLite与Hive的对比
|
||||
- 云存储集成
|
||||
- 数据同步策略
|
||||
|
||||
---
|
||||
|
||||
## 🎉 章节总结
|
||||
|
||||
恭喜你!你已经掌握了Flutter中最重要的技能之一:**数据持久化**!
|
||||
|
||||
通过本章学习,你获得了:
|
||||
|
||||
1. **🏗️ 完整的数据持久化架构**
|
||||
2. **🔧 实际的编码技能**
|
||||
3. **💡 设计模式的理解**
|
||||
4. **🐛 问题解决能力**
|
||||
5. **🚀 项目实践经验**
|
||||
|
||||
**🎯 关键成就:**
|
||||
- ✅ 成功实现了Readful项目的完整数据层
|
||||
- ✅ 掌握了Hive数据库的核心使用方法
|
||||
- ✅ 理解了Repository模式的实际应用
|
||||
- ✅ 具备了独立开发移动应用数据持久化的能力
|
||||
|
||||
这些知识点将是你Flutter开发路上的重要基石,无论开发什么类型的应用,数据持久化都是必备技能!
|
||||
|
||||
**📖 下一步:** 让我们基于这个强大的数据基础,开始实现用户界面功能!
|
||||
103
lib/main.dart
103
lib/main.dart
@ -1,93 +1,12 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:hive_flutter/hive_flutter.dart";
|
||||
import 'services/database_service.dart';
|
||||
import 'services/book_repository.dart';
|
||||
import 'models/book.dart';
|
||||
|
||||
void main() async {
|
||||
// 确保Flutter初始化完成
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 初始化Hive数据库
|
||||
try {
|
||||
await DatabaseService.instance.init();
|
||||
print('✅ 数据库初始化成功');
|
||||
|
||||
// 运行数据持久化测试
|
||||
await runBookRepositoryTest();
|
||||
|
||||
} catch (e) {
|
||||
print('❌ 数据库初始化失败: $e');
|
||||
}
|
||||
|
||||
runApp(const MyApp());
|
||||
void main() {
|
||||
runApp(const Myapp());
|
||||
}
|
||||
|
||||
/// 测试BookRepository的数据持久化功能
|
||||
Future<void> runBookRepositoryTest() async {
|
||||
final bookRepository = BookRepository();
|
||||
|
||||
print('\n🧪 开始测试BookRepository数据持久化功能...\n');
|
||||
|
||||
try {
|
||||
// 1. 获取当前所有书籍
|
||||
List<Book> currentBooks = await bookRepository.getAllBooks();
|
||||
print('📚 当前书籍数量: ${currentBooks.length}');
|
||||
|
||||
// 2. 创建测试书籍
|
||||
final testBook = Book(
|
||||
id: 'test_book_001',
|
||||
title: 'Flutter开发入门',
|
||||
author: '张三',
|
||||
publisher: '技术出版社',
|
||||
description: '这是一本关于Flutter开发的入门书籍',
|
||||
filePath: '/path/to/flutter_book.epub',
|
||||
format: BookFormat.epub,
|
||||
fileSize: 1024 * 1024, // 1MB
|
||||
addedDate: DateTime.now(),
|
||||
status: ReadingStatus.pending,
|
||||
totalPages: 300,
|
||||
);
|
||||
|
||||
// 3. 添加测试书籍
|
||||
await bookRepository.addBook(testBook);
|
||||
|
||||
// 4. 再次获取所有书籍
|
||||
List<Book> updatedBooks = await bookRepository.getAllBooks();
|
||||
print('📚 添加后书籍数量: ${updatedBooks.length}');
|
||||
|
||||
// 5. 根据ID查找书籍
|
||||
Book? foundBook = await bookRepository.getBookById(testBook.id);
|
||||
if (foundBook != null) {
|
||||
print('🔍 成功找到书籍: ${foundBook.title}');
|
||||
print(' - 作者: ${foundBook.author}');
|
||||
print(' - 格式: ${foundBook.format.name}');
|
||||
print(' - 状态: ${foundBook.status.name}');
|
||||
} else {
|
||||
print('❌ 未找到指定书籍');
|
||||
}
|
||||
|
||||
// 6. 测试更新书籍
|
||||
final updatedBook = testBook.copyWith(status: ReadingStatus.completed);
|
||||
await bookRepository.updateBook(updatedBook);
|
||||
print('✅ 书籍状态更新为: ${updatedBook.status.name}');
|
||||
|
||||
// 7. 验证更新后的状态
|
||||
Book? updatedFoundBook = await bookRepository.getBookById(testBook.id);
|
||||
if (updatedFoundBook != null && updatedFoundBook.status == ReadingStatus.completed) {
|
||||
print('✅ 状态更新验证成功!');
|
||||
} else {
|
||||
print('❌ 状态更新验证失败!');
|
||||
}
|
||||
|
||||
print('\n✅ BookRepository测试完成!所有功能正常工作\n');
|
||||
} catch (e) {
|
||||
print('❌ BookRepository测试失败: $e\n');
|
||||
}
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
class Myapp extends StatelessWidget{
|
||||
const Myapp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -96,19 +15,7 @@ class MyApp extends StatelessWidget {
|
||||
theme: ThemeData(primarySwatch: Colors.blue),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('readful 电子书阅读器')),
|
||||
body: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('欢迎使用 readful', style: TextStyle(fontSize: 20)),
|
||||
SizedBox(height: 20),
|
||||
Text('✅ 数据库已初始化', style: TextStyle(color: Colors.green)),
|
||||
Text('✅ BookRepository测试完成', style: TextStyle(color: Colors.green)),
|
||||
SizedBox(height: 10),
|
||||
Text('请查看控制台输出查看详细测试结果'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: const Center(child: Text('欢迎使用 readful')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,31 +1,19 @@
|
||||
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 // 待阅读
|
||||
}
|
||||
|
||||
@ -36,62 +24,47 @@ enum ReadingStatus {
|
||||
/// 2. 提供copyWith方法创建新对象
|
||||
/// 3. 提供序列化和反序列化方法
|
||||
/// 4. 使用空值安全语法
|
||||
@HiveType(typeId: 2)
|
||||
class Book {
|
||||
/// 唯一标识符 - 使用UUID确保全局唯一
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
/// 书名 - 必需字段
|
||||
@HiveField(1)
|
||||
final String title;
|
||||
|
||||
/// 作者 - 可为null(因为有些书籍可能没有作者信息)
|
||||
@HiveField(2)
|
||||
final String? author;
|
||||
|
||||
/// 出版社 - 可选字段
|
||||
@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;
|
||||
|
||||
/// 总页数 - 必需字段
|
||||
@HiveField(13)
|
||||
final int totalPages;
|
||||
|
||||
/// 构造函数
|
||||
|
||||
@ -1,173 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'book.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class BookAdapter extends TypeAdapter<Book> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
Book read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Book(
|
||||
id: fields[0] as String,
|
||||
title: fields[1] as String,
|
||||
author: fields[2] as String?,
|
||||
publisher: fields[3] as String?,
|
||||
description: fields[4] as String?,
|
||||
coverImagePath: fields[5] as String?,
|
||||
filePath: fields[6] as String,
|
||||
format: fields[7] as BookFormat,
|
||||
fileSize: fields[8] as int,
|
||||
addedDate: fields[9] as DateTime,
|
||||
status: fields[10] as ReadingStatus,
|
||||
rating: fields[11] as double?,
|
||||
tags: (fields[12] as List).cast<String>(),
|
||||
totalPages: fields[13] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Book obj) {
|
||||
writer
|
||||
..writeByte(14)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.title)
|
||||
..writeByte(2)
|
||||
..write(obj.author)
|
||||
..writeByte(3)
|
||||
..write(obj.publisher)
|
||||
..writeByte(4)
|
||||
..write(obj.description)
|
||||
..writeByte(5)
|
||||
..write(obj.coverImagePath)
|
||||
..writeByte(6)
|
||||
..write(obj.filePath)
|
||||
..writeByte(7)
|
||||
..write(obj.format)
|
||||
..writeByte(8)
|
||||
..write(obj.fileSize)
|
||||
..writeByte(9)
|
||||
..write(obj.addedDate)
|
||||
..writeByte(10)
|
||||
..write(obj.status)
|
||||
..writeByte(11)
|
||||
..write(obj.rating)
|
||||
..writeByte(12)
|
||||
..write(obj.tags)
|
||||
..writeByte(13)
|
||||
..write(obj.totalPages);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BookAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class BookFormatAdapter extends TypeAdapter<BookFormat> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
BookFormat read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return BookFormat.epub;
|
||||
case 1:
|
||||
return BookFormat.mobi;
|
||||
case 2:
|
||||
return BookFormat.txt;
|
||||
case 3:
|
||||
return BookFormat.pdf;
|
||||
default:
|
||||
return BookFormat.epub;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, BookFormat obj) {
|
||||
switch (obj) {
|
||||
case BookFormat.epub:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case BookFormat.mobi:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case BookFormat.txt:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case BookFormat.pdf:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BookFormatAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class ReadingStatusAdapter extends TypeAdapter<ReadingStatus> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
ReadingStatus read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return ReadingStatus.reading;
|
||||
case 1:
|
||||
return ReadingStatus.completed;
|
||||
case 2:
|
||||
return ReadingStatus.pending;
|
||||
default:
|
||||
return ReadingStatus.reading;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ReadingStatus obj) {
|
||||
switch (obj) {
|
||||
case ReadingStatus.reading:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case ReadingStatus.completed:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case ReadingStatus.pending:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ReadingStatusAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@ -1,156 +0,0 @@
|
||||
/// 书签模型
|
||||
/// 用于记录和管理阅读位置标记
|
||||
class Bookmark {
|
||||
final String id;
|
||||
final String bookId;
|
||||
final String? chapterId;
|
||||
final String title;
|
||||
final String? description;
|
||||
final int pageIndex;
|
||||
final double position; // 0.0-1.0之间的值,表示在文档中的相对位置
|
||||
final String? previewText;
|
||||
final DateTime createdTime;
|
||||
final int sortOrder;
|
||||
|
||||
// 构造函数...
|
||||
const Bookmark(
|
||||
{required this.id,
|
||||
required this.bookId,
|
||||
this.chapterId,
|
||||
required this.title,
|
||||
this.description,
|
||||
required this.pageIndex,
|
||||
required this.position,
|
||||
this.previewText,
|
||||
required this.createdTime,
|
||||
required this.sortOrder});
|
||||
|
||||
// copyWith方法...
|
||||
Bookmark copyWith(
|
||||
{String? id,
|
||||
String? bookId,
|
||||
String? chapterId,
|
||||
String? title,
|
||||
String? description,
|
||||
int? pageIndex,
|
||||
double? position,
|
||||
String? previewText,
|
||||
DateTime? createdTime,
|
||||
int? sortOrder}) {
|
||||
return Bookmark(
|
||||
id: id ?? this.id, // 如果id不为null就使用新值,否则保持原值
|
||||
bookId: bookId ?? this.bookId,
|
||||
chapterId: chapterId ?? this.chapterId,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
pageIndex: pageIndex ?? this.pageIndex,
|
||||
position: position ?? this.position,
|
||||
previewText: previewText ?? this.previewText,
|
||||
createdTime: createdTime ?? this.createdTime,
|
||||
sortOrder: sortOrder ?? this.sortOrder);
|
||||
}
|
||||
|
||||
// toMap/fromMap方法...
|
||||
/// toMap方法 - 将对象转换为Map
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'bookId': bookId,
|
||||
'chapterId': chapterId,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'pageIndex': pageIndex,
|
||||
'position': position,
|
||||
'previewText': previewText,
|
||||
'createdTime': createdTime.toIso8601String(),
|
||||
'sortOrder': sortOrder
|
||||
};
|
||||
}
|
||||
|
||||
/// fromMap构造函数 - 从Map创建Book对象
|
||||
///
|
||||
/// 这是toMap的逆操作,用于:
|
||||
/// 1. 从本地存储中读取数据
|
||||
/// 2. 从网络请求中解析数据
|
||||
/// 3. 从保存的状态中恢复对象
|
||||
factory Bookmark.fromMap(Map<String, dynamic> map) {
|
||||
return Bookmark(
|
||||
id: map['id'],
|
||||
bookId: map['bookId'],
|
||||
chapterId: map['chapterId'],
|
||||
title: map['title'],
|
||||
description: map['description'],
|
||||
pageIndex: map['pageIndex'],
|
||||
position: map['position'],
|
||||
previewText: map['previewText'],
|
||||
createdTime: DateTime.parse(map['createdTime']),
|
||||
sortOrder: map['sortOrder']);
|
||||
}
|
||||
// 工厂方法...
|
||||
factory Bookmark.create({
|
||||
required String bookId,
|
||||
required String title,
|
||||
required int pageIndex,
|
||||
required double position,
|
||||
String? chapterId,
|
||||
String? description,
|
||||
String? previewText,
|
||||
}) {
|
||||
return Bookmark(
|
||||
id: _generateId(), // 生成唯一ID
|
||||
bookId: bookId,
|
||||
chapterId: chapterId,
|
||||
title: title,
|
||||
pageIndex: pageIndex,
|
||||
position: position,
|
||||
description: description,
|
||||
previewText: previewText,
|
||||
createdTime: DateTime.now(),
|
||||
sortOrder: 0);
|
||||
}
|
||||
|
||||
// 计算属性和实用方法...
|
||||
/// 生成唯一ID的私有方法
|
||||
static String _generateId() {
|
||||
return '${DateTime.now().millisecondsSinceEpoch}_${(DateTime.now().microsecond).toString()}';
|
||||
}
|
||||
|
||||
/// 重写equals和hashCode,用于对象比较
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Bookmark && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
|
||||
/// 重写toString方法,便于调试
|
||||
@override
|
||||
String toString() {
|
||||
return 'Bookmark(id: $id, title: $title, bookId: $bookId, sortOrder: $sortOrder)';
|
||||
}
|
||||
|
||||
// 更新位置
|
||||
Bookmark updatePosition(int newPageIndex, double newPosition) {
|
||||
return copyWith(
|
||||
pageIndex: newPageIndex,
|
||||
position: newPosition,
|
||||
);
|
||||
}
|
||||
|
||||
// 更新标题和描述
|
||||
Bookmark updateInfo({String? title, String? description}) {
|
||||
return copyWith(
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
);
|
||||
}
|
||||
|
||||
// 格式化位置显示
|
||||
String get positionDisplay => '${(position * 100).toInt()}%';
|
||||
|
||||
// 检查位置是否有效
|
||||
bool get isValidPosition => position >= 0.0 && position <= 1.0;
|
||||
}
|
||||
@ -1,252 +0,0 @@
|
||||
///书架类型
|
||||
enum BookshelfType {
|
||||
system, //系统默认书架
|
||||
custom //用户自定义书架
|
||||
}
|
||||
|
||||
/// 书架数据模型
|
||||
class Bookshelf {
|
||||
final String id; // 唯一标识
|
||||
final String name; // 书架名称
|
||||
final String? description; // 书架描述
|
||||
final String? coverImagePath; // 书架封面
|
||||
final DateTime createdTime; // 创建时间
|
||||
final DateTime lastModifiedTime; // 最后修改时间
|
||||
final int bookCount; // 书籍数量
|
||||
final BookshelfType type; // 书架类型
|
||||
final bool isDefault; // 是否为默认书架
|
||||
final int sortOrder; // 排序顺序
|
||||
|
||||
// 构造函数
|
||||
const Bookshelf(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
this.description,
|
||||
this.coverImagePath,
|
||||
required this.createdTime,
|
||||
required this.lastModifiedTime,
|
||||
required this.bookCount,
|
||||
required this.type,
|
||||
required this.isDefault,
|
||||
required this.sortOrder});
|
||||
|
||||
// copyWith方法...
|
||||
Bookshelf copyWith(
|
||||
{String? id,
|
||||
String? name,
|
||||
String? description,
|
||||
String? coverImagePath,
|
||||
DateTime? createdTime,
|
||||
DateTime? lastModifiedTime,
|
||||
int? bookCount,
|
||||
BookshelfType? type,
|
||||
bool? isDefault,
|
||||
int? sortOrder}) {
|
||||
return Bookshelf(
|
||||
id: id ?? this.id, // 如果id不为null就使用新值,否则保持原值
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
coverImagePath: coverImagePath ?? this.coverImagePath,
|
||||
createdTime: createdTime ?? this.createdTime,
|
||||
lastModifiedTime: lastModifiedTime ?? this.lastModifiedTime,
|
||||
bookCount: bookCount ?? this.bookCount,
|
||||
type: type ?? this.type,
|
||||
isDefault: isDefault ?? this.isDefault,
|
||||
sortOrder: sortOrder ?? this.sortOrder);
|
||||
}
|
||||
|
||||
// toMap、fromMap、
|
||||
/// toMap方法 - 将对象转换为Map
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'coverImagePath': coverImagePath,
|
||||
'createdTime': createdTime.toIso8601String(),
|
||||
'lastModifiedTime': lastModifiedTime.toIso8601String(),
|
||||
'bookCount': bookCount,
|
||||
'type': type.name,
|
||||
'isDefault': isDefault,
|
||||
'sortOrder': sortOrder
|
||||
};
|
||||
}
|
||||
|
||||
/// fromMap构造函数
|
||||
factory Bookshelf.fromMap(Map<String, dynamic> map) {
|
||||
return Bookshelf(
|
||||
id: map['id'],
|
||||
name: map['name'],
|
||||
description: map['description'],
|
||||
coverImagePath: map['coverImagePath'],
|
||||
createdTime: DateTime.parse(map['createdTime']),
|
||||
lastModifiedTime: DateTime.parse(map['lastModifiedTime']),
|
||||
bookCount: map['bookCount'],
|
||||
type: BookshelfType.values.firstWhere((e) => e.name == map['type']),
|
||||
isDefault: map['isDefault'],
|
||||
sortOrder: map['sortOrder']);
|
||||
}
|
||||
|
||||
// 工厂方法
|
||||
factory Bookshelf.create({
|
||||
required String name,
|
||||
BookshelfType type = BookshelfType.custom,
|
||||
String? description,
|
||||
}) {
|
||||
return Bookshelf(
|
||||
id: _generateId(),
|
||||
name: name,
|
||||
type: type,
|
||||
createdTime: DateTime.now(),
|
||||
lastModifiedTime: DateTime.now(),
|
||||
bookCount: 0,
|
||||
sortOrder: 0,
|
||||
isDefault: false,
|
||||
description: description,
|
||||
);
|
||||
}
|
||||
|
||||
// 创建系统书架的便利方法
|
||||
factory Bookshelf.createSystem({
|
||||
required String name,
|
||||
String? description,
|
||||
}) {
|
||||
return Bookshelf(
|
||||
id: _generateSystemId(name),
|
||||
name: name,
|
||||
type: BookshelfType.system,
|
||||
isDefault: true,
|
||||
createdTime: DateTime.now(),
|
||||
lastModifiedTime: DateTime.now(),
|
||||
bookCount: 0,
|
||||
sortOrder: 0,
|
||||
description: description,
|
||||
);
|
||||
}
|
||||
|
||||
/// 生成唯一ID的私有方法
|
||||
/// 用于用户自定义书架
|
||||
static String _generateId() {
|
||||
return 'shelf_${DateTime.now().millisecondsSinceEpoch}_${DateTime.now().microsecond}';
|
||||
}
|
||||
|
||||
/// 生成系统书架ID的私有方法
|
||||
/// 基于书架名称生成可预测的ID,确保应用重启后ID保持一致
|
||||
static String _generateSystemId(String name) {
|
||||
// 清理名称:移除空格和特殊字符,保留中文、英文、数字
|
||||
final cleanName = name
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9\u4e00-\u9fff]'), '') // 保留中文、英文、数字
|
||||
.toLowerCase();
|
||||
return 'system_$cleanName'; // 添加前缀区分系统书架
|
||||
}
|
||||
|
||||
/// 重写equals和hashCode,用于对象比较
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is Bookshelf && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
/// 重写toString方法,便于调试
|
||||
@override
|
||||
String toString() {
|
||||
final typeInfo = type == BookshelfType.system ? '[系统]' : '[自定义]';
|
||||
return 'Bookshelf$typeInfo(id: $id, name: $name, books: $bookCount)';
|
||||
}
|
||||
|
||||
/// 计算属性:检查是否为系统书架
|
||||
bool get isSystemShelf => type == BookshelfType.system;
|
||||
|
||||
/// 计算属性:检查是否为用户自定义书架
|
||||
bool get isCustomShelf => type == BookshelfType.custom;
|
||||
|
||||
/// 计算属性:检查是否有描述
|
||||
bool get hasDescription => description != null && description!.isNotEmpty;
|
||||
|
||||
/// 计算属性:检查是否有封面图片
|
||||
bool get hasCoverImage => coverImagePath != null && coverImagePath!.isNotEmpty;
|
||||
|
||||
/// 计算属性:检查书架是否为空
|
||||
bool get isEmpty => bookCount == 0;
|
||||
|
||||
/// 计算属性:获取书籍数量的显示文本
|
||||
String get bookCountDisplay {
|
||||
if (bookCount == 0) return '暂无书籍';
|
||||
if (bookCount == 1) return '1本书';
|
||||
return '$bookCount本书';
|
||||
}
|
||||
|
||||
/// 便利方法:更新书籍数量
|
||||
/// 通常在添加或删除书籍时调用
|
||||
Bookshelf updateBookCount(int newCount) {
|
||||
return copyWith(
|
||||
bookCount: newCount,
|
||||
lastModifiedTime: DateTime.now(), // 更新修改时间
|
||||
);
|
||||
}
|
||||
|
||||
/// 便利方法:增加书籍数量
|
||||
Bookshelf incrementBookCount({int increment = 1}) {
|
||||
return copyWith(
|
||||
bookCount: bookCount + increment,
|
||||
lastModifiedTime: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 便利方法:减少书籍数量
|
||||
Bookshelf decrementBookCount({int decrement = 1}) {
|
||||
final newCount = (bookCount - decrement).clamp(0, bookCount);
|
||||
return copyWith(
|
||||
bookCount: newCount,
|
||||
lastModifiedTime: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 便利方法:更新书架信息
|
||||
Bookshelf updateInfo({
|
||||
String? name,
|
||||
String? description,
|
||||
String? coverImagePath,
|
||||
}) {
|
||||
return copyWith(
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
coverImagePath: coverImagePath ?? this.coverImagePath,
|
||||
lastModifiedTime: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 静态方法:创建默认的系统书架集合
|
||||
/// 应用启动时调用,创建必要的系统书架
|
||||
static List<Bookshelf> createDefaultSystemShelves() {
|
||||
return [
|
||||
Bookshelf.createSystem(
|
||||
name: '全部书籍',
|
||||
description: '包含所有导入的电子书',
|
||||
),
|
||||
Bookshelf.createSystem(
|
||||
name: '最近阅读',
|
||||
description: '最近打开过的书籍',
|
||||
),
|
||||
Bookshelf.createSystem(
|
||||
name: '收藏',
|
||||
description: '用户收藏的书籍',
|
||||
),
|
||||
Bookshelf.createSystem(
|
||||
name: '阅读中',
|
||||
description: '正在阅读的书籍',
|
||||
),
|
||||
Bookshelf.createSystem(
|
||||
name: '已完成',
|
||||
description: '已经读完的书籍',
|
||||
),
|
||||
Bookshelf.createSystem(
|
||||
name: '待阅读',
|
||||
description: '计划阅读的书籍',
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,7 @@
|
||||
///高亮模型
|
||||
|
||||
/// 不同颜色的高亮
|
||||
enum HighlightColor {
|
||||
yellow, // 黄色 - 默认高亮
|
||||
orange, // 橙色 - 重要内容
|
||||
green, // 绿色 - 已理解内容
|
||||
blue, // 蓝色 - 需要复习
|
||||
pink // 粉色 - 个人感想
|
||||
}
|
||||
|
||||
/// 批注类型枚举
|
||||
/// 用于分类不同类型的批注内容
|
||||
enum AnnotationType {
|
||||
note, // 笔记 - 记录知识点
|
||||
thought, // 感想 - 个人想法
|
||||
summary, // 摘要 - 内容总结
|
||||
question // 问题 - 疑问记录
|
||||
}
|
||||
//不同颜色的高亮
|
||||
enum HighlightColor { yellow, orange, green, blue, pink }
|
||||
|
||||
///高亮模型
|
||||
// 关联书籍和章节
|
||||
@ -48,28 +33,15 @@ class Highlight {
|
||||
// 创建时间
|
||||
final DateTime createdTime;
|
||||
|
||||
/// 批注内容 - 可选字段,用于存储用户对高亮的注释
|
||||
final String? annotation;
|
||||
|
||||
/// 批注类型 - 可选字段,用于分类批注
|
||||
final AnnotationType? annotationType;
|
||||
|
||||
/// 批注添加时间 - 可选字段,记录批注的最后修改时间
|
||||
final DateTime? annotationTime;
|
||||
|
||||
const Highlight({
|
||||
required this.id,
|
||||
required this.bookId,
|
||||
this.chapterId,
|
||||
required this.selectedText,
|
||||
required this.startIndex,
|
||||
required this.endIndex,
|
||||
required this.color,
|
||||
required this.createdTime,
|
||||
this.annotation, // 批注内容
|
||||
this.annotationType, // 批注类型
|
||||
this.annotationTime, // 批注时间
|
||||
});
|
||||
const Highlight(
|
||||
{required this.id,
|
||||
required this.bookId,
|
||||
this.chapterId,
|
||||
required this.selectedText,
|
||||
required this.startIndex,
|
||||
required this.endIndex,
|
||||
required this.color,
|
||||
required this.createdTime});
|
||||
|
||||
/// copyWith方法 - 创建对象的副本
|
||||
Highlight copyWith({
|
||||
@ -81,22 +53,16 @@ class Highlight {
|
||||
int? endIndex,
|
||||
HighlightColor? color,
|
||||
DateTime? createdTime,
|
||||
String? annotation,
|
||||
AnnotationType? annotationType,
|
||||
DateTime? annotationTime,
|
||||
}) {
|
||||
return Highlight(
|
||||
id: id ?? this.id,
|
||||
bookId: bookId ?? this.bookId,
|
||||
chapterId: chapterId ?? this.chapterId,
|
||||
chapterId: chapterId ?? this.chapterId, // 修复:添加缺失的参数
|
||||
selectedText: selectedText ?? this.selectedText,
|
||||
startIndex: startIndex ?? this.startIndex,
|
||||
endIndex: endIndex ?? this.endIndex,
|
||||
color: color ?? this.color,
|
||||
createdTime: createdTime ?? this.createdTime,
|
||||
annotation: annotation ?? this.annotation,
|
||||
annotationType: annotationType ?? this.annotationType,
|
||||
annotationTime: annotationTime ?? this.annotationTime,
|
||||
);
|
||||
}
|
||||
|
||||
@ -111,9 +77,6 @@ class Highlight {
|
||||
'endIndex': endIndex,
|
||||
'color': color.name, // 枚举值转换为字符串
|
||||
'createdTime': createdTime.toIso8601String(), // DateTime转换为字符串
|
||||
'annotation': annotation,
|
||||
'annotationType': annotationType?.name, // 枚举转换为字符串,注意可能是null
|
||||
'annotationTime': annotationTime?.toIso8601String(), // DateTime转换为字符串,注意可能是null
|
||||
};
|
||||
}
|
||||
|
||||
@ -128,13 +91,6 @@ class Highlight {
|
||||
endIndex: map['endIndex'],
|
||||
color: HighlightColor.values.firstWhere((e) => e.name == map['color']),
|
||||
createdTime: DateTime.parse(map['createdTime']),
|
||||
annotation: map['annotation'], // 批注内容直接取值
|
||||
annotationType: map['annotationType'] != null
|
||||
? AnnotationType.values.firstWhere((e) => e.name == map['annotationType'])
|
||||
: null, // 处理可选的枚举字段
|
||||
annotationTime: map['annotationTime'] != null
|
||||
? DateTime.parse(map['annotationTime'])
|
||||
: null, // 处理可选的DateTime字段
|
||||
);
|
||||
}
|
||||
|
||||
@ -166,6 +122,12 @@ class Highlight {
|
||||
return '${DateTime.now().millisecondsSinceEpoch}_${(DateTime.now().microsecond).toString()}';
|
||||
}
|
||||
|
||||
/// 重写toString方法,便于调试
|
||||
@override
|
||||
String toString() {
|
||||
return 'Highlight(id: $id, bookId: $bookId, chapterId: $chapterId, selectedText: $selectedText)';
|
||||
}
|
||||
|
||||
/// 重写equals和hashCode,用于对象比较
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@ -175,49 +137,4 @@ class Highlight {
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
/// 计算属性:检查是否有批注
|
||||
bool get hasAnnotation => annotation != null && annotation!.isNotEmpty;
|
||||
|
||||
/// 计算属性:获取批注文本长度
|
||||
int get annotationLength => annotation?.length ?? 0;
|
||||
|
||||
/// 添加批注的方法
|
||||
/// 返回一个新的Highlight对象,包含指定的批注内容
|
||||
Highlight addAnnotation(String content, AnnotationType type) {
|
||||
return copyWith(
|
||||
annotation: content,
|
||||
annotationType: type,
|
||||
annotationTime: DateTime.now(), // 设置当前时间为批注时间
|
||||
);
|
||||
}
|
||||
|
||||
/// 更新批注内容的方法
|
||||
/// 返回一个新的Highlight对象,包含更新的批注内容
|
||||
Highlight updateAnnotation(String content, AnnotationType type) {
|
||||
return copyWith(
|
||||
annotation: content,
|
||||
annotationType: type,
|
||||
annotationTime: DateTime.now(), // 更新批注时间
|
||||
);
|
||||
}
|
||||
|
||||
/// 移除批注的方法
|
||||
/// 返回一个新的Highlight对象,移除所有批注相关字段
|
||||
Highlight removeAnnotation() {
|
||||
return copyWith(
|
||||
annotation: null,
|
||||
annotationType: null,
|
||||
annotationTime: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// 重写toString方法,增加批注信息显示
|
||||
@override
|
||||
String toString() {
|
||||
final annotationInfo = hasAnnotation
|
||||
? ' [批注: $annotation]'
|
||||
: '';
|
||||
return 'Highlight(id: $id, bookId: $bookId, text: "$selectedText"$annotationInfo)';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import '../models/book.dart';
|
||||
import 'database_service.dart';
|
||||
|
||||
class BookRepository {
|
||||
final DatabaseService _databaseService = DatabaseService.instance;
|
||||
|
||||
/// 获取所有书籍
|
||||
Future<List<Book>> getAllBooks() async {
|
||||
// 实现获取所有书籍的逻辑
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
return booksBox.values.toList();
|
||||
} catch (e) {
|
||||
print('❌ 获取所有书籍失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据ID获取书籍
|
||||
Future<Book?> getBookById(String id) async {
|
||||
// 实现根据ID获取书籍的逻辑
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
return booksBox.get(id);
|
||||
} catch (e) {
|
||||
print('❌ 根据ID获取书籍失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加新书
|
||||
Future<void> addBook(Book book) async {
|
||||
// 实现添加书籍的逻辑
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
await booksBox.put(book.id, book);
|
||||
print('✅ 书籍添加成功: ${book.title}');
|
||||
} catch (e) {
|
||||
print('❌ 添加书籍失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新书籍信息
|
||||
Future<void> updateBook(Book book) async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
await booksBox.put(book.id, book);
|
||||
print('✅ 书籍更新成功: ${book.title}');
|
||||
} catch (e) {
|
||||
print('❌ 更新书籍失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除书籍
|
||||
Future<void> deleteBook(String id) async {
|
||||
try {
|
||||
final booksBox = _databaseService.getBooksBox();
|
||||
await booksBox.delete(id);
|
||||
print('✅ 书籍删除成功: $id');
|
||||
} catch (e) {
|
||||
print('❌ 删除书籍失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import '../models/book.dart';
|
||||
|
||||
class DatabaseService {
|
||||
static DatabaseService? _instance;
|
||||
static DatabaseService get instance => _instance ??= DatabaseService._();
|
||||
|
||||
DatabaseService._();
|
||||
|
||||
late Box<Book> _booksBox;
|
||||
|
||||
Future<void> init() async {
|
||||
try {
|
||||
// 1. 获取应用文档目录
|
||||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||||
// 2. 初始化Hive
|
||||
await Hive.initFlutter(appDocumentDir.path);
|
||||
|
||||
// 3. 注册枚举TypeAdapter(使用内置的枚举适配器)
|
||||
Hive.registerAdapter(BookFormatAdapter());
|
||||
Hive.registerAdapter(ReadingStatusAdapter());
|
||||
|
||||
// 4. 注册Book TypeAdapter
|
||||
Hive.registerAdapter(BookAdapter());
|
||||
|
||||
// 5. 打开Book Box
|
||||
_booksBox = await Hive.openBox<Book>('books');
|
||||
|
||||
print('✅ Hive数据库初始化成功');
|
||||
} catch (e) {
|
||||
print('❌ Hive数据库初始化失败: $e');
|
||||
rethrow; // 重新抛出异常,让调用者知道初始化失败
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Book Box的方法
|
||||
Box<Book> getBooksBox() {
|
||||
if (!Hive.isBoxOpen('books')) {
|
||||
throw Exception('Book Box未打开,请先调用init()');
|
||||
}
|
||||
return _booksBox; // 添加这行返回语句
|
||||
}
|
||||
|
||||
/// 关闭数据库
|
||||
Future<void> close() async {
|
||||
await _booksBox.close();
|
||||
await Hive.close();
|
||||
}
|
||||
}
|
||||
486
pubspec.lock
486
pubspec.lock
@ -1,30 +1,6 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "64.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -41,70 +17,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "7.3.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "8.12.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -113,14 +25,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -129,14 +33,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.10.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -145,22 +41,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -169,14 +49,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.3.6"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -185,38 +57,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -230,112 +70,11 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.19"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive_flutter
|
||||
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
hive_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -344,14 +83,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -376,30 +107,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -408,139 +115,11 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.1.5+1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.3.4"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -565,14 +144,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -597,22 +168,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -621,14 +176,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -637,38 +184,5 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.2.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.5 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
|
||||
14
pubspec.yaml
14
pubspec.yaml
@ -31,19 +31,11 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2 # iOS 风格图标库
|
||||
|
||||
# 数据库和存储相关依赖
|
||||
hive: ^2.2.3 # Hive轻量级NoSQL数据库
|
||||
hive_flutter: ^1.1.0 # Hive与Flutter的集成
|
||||
path_provider: ^2.0.14 # 获取文件存储路径
|
||||
file_picker: ^5.2.1 # 文件选择器,用于导入电子书
|
||||
|
||||
# 状态管理(后续使用)
|
||||
provider: ^6.0.5 # Provider状态管理
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@ -55,10 +47,6 @@ dev_dependencies:
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
# Hive代码生成相关依赖
|
||||
hive_generator: ^2.0.0 # Hive TypeAdapter代码生成器
|
||||
build_runner: ^2.4.6 # Dart代码生成工具
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user