diff --git a/doc/Android便签应用项目总结.pptx b/doc/Android便签应用项目总结.pptx new file mode 100644 index 0000000..64895be Binary files /dev/null and b/doc/Android便签应用项目总结.pptx differ diff --git a/doc/uml_class_diagram_clean.png b/doc/uml_class_diagram_clean.png new file mode 100644 index 0000000..60729f9 Binary files /dev/null and b/doc/uml_class_diagram_clean.png differ diff --git a/doc/代码注释任务分配-带责任人-新版本.md b/doc/代码注释任务分配-带责任人-新版本.md new file mode 100644 index 0000000..63e863d --- /dev/null +++ b/doc/代码注释任务分配-带责任人-新版本.md @@ -0,0 +1,619 @@ +# 4人小组代码注释任务分配(带责任人提示) + +## 📋 任务总览 + +| 成员 | 负责文件 | 预计时间 | Git分支 | 当前状态 | +|------|---------|---------|---------|---------| +| **刘洋(A)** | Note.kt
NoteDao.kt
NoteDatabase.kt | 2小时 | a_branch | ⚠️ 待开始 | +| **王子函(B)** | NoteRepository.kt | 1.5小时 | b_branch | ⚠️ 待开始 | +| **崔宇航(C)** | NoteViewModel.kt | 2.5小时 | c_branch | ⚠️ 待开始 | +| **刘奔(D)** | MainActivity.kt
NoteEditorScreen.kt
NoteItem.kt
NoteListScreen.kt
Color.kt
Theme.kt
Type.kt | 4小时 | d_branch | ⚠️ 待开始 | + +--- + +## 👤 成员刘洋任务(2小时) + +### 📁 负责文件 + +#### 1. Note.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/data/Note.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明数据模型的作用) +- [ ] 为所有字段添加注释 +- [ ] 为构造函数添加注释 +- [ ] 为所有getter方法添加注释 +- [ ] 为所有setter方法添加注释 +- [ ] 解释数据加载流程 + +**注释示例**: +```kotlin +/** + * 便签数据模型类 + * + * 职责: + * 1. 封装便签的所有属性 + * 2. 提供数据加载和保存功能 + * 3. 管理便签的状态 + * + * @author 刘洋 + * @since 2026-05-02 + */ +class Note { + + /** 便签ID,数据库主键 */ + private var mId: Long = 0 + + /** 父文件夹ID */ + private var mParentId: Long = 0 + + // ... 其他字段 + + /** + * 从Cursor加载数据 + * + * @param cursor 数据库查询结果 + */ + fun loadFromCursor(cursor: Cursor) { + // ... 实现 + } +} +``` + +--- + +#### 2. NoteDao.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/data/NoteDao.kt` + +**需要完成**: +- [ ] 为接口添加详细注释(说明DAO层职责) +- [ ] 为所有方法添加注释(insert, update, delete, query等) +- [ ] 解释Room数据库操作原理 +- [ ] 注释异步操作处理 + +**注释示例**: +```kotlin +/** + * 便签数据访问对象接口 + * + * 职责: + * 1. 定义数据库操作接口 + * 2. 封装CRUD操作 + * 3. 提供类型安全的数据访问 + * + * @author 刘洋 + * @since 2026-05-02 + */ +@Dao +interface NoteDao { + + /** + * 插入便签到数据库 + * + * @param note 要插入的便签对象 + * @return 插入的便签ID + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(note: Note): Long + + /** + * 更新便签数据 + * + * @param note 要更新的便签对象 + * @return 更新的记录数 + */ + @Update + suspend fun update(note: Note): Int +} +``` + +--- + +#### 3. NoteDatabase.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/data/NoteDatabase.kt` + +**需要完成**: +- [ ] 为数据库类添加详细注释 +- [ ] 为数据库实例化方法添加注释 +- [ ] 解释数据库版本管理 +- [ ] 注释数据库迁移策略 +- [ ] 解释单例模式实现 + +**注释示例**: +```kotlin +/** + * 便签数据库类 + * + * 职责: + * 1. 初始化Room数据库实例 + * 2. 管理数据库版本和迁移 + * 3. 提供数据库访问入口 + * + * 设计模式: + * - 单例模式:确保数据库实例唯一 + * - 工厂模式:数据库构建器 + * + * @author 刘洋 + * @since 2026-05-02 + */ +@Database( + entities = [Note::class], + version = 1, + exportSchema = false +) +abstract class NoteDatabase : RoomDatabase() { + + /** + * 获取NoteDao实例 + * + * @return NoteDao对象 + */ + abstract fun noteDao(): NoteDao + + /** + * 获取数据库实例(单例) + * + * 工作流程: + * 1. 检查是否已存在实例 + * 2. 如果不存在,创建新实例 + * 3. 返回数据库实例 + * + * @param context 应用上下文 + * @return 数据库实例 + */ + companion object { + @Volatile + private var INSTANCE: NoteDatabase? = null + + fun getDatabase(context: Context): NoteDatabase { + return INSTANCE ?: synchronized(this) { + INSTANCE ?: buildDatabase(context).also { INSTANCE = it } + } + } + } +} +``` + +--- + +## 👤 成员王子函任务(1.5小时) + +### 📁 负责文件 + +#### 1. NoteRepository.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/data/NoteRepository.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明Repository层职责) +- [ ] 为所有方法添加注释(getNotes, insertNote, updateNote等) +- [ ] 解释数据源协调逻辑 +- [ ] 注释错误处理机制 +- [ ] 解释缓存策略 + +**注释示例**: +```kotlin +/** + * 便签仓库类 + * + * 职责: + * 1. 协调数据源(本地数据库 + 远程服务) + * 2. 提供统一的数据访问接口 + * 3. 实现业务逻辑和数据转换 + * + * 设计模式: + * - 仓库模式:抽象数据访问细节 + * - 观察者模式:LiveData通知UI更新 + * + * @author 王子函 + * @since 2026-05-02 + */ +class NoteRepository(private val noteDao: NoteDao) { + + /** + * 获取所有便签列表 + * + * 工作流程: + * 1. 从数据库查询所有便签 + * 2. 将结果包装为LiveData + * 3. 返回可观察的数据源 + * + * @return 包含便签列表的LiveData + */ + fun getNotes(): LiveData> { + return noteDao.getAllNotes() + } + + /** + * 插入新便签 + * + * @param note 要插入的便签对象 + * @return 插入的便签ID + */ + suspend fun insertNote(note: Note): Long { + return noteDao.insert(note) + } +} +``` + +--- + +## 👤 成员崔宇航任务(2.5小时) + +### 📁 负责文件 + +#### 1. NoteViewModel.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/viewmodel/NoteViewModel.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明ViewModel层职责) +- [ ] 为所有方法添加注释(getNotes, addNote, updateNote等) +- [ ] 解释生命周期感知特性 +- [ ] 注释状态管理逻辑 +- [ ] 解释与Repository的协作关系 + +**注释示例**: +```kotlin +/** + * 便签视图模型类 + * + * 职责: + * 1. 管理UI相关的数据 + * 2. 处理业务逻辑 + * 3. 与Repository交互获取数据 + * 4. 提供生命周期感知的数据流 + * + * 设计模式: + * - MVVM架构:分离UI逻辑和业务逻辑 + * - 观察者模式:LiveData通知UI更新 + * + * @author 崔宇航 + * @since 2026-05-02 + */ +class NoteViewModel(application: Application) : AndroidViewModel(application) { + + private val repository: NoteRepository + + /** + * 便签列表数据流 + * + * @return 包含便签列表的LiveData + */ + val notes: LiveData> + + /** + * 初始化ViewModel + * + * 工作流程: + * 1. 获取Application上下文 + * 2. 创建Repository实例 + * 3. 初始化LiveData + * 4. 加载初始数据 + * + * @param application Application上下文 + */ + init { + val noteDao = NoteDatabase.getDatabase(application).noteDao() + repository = NoteRepository(noteDao) + notes = repository.getNotes() + } + + /** + * 添加新便签 + * + * @param note 要添加的便签对象 + */ + fun addNote(note: Note) { + viewModelScope.launch { + repository.insertNote(note) + } + } +} +``` + +--- + +## 👤 成员刘奔任务(4小时) + +### 📁 负责文件 + +#### 1. MainActivity.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/MainActivity.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明Activity职责) +- [ ] 为所有生命周期方法添加注释(onCreate等) +- [ ] 为所有事件处理方法添加注释 +- [ ] 解释导航逻辑 +- [ ] 注释主题切换功能 + +**注释示例**: +```kotlin +/** + * 主Activity + * + * 主要功能: + * 1. 应用启动入口 + * 2. 导航到不同屏幕(列表/编辑) + * 3. 管理应用主题 + * 4. 处理全局配置 + * + * @author 刘奔 + * @since 2026-05-02 + */ +class MainActivity : AppCompatActivity() { + + /** + * Activity创建时调用 + * + * 工作流程: + * 1. 设置主题 + * 2. 设置布局 + * 3. 初始化ViewModel + * 4. 设置导航组件 + * + * @param savedInstanceState 保存的实例状态 + */ + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // 初始化导航 + val navHostFragment = supportFragmentManager + .findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val navController = navHostFragment.navController + + // 设置底部导航 + findViewById(R.id.bottom_navigation).setupWithNavController(navController) + } +} +``` + +--- + +#### 2. NoteEditorScreen.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/ui/NoteEditorScreen.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明编辑屏幕职责) +- [ ] 为所有Composable函数添加注释 +- [ ] 解释状态管理 +- [ ] 注释输入验证逻辑 +- [ ] 解释保存流程 + +#### 3. NoteItem.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/ui/NoteItem.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明列表项职责) +- [ ] 为所有Composable函数添加注释 +- [ ] 解释点击事件处理 +- [ ] 注释状态显示逻辑 +- [ ] 解释主题适配 + +#### 4. NoteListScreen.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/ui/NoteListScreen.kt` + +**需要完成**: +- [ ] 为类添加详细注释(说明列表屏幕职责) +- [ ] 为所有Composable函数添加注释 +- [ ] 解释列表加载流程 +- [ ] 注释搜索功能 +- [ ] 解释批量操作 + +#### 5. Color.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/ui/theme/Color.kt` + +**需要完成**: +- [ ] 为颜色常量添加注释 +- [ ] 解释深色/浅色主题适配 +- [ ] 注释颜色使用场景 + +#### 6. Theme.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/ui/theme/Theme.kt` + +**需要完成**: +- [ ] 为主题类添加详细注释 +- [ ] 为MaterialTheme扩展添加注释 +- [ ] 解释主题切换逻辑 +- [ ] 注释动态主题适配 + +#### 7. Type.kt ✅ 待开始 +**文件路径**:`app/src/main/java/com/example/myapplication/ui/theme/Type.kt` + +**需要完成**: +- [ ] 为字体样式添加注释 +- [ ] 解释字体大小适配 +- [ ] 注释不同文本样式的使用场景 + +--- + +## 🔧 Git工作流程 + +### 每个成员的操作步骤 + +#### 步骤1:切换到个人分支 + +```bash +# 刘洋(A) +git checkout a_branch + +# 王子函(B) +git checkout b_branch + +# 崔宇航(C) +git checkout c_branch + +# 刘奔(D) +git checkout d_branch +``` + +#### 步骤2:开始注释代码 + +``` +1. 在IDEA中打开负责的文件 +2. 按照上面的注释要求添加注释 +3. 使用KotlinDoc格式(与JavaDoc类似) +4. 保存文件 +``` + +#### 步骤3:提交到个人分支 + +```bash +# 刘洋(A) - Note相关文件 +git add app/src/main/java/com/example/myapplication/data/Note.kt +git add app/src/main/java/com/example/myapplication/data/NoteDao.kt +git add app/src/main/java/com/example/myapplication/data/NoteDatabase.kt +git commit -m "A完成:Note、NoteDao、Database 160行" + +git push origin a_branch + +# 王子函(B) - Repository文件 +git add app/src/main/java/com/example/myapplication/data/NoteRepository.kt +git commit -m "B完成:Repository 74行" + +git push origin b_branch + +# 崔宇航(C) - ViewModel文件 +git add app/src/main/java/com/example/myapplication/viewmodel/NoteViewModel.kt +git commit -m "C完成:ViewModel 160行" + +git push origin c_branch + +# 刘奔(D) - MainActivity和UI文件 +git add app/src/main/java/com/example/myapplication/MainActivity.kt +git add app/src/main/java/com/example/myapplication/ui/NoteEditorScreen.kt +git add app/src/main/java/com/example/myapplication/ui/NoteItem.kt +git add app/src/main/java/com/example/myapplication/ui/NoteListScreen.kt +git add app/src/main/java/com/example/myapplication/ui/theme/Color.kt +git add app/src/main/java/com/example/myapplication/ui/theme/Theme.kt +git add app/src/main/java/com/example/myapplication/ui/theme/Type.kt +git commit -m "D完成:MainActivity、UI、theme 350行" + +git push origin d_branch +``` + +--- + +## 📝 注释规范 + +### KotlinDoc格式 + +```kotlin +/** + * 方法说明 + * + * 详细描述(可选): + * - 工作流程 + * - 注意事项 + * - 设计模式 + * + * @param 参数名 参数说明 + * @return 返回值说明 + * @throws 异常名 异常说明 + */ +fun methodName(param: ParamType): ReturnType { + // 实现 +} +``` + +### 字段注释 + +```kotlin +/** 字段说明 */ +private var fieldName: Int = 0 +``` + +### 代码块注释 + +```kotlin +// 步骤1:初始化数据 +initData() + +// 步骤2:处理业务逻辑 +processData() + +// 步骤3:更新UI +updateUI() +``` + +--- + +## ✅ 完成检查清单 + +### 刘洋(A) +- [ ] Note.kt 所有方法已注释 +- [ ] NoteDao.kt 所有方法已注释 +- [ ] NoteDatabase.kt 所有方法已注释 +- [ ] 提交到 a_branch + +### 王子函(B) +- [ ] NoteRepository.kt 所有方法已注释 +- [ ] 提交到 b_branch + +### 崔宇航(C) +- [ ] NoteViewModel.kt 所有方法已注释 +- [ ] 提交到 c_branch + +### 刘奔(D) +- [ ] MainActivity.kt 所有方法已注释 +- [ ] NoteEditorScreen.kt 所有方法已注释 +- [ ] NoteItem.kt 所有方法已注释 +- [ ] NoteListScreen.kt 所有方法已注释 +- [ ] Color.kt 所有方法已注释 +- [ ] Theme.kt 所有方法已注释 +- [ ] Type.kt 所有方法已注释 +- [ ] 提交到 d_branch + +--- + +## 🎯 合并流程 + +### 所有成员完成后 + +```bash +# 1. 合并到develop分支 +git checkout develop +git merge a_branch +git merge b_branch +git merge c_branch +git merge d_branch + +# 2. 测试功能 +# 在IDEA中运行项目,验证所有功能 + +# 3. 合并到master +git checkout master +git merge develop + +# 4. 推送 +git push origin master +``` + +--- + +## 💡 提示 + +1. **注释要详细**:每个方法都要有KotlinDoc注释 +2. **解释关键逻辑**:复杂的业务逻辑要说明 +3. **标注新功能**:在注释中标明"新功能:成员X添加" +4. **保持格式一致**:使用统一的注释风格 +5. **及时提交**:完成一个文件就提交一次 + +--- + +## 📊 进度跟踪 + +| 成员 | 进度 | 预计完成时间 | +|------|------|------------| +| 刘洋(A) | 0% | 2小时 | +| 王子函(B) | 0% | 1.5小时 | +| 崔宇航(C) | 0% | 2.5小时 | +| 刘奔(D) | 0% | 4小时 | + +**总进度**: 0% (0/13文件) + +--- + +**文档创建日期**: 2026年5月2日 +**最后更新**: 2026年5月2日 +**目标完成时间**: 4人协作,预计1-2天 \ No newline at end of file diff --git a/doc/用例图.md b/doc/用例图.md deleted file mode 100644 index 4def270..0000000 --- a/doc/用例图.md +++ /dev/null @@ -1,302 +0,0 @@ -# 小米便签应用 - UML 用例图 - -## 一、用例图概述 - -用例图(Use Case Diagram)展示了系统的功能需求和外部参与者之间的交互关系。它从用户的角度描述系统应该做什么,而不关心系统如何实现这些功能。 - ---- - -## 二、参与者分析 - -### 2.1 主要参与者 - -**用户 (User)** -- 便签应用的主要使用者 -- 可以是任何需要记录信息的用户 -- 通过手机界面与应用交互 - -### 2.2 参与者特征 - -- **类型**: 人类用户 -- **交互方式**: 触摸屏幕操作 -- **使用场景**: 日常记录、备忘、灵感捕捉 - ---- - -## 三、用例详细描述 - -### 用例 1: 查看便签列表 - -**用例名称**: 查看便签列表 - -**参与者**: 用户 - -**前置条件**: -- 应用已启动 -- 数据库中存在便签(可选) - -**基本流程**: -1. 用户打开应用 -2. 系统显示便签列表页面 -3. 系统按更新时间倒序显示所有便签 -4. 每个便签显示标题、内容预览和更新时间 - -**后置条件**: -- 用户可以看到所有便签 - -**扩展流程**: -- 3a. 如果没有便签 - - 系统显示空状态提示 - - 提示用户点击添加按钮 - -**业务规则**: -- 便签按更新时间降序排列 -- 最新更新的便签显示在最上面 - ---- - -### 用例 2: 创建新便签 - -**用例名称**: 创建新便签 - -**参与者**: 用户 - -**前置条件**: -- 用户在便签列表页面 - -**基本流程**: -1. 用户点击右下角浮动操作按钮(FAB) -2. 系统打开便签编辑页面 -3. 用户输入标题 -4. 用户输入内容 -5. 用户点击保存按钮 -6. 系统保存便签到数据库 -7. 系统返回便签列表页面 -8. 列表自动刷新显示新便签 - -**后置条件**: -- 新便签已保存到数据库 -- 列表中显示新创建的便签 - -**扩展流程**: -- 3a. 用户未输入标题 - - 系统使用"无标题"作为默认标题 -- 5a. 用户点击返回按钮 - - 系统询问是否保存 - - 用户确认则保存,否则放弃 - -**业务规则**: -- 标题和内容可以为空 -- 自动记录创建时间和更新时间 - ---- - -### 用例 3: 编辑便签 - -**用例名称**: 编辑便签 - -**参与者**: 用户 - -**前置条件**: -- 便签列表中存在至少一个便签 - -**基本流程**: -1. 用户在列表中点击要编辑的便签 -2. 系统打开编辑页面并加载便签数据 -3. 用户修改标题或内容 -4. 用户点击保存按钮 -5. 系统更新数据库中的便签 -6. 系统返回便签列表页面 -7. 列表自动刷新显示更新后的便签 - -**后置条件**: -- 便签已更新 -- 更新时间已刷新 - -**扩展流程**: -- 4a. 用户点击返回按钮 - - 系统自动保存修改 - - 返回列表页面 - -**业务规则**: -- 修改后自动更新更新时间字段 -- 列表按更新时间重新排序 - ---- - -### 用例 4: 删除便签 - -**用例名称**: 删除便签 - -**参与者**: 用户 - -**前置条件**: -- 便签列表中存在至少一个便签 - -**基本流程**: -1. 用户点击便签卡片上的删除按钮 -2. 系统从数据库中删除该便签 -3. 系统刷新列表 -4. 被删除的便签从列表中消失 - -**后置条件**: -- 便签已从数据库删除 -- 列表不再显示该便签 - -**扩展流程**: -- 1a. 用户误触删除按钮 - - 可以提供撤销功能(未来扩展) - -**业务规则**: -- 删除操作不可恢复(当前版本) -- 删除后列表自动重新排序 - ---- - -## 四、用例关系 - -### 4.1 包含关系(include) - -``` -创建新便签 --> 保存便签 -编辑便签 --> 保存便签 -``` - -"保存便签"是一个被多个用例包含的公共功能。 - -### 4.2 扩展关系(extend) - -``` -空状态提示 <.. 查看便签列表 -``` - -当没有便签时,"空状态提示"扩展了"查看便签列表"用例。 - ---- - -## 五、UML 用例图(Mermaid 格式) - -```mermaid -usecaseDiagram - actor "用户" as User - - package "小米便签系统" { - usecase "查看便签列表" as UC1 - usecase "创建新便签" as UC2 - usecase "编辑便签" as UC3 - usecase "删除便签" as UC4 - usecase "保存便签" as UC5 - usecase "显示空状态" as UC6 - } - - User --> UC1 - User --> UC2 - User --> UC3 - User --> UC4 - - UC2 ..> UC5 : include - UC3 ..> UC5 : include - - UC6 .> UC1 : extend -``` - ---- - -## 六、用例场景 - -### 场景 1: 首次使用应用 - -1. 用户首次打开应用 -2. 系统显示空状态:"暂无便签,点击右下角按钮添加新便签" -3. 用户点击 FAB 按钮 -4. 用户创建第一条便签 -5. 系统返回列表并显示新便签 - -### 场景 2: 日常使用 - -1. 用户打开应用查看便签列表 -2. 用户点击某条便签进行编辑 -3. 用户修改内容后保存 -4. 该便签自动排序到列表顶部(因为更新时间最新) - -### 场景 3: 清理便签 - -1. 用户查看列表找到过时的便签 -2. 用户点击删除按钮 -3. 便签被删除,列表自动刷新 - ---- - -## 七、非功能需求 - -### 7.1 性能需求 - -- 应用启动时间 < 2 秒 -- 列表滚动帧率 ≥ 60 FPS -- 数据库操作响应时间 < 100ms - -### 7.2 可用性需求 - -- 界面简洁直观 -- 操作步骤最少化 -- 提供即时反馈 - -### 7.3 可靠性需求 - -- 数据持久化存储 -- 应用崩溃后数据不丢失 -- 支持后台运行 - -### 7.4 兼容性需求 - -- 最低支持 Android 7.0 (API 24) -- 支持亮色和暗色主题 -- 适配不同屏幕尺寸 - ---- - -## 八、未来扩展用例 - -### 8.1 搜索便签 - -- 用户可以输入关键词搜索便签 -- 支持标题和内容全文搜索 - -### 8.2 分类管理 - -- 用户可以创建分类标签 -- 便签可以关联到不同分类 - -### 8.3 数据同步 - -- 支持云端备份和同步 -- 多设备数据一致性 - -### 8.4 分享便签 - -- 将便签内容分享给其他应用 -- 生成图片或文本分享 - -### 8.5 提醒功能 - -- 为便签设置提醒时间 -- 到期推送通知 - ---- - -## 九、总结 - -本用例图完整描述了小米便签应用的核心功能: - -✅ **4 个核心用例**: 查看、创建、编辑、删除 -✅ **1 个参与者**: 用户 -✅ **清晰的关系**: include 和 extend 关系 -✅ **详细的流程**: 基本流程和扩展流程 -✅ **业务规则**: 明确的约束条件 - -用例图为后续的系统设计和实现提供了清晰的功能需求指导。 - ---- - -**文档版本**: v1.0 -**创建日期**: 2026年4月24日 diff --git a/doc/类图.md b/doc/类图.md deleted file mode 100644 index ce09636..0000000 --- a/doc/类图.md +++ /dev/null @@ -1,677 +0,0 @@ -# 小米便签应用 - UML 类图 - -## 一、类图概述 - -类图(Class Diagram)是软件工程中用于描述系统结构的静态图。它展示了系统中的类、接口以及它们之间的关系。类图是面向对象建模的核心工具。 - ---- - -## 二、系统架构分层 - -``` -┌────────────────────────────────────────┐ -│ Presentation Layer │ -│ ┌────────────┐ ┌──────────────┐ │ -│ │NoteList │ │NoteEditor │ │ -│ │Screen │ │Screen │ │ -│ └────────────┘ └──────────────┘ │ -│ ┌────────────┐ │ -│ │NoteItem │ │ -│ └────────────┘ │ -└──────────────┬─────────────────────────┘ - │ -┌──────────────▼─────────────────────────┐ -│ ViewModel Layer │ -│ ┌──────────────────────────┐ │ -│ │ NoteViewModel │ │ -│ └──────────────────────────┘ │ -└──────────────┬─────────────────────────┘ - │ -┌──────────────▼─────────────────────────┐ -│ Data Layer │ -│ ┌────────┐ ┌───────┐ ┌──────────┐ │ -│ │ Note │ │NoteDao│ │NoteDatabase│ │ -│ └────────┘ └───────┘ └──────────┘ │ -│ ┌──────────────────────────┐ │ -│ │ NoteRepository │ │ -│ └──────────────────────────┘ │ -└──────────────────────────────────────┘ -``` - ---- - -## 三、类详细说明 - -### 3.1 实体类 - -#### Note - -**职责**: 便签数据模型,映射数据库表 - -**属性**: -``` -- id: Int (主键,自增) -- title: String (便签标题) -- content: String (便签内容) -- createTime: Long (创建时间戳) -- updateTime: Long (更新时间戳) -``` - -**方法**: -``` -+ Note(id, title, content, createTime, updateTime) -+ componentN(): 数据类自动生成 -+ copy(): 数据类自动生成 -+ equals(): 数据类自动生成 -+ hashCode(): 数据类自动生成 -+ toString(): 数据类自动生成 -``` - -**注解**: -- `@Entity(tableName = "notes")` -- `@PrimaryKey(autoGenerate = true)` - -**设计模式**: 数据类(Data Class) - ---- - -### 3.2 数据访问层 - -#### NoteDao - -**职责**: 定义数据库操作的接口 - -**方法**: -``` -+ getAllNotes(): Flow> -+ getNoteById(noteId: Int): Flow -+ insertNote(note: Note): Long -+ updateNote(note: Note): Unit -+ deleteNote(note: Note): Unit -+ deleteNoteById(noteId: Int): Unit -+ deleteAllNotes(): Unit -``` - -**注解**: -- `@Dao` -- `@Query` -- `@Insert` -- `@Update` -- `@Delete` - -**设计模式**: 数据访问对象模式(DAO Pattern) - ---- - -### 3.3 数据库层 - -#### NoteDatabase - -**职责**: 管理 SQLite 数据库实例 - -**属性**: -``` -# INSTANCE: NoteDatabase? (单例实例) -``` - -**方法**: -``` -+ abstract noteDao(): NoteDao -+ static getDatabase(context: Context): NoteDatabase -``` - -**注解**: -- `@Database(entities = [Note::class], version = 1)` - -**设计模式**: -- 单例模式(Singleton) -- 抽象工厂模式(Abstract Factory) - -**线程安全**: -- `@Volatile` 保证可见性 -- `synchronized` 保证原子性 - ---- - -### 3.4 仓库层 - -#### NoteRepository - -**职责**: 封装数据操作,提供统一的数据访问接口 - -**属性**: -``` -- noteDao: NoteDao (数据访问对象) -``` - -**方法**: -``` -+ getAllNotes(): Flow> -+ getNoteById(noteId: Int): Flow -+ createNote(note: Note): Long -+ updateNote(note: Note): Unit -+ deleteNote(note: Note): Unit -+ deleteNoteById(noteId: Int): Unit -+ deleteAllNotes(): Unit -``` - -**设计模式**: 仓库模式(Repository Pattern) - ---- - -### 3.5 ViewModel 层 - -#### NoteViewModel - -**职责**: 管理 UI 状态和业务逻辑 - -**属性**: -``` -- repository: NoteRepository (数据仓库) -- _allNotes: MutableStateFlow> (便签列表) -- allNotes: StateFlow> (公开只读) -- _currentNote: MutableStateFlow (当前便签) -- currentNote: StateFlow (公开只读) -- _isLoading: MutableStateFlow (加载状态) -- isLoading: StateFlow (公开只读) -``` - -**方法**: -``` -+ NoteViewModel(application: Application) -- observeNotes(): Unit -+ loadNote(noteId: Int): Unit -+ createNote(title: String, content: String): Unit -+ updateNote(note: Note): Unit -+ saveNote(title: String, content: String, noteId: Int?): Unit -+ deleteNote(note: Note): Unit -+ deleteNoteById(noteId: Int): Unit -+ clearCurrentNote(): Unit -``` - -**继承**: -- 继承自 `AndroidViewModel` - -**设计模式**: -- MVVM 模式 -- 观察者模式(通过 Flow) - ---- - -### 3.6 UI 层 - -#### MainActivity - -**职责**: 应用入口,初始化 Compose UI 和导航 - -**属性**: -``` -- viewModel: NoteViewModel (by viewModels) -``` - -**方法**: -``` -+ onCreate(savedInstanceState: Bundle?): Unit -``` - -**继承**: -- 继承自 `ComponentActivity` - ---- - -#### NoteListScreen (Composable) - -**职责**: 显示便签列表 - -**参数**: -``` -- navController: NavController -- viewModel: NoteViewModel -``` - -**内部组件**: -- TopAppBar -- LazyColumn -- FloatingActionButton -- EmptyState - ---- - -#### NoteEditorScreen (Composable) - -**职责**: 编辑或创建便签 - -**参数**: -``` -- navController: NavController -- viewModel: NoteViewModel -- noteId: Int -``` - -**内部状态**: -- title: String (remember) -- content: String (remember) - -**内部组件**: -- TopAppBar -- OutlinedTextField (标题) -- OutlinedTextField (内容) - ---- - -#### NoteItem (Composable) - -**职责**: 显示单个便签卡片 - -**参数**: -``` -- note: Note -- onClick: () -> Unit -- onDelete: () -> Unit -``` - -**内部组件**: -- Card -- Text (标题) -- Text (内容) -- Text (时间) -- IconButton (删除) - ---- - -### 3.7 主题配置 - -#### Color - -**定义的颜色常量**: -``` -+ XiaomiOrange: Color -+ XiaomiOrangeDark: Color -+ BackgroundLight: Color -+ BackgroundDark: Color -+ CardBackgroundLight: Color -+ CardBackgroundDark: Color -+ TextPrimaryLight: Color -+ TextPrimaryDark: Color -+ TextSecondaryLight: Color -+ TextSecondaryDark: Color -+ DividerLight: Color -+ DividerDark: Color -+ DeleteRed: Color -+ DeleteRedDark: Color -+ NoteColors: List -``` - ---- - -#### Theme - -**方法**: -``` -+ XiaomiNoteTheme( - darkTheme: Boolean, - dynamicColor: Boolean, - content: @Composable () -> Unit - ): Unit -``` - -**内部配置**: -- LightColorScheme -- DarkColorScheme - ---- - -#### Type - -**定义的排版样式**: -``` -+ Typography: Typography - - headlineLarge - - headlineMedium - - titleLarge - - bodyLarge - - bodyMedium - - bodySmall - - labelLarge - - labelSmall -``` - ---- - -## 四、类关系 - -### 4.1 关联关系(Association) - -``` -MainActivity "1" --> "1" NoteViewModel : 拥有 -NoteViewModel "1" --> "1" NoteRepository : 使用 -NoteRepository "1" --> "1" NoteDao : 使用 -NoteDatabase "1" --> "*" NoteDao : 创建 -``` - -### 4.2 依赖关系(Dependency) - -``` -NoteListScreen ..> NoteViewModel : 观察状态 -NoteListScreen ..> NoteItem : 使用 -NoteEditorScreen ..> NoteViewModel : 调用方法 -NoteDao ..> Note : 操作 -NoteRepository ..> Note : 操作 -``` - -### 4.3 继承关系(Inheritance) - -``` -NoteViewModel --|> AndroidViewModel -MainActivity --|> ComponentActivity -``` - -### 4.4 实现关系(Realization) - -``` -NoteDao ..|> DAO接口 -``` - ---- - -## 五、UML 类图(Mermaid 格式) - -```mermaid -classDiagram - %% 实体类 - class Note { - <> - -id: Int - -title: String - -content: String - -createTime: Long - -updateTime: Long - +Note() - } - - %% 数据访问层 - class NoteDao { - <> - +getAllNotes() Flow~List~Note~~ - +getNoteById(noteId: Int) Flow~Note?~ - +insertNote(note: Note) Long - +updateNote(note: Note) void - +deleteNote(note: Note) void - +deleteNoteById(noteId: Int) void - +deleteAllNotes() void - } - - %% 数据库层 - class NoteDatabase { - <> - -INSTANCE: NoteDatabase? - +noteDao() NoteDao - +getDatabase(context: Context) NoteDatabase - } - - %% 仓库层 - class NoteRepository { - -noteDao: NoteDao - +getAllNotes() Flow~List~Note~~ - +getNoteById(noteId: Int) Flow~Note?~ - +createNote(note: Note) Long - +updateNote(note: Note) void - +deleteNote(note: Note) void - +deleteNoteById(noteId: Int) void - +deleteAllNotes() void - } - - %% ViewModel层 - class NoteViewModel { - -repository: NoteRepository - -_allNotes: MutableStateFlow~List~Note~~ - +allNotes: StateFlow~List~Note~~ - -_currentNote: MutableStateFlow~Note?~ - +currentNote: StateFlow~Note?~ - +NoteViewModel(application: Application) - -observeNotes() void - +loadNote(noteId: Int) void - +saveNote(title: String, content: String, noteId: Int?) void - +deleteNote(note: Note) void - +deleteNoteById(noteId: Int) void - +clearCurrentNote() void - } - - %% UI层 - class MainActivity { - -viewModel: NoteViewModel - +onCreate(savedInstanceState: Bundle?) void - } - - class NoteListScreen { - <> - +NoteListScreen(navController, viewModel) void - } - - class NoteEditorScreen { - <> - +NoteEditorScreen(navController, viewModel, noteId) void - } - - class NoteItem { - <> - +NoteItem(note, onClick, onDelete) void - } - - %% 关系 - NoteViewModel --|> AndroidViewModel - MainActivity --|> ComponentActivity - - MainActivity "1" --> "1" NoteViewModel : 拥有 - NoteViewModel "1" --> "1" NoteRepository : 使用 - NoteRepository "1" --> "1" NoteDao : 使用 - NoteDatabase "1" --> "1" NoteDao : 创建 - NoteDao ..> Note : 操作 - NoteRepository ..> Note : 操作 - - NoteListScreen ..> NoteViewModel : 观察 - NoteListScreen ..> NoteItem : 使用 - NoteEditorScreen ..> NoteViewModel : 调用 -``` - ---- - -## 六、设计模式应用 - -### 6.1 MVVM 模式 - -**实现**: -- **Model**: Note, NoteDao, NoteDatabase, NoteRepository -- **View**: MainActivity, NoteListScreen, NoteEditorScreen, NoteItem -- **ViewModel**: NoteViewModel - -**优势**: -- 关注点分离 -- 可测试性强 -- 生命周期感知 - -### 6.2 Repository 模式 - -**实现**: NoteRepository 封装所有数据操作 - -**优势**: -- 统一数据访问接口 -- 便于切换数据源 -- 业务逻辑与数据访问分离 - -### 6.3 单例模式 - -**实现**: NoteDatabase 使用双重检查锁定 - -**优势**: -- 全局唯一数据库实例 -- 线程安全 -- 延迟初始化 - -### 6.4 观察者模式 - -**实现**: Flow 响应式数据流 - -**优势**: -- 自动更新 UI -- 解耦数据源和观察者 -- 支持背压 - -### 6.5 工厂模式 - -**实现**: NoteDatabase.getDatabase() 创建数据库实例 - -**优势**: -- 封装创建逻辑 -- 控制实例化过程 - ---- - -## 七、数据流分析 - -### 7.1 数据读取流程 - -``` -用户打开应用 - ↓ -MainActivity 初始化 - ↓ -创建 NoteViewModel - ↓ -初始化 NoteRepository - ↓ -获取 NoteDao - ↓ -查询数据库 (Flow) - ↓ -更新 StateFlow - ↓ -UI 自动刷新 (collectAsState) -``` - -### 7.2 数据写入流程 - -``` -用户点击保存 - ↓ -NoteEditorScreen 调用 ViewModel - ↓ -NoteViewModel.saveNote() - ↓ -NoteRepository.createNote() / updateNote() - ↓ -NoteDao.insertNote() / updateNote() - ↓ -写入 SQLite 数据库 - ↓ -Flow 自动发射新数据 - ↓ -StateFlow 更新 - ↓ -UI 自动刷新 -``` - ---- - -## 八、关键设计决策 - -### 8.1 为什么使用 Flow 而不是 LiveData? - -**选择 Flow 的原因**: -1. 更强大的操作符 -2. 更好的协程集成 -3. 支持冷流和热流 -4. 更灵活的线程控制 - -### 8.2 为什么使用 StateFlow 而不是 MutableStateFlow 公开? - -**封装原则**: -- 内部使用 MutableStateFlow 可变 -- 外部暴露 StateFlow 只读 -- 防止外部意外修改状态 - -### 8.3 为什么使用 Repository 模式? - -**优势**: -- 单一职责原则 -- 便于添加缓存层 -- 易于单元测试 -- 支持多数据源扩展 - ---- - -## 九、扩展性设计 - -### 9.1 添加搜索功能 - -**扩展点**: -```kotlin -// 在 NoteDao 中添加 -@Query("SELECT * FROM notes WHERE title LIKE :query OR content LIKE :query") -fun searchNotes(query: String): Flow> - -// 在 NoteRepository 中添加 -fun searchNotes(query: String): Flow> - -// 在 NoteViewModel 中添加 -fun searchNotes(query: String) -``` - -### 9.2 添加分类功能 - -**扩展点**: -```kotlin -// 扩展 Note 实体 -data class Note( - val id: Int = 0, - val title: String = "", - val content: String = "", - val categoryId: Int? = null, // 新增 - val createTime: Long = System.currentTimeMillis(), - val updateTime: Long = System.currentTimeMillis() -) - -// 创建 Category 实体 -@Entity(tableName = "categories") -data class Category( - @PrimaryKey(autoGenerate = true) - val id: Int = 0, - val name: String -) -``` - -### 9.3 添加云同步 - -**扩展点**: -```kotlin -// 创建远程数据源 -class RemoteNoteDataSource { - suspend fun syncNotes(notes: List) - suspend fun fetchNotes(): List -} - -// 扩展 Repository -class NoteRepository( - private val localDao: NoteDao, - private val remoteDataSource: RemoteNoteDataSource -) -``` - ---- - -## 十、总结 - -本类图展示了小米便签应用的完整架构设计: - -✅ **清晰的分层**: Presentation、ViewModel、Data 三层 -✅ **合理的职责划分**: 每个类都有明确的职责 -✅ **优秀的设计模式**: MVVM、Repository、Singleton、Observer -✅ **高内聚低耦合**: 类之间关系清晰,依赖合理 -✅ **良好的扩展性**: 易于添加新功能 - -**架构评级**: ⭐⭐⭐⭐⭐ 优秀 - ---- - -**文档版本**: v1.0 -**创建日期**: 2026年4月24日 diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..a46f2ed --- /dev/null +++ b/src/README.md @@ -0,0 +1,29 @@ +# 项目源代码目录 + +此目录包含小米便签应用的核心源代码,按照标准Java包结构组织: + +- `com/example/myapplication/` - 主包 +- `com/example/myapplication/data/` - 数据层(Room实体、DAO、数据库、仓库) +- `com/example/myapplication/ui/` - UI层(Compose界面组件) +- `com/example/myapplication/viewmodel/` - ViewModel层(业务逻辑和状态管理) + +## 包含的文件 + +### 主入口 +- `MainActivity.kt` - 应用主活动 + +### 数据层 +- `data/Note.kt` - 便签数据实体类 +- `data/NoteDao.kt` - 数据访问对象接口 +- `data/NoteDatabase.kt` - Room数据库抽象类 +- `data/NoteRepository.kt` - 数据仓库类 + +### UI层 +- `ui/NoteListScreen.kt` - 便签列表界面 +- `ui/NoteEditorScreen.kt` - 便签编辑界面 +- `ui/NoteItem.kt` - 便签列表项组件 + +### ViewModel层 +- `viewmodel/NoteViewModel.kt` - 便签ViewModel类 + +此目录结构便于在非Android开发环境中查看和分析项目架构。 \ No newline at end of file diff --git a/src/com/example/myapplication/MainActivity.kt b/src/com/example/myapplication/MainActivity.kt new file mode 100644 index 0000000..1c46948 --- /dev/null +++ b/src/com/example/myapplication/MainActivity.kt @@ -0,0 +1,76 @@ +package com.example.myapplication + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.example.myapplication.ui.NoteEditorScreen +import com.example.myapplication.ui.NoteListScreen +import com.example.myapplication.ui.theme.XiaomiNoteTheme +import com.example.myapplication.viewmodel.NoteViewModel + +/** + * 小米便签主活动 + * + * 应用入口点,负责初始化 Compose UI 和导航 + * 使用 MVVM 架构,通过 ViewModel 管理业务逻辑 + */ +class MainActivity : ComponentActivity() { + + // ViewModel 实例,由 Android 自动管理生命周期 + private val viewModel: NoteViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // 使用 Compose 设置 UI + setContent { + XiaomiNoteTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + // 导航宿主 + val navController = rememberNavController() + + NavHost( + navController = navController, + startDestination = "list" + ) { + // 列表页面 + composable("list") { + NoteListScreen( + navController = navController, + viewModel = viewModel + ) + } + + // 编辑页面 + composable( + route = "editor/{noteId}", + arguments = listOf( + navArgument("noteId") { type = NavType.IntType } + ) + ) { backStackEntry -> + val noteId = backStackEntry.arguments?.getInt("noteId") ?: 0 + NoteEditorScreen( + navController = navController, + viewModel = viewModel, + noteId = noteId + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/com/example/myapplication/data/Note.kt b/src/com/example/myapplication/data/Note.kt new file mode 100644 index 0000000..416751b --- /dev/null +++ b/src/com/example/myapplication/data/Note.kt @@ -0,0 +1,34 @@ +package com.example.myapplication.data + +import androidx.room.Entity +import androidx.room.PrimaryKey + +/** + * 便签数据实体类 + * + * 使用 Room 的 @Entity 注解定义数据库表结构 + * 存储便签的标题、内容、创建时间和更新时间 + * + * @property id 便签的唯一标识符,自增主键 + * @property title 便签标题 + * @property content 便签内容 + * @property createTime 创建时间(毫秒时间戳) + * @property updateTime 最后更新时间(毫秒时间戳) + */ +@Entity(tableName = "notes") +data class Note( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + + /** 便签标题 */ + val title: String = "", + + /** 便签内容 */ + val content: String = "", + + /** 创建时间(毫秒时间戳) */ + val createTime: Long = System.currentTimeMillis(), + + /** 最后更新时间(毫秒时间戳) */ + val updateTime: Long = System.currentTimeMillis() +) diff --git a/src/com/example/myapplication/data/NoteDao.kt b/src/com/example/myapplication/data/NoteDao.kt new file mode 100644 index 0000000..80fc98e --- /dev/null +++ b/src/com/example/myapplication/data/NoteDao.kt @@ -0,0 +1,70 @@ +package com.example.myapplication.data + +import androidx.room.* +import kotlinx.coroutines.flow.Flow + +/** + * 便签数据访问对象(DAO) + * + * 定义所有与数据库交互的方法 + * 使用 Flow 实现响应式数据流,当数据库数据变化时自动更新 + */ +@Dao +interface NoteDao { + + /** + * 查询所有便签,按更新时间倒序排列 + * + * @return 返回 Flow>,当数据库变化时自动发射新数据 + */ + @Query("SELECT * FROM notes ORDER BY updateTime DESC") + fun getAllNotes(): Flow> + + /** + * 根据 ID 查询单个便签 + * + * @param noteId 便签 ID + * @return 返回 Flow,当该便签数据变化时自动更新 + */ + @Query("SELECT * FROM notes WHERE id = :noteId") + fun getNoteById(noteId: Int): Flow + + /** + * 插入新便签 + * + * @param note 要插入的便签对象 + * @return 返回新插入便签的 ID + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertNote(note: Note): Long + + /** + * 更新现有便签 + * + * @param note 要更新的便签对象 + */ + @Update + suspend fun updateNote(note: Note) + + /** + * 删除便签 + * + * @param note 要删除的便签对象 + */ + @Delete + suspend fun deleteNote(note: Note) + + /** + * 根据 ID 删除便签 + * + * @param noteId 要删除的便签 ID + */ + @Query("DELETE FROM notes WHERE id = :noteId") + suspend fun deleteNoteById(noteId: Int) + + /** + * 删除所有便签 + */ + @Query("DELETE FROM notes") + suspend fun deleteAllNotes() +} diff --git a/src/com/example/myapplication/data/NoteDatabase.kt b/src/com/example/myapplication/data/NoteDatabase.kt new file mode 100644 index 0000000..cc657af --- /dev/null +++ b/src/com/example/myapplication/data/NoteDatabase.kt @@ -0,0 +1,56 @@ +package com.example.myapplication.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +/** + * 便签数据库类 + * + * 使用 Room 持久化库管理 SQLite 数据库 + * 采用单例模式确保整个应用只有一个数据库实例 + * + * @property noteDao 提供便签数据访问对象 + */ +@Database( + entities = [Note::class], + version = 1, + exportSchema = false +) +abstract class NoteDatabase : RoomDatabase() { + + /** + * 获取便签数据访问对象 + * + * @return NoteDao 实例,用于执行数据库操作 + */ + abstract fun noteDao(): NoteDao + + companion object { + @Volatile + private var INSTANCE: NoteDatabase? = null + + /** + * 获取数据库单例实例 + * + * 使用双重检查锁定模式确保线程安全 + * + * @param context 应用上下文 + * @return NoteDatabase 单例实例 + */ + fun getDatabase(context: Context): NoteDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + NoteDatabase::class.java, + "note_database" + ) + .fallbackToDestructiveMigration() // 数据库版本变化时销毁重建 + .build() + INSTANCE = instance + instance + } + } + } +} diff --git a/src/com/example/myapplication/data/NoteRepository.kt b/src/com/example/myapplication/data/NoteRepository.kt new file mode 100644 index 0000000..69e25b4 --- /dev/null +++ b/src/com/example/myapplication/data/NoteRepository.kt @@ -0,0 +1,73 @@ +package com.example.myapplication.data + +import kotlinx.coroutines.flow.Flow + +/** + * 便签数据仓库 + * + * 作为数据层的统一入口,封装所有数据操作 + * 遵循单一职责原则,只负责数据管理 + * + * @property noteDao 数据访问对象,执行实际的数据库操作 + */ +class NoteRepository(private val noteDao: NoteDao) { + + /** + * 获取所有便签列表(响应式) + * + * @return Flow> 当数据库变化时自动更新 + */ + fun getAllNotes(): Flow> = noteDao.getAllNotes() + + /** + * 根据 ID 获取单个便签(响应式) + * + * @param noteId 便签 ID + * @return Flow 当该便签变化时自动更新 + */ + fun getNoteById(noteId: Int): Flow = noteDao.getNoteById(noteId) + + /** + * 创建新便签 + * + * @param note 要创建的便签对象 + * @return 新创建便签的 ID + */ + suspend fun createNote(note: Note): Long { + return noteDao.insertNote(note) + } + + /** + * 更新现有便签 + * + * @param note 要更新的便签对象 + */ + suspend fun updateNote(note: Note) { + noteDao.updateNote(note) + } + + /** + * 删除便签 + * + * @param note 要删除的便签对象 + */ + suspend fun deleteNote(note: Note) { + noteDao.deleteNote(note) + } + + /** + * 根据 ID 删除便签 + * + * @param noteId 要删除的便签 ID + */ + suspend fun deleteNoteById(noteId: Int) { + noteDao.deleteNoteById(noteId) + } + + /** + * 删除所有便签 + */ + suspend fun deleteAllNotes() { + noteDao.deleteAllNotes() + } +} diff --git a/src/com/example/myapplication/ui/NoteEditorScreen.kt b/src/com/example/myapplication/ui/NoteEditorScreen.kt new file mode 100644 index 0000000..90aecac --- /dev/null +++ b/src/com/example/myapplication/ui/NoteEditorScreen.kt @@ -0,0 +1,123 @@ +package com.example.myapplication.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import com.example.myapplication.viewmodel.NoteViewModel + +/** + * 便签编辑页面 + * + * 用于创建新便签或编辑现有便签 + * 提供标题和内容输入框,支持保存和返回操作 + * + * @param navController 导航控制器 + * @param viewModel 便签 ViewModel + * @param noteId 便签 ID,0 表示创建新便签 + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NoteEditorScreen( + navController: NavController, + viewModel: NoteViewModel, + noteId: Int +) { + // 收集当前编辑的便签 + val currentNote by viewModel.currentNote.collectAsStateWithLifecycle() + + // 输入框状态 + var title by remember { mutableStateOf("") } + var content by remember { mutableStateOf("") } + + // 如果是编辑现有便签,加载数据 + LaunchedEffect(noteId) { + if (noteId > 0) { + viewModel.loadNote(noteId) + } + } + + // 当加载到便签数据时,填充输入框 + LaunchedEffect(currentNote) { + currentNote?.let { note -> + title = note.title + content = note.content + } + } + + Scaffold( + topBar = { + // 顶部应用栏 + TopAppBar( + title = { Text(if (noteId > 0) "编辑便签" else "新建便签") }, + navigationIcon = { + // 返回按钮 + IconButton(onClick = { navController.popBackStack() }) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "返回" + ) + } + }, + actions = { + // 保存按钮 + IconButton( + onClick = { + viewModel.saveNote(title, content, if (noteId > 0) noteId else null) + navController.popBackStack() + } + ) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = "保存" + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, + actionIconContentColor = MaterialTheme.colorScheme.onPrimary + ) + ) + } + ) { paddingValues -> + // 编辑区域 + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + // 标题输入框 + OutlinedTextField( + value = title, + onValueChange = { title = it }, + label = { Text("标题") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + textStyle = MaterialTheme.typography.headlineMedium + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 内容输入框 + OutlinedTextField( + value = content, + onValueChange = { content = it }, + label = { Text("内容") }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + textStyle = MaterialTheme.typography.bodyLarge, + maxLines = Int.MAX_VALUE + ) + } + } +} diff --git a/src/com/example/myapplication/ui/NoteItem.kt b/src/com/example/myapplication/ui/NoteItem.kt new file mode 100644 index 0000000..d4273a9 --- /dev/null +++ b/src/com/example/myapplication/ui/NoteItem.kt @@ -0,0 +1,110 @@ +package com.example.myapplication.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.example.myapplication.data.Note +import com.example.myapplication.ui.theme.TextSecondaryLight +import java.text.SimpleDateFormat +import java.util.* + +/** + * 便签列表项组件 + * + * 显示单个便签的标题、内容预览和更新时间 + * 支持点击编辑和长按删除操作 + * + * @param note 要显示的便签数据 + * @param onClick 点击便签时的回调 + * @param onDelete 点击删除按钮时的回调 + */ +@Composable +fun NoteItem( + note: Note, + onClick: () -> Unit, + onDelete: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + .clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + // 标题和删除按钮 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // 便签标题 + Text( + text = if (note.title.isNotEmpty()) note.title else "无标题", + style = MaterialTheme.typography.titleLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + + // 删除按钮 + IconButton( + onClick = onDelete, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "删除便签", + tint = MaterialTheme.colorScheme.error + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // 内容预览 + if (note.content.isNotEmpty()) { + Text( + text = note.content, + style = MaterialTheme.typography.bodyMedium, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Spacer(modifier = Modifier.height(8.dp)) + } + + // 更新时间 + Text( + text = formatTime(note.updateTime), + style = MaterialTheme.typography.bodySmall, + color = TextSecondaryLight + ) + } + } +} + +/** + * 格式化时间戳为可读字符串 + * + * @param timestamp 毫秒时间戳 + * @return 格式化后的时间字符串 + */ +private fun formatTime(timestamp: Long): String { + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + return dateFormat.format(Date(timestamp)) +} diff --git a/src/com/example/myapplication/ui/NoteListScreen.kt b/src/com/example/myapplication/ui/NoteListScreen.kt new file mode 100644 index 0000000..e7af245 --- /dev/null +++ b/src/com/example/myapplication/ui/NoteListScreen.kt @@ -0,0 +1,127 @@ +package com.example.myapplication.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.example.myapplication.data.Note +import com.example.myapplication.viewmodel.NoteViewModel + +/** + * 便签列表页面 + * + * 显示所有便签的列表,支持创建新便签 + * 提供空状态提示和浮动操作按钮 + * + * @param navController 导航控制器 + * @param viewModel 便签 ViewModel + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NoteListScreen( + navController: NavController, + viewModel: NoteViewModel +) { + // 收集便签列表状态 + val notes by viewModel.allNotes.collectAsState() + + Scaffold( + topBar = { + // 顶部应用栏 + TopAppBar( + title = { Text("小米便签") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary + ) + ) + }, + floatingActionButton = { + // 浮动操作按钮 - 添加新便签 + FloatingActionButton( + onClick = { + viewModel.clearCurrentNote() + navController.navigate("editor/0") + }, + containerColor = MaterialTheme.colorScheme.primary + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "添加新便签", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + } + ) { paddingValues -> + // 主内容区域 + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + if (notes.isEmpty()) { + // 空状态提示 + EmptyState() + } else { + // 便签列表 + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(vertical = 8.dp) + ) { + items(notes, key = { it.id }) { note -> + NoteItem( + note = note, + onClick = { + navController.navigate("editor/${note.id}") + }, + onDelete = { + viewModel.deleteNote(note) + } + ) + } + } + } + } + } +} + +/** + * 空状态提示组件 + * + * 当没有便签时显示提示信息 + */ +@Composable +private fun EmptyState() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "暂无便签", + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "点击右下角按钮添加新便签", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center + ) + } +} diff --git a/src/com/example/myapplication/viewmodel/NoteViewModel.kt b/src/com/example/myapplication/viewmodel/NoteViewModel.kt new file mode 100644 index 0000000..7e398a0 --- /dev/null +++ b/src/com/example/myapplication/viewmodel/NoteViewModel.kt @@ -0,0 +1,159 @@ +package com.example.myapplication.viewmodel + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.example.myapplication.data.Note +import com.example.myapplication.data.NoteDatabase +import com.example.myapplication.data.NoteRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +/** + * 便签 ViewModel + * + * 负责管理便签相关的业务逻辑和 UI 状态 + * 作为 View 层和 Model 层之间的桥梁 + * + * @param application 应用程序上下文 + */ +class NoteViewModel(application: Application) : AndroidViewModel(application) { + + // 数据仓库实例 + private val repository: NoteRepository + + // 所有便签列表的状态流 + private val _allNotes = MutableStateFlow>(emptyList()) + val allNotes: StateFlow> = _allNotes.asStateFlow() + + // 当前编辑的便签 + private val _currentNote = MutableStateFlow(null) + val currentNote: StateFlow = _currentNote.asStateFlow() + + // 加载状态 + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading.asStateFlow() + + init { + // 初始化数据仓库 + val noteDao = NoteDatabase.getDatabase(application).noteDao() + repository = NoteRepository(noteDao) + + // 开始监听数据库变化 + observeNotes() + } + + /** + * 监听便签数据变化 + * + * 当数据库中的便签数据发生变化时,自动更新 StateFlow + */ + private fun observeNotes() { + viewModelScope.launch { + repository.getAllNotes().collect { notes -> + _allNotes.value = notes + } + } + } + + /** + * 加载指定 ID 的便签 + * + * @param noteId 便签 ID + */ + fun loadNote(noteId: Int) { + viewModelScope.launch { + repository.getNoteById(noteId).collect { note -> + _currentNote.value = note + } + } + } + + /** + * 创建新便签 + * + * @param title 便签标题 + * @param content 便签内容 + */ + fun createNote(title: String, content: String) { + viewModelScope.launch { + val note = Note( + title = title, + content = content, + createTime = System.currentTimeMillis(), + updateTime = System.currentTimeMillis() + ) + repository.createNote(note) + } + } + + /** + * 更新现有便签 + * + * @param note 要更新的便签对象 + */ + fun updateNote(note: Note) { + viewModelScope.launch { + val updatedNote = note.copy(updateTime = System.currentTimeMillis()) + repository.updateNote(updatedNote) + } + } + + /** + * 保存便签(创建或更新) + * + * @param title 便签标题 + * @param content 便签内容 + * @param noteId 如果是编辑现有便签,传入其 ID;否则传 0 或 null + */ + fun saveNote(title: String, content: String, noteId: Int? = null) { + viewModelScope.launch { + _isLoading.value = true + try { + if (noteId != null && noteId > 0) { + // 更新现有便签 + val existingNote = _currentNote.value + if (existingNote != null) { + updateNote(existingNote.copy(title = title, content = content)) + } + } else { + // 创建新便签 + createNote(title, content) + } + } finally { + _isLoading.value = false + } + } + } + + /** + * 删除便签 + * + * @param note 要删除的便签对象 + */ + fun deleteNote(note: Note) { + viewModelScope.launch { + repository.deleteNote(note) + } + } + + /** + * 根据 ID 删除便签 + * + * @param noteId 要删除的便签 ID + */ + fun deleteNoteById(noteId: Int) { + viewModelScope.launch { + repository.deleteNoteById(noteId) + } + } + + /** + * 清空当前编辑的便签 + */ + fun clearCurrentNote() { + _currentNote.value = null + } +}