diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 0000000..c61ea33
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..639900d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/src/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml
new file mode 100644
index 0000000..639900d
--- /dev/null
+++ b/src/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml
new file mode 100644
index 0000000..f669a0e
--- /dev/null
+++ b/src/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/src/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/.gitignore b/src/Notesmaster/.gitignore
index 7f909be..aa724b7 100644
--- a/src/Notesmaster/.gitignore
+++ b/src/Notesmaster/.gitignore
@@ -13,8 +13,3 @@
.externalNativeBuild
.cxx
local.properties
-build.gradle.kts
-gradle.properties
-gradlew
-gradlew.bat
-settings.gradle.kts
\ No newline at end of file
diff --git a/src/Notesmaster/.idea/deploymentTargetSelector.xml b/src/Notesmaster/.idea/deploymentTargetSelector.xml
index b268ef3..0be8744 100644
--- a/src/Notesmaster/.idea/deploymentTargetSelector.xml
+++ b/src/Notesmaster/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/Notesmaster/PROJECT_ANALYSIS_AND_PLAN.md b/src/Notesmaster/PROJECT_ANALYSIS_AND_PLAN.md
new file mode 100644
index 0000000..67a4290
--- /dev/null
+++ b/src/Notesmaster/PROJECT_ANALYSIS_AND_PLAN.md
@@ -0,0 +1,117 @@
+# 小米便签 (MiNotes) 项目深度分析与拓展计划书
+
+## 1. 项目概览与架构分析 (Project Overview & Architecture)
+
+### 1.1 总体架构
+本项目采用了 **混合架构 (Hybrid Architecture)**,处于从传统的 Android 开发模式向现代 MVVM 模式过渡的阶段。
+* **Legacy 部分**: 核心编辑页面 `NoteEditActivity` 及其对应的逻辑类 `WorkingNote` 采用了一种类似于 MVP/MVC 的变体,其中 `WorkingNote` 充当了“上帝类”的角色,承担了数据加载、状态管理、UI 回调等过多职责,导致耦合度较高。
+* **Modern 部分**: 列表页面 `NotesListActivity` 引入了 `ViewModel` (`NotesListViewModel`) 和 `Repository` (`NotesRepository`),利用 `LiveData` 进行数据驱动的 UI 更新,体现了较好的分层设计思想。
+
+### 1.2 数据流向
+* **核心数据源**: `NotesProvider` (继承自 `ContentProvider`) 是应用的数据中枢。它不仅为应用内部提供数据(通过 `ContentResolver`),还设计用于跨进程/跨应用的数据共享。
+* **数据访问**:
+ * **读操作**: 大部分通过 `NotesRepository` -> `ContentResolver` -> `NotesProvider` -> `SQLiteDatabase` 的链路。
+ * **写操作**: 编辑页直接通过 `WorkingNote` 操作 `ContentResolver`,绕过了 Repository 层。
+* **同步机制**: `GTaskManager` 负责与 Google Tasks 的同步,它直接操作本地数据库并进行网络请求。
+
+### 1.3 关键组件
+* **WorkingNote**: 负责单条笔记的生命周期管理(加载、保存、转换模式)。
+* **NotesDatabaseHelper**: 管理 SQLite 数据库创建、升级及复杂的触发器(Trigger)逻辑。
+* **GTaskSyncService**: 后台同步服务。
+
+---
+
+## 2. 现有功能分析 (Feature Analysis)
+
+### 2.1 核心功能
+* **笔记编辑**: 支持普通文本模式和清单模式(Checklist)。
+* **文件夹管理**: 支持多层级文件夹(虽然 UI 上主要体现为单层),包含系统预置文件夹(通话记录、回收站)。
+* **通话记录笔记**: 特色功能,能够根据来电号码自动关联或创建笔记。
+
+### 2.2 辅助功能
+* **桌面小部件 (Widgets)**: 提供 2x2 和 4x4 两种规格,通过 `RemoteViews` 更新。
+* **定时提醒**: 基于 `AlarmManager` 实现。
+* **云同步**: 仅支持 Google Tasks 同步。
+
+---
+
+## 3. 可扩展性与兼容性评估 (Scalability & Compatibility)
+
+### 3.1 可扩展性 (Scalability)
+* **优势**:
+ * **ContentProvider**: 数据结构标准,易于被外部模块访问。
+ * **MVVM 雏形**: 新增模块若遵循 MVVM 模式,将具有良好的测试性和维护性。
+* **劣势 (瓶颈)**:
+ * **数据库耦合**: 大量硬编码的 SQL 语句和触发器逻辑,难以迁移到 Room 等现代 ORM 框架。
+ * **WorkingNote 臃肿**: 修改编辑逻辑牵一发而动全身。
+ * **同步接口缺失**: 缺乏通用的同步接口抽象,难以扩展其他云服务(如 WebDAV)。
+
+### 3.2 兼容性 (Compatibility)
+当前代码库存在显著的兼容性风险,尤其是在 Android 10+ 设备上:
+* **AsyncTask**: `GTaskASyncTask` 使用了已废弃的 API,可能导致内存泄漏或崩溃。
+* **存储权限**: 未适配 Android 10+ 的分区存储 (Scoped Storage),直接文件路径访问将失效。
+* **精确闹钟**: Android 12+ 需要 `SCHEDULE_EXACT_ALARM` 权限。
+* **通知渠道**: Android 8.0+ 必须适配 Notification Channels。
+
+---
+
+## 4. 项目拓展计划 (Extension Plan)
+
+本计划旨在将小米便签打造为一款现代化、高效率、高颜值的笔记应用。
+
+### Phase 1: 现代化重构与基石搭建 (Modernization)
+**目标**: 消除技术债务,确保在 Android 14/15 上稳定运行。
+
+1. **并发模型升级**:
+ * 废弃 `AsyncTask`。
+ * 引入 **Kotlin Coroutines** (协程) 处理异步任务(数据库读写、网络请求)。
+2. **系统适配**:
+ * **权限**: 适配动态权限申请,特别是闹钟和通知权限。
+ * **存储**: 使用 `MediaStore` API 或 SAF (Storage Access Framework) 替代直接文件操作。
+ * **服务**: 将后台同步服务适配为 WorkManager 或 Foreground Service。
+3. **代码解耦**:
+ * 重构 `NoteEditActivity`,引入 `NoteEditViewModel`。
+ * 拆分 `WorkingNote`,将其纯化为数据模型,逻辑移入 ViewModel/UseCase。
+
+### Phase 2: 核心体验升级 (Core Experience)
+**目标**: 提升笔记的编辑与组织能力。
+
+1. **富文本支持 (Markdown)**:
+ * 数据库 `data` 表新增 `mime_type` 类型支持。
+ * 集成 Markdown 解析器(如 Markwon),支持标题、列表、代码块渲染。
+2. **多媒体附件**:
+ * 新增 `attachments` 数据表。
+ * 支持插入图片(压缩存储或引用 URI)、音频文件。
+3. **标签系统 (Tagging)**:
+ * 实现笔记与标签的多对多关系。
+ * UI 上支持按标签筛选、颜色标记。
+
+### Phase 3: 云服务与互联 (Cloud & Connectivity)
+**目标**: 打破数据孤岛,提供数据安全保障。
+
+1. **同步架构抽象**:
+ * 定义 `ISyncProvider` 接口。
+ * 将 `GTaskManager` 降级为一种实现。
+2. **WebDAV 支持**:
+ * 实现通用的 WebDAV 协议,允许用户连接坚果云、Nextcloud、NAS 等私有云。
+3. **本地备份**:
+ * 支持导出为 JSON/Zip 格式。
+ * 实现自动定时本地备份。
+
+### Phase 4: UI/UX 焕新 (Design Refresh)
+**目标**: 拥抱 Material Design 3 (Material You)。
+
+1. **动态取色**: 支持 Android 12+ 的动态主题色。
+2. **深色模式**: 完善的 Dark Mode 资源适配。
+3. **现代交互**:
+ * 列表页支持侧滑删除/归档。
+ * 笔记拖拽排序。
+ * 沉浸式状态栏与导航栏。
+
+## 5. 关键决策与建议
+* **开发语言**: 建议新功能完全使用 **Kotlin** 开发,旧 Java 代码按需迁移。
+* **数据库**: 虽然迁移到 Room 工作量大,但建议在 Phase 2 开始尝试引入 Room,与现有 Helper 共存,逐步替换。
+* **同步策略**: 鉴于网络环境,建议将 **WebDAV** 作为首推的同步方式,Google Tasks 作为可选插件。
+
+---
+*Generated by Trae AI Assistant*
diff --git a/src/Notesmaster/PROJECT_EXPANSION_PLAN.md b/src/Notesmaster/PROJECT_EXPANSION_PLAN.md
new file mode 100644
index 0000000..9944480
--- /dev/null
+++ b/src/Notesmaster/PROJECT_EXPANSION_PLAN.md
@@ -0,0 +1,122 @@
+# 小米便签 (MiNotes) 功能拓展详细规划书 (v3.0 - 全面增强版)
+
+本规划书在 v2.0 离线优先的基础上,进一步汲取了市场主流笔记应用(Notion, Obsidian, Flomo 等)的精华,旨在打造一款**全能型、离线优先、极具差异化**的笔记应用。我们从效率、知识管理、创意、安全四个维度进行了深度发散和规划。
+
+## 1. 效率与工作流 (Productivity & Workflow)
+*定位:让笔记不仅是记录,更是行动的开始。*
+
+### 1.1 专注模式 (Focus Mode / Pomodoro)
+* **功能描述**: 在编辑笔记时,提供一个沉浸式的“专注模式”。开启后,隐藏所有无关 UI(状态栏、工具栏),并可叠加一个倒计时番茄钟。
+* **用户价值**: 帮助用户进入心流状态,适合深度写作、思考或复习。
+* **离线适配**: 纯本地逻辑。
+* **开发难度**: 低。
+
+### 1.2 悬浮窗速记 (Floating Quick Note)
+* **功能描述**: 在其他应用之上显示一个半透明悬浮球。点击后展开一个小窗口,支持快速输入文字、语音或粘贴剪贴板内容,无需切换应用。
+* **用户价值**: 随时随地捕捉灵感,阅读/看视频时的绝佳伴侣。
+* **离线适配**: 本地 WindowManager 实现。
+* **开发难度**: 中(需处理悬浮窗权限适配)。
+
+### 1.3 每日回顾 (Daily Review / Journaling)
+* **功能描述**:
+ * **自动日志**: 每天自动创建一个以“日期”命名的笔记,聚合当天的所有操作(新建笔记、完成待办、通话记录)。
+ * **那年今日**: 首页顶部展示往年今天的笔记回顾。
+* **用户价值**: 自动化的生活记录与复盘,无需手动整理。
+* **开发难度**: 中。
+
+### 1.4 模板中心 (Template Center)
+* **功能描述**: 提供预设模板库(如康奈尔笔记法、SWOT 分析、会议纪要、周报),并允许用户将现有笔记保存为自定义模板。
+* **用户价值**: 快速开始标准化记录,降低启动门槛。
+* **开发难度**: 低。
+
+---
+
+## 2. 知识管理与可视化 (Knowledge & Visualization)
+*定位:将碎片化信息重组为结构化知识。*
+
+### 2.1 看板视图 (Kanban View)
+* **功能描述**: 将文件夹内的笔记或待办事项以卡片形式展示在“未开始”、“进行中”、“已完成”等列中,支持拖拽流转状态。
+* **用户价值**: 轻量级的项目管理工具,直观掌控任务进度。
+* **开发难度**: 高。
+
+### 2.2 日历视图 (Calendar View)
+* **功能描述**: 在日历上以圆点或标题形式展示笔记(按创建/修改时间或提醒时间分布)。支持点击日期直接创建笔记。
+* **用户价值**: 从时间维度回顾历史,规划未来。
+* **开发难度**: 中。
+
+### 2.3 知识图谱 (Knowledge Graph)
+* **功能描述**: 基于双向链接(`[[Link]]`),用节点和连线可视化展示笔记之间的引用关系。支持缩放、拖拽节点,点击节点跳转。
+* **用户价值**: 上帝视角俯瞰知识结构,发现知识盲区与隐性联系。
+* **开发难度**: 极高(需引入图形渲染库如 MPAndroidChart 修改版或自定义 View)。
+
+### 2.4 标签墙与嵌套标签 (Tag Hierarchy)
+* **功能描述**: 支持多级标签系统(如 `#工作/项目A/会议`),并提供一个动态的标签云或标签墙视图进行筛选。
+* **用户价值**: 突破文件夹的单一维度限制,实现多维度灵活管理。
+* **开发难度**: 中。
+
+---
+
+## 3. 创意与多媒体 (Creativity & Media)
+*定位:释放表达欲,记录不设限。*
+
+### 3.1 手写与绘图 (Handwriting & Sketch)
+* **功能描述**: 提供画笔工具,支持在笔记中插入手写区域或涂鸦。支持压感(如果设备支持),支持橡皮擦、套索工具。
+* **用户价值**: 还原纸笔体验,适合绘制草图、数学公式、手写签名。
+* **开发难度**: 高。
+
+### 3.2 语音笔记与时间戳 (Audio Note with Timestamp)
+* **功能描述**: 录音的同时可以打字记录。回放录音时,点击文字可跳转到对应的录音进度(类似 Notability 的功能)。
+* **用户价值**: 会议记录、课堂录音的神器,不再错过任何细节。
+* **开发难度**: 高。
+
+### 3.3 闪念胶囊 (Flash Card / Flomo-like)
+* **功能描述**: 一个类似于聊天界面的输入框,发送即保存为一张卡片。支持随机漫游回顾,支持标签分类。
+* **用户价值**: 极速捕捉转瞬即逝的灵感,无压力记录。
+* **开发难度**: 低。
+
+---
+
+## 4. 隐私与安全 (Privacy & Security)
+*定位:数据主权,绝对安全。*
+
+### 4.1 生物识别锁 (Biometric Lock)
+* **功能描述**: 支持指纹或面部解锁进入应用,或锁定特定私密文件夹/笔记。
+* **用户价值**: 保护日记、账号密码等敏感信息,防止他人窥探。
+* **开发难度**: 低(使用 Android BiometricPrompt API)。
+
+### 4.2 本地加密存储 (Local Encryption)
+* **功能描述**: 使用 AES-256 算法对数据库文件和附件进行加密存储。即使手机丢失,导出文件也无法被暴力破解。
+* **用户价值**: 满足极高安全需求场景(如商业机密、个人隐私)。
+* **开发难度**: 中(需引入 SQLCipher)。
+
+### 4.3 隐身模式 (Stealth Mode)
+* **功能描述**: 在多任务界面模糊显示应用内容;提供一个“伪装密码”,输入后进入一个空的或预设的伪装空间。
+* **用户价值**: 极致的隐私保护。
+* **开发难度**: 中。
+
+---
+
+## 5. 推荐实施路线 (Roadmap v3.0) - 离线优先策略
+
+### Phase 1: 基础体验增强 (Foundation)
+* **目标**: 夯实基础,提升易用性和安全性。
+* **任务**:
+ 1. **回收站** (现有计划)
+ 2. **深色模式** (现有计划)
+ 3. **生物识别锁**: 增加安全感,实现简单,价值高。
+ 4. **模板功能**: 提升记录效率。
+
+### Phase 2: 核心生产力 (Core Productivity)
+* **目标**: 丰富记录形式,提升编辑效率。
+* **任务**:
+ 1. **本地备份与导出** (现有计划)
+ 2. **Markdown 支持** (现有计划)
+ 3. **悬浮窗速记**: 扩展记录场景。
+ 4. **日历视图**: 提供新的时间维度视角。
+
+### Phase 3: 高级与创新 (Innovation)
+* **目标**: 打造差异化壁垒。
+* **任务**:
+ 1. **离线 OCR** (现有计划)
+ 2. **看板视图**: 引入项目管理能力。
+ 3. **双向链接 & 知识图谱**: 打造知识库神器的核心。
diff --git a/src/Notesmaster/app/.project b/src/Notesmaster/app/.project
index 4b50465..33c0cbe 100644
--- a/src/Notesmaster/app/.project
+++ b/src/Notesmaster/app/.project
@@ -5,6 +5,11 @@
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
org.eclipse.buildship.core.gradleprojectbuilder
@@ -12,6 +17,7 @@
+ org.eclipse.jdt.core.javanature
org.eclipse.buildship.core.gradleprojectnature
diff --git a/src/Notesmaster/app/build.gradle.kts b/src/Notesmaster/app/build.gradle.kts
index f0dba31..362d5b4 100644
--- a/src/Notesmaster/app/build.gradle.kts
+++ b/src/Notesmaster/app/build.gradle.kts
@@ -53,6 +53,8 @@ dependencies {
// RecyclerView依赖
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.cursoradapter:cursoradapter:1.0.0")
+ implementation("androidx.preference:preference:1.2.1")
+ implementation("androidx.palette:palette-ktx:1.0.0")
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/capsule/CapsuleService.java b/src/Notesmaster/app/src/main/java/net/micode/notes/capsule/CapsuleService.java
new file mode 100644
index 0000000..d75bb44
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/capsule/CapsuleService.java
@@ -0,0 +1,339 @@
+package net.micode.notes.capsule;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Build;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.os.Handler;
+import android.util.Log;
+import android.view.DragEvent;
+import android.content.ClipData;
+import android.content.ClipDescription;
+
+import net.micode.notes.R;
+import net.micode.notes.model.Note;
+import net.micode.notes.data.Notes;
+
+public class CapsuleService extends Service {
+
+ private static final String TAG = "CapsuleService";
+ private WindowManager mWindowManager;
+ private View mCollapsedView;
+ private View mExpandedView;
+ private WindowManager.LayoutParams mCollapsedParams;
+ private WindowManager.LayoutParams mExpandedParams;
+
+ private Handler mHandler = new Handler();
+ public static String currentSourcePackage = "";
+
+ private static final String CHANNEL_ID = "CapsuleServiceChannel";
+
+ public static final String ACTION_SAVE_SUCCESS = "net.micode.notes.capsule.ACTION_SAVE_SUCCESS";
+
+ private final android.content.BroadcastReceiver mSaveReceiver = new android.content.BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_SAVE_SUCCESS.equals(intent.getAction())) {
+ highlightCapsule();
+ }
+ }
+ };
+
+ public static void setCurrentSourcePackage(String pkg) {
+ currentSourcePackage = pkg;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
+ createNotificationChannel();
+ startForeground(1, createNotification());
+
+ initViews();
+ }
+
+ private void initViews() {
+ // Collapsed View
+ mCollapsedView = LayoutInflater.from(this).inflate(R.layout.layout_capsule_collapsed, null);
+
+ int layoutFlag;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ layoutFlag = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ } else {
+ layoutFlag = WindowManager.LayoutParams.TYPE_PHONE;
+ }
+
+ mCollapsedParams = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ layoutFlag,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+
+ mCollapsedParams.gravity = Gravity.TOP | Gravity.START;
+ mCollapsedParams.x = 0;
+ mCollapsedParams.y = 100;
+
+ // Expanded View
+ mExpandedView = LayoutInflater.from(this).inflate(R.layout.layout_capsule_expanded, null);
+
+ mExpandedParams = new WindowManager.LayoutParams(
+ dp2px(300),
+ dp2px(400),
+ layoutFlag,
+ WindowManager.LayoutParams.FLAG_DIM_BEHIND, // Allow focus for EditText
+ PixelFormat.TRANSLUCENT);
+ mExpandedParams.dimAmount = 0.5f;
+ mExpandedParams.gravity = Gravity.CENTER;
+
+ // Setup Listeners
+ setupCollapsedListener();
+ setupExpandedListener();
+
+ // Add Collapsed View initially
+ try {
+ mWindowManager.addView(mCollapsedView, mCollapsedParams);
+ Log.d(TAG, "initViews: Collapsed view added");
+ } catch (Exception e) {
+ Log.e(TAG, "initViews: Failed to add collapsed view", e);
+ }
+ }
+
+ private void setupCollapsedListener() {
+ mCollapsedView.setOnTouchListener(new View.OnTouchListener() {
+ private int initialX;
+ private int initialY;
+ private float initialTouchX;
+ private float initialTouchY;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ initialX = mCollapsedParams.x;
+ initialY = mCollapsedParams.y;
+ initialTouchX = event.getRawX();
+ initialTouchY = event.getRawY();
+ Log.d(TAG, "onTouch: ACTION_DOWN at " + initialTouchX + ", " + initialTouchY);
+ return true;
+ case MotionEvent.ACTION_UP:
+ int Xdiff = (int) (event.getRawX() - initialTouchX);
+ int Ydiff = (int) (event.getRawY() - initialTouchY);
+ Log.d(TAG, "onTouch: ACTION_UP, diff: " + Xdiff + ", " + Ydiff);
+ // If click (small movement)
+ if (Math.abs(Xdiff) < 10 && Math.abs(Ydiff) < 10) {
+ Log.d(TAG, "onTouch: Click detected, showing expanded view");
+ showExpandedView();
+ }
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ mCollapsedParams.x = initialX + (int) (event.getRawX() - initialTouchX);
+ mCollapsedParams.y = initialY + (int) (event.getRawY() - initialTouchY);
+ mWindowManager.updateViewLayout(mCollapsedView, mCollapsedParams);
+ return true;
+ }
+ return false;
+ }
+ });
+
+ mCollapsedView.setOnDragListener((v, event) -> {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ Log.d(TAG, "onDrag: ACTION_DRAG_STARTED");
+ if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) ||
+ event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) {
+ v.setAlpha(1.0f);
+ return true;
+ }
+ return false;
+ case DragEvent.ACTION_DRAG_ENTERED:
+ Log.d(TAG, "onDrag: ACTION_DRAG_ENTERED");
+ v.animate().scaleX(1.2f).scaleY(1.2f).setDuration(200).start();
+ return true;
+ case DragEvent.ACTION_DRAG_EXITED:
+ Log.d(TAG, "onDrag: ACTION_DRAG_EXITED");
+ v.animate().scaleX(1.0f).scaleY(1.0f).setDuration(200).start();
+ return true;
+ case DragEvent.ACTION_DROP:
+ Log.d(TAG, "onDrag: ACTION_DROP");
+ ClipData.Item item = event.getClipData().getItemAt(0);
+ CharSequence text = item.getText();
+ if (text != null) {
+ saveNote(text.toString());
+ }
+ v.animate().scaleX(1.0f).scaleY(1.0f).setDuration(200).start();
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ Log.d(TAG, "onDrag: ACTION_DRAG_ENDED");
+ v.setAlpha(0.8f);
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private void setupExpandedListener() {
+ Button btnCancel = mExpandedView.findViewById(R.id.btn_cancel);
+ Button btnSave = mExpandedView.findViewById(R.id.btn_save);
+ EditText etContent = mExpandedView.findViewById(R.id.et_content);
+
+ btnCancel.setOnClickListener(v -> showCollapsedView());
+
+ btnSave.setOnClickListener(v -> {
+ String content = etContent.getText().toString();
+ if (!content.isEmpty()) {
+ saveNote(content);
+ etContent.setText("");
+ showCollapsedView();
+ }
+ });
+ }
+
+ private void showExpandedView() {
+ if (mCollapsedView.getParent() != null) {
+ mWindowManager.removeView(mCollapsedView);
+ }
+ if (mExpandedView.getParent() == null) {
+ mWindowManager.addView(mExpandedView, mExpandedParams);
+
+ TextView tvSource = mExpandedView.findViewById(R.id.tv_source);
+ if (currentSourcePackage != null && !currentSourcePackage.isEmpty()) {
+ tvSource.setText("Source: " + currentSourcePackage);
+ tvSource.setVisibility(View.VISIBLE);
+ } else {
+ tvSource.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void showCollapsedView() {
+ if (mExpandedView.getParent() != null) {
+ mWindowManager.removeView(mExpandedView);
+ }
+ if (mCollapsedView.getParent() == null) {
+ mWindowManager.addView(mCollapsedView, mCollapsedParams);
+ }
+ }
+
+ private void saveNote(String content) {
+ new Thread(() -> {
+ try {
+ // 1. Create new note in CAPSULE folder
+ long noteId = Note.getNewNoteId(this, Notes.ID_CAPSULE_FOLDER);
+
+ // 2. Create Note object
+ Note note = new Note();
+ note.setNoteValue(Notes.NoteColumns.ID, String.valueOf(noteId));
+ note.setTextData(Notes.DataColumns.CONTENT, content);
+
+ // Generate Summary (First 20 chars or first line)
+ String summary = content.length() > 20 ? content.substring(0, 20) + "..." : content;
+ int firstLineEnd = content.indexOf('\n');
+ if (firstLineEnd > 0 && firstLineEnd < 20) {
+ summary = content.substring(0, firstLineEnd);
+ }
+ note.setNoteValue(Notes.NoteColumns.SNIPPET, summary);
+
+ // Add Source Info if available
+ if (currentSourcePackage != null && !currentSourcePackage.isEmpty()) {
+ note.setTextData(Notes.DataColumns.DATA3, currentSourcePackage);
+ }
+
+ boolean success = note.syncNote(this, noteId);
+
+ mHandler.post(() -> {
+ if (success) {
+ Log.d(TAG, "saveNote: Success");
+ Toast.makeText(this, "Saved to Notes", Toast.LENGTH_SHORT).show();
+ } else {
+ Log.e(TAG, "saveNote: Failed");
+ Toast.makeText(this, "Failed to save", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, "saveNote: Exception", e);
+ mHandler.post(() -> Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show());
+ }
+ }).start();
+ }
+
+ private int dp2px(int dp) {
+ return (int) (dp * getResources().getDisplayMetrics().density);
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel serviceChannel = new NotificationChannel(
+ CHANNEL_ID,
+ "Capsule Service Channel",
+ NotificationManager.IMPORTANCE_DEFAULT
+ );
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ if (manager != null) {
+ manager.createNotificationChannel(serviceChannel);
+ }
+ }
+ }
+
+ private Notification createNotification() {
+ Notification.Builder builder;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ builder = new Notification.Builder(this, CHANNEL_ID);
+ } else {
+ builder = new Notification.Builder(this);
+ }
+
+ return builder.setContentTitle("Global Capsule Running")
+ .setContentText("Tap to configure")
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .build();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ try {
+ unregisterReceiver(mSaveReceiver);
+ } catch (Exception e) {
+ Log.e(TAG, "Receiver not registered", e);
+ }
+ if (mCollapsedView != null && mCollapsedView.getParent() != null) {
+ mWindowManager.removeView(mCollapsedView);
+ }
+ if (mExpandedView != null && mExpandedView.getParent() != null) {
+ mWindowManager.removeView(mExpandedView);
+ }
+ }
+
+ private void highlightCapsule() {
+ if (mCollapsedView != null && mCollapsedView.getParent() != null) {
+ mHandler.post(() -> {
+ mCollapsedView.animate().scaleX(1.5f).scaleY(1.5f).setDuration(200).withEndAction(() -> {
+ mCollapsedView.animate().scaleX(1.0f).scaleY(1.0f).setDuration(200).start();
+ }).start();
+ });
+ }
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/capsule/ClipboardMonitorService.java b/src/Notesmaster/app/src/main/java/net/micode/notes/capsule/ClipboardMonitorService.java
new file mode 100644
index 0000000..f9b93d5
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/capsule/ClipboardMonitorService.java
@@ -0,0 +1,67 @@
+package net.micode.notes.capsule;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Toast;
+
+public class ClipboardMonitorService extends AccessibilityService {
+
+ private ClipboardManager mClipboardManager;
+ private ClipboardManager.OnPrimaryClipChangedListener mClipListener;
+ private long mLastClipTime = 0;
+ private static final long MERGE_THRESHOLD = 2000; // 2 seconds
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ }
+
+ @Override
+ protected void onServiceConnected() {
+ super.onServiceConnected();
+ // Register clipboard listener
+ if (mClipboardManager != null) {
+ mClipListener = () -> {
+ handleClipChanged();
+ };
+ mClipboardManager.addPrimaryClipChangedListener(mClipListener);
+ }
+ }
+
+ private void handleClipChanged() {
+ long now = System.currentTimeMillis();
+ if (now - mLastClipTime < MERGE_THRESHOLD) {
+ // Notify CapsuleService to show "Merge" bubble
+ // For now just show a toast or log
+ // Intent intent = new Intent("net.micode.notes.capsule.ACTION_MERGE_SUGGESTION");
+ // sendBroadcast(intent);
+ }
+ mLastClipTime = now;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ if (event.getPackageName() != null) {
+ // Store current package name in CapsuleService
+ CapsuleService.setCurrentSourcePackage(event.getPackageName().toString());
+ }
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mClipboardManager != null && mClipListener != null) {
+ mClipboardManager.removePrimaryClipChangedListener(mClipListener);
+ }
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleActionActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleActionActivity.java
new file mode 100644
index 0000000..dd47212
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleActionActivity.java
@@ -0,0 +1,72 @@
+package net.micode.notes.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+import android.util.Log;
+import net.micode.notes.data.Notes;
+import net.micode.notes.model.Note;
+import net.micode.notes.capsule.CapsuleService;
+
+public class CapsuleActionActivity extends Activity {
+
+ private static final String TAG = "CapsuleActionActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ CharSequence text = getIntent().getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
+ String sourcePackage = null;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
+ if (getReferrer() != null) {
+ sourcePackage = getReferrer().getAuthority(); // or getHost()
+ }
+ }
+
+ if (text != null) {
+ saveNote(text.toString(), sourcePackage);
+
+ // Notify CapsuleService to animate (if running)
+ Intent intent = new Intent("net.micode.notes.capsule.ACTION_SAVE_SUCCESS");
+ sendBroadcast(intent);
+ }
+
+ finish();
+ }
+
+ private void saveNote(String content, String source) {
+ new Thread(() -> {
+ try {
+ long noteId = Note.getNewNoteId(this, Notes.ID_CAPSULE_FOLDER);
+ Note note = new Note();
+ note.setNoteValue(Notes.NoteColumns.ID, String.valueOf(noteId));
+ note.setTextData(Notes.DataColumns.CONTENT, content);
+
+ String summary = content.length() > 20 ? content.substring(0, 20) + "..." : content;
+ int firstLineEnd = content.indexOf('\n');
+ if (firstLineEnd > 0 && firstLineEnd < 20) {
+ summary = content.substring(0, firstLineEnd);
+ }
+ note.setNoteValue(Notes.NoteColumns.SNIPPET, summary);
+
+ if (source != null) {
+ note.setTextData(Notes.DataColumns.DATA3, source);
+ }
+
+ boolean success = note.syncNote(this, noteId);
+
+ runOnUiThread(() -> {
+ if (success) {
+ Toast.makeText(this, "已保存到胶囊", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }).start();
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListFragment.java
new file mode 100644
index 0000000..0bebe45
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListFragment.java
@@ -0,0 +1,164 @@
+package net.micode.notes.ui;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.model.Note;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+public class CapsuleListFragment extends Fragment {
+
+ private RecyclerView mRecyclerView;
+ private CapsuleAdapter mAdapter;
+ private TextView mEmptyView;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_capsule_list, container, false);
+ mRecyclerView = view.findViewById(R.id.capsule_list);
+ mEmptyView = view.findViewById(R.id.tv_empty);
+
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ mAdapter = new CapsuleAdapter();
+ mRecyclerView.setAdapter(mAdapter);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ loadCapsules();
+ }
+
+ private void loadCapsules() {
+ new Thread(() -> {
+ if (getContext() == null) return;
+
+ // Query notes in CAPSULE folder
+ String selection = Notes.NoteColumns.PARENT_ID + "=?";
+ String[] selectionArgs = new String[]{String.valueOf(Notes.ID_CAPSULE_FOLDER)};
+
+ Cursor cursor = getContext().getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ null,
+ selection,
+ selectionArgs,
+ Notes.NoteColumns.MODIFIED_DATE + " DESC"
+ );
+
+ List items = new ArrayList<>();
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(Notes.NoteColumns.ID));
+ String snippet = cursor.getString(cursor.getColumnIndexOrThrow(Notes.NoteColumns.SNIPPET));
+ long modifiedDate = cursor.getLong(cursor.getColumnIndexOrThrow(Notes.NoteColumns.MODIFIED_DATE));
+
+ // We need to fetch DATA3 (source) which is in DATA table.
+ // For performance, we might do a join or lazy load.
+ // For now, let's just use snippet and date.
+ // To get Source, we really should query DATA table or use a projection if CONTENT_NOTE_URI supports joining.
+ // NotesProvider usually joins. Let's check NoteColumns.
+ // Notes.DataColumns.DATA3 is NOT in NoteColumns.
+
+ items.add(new CapsuleItem(id, snippet, modifiedDate, "Loading source..."));
+ }
+ cursor.close();
+ }
+
+ // Update UI
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> {
+ mAdapter.setItems(items);
+ mEmptyView.setVisibility(items.isEmpty() ? View.VISIBLE : View.GONE);
+ });
+ }
+ }).start();
+ }
+
+ private static class CapsuleItem {
+ long id;
+ String summary;
+ long time;
+ String source;
+
+ public CapsuleItem(long id, String summary, long time, String source) {
+ this.id = id;
+ this.summary = summary;
+ this.time = time;
+ this.source = source;
+ }
+ }
+
+ private class CapsuleAdapter extends RecyclerView.Adapter {
+ private List mItems = new ArrayList<>();
+
+ public void setItems(List items) {
+ mItems = items;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_capsule, parent, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ CapsuleItem item = mItems.get(position);
+ holder.tvSummary.setText(item.summary);
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
+ holder.tvTime.setText(sdf.format(new Date(item.time)));
+
+ if (item.source != null && !item.source.isEmpty()) {
+ holder.tvSource.setText("Source: " + item.source);
+ holder.tvSource.setVisibility(View.VISIBLE);
+ } else {
+ holder.tvSource.setVisibility(View.GONE);
+ }
+
+ holder.itemView.setOnClickListener(v -> {
+ // Open Note Edit
+ // We need to implement this
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItems.size();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ TextView tvSummary, tvTime, tvSource;
+
+ public ViewHolder(@NonNull View itemView) {
+ super(itemView);
+ tvSummary = itemView.findViewById(R.id.tv_summary);
+ tvTime = itemView.findViewById(R.id.tv_time);
+ tvSource = itemView.findViewById(R.id.tv_source);
+ }
+ }
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/FolderAdapter.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/FolderAdapter.java
new file mode 100644
index 0000000..270d51f
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/FolderAdapter.java
@@ -0,0 +1,91 @@
+package net.micode.notes.ui;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.micode.notes.R;
+import net.micode.notes.data.NotesRepository;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FolderAdapter extends RecyclerView.Adapter {
+
+ private Context context;
+ private List folders;
+ private long selectedFolderId = -1;
+ private OnFolderClickListener listener;
+
+ public interface OnFolderClickListener {
+ void onFolderClick(long folderId);
+ }
+
+ public FolderAdapter(Context context) {
+ this.context = context;
+ this.folders = new ArrayList<>();
+ }
+
+ public void setFolders(List folders) {
+ this.folders = folders != null ? folders : new ArrayList<>();
+ notifyDataSetChanged();
+ }
+
+ public void setSelectedFolderId(long folderId) {
+ this.selectedFolderId = folderId;
+ notifyDataSetChanged();
+ }
+
+ public void setOnFolderClickListener(OnFolderClickListener listener) {
+ this.listener = listener;
+ }
+
+ @NonNull
+ @Override
+ public FolderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(context).inflate(R.layout.folder_tab_item, parent, false);
+ return new FolderViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull FolderViewHolder holder, int position) {
+ NotesRepository.NoteInfo folder = folders.get(position);
+ holder.bind(folder);
+ }
+
+ @Override
+ public int getItemCount() {
+ return folders.size();
+ }
+
+ class FolderViewHolder extends RecyclerView.ViewHolder {
+ TextView tvName;
+
+ public FolderViewHolder(View itemView) {
+ super(itemView);
+ tvName = itemView.findViewById(R.id.tv_folder_name);
+ itemView.setOnClickListener(v -> {
+ if (listener != null) {
+ int pos = getAdapterPosition();
+ if (pos != RecyclerView.NO_POSITION) {
+ listener.onFolderClick(folders.get(pos).getId());
+ }
+ }
+ });
+ }
+
+ public void bind(NotesRepository.NoteInfo folder) {
+ String name = folder.snippet; // Folder name is stored in snippet
+ if (name == null || name.isEmpty()) {
+ name = "Folder";
+ }
+ tvName.setText(name);
+ tvName.setSelected(folder.getId() == selectedFolderId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java
new file mode 100644
index 0000000..de8910c
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListFragment.java
@@ -0,0 +1,203 @@
+package net.micode.notes.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import android.app.AlertDialog;
+import android.text.InputType;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import android.preference.PreferenceManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.StaggeredGridLayoutManager;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.NotesRepository;
+import net.micode.notes.databinding.NoteListBinding;
+import net.micode.notes.viewmodel.NotesListViewModel;
+
+public class NotesListFragment extends Fragment implements
+ NoteInfoAdapter.OnNoteItemClickListener,
+ NoteInfoAdapter.OnNoteItemLongClickListener {
+
+ private static final String TAG = "NotesListFragment";
+ private static final String PREF_KEY_IS_STAGGERED = "is_staggered";
+
+ private NotesListViewModel viewModel;
+ private NoteListBinding binding;
+ private NoteInfoAdapter adapter;
+
+ private static final int REQUEST_CODE_OPEN_NODE = 102;
+ private static final int REQUEST_CODE_NEW_NODE = 103;
+ private static final int REQUEST_CODE_VERIFY_PASSWORD_FOR_OPEN = 107;
+
+ private NotesRepository.NoteInfo pendingNote;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ binding = NoteListBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ initViewModel();
+ initViews(view);
+ observeViewModel();
+ }
+
+ private void initViewModel() {
+ NotesRepository repository = new NotesRepository(requireContext().getContentResolver());
+ // Use requireActivity() to share ViewModel with Activity (for Sidebar filtering)
+ viewModel = new ViewModelProvider(requireActivity(),
+ new ViewModelProvider.Factory() {
+ @Override
+ public T create(Class modelClass) {
+ return (T) new NotesListViewModel(repository);
+ }
+ }).get(NotesListViewModel.class);
+ }
+
+ private void initViews(View view) {
+ adapter = new NoteInfoAdapter(requireContext());
+ binding.notesList.setAdapter(adapter);
+
+ // Restore layout preference
+ boolean isStaggered = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .getBoolean(PREF_KEY_IS_STAGGERED, true);
+ setLayoutManager(isStaggered);
+
+ adapter.setOnNoteItemClickListener(this);
+ adapter.setOnNoteItemLongClickListener(this);
+
+ // Fix FAB: Enable creating new notes
+ binding.btnNewNote.setOnClickListener(v -> {
+ Intent intent = new Intent(getActivity(), NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
+ intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, viewModel.getCurrentFolderId());
+ startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
+ });
+ }
+
+ private void setLayoutManager(boolean isStaggered) {
+ if (isStaggered) {
+ StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+ layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
+ binding.notesList.setLayoutManager(layoutManager);
+ } else {
+ binding.notesList.setLayoutManager(new LinearLayoutManager(requireContext()));
+ }
+ }
+
+ public boolean toggleLayout() {
+ boolean isStaggered = binding.notesList.getLayoutManager() instanceof StaggeredGridLayoutManager;
+ boolean newIsStaggered = !isStaggered;
+
+ setLayoutManager(newIsStaggered);
+
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
+ .edit()
+ .putBoolean(PREF_KEY_IS_STAGGERED, newIsStaggered)
+ .apply();
+
+ return newIsStaggered;
+ }
+
+ public boolean isStaggeredLayout() {
+ return binding.notesList.getLayoutManager() instanceof StaggeredGridLayoutManager;
+ }
+
+ private void observeViewModel() {
+ viewModel.getNotesLiveData().observe(getViewLifecycleOwner(), notes -> {
+ adapter.setNotes(notes);
+ });
+
+ viewModel.getIsSelectionMode().observe(getViewLifecycleOwner(), isSelection -> {
+ adapter.setSelectionMode(isSelection);
+ });
+
+ viewModel.getSelectedIdsLiveData().observe(getViewLifecycleOwner(), selectedIds -> {
+ adapter.setSelectedIds(selectedIds);
+ });
+ }
+
+ @Override
+ public void onNoteItemClick(int position, long noteId) {
+ if (Boolean.TRUE.equals(viewModel.getIsSelectionMode().getValue())) {
+ boolean isSelected = viewModel.getSelectedIdsLiveData().getValue() != null &&
+ viewModel.getSelectedIdsLiveData().getValue().contains(noteId);
+ viewModel.toggleNoteSelection(noteId, !isSelected);
+ return;
+ }
+
+ if (viewModel.getNotesLiveData().getValue() != null && position < viewModel.getNotesLiveData().getValue().size()) {
+ NotesRepository.NoteInfo note = viewModel.getNotesLiveData().getValue().get(position);
+ if (note.type == Notes.TYPE_FOLDER) {
+ viewModel.enterFolder(note.getId());
+ } else {
+ if (note.isLocked) {
+ pendingNote = note;
+ Intent intent = new Intent(getActivity(), PasswordActivity.class);
+ intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
+ startActivityForResult(intent, REQUEST_CODE_VERIFY_PASSWORD_FOR_OPEN);
+ } else {
+ openNoteEditor(note);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_CODE_VERIFY_PASSWORD_FOR_OPEN && resultCode == android.app.Activity.RESULT_OK) {
+ if (pendingNote != null) {
+ openNoteEditor(pendingNote);
+ pendingNote = null;
+ }
+ }
+ }
+
+ @Override
+ public void onNoteItemLongClick(int position, long noteId) {
+ if (!Boolean.TRUE.equals(viewModel.getIsSelectionMode().getValue())) {
+ viewModel.setIsSelectionMode(true);
+ viewModel.toggleNoteSelection(noteId, true);
+ } else {
+ boolean isSelected = viewModel.getSelectedIdsLiveData().getValue() != null &&
+ viewModel.getSelectedIdsLiveData().getValue().contains(noteId);
+ viewModel.toggleNoteSelection(noteId, !isSelected);
+ }
+ }
+
+ // Deprecated Context Menu
+ private void showContextMenu(NotesRepository.NoteInfo note) {
+ // ... kept for reference or removed
+ }
+
+ private void openNoteEditor(NotesRepository.NoteInfo note) {
+ Intent intent = new Intent(getActivity(), NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, note.getParentId());
+ intent.putExtra(Intent.EXTRA_UID, note.getId());
+ startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ viewModel.refreshNotes();
+ }
+}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerAdapter.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerAdapter.java
new file mode 100644
index 0000000..5dadec7
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerAdapter.java
@@ -0,0 +1,140 @@
+package net.micode.notes.ui;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.tool.ResourceParser;
+
+public class NotesRecyclerAdapter extends RecyclerView.Adapter {
+
+ private Context mContext;
+ private Cursor mCursor;
+ private OnNoteItemClickListener mListener;
+ private boolean mChoiceMode;
+
+ public interface OnNoteItemClickListener {
+ void onNoteClick(int position, long noteId);
+ boolean onNoteLongClick(int position, long noteId);
+ }
+
+ public NotesRecyclerAdapter(Context context) {
+ mContext = context;
+ }
+
+ public void setOnNoteItemClickListener(OnNoteItemClickListener listener) {
+ mListener = listener;
+ }
+
+ public void swapCursor(Cursor newCursor) {
+ if (mCursor == newCursor) return;
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ mCursor = newCursor;
+ notifyDataSetChanged();
+ }
+
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ @NonNull
+ @Override
+ public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(mContext).inflate(R.layout.note_item, parent, false);
+ return new NoteViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull NoteViewHolder holder, int position) {
+ if (mCursor == null || !mCursor.moveToPosition(position)) {
+ return;
+ }
+ NoteItemData itemData = new NoteItemData(mContext, mCursor);
+ holder.bind(itemData, mChoiceMode, false); // Checked logic omitted for now
+
+ holder.itemView.setOnClickListener(v -> {
+ if (mListener != null) {
+ mListener.onNoteClick(position, itemData.getId());
+ }
+ });
+
+ holder.itemView.setOnLongClickListener(v -> {
+ if (mListener != null) {
+ return mListener.onNoteLongClick(position, itemData.getId());
+ }
+ return false;
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mCursor == null ? 0 : mCursor.getCount();
+ }
+
+ class NoteViewHolder extends RecyclerView.ViewHolder {
+ CardView cardView;
+ TextView title, time, name;
+ ImageView typeIcon, lockIcon, alertIcon;
+ CheckBox checkBox;
+
+ public NoteViewHolder(View itemView) {
+ super(itemView);
+ // Since root is CardView
+ cardView = (CardView) itemView;
+ title = itemView.findViewById(R.id.tv_title);
+ time = itemView.findViewById(R.id.tv_time);
+ name = itemView.findViewById(R.id.tv_name);
+ checkBox = itemView.findViewById(android.R.id.checkbox);
+ typeIcon = itemView.findViewById(R.id.iv_type_icon);
+ lockIcon = itemView.findViewById(R.id.iv_lock_icon);
+ alertIcon = itemView.findViewById(R.id.iv_alert_icon);
+ }
+
+ public void bind(NoteItemData data, boolean choiceMode, boolean checked) {
+ if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
+ checkBox.setVisibility(View.VISIBLE);
+ checkBox.setChecked(checked);
+ } else {
+ checkBox.setVisibility(View.GONE);
+ }
+
+ if (data.getType() == Notes.TYPE_FOLDER) {
+ String snippet = data.getSnippet();
+ if (snippet == null) snippet = "";
+ title.setText(snippet + " (" + data.getNotesCount() + ")");
+ time.setVisibility(View.GONE);
+ typeIcon.setVisibility(View.VISIBLE);
+ typeIcon.setImageResource(R.drawable.ic_folder);
+ cardView.setCardBackgroundColor(mContext.getColor(R.color.bg_white));
+ } else {
+ typeIcon.setVisibility(View.GONE);
+ time.setVisibility(View.VISIBLE);
+ String titleStr = data.getTitle();
+ if (titleStr == null || titleStr.isEmpty()) {
+ titleStr = data.getSnippet();
+ }
+ title.setText(titleStr);
+ time.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
+
+ // Background Color
+ int colorId = data.getBgColorId();
+ int color = ResourceParser.getNoteBgColor(mContext, colorId);
+ cardView.setCardBackgroundColor(color);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java
new file mode 100644
index 0000000..8f9b68c
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsActivity.java
@@ -0,0 +1,30 @@
+package net.micode.notes.ui;
+
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceFragmentCompat;
+import net.micode.notes.R;
+
+public class SettingsActivity extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+ if (savedInstanceState == null) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.settings_container, new SettingsFragment())
+ .commit();
+ }
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle(R.string.menu_settings);
+ }
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ finish();
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListFragment.java
new file mode 100644
index 0000000..2f04d4b
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListFragment.java
@@ -0,0 +1,123 @@
+package net.micode.notes.ui;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.model.Task;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TaskListFragment extends Fragment implements TaskListAdapter.OnTaskItemClickListener {
+
+ private RecyclerView recyclerView;
+ private TaskListAdapter adapter;
+ private FloatingActionButton fab;
+ private static final int REQUEST_EDIT_TASK = 1001;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.activity_task_list, container, false);
+
+ // Hide Toolbar in fragment if Activity has one or Tabs
+ // For now, let's keep it but remove navigation logic or hide it if needed
+ View toolbar = view.findViewById(R.id.toolbar);
+ if (toolbar != null) {
+ // toolbar.setVisibility(View.GONE); // Optional: Hide if using main tabs
+ }
+
+ recyclerView = view.findViewById(R.id.task_list_view);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ adapter = new TaskListAdapter(getContext(), this);
+ recyclerView.setAdapter(adapter);
+
+ fab = view.findViewById(R.id.btn_new_task);
+ fab.setOnClickListener(v -> {
+ Intent intent = new Intent(getActivity(), TaskEditActivity.class);
+ startActivityForResult(intent, REQUEST_EDIT_TASK);
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ loadTasks();
+ }
+
+ private void loadTasks() {
+ new Thread(() -> {
+ if (getContext() == null) return;
+ Cursor cursor = getContext().getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ null,
+ NoteColumns.TYPE + "=?",
+ new String[]{String.valueOf(Notes.TYPE_TASK)},
+ null
+ );
+
+ List tasks = new ArrayList<>();
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ tasks.add(Task.fromCursor(cursor));
+ }
+ cursor.close();
+ }
+
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> adapter.setTasks(tasks));
+ }
+ }).start();
+ }
+
+ @Override
+ public void onItemClick(Task task) {
+ Intent intent = new Intent(getActivity(), TaskEditActivity.class);
+ intent.putExtra(Intent.EXTRA_UID, task.id);
+ startActivityForResult(intent, REQUEST_EDIT_TASK);
+ }
+
+ @Override
+ public void onCheckBoxClick(Task task) {
+ task.status = (task.status == Task.STATUS_ACTIVE) ? Task.STATUS_COMPLETED : Task.STATUS_ACTIVE;
+ if (task.status == Task.STATUS_COMPLETED) {
+ task.finishedTime = System.currentTimeMillis();
+ } else {
+ task.finishedTime = 0;
+ }
+
+ new Thread(() -> {
+ if (getContext() != null) {
+ task.save(getContext());
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> loadTasks());
+ }
+ }
+ }).start();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_EDIT_TASK && resultCode == android.app.Activity.RESULT_OK) {
+ loadTasks();
+ }
+ }
+}
diff --git a/src/Notesmaster/app/src/main/res/color/selector_bottom_nav_color.xml b/src/Notesmaster/app/src/main/res/color/selector_bottom_nav_color.xml
new file mode 100644
index 0000000..3c57e92
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/color/selector_bottom_nav_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/capsule_collapsed_bg.xml b/src/Notesmaster/app/src/main/res/drawable/capsule_collapsed_bg.xml
new file mode 100644
index 0000000..dc7140e
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/capsule_collapsed_bg.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/folder_tab_bg_selector.xml b/src/Notesmaster/app/src/main/res/drawable/folder_tab_bg_selector.xml
new file mode 100644
index 0000000..a180cd1
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/folder_tab_bg_selector.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_checkbox_checked_round.xml b/src/Notesmaster/app/src/main/res/drawable/ic_checkbox_checked_round.xml
new file mode 100644
index 0000000..03f6369
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/ic_checkbox_checked_round.xml
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_checkbox_unchecked_round.xml b/src/Notesmaster/app/src/main/res/drawable/ic_checkbox_unchecked_round.xml
new file mode 100644
index 0000000..0859dbf
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/ic_checkbox_unchecked_round.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_menu_hamburger.xml b/src/Notesmaster/app/src/main/res/drawable/ic_menu_hamburger.xml
new file mode 100644
index 0000000..7ff2549
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/ic_menu_hamburger.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_view_grid.xml b/src/Notesmaster/app/src/main/res/drawable/ic_view_grid.xml
new file mode 100644
index 0000000..00cee0b
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/ic_view_grid.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/ic_view_list.xml b/src/Notesmaster/app/src/main/res/drawable/ic_view_list.xml
new file mode 100644
index 0000000..58b67b3
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/ic_view_list.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/search_bar_bg.xml b/src/Notesmaster/app/src/main/res/drawable/search_bar_bg.xml
new file mode 100644
index 0000000..3feadd2
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/search_bar_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/selector_checkbox_round.xml b/src/Notesmaster/app/src/main/res/drawable/selector_checkbox_round.xml
new file mode 100644
index 0000000..fdd9ce6
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/selector_checkbox_round.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/activity_home.xml b/src/Notesmaster/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..1a3cd5a
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/folder_tab_item.xml b/src/Notesmaster/app/src/main/res/layout/folder_tab_item.xml
new file mode 100644
index 0000000..7645a63
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/folder_tab_item.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/fragment_capsule_list.xml b/src/Notesmaster/app/src/main/res/layout/fragment_capsule_list.xml
new file mode 100644
index 0000000..e7df63f
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/fragment_capsule_list.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Notesmaster/app/src/main/res/layout/item_capsule.xml b/src/Notesmaster/app/src/main/res/layout/item_capsule.xml
new file mode 100644
index 0000000..650e18b
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/item_capsule.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Notesmaster/app/src/main/res/layout/layout_capsule_collapsed.xml b/src/Notesmaster/app/src/main/res/layout/layout_capsule_collapsed.xml
new file mode 100644
index 0000000..db340b7
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/layout_capsule_collapsed.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/layout_capsule_expanded.xml b/src/Notesmaster/app/src/main/res/layout/layout_capsule_expanded.xml
new file mode 100644
index 0000000..62d1115
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/layout_capsule_expanded.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/menu/bottom_nav_menu.xml b/src/Notesmaster/app/src/main/res/menu/bottom_nav_menu.xml
new file mode 100644
index 0000000..8b16237
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/menu/bottom_nav_menu.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/xml/accessibility_service_config.xml b/src/Notesmaster/app/src/main/res/xml/accessibility_service_config.xml
new file mode 100644
index 0000000..76896c3
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/xml/accessibility_service_config.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/src/Notesmaster/gradle/wrapper/gradle-wrapper.properties b/src/Notesmaster/gradle/wrapper/gradle-wrapper.properties
index f5e0540..06f1535 100644
--- a/src/Notesmaster/gradle/wrapper/gradle-wrapper.properties
+++ b/src/Notesmaster/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
#Mon Dec 22 21:06:32 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/src/Notesmaster/log b/src/Notesmaster/log
new file mode 100644
index 0000000..b789757
--- /dev/null
+++ b/src/Notesmaster/log
@@ -0,0 +1,128 @@
+2026-01-28 18:33:05.844 12829-12829 Compatibil...geReporter net.micode.notes D Compat change id reported: 171979766; UID 10145; state: ENABLED
+2026-01-28 18:33:06.272 12829-12829 GraphicsEnvironment net.micode.notes V ANGLE Developer option for 'net.micode.notes' set to: 'default'
+2026-01-28 18:33:06.273 12829-12829 GraphicsEnvironment net.micode.notes V Neither updatable production driver nor prerelease driver is supported.
+2026-01-28 18:33:06.279 12829-12829 NetworkSecurityConfig net.micode.notes D No Network Security Config specified, using platform default
+2026-01-28 18:33:06.282 12829-12829 NetworkSecurityConfig net.micode.notes D No Network Security Config specified, using platform default
+2026-01-28 18:33:06.362 12829-12850 libEGL net.micode.notes D loaded /vendor/lib64/egl/libEGL_emulation.so
+2026-01-28 18:33:06.364 12829-12850 libEGL net.micode.notes D loaded /vendor/lib64/egl/libGLESv1_CM_emulation.so
+2026-01-28 18:33:06.369 12829-12850 libEGL net.micode.notes D loaded /vendor/lib64/egl/libGLESv2_emulation.so
+2026-01-28 18:33:06.420 12829-12829 AppCompatDelegate net.micode.notes D Checking for metadata for AppLocalesMetadataHolderService : Service not found
+2026-01-28 18:33:06.488 12829-12829 et.micode.note net.micode.notes W Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (unsupported, reflection, allowed)
+2026-01-28 18:33:06.489 12829-12829 et.micode.note net.micode.notes W Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (unsupported, reflection, allowed)
+2026-01-28 18:33:06.774 12829-12829 NotesRepository net.micode.notes D NotesRepository initialized
+2026-01-28 18:33:06.797 12829-12829 NotesRepository net.micode.notes D NotesRepository initialized
+2026-01-28 18:33:06.801 12829-12829 NotesListViewModel net.micode.notes D ViewModel created
+2026-01-28 18:33:06.815 12829-12851 FolderListViewModel net.micode.notes D Query executed, cursor: 5
+2026-01-28 18:33:06.816 12829-12851 FolderListViewModel net.micode.notes D Column names: [_id, parent_id, alert_date, bg_color_id, created_date, has_attachment, modified_date, notes_count, snippet, type, widget_id, widget_type, sync_id, local_modified, origin_parent_id, gtask_id, version, top, locked, title, gtask_priority, gtask_due_date, gtask_status, gtask_finished_time]
+2026-01-28 18:33:06.818 12829-12851 FolderListViewModel net.micode.notes D Folder data: id=27, name=操作系统, parentId=0, noteCount=1
+2026-01-28 18:33:06.819 12829-12851 FolderListViewModel net.micode.notes D Folder data: id=19, name=学习, parentId=-4, noteCount=1
+2026-01-28 18:33:06.822 12829-12851 FolderListViewModel net.micode.notes D Folder data: id=16, name=生活, parentId=-4, noteCount=2
+2026-01-28 18:33:06.824 12829-12852 NotesRepository net.micode.notes D Successfully loaded sub-folders for folder: 0
+2026-01-28 18:33:06.825 12829-12851 FolderListViewModel net.micode.notes D Folder data: id=13, name=工作, parentId=-4, noteCount=2
+2026-01-28 18:33:06.827 12829-12851 FolderListViewModel net.micode.notes D Folder data: id=7, name=软件工程, parentId=0, noteCount=3
+2026-01-28 18:33:06.827 12829-12851 FolderListViewModel net.micode.notes D QueryAllFolders returned 5 folders
+2026-01-28 18:33:06.836 12829-12851 FolderListViewModel net.micode.notes D Folder: id=27, name=操作系统, parentId=0
+2026-01-28 18:33:06.838 12829-12852 NotesListViewModel net.micode.notes D Successfully loaded 18 notes
+2026-01-28 18:33:06.838 12829-12852 NotesRepository net.micode.notes D Successfully loaded notes for folder: -10
+2026-01-28 18:33:06.851 12829-12851 FolderListViewModel net.micode.notes D Added root folder: 操作系统
+2026-01-28 18:33:06.851 12829-12851 FolderListViewModel net.micode.notes D Folder: id=19, name=学习, parentId=-4
+2026-01-28 18:33:06.851 12829-12851 FolderListViewModel net.micode.notes D Folder: id=16, name=生活, parentId=-4
+2026-01-28 18:33:06.852 12829-12851 FolderListViewModel net.micode.notes D Folder: id=13, name=工作, parentId=-4
+2026-01-28 18:33:06.852 12829-12851 FolderListViewModel net.micode.notes D Folder: id=7, name=软件工程, parentId=0
+2026-01-28 18:33:06.853 12829-12851 FolderListViewModel net.micode.notes D Added root folder: 软件工程
+2026-01-28 18:33:06.853 12829-12851 FolderListViewModel net.micode.notes D Root folders count: 2
+2026-01-28 18:33:06.854 12829-12851 FolderListViewModel net.micode.notes D Root expanded: false
+2026-01-28 18:33:06.854 12829-12851 FolderListViewModel net.micode.notes D Final folder tree size: 2
+2026-01-28 18:33:06.895 12829-12829 Choreographer net.micode.notes I Skipped 34 frames! The application may be doing too much work on its main thread.
+2026-01-28 18:33:06.909 12829-12841 System net.micode.notes W A resource failed to call close.
+2026-01-28 18:33:06.972 12829-12848 HostConnection net.micode.notes D createUnique: call
+2026-01-28 18:33:07.104 12829-12848 HostConnection net.micode.notes D HostConnection::get() New Host Connection established 0x77af048dc090, tid 12848
+2026-01-28 18:33:07.110 12829-12848 HostConnection net.micode.notes D HostComposition ext ANDROID_EMU_CHECKSUM_HELPER_v1 ANDROID_EMU_native_sync_v2 ANDROID_EMU_native_sync_v3 ANDROID_EMU_native_sync_v4 ANDROID_EMU_dma_v1 ANDROID_EMU_direct_mem ANDROID_EMU_host_composition_v1 ANDROID_EMU_host_composition_v2 ANDROID_EMU_vulkan ANDROID_EMU_deferred_vulkan_commands ANDROID_EMU_vulkan_null_optional_strings ANDROID_EMU_vulkan_create_resources_with_requirements ANDROID_EMU_YUV_Cache ANDROID_EMU_vulkan_ignored_handles ANDROID_EMU_has_shared_slots_host_memory_allocator ANDROID_EMU_vulkan_free_memory_sync ANDROID_EMU_vulkan_shader_float16_int8 ANDROID_EMU_vulkan_async_queue_submit ANDROID_EMU_vulkan_queue_submit_with_commands ANDROID_EMU_sync_buffer_data ANDROID_EMU_vulkan_async_qsri ANDROID_EMU_read_color_buffer_dma ANDROID_EMU_hwc_color_transform GL_OES_EGL_image_external_essl3 GL_OES_vertex_array_object GL_KHR_texture_compression_astc_ldr ANDROID_EMU_host_side_tracing ANDROID_EMU_gles_max_version_3_1
+2026-01-28 18:33:07.126 12829-12848 OpenGLRenderer net.micode.notes W Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...
+2026-01-28 18:33:07.127 12829-12848 OpenGLRenderer net.micode.notes W Failed to initialize 101010-2 format, error = EGL_SUCCESS
+2026-01-28 18:33:07.153 12829-12829 NotesRepository net.micode.notes D NotesRepository initialized
+2026-01-28 18:33:07.196 12829-12852 NotesRepository net.micode.notes D Successfully loaded sub-folders for folder: 0
+2026-01-28 18:33:07.199 12829-12852 NotesListViewModel net.micode.notes D Successfully loaded 18 notes
+2026-01-28 18:33:07.200 12829-12852 NotesRepository net.micode.notes D Successfully loaded notes for folder: -10
+2026-01-28 18:33:07.235 12829-12848 EGL_emulation net.micode.notes D eglCreateContext: 0x77af048dd290: maj 3 min 1 rcv 4
+2026-01-28 18:33:07.492 12829-12848 EGL_emulation net.micode.notes D eglMakeCurrent: 0x77af048dd290: ver 3 1 (tinfo 0x77b11dbe5080) (first time)
+2026-01-28 18:33:07.531 12829-12848 Gralloc4 net.micode.notes I mapper 4.x is not supported
+2026-01-28 18:33:07.534 12829-12848 HostConnection net.micode.notes D createUnique: call
+2026-01-28 18:33:07.534 12829-12848 HostConnection net.micode.notes D HostConnection::get() New Host Connection established 0x77af048dd7d0, tid 12848
+2026-01-28 18:33:07.538 12829-12848 goldfish-address-space net.micode.notes D allocate: Ask for block of size 0x100
+2026-01-28 18:33:07.538 12829-12848 goldfish-address-space net.micode.notes D allocate: ioctl allocate returned offset 0x3edffc000 size 0x2000
+2026-01-28 18:33:07.544 12829-12848 Gralloc4 net.micode.notes W allocator 4.x is not supported
+2026-01-28 18:33:07.658 12829-12848 HostConnection net.micode.notes D HostComposition ext ANDROID_EMU_CHECKSUM_HELPER_v1 ANDROID_EMU_native_sync_v2 ANDROID_EMU_native_sync_v3 ANDROID_EMU_native_sync_v4 ANDROID_EMU_dma_v1 ANDROID_EMU_direct_mem ANDROID_EMU_host_composition_v1 ANDROID_EMU_host_composition_v2 ANDROID_EMU_vulkan ANDROID_EMU_deferred_vulkan_commands ANDROID_EMU_vulkan_null_optional_strings ANDROID_EMU_vulkan_create_resources_with_requirements ANDROID_EMU_YUV_Cache ANDROID_EMU_vulkan_ignored_handles ANDROID_EMU_has_shared_slots_host_memory_allocator ANDROID_EMU_vulkan_free_memory_sync ANDROID_EMU_vulkan_shader_float16_int8 ANDROID_EMU_vulkan_async_queue_submit ANDROID_EMU_vulkan_queue_submit_with_commands ANDROID_EMU_sync_buffer_data ANDROID_EMU_vulkan_async_qsri ANDROID_EMU_read_color_buffer_dma ANDROID_EMU_hwc_color_transform GL_OES_EGL_image_external_essl3 GL_OES_vertex_array_object GL_KHR_texture_compression_astc_ldr ANDROID_EMU_host_side_tracing ANDROID_EMU_gles_max_version_3_1
+2026-01-28 18:33:08.276 12829-12829 Choreographer net.micode.notes I Skipped 81 frames! The application may be doing too much work on its main thread.
+2026-01-28 18:33:08.312 12829-12845 OpenGLRenderer net.micode.notes I Davey! duration=1797ms; Flags=1, FrameTimelineVsyncId=168280, IntendedVsync=13039839683058, Vsync=13040406349702, InputEventId=0, HandleInputStart=13040418543500, AnimationStart=13040418686200, PerformTraversalsStart=13040419832600, DrawStart=13041233560100, FrameDeadline=13039856349724, FrameInterval=13040418003100, FrameStartTime=16666666, SyncQueued=13041257581200, SyncStart=13041428592800, IssueDrawCommandsStart=13041438100800, SwapBuffers=13041789423800, FrameCompleted=13041808230500, DequeueBufferDuration=16100, QueueBufferDuration=880700, GpuCompleted=13041808230500, SwapBuffersCompleted=13041790948300, DisplayPresentTime=0,
+2026-01-28 18:33:08.540 12829-12838 et.micode.note net.micode.notes I JIT allocated 77KB for compiled code of void android.view.View.(android.content.Context, android.util.AttributeSet, int, int)
+2026-01-28 18:33:08.947 12829-12845 OpenGLRenderer net.micode.notes I Davey! duration=1921ms; Flags=0, FrameTimelineVsyncId=168315, IntendedVsync=13040439683034, Vsync=13041789682980, InputEventId=0, HandleInputStart=13041798356600, AnimationStart=13041798370800, PerformTraversalsStart=13041798600200, DrawStart=13041987454800, FrameDeadline=13040473016366, FrameInterval=13041798273300, FrameStartTime=16666666, SyncQueued=13041989887300, SyncStart=13041989992000, IssueDrawCommandsStart=13041990149900, SwapBuffers=13042273806700, FrameCompleted=13042361397200, DequeueBufferDuration=33100, QueueBufferDuration=382800, GpuCompleted=13042361397200, SwapBuffersCompleted=13042313916500, DisplayPresentTime=0,
+2026-01-28 18:33:09.888 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=241.80ms min=31.97ms max=647.35ms count=6
+2026-01-28 18:33:12.865 12829-12858 ProfileInstaller net.micode.notes D Installing profile for net.micode.notes
+2026-01-28 18:33:13.208 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=518.53ms min=61.05ms max=2682.90ms count=6
+2026-01-28 18:33:14.315 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=48.21ms min=2.42ms max=89.51ms count=15
+2026-01-28 18:33:16.533 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=231.10ms min=31.68ms max=1245.57ms count=9
+2026-01-28 18:33:17.613 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=104.82ms min=7.29ms max=229.11ms count=8
+2026-01-28 18:33:18.823 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=302.48ms min=126.71ms max=676.01ms count=4
+2026-01-28 18:33:19.996 12829-12852 NotesListViewModel net.micode.notes D Successfully toggled lock state to true
+2026-01-28 18:33:19.996 12829-12852 NotesRepository net.micode.notes D Successfully updated lock state for 1 notes
+2026-01-28 18:33:19.998 12829-12852 NotesRepository net.micode.notes D Successfully loaded sub-folders for folder: 0
+2026-01-28 18:33:20.001 12829-12852 NotesListViewModel net.micode.notes D Successfully loaded 18 notes
+2026-01-28 18:33:20.002 12829-12852 NotesRepository net.micode.notes D Successfully loaded notes for folder: -10
+2026-01-28 18:33:20.008 12829-12852 NotesRepository net.micode.notes D Successfully loaded sub-folders for folder: 0
+2026-01-28 18:33:20.011 12829-12852 NotesListViewModel net.micode.notes D Successfully loaded 18 notes
+2026-01-28 18:33:20.015 12829-12852 NotesRepository net.micode.notes D Successfully loaded notes for folder: -10
+2026-01-28 18:33:20.585 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=579.26ms min=69.15ms max=1150.06ms count=3
+2026-01-28 18:33:21.989 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=217.92ms min=6.06ms max=925.72ms count=7
+2026-01-28 18:33:23.885 12829-12829 Compatibil...geReporter net.micode.notes D Compat change id reported: 171228096; UID 10145; state: ENABLED
+2026-01-28 18:33:24.375 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=274.96ms min=37.73ms max=1773.53ms count=8
+2026-01-28 18:33:25.462 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=138.04ms min=31.14ms max=528.49ms count=8
+2026-01-28 18:33:25.561 12829-12829 Compatibil...geReporter net.micode.notes D Compat change id reported: 163400105; UID 10145; state: DISABLED
+2026-01-28 18:33:25.562 12829-12829 InputMethodManager net.micode.notes D showSoftInput() view=android.widget.EditText{6afc15c VFED..CL. .F.P..ID 0,0-936,124 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
+2026-01-28 18:33:25.584 12829-12829 AssistStructure net.micode.notes I Flattened final assist data: 6540 bytes, containing 2 windows, 48 views
+2026-01-28 18:33:26.165 12829-12829 InsetsController net.micode.notes D show(ime(), fromIme=true)
+2026-01-28 18:33:26.580 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=129.20ms min=47.16ms max=324.82ms count=8
+2026-01-28 18:33:27.735 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=225.02ms min=26.70ms max=514.93ms count=5
+2026-01-28 18:33:27.800 12829-12829 InsetsController net.micode.notes D show(ime(), fromIme=true)
+2026-01-28 18:33:29.265 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=495.39ms min=20.28ms max=1320.45ms count=3
+2026-01-28 18:33:29.629 12829-12848 OpenGLRenderer net.micode.notes D endAllActiveAnimators on 0x77af8494a510 (RippleDrawable) with handle 0x77ae949a9830
+2026-01-28 18:33:29.791 12829-12838 et.micode.note net.micode.notes I JIT allocated 77KB for compiled code of void android.view.View.(android.content.Context, android.util.AttributeSet, int, int)
+2026-01-28 18:33:31.635 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=254.05ms min=55.70ms max=974.59ms count=5
+2026-01-28 18:33:31.899 12829-12852 NotesRepository net.micode.notes D Successfully loaded sub-folders for folder: 0
+2026-01-28 18:33:31.903 12829-12852 NotesListViewModel net.micode.notes D Successfully loaded 18 notes
+2026-01-28 18:33:31.903 12829-12852 NotesRepository net.micode.notes D Successfully loaded notes for folder: -10
+2026-01-28 18:33:32.643 12829-12829 Choreographer net.micode.notes I Skipped 44 frames! The application may be doing too much work on its main thread.
+2026-01-28 18:33:32.697 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=86.15ms min=38.44ms max=217.87ms count=10
+2026-01-28 18:33:32.859 12829-12877 OpenGLRenderer net.micode.notes I Davey! duration=835ms; Flags=0, FrameTimelineVsyncId=169346, IntendedVsync=13065423015368, Vsync=13066156348672, InputEventId=0, HandleInputStart=13066165642400, AnimationStart=13066165663300, PerformTraversalsStart=13066165738500, DrawStart=13066166834500, FrameDeadline=13065456348700, FrameInterval=13066165468000, FrameStartTime=16666666, SyncQueued=13066168091300, SyncStart=13066220366400, IssueDrawCommandsStart=13066220448100, SwapBuffers=13066252767600, FrameCompleted=13066310985200, DequeueBufferDuration=27682800, QueueBufferDuration=330100, GpuCompleted=13066285932000, SwapBuffersCompleted=13066310985200, DisplayPresentTime=0,
+2026-01-28 18:33:32.916 12829-12877 OpenGLRenderer net.micode.notes I Davey! duration=873ms; Flags=0, FrameTimelineVsyncId=169346, IntendedVsync=13065423015368, Vsync=13066156348672, InputEventId=0, HandleInputStart=13066165642400, AnimationStart=13066165663300, PerformTraversalsStart=13066165738500, DrawStart=13066242021400, FrameDeadline=13065456348700, FrameInterval=13066165468000, FrameStartTime=16666666, SyncQueued=13066243836100, SyncStart=13066353178700, IssueDrawCommandsStart=13066353471800, SwapBuffers=13066355119200, FrameCompleted=13066405600900, DequeueBufferDuration=143500, QueueBufferDuration=24341500, GpuCompleted=13066405345300, SwapBuffersCompleted=13066405600900, DisplayPresentTime=0,
+2026-01-28 18:33:33.958 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=126.48ms min=51.36ms max=248.94ms count=10
+2026-01-28 18:33:34.959 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=72.41ms min=27.46ms max=168.65ms count=10
+2026-01-28 18:33:36.123 12829-12848 EGL_emulation net.micode.notes D app_time_stats: avg=1127.39ms min=1127.39ms max=1127.39ms count=1
+2026-01-28 18:33:36.147 12829-12829 AndroidRuntime net.micode.notes D Shutting down VM
+2026-01-28 18:33:36.149 12829-12829 AndroidRuntime net.micode.notes E FATAL EXCEPTION: main
+ Process: net.micode.notes, PID: 12829
+ android.content.ActivityNotFoundException: Unable to find explicit activity class {net.micode.notes/net.micode.notes.ui.SettingsActivity}; have you declared this activity in your AndroidManifest.xml?
+ at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2085)
+ at android.app.Instrumentation.execStartActivity(Instrumentation.java:1747)
+ at android.app.Activity.startActivityForResult(Activity.java:5404)
+ at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:780)
+ at android.app.Activity.startActivityForResult(Activity.java:5362)
+ at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:761)
+ at android.app.Activity.startActivity(Activity.java:5748)
+ at android.app.Activity.startActivity(Activity.java:5701)
+ at net.micode.notes.ui.NotesListActivity.onSettingsSelected(NotesListActivity.java:414)
+ at net.micode.notes.ui.SidebarFragment.lambda$setupListeners$6$net-micode-notes-ui-SidebarFragment(SidebarFragment.java:236)
+ at net.micode.notes.ui.SidebarFragment$$ExternalSyntheticLambda2.onClick(D8$$SyntheticClass:0)
+ at android.view.View.performClick(View.java:7441)
+ at android.view.View.performClickInternal(View.java:7418)
+ at android.view.View.access$3700(View.java:835)
+ at android.view.View$PerformClick.run(View.java:28676)
+ at android.os.Handler.handleCallback(Handler.java:938)
+ at android.os.Handler.dispatchMessage(Handler.java:99)
+ at android.os.Looper.loopOnce(Looper.java:201)
+ at android.os.Looper.loop(Looper.java:288)
+ at android.app.ActivityThread.main(ActivityThread.java:7839)
+ at java.lang.reflect.Method.invoke(Native Method)
+ at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
+ at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
+---------------------------- PROCESS ENDED (12829) for package net.micode.notes ----------------------------