便签编辑UI优化、背景自定义和时间、地点智能识别 #32

Merged
mbls3xqnp merged 3 commits from baoerjun_branch into master 3 weeks ago

@ -4,21 +4,6 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<<<<<<< HEAD
<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" />
=======
<DropdownSelection timestamp="2026-01-30T10:15:59.196226600Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\啊?\.android\avd\Pixel_4a_API_31.avd" />
>>>>>>> baoerjun_branch
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

@ -22,6 +22,24 @@
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<!-- 允许接收系统启动完成广播,用于初始化闹钟 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- 允许设置闹钟 -->
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SET_ALARM" />
<!-- 允许在 Android 11+ 上查询外部应用 -->
<queries>
<intent>
<action android:name="android.intent.action.SET_ALARM" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="geo" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
<!-- 阿里云推送服务权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -110,6 +128,12 @@
android:exported="false" />
<!-- ==================== 待办任务列表活动 ==================== -->
<activity
android:name=".ui.CapsuleListActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait"
android:theme="@style/Theme.Notesmaster" />
<activity
android:name=".ui.TaskListActivity"
android:label="Tasks"

@ -149,6 +149,14 @@ public class MainActivity extends AppCompatActivity implements SidebarFragment.O
closeSidebar();
}
@Override
public void onCapsuleSelected() {
Log.d(TAG, "Capsule selected");
Intent intent = new Intent(this, net.micode.notes.ui.CapsuleListActivity.class);
startActivity(intent);
closeSidebar();
}
@Override
public void onCreateFolder() {
Log.d(TAG, "Create folder");

@ -62,6 +62,11 @@ public class Notes {
*/
public static final int TYPE_TASK = 3;
/**
*
*/
public static final int TYPE_TEMPLATE = 4;
/**
* ID
* {@link Notes#ID_ROOT_FOLDER }

@ -982,21 +982,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 工作模板
long workFolderId = insertFolder(db, Notes.ID_TEMPLATE_FOLDER, "工作");
if (workFolderId > 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

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

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

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

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

@ -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
};
/** 标题栏背景资源数组 */

@ -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;
/**
*
* <p>
* Android AI (TextClassifier)
* URL
* </p>
*/
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);
}
}
}

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

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

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

@ -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
* </p>
*/
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<Integer> 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};

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

@ -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()) {
// 前一项是文件夹或系统文件夹

@ -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<Long>() {
@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

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

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

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

@ -105,7 +105,7 @@ public class NotesRecyclerAdapter extends RecyclerView.Adapter<NotesRecyclerAdap
}
public void bind(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)) {
checkBox.setVisibility(View.VISIBLE);
checkBox.setChecked(checked);
} else {

@ -71,6 +71,7 @@ public class SidebarFragment extends Fragment {
private LinearLayout menuTemplates;
private LinearLayout menuExport;
private LinearLayout menuSettings;
private LinearLayout menuCapsule;
private LinearLayout menuLogin;
private LinearLayout menuLogout;
@ -106,6 +107,7 @@ public class SidebarFragment extends Fragment {
void onExportSelected();
void onTemplateSelected();
void onSettingsSelected();
void onCapsuleSelected();
void onCreateFolder();
void onCloseSidebar();
void onRenameFolder(long folderId);
@ -170,6 +172,7 @@ public class SidebarFragment extends Fragment {
menuTemplates = view.findViewById(R.id.menu_templates);
menuExport = view.findViewById(R.id.menu_export);
menuSettings = view.findViewById(R.id.menu_settings);
menuCapsule = view.findViewById(R.id.menu_capsule);
menuLogin = view.findViewById(R.id.menu_login);
menuLogout = view.findViewById(R.id.menu_logout);
@ -237,6 +240,15 @@ public class SidebarFragment extends Fragment {
}
});
if (menuCapsule != null) {
menuCapsule.setOnClickListener(v -> {
if (listener != null) {
listener.onCapsuleSelected();
listener.onCloseSidebar();
}
});
}
menuLogin.setOnClickListener(v -> {
if (listener != null) {
listener.onLoginSelected();

@ -160,44 +160,60 @@ public class NotesListViewModel extends ViewModel {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> 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<List<NotesRepository.NoteInfo>>() {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> folders) {
// Construct the display list with "All" and "Uncategorized"
List<NotesRepository.NoteInfo> 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<List<NotesRepository.NoteInfo>>() {
@Override
public void onSuccess(List<NotesRepository.NoteInfo> folders) {
// Construct the display list with "All" and "Uncategorized"
List<NotesRepository.NoteInfo> 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);
}
});

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

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_forest_start"
android:endColor="@color/preset_forest_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_lavender_start"
android:endColor="@color/preset_lavender_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_ocean_start"
android:endColor="@color/preset_ocean_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="@color/preset_sunset_start"
android:endColor="@color/preset_sunset_end"
android:angle="45" />

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@drawable/bg_bottom_sheet">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择背景"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_color_primary"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="纯色与预设"
android:textSize="14sp"
android:textColor="@color/text_color_secondary"
android:layout_marginBottom="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_background_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更多选项"
android:textSize="14sp"
android:textColor="@color/text_color_secondary"
android:layout_marginBottom="12dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_pick_wallpaper"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="选择图片"
app:icon="@android:drawable/ic_menu_gallery"
android:layout_marginRight="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_custom_color"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="自定义颜色"
app:icon="@android:drawable/ic_menu_edit"
android:layout_marginLeft="8dp" />
</LinearLayout>
</LinearLayout>

@ -163,6 +163,34 @@
</LinearLayout>
<!-- 速记胶囊 -->
<LinearLayout
android:id="@+id/menu_capsule"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:background="?attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_menu_rich_text"
app:tint="?attr/colorOnSurfaceVariant" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:text="速记胶囊"
android:textSize="14sp"
android:textColor="?attr/colorOnSurface" />
</LinearLayout>
<!-- 同步设置 -->
<LinearLayout
android:id="@+id/menu_sync_settings"

@ -1,36 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView 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="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@android:color/white">
<TextView
android:id="@+id/tv_summary"
<LinearLayout
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" />
android:orientation="vertical"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
<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" />
<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="3"
android:ellipsize="end" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="12sp"
android:textColor="@android:color/darker_gray" />
<TextView
android:id="@+id/tv_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="11sp"
android:textColor="#1976D2"
android:paddingHorizontal="8dp"
android:paddingVertical="2dp"
android:background="@drawable/swipe_button_bg"
android:backgroundTint="#F0F0F0" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

@ -15,482 +15,258 @@
limitations under the License.
-->
<!-- 统一使用Material风格与列表页面一致 -->
<LinearLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/note_edit_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:background="@color/background_color">
<!-- 统一使用MaterialToolbar与列表页面一致 -->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<!-- 全屏背景层 -->
<ImageView
android:id="@+id/iv_note_wallpaper"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="@string/menu_edit_note"
app:navigationIcon="@android:drawable/ic_menu_close_clear_cancel" />
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="gone" />
<!-- 主内容区域 -->
<LinearLayout
<View
android:id="@+id/view_bg_mask"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/note_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="match_parent"
android:background="#1A000000"
android:visibility="gone" />
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginRight="8dp"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<TextView
android:id="@+id/tv_char_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:text="0 字"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/title_alert" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="2dp"
android:layout_marginRight="8dp"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
</LinearLayout>
<!-- 顶部 Toolbar -->
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp">
<!-- Title Input Field -->
<EditText
android:id="@+id/et_title"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="Title"
android:paddingLeft="15dip"
android:paddingRight="15dip"
android:paddingTop="10dip"
android:paddingBottom="5dip"
android:singleLine="true"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:textColorHint="@android:color/darker_gray" />
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" />
</com.google.android.material.appbar.AppBarLayout>
<!-- 编辑区域容器 -->
<androidx.core.widget.NestedScrollView
android:id="@+id/sv_note_edit_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="7dp"
android:background="@drawable/bg_color_btn_mask" />
<ScrollView
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="20dp"
android:paddingTop="8dp"
android:paddingBottom="100dp">
<!-- 玻璃拟态卡片容器 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/cv_editor_surface"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dp">
android:layout_height="wrap_content"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#CCFFFFFF"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<!-- 状态与操作行 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:alpha="0.6"
android:textColor="@color/text_color_primary"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/tv_char_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="12sp"
android:alpha="0.4"
android:textColor="@color/text_color_primary"
android:text="0 characters" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/title_alert"
android:tint="@color/fab_color"
android:visibility="gone" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:textSize="12sp"
android:textColor="@color/fab_color"
android:visibility="gone" />
<ImageButton
android:id="@+id/btn_set_bg_color"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/bg_btn_set_color"
android:padding="8dp"
android:scaleType="centerInside" />
</LinearLayout>
</RelativeLayout>
<!-- 标题输入 -->
<EditText
android:id="@+id/et_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="Note Title"
android:paddingVertical="8dp"
android:singleLine="true"
android:textSize="28sp"
android:textStyle="bold"
android:fontFamily="sans-serif-black"
android:textColor="@color/text_color_primary"
android:textColorHint="#4D000000" />
<View
android:layout_width="40dp"
android:layout_height="4dp"
android:background="#21000000"
android:layout_marginTop="12dp"
android:layout_marginBottom="24dp" />
<!-- 正文编辑 -->
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:gravity="top"
android:background="@null"
android:autoLink="all"
android:linksClickable="false"
android:minLines="12"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
android:textSize="18sp"
android:textColor="@color/text_color_primary"
android:lineSpacingMultiplier="1.5"
android:fontFamily="sans-serif" />
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dp"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ImageView
android:layout_width="match_parent"
android:layout_height="7dp"
android:background="@drawable/bg_color_btn_mask" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dp"
</androidx.core.widget.NestedScrollView>
<!-- 底部悬浮富文本工具栏 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/rich_text_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="24dp"
android:visibility="gone"
app:cardCornerRadius="30dp"
app:cardElevation="8dp"
app:cardBackgroundColor="#F0FFFFFF"
app:strokeWidth="0.5dp"
app:strokeColor="#1A000000">
<HorizontalScrollView
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
android:layout_height="56dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/note_bg_color_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/note_edit_color_selector_panel"
android:layout_marginTop="30dp"
android:layout_marginRight="8dp"
android:layout_gravity="top|right"
android:visibility="gone"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_bg_color_selector"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
<LinearLayout
android:id="@+id/font_size_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/font_size_selector_bg"
android:layout_gravity="bottom"
android:visibility="gone">
<FrameLayout
android:id="@+id/ll_font_small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_small"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_small"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_small_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="6dp"
android:layout_marginBottom="-7dp"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_normal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_normal"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_normal"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_medium_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dp"
android:layout_marginBottom="-7dp"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_large"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_large"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_large"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_large_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dp"
android:layout_marginBottom="-7dp"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_super"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_super"
android:layout_marginBottom="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_super"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_super_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dp"
android:layout_marginBottom="-7dp"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/rich_text_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/font_size_selector_bg"
android:layout_gravity="bottom"
android:visibility="gone"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_bold"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Bold"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_italic"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Italic"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_underline"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Underline"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_strikethrough"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_strikethrough"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Strikethrough"
android:scaleType="centerInside" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:background="#CCCCCC"/>
<ImageButton
android:id="@+id/btn_header"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_header"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Header"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_list"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_list_bulleted"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="List"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_quote"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_quote"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Quote"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_code"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_code"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Code"
android:scaleType="centerInside" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:background="#CCCCCC"/>
<ImageButton
android:id="@+id/btn_link"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_insert_link"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Link"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_divider"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_insert_divider"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Divider"
android:scaleType="centerInside" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:background="#CCCCCC"/>
<ImageButton
android:id="@+id/btn_color_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_color_text"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Text Color"
android:scaleType="centerInside" />
<ImageButton
android:id="@+id/btn_color_fill"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_format_color_fill"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="Background Color"
android:scaleType="centerInside" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
</LinearLayout>
android:gravity="center_vertical"
android:paddingHorizontal="12dp">
<ImageButton android:id="@+id/btn_bold" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_bold" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_italic" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_italic" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_underline" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_underline" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_strikethrough" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_strikethrough" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<View android:layout_width="1dp" android:layout_height="24dp" android:background="#1A000000" android:layout_marginHorizontal="8dp"/>
<ImageButton android:id="@+id/btn_header" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_header" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_list" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_list_bulleted" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_quote" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_quote" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_code" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_code" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<View android:layout_width="1dp" android:layout_height="24dp" android:background="#1A000000" android:layout_marginHorizontal="8dp"/>
<ImageButton android:id="@+id/btn_link" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_insert_link" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_divider" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_insert_divider" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_color_text" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_color_text" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
<ImageButton android:id="@+id/btn_color_fill" android:layout_width="44dp" android:layout_height="44dp" android:src="@drawable/ic_format_color_fill" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/on_primary_color"/>
</LinearLayout>
</HorizontalScrollView>
</com.google.android.material.card.MaterialCardView>
<!-- 兼容性保留 ID 的隐藏视图 -->
<View android:id="@+id/note_bg_color_selector" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/font_size_selector" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_small" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_normal" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_large" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/ll_font_super" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_small_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_medium_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_large_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/iv_super_select" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
<View android:id="@+id/sv_note_edit" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -43,6 +43,16 @@
<color name="bg_warm">#FFE0B2</color>
<color name="bg_cool">#E1BEE7</color>
<!-- Modern Presets -->
<color name="preset_sunset_start">#FF512F</color>
<color name="preset_sunset_end">#DD2476</color>
<color name="preset_ocean_start">#2193B0</color>
<color name="preset_ocean_end">#6DD5ED</color>
<color name="preset_forest_start">#11998E</color>
<color name="preset_forest_end">#38EF7D</color>
<color name="preset_lavender_start">#834D9B</color>
<color name="preset_lavender_end">#D04ED6</color>
<!-- Drawer Header Colors - Material Design 3 Blue Theme -->
<color name="drawer_header_gradient_start">#1976D2</color>
<color name="drawer_header_gradient_center">#2196F3</color>

@ -35,6 +35,8 @@
<string name="note_link_email">Send email</string>
<string name="note_link_web">Browse web</string>
<string name="note_link_other">Open map</string>
<string name="note_link_time">创建闹钟</string>
<string name="note_link_geo">查看地图</string>
<!-- Text export file information -->
<string name="file_path" translatable="false">/MIUI/notes/</string>
<string name="file_name_txt_format" translatable="false">notes_%s.txt</string>

Loading…
Cancel
Save