ui界面现代化补全

pull/32/head
包尔俊 3 weeks ago
parent 2e7dc5d079
commit 87b5daed82

3
.idea/.gitignore vendored

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

@ -13,8 +13,3 @@
.externalNativeBuild
.cxx
local.properties
build.gradle.kts
gradle.properties
gradlew
gradlew.bat
settings.gradle.kts

@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-26T02:44:48.273765700Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\archmaxjtx\.android\avd\Pixel_4a.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

@ -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*

@ -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. **双向链接 & 知识图谱**: 打造知识库神器的核心。

@ -5,6 +5,11 @@
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
@ -12,6 +17,7 @@
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>

@ -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)

@ -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();
});
}
}
}

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

@ -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();
}
}

@ -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<CapsuleItem> 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<CapsuleAdapter.ViewHolder> {
private List<CapsuleItem> mItems = new ArrayList<>();
public void setItems(List<CapsuleItem> 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);
}
}
}
}

@ -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<FolderAdapter.FolderViewHolder> {
private Context context;
private List<NotesRepository.NoteInfo> 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<NotesRepository.NoteInfo> 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);
}
}
}

@ -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 extends androidx.lifecycle.ViewModel> T create(Class<T> 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();
}
}

@ -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<NotesRecyclerAdapter.NoteViewHolder> {
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);
}
}
}
}

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

@ -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<Task> 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();
}
}
}

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@color/text_color_primary"/>
<item android:color="@color/text_color_secondary"/>
</selector>

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#80000000"/>
<corners android:radius="25dp"/>
</shape>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape>
<solid android:color="#FFD54F"/> <!-- Yellow -->
<corners android:radius="16dp"/>
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/transparent"/>
<corners android:radius="16dp"/>
</shape>
</item>
</selector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFC107"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:strokeColor="#757575"
android:strokeWidth="2"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M4,11h5V5H4v6zm0,7h5v-6H4v6zm6,0h5v-6h-5v6zm6,0h5v-6h-5v6zm-6,-7h5V5h-5v6zm6,-6v6h5V5h-5z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,13h2v-2H3v2zm0,4h2v-2H3v2zm0,-8h2V7H3v2zm4,4h14v-2H7v2zm0,4h14v-2H7v2zM7,7v2h14V7H7z"/>
</vector>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F0F0F0" />
<corners android:radius="24dp" />
</shape>

@ -0,0 +1,4 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_checkbox_checked_round" />
<item android:drawable="@drawable/ic_checkbox_unchecked_round" />
</selector>

@ -0,0 +1,215 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_color">
<!-- Normal Header Container -->
<LinearLayout
android:id="@+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/background_color"
app:layout_constraintTop_toTopOf="parent">
<!-- Top Bar (Hamburger + Title) -->
<LinearLayout
android:id="@+id/top_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/btn_sidebar"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_menu_hamburger"
app:tint="@color/text_color_primary"
android:padding="4dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Menu"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/app_name"
android:textAppearance="@style/NotesBigTitle"
android:layout_marginStart="12dp"/>
<ImageView
android:id="@+id/btn_change_layout"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_view_list"
app:tint="@color/text_color_primary"
android:padding="4dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Change Layout"
android:visibility="visible"/>
</LinearLayout>
<!-- Search Bar -->
<TextView
android:id="@+id/tv_search_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="@drawable/search_bar_bg"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="Search notes"
android:textColor="#9E9E9E"
android:textSize="16sp" />
<!-- Category Tabs -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_folder_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp"
android:clipToPadding="false"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/folder_tab_item" />
</LinearLayout>
<!-- ViewPager -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/header_container"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"/>
<!-- Bottom Navigation -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_white"
app:elevation="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_nav_menu"
app:labelVisibilityMode="labeled"
app:itemIconTint="@color/selector_bottom_nav_color"
app:itemTextColor="@color/selector_bottom_nav_color"/>
<!-- Selection Header Overlay -->
<LinearLayout
android:id="@+id/selection_header"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/bg_white"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone"
android:elevation="4dp"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/btn_close_selection"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
app:tint="@color/text_color_primary"/>
<TextView
android:id="@+id/tv_selection_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="已选择 0 项"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_color_primary"
android:layout_marginStart="16dp"/>
<TextView
android:id="@+id/btn_select_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="全选"
android:textColor="@color/text_color_primary"
android:textSize="16sp"
android:background="?attr/selectableItemBackground"
android:padding="8dp"/>
<TextView
android:id="@+id/btn_selection_restore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="恢复"
android:textColor="@color/text_color_primary"
android:textSize="16sp"
android:background="?attr/selectableItemBackground"
android:padding="8dp"
android:visibility="gone"/>
<TextView
android:id="@+id/btn_selection_delete_forever"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="永久删除"
android:textColor="@color/text_color_primary"
android:textSize="16sp"
android:background="?attr/selectableItemBackground"
android:padding="8dp"
android:visibility="gone"/>
</LinearLayout>
<!-- Selection Bottom Bar Overlay -->
<LinearLayout
android:id="@+id/selection_bottom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/bg_white"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:elevation="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent">
<TextView android:id="@+id/btn_action_hide" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="隐藏" android:drawableTop="@android:drawable/ic_menu_close_clear_cancel" android:gravity="center" android:visibility="gone"/>
<TextView android:id="@+id/btn_action_pin" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="置顶" android:drawableTop="@android:drawable/ic_menu_upload" android:gravity="center"/>
<TextView android:id="@+id/btn_action_move" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="移动到" android:drawableTop="@drawable/ic_folder" android:gravity="center"/>
<TextView android:id="@+id/btn_action_lock" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="加锁" android:drawableTop="@android:drawable/ic_lock_lock" android:gravity="center"/>
<TextView android:id="@+id/btn_action_delete" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="删除" android:drawableTop="@android:drawable/ic_menu_delete" android:gravity="center"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<fragment
android:id="@+id/sidebar_fragment"
android:name="net.micode.notes.ui.SidebarFragment"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
tools:layout="@layout/sidebar_layout" />
</androidx.drawerlayout.widget.DrawerLayout>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_folder_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_marginEnd="8dp"
android:text="Folder"
android:textSize="14sp"
android:textColor="@color/text_color_primary"
android:background="@drawable/folder_tab_bg_selector"
android:fontFamily="sans-serif-medium" />

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_color">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.Notesmaster">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="胶囊"
app:titleTextColor="@android:color/white" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/capsule_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="80dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<TextView
android:id="@+id/tv_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂无速记胶囊"
android:visibility="gone"
android:layout_gravity="center" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
<TextView
android:id="@+id/tv_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:maxLines="2"
android:ellipsize="end" />
<TextView
android:id="@+id/tv_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@android:color/darker_gray"
android:layout_marginTop="4dp" />
<TextView
android:id="@+id/tv_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/text_color_secondary"
android:gravity="end"
android:layout_marginTop="8dp" />
</LinearLayout>

@ -0,0 +1,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="50dp"
android:layout_height="80dp"
android:background="@drawable/capsule_collapsed_bg">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_menu_rich_text" />
</FrameLayout>

@ -0,0 +1,48 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/white"
android:padding="16dp"
android:elevation="8dp">
<TextView
android:id="@+id/tv_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Source: Unknown"
android:textSize="12sp"
android:textColor="#888888"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top|start"
android:hint="Type here..."
android:background="@null"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel"
style="?android:attr/buttonBarButtonStyle"/>
<Button
android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save"
style="?android:attr/buttonBarButtonStyle"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_notes"
android:icon="@drawable/ic_menu_notes"
android:title="@string/app_name"/>
<item
android:id="@+id/nav_tasks"
android:icon="@drawable/ic_menu_tasks"
android:title="待办"/>
</menu>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeViewTextSelectionChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description"
android:notificationTimeout="100" />

@ -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

@ -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.<init>(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.<init>(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 ----------------------------
Loading…
Cancel
Save