diff --git a/src/Notesmaster/.idea/deploymentTargetSelector.xml b/src/Notesmaster/.idea/deploymentTargetSelector.xml
index f802cd9..b268ef3 100644
--- a/src/Notesmaster/.idea/deploymentTargetSelector.xml
+++ b/src/Notesmaster/.idea/deploymentTargetSelector.xml
@@ -4,21 +4,6 @@
-<<<<<<< HEAD
-
-
-
-
-=======
-
-
-
-
->>>>>>> baoerjun_branch
-
-
-
-
diff --git a/src/Notesmaster/app/src/main/AndroidManifest.xml b/src/Notesmaster/app/src/main/AndroidManifest.xml
index b4152a3..96ce6f4 100644
--- a/src/Notesmaster/app/src/main/AndroidManifest.xml
+++ b/src/Notesmaster/app/src/main/AndroidManifest.xml
@@ -22,6 +22,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -110,6 +128,12 @@
android:exported="false" />
+
+
0) {
- insertNote(db, workFolderId, "会议记录", "会议主题:\n时间:\n地点:\n参会人:\n\n会议内容:\n\n行动项:\n");
- insertNote(db, workFolderId, "周报", "本周工作总结:\n1. \n2. \n\n下周工作计划:\n1. \n2. \n\n需要协调的问题:\n");
+ insertNote(db, workFolderId, "会议记录", "会议主题:\n时间:\n地点:\n参会人:\n\n会议内容:\n\n行动项:\n", Notes.TYPE_TEMPLATE);
+ insertNote(db, workFolderId, "周报", "本周工作总结:\n1. \n2. \n\n下周工作计划:\n1. \n2. \n\n需要协调的问题:\n", Notes.TYPE_TEMPLATE);
}
// 生活模板
long lifeFolderId = insertFolder(db, Notes.ID_TEMPLATE_FOLDER, "生活");
if (lifeFolderId > 0) {
- insertNote(db, lifeFolderId, "日记", "日期:\n天气:\n心情:\n\n正文:\n");
- insertNote(db, lifeFolderId, "购物清单", "1. \n2. \n3. \n");
+ insertNote(db, lifeFolderId, "日记", "日期:\n天气:\n心情:\n\n正文:\n", Notes.TYPE_TEMPLATE);
+ insertNote(db, lifeFolderId, "购物清单", "1. \n2. \n3. \n", Notes.TYPE_TEMPLATE);
}
// 学习模板
long studyFolderId = insertFolder(db, Notes.ID_TEMPLATE_FOLDER, "学习");
if (studyFolderId > 0) {
- insertNote(db, studyFolderId, "读书笔记", "书名:\n作者:\n\n核心观点:\n\n精彩摘录:\n\n读后感:\n");
+ insertNote(db, studyFolderId, "读书笔记", "书名:\n作者:\n\n核心观点:\n\n精彩摘录:\n\n读后感:\n", Notes.TYPE_TEMPLATE);
}
}
@@ -1012,10 +1012,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
return db.insert(TABLE.NOTE, null, values);
}
- private void insertNote(SQLiteDatabase db, long parentId, String title, String content) {
+ private void insertNote(SQLiteDatabase db, long parentId, String title, String content, int type) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, parentId);
- values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
+ values.put(NoteColumns.TYPE, type);
values.put(NoteColumns.CREATED_DATE, System.currentTimeMillis());
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
values.put(NoteColumns.SNIPPET, content); // SNIPPET acts as content preview or full content for simple notes
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java
index aa2cf34..084d09a 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java
@@ -168,7 +168,7 @@ public class NotesProvider extends ContentProvider {
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
- + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
+ + " AND (" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " OR " + NoteColumns.TYPE + "=" + Notes.TYPE_TEMPLATE + ")";
/**
* 创建Content Provider
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java
index cdd7c4d..169d437 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesRepository.java
@@ -88,6 +88,10 @@ public class NotesRepository {
return parentId;
}
+ public void setParentId(long parentId) {
+ this.parentId = parentId;
+ }
+
public String getNoteDataValue() {
return snippet;
}
@@ -308,11 +312,17 @@ public class NotesRepository {
selection = NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " +
NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
selectionArgs = null;
+ } else if (folderId == Notes.ID_TEMPLATE_FOLDER) {
+ // Special case for template folder: show all templates regardless of category
+ selection = NoteColumns.TYPE + "=" + Notes.TYPE_TEMPLATE;
+ selectionArgs = null;
} else if (folderId == Notes.ID_ROOT_FOLDER) {
selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=?)";
selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
} else {
- selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
+ // In a sub-folder, show both normal notes and templates if they exist there
+ selection = NoteColumns.PARENT_ID + "=? AND (" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE +
+ " OR " + NoteColumns.TYPE + "=" + Notes.TYPE_TEMPLATE + ")";
selectionArgs = new String[]{String.valueOf(folderId)};
}
@@ -605,8 +615,19 @@ public class NotesRepository {
ContentValues values = new ContentValues();
long currentTime = System.currentTimeMillis();
+ int type = Notes.TYPE_NOTE;
+ if (folderId == Notes.ID_TEMPLATE_FOLDER) {
+ type = Notes.TYPE_TEMPLATE;
+ } else if (folderId > 0) {
+ // Check if folder is under templates
+ NoteInfo folder = getFolderInfo(folderId);
+ if (folder != null && folder.parentId == Notes.ID_TEMPLATE_FOLDER) {
+ type = Notes.TYPE_TEMPLATE;
+ }
+ }
+
values.put(NoteColumns.PARENT_ID, folderId);
- values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
+ values.put(NoteColumns.TYPE, type);
values.put(NoteColumns.CREATED_DATE, currentTime);
values.put(NoteColumns.MODIFIED_DATE, currentTime);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
@@ -1254,7 +1275,7 @@ public class NotesRepository {
}
ContentValues values = new ContentValues();
- // 同时更新 TITLE 和 SNIPPET,保持一致性
+ // 同时更新 TITLE 和 SNIPPET,保持一致性(文件夹名存储在SNIPPET中)
values.put(NoteColumns.TITLE, newName);
values.put(NoteColumns.SNIPPET, newName);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
@@ -1295,9 +1316,8 @@ public class NotesRepository {
}
ContentValues values = new ContentValues();
- // 同时更新 TITLE 和 SNIPPET,保持一致性
+ // 仅更新 TITLE,保留原始 SNIPPET(内容预览)
values.put(NoteColumns.TITLE, newName);
- values.put(NoteColumns.SNIPPET, newName);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@@ -1483,7 +1503,7 @@ public class NotesRepository {
long currentTime = System.currentTimeMillis();
values.put(NoteColumns.PARENT_ID, categoryId);
- values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
+ values.put(NoteColumns.TYPE, Notes.TYPE_TEMPLATE);
values.put(NoteColumns.CREATED_DATE, currentTime);
values.put(NoteColumns.MODIFIED_DATE, currentTime);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java b/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java
index 75a6ee1..a8e50c8 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java
@@ -115,6 +115,7 @@ public class WorkingNote {
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
+ DataColumns.DATA5,
};
/** 数据查询投影 - 笔记元数据 */
@@ -190,7 +191,36 @@ public class WorkingNote {
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mLocalModified = 1; // 新建笔记需要同步
- mType = Notes.TYPE_NOTE; // 默认为普通笔记类型
+
+ // Determine type based on folder
+ if (folderId == Notes.ID_TEMPLATE_FOLDER) {
+ mType = Notes.TYPE_TEMPLATE;
+ } else if (folderId > 0) {
+ // Check if parent is template folder
+ int parentType = net.micode.notes.tool.DataUtils.getNoteTypeById(context.getContentResolver(), folderId);
+ if (parentType == Notes.TYPE_FOLDER) {
+ // We need to check the folder's parent
+ long parentId = 0;
+ android.database.Cursor c = context.getContentResolver().query(
+ android.content.ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, folderId),
+ new String[] { NoteColumns.PARENT_ID }, null, null, null);
+ if (c != null) {
+ if (c.moveToFirst()) {
+ parentId = c.getLong(0);
+ }
+ c.close();
+ }
+ if (parentId == Notes.ID_TEMPLATE_FOLDER) {
+ mType = Notes.TYPE_TEMPLATE;
+ } else {
+ mType = Notes.TYPE_NOTE;
+ }
+ } else {
+ mType = Notes.TYPE_NOTE;
+ }
+ } else {
+ mType = Notes.TYPE_NOTE;
+ }
}
/**
@@ -287,6 +317,17 @@ public class WorkingNote {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
+
+ // 加载壁纸路径
+ int wallpaperIndex = cursor.getColumnIndex(DataColumns.DATA5);
+ if (wallpaperIndex != -1) {
+ String path = cursor.getString(wallpaperIndex);
+ if (!TextUtils.isEmpty(path)) {
+ mWallpaperPath = path;
+ } else {
+ mWallpaperPath = null;
+ }
+ }
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 加载通话记录数据
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
@@ -366,9 +407,9 @@ public class WorkingNote {
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
- // 创建新笔记
- if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
- Log.e(TAG, "Create new note fail with id:" + mNoteId);
+ // 创建新笔记
+ if ((mNoteId = Note.getNewNoteId(mContext, mFolderId, mType)) == 0) {
+ Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
@@ -413,7 +454,7 @@ public class WorkingNote {
* @return 如果值得保存返回 true,否则返回 false
*/
private boolean isWorthSaving() {
- if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
+ if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent) && TextUtils.isEmpty(mWallpaperPath))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
@@ -442,8 +483,11 @@ public class WorkingNote {
public void setTitle(String title) {
mTitle = title;
mNote.setNoteValue(NoteColumns.TITLE, mTitle);
- // 同步设置SNIPPET字段以保持兼容性
- mNote.setNoteValue(NoteColumns.SNIPPET, mTitle);
+ // 只有文件夹需要将标题同步到SNIPPET字段(文件夹名存储在SNIPPET中以保持兼容性)
+ // 普通便签的SNIPPET应由内容触发器自动维护
+ if (mType == Notes.TYPE_FOLDER) {
+ mNote.setNoteValue(NoteColumns.SNIPPET, mTitle);
+ }
}
public String getTitle() {
@@ -498,16 +542,12 @@ public class WorkingNote {
private String mWallpaperPath;
public void setWallpaper(String path) {
- mWallpaperPath = path;
- // Ideally we should save this to DB, but for now we might use shared prefs or a separate table
- // Or reuse bg_color_id with a special flag if we want to stick to existing schema strictly?
- // Better: store in a new column or reuse a data column if possible.
- // Given existing schema, let's use DataColumns.DATA5 if available? No DATA5.
- // Let's use a SharedPreference for mapping noteId -> wallpaperPath for now to avoid schema migration complexity in this step.
- // Or just use a special negative color ID range for wallpapers?
- // Actually, let's use a separate storage for wallpapers map: note_id -> uri string
- if (mNoteSettingStatusListener != null) {
- mNoteSettingStatusListener.onBackgroundColorChanged(); // Reuse this to trigger refresh
+ if (!TextUtils.equals(mWallpaperPath, path)) {
+ mWallpaperPath = path;
+ mNote.setTextData(DataColumns.DATA5, mWallpaperPath);
+ if (mNoteSettingStatusListener != null) {
+ mNoteSettingStatusListener.onBackgroundColorChanged(); // Reuse this to trigger refresh
+ }
}
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java
index d237122..46fd4c6 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java
@@ -182,10 +182,22 @@ public class DataUtils {
* @return 如果笔记可见返回 true,否则返回 false
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
+ String selection;
+ String[] selectionArgs;
+
+ if (type == Notes.TYPE_NOTE) {
+ // If checking for a regular note, also allow templates as they are essentially notes
+ selection = "(" + NoteColumns.TYPE + "=? OR " + NoteColumns.TYPE + "=?) AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
+ selectionArgs = new String[] {String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.TYPE_TEMPLATE)};
+ } else {
+ selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
+ selectionArgs = new String [] {String.valueOf(type)};
+ }
+
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
- NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
- new String [] {String.valueOf(type)},
+ selection,
+ selectionArgs,
null);
boolean exist = false;
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java
index 595d7a5..3743336 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java
@@ -51,6 +51,12 @@ public class ResourceParser {
public static final int EYE_CARE_GREEN = 6;
public static final int WARM = 7;
public static final int COOL = 8;
+
+ // Gradient Presets
+ public static final int SUNSET = 9;
+ public static final int OCEAN = 10;
+ public static final int FOREST = 11;
+ public static final int LAVENDER = 12;
/** 自定义颜色按钮 ID (用于 UI 显示) */
public static final int CUSTOM_COLOR_BUTTON_ID = -100;
@@ -108,7 +114,15 @@ public class ResourceParser {
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
- R.drawable.edit_red
+ R.drawable.edit_red,
+ R.color.bg_midnight_black,
+ R.color.bg_eye_care_green,
+ R.color.bg_warm,
+ R.color.bg_cool,
+ R.drawable.preset_sunset,
+ R.drawable.preset_ocean,
+ R.drawable.preset_forest,
+ R.drawable.preset_lavender
};
/** 标题栏背景资源数组 */
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SmartParser.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SmartParser.java
new file mode 100644
index 0000000..0576ed1
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SmartParser.java
@@ -0,0 +1,167 @@
+package net.micode.notes.tool;
+
+import android.content.Context;
+import android.os.Build;
+import android.text.Spannable;
+import android.text.style.URLSpan;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 智能解析工具类
+ *
+ * 整合了 Android 系统级 AI (TextClassifier) 和自定义正则表达式。
+ * 能够识别文本中的时间、地点、电话、URL 等信息。
+ *
+ */
+public class SmartParser {
+ // 时间 Scheme 前缀
+ public static final String SCHEME_TIME = "smarttime:";
+ // 地点 Scheme 前缀
+ public static final String SCHEME_GEO = "smartgeo:";
+
+ // 优化的时间正则表达式(作为补充)
+ private static final String TIME_REGEX =
+ "((今天|明天|后天|下周[一二三四五六日])?\\s*([上下]午)?\\s*(\\d{1,2})[:点](\\d{0,2})分?)" +
+ "|(\\b\\d{1,2}:\\d{2}\\b)";
+
+ // 优化的地点正则表达式:匹配 1-10 个中文字符/数字 + 常见的地点后缀
+ private static final String GEO_REGEX =
+ "([一-龥0-9]{1,10}(?:省|市|区|县|街道|路|弄|巷|楼|院|场|店|里|广场|大厦|中心|医院|学校|大学|公园|车站|机场|酒店|宾馆|超市|商场))";
+
+ // 扩展噪音词列表:包含动词、代词、时间单位和方位词
+ private static final String NOISE_PREFIXES = "我在去到从的地了你他们这那点分时上下午";
+
+ /**
+ * 解析文本并应用智能链接
+ *
+ * @param context 上下文,用于获取系统服务
+ * @param text 要解析的文本内容
+ */
+ public static void parse(Context context, Spannable text) {
+ if (text == null || text.length() == 0) return;
+
+ // 1. 清除旧的智能链接
+ URLSpan[] allSpans = text.getSpans(0, text.length(), URLSpan.class);
+ for (URLSpan span : allSpans) {
+ String url = span.getURL();
+ if (url != null && (url.startsWith(SCHEME_TIME) || url.startsWith(SCHEME_GEO))) {
+ text.removeSpan(span);
+ }
+ }
+
+ // 2. 识别逻辑
+ // 步骤 A: 优先识别时间(因为时间格式相对固定,误报率低)
+ applyRegexLinks(text, Pattern.compile(TIME_REGEX, Pattern.CASE_INSENSITIVE), SCHEME_TIME);
+
+ // 步骤 B: 识别地点(地点正则较宽松,需要避开已识别的时间)
+ applyRegexLinks(text, Pattern.compile(GEO_REGEX), SCHEME_GEO);
+
+ // 3. 使用系统级 AI (TextClassifier) 作为增强
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ try {
+ TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class);
+ if (tcm != null) {
+ TextClassifier classifier = tcm.getTextClassifier();
+ TextLinks.Request request = new TextLinks.Request.Builder(text).build();
+ TextLinks links = classifier.generateLinks(request);
+
+ for (TextLinks.TextLink link : links.getLinks()) {
+ String entityType = getTopEntity(link);
+ if ("address".equals(entityType) || "location".equals(entityType) || "place".equals(entityType)) {
+ applySmartSpan(text, link.getStart(), link.getEnd(), SCHEME_GEO);
+ } else if ("date".equals(entityType) || "datetime".equals(entityType)) {
+ applySmartSpan(text, link.getStart(), link.getEnd(), SCHEME_TIME);
+ }
+ }
+ }
+ } catch (Exception e) {
+ // 静默回退
+ }
+ }
+ }
+
+ /**
+ * 获取置信度最高的实体类型
+ */
+ private static String getTopEntity(TextLinks.TextLink link) {
+ float maxConfidence = -1;
+ String topType = null;
+ for (int i = 0; i < link.getEntityCount(); i++) {
+ String type = link.getEntity(i);
+ float confidence = link.getConfidenceScore(type);
+ if (confidence > maxConfidence) {
+ maxConfidence = confidence;
+ topType = type;
+ }
+ }
+ return topType;
+ }
+
+ /**
+ * 应用智能 Span 并处理重叠冲突
+ */
+ private static void applySmartSpan(Spannable text, int start, int end, String scheme) {
+ // 1. 噪音修剪(特别是地点识别)
+ if (SCHEME_GEO.equals(scheme)) {
+ while (start < end && NOISE_PREFIXES.indexOf(text.charAt(start)) != -1) {
+ start++;
+ }
+ }
+
+ if (start >= end) return;
+
+ // 2. 处理重叠冲突
+ URLSpan[] existing = text.getSpans(start, end, URLSpan.class);
+ if (existing.length > 0) {
+ for (URLSpan span : existing) {
+ int spanStart = text.getSpanStart(span);
+ int spanEnd = text.getSpanEnd(span);
+
+ // 如果当前识别结果完全落在已有 span 内部,则跳过
+ if (start >= spanStart && end <= spanEnd) {
+ return;
+ }
+
+ // 如果当前识别结果包含了已有 span,尝试修剪当前结果的起始位置
+ if (start < spanEnd && end > spanStart) {
+ // 如果重叠发生在开头,将起始位置移动到已有 span 之后
+ if (start < spanEnd) {
+ start = spanEnd;
+ }
+ }
+ }
+ }
+
+ // 再次检查修剪后的合法性
+ if (start >= end) return;
+
+ // 针对地点识别,修剪后可能剩下的是噪音或过短
+ if (SCHEME_GEO.equals(scheme)) {
+ // 再次修剪新起点处的噪音
+ while (start < end && NOISE_PREFIXES.indexOf(text.charAt(start)) != -1) {
+ start++;
+ }
+ // 如果剩下的文本太短(如只有 1 个字且不是后缀),则放弃
+ if (end - start < 2) return;
+ }
+
+ text.setSpan(new SmartURLSpan(scheme + text.subSequence(start, end)),
+ start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ /**
+ * 正则应用链接
+ */
+ private static void applyRegexLinks(Spannable text, Pattern pattern, String scheme) {
+ Matcher m = pattern.matcher(text);
+ while (m.find()) {
+ applySmartSpan(text, m.start(), m.end(), scheme);
+ }
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SmartURLSpan.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SmartURLSpan.java
new file mode 100644
index 0000000..ed08191
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/SmartURLSpan.java
@@ -0,0 +1,122 @@
+package net.micode.notes.tool;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.AlarmClock;
+import android.text.style.URLSpan;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+
+import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 自定义的 URLSpan,用于处理智能识别出的时间、地点点击事件。
+ */
+public class SmartURLSpan extends URLSpan {
+ private static final String TAG = "SmartURLSpan";
+
+ public SmartURLSpan(String url) {
+ super(url);
+ }
+
+ @Override
+ public void onClick(View widget) {
+ String url = getURL();
+ Context context = widget.getContext();
+
+ if (url.startsWith(SmartParser.SCHEME_TIME)) {
+ handleTimeClick(context, url.substring(SmartParser.SCHEME_TIME.length()));
+ } else if (url.startsWith(SmartParser.SCHEME_GEO)) {
+ handleGeoClick(context, url.substring(SmartParser.SCHEME_GEO.length()));
+ } else {
+ super.onClick(widget);
+ }
+ }
+
+ /**
+ * 处理时间点击:跳转到系统闹钟设置页面
+ */
+ private void handleTimeClick(Context context, String timeStr) {
+ try {
+ int hour = -1;
+ int minute = 0;
+
+ // 尝试解析小时和分钟
+ // 支持格式如:14:30, 10点30分, 9点
+ Pattern p = Pattern.compile("(\\d{1,2})[:点](\\d{0,2})");
+ Matcher m = p.matcher(timeStr);
+ if (m.find()) {
+ hour = Integer.parseInt(m.group(1));
+ String minStr = m.group(2);
+ if (minStr != null && !minStr.isEmpty()) {
+ minute = Integer.parseInt(minStr);
+ }
+ }
+
+ // 处理上下午
+ if (timeStr.contains("下午") && hour < 12) {
+ hour += 12;
+ } else if (timeStr.contains("上午") && hour == 12) {
+ hour = 0;
+ }
+
+ if (hour == -1) {
+ // 如果没解析出来,默认打开闹钟主界面
+ Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM);
+ context.startActivity(intent);
+ return;
+ }
+
+ // 设置闹钟意图
+ Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
+ .putExtra(AlarmClock.EXTRA_HOUR, hour)
+ .putExtra(AlarmClock.EXTRA_MINUTES, minute)
+ .putExtra(AlarmClock.EXTRA_SKIP_UI, false);
+
+ if (intent.resolveActivity(context.getPackageManager()) != null) {
+ context.startActivity(intent);
+ } else {
+ Log.w(TAG, "No activity found to handle set alarm, trying without resolveActivity");
+ try {
+ context.startActivity(intent);
+ } catch (Exception e2) {
+ Toast.makeText(context, "无法打开闹钟应用", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set alarm", e);
+ Toast.makeText(context, "解析时间失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * 处理地点点击:跳转到地图应用
+ */
+ private void handleGeoClick(Context context, String location) {
+ try {
+ // 使用 geo:0,0?q=location 格式打开地图
+ Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
+ Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
+
+ if (mapIntent.resolveActivity(context.getPackageManager()) != null) {
+ context.startActivity(mapIntent);
+ } else {
+ Log.w(TAG, "No activity found to handle geo intent, trying web fallback");
+ // 如果没有地图应用支持 geo 协议,尝试搜索
+ Uri webUri = Uri.parse("https://www.google.com/maps/search/" + Uri.encode(location));
+ Intent webIntent = new Intent(Intent.ACTION_VIEW, webUri);
+ context.startActivity(webIntent);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to open map", e);
+ Toast.makeText(context, "无法打开地图应用", Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java
new file mode 100644
index 0000000..df5aa30
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/CapsuleListActivity.java
@@ -0,0 +1,33 @@
+package net.micode.notes.ui;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import net.micode.notes.R;
+
+public class CapsuleListActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_capsule_list);
+
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.container, new CapsuleListFragment())
+ .commit();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
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
index 0bebe45..7712dcd 100644
--- 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
@@ -1,21 +1,25 @@
package net.micode.notes.ui;
-import android.content.Context;
+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 android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
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.data.NotesRepository;
import net.micode.notes.model.Note;
import java.text.SimpleDateFormat;
@@ -29,6 +33,7 @@ public class CapsuleListFragment extends Fragment {
private RecyclerView mRecyclerView;
private CapsuleAdapter mAdapter;
private TextView mEmptyView;
+ private Toolbar mToolbar;
@Nullable
@Override
@@ -36,6 +41,9 @@ public class CapsuleListFragment extends Fragment {
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);
+ mToolbar = view.findViewById(R.id.toolbar);
+
+ setupToolbar();
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mAdapter = new CapsuleAdapter();
@@ -44,6 +52,17 @@ public class CapsuleListFragment extends Fragment {
return view;
}
+ private void setupToolbar() {
+ if (mToolbar != null && getActivity() instanceof AppCompatActivity) {
+ AppCompatActivity activity = (AppCompatActivity) getActivity();
+ activity.setSupportActionBar(mToolbar);
+ if (activity.getSupportActionBar() != null) {
+ activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getSupportActionBar().setTitle("速记胶囊");
+ }
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -54,15 +73,13 @@ public class CapsuleListFragment extends Fragment {
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)};
-
+ // Query notes in CAPSULE folder.
+ // Join with Data table to get DATA3 (source package)
Cursor cursor = getContext().getContentResolver().query(
Notes.CONTENT_NOTE_URI,
null,
- selection,
- selectionArgs,
+ Notes.NoteColumns.PARENT_ID + "=?",
+ new String[]{String.valueOf(Notes.ID_CAPSULE_FOLDER)},
Notes.NoteColumns.MODIFIED_DATE + " DESC"
);
@@ -73,14 +90,18 @@ public class CapsuleListFragment extends Fragment {
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.
+ // Try to get source from projection (if joined) or query separately
+ String source = "";
+ try {
+ int sourceIdx = cursor.getColumnIndex(Notes.DataColumns.DATA3);
+ if (sourceIdx != -1) {
+ source = cursor.getString(sourceIdx);
+ }
+ } catch (Exception e) {
+ // Not joined, ignore for now or lazy load
+ }
- items.add(new CapsuleItem(id, snippet, modifiedDate, "Loading source..."));
+ items.add(new CapsuleItem(id, snippet, modifiedDate, source));
}
cursor.close();
}
@@ -95,6 +116,13 @@ public class CapsuleListFragment extends Fragment {
}).start();
}
+ private void openNoteEditor(long noteId) {
+ Intent intent = new Intent(getActivity(), NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.putExtra(Intent.EXTRA_UID, noteId);
+ startActivity(intent);
+ }
+
private static class CapsuleItem {
long id;
String summary;
@@ -133,15 +161,14 @@ public class CapsuleListFragment extends Fragment {
holder.tvTime.setText(sdf.format(new Date(item.time)));
if (item.source != null && !item.source.isEmpty()) {
- holder.tvSource.setText("Source: " + item.source);
+ holder.tvSource.setText(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
+ openNoteEditor(item.id);
});
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
index efdb3e1..5a3b1c0 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
@@ -77,9 +77,13 @@ import java.util.regex.Pattern;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.appbar.MaterialToolbar;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import net.micode.notes.databinding.DialogBackgroundSelectorBinding;
+import net.micode.notes.databinding.DialogColorPickerBinding;
import net.micode.notes.databinding.NoteEditBinding;
import net.micode.notes.tool.RichTextHelper;
+import net.micode.notes.tool.SmartParser;
import net.micode.notes.data.FontManager;
@@ -162,7 +166,6 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private UndoRedoManager mUndoRedoManager;
private boolean mInUndoRedo = false;
- private androidx.recyclerview.widget.RecyclerView mColorSelectorRv;
private NoteColorAdapter mColorAdapter;
/**
@@ -331,7 +334,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
*
*/
private void initResources() {
- mHeadViewPanel = binding.noteTitle;
+ mHeadViewPanel = binding.cvEditorSurface;
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = binding.tvModifiedDate;
mNoteHeaderHolder.ivAlertIcon = binding.ivAlertIcon;
@@ -342,9 +345,8 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
mNoteHeaderHolder.etTitle = binding.etTitle;
mNoteEditor = binding.noteEditView;
- mNoteEditorPanel = binding.svNoteEdit;
+ mNoteEditorPanel = binding.cvEditorSurface;
mNoteBgColorSelector = binding.noteBgColorSelector;
- mColorSelectorRv = binding.rvBgColorSelector;
mNoteEditor.addTextChangedListener(new TextWatcher() {
private CharSequence mBeforeText;
@@ -406,6 +408,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
ResourceParser.EYE_CARE_GREEN,
ResourceParser.WARM,
ResourceParser.COOL,
+ ResourceParser.SUNSET,
+ ResourceParser.OCEAN,
+ ResourceParser.FOREST,
+ ResourceParser.LAVENDER,
ResourceParser.CUSTOM_COLOR_BUTTON_ID,
ResourceParser.WALLPAPER_BUTTON_ID
);
@@ -418,11 +424,11 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
pickWallpaper();
} else {
mWorkingNote.setBgColorId(colorId);
+ mWorkingNote.setWallpaper(null);
mNoteBgColorSelector.setVisibility(View.GONE);
}
}
});
- mColorSelectorRv.setAdapter(mColorAdapter);
mFontSizeSelector = binding.fontSizeSelector;
for (int id : sFontSizeBtnsMap.keySet()) {
@@ -680,8 +686,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
- mNoteBgColorSelector.setVisibility(View.VISIBLE);
- // Note: Adapter selection is already set in onBackgroundColorChanged or init
+ showBackgroundSelector();
} else if (sFontSizeBtnsMap.containsKey(id)) {
View fontView = getFontSelectorView(sFontSelectorSelectionMap.get(mFontSizeId));
if (fontView != null) {
@@ -764,27 +769,14 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
String wallpaperPath = mWorkingNote.getWallpaperPath();
if (wallpaperPath != null) {
- // Load wallpaper
+ binding.ivNoteWallpaper.setVisibility(View.VISIBLE);
+ binding.viewBgMask.setVisibility(View.VISIBLE);
+
android.net.Uri uri = android.net.Uri.parse(wallpaperPath);
try {
java.io.InputStream inputStream = getContentResolver().openInputStream(uri);
android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(inputStream);
- android.graphics.drawable.BitmapDrawable drawable = new android.graphics.drawable.BitmapDrawable(getResources(), bitmap);
-
- // Tiling mode (can be configurable later)
- drawable.setTileModeXY(android.graphics.Shader.TileMode.REPEAT, android.graphics.Shader.TileMode.REPEAT);
-
- // Add Blur Effect for Android 12+
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
- mNoteEditorPanel.setBackground(drawable);
- mNoteEditorPanel.setRenderEffect(android.graphics.RenderEffect.createBlurEffect(
- 20f, 20f, android.graphics.Shader.TileMode.CLAMP));
- } else {
- mNoteEditorPanel.setBackground(drawable);
- }
-
- // Header always uses original wallpaper (or maybe slightly darker?)
- mHeadViewPanel.setBackground(drawable.getConstantState().newDrawable());
+ binding.ivNoteWallpaper.setImageBitmap(bitmap);
// Dynamic Coloring with Palette
androidx.palette.graphics.Palette.from(bitmap).generate(palette -> {
@@ -795,12 +787,14 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
} catch (Exception e) {
Log.e(TAG, "Failed to load wallpaper", e);
- // Fallback to color
+ binding.ivNoteWallpaper.setVisibility(View.GONE);
+ binding.viewBgMask.setVisibility(View.GONE);
applyColorBackground(colorId);
}
} else {
+ binding.ivNoteWallpaper.setVisibility(View.GONE);
+ binding.viewBgMask.setVisibility(View.GONE);
applyColorBackground(colorId);
- // Reset toolbar colors to default/theme
resetToolbarColors();
}
updateTextColor(colorId);
@@ -809,30 +803,47 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
private void applyPaletteColors(androidx.palette.graphics.Palette palette) {
int primaryColor = palette.getDominantColor(getResources().getColor(R.color.primary_color));
int onPrimaryColor = getResources().getColor(R.color.on_primary_color);
+ int mutedColor = palette.getMutedColor(android.graphics.Color.WHITE);
// Ensure contrast for onPrimaryColor
if (androidx.core.graphics.ColorUtils.calculateContrast(onPrimaryColor, primaryColor) < 3.0) {
- onPrimaryColor = android.graphics.Color.WHITE;
+ onPrimaryColor = isColorDark(primaryColor) ? android.graphics.Color.WHITE : android.graphics.Color.BLACK;
}
- binding.toolbar.setBackgroundColor(primaryColor);
binding.toolbar.setTitleTextColor(onPrimaryColor);
if (binding.toolbar.getNavigationIcon() != null) {
binding.toolbar.getNavigationIcon().setTint(onPrimaryColor);
}
getWindow().setStatusBarColor(primaryColor);
+
+ // Update Card Surface - semi-transparent glass effect
+ int surfaceColor = androidx.core.graphics.ColorUtils.setAlphaComponent(mutedColor, 230); // 90% opacity
+ binding.cvEditorSurface.setCardBackgroundColor(surfaceColor);
+
+ // Update input text color based on surface color
+ int textColor = isColorDark(surfaceColor) ? android.graphics.Color.WHITE : android.graphics.Color.BLACK;
+ mNoteEditor.setTextColor(textColor);
+ if (mNoteHeaderHolder != null && mNoteHeaderHolder.etTitle != null) {
+ mNoteHeaderHolder.etTitle.setTextColor(textColor);
+ mNoteHeaderHolder.etTitle.setHintTextColor(androidx.core.graphics.ColorUtils.setAlphaComponent(textColor, 128));
+ }
+ binding.tvCharCount.setTextColor(textColor);
+ binding.tvModifiedDate.setTextColor(textColor);
}
private void resetToolbarColors() {
int primaryColor = getResources().getColor(R.color.primary_color);
int onPrimaryColor = getResources().getColor(R.color.on_primary_color);
- binding.toolbar.setBackgroundColor(primaryColor);
+ binding.toolbar.setBackgroundColor(android.graphics.Color.TRANSPARENT);
binding.toolbar.setTitleTextColor(onPrimaryColor);
if (binding.toolbar.getNavigationIcon() != null) {
binding.toolbar.getNavigationIcon().setTint(onPrimaryColor);
}
getWindow().setStatusBarColor(primaryColor);
+
+ // Reset Card Surface
+ binding.cvEditorSurface.setCardBackgroundColor(android.graphics.Color.parseColor("#CCFFFFFF"));
}
private void updateTextColor(int colorId) {
@@ -843,20 +854,27 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
textColor = android.graphics.Color.WHITE;
} else if (colorId < 0) {
// Custom color: Calculate luminance
- // colorId is the ARGB value for custom colors
if (isColorDark(colorId)) {
textColor = android.graphics.Color.WHITE;
}
}
- // For wallpaper, we might want to check palette, but for now default to black or keep current
- // If wallpaper is set, this method is called with the underlying colorId.
- // We should probably rely on the underlying color or default to white/black.
-
- mNoteEditor.setTextColor(textColor);
- // Also update title color if needed
- if (mNoteHeaderHolder != null && mNoteHeaderHolder.etTitle != null) {
- mNoteHeaderHolder.etTitle.setTextColor(textColor);
+ // If wallpaper is set, applyPaletteColors already handled text color.
+ if (mWorkingNote.getWallpaperPath() == null) {
+ mNoteEditor.setTextColor(textColor);
+ if (mNoteHeaderHolder != null && mNoteHeaderHolder.etTitle != null) {
+ mNoteHeaderHolder.etTitle.setTextColor(textColor);
+ mNoteHeaderHolder.etTitle.setHintTextColor(androidx.core.graphics.ColorUtils.setAlphaComponent(textColor, 128));
+ }
+ binding.tvCharCount.setTextColor(textColor);
+ binding.tvModifiedDate.setTextColor(textColor);
+
+ // Adjust card surface opacity for pure colors
+ if (colorId == ResourceParser.WHITE) {
+ binding.cvEditorSurface.setCardBackgroundColor(android.graphics.Color.WHITE);
+ } else {
+ binding.cvEditorSurface.setCardBackgroundColor(android.graphics.Color.parseColor("#CCFFFFFF"));
+ }
}
}
@@ -868,27 +886,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
private void applyColorBackground(int colorId) {
- mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
- mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
-
- if (colorId >= ResourceParser.MIDNIGHT_BLACK || colorId < 0) {
- int color = ResourceParser.getNoteBgColor(this, colorId);
- if (mNoteEditorPanel.getBackground() != null) {
- mNoteEditorPanel.getBackground().setTint(color);
- mNoteEditorPanel.getBackground().setTintMode(android.graphics.PorterDuff.Mode.MULTIPLY);
- }
- if (mHeadViewPanel.getBackground() != null) {
- mHeadViewPanel.getBackground().setTint(color);
- mHeadViewPanel.getBackground().setTintMode(android.graphics.PorterDuff.Mode.MULTIPLY);
- }
+ if (colorId < 0) {
+ binding.noteEditRoot.setBackgroundColor(colorId);
} else {
- // Clear tint for legacy resources
- if (mNoteEditorPanel.getBackground() != null) {
- mNoteEditorPanel.getBackground().clearColorFilter();
- }
- if (mHeadViewPanel.getBackground() != null) {
- mHeadViewPanel.getBackground().clearColorFilter();
- }
+ binding.noteEditRoot.setBackgroundResource(ResourceParser.NoteBgResources.getNoteBgResource(colorId));
}
}
@@ -1285,6 +1286,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
*/
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
+
+ // 应用智能解析(时间、地点识别)
+ SmartParser.parse(this, spannable);
+
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
@@ -1535,13 +1540,50 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
- private void showColorPickerDialog() {
- final View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_color_picker, null);
- final View colorPreview = dialogView.findViewById(R.id.view_color_preview);
- android.widget.SeekBar sbRed = dialogView.findViewById(R.id.sb_red);
- android.widget.SeekBar sbGreen = dialogView.findViewById(R.id.sb_green);
- android.widget.SeekBar sbBlue = dialogView.findViewById(R.id.sb_blue);
+ private void showBackgroundSelector() {
+ BottomSheetDialog dialog = new BottomSheetDialog(this);
+ DialogBackgroundSelectorBinding dialogBinding = DialogBackgroundSelectorBinding.inflate(getLayoutInflater());
+
+ java.util.List colors = java.util.Arrays.asList(
+ ResourceParser.YELLOW,
+ ResourceParser.BLUE,
+ ResourceParser.WHITE,
+ ResourceParser.GREEN,
+ ResourceParser.RED,
+ ResourceParser.MIDNIGHT_BLACK,
+ ResourceParser.EYE_CARE_GREEN,
+ ResourceParser.WARM,
+ ResourceParser.COOL,
+ ResourceParser.SUNSET,
+ ResourceParser.OCEAN,
+ ResourceParser.FOREST,
+ ResourceParser.LAVENDER
+ );
+
+ NoteColorAdapter adapter = new NoteColorAdapter(colors, mWorkingNote.getBgColorId(), colorId -> {
+ mWorkingNote.setBgColorId(colorId);
+ mWorkingNote.setWallpaper(null); // Clear wallpaper when color selected
+ dialog.dismiss();
+ });
+ dialogBinding.rvBackgroundOptions.setAdapter(adapter);
+
+ dialogBinding.btnPickWallpaper.setOnClickListener(v -> {
+ pickWallpaper();
+ dialog.dismiss();
+ });
+
+ dialogBinding.btnCustomColor.setOnClickListener(v -> {
+ showColorPickerDialog();
+ dialog.dismiss();
+ });
+
+ dialog.setContentView(dialogBinding.getRoot());
+ dialog.show();
+ }
+ private void showColorPickerDialog() {
+ DialogColorPickerBinding dialogBinding = DialogColorPickerBinding.inflate(getLayoutInflater());
+
int currentColor = android.graphics.Color.WHITE;
if (mWorkingNote.getBgColorId() < 0) {
currentColor = mWorkingNote.getBgColorId();
@@ -1553,10 +1595,10 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
android.graphics.Color.blue(currentColor)
};
- colorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
- sbRed.setProgress(rgb[0]);
- sbGreen.setProgress(rgb[1]);
- sbBlue.setProgress(rgb[2]);
+ dialogBinding.viewColorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
+ dialogBinding.sbRed.setProgress(rgb[0]);
+ dialogBinding.sbGreen.setProgress(rgb[1]);
+ dialogBinding.sbBlue.setProgress(rgb[2]);
android.widget.SeekBar.OnSeekBarChangeListener listener = new android.widget.SeekBar.OnSeekBarChangeListener() {
@Override
@@ -1564,7 +1606,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
if (seekBar.getId() == R.id.sb_red) rgb[0] = progress;
else if (seekBar.getId() == R.id.sb_green) rgb[1] = progress;
else if (seekBar.getId() == R.id.sb_blue) rgb[2] = progress;
- colorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
+ dialogBinding.viewColorPreview.setBackgroundColor(android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]));
}
@Override
@@ -1574,21 +1616,18 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
public void onStopTrackingTouch(android.widget.SeekBar seekBar) {}
};
- sbRed.setOnSeekBarChangeListener(listener);
- sbGreen.setOnSeekBarChangeListener(listener);
- sbBlue.setOnSeekBarChangeListener(listener);
+ dialogBinding.sbRed.setOnSeekBarChangeListener(listener);
+ dialogBinding.sbGreen.setOnSeekBarChangeListener(listener);
+ dialogBinding.sbBlue.setOnSeekBarChangeListener(listener);
new AlertDialog.Builder(this)
.setTitle("Custom Color")
- .setView(dialogView)
+ .setView(dialogBinding.getRoot())
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int newColor = android.graphics.Color.rgb(rgb[0], rgb[1], rgb[2]);
- // Use negative integer for custom color. Ensure it's negative.
- // ARGB color with alpha 255 is negative in Java int.
- // If alpha is 0, it might be positive. We assume full opacity.
newColor |= 0xFF000000;
mWorkingNote.setBgColorId(newColor);
- mNoteBgColorSelector.setVisibility(View.GONE);
+ mWorkingNote.setWallpaper(null);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
@@ -1647,21 +1686,53 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}).start();
}
+ private void saveWallpaperToPrivateStorage(android.net.Uri uri) {
+ new Thread(() -> {
+ try {
+ java.io.InputStream is = getContentResolver().openInputStream(uri);
+ if (is == null) return;
+
+ // Create wallpapers directory if not exists
+ java.io.File wallpapersDir = new java.io.File(getFilesDir(), "wallpapers");
+ if (!wallpapersDir.exists()) {
+ wallpapersDir.mkdirs();
+ }
+
+ // Create a unique file name
+ String fileName = "wp_" + System.currentTimeMillis() + ".jpg";
+ java.io.File destFile = new java.io.File(wallpapersDir, fileName);
+
+ java.io.FileOutputStream fos = new java.io.FileOutputStream(destFile);
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = is.read(buffer)) != -1) {
+ fos.write(buffer, 0, bytesRead);
+ }
+ fos.close();
+ is.close();
+
+ final String filePath = "file://" + destFile.getAbsolutePath();
+ runOnUiThread(() -> {
+ mWorkingNote.setWallpaper(filePath);
+ mNoteBgColorSelector.setVisibility(View.GONE);
+ });
+
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy wallpaper", e);
+ runOnUiThread(() -> {
+ showToast(R.string.failed_sdcard_export);
+ });
+ }
+ }).start();
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_WALLPAPER && resultCode == RESULT_OK && data != null) {
android.net.Uri uri = data.getData();
if (uri != null) {
- // Take persistent permissions
- try {
- getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- } catch (SecurityException e) {
- Log.e(TAG, "Failed to take persistable uri permission", e);
- }
-
- mWorkingNote.setWallpaper(uri.toString());
- mNoteBgColorSelector.setVisibility(View.GONE);
+ saveWallpaperToPrivateStorage(uri);
}
} else if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == RESULT_OK && data != null) {
android.net.Uri uri = data.getData();
@@ -1695,27 +1766,26 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
}
private void initRichTextToolbar() {
- mRichTextSelector = findViewById(R.id.rich_text_selector);
- findViewById(R.id.btn_bold).setOnClickListener(new OnClickListener() {
+ mRichTextSelector = binding.richTextSelector;
+ binding.btnBold.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyBold(mNoteEditor); }
});
- findViewById(R.id.btn_italic).setOnClickListener(new OnClickListener() {
+ binding.btnItalic.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyItalic(mNoteEditor); }
});
- findViewById(R.id.btn_underline).setOnClickListener(new OnClickListener() {
+ binding.btnUnderline.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyUnderline(mNoteEditor); }
});
- findViewById(R.id.btn_strikethrough).setOnClickListener(new OnClickListener() {
+ binding.btnStrikethrough.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyStrikethrough(mNoteEditor); }
});
- findViewById(R.id.btn_header).setOnClickListener(new OnClickListener() {
+ binding.btnHeader.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final CharSequence[] items = {"H1 (Largest)", "H2", "H3", "H4", "H5", "H6 (Smallest)", "Normal"};
AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this);
builder.setTitle("Header Level");
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
- // item index maps to level: 0->1, 1->2, ..., 5->6, 6->0 (Normal)
int level = (item == 6) ? 0 : (item + 1);
RichTextHelper.applyHeading(mNoteEditor, level);
}
@@ -1723,22 +1793,22 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
builder.show();
}
});
- findViewById(R.id.btn_list).setOnClickListener(new OnClickListener() {
+ binding.btnList.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyBullet(mNoteEditor); }
});
- findViewById(R.id.btn_quote).setOnClickListener(new OnClickListener() {
+ binding.btnQuote.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyQuote(mNoteEditor); }
});
- findViewById(R.id.btn_code).setOnClickListener(new OnClickListener() {
+ binding.btnCode.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.applyCode(mNoteEditor); }
});
- findViewById(R.id.btn_link).setOnClickListener(new OnClickListener() {
+ binding.btnLink.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.insertLink(NoteEditActivity.this, mNoteEditor); }
});
- findViewById(R.id.btn_divider).setOnClickListener(new OnClickListener() {
+ binding.btnDivider.setOnClickListener(new OnClickListener() {
public void onClick(View v) { RichTextHelper.insertDivider(mNoteEditor); }
});
- findViewById(R.id.btn_color_text).setOnClickListener(new OnClickListener() {
+ binding.btnColorText.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final CharSequence[] items = {"Black", "Red", "Blue"};
final int[] colors = {android.graphics.Color.BLACK, android.graphics.Color.RED, android.graphics.Color.BLUE};
@@ -1752,7 +1822,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen
builder.show();
}
});
- findViewById(R.id.btn_color_fill).setOnClickListener(new OnClickListener() {
+ binding.btnColorFill.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final CharSequence[] items = {"None", "Yellow", "Green", "Cyan"};
final int[] colors = {android.graphics.Color.TRANSPARENT, android.graphics.Color.YELLOW, android.graphics.Color.GREEN, android.graphics.Color.CYAN};
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java
index e8efb9e..789b515 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java
@@ -59,6 +59,12 @@ import android.view.ScaleGestureDetector;
import android.view.GestureDetector;
import android.text.style.ImageSpan;
import net.micode.notes.tool.RichTextHelper;
+import net.micode.notes.tool.SmartParser;
+import net.micode.notes.tool.SmartURLSpan;
+import android.text.TextWatcher;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.style.ClickableSpan;
import android.app.AlertDialog;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -93,6 +99,8 @@ public class NoteEditText extends EditText implements ScaleGestureDetector.OnSca
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
+ sSchemaActionResMap.put(SmartParser.SCHEME_TIME, R.string.note_link_time);
+ sSchemaActionResMap.put(SmartParser.SCHEME_GEO, R.string.note_link_geo);
}
@Override
@@ -213,6 +221,20 @@ public class NoteEditText extends EditText implements ScaleGestureDetector.OnSca
private void init(Context context) {
mScaleDetector = new ScaleGestureDetector(context, this);
+ setLinkTextColor(getResources().getColor(R.color.primary_color)); // 设置链接颜色
+ addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // 触发智能解析
+ SmartParser.parse(getContext(), s);
+ }
+ });
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
@@ -396,6 +418,16 @@ public class NoteEditText extends EditText implements ScaleGestureDetector.OnSca
int off = layout.getOffsetForHorizontal(line, x);
// 设置文本选择光标位置
Selection.setSelection(getText(), off);
+
+ // 检查是否有 ClickableSpan(如智能链接)
+ if (getText() instanceof Spannable) {
+ Spannable spannable = (Spannable) getText();
+ ClickableSpan[] links = spannable.getSpans(off, off, ClickableSpan.class);
+ if (links.length != 0) {
+ links[0].onClick(this);
+ return true;
+ }
+ }
break;
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java
index 5e60636..5ca30f1 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java
@@ -196,8 +196,8 @@ public class NoteItemData {
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
- // 如果是普通笔记且不是第一项,检查前一项是否为文件夹
- if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
+ // 如果是普通笔记或模板且不是第一项,检查前一项是否为文件夹
+ if ((mType == Notes.TYPE_NOTE || mType == Notes.TYPE_TEMPLATE) && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
// 前一项是文件夹或系统文件夹
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java
index 523120b..9fc2d88 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteSearchActivity.java
@@ -154,14 +154,36 @@ public class NoteSearchActivity extends AppCompatActivity implements SearchView.
mHistoryManager.addHistory(query);
}
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(Intent.EXTRA_UID, note.getId());
- // Pass search keyword for highlighting in editor
- // NoteEditActivity uses SearchManager.EXTRA_DATA_KEY for ID and USER_QUERY for keyword
- intent.putExtra(android.app.SearchManager.EXTRA_DATA_KEY, String.valueOf(note.getId()));
- intent.putExtra(android.app.SearchManager.USER_QUERY, mSearchView.getQuery().toString());
- startActivity(intent);
+ if (note.type == Notes.TYPE_TEMPLATE) {
+ // Apply template: create a new note based on this template
+ mRepository.applyTemplate(note.getId(), Notes.ID_ROOT_FOLDER, new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(Long newNoteId) {
+ runOnUiThread(() -> {
+ Intent intent = new Intent(NoteSearchActivity.this, NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.putExtra(Intent.EXTRA_UID, newNoteId);
+ startActivity(intent);
+ Toast.makeText(NoteSearchActivity.this, "已根据模板创建新笔记", Toast.LENGTH_SHORT).show();
+ });
+ }
+
+ @Override
+ public void onError(Exception e) {
+ runOnUiThread(() -> {
+ Toast.makeText(NoteSearchActivity.this, "应用模板失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ });
+ }
+ });
+ } else {
+ Intent intent = new Intent(this, NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.putExtra(Intent.EXTRA_UID, note.getId());
+ // Pass search keyword for highlighting in editor
+ intent.putExtra(android.app.SearchManager.EXTRA_DATA_KEY, String.valueOf(note.getId()));
+ intent.putExtra(android.app.SearchManager.USER_QUERY, query);
+ startActivity(intent);
+ }
}
@Override
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
index a58beb3..fa8b2de 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
@@ -368,6 +368,10 @@ public class NotesListActivity extends AppCompatActivity implements SidebarFragm
@Override public void onExportSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); Toast.makeText(this, "导出功能待实现", Toast.LENGTH_SHORT).show(); }
@Override public void onTemplateSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); viewModel.enterFolder(Notes.ID_TEMPLATE_FOLDER); }
@Override public void onSettingsSelected() { binding.drawerLayout.closeDrawer(GravityCompat.START); startActivity(new Intent(this, SettingsActivity.class)); }
+ @Override public void onCapsuleSelected() {
+ binding.drawerLayout.closeDrawer(GravityCompat.START);
+ startActivity(new Intent(this, CapsuleListActivity.class));
+ }
@Override public void onCreateFolder() { binding.drawerLayout.closeDrawer(GravityCompat.START); /* Show dialog */ }
@Override public void onCloseSidebar() { binding.drawerLayout.closeDrawer(GravityCompat.START); }
@Override public void onRenameFolder(long folderId) { /* Handle rename */ }
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
index 260f015..2fca2c1 100644
--- 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
@@ -148,6 +148,30 @@ public class NotesListFragment extends Fragment implements
NotesRepository.NoteInfo note = viewModel.getNotesLiveData().getValue().get(position);
if (note.type == Notes.TYPE_FOLDER) {
viewModel.enterFolder(note.getId());
+ } else if (note.type == Notes.TYPE_TEMPLATE) {
+ // Apply template: create a new note based on this template
+ viewModel.applyTemplate(note.getId(), new net.micode.notes.data.NotesRepository.Callback() {
+ @Override
+ public void onSuccess(Long newNoteId) {
+ // Create a temporary NoteInfo to open the editor
+ net.micode.notes.data.NotesRepository.NoteInfo newNote = new net.micode.notes.data.NotesRepository.NoteInfo();
+ newNote.setId(newNoteId);
+ newNote.setParentId(Notes.ID_ROOT_FOLDER);
+ newNote.type = Notes.TYPE_NOTE;
+
+ requireActivity().runOnUiThread(() -> {
+ openNoteEditor(newNote);
+ Toast.makeText(requireContext(), "已根据模板创建新笔记", Toast.LENGTH_SHORT).show();
+ });
+ }
+
+ @Override
+ public void onError(Exception e) {
+ requireActivity().runOnUiThread(() -> {
+ Toast.makeText(requireContext(), "应用模板失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ });
+ }
+ });
} else {
if (note.isLocked) {
pendingNote = note;
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java
index 9a6d238..38ee421 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java
@@ -69,7 +69,7 @@ public class NotesListItem extends LinearLayout {
* @param checked 该项是否被选中(仅在多选模式下有意义)
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
- if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
+ if (choiceMode && (data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE)) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
@@ -136,7 +136,7 @@ public class NotesListItem extends LinearLayout {
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
int resId;
- if (data.getType() == Notes.TYPE_NOTE) {
+ if (data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
resId = NoteItemBgResources.getNoteBgSingleRes(id);
} else if (data.isLast()) {
@@ -153,7 +153,7 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(resId);
// Apply tint for new colors
- if (data.getType() == Notes.TYPE_NOTE && (id >= net.micode.notes.tool.ResourceParser.MIDNIGHT_BLACK || id < 0)) {
+ if ((data.getType() == Notes.TYPE_NOTE || data.getType() == Notes.TYPE_TEMPLATE) && (id >= net.micode.notes.tool.ResourceParser.MIDNIGHT_BLACK || id < 0)) {
int color = net.micode.notes.tool.ResourceParser.getNoteBgColor(getContext(), id);
if (getBackground() != null) {
getBackground().setTint(color);
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
index 5dadec7..3a5c484 100644
--- 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
@@ -105,7 +105,7 @@ public class NotesRecyclerAdapter extends RecyclerView.Adapter {
+ if (listener != null) {
+ listener.onCapsuleSelected();
+ listener.onCloseSidebar();
+ }
+ });
+ }
+
menuLogin.setOnClickListener(v -> {
if (listener != null) {
listener.onLoginSelected();
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
index ebda9ec..2b051dd 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
@@ -160,44 +160,60 @@ public class NotesListViewModel extends ViewModel {
@Override
public void onSuccess(List path) {
folderPathLiveData.postValue(path);
- }
-
- @Override
- public void onError(Exception error) {
- Log.e(TAG, "Failed to load folder path", error);
- }
- });
-
- // 加载子文件夹 (Category Tabs) - Always load root folders to keep tabs visible
- repository.getSubFolders(Notes.ID_ROOT_FOLDER, new NotesRepository.Callback>() {
- @Override
- public void onSuccess(List folders) {
- // Construct the display list with "All" and "Uncategorized"
- List displayFolders = new ArrayList<>();
-
- // 1. "All" Folder (Virtual)
- NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
- allFolder.setId(Notes.ID_ALL_NOTES_FOLDER);
- allFolder.snippet = "所有"; // Name
- displayFolders.add(allFolder);
- // 2. Real Folders (from DB)
- if (folders != null) {
- displayFolders.addAll(folders);
+ // Determine if we are in template mode
+ boolean isTemplate = (folderId == Notes.ID_TEMPLATE_FOLDER);
+ if (!isTemplate && path != null) {
+ for (NotesRepository.NoteInfo info : path) {
+ if (info.getId() == Notes.ID_TEMPLATE_FOLDER) {
+ isTemplate = true;
+ break;
+ }
+ }
}
- // 3. "Uncategorized" Folder (Actually Root Folder)
- NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
- uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
- uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
- displayFolders.add(uncategorizedFolder);
-
- foldersLiveData.postValue(displayFolders);
+ final boolean templateMode = isTemplate;
+ long tabParentId = templateMode ? Notes.ID_TEMPLATE_FOLDER : Notes.ID_ROOT_FOLDER;
+
+ // 加载子文件夹 (Category Tabs)
+ repository.getSubFolders(tabParentId, new NotesRepository.Callback>() {
+ @Override
+ public void onSuccess(List folders) {
+ // Construct the display list with "All" and "Uncategorized"
+ List displayFolders = new ArrayList<>();
+
+ // 1. "All" / "All Templates" Folder (Virtual)
+ NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
+ allFolder.setId(templateMode ? Notes.ID_TEMPLATE_FOLDER : Notes.ID_ALL_NOTES_FOLDER);
+ allFolder.snippet = templateMode ? "所有模板" : "所有"; // Name
+ displayFolders.add(allFolder);
+
+ // 2. Real Folders (from DB)
+ if (folders != null) {
+ displayFolders.addAll(folders);
+ }
+
+ // 3. "Uncategorized" Folder (only for normal notes)
+ if (!templateMode) {
+ NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
+ uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
+ uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
+ displayFolders.add(uncategorizedFolder);
+ }
+
+ foldersLiveData.postValue(displayFolders);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ Log.e(TAG, "Failed to load sub-folders", error);
+ }
+ });
}
-
+
@Override
public void onError(Exception error) {
- Log.e(TAG, "Failed to load sub-folders", error);
+ Log.e(TAG, "Failed to load folder path", error);
}
});
diff --git a/src/Notesmaster/app/src/main/res/drawable/bg_bottom_sheet.xml b/src/Notesmaster/app/src/main/res/drawable/bg_bottom_sheet.xml
new file mode 100644
index 0000000..a91aca0
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/bg_bottom_sheet.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/preset_forest.xml b/src/Notesmaster/app/src/main/res/drawable/preset_forest.xml
new file mode 100644
index 0000000..aced12d
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/preset_forest.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/preset_lavender.xml b/src/Notesmaster/app/src/main/res/drawable/preset_lavender.xml
new file mode 100644
index 0000000..b9803bb
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/preset_lavender.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/preset_ocean.xml b/src/Notesmaster/app/src/main/res/drawable/preset_ocean.xml
new file mode 100644
index 0000000..dd2e1ee
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/preset_ocean.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/drawable/preset_sunset.xml b/src/Notesmaster/app/src/main/res/drawable/preset_sunset.xml
new file mode 100644
index 0000000..802f1ca
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/drawable/preset_sunset.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/activity_capsule_list.xml b/src/Notesmaster/app/src/main/res/layout/activity_capsule_list.xml
new file mode 100644
index 0000000..244af55
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/activity_capsule_list.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/src/Notesmaster/app/src/main/res/layout/dialog_background_selector.xml b/src/Notesmaster/app/src/main/res/layout/dialog_background_selector.xml
new file mode 100644
index 0000000..3f50cf6
--- /dev/null
+++ b/src/Notesmaster/app/src/main/res/layout/dialog_background_selector.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml b/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml
index 5d4d1b4..34683d4 100644
--- a/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml
+++ b/src/Notesmaster/app/src/main/res/layout/fragment_sidebar.xml
@@ -163,6 +163,34 @@
+
+
+
+
+
+
+
+
+
-
+ android:layout_marginHorizontal="12dp"
+ android:layout_marginVertical="6dp"
+ app:cardCornerRadius="16dp"
+ app:cardElevation="2dp"
+ app:cardBackgroundColor="@android:color/white">
-
-
-
+ android:orientation="vertical"
+ android:padding="16dp"
+ android:background="?android:attr/selectableItemBackground">
-
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/Notesmaster/app/src/main/res/layout/note_edit.xml b/src/Notesmaster/app/src/main/res/layout/note_edit.xml
index aba7781..5bacd39 100644
--- a/src/Notesmaster/app/src/main/res/layout/note_edit.xml
+++ b/src/Notesmaster/app/src/main/res/layout/note_edit.xml
@@ -15,482 +15,258 @@
limitations under the License.
-->
-
-
+ android:background="@color/background_color">
-
-
+
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:visibility="gone" />
-
-
-
-
+ android:layout_height="match_parent"
+ android:background="#1A000000"
+ android:visibility="gone" />
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+ android:layout_height="?attr/actionBarSize"
+ android:background="@android:color/transparent"
+ app:navigationIcon="@android:drawable/ic_menu_close_clear_cancel"
+ app:title="@string/menu_edit_note"
+ app:titleTextAppearance="@style/TextAppearance.Material3.TitleMedium" />
+
+
+
+
-
-
-
-
+
+
+
+ android:layout_height="wrap_content"
+ app:cardCornerRadius="24dp"
+ app:cardElevation="0dp"
+ app:cardBackgroundColor="#CCFFFFFF"
+ app:strokeWidth="0dp">
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="24dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:textSize="18sp"
+ android:textColor="@color/text_color_primary"
+ android:lineSpacingMultiplier="1.5"
+ android:fontFamily="sans-serif" />
-
-
-
+
+
-
-
+
+
+
+
+
+ android:layout_height="56dp"
+ android:scrollbars="none">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:gravity="center_vertical"
+ android:paddingHorizontal="12dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Notesmaster/app/src/main/res/values/colors.xml b/src/Notesmaster/app/src/main/res/values/colors.xml
index 8c4a5a3..6405010 100644
--- a/src/Notesmaster/app/src/main/res/values/colors.xml
+++ b/src/Notesmaster/app/src/main/res/values/colors.xml
@@ -43,6 +43,16 @@
#FFE0B2
#E1BEE7
+
+ #FF512F
+ #DD2476
+ #2193B0
+ #6DD5ED
+ #11998E
+ #38EF7D
+ #834D9B
+ #D04ED6
+
#1976D2
#2196F3
diff --git a/src/Notesmaster/app/src/main/res/values/strings.xml b/src/Notesmaster/app/src/main/res/values/strings.xml
index 2c51ccd..80148b2 100644
--- a/src/Notesmaster/app/src/main/res/values/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values/strings.xml
@@ -35,6 +35,8 @@
Send email
Browse web
Open map
+ 创建闹钟
+ 查看地图
/MIUI/notes/
notes_%s.txt