From a6feecbe39c08b981aade698765337993124d70d Mon Sep 17 00:00:00 2001
From: mc19 <2716188191@qq.com>
Date: Thu, 29 Jan 2026 20:36:00 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=9A=90=E7=A7=81=E9=94=81?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/notes/data/Notes.java | 62 +-
src/notes/data/NotesDatabaseHelper.java | 37 +-
src/notes/data/UserDatabaseHelper.java | 3 -
src/notes/model/WorkingNote.java | 2 +-
src/notes/tool/PrivacyLockManager.java | 353 +++
src/notes/tool/UserManager.java | 42 +-
src/notes/ui/GestureLockView.java | 264 +++
src/notes/ui/LoginRegisterActivity.java | 42 +-
src/notes/ui/NoteEditActivity.java | 2676 ++++++++++++-----------
src/notes/ui/NotesListActivity.java | 358 ++-
src/notes/ui/NotesListItem.java | 22 +-
11 files changed, 2572 insertions(+), 1289 deletions(-)
create mode 100644 src/notes/tool/PrivacyLockManager.java
create mode 100644 src/notes/ui/GestureLockView.java
diff --git a/src/notes/data/Notes.java b/src/notes/data/Notes.java
index 2958f09..414c31f 100644
--- a/src/notes/data/Notes.java
+++ b/src/notes/data/Notes.java
@@ -26,20 +26,17 @@ import android.net.Uri;
/**
*Notes是便签数据库类
-
*
定义了URI常量、便签/文件夹类型常量、Intent扩展字段名、数据库接口、两种与业务实体
* 本类所有字段均用static final修饰,说明字段不会变,可以直接用
-
-* @author 蒙程
-* @since [起始版本]
*/
public class Notes {
- //一、ContentProvider权威域名
public static final String AUTHORITY = "micode_notes"; //拼接URI的前缀
public static final String TAG = "Notes"; //日志显示Notes
- //二、便签/文件夹/系统文件夹 类型常量
+ /**
+ * 定义便签/文件夹/系统文件夹类型常量
+ */
public static final int TYPE_NOTE = 0; //普通文本便签类型常量为0
public static final int TYPE_FOLDER = 1; //文件夹类型常量为1
public static final int TYPE_SYSTEM = 2; //系统文件夹(如回收站)类型常量为2
@@ -50,15 +47,16 @@ public class Notes {
* {@link Notes#ID_TEMPORARY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
-
- //三、系统保留文件夹 ID
public static final int ID_ROOT_FOLDER = 0; //默认文件夹ID为0
public static final int ID_TEMPORARY_FOLDER = -1; //临时文件夹
public static final int ID_CALL_RECORD_FOLDER = -2; //通话记录文件夹ID,保存由通话备忘功能生成的便签
public static final int ID_TRASH_FOLDER = -3;
- //四、Intent是什么?
+ /**
+ * Intent实现各个组件(界面)之间数据的传递
+ *
+ */
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; //提醒时间戳
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; //便签背景颜色
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; //桌面便签小部件
@@ -66,14 +64,16 @@ public class Notes {
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; //便签父文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; //通话记录时间戳
- //五、桌面小部件类型常量
+ /**
+ * 定义桌面小部件类型常量
+ */
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
/**
- *六、便签数据MIME汇总常量类
- * Android 的 ContentResolver 在查询/插入/打开文件时会先问 URI 对应的 MIME:
+ * MIME是描述文件类型的一种标准
+ * Android的ContentResolver在查询/插入/打开文件时会首先查询MIME类型
*/
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; //文本便签MIME类型
@@ -81,7 +81,6 @@ public class Notes {
public static final String IMAGE = ImageData.CONTENT_ITEM_TYPE; //图片MIME类型
}
- //七、基础URI
/**
* Uri to query all notes and folders
*/
@@ -93,10 +92,9 @@ public class Notes {
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
- //八、数据库列名接口 —— Note 表
/**
- * NoteColumns是便签数据库列名接口,只定义/声明一些方法、变量
- * 此处声明了便签数据列名有ID,PARENT_ID,创建日期,修改日期,提醒日期等
+ * NoteColumns是便签数据库列名接口
+ * 声明便签信息列名,包括便签ID,父文件夹ID,创建日期,修改日期,提醒日期等。
*/
public interface NoteColumns {
/**
@@ -227,13 +225,29 @@ public class Notes {
* Type : INTEGER (long)
*/
public static final String VERSION = "version";
+
+ /**
+ * Lock status: 0-unlocked, 1-locked
+ * Type: INTEGER
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * Lock type: 0-none, 1-password, 2-gesture
+ * Type: INTEGER
+ */
+ public static final String LOCK_TYPE = "lock_type";
+
+ /**
+ * Encrypted password for privacy lock
+ * Type: TEXT
+ */
+ public static final String ENCRYPTED_PASSWORD = "encrypted_password";
}
- //九、数据库列名接口 —— Data 表
-
/**
- * DataColumns是数据库列名接口
+ * 数据库列名接口,声明与便签内容相关的字段
*/
public interface DataColumns {
/**
@@ -322,8 +336,10 @@ public class Notes {
}
- //十、文本便签实体常量类
- //文本便签TextNote类实现DataColumns接口中声明的方法
+ /**
+ * TextNote是文本便签实体常量类
+ * 实现DataColumns接口中声明的方法
+ */
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
@@ -341,8 +357,8 @@ public class Notes {
}
/**
- *通话记录便签实体常量类
- * 通话记录CallNote类也实现了DataColumns接口,体现了接口的多实现特性。
+ * CallNote是通话记录便签实体常量类
+ * 实现了DataColumns接口,与TextNote一起,体现了接口的多实现特性。
*/
public static final class CallNote implements DataColumns {
/**
diff --git a/src/notes/data/NotesDatabaseHelper.java b/src/notes/data/NotesDatabaseHelper.java
index b761c42..ee7df8c 100644
--- a/src/notes/data/NotesDatabaseHelper.java
+++ b/src/notes/data/NotesDatabaseHelper.java
@@ -54,7 +54,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
*/
private static final String DB_NAME = "note.db";
- private static final int DB_VERSION = 6;
+ private static final int DB_VERSION = 7;
/**表明常量接口*/
@@ -95,6 +95,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
+ NoteColumns.ALERT_LATITUDE + " REAL NOT NULL DEFAULT 0," +
+ NoteColumns.ALERT_LONGITUDE + " REAL NOT NULL DEFAULT 0," +
+ NoteColumns.ALERT_RADIUS + " REAL NOT NULL DEFAULT 0," +
+ NoteColumns.ALERT_LOCATION_NAME + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
@@ -117,7 +121,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
- DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
+ DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.RICH_TEXT_FORMAT + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.IMAGE_PATH + " TEXT NOT NULL DEFAULT ''" +
")";
/**
@@ -412,6 +418,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
+ if (oldVersion == 6) {
+ upgradeToV7(db);
+ oldVersion++;
+ }
+
+ if (oldVersion == 7) {
+ upgradeToV8(db);
+ oldVersion++;
+ }
+
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
@@ -463,4 +479,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 为data表添加一个专门存储图片路径的列
db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN image_path TEXT NOT NULL DEFAULT ''");
}
+
+ //v6到v7的升级,为note表新增位置提醒相关的列
+ private void upgradeToV7(SQLiteDatabase db) {
+ // 为note表添加位置提醒相关的列
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LATITUDE + " REAL NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LONGITUDE + " REAL NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_RADIUS + " REAL NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ALERT_LOCATION_NAME + " TEXT NOT NULL DEFAULT ''");
+ }
+
+ //v7到v8的升级,为note表新增隐私锁相关的列
+ private void upgradeToV8(SQLiteDatabase db) {
+ // 为note表添加隐私锁相关列
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCKED + " INTEGER NOT NULL DEFAULT 0"); // 锁定状态:0-未锁定,1-已锁定
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0"); // 锁类型:0-无锁,1-密码锁,2-手势锁
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.ENCRYPTED_PASSWORD + " TEXT NOT NULL DEFAULT ''"); // 加密后的密码
+ }
}
diff --git a/src/notes/data/UserDatabaseHelper.java b/src/notes/data/UserDatabaseHelper.java
index cb21d34..64d9c4c 100644
--- a/src/notes/data/UserDatabaseHelper.java
+++ b/src/notes/data/UserDatabaseHelper.java
@@ -108,10 +108,8 @@ public class UserDatabaseHelper extends SQLiteOpenHelper {
boolean authenticated = cursor.getCount() > 0;
cursor.close();
-
return authenticated;
}
-
/**
* 检查用户名是否存在
* @param username 用户名
@@ -128,7 +126,6 @@ public class UserDatabaseHelper extends SQLiteOpenHelper {
boolean exists = cursor.getCount() > 0;
cursor.close();
-
return exists;
}
diff --git a/src/notes/model/WorkingNote.java b/src/notes/model/WorkingNote.java
index f3c37c8..cf00c34 100644
--- a/src/notes/model/WorkingNote.java
+++ b/src/notes/model/WorkingNote.java
@@ -82,7 +82,7 @@ public class WorkingNote {
DataColumns.DATA4,
DataColumns.DATA5,
DataColumns.RICH_TEXT_FORMAT,
- DataColumns.IMAGE_PATH,
+ DataColumns.IMAGE_PATH
};
//指定查询Note表时需要返回的字段
diff --git a/src/notes/tool/PrivacyLockManager.java b/src/notes/tool/PrivacyLockManager.java
new file mode 100644
index 0000000..be9de9d
--- /dev/null
+++ b/src/notes/tool/PrivacyLockManager.java
@@ -0,0 +1,353 @@
+package net.micode.notes.tool;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.NoteColumns;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 隐私锁管理类,用于处理便签和文件夹的隐私锁功能
+ */
+public class PrivacyLockManager {
+ private static final String TAG = "PrivacyLockManager";
+
+ // 锁类型常量
+ public static final int LOCK_TYPE_NONE = 0;
+ public static final int LOCK_TYPE_PASSWORD = 1;
+ public static final int LOCK_TYPE_GESTURE = 2;
+
+ // 锁状态常量
+ public static final int LOCK_STATUS_UNLOCKED = 0;
+ public static final int LOCK_STATUS_LOCKED = 1;
+
+ private Context mContext;
+
+ public PrivacyLockManager(Context context) {
+ this.mContext = context;
+ }
+
+ /**
+ * SHA256加密密码
+ * @param password 原始密码
+ * @return 加密后的密码
+ */
+ public static String encryptPassword(String password) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(password.getBytes("UTF-8"));
+
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : hash) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+
+ return hexString.toString();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error encrypting password", ex);
+ return "";
+ }
+ }
+
+ /**
+ * 检查指定便签是否已加锁
+ * @param noteId 便签ID
+ * @return 是否已加锁
+ */
+ public boolean isNoteLocked(long noteId) {
+ String[] projection = new String[]{NoteColumns.LOCKED};
+ String selection = NoteColumns.ID + " = ?";
+ String[] selectionArgs = new String[]{String.valueOf(noteId)};
+
+ Cursor cursor = mContext.getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ );
+
+ boolean isLocked = false;
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ isLocked = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCKED)) == LOCK_STATUS_LOCKED;
+ }
+ cursor.close();
+ }
+
+ return isLocked;
+ }
+
+ /**
+ * 为指定便签添加隐私锁
+ * @param noteId 便签ID
+ * @param lockType 锁类型
+ * @param passwordOrGesture 加密后的密码或手势序列(如果使用密码锁或手势锁)
+ * @return 是否成功
+ */
+ public boolean addPrivacyLock(long noteId, int lockType, String passwordOrGesture) {
+ if (noteId <= 0) {
+ Log.e(TAG, "Invalid note ID: " + noteId);
+ return false;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.LOCKED, LOCK_STATUS_LOCKED);
+ values.put(NoteColumns.LOCK_TYPE, lockType);
+
+ if ((lockType == LOCK_TYPE_PASSWORD || lockType == LOCK_TYPE_GESTURE) && passwordOrGesture != null) {
+ values.put(NoteColumns.ENCRYPTED_PASSWORD, passwordOrGesture);
+ } else if (lockType == LOCK_TYPE_NONE) {
+ values.put(NoteColumns.ENCRYPTED_PASSWORD, "");
+ }
+
+ int rowsUpdated = mContext.getContentResolver().update(
+ Notes.CONTENT_NOTE_URI,
+ values,
+ NoteColumns.ID + " = ?",
+ new String[]{String.valueOf(noteId)}
+ );
+
+ return rowsUpdated > 0;
+ }
+
+ /**
+ * 移除指定便签的隐私锁
+ * @param noteId 便签ID
+ * @return 是否成功
+ */
+ public boolean removePrivacyLock(long noteId) {
+ if (noteId <= 0) {
+ Log.e(TAG, "Invalid note ID: " + noteId);
+ return false;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.LOCKED, LOCK_STATUS_UNLOCKED);
+ values.put(NoteColumns.LOCK_TYPE, LOCK_TYPE_NONE);
+ values.put(NoteColumns.ENCRYPTED_PASSWORD, "");
+
+ int rowsUpdated = mContext.getContentResolver().update(
+ Notes.CONTENT_NOTE_URI,
+ values,
+ NoteColumns.ID + " = ?",
+ new String[]{String.valueOf(noteId)}
+ );
+
+ return rowsUpdated > 0;
+ }
+
+ /**
+ * 验证密码是否正确
+ * @param noteId 便签ID
+ * @param password 待验证的密码
+ * @return 是否验证成功
+ */
+ public boolean verifyPassword(long noteId, String password) {
+ String[] projection = new String[]{NoteColumns.ENCRYPTED_PASSWORD};
+ String selection = NoteColumns.ID + " = ?";
+ String[] selectionArgs = new String[]{String.valueOf(noteId)};
+
+ Cursor cursor = mContext.getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ );
+
+ boolean isValid = false;
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ String storedEncryptedPassword = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.ENCRYPTED_PASSWORD));
+ String encryptedInput = encryptPassword(password);
+
+ isValid = storedEncryptedPassword.equals(encryptedInput);
+ }
+ cursor.close();
+ }
+
+ return isValid;
+ }
+
+ /**
+ * 获取便签的锁类型
+ * @param noteId 便签ID
+ * @return 锁类型
+ */
+ public int getLockType(long noteId) {
+ String[] projection = new String[]{NoteColumns.LOCK_TYPE};
+ String selection = NoteColumns.ID + " = ?";
+ String[] selectionArgs = new String[]{String.valueOf(noteId)};
+
+ Cursor cursor = mContext.getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ );
+
+ int lockType = LOCK_TYPE_NONE;
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ lockType = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCK_TYPE));
+ }
+ cursor.close();
+ }
+
+ return lockType;
+ }
+
+ /**
+ * 为整个文件夹及其所有子便签添加隐私锁
+ * @param folderId 文件夹ID
+ * @param lockType 锁类型
+ * @param passwordOrGesture 加密后的密码或手势序列(如果使用密码锁或手势锁)
+ * @return 是否成功
+ */
+ public boolean addPrivacyLockToFolder(long folderId, int lockType, String passwordOrGesture) {
+ // 首先为文件夹本身添加锁
+ boolean folderSuccess = addPrivacyLock(folderId, lockType, passwordOrGesture);
+
+ // 然后为文件夹内的所有便签添加锁
+ String[] projection = new String[]{NoteColumns.ID};
+ String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.TYPE + " = ?";
+ String[] selectionArgs = new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_NOTE)};
+
+ Cursor cursor = mContext.getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ );
+
+ boolean allNotesSuccess = true;
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
+ boolean noteSuccess = addPrivacyLock(noteId, lockType, passwordOrGesture);
+ if (!noteSuccess) {
+ allNotesSuccess = false;
+ }
+ }
+ cursor.close();
+ }
+
+ return folderSuccess && allNotesSuccess;
+ }
+
+ /**
+ * 移除整个文件夹及其所有子便签的隐私锁
+ * @param folderId 文件夹ID
+ * @return 是否成功
+ */
+ public boolean removePrivacyLockFromFolder(long folderId) {
+ // 首先移除文件夹本身的锁
+ boolean folderSuccess = removePrivacyLock(folderId);
+
+ // 然后移除文件夹内的所有便签的锁
+ String[] projection = new String[]{NoteColumns.ID};
+ String selection = NoteColumns.PARENT_ID + " = ? AND " + NoteColumns.TYPE + " = ?";
+ String[] selectionArgs = new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_NOTE)};
+
+ Cursor cursor = mContext.getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ );
+
+ boolean allNotesSuccess = true;
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
+ boolean noteSuccess = removePrivacyLock(noteId);
+ if (!noteSuccess) {
+ allNotesSuccess = false;
+ }
+ }
+ cursor.close();
+ }
+
+ return folderSuccess && allNotesSuccess;
+ }
+
+ /**
+ * 验证手势是否正确
+ * @param noteId 便签ID
+ * @param gestureSequence 手势序列
+ * @return 是否验证成功
+ */
+ public boolean verifyGesture(long noteId, List gestureSequence) {
+ String[] projection = new String[]{NoteColumns.ENCRYPTED_PASSWORD};
+ String selection = NoteColumns.ID + " = ?";
+ String[] selectionArgs = new String[]{String.valueOf(noteId)};
+
+ Cursor cursor = mContext.getContentResolver().query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ selectionArgs,
+ null
+ );
+
+ boolean isValid = false;
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ String storedGestureSequence = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.ENCRYPTED_PASSWORD));
+
+ // 将手势序列转换为字符串进行比较
+ StringBuilder gestureBuilder = new StringBuilder();
+ for (int point : gestureSequence) {
+ gestureBuilder.append(point);
+ }
+ String inputGestureSequence = gestureBuilder.toString();
+
+ isValid = storedGestureSequence.equals(inputGestureSequence);
+ }
+ cursor.close();
+ }
+
+ return isValid;
+ }
+
+ /**
+ * 将手势点列表转换为字符串
+ * @param gesturePoints 手势点列表
+ * @return 手势序列字符串
+ */
+ public static String gestureToString(List gesturePoints) {
+ StringBuilder sb = new StringBuilder();
+ for (int point : gesturePoints) {
+ sb.append(point);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 将手势序列字符串转换为点列表
+ * @param gestureString 手势序列字符串
+ * @return 手势点列表
+ */
+ public static List stringToGesture(String gestureString) {
+ List gesturePoints = new ArrayList<>();
+ for (char c : gestureString.toCharArray()) {
+ gesturePoints.add(Character.getNumericValue(c));
+ }
+ return gesturePoints;
+ }
+}
\ No newline at end of file
diff --git a/src/notes/tool/UserManager.java b/src/notes/tool/UserManager.java
index 7154346..be6f5c3 100644
--- a/src/notes/tool/UserManager.java
+++ b/src/notes/tool/UserManager.java
@@ -11,17 +11,33 @@ public class UserManager {
private static final String PREF_NAME = "user_prefs";
private static final String KEY_IS_LOGGED_IN = "is_logged_in";
private static final String KEY_CURRENT_USER = "current_user";
+ private static final String KEY_LOGIN_TIMESTAMP = "login_timestamp";
- private static UserManager instance;
+ private static volatile UserManager instance; // 使用volatile关键字确保多线程环境下的可见性
private SharedPreferences sharedPreferences;
+ private Context applicationContext;
private UserManager(Context context) {
- sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ if (context != null) {
+ this.applicationContext = context.getApplicationContext();
+ if (this.applicationContext != null) {
+ this.sharedPreferences = this.applicationContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ }
+ }
}
- public static synchronized UserManager getInstance(Context context) {
+ public static UserManager getInstance(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null");
+ }
+
+ // 双重检查锁定模式,确保线程安全
if (instance == null) {
- instance = new UserManager(context.getApplicationContext());
+ synchronized (UserManager.class) {
+ if (instance == null) {
+ instance = new UserManager(context.getApplicationContext());
+ }
+ }
}
return instance;
}
@@ -33,6 +49,7 @@ public class UserManager {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, true)
.putString(KEY_CURRENT_USER, username)
+ .putLong(KEY_LOGIN_TIMESTAMP, System.currentTimeMillis()) // 保存当前登录时间
.apply();
}
@@ -50,6 +67,22 @@ public class UserManager {
return sharedPreferences.getString(KEY_CURRENT_USER, "");
}
+ /**
+ * 检查登录状态是否仍然有效(3天内)
+ */
+ public boolean isLoginValid() {
+ if (!isLoggedIn()) {
+ return false;
+ }
+
+ long loginTime = sharedPreferences.getLong(KEY_LOGIN_TIMESTAMP, 0);
+ long currentTime = System.currentTimeMillis();
+ // 3天 = 3 * 24 * 60 * 60 * 1000 毫秒
+ long threeDaysInMillis = 3L * 24 * 60 * 60 * 1000;
+
+ return (currentTime - loginTime) < threeDaysInMillis;
+ }
+
/**
* 退出登录,清除登录状态
*/
@@ -57,6 +90,7 @@ public class UserManager {
sharedPreferences.edit()
.putBoolean(KEY_IS_LOGGED_IN, false)
.remove(KEY_CURRENT_USER)
+ .remove(KEY_LOGIN_TIMESTAMP)
.apply();
}
}
\ No newline at end of file
diff --git a/src/notes/ui/GestureLockView.java b/src/notes/ui/GestureLockView.java
new file mode 100644
index 0000000..d2d48f0
--- /dev/null
+++ b/src/notes/ui/GestureLockView.java
@@ -0,0 +1,264 @@
+package net.micode.notes.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 手势锁自定义视图
+ */
+public class GestureLockView extends View {
+ private static final int MATRIX_SIZE = 3; // 3x3矩阵
+
+ private Paint mPaint; // 画笔
+ private List mPoints; // 手势点集合
+ private List mSelectedPoints; // 已选择的点集合
+ private List mLines; // 连接线集合
+ private GesturePoint mCurrentPoint; // 当前手指位置点
+ private boolean mIsDrawing; // 是否正在绘制
+
+ private OnGestureCompleteListener mListener; // 手势完成监听器
+
+ public interface OnGestureCompleteListener {
+ void onGestureComplete(List selectedPoints);
+ }
+
+ public static class GesturePoint {
+ public float x; // X坐标
+ public float y; // Y坐标
+ public int index; // 点的索引 (0-8)
+ public boolean isSelected; // 是否被选中
+
+ public GesturePoint(float x, float y, int index) {
+ this.x = x;
+ this.y = y;
+ this.index = index;
+ this.isSelected = false;
+ }
+ }
+
+ public static class GestureLine {
+ public GesturePoint startPoint; // 起始点
+ public GesturePoint endPoint; // 结束点
+
+ public GestureLine(GesturePoint start, GesturePoint end) {
+ this.startPoint = start;
+ this.endPoint = end;
+ }
+ }
+
+ public GestureLockView(Context context) {
+ super(context);
+ init();
+ }
+
+ public GestureLockView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public GestureLockView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true); // 抗锯齿
+ mPaint.setStrokeWidth(4); // 线宽
+
+ mPoints = new ArrayList<>();
+ mSelectedPoints = new ArrayList<>();
+ mLines = new ArrayList<>();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ // 计算点的位置
+ int padding = Math.min(w, h) / 10; // 边距
+ int cellWidth = (w - 2 * padding) / (MATRIX_SIZE - 1); // 每个格子的宽度
+ int cellHeight = (h - 2 * padding) / (MATRIX_SIZE - 1); // 每个格子的高度
+
+ mPoints.clear();
+ for (int i = 0; i < MATRIX_SIZE; i++) {
+ for (int j = 0; j < MATRIX_SIZE; j++) {
+ float x = padding + j * cellWidth;
+ float y = padding + i * cellHeight;
+ mPoints.add(new GesturePoint(x, y, i * MATRIX_SIZE + j));
+ }
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // 绘制所有点
+ for (GesturePoint point : mPoints) {
+ drawPoint(canvas, point);
+ }
+
+ // 绘制连接线
+ for (GestureLine line : mLines) {
+ drawLine(canvas, line);
+ }
+
+ // 如果正在绘制,绘制从最后一点到当前手指位置的连线
+ if (mIsDrawing && mCurrentPoint != null && !mSelectedPoints.isEmpty()) {
+ GesturePoint lastPoint = mPoints.get(mSelectedPoints.get(mSelectedPoints.size() - 1));
+ mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
+ mPaint.setStyle(Paint.Style.STROKE);
+ canvas.drawLine(lastPoint.x, lastPoint.y, mCurrentPoint.x, mCurrentPoint.y, mPaint);
+ }
+ }
+
+ private void drawPoint(Canvas canvas, GesturePoint point) {
+ float radius = Math.min(getWidth(), getHeight()) / 15f; // 点的半径
+
+ if (point.isSelected) {
+ // 绘制选中的点(大圆圈)
+ mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
+ mPaint.setStyle(Paint.Style.FILL);
+ canvas.drawCircle(point.x, point.y, radius, mPaint);
+
+ // 绘制内部小圆点
+ mPaint.setColor(Color.WHITE);
+ canvas.drawCircle(point.x, point.y, radius / 2, mPaint);
+ } else {
+ // 绘制未选中的点(圆环)
+ mPaint.setColor(Color.GRAY);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(4);
+ canvas.drawCircle(point.x, point.y, radius, mPaint);
+ }
+ }
+
+ private void drawLine(Canvas canvas, GestureLine line) {
+ mPaint.setColor(Color.parseColor("#FFA500")); // 橙色
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(8);
+ canvas.drawLine(line.startPoint.x, line.startPoint.y,
+ line.endPoint.x, line.endPoint.y, mPaint);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ handleTouchDown(x, y);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ handleTouchMove(x, y);
+ break;
+ case MotionEvent.ACTION_UP:
+ handleTouchUp();
+ break;
+ }
+
+ invalidate(); // 重绘
+ return true;
+ }
+
+ private void handleTouchDown(float x, float y) {
+ mSelectedPoints.clear();
+ mLines.clear();
+ mIsDrawing = true;
+
+ // 检查是否点击了某个点
+ for (GesturePoint point : mPoints) {
+ float distance = (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
+ if (distance < Math.min(getWidth(), getHeight()) / 10f) { // 如果在点的范围内
+ selectPoint(point);
+ break;
+ }
+ }
+ }
+
+ private void handleTouchMove(float x, float y) {
+ if (!mIsDrawing) return;
+
+ // 更新当前手指位置
+ mCurrentPoint = new GesturePoint(x, y, -1);
+
+ // 检查是否有新的点被经过
+ for (GesturePoint point : mPoints) {
+ if (point.isSelected) continue; // 已经选过的点不再处理
+
+ float distance = (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
+ if (distance < Math.min(getWidth(), getHeight()) / 10f) { // 如果在点的范围内
+ selectPoint(point);
+ break;
+ }
+ }
+ }
+
+ private void handleTouchUp() {
+ if (!mIsDrawing) return;
+
+ mIsDrawing = false;
+ mCurrentPoint = null;
+
+ // 通知手势完成
+ if (mListener != null && !mSelectedPoints.isEmpty()) {
+ mListener.onGestureComplete(mSelectedPoints);
+ }
+
+ // 重置状态
+ reset();
+ }
+
+ private void selectPoint(GesturePoint point) {
+ if (!point.isSelected) {
+ point.isSelected = true;
+ mSelectedPoints.add(point.index);
+
+ // 添加连接线(如果不是第一个点)
+ if (mSelectedPoints.size() > 1) {
+ int lastIndex = mSelectedPoints.get(mSelectedPoints.size() - 2);
+ GesturePoint lastPoint = mPoints.get(lastIndex);
+ mLines.add(new GestureLine(lastPoint, point));
+ }
+ }
+ }
+
+ private void reset() {
+ for (GesturePoint point : mPoints) {
+ point.isSelected = false;
+ }
+ mSelectedPoints.clear();
+ mLines.clear();
+ mCurrentPoint = null;
+ }
+
+ public void setOnGestureCompleteListener(OnGestureCompleteListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * 清除当前手势并重置视图
+ */
+ public void clearGesture() {
+ reset();
+ invalidate();
+ }
+
+ /**
+ * 获取当前手势点的数量
+ */
+ public int getSelectedPointsCount() {
+ return mSelectedPoints.size();
+ }
+}
\ No newline at end of file
diff --git a/src/notes/ui/LoginRegisterActivity.java b/src/notes/ui/LoginRegisterActivity.java
index 28d4516..b2ac984 100644
--- a/src/notes/ui/LoginRegisterActivity.java
+++ b/src/notes/ui/LoginRegisterActivity.java
@@ -29,10 +29,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
private View tabIndicator;
private EditText etUsername;
private EditText etPassword;
+ private EditText etConfirmPassword;
private ImageView ivPasswordVisibility;
private TextView tvErrorMessage;
private Button btnAction;
private TextView tvForgotPassword;
+ private LinearLayout confirmPasswordLayout;
// 数据库帮助类
private UserDatabaseHelper dbHelper;
@@ -51,8 +53,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_register);
- // 检查是否已登录,如果已登录则直接跳转到主页面
- if (userManager.isLoggedIn()) {
+ //通过getInstance获取已初始化的UserManager实例
+ UserManager userManager = UserManager.getInstance(this);
+
+ //安全调用:先判空,再调用方法,避免null
+ // 检查是否已登录且在有效期内,如果已登录且在有效期内则直接跳转到主页面
+ if (userManager != null && userManager.isLoginValid()) {
startActivity(new Intent(this, NotesListActivity.class));
finish();
return;
@@ -73,10 +79,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
tabIndicator = findViewById(R.id.tab_indicator);
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
+ etConfirmPassword = findViewById(R.id.et_confirm_password);
ivPasswordVisibility = findViewById(R.id.iv_password_visibility);
tvErrorMessage = findViewById(R.id.tv_error_message);
btnAction = findViewById(R.id.btn_action);
tvForgotPassword = findViewById(R.id.tv_forgot_password);
+ confirmPasswordLayout = findViewById(R.id.confirm_password_layout);
// 初始化数据库帮助类
dbHelper = UserDatabaseHelper.getInstance(this);
@@ -147,11 +155,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
// 更新指示器位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabIndicator.getLayoutParams();
- params.leftMargin = 40; // 左侧位置
+ params.leftMargin = 40; // 左侧位置 (登录tab的位置)
tabIndicator.setLayoutParams(params);
btnAction.setText("登录");
tvForgotPassword.setVisibility(View.VISIBLE);
+ confirmPasswordLayout.setVisibility(View.GONE); // 隐藏确认密码输入框
} else {
// 注册模式
tvRegisterTab.setTextColor(getResources().getColor(android.R.color.holo_orange_dark));
@@ -159,11 +168,12 @@ public class LoginRegisterActivity extends AppCompatActivity {
// 更新指示器位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabIndicator.getLayoutParams();
- params.leftMargin = 120; // 右侧位置
+ params.leftMargin = 120; // 右侧位置 (注册tab的位置)
tabIndicator.setLayoutParams(params);
btnAction.setText("注册");
tvForgotPassword.setVisibility(View.GONE);
+ confirmPasswordLayout.setVisibility(View.VISIBLE); // 显示确认密码输入框
}
// 清除错误消息
@@ -227,6 +237,7 @@ public class LoginRegisterActivity extends AppCompatActivity {
private void performRegister() {
String username = etUsername.getText().toString().trim();
String password = etPassword.getText().toString().trim();
+ String confirmPassword = etConfirmPassword.getText().toString().trim();
// 验证输入
if (TextUtils.isEmpty(username)) {
@@ -248,6 +259,16 @@ public class LoginRegisterActivity extends AppCompatActivity {
showError("密码长度不能少于6位");
return;
}
+
+ if (TextUtils.isEmpty(confirmPassword)) {
+ showError("请确认密码");
+ return;
+ }
+
+ if (!password.equals(confirmPassword)) {
+ showError("两次输入的密码不一致");
+ return;
+ }
// 检查用户名是否已存在
if (dbHelper.isUsernameExists(username)) {
@@ -260,9 +281,10 @@ public class LoginRegisterActivity extends AppCompatActivity {
if (success) {
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
- // 自动切换到登录模式
+ // 自动切换到登录模式并清空输入框
isLoginMode = true;
updateUIForLoginMode();
+ clearInputFields(); // 清空输入框
} else {
showError("注册失败,请重试");
}
@@ -275,6 +297,16 @@ public class LoginRegisterActivity extends AppCompatActivity {
tvErrorMessage.setText(message);
tvErrorMessage.setVisibility(View.VISIBLE);
}
+
+ /**
+ * 清空输入框
+ */
+ private void clearInputFields() {
+ etUsername.setText("");
+ etPassword.setText("");
+ etConfirmPassword.setText("");
+ tvErrorMessage.setVisibility(View.GONE);
+ }
/**
* 显示忘记密码对话框
diff --git a/src/notes/ui/NoteEditActivity.java b/src/notes/ui/NoteEditActivity.java
index f3c30fc..d3e4097 100644
--- a/src/notes/ui/NoteEditActivity.java
+++ b/src/notes/ui/NoteEditActivity.java
@@ -64,6 +64,7 @@ import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.PrivacyLockManager;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
@@ -74,6 +75,8 @@ import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -154,15 +157,21 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 桌面快捷方式标题最大长度
private static final int REQUEST_CODE_SELECT_LOCATION = 1001; // 选择位置请求码
-
+
// 图片选择请求码
private static final int REQUEST_CODE_PICK_IMAGE = 1002; // 从相册选择图片
private static final int REQUEST_CODE_CAPTURE_IMAGE = 1003; // 拍照 - 保留以避免编译错误,但不使用
private static final int REQUEST_CODE_CHOOSE_IMAGE = 1004; // 选择系统图片
// 清单模式下的标记符号
- public static final String TAG_CHECKED = String.valueOf('\u221A'); // 对勾符号 √
- public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 方框符号 □
+ static final String TAG_CHECKED = String.valueOf('\u221A'); // 对勾符号 √
+ static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 方框符号 □
+
+ // 隐私锁相关常量
+ private static final int MAX_UNLOCK_ATTEMPTS = 3; // 最大解锁尝试次数
+
+ private PrivacyLockManager mPrivacyLockManager; // 隐私锁管理器
+ private int mUnlockAttempts = 0; // 当前解锁尝试次数
private LinearLayout mEditTextList; // 清单模式下的编辑框列表容器
private String mUserQuery; // 用户搜索查询词(从搜索跳转过来时使用)
@@ -191,6 +200,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return;
}
initResources(); // 初始化视图资源
+ mPrivacyLockManager = new PrivacyLockManager(this); // 初始化隐私锁管理器
+
+ // 检查是否需要解锁
+ if (mWorkingNote != null && mPrivacyLockManager.isNoteLocked(mWorkingNote.getNoteId())) {
+ showUnlockDialog();
+ return; // 暂停初始化,等待解锁
+ }
// 添加对OnBackPressedDispatcher的支持,以便处理手势返回
try {
@@ -351,7 +367,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
// 普通文本模式,高亮搜索词
String content = mWorkingNote.getContent();
String formatInfo = mWorkingNote.getRichTextFormat();
-
+
// 恢复富文本格式
if (formatInfo != null && !formatInfo.isEmpty()) {
// 使用序列化的格式信息恢复文本格式
@@ -361,7 +377,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
// 没有格式信息时,正常显示高亮文本
mNoteEditor.setText(getHighlightQueryResult(content, mUserQuery));
}
-
+
mNoteEditor.setSelection(mNoteEditor.getText().length());
// 显示富文本格式工具栏
@@ -386,10 +402,27 @@ public class NoteEditActivity extends Activity implements OnClickListener,
// 显示提醒信息
showAlertHeader();
+ // 显示锁图标(如果便签被锁定)
+ showLockIcon();
+
// 初始化时设置格式工具栏背景
updateFormatToolbarBackground();
}
+ /**
+ * 显示锁图标(如果便签被锁定)
+ */
+ private void showLockIcon() {
+ if (mPrivacyLockManager.isNoteLocked(mWorkingNote.getNoteId())) {
+ // 在修改时间旁边添加锁图标
+ if (mNoteHeaderHolder.tvModified != null) {
+ // 添加锁图标到修改时间文本末尾
+ String originalText = mNoteHeaderHolder.tvModified.getText().toString();
+ mNoteHeaderHolder.tvModified.setText(originalText + " 🔒");
+ }
+ }
+ }
+
/**
* 显示或隐藏提醒相关的UI组件
*/
@@ -425,75 +458,75 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} else {
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
- };
+ }
}
/**
* 处理新的Intent(活动被重新启动时)
*/
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- initActivityState(intent);
- }
+@Override
+protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ initActivityState(intent);
+}
- /**
- * 保存活动状态
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- // 保存新笔记的ID
- if (!mWorkingNote.existInDatabase()) {
- saveNote();
- }
- outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
- Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
+/**
+ * 保存活动状态
+ */
+@Override
+protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ // 保存新笔记的ID
+ if (!mWorkingNote.existInDatabase()) {
+ saveNote();
}
+ outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
+ Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
+}
- /**
- * 处理从地图应用返回的位置数据
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_SELECT_LOCATION) {
- if (data != null && data.getData() != null) {
- Uri uri = data.getData();
- String geoString = uri.toString();
-
- // 解析地图应用返回的位置数据
- // 格式通常为: geo:latitude,longitude?q=address
- Pattern pattern = Pattern.compile("geo:([-0-9.]+),([-0-9.]+)");
- Matcher matcher = pattern.matcher(geoString);
- if (matcher.find()) {
- try {
- double latitude = Double.parseDouble(matcher.group(1));
- double longitude = Double.parseDouble(matcher.group(2));
-
- // 更新UI显示选择的位置
- Toast.makeText(this, "位置已选择: " + latitude + ", " + longitude, Toast.LENGTH_SHORT).show();
-
- // 更新对话框中的输入框
- if (mLatitudeEditText != null) {
- mLatitudeEditText.setText(String.valueOf(latitude));
- }
- if (mLongitudeEditText != null) {
- mLongitudeEditText.setText(String.valueOf(longitude));
- }
- } catch (NumberFormatException e) {
- Toast.makeText(this, "解析位置数据失败", Toast.LENGTH_SHORT).show();
+/**
+ * 处理从地图应用返回的位置数据
+ */
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_SELECT_LOCATION) {
+ if (data != null && data.getData() != null) {
+ Uri uri = data.getData();
+ String geoString = uri.toString();
+
+ // 解析地图应用返回的位置数据
+ // 格式通常为: geo:latitude,longitude?q=address
+ Pattern pattern = Pattern.compile("geo:([-0-9.]+),([-0-9.]+)");
+ Matcher matcher = pattern.matcher(geoString);
+ if (matcher.find()) {
+ try {
+ double latitude = Double.parseDouble(matcher.group(1));
+ double longitude = Double.parseDouble(matcher.group(2));
+
+ // 更新UI显示选择的位置
+ Toast.makeText(this, "位置已选择: " + latitude + ", " + longitude, Toast.LENGTH_SHORT).show();
+
+ // 更新对话框中的输入框
+ if (mLatitudeEditText != null) {
+ mLatitudeEditText.setText(String.valueOf(latitude));
+ }
+ if (mLongitudeEditText != null) {
+ mLongitudeEditText.setText(String.valueOf(longitude));
}
+ } catch (NumberFormatException e) {
+ Toast.makeText(this, "解析位置数据失败", Toast.LENGTH_SHORT).show();
}
}
- } else if (resultCode == RESULT_OK) {
- // 处理图片选择结果
- Uri imageUri = null;
- if (requestCode == REQUEST_CODE_PICK_IMAGE || requestCode == REQUEST_CODE_CHOOSE_IMAGE) {
- if (data != null) {
- imageUri = data.getData();
- }
- } /* 不再处理相机拍照请求
+ }
+ } else if (resultCode == RESULT_OK) {
+ // 处理图片选择结果
+ Uri imageUri = null;
+ if (requestCode == REQUEST_CODE_PICK_IMAGE || requestCode == REQUEST_CODE_CHOOSE_IMAGE) {
+ if (data != null) {
+ imageUri = data.getData();
+ }
+ } /* 不再处理相机拍照请求
else if (requestCode == REQUEST_CODE_CAPTURE_IMAGE) {
if (data != null && data.getExtras() != null) {
// 拍照返回的图片通常在data.getExtras().get("data")中
@@ -507,1349 +540,1512 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
*/
- if (imageUri != null) {
- insertImageToEditor(imageUri);
- }
+ if (imageUri != null) {
+ insertImageToEditor(imageUri);
}
}
+}
- /**
- * 将Bitmap转换为Uri
- */
- private Uri getImageUriFromBitmap(android.graphics.Bitmap bitmap) {
- // 将Bitmap保存到临时文件,然后返回Uri
- try {
- java.io.ByteArrayOutputStream bytes = new java.io.ByteArrayOutputStream();
- bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 100, bytes);
- String path = android.provider.MediaStore.Images.Media.insertImage(
- getContentResolver(), bitmap, "Title", null);
- return android.net.Uri.parse(path);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
+/**
+ * 将Bitmap转换为Uri
+ */
+private Uri getImageUriFromBitmap(android.graphics.Bitmap bitmap) {
+ // 将Bitmap保存到临时文件,然后返回Uri
+ try {
+ java.io.ByteArrayOutputStream bytes = new java.io.ByteArrayOutputStream();
+ bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 100, bytes);
+ String path = android.provider.MediaStore.Images.Media.insertImage(
+ getContentResolver(), bitmap, "Title", null);
+ return android.net.Uri.parse(path);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
}
+}
- /**
- * 在编辑器光标位置插入图片
- */
- private void insertImageToEditor(Uri imageUri) {
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- // 清单模式下,显示图片路径
- int cursorPos = mNoteEditor.getSelectionStart();
- String imagePath = imageUri.toString();
- mNoteEditor.getText().insert(cursorPos, "[图片: " + imagePath + "]");
- } else {
- // 普通模式下,插入图片
- try {
- // 获取图片的实际路径
- String imagePath = getRealPathFromURI(imageUri);
-
- // 创建图片Span
- android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
-
- // 缩放图片以适应编辑器
- int maxWidth = mNoteEditor.getWidth() - 40; // 留一些边距
- if (bitmap != null && bitmap.getWidth() > maxWidth) {
- float scale = (float) maxWidth / bitmap.getWidth();
- int newHeight = (int) (bitmap.getHeight() * scale);
- bitmap = android.graphics.Bitmap.createScaledBitmap(bitmap, maxWidth, newHeight, true);
- }
-
- if (bitmap != null) {
- android.text.style.ImageSpan imageSpan = new android.text.style.ImageSpan(this, bitmap);
-
- // 获取当前光标位置
- int cursorPos = mNoteEditor.getSelectionStart();
-
- // 创建包含图片的SpannableString
- android.text.SpannableStringBuilder ssb = new android.text.SpannableStringBuilder(mNoteEditor.getText());
- ssb.insert(cursorPos, "🖼️"); // 使用占位符文本
- ssb.setSpan(imageSpan, cursorPos, cursorPos + 2, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- // 更新编辑器内容
- mNoteEditor.setText(ssb);
- mNoteEditor.setSelection(cursorPos + 2); // 将光标移到图片后面
-
- // 保存图片信息到数据库
- saveImageToDatabase(imagePath);
- }
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(this, "插入图片失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
- }
- }
- }
-
- /**
- * 保存图片信息到数据库
- */
- private void saveImageToDatabase(String imagePath) {
- try {
- // 通过Note对象保存图片数据
- mWorkingNote.getNote().setImageData(net.micode.notes.data.Notes.DataColumns.IMAGE_PATH, imagePath);
- } catch (Exception e) {
- e.printStackTrace();
- Log.e(TAG, "Failed to save image data to database: " + e.getMessage());
- }
- }
-
- /**
- * 从URI获取真实路径
- */
- private String getRealPathFromURI(Uri contentUri) {
- String[] proj = {android.provider.MediaStore.Images.Media.DATA};
+/**
+ * 在编辑器光标位置插入图片
+ */
+private void insertImageToEditor(Uri imageUri) {
+ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
+ // 清单模式下,显示图片路径
+ int cursorPos = mNoteEditor.getSelectionStart();
+ String imagePath = imageUri.toString();
+ mNoteEditor.getText().insert(cursorPos, "[图片: " + imagePath + "]");
+ } else {
+ // 普通模式下,插入图片
try {
- android.database.Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
- if (cursor != null) {
- int column_index = cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media.DATA);
- cursor.moveToFirst();
- String result = cursor.getString(column_index);
- cursor.close();
- return result;
+ // 获取图片的实际路径
+ String imagePath = getRealPathFromURI(imageUri);
+
+ // 创建图片Span
+ android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
+
+ // 缩放图片以适应编辑器
+ int maxWidth = mNoteEditor.getWidth() - 40; // 留一些边距
+ if (bitmap != null && bitmap.getWidth() > maxWidth) {
+ float scale = (float) maxWidth / bitmap.getWidth();
+ int newHeight = (int) (bitmap.getHeight() * scale);
+ bitmap = android.graphics.Bitmap.createScaledBitmap(bitmap, maxWidth, newHeight, true);
+ }
+
+ if (bitmap != null) {
+ android.text.style.ImageSpan imageSpan = new android.text.style.ImageSpan(this, bitmap);
+
+ // 获取当前光标位置
+ int cursorPos = mNoteEditor.getSelectionStart();
+
+ // 创建包含图片的SpannableString
+ android.text.SpannableStringBuilder ssb = new android.text.SpannableStringBuilder(mNoteEditor.getText());
+ ssb.insert(cursorPos, "🖼️"); // 使用占位符文本
+ ssb.setSpan(imageSpan, cursorPos, cursorPos + 2, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // 更新编辑器内容
+ mNoteEditor.setText(ssb);
+ mNoteEditor.setSelection(cursorPos + 2); // 将光标移到图片后面
+
+ // 保存图片信息到数据库
+ saveImageToDatabase(imagePath);
}
} catch (Exception e) {
e.printStackTrace();
+ Toast.makeText(this, "插入图片失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
- return contentUri.toString();
}
+}
- /**
- * 触摸事件分发,用于点击选择器外部时关闭选择器
- */
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
- && !inRangeOfView(mNoteBgColorSelector, ev)) {
- mNoteBgColorSelector.setVisibility(View.GONE);
- return true;
- }
+/**
+ * 保存图片信息到数据库
+ */
+private void saveImageToDatabase(String imagePath) {
+ try {
+ // 通过Note对象保存图片数据
+ mWorkingNote.getNote().setImageData(net.micode.notes.data.Notes.DataColumns.IMAGE_PATH, imagePath);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, "Failed to save image data to database: " + e.getMessage());
+ }
+}
- if (mFontSizeSelector.getVisibility() == View.VISIBLE
- && !inRangeOfView(mFontSizeSelector, ev)) {
- mFontSizeSelector.setVisibility(View.GONE);
- return true;
+/**
+ * 从URI获取真实路径
+ */
+private String getRealPathFromURI(Uri contentUri) {
+ String[] proj = {android.provider.MediaStore.Images.Media.DATA};
+ try {
+ android.database.Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
+ if (cursor != null) {
+ int column_index = cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media.DATA);
+ cursor.moveToFirst();
+ String result = cursor.getString(column_index);
+ cursor.close();
+ return result;
}
- return super.dispatchTouchEvent(ev);
+ } catch (Exception e) {
+ e.printStackTrace();
}
+ return contentUri.toString();
+}
- /**
- * 判断触摸点是否在指定视图范围内
- */
- private boolean inRangeOfView(View view, MotionEvent ev) {
- int []location = new int[2];
- view.getLocationOnScreen(location);
- int x = location[0];
- int y = location[1];
- if (ev.getX() < x
- || ev.getX() > (x + view.getWidth())
- || ev.getY() < y
- || ev.getY() > (y + view.getHeight())) {
- return false;
- }
+/**
+ * 触摸事件分发,用于点击选择器外部时关闭选择器
+ */
+@Override
+public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
+ && !inRangeOfView(mNoteBgColorSelector, ev)) {
+ mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
- /**
- * 初始化视图资源和事件监听
- */
- private void initResources() {
- mHeadViewPanel = findViewById(R.id.note_title);
- mNoteHeaderHolder = new HeadViewHolder();
- mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
- mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
- mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
- mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
- mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
- mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
- mNoteEditorPanel = findViewById(R.id.sv_note_edit);
- mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
-
- // 设置背景颜色选择按钮的点击监听
- for (int id : sBgSelectorBtnsMap.keySet()) {
- ImageView iv = (ImageView) findViewById(id);
- iv.setOnClickListener(this);
- }
-
- mFontSizeSelector = findViewById(R.id.font_size_selector);
- // 设置字体大小选择项的点击监听
- for (int id : sFontSizeBtnsMap.keySet()) {
- View view = findViewById(id);
- view.setOnClickListener(this);
- };
-
- // 初始化富文本编辑工具栏
- mFormatToolbar = findViewById(R.id.format_toolbar);
- mBtnBold = findViewById(R.id.btn_bold);
- mBtnItalic = findViewById(R.id.btn_italic);
- mBtnUnderline = findViewById(R.id.btn_underline);
- mBtnStrikethrough = findViewById(R.id.btn_strikethrough);
-
- // 设置富文本编辑按钮点击监听器
- mBtnBold.setOnClickListener(this);
- mBtnItalic.setOnClickListener(this);
- mBtnUnderline.setOnClickListener(this);
- mBtnStrikethrough.setOnClickListener(this);
-
- // 初始化并设置添加图片按钮
- mBtnAddImage = findViewById(R.id.add_img_btn);
- mBtnAddImage.setOnClickListener(this);
-
- mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
-
- // 修复字体大小ID可能超出范围的问题
- if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
- mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
- }
- mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
-
- // 设置图片删除处理
- setupImageDeletionHandling();
+ if (mFontSizeSelector.getVisibility() == View.VISIBLE
+ && !inRangeOfView(mFontSizeSelector, ev)) {
+ mFontSizeSelector.setVisibility(View.GONE);
+ return true;
}
+ return super.dispatchTouchEvent(ev);
+}
- /**
- * Activity暂停时保存笔记
- */
- @Override
- protected void onPause() {
- super.onPause();
- // 保存当前编辑的笔记
- if(saveNote()) {
- Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
- }
- clearSettingState(); // 清理设置面板状态
-
- // 隐藏富文本格式工具栏
- if (mFormatToolbar != null) {
- mFormatToolbar.setVisibility(View.GONE);
- }
+/**
+ * 判断触摸点是否在指定视图范围内
+ */
+private boolean inRangeOfView(View view, MotionEvent ev) {
+ int []location = new int[2];
+ view.getLocationOnScreen(location);
+ int x = location[0];
+ int y = location[1];
+ if (ev.getX() < x
+ || ev.getX() > (x + view.getWidth())
+ || ev.getY() < y
+ || ev.getY() > (y + view.getHeight())) {
+ return false;
}
+ return true;
+}
- /**
- * 更新关联的桌面小部件
- */
- private void updateWidget() {
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- // 根据小部件类型设置对应的广播接收器
- if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
- intent.setClass(this, NoteWidgetProvider_2x.class);
- } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
- intent.setClass(this, NoteWidgetProvider_4x.class);
- } else {
- Log.e(TAG, "Unspported widget type");
- return;
- }
-
- // 设置要更新的小部件ID
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
- mWorkingNote.getWidgetId()
- });
-
- sendBroadcast(intent); // 发送广播通知小部件更新
- setResult(RESULT_OK, intent); // 设置活动结果
+/**
+ * 初始化视图资源和事件监听
+ */
+private void initResources() {
+ mHeadViewPanel = findViewById(R.id.note_title);
+ mNoteHeaderHolder = new HeadViewHolder();
+ mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
+ mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
+ mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
+ mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
+ mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
+ mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
+ mNoteEditorPanel = findViewById(R.id.sv_note_edit);
+ mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
+
+ // 设置背景颜色选择按钮的点击监听
+ for (int id : sBgSelectorBtnsMap.keySet()) {
+ ImageView iv = (ImageView) findViewById(id);
+ iv.setOnClickListener(this);
}
- /**
- * 点击事件处理
- */
- public void onClick(View v) {
- int id = v.getId();
- if (id == R.id.btn_set_bg_color) {
- // 点击背景颜色设置按钮,显示颜色选择器
- mNoteBgColorSelector.setVisibility(View.VISIBLE);
- // 显示当前选中颜色的标记
- findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
- } else if (sBgSelectorBtnsMap.containsKey(id)) {
- // 点击了某个背景颜色按钮
- // 隐藏之前选中颜色的标记
- findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.GONE);
- // 更新笔记的背景颜色ID
- mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
- mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏选择器
- } else if (sFontSizeBtnsMap.containsKey(id)) {
- // 点击了字体大小选项
- // 隐藏之前选中字体的标记
- findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
- // 更新字体大小ID并保存到偏好设置
- mFontSizeId = sFontSizeBtnsMap.get(id);
- mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
- // 显示新选中字体的标记
- findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
-
- // 应用新的字体大小
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- // 清单模式:重新加载文本并切换到列表模式
- getWorkingText();
- switchToListMode(mWorkingNote.getContent());
- } else {
- // 普通模式:直接设置编辑器的文本外观
- mNoteEditor.setTextAppearance(this,
- TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
- }
- mFontSizeSelector.setVisibility(View.GONE); // 隐藏字体选择器
- } else if (id == R.id.btn_bold || id == R.id.btn_italic ||
- id == R.id.btn_underline || id == R.id.btn_strikethrough) {
- // 处理富文本编辑按钮点击
- handleRichTextFormatting(v);
- } else if (id == R.id.add_img_btn) {
- // 处理添加图片按钮点击
- showImageSelectionDialog();
- }
+ mFontSizeSelector = findViewById(R.id.font_size_selector);
+ // 设置字体大小选择项的点击监听
+ for (int id : sFontSizeBtnsMap.keySet()) {
+ View view = findViewById(id);
+ view.setOnClickListener(this);
+ };
+
+ // 初始化富文本编辑工具栏
+ mFormatToolbar = findViewById(R.id.format_toolbar);
+ mBtnBold = findViewById(R.id.btn_bold);
+ mBtnItalic = findViewById(R.id.btn_italic);
+ mBtnUnderline = findViewById(R.id.btn_underline);
+ mBtnStrikethrough = findViewById(R.id.btn_strikethrough);
+
+ // 设置富文本编辑按钮点击监听器
+ mBtnBold.setOnClickListener(this);
+ mBtnItalic.setOnClickListener(this);
+ mBtnUnderline.setOnClickListener(this);
+ mBtnStrikethrough.setOnClickListener(this);
+
+ // 初始化并设置添加图片按钮
+ mBtnAddImage = findViewById(R.id.add_img_btn);
+ mBtnAddImage.setOnClickListener(this);
+
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
+
+ // 修复字体大小ID可能超出范围的问题
+ if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
+ mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
+ mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
- /**
- * 返回键按下事件处理
- */
- @Override
- public void onBackPressedDispatcher() {
- // 如果有设置面板打开,先关闭面板
- if(clearSettingState()) {
- return;
- }
+ // 设置图片删除处理
+ setupImageDeletionHandling();
+}
- // 保存笔记后执行默认返回操作
- saveNote();
- super.onBackPressed();
+/**
+ * Activity暂停时保存笔记
+ */
+@Override
+protected void onPause() {
+ super.onPause();
+ // 保存当前编辑的笔记
+ if(saveNote()) {
+ Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
+ clearSettingState(); // 清理设置面板状态
- /**
- * 清理设置面板状态(关闭打开的面板)
- * @return 是否有面板被关闭
- */
- private boolean clearSettingState() {
- if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
- mNoteBgColorSelector.setVisibility(View.GONE);
- return true;
- } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
- mFontSizeSelector.setVisibility(View.GONE);
- return true;
- }
- return false;
+ // 隐藏富文本格式工具栏
+ if (mFormatToolbar != null) {
+ mFormatToolbar.setVisibility(View.GONE);
}
+}
- /**
- * 背景颜色改变回调(NoteSettingChangedListener接口方法)
- */
- public void onBackgroundColorChanged() {
- // 显示新选中颜色的标记
- findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
- // 更新编辑器面板和头部面板的背景
- mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
- mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
-
- // 根据当前背景颜色调整富文本格式工具栏的背景
- updateFormatToolbarBackground();
+/**
+ * 更新关联的桌面小部件
+ */
+private void updateWidget() {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ // 根据小部件类型设置对应的广播接收器
+ if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
+ intent.setClass(this, NoteWidgetProvider_2x.class);
+ } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
+ intent.setClass(this, NoteWidgetProvider_4x.class);
+ } else {
+ Log.e(TAG, "Unspported widget type");
+ return;
}
- /**
- * 根据当前便签背景颜色更新格式工具栏的背景
- */
- private void updateFormatToolbarBackground() {
- if (mFormatToolbar != null) {
- // 获取当前背景颜色资源ID
- int bgColorResId = mWorkingNote.getBgColorResId();
+ // 设置要更新的小部件ID
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
+ mWorkingNote.getWidgetId()
+ });
- // 根据背景颜色计算合适的工具栏背景色
- int toolbarBgColor = calculateToolbarBackgroundColor(bgColorResId);
+ sendBroadcast(intent); // 发送广播通知小部件更新
+ setResult(RESULT_OK, intent); // 设置活动结果
+}
- // 设置工具栏背景
- mFormatToolbar.setBackgroundColor(toolbarBgColor);
+/**
+ * 点击事件处理
+ */
+public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.btn_set_bg_color) {
+ // 点击背景颜色设置按钮,显示颜色选择器
+ mNoteBgColorSelector.setVisibility(View.VISIBLE);
+ // 显示当前选中颜色的标记
+ findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
+ View.VISIBLE);
+ } else if (sBgSelectorBtnsMap.containsKey(id)) {
+ // 点击了某个背景颜色按钮
+ // 隐藏之前选中颜色的标记
+ findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
+ View.GONE);
+ // 更新笔记的背景颜色ID
+ mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
+ mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏选择器
+ } else if (sFontSizeBtnsMap.containsKey(id)) {
+ // 点击了字体大小选项
+ // 隐藏之前选中字体的标记
+ findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
+ // 更新字体大小ID并保存到偏好设置
+ mFontSizeId = sFontSizeBtnsMap.get(id);
+ mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
+ // 显示新选中字体的标记
+ findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
+
+ // 应用新的字体大小
+ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
+ // 清单模式:重新加载文本并切换到列表模式
+ getWorkingText();
+ switchToListMode(mWorkingNote.getContent());
+ } else {
+ // 普通模式:直接设置编辑器的文本外观
+ mNoteEditor.setTextAppearance(this,
+ TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
+ mFontSizeSelector.setVisibility(View.GONE); // 隐藏字体选择器
+ } else if (id == R.id.btn_bold || id == R.id.btn_italic ||
+ id == R.id.btn_underline || id == R.id.btn_strikethrough) {
+ // 处理富文本编辑按钮点击
+ handleRichTextFormatting(v);
+ } else if (id == R.id.add_img_btn) {
+ // 处理添加图片按钮点击
+ showImageSelectionDialog();
}
+}
- /**
- * 根据便签背景资源计算工具栏合适的背景颜色
- * @param bgColorResId 便签背景资源ID
- * @return 计算出的工具栏背景颜色
- */
- private int calculateToolbarBackgroundColor(int bgColorResId) {
- // 获取当前背景颜色ID
- int bgColorId = mWorkingNote.getBgColorId();
-
- // 根据不同背景颜色ID返回相应的半透明工具栏背景
- switch (bgColorId) {
- case ResourceParser.YELLOW: // 黄色背景
- // 黄色背景较亮,使用浅灰色半透明背景
- return getColorWithAlpha(0x4D, 0xDD, 0xDD, 0xDD); // 浅灰半透明
- case ResourceParser.BLUE: // 蓝色背景
- // 蓝色背景,使用浅蓝白半透明背景
- return getColorWithAlpha(0x66, 0xF0, 0xF8, 0xFF); // 浅蓝白半透明
- case ResourceParser.WHITE: // 白色背景
- // 白色背景,使用浅灰半透明背景
- return getColorWithAlpha(0x4D, 0xCC, 0xCC, 0xCC); // 中灰半透明
- case ResourceParser.GREEN: // 绿色背景
- // 绿色背景,使用浅绿白半透明背景
- return getColorWithAlpha(0x66, 0xE8, 0xF5, 0xE8); // 浅绿白半透明
- case ResourceParser.RED: // 红色背景
- // 红色背景,使用浅红白半透明背景
- return getColorWithAlpha(0x66, 0xFA, 0xE0, 0xE0); // 浅红白半透明
- default:
- // 默认使用半透明白色背景
- return getColorWithAlpha(0x66, 0xFF, 0xFF, 0xFF); // 半透明白色
- }
+/**
+ * 返回键按下事件处理
+ */
+@Override
+public void onBackPressedDispatcher() {
+ // 如果有设置面板打开,先关闭面板
+ if(clearSettingState()) {
+ return;
}
- /**
- * 组合ARGB值为颜色整数
- * @param alpha 透明度 (0-255)
- * @param red 红色值 (0-255)
- * @param green 绿色值 (0-255)
- * @param blue 蓝色值 (0-255)
- * @return ARGB颜色整数
- */
- private int getColorWithAlpha(int alpha, int red, int green, int blue) {
- return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ // 保存笔记后执行默认返回操作
+ saveNote();
+ super.onBackPressed();
+}
+
+/**
+ * 清理设置面板状态(关闭打开的面板)
+ * @return 是否有面板被关闭
+ */
+private boolean clearSettingState() {
+ if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
+ mNoteBgColorSelector.setVisibility(View.GONE);
+ return true;
+ } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
+ mFontSizeSelector.setVisibility(View.GONE);
+ return true;
}
+ return false;
+}
- /**
- * 准备选项菜单
- */
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- if (isFinishing()) {
- return true;
- }
- clearSettingState(); // 清理设置面板状态
- menu.clear(); // 清空菜单
+/**
+ * 背景颜色改变回调(NoteSettingChangedListener接口方法)
+ */
+public void onBackgroundColorChanged() {
+ // 显示新选中颜色的标记
+ findViewById(NoteEditActivity.sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
+ View.VISIBLE);
+ // 更新编辑器面板和头部面板的背景
+ mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
+ mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
+
+ // 根据当前背景颜色调整富文本格式工具栏的背景
+ updateFormatToolbarBackground();
+}
- // 根据笔记类型加载不同的菜单
- if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
- getMenuInflater().inflate(R.menu.call_note_edit, menu);
- } else {
- getMenuInflater().inflate(R.menu.note_edit, menu);
- }
+/**
+ * 根据当前便签背景颜色更新格式工具栏的背景
+ */
+private void updateFormatToolbarBackground() {
+ if (mFormatToolbar != null) {
+ // 获取当前背景颜色资源ID
+ int bgColorResId = mWorkingNote.getBgColorResId();
- // 设置清单模式/普通模式菜单项标题
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
- } else {
- menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
- }
+ // 根据背景颜色计算合适的工具栏背景色
+ int toolbarBgColor = calculateToolbarBackgroundColor(bgColorResId);
- // 根据是否有提醒设置菜单项显示
- if (mWorkingNote.hasClockAlert()) {
- menu.findItem(R.id.menu_alert).setVisible(false);
- menu.findItem(R.id.menu_delete_remind).setVisible(true);
- } else {
- menu.findItem(R.id.menu_alert).setVisible(true);
- menu.findItem(R.id.menu_delete_remind).setVisible(false);
- }
+ // 设置工具栏背景
+ mFormatToolbar.setBackgroundColor(toolbarBgColor);
+ }
+}
- // 根据是否有位置提醒设置菜单项显示
- if (mWorkingNote.hasLocationAlert()) {
- menu.findItem(R.id.menu_location_alert).setVisible(false);
- menu.findItem(R.id.menu_delete_location_remind).setVisible(true);
- } else {
- menu.findItem(R.id.menu_location_alert).setVisible(true);
- menu.findItem(R.id.menu_delete_location_remind).setVisible(false);
- }
+/**
+ * 根据便签背景资源计算工具栏合适的背景颜色
+ * @param bgColorResId 便签背景资源ID
+ * @return 计算出的工具栏背景颜色
+ */
+private int calculateToolbarBackgroundColor(int bgColorResId) {
+ // 获取当前背景颜色ID
+ int bgColorId = mWorkingNote.getBgColorId();
+
+ // 根据不同背景颜色ID返回相应的半透明工具栏背景
+ switch (bgColorId) {
+ case ResourceParser.YELLOW: // 黄色背景
+ // 黄色背景较亮,使用浅灰色半透明背景
+ return getColorWithAlpha(0x4D, 0xDD, 0xDD, 0xDD); // 浅灰半透明
+ case ResourceParser.BLUE: // 蓝色背景
+ // 蓝色背景,使用浅蓝白半透明背景
+ return getColorWithAlpha(0x66, 0xF0, 0xF8, 0xFF); // 浅蓝白半透明
+ case ResourceParser.WHITE: // 白色背景
+ // 白色背景,使用浅灰半透明背景
+ return getColorWithAlpha(0x4D, 0xCC, 0xCC, 0xCC); // 中灰半透明
+ case ResourceParser.GREEN: // 绿色背景
+ // 绿色背景,使用浅绿白半透明背景
+ return getColorWithAlpha(0x66, 0xE8, 0xF5, 0xE8); // 浅绿白半透明
+ case ResourceParser.RED: // 红色背景
+ // 红色背景,使用浅红白半透明背景
+ return getColorWithAlpha(0x66, 0xFA, 0xE0, 0xE0); // 浅红白半透明
+ default:
+ // 默认使用半透明白色背景
+ return getColorWithAlpha(0x66, 0xFF, 0xFF, 0xFF); // 半透明白色
+ }
+}
+
+/**
+ * 组合ARGB值为颜色整数
+ * @param alpha 透明度 (0-255)
+ * @param red 红色值 (0-255)
+ * @param green 绿色值 (0-255)
+ * @param blue 蓝色值 (0-255)
+ * @return ARGB颜色整数
+ */
+private int getColorWithAlpha(int alpha, int red, int green, int blue) {
+ return (alpha << 24) | (red << 16) | (green << 8) | blue;
+}
+/**
+ * 准备选项菜单
+ */
+@Override
+public boolean onPrepareOptionsMenu(Menu menu) {
+ if (isFinishing()) {
return true;
}
+ clearSettingState(); // 清理设置面板状态
+ menu.clear(); // 清空菜单
+
+ // 根据笔记类型加载不同的菜单
+ if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
+ getMenuInflater().inflate(R.menu.call_note_edit, menu);
+ } else {
+ getMenuInflater().inflate(R.menu.note_edit, menu);
+ }
- /**
- * 菜单项点击处理
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_new_note:
- // 创建新笔记
- createNewNote();
- break;
- case R.id.menu_delete:
- // 删除笔记,弹出确认对话框
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.alert_title_delete));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(getString(R.string.alert_message_delete_note));
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- deleteCurrentNote(); // 删除当前笔记
- finish(); // 结束活动
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- break;
- case R.id.menu_font_size:
- // 显示字体大小选择器
- mFontSizeSelector.setVisibility(View.VISIBLE);
- findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
- break;
- case R.id.menu_list_mode:
- // 切换清单模式/普通模式
- mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
- TextNote.MODE_CHECK_LIST : 0);
- break;
- case R.id.menu_share:
- // 分享笔记内容
- getWorkingText();
- sendTo(this, mWorkingNote.getContent());
- break;
- case R.id.menu_send_to_desktop:
- // 发送到桌面快捷方式
- sendToDesktop();
- break;
- case R.id.menu_alert:
- // 设置提醒
- setReminder();
- break;
- case R.id.menu_delete_remind:
- // 删除提醒
- mWorkingNote.setAlertDate(0, false);
- break;
- case R.id.menu_location_alert:
- // 设置位置提醒
- setLocationReminder();
- break;
+ // 设置清单模式/普通模式菜单项标题
+ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
+ menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
+ } else {
+ menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
+ }
- case R.id.menu_delete_location_remind:
- // 删除位置提醒
- mWorkingNote.setAlertLocation(0, 0, 0, "", false);
- break;
- default:
- break;
- }
- return true;
+ // 根据是否有提醒设置菜单项显示
+ if (mWorkingNote.hasClockAlert()) {
+ menu.findItem(R.id.menu_alert).setVisible(false);
+ menu.findItem(R.id.menu_delete_remind).setVisible(true);
+ } else {
+ menu.findItem(R.id.menu_alert).setVisible(true);
+ menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
- /**
- * 设置提醒时间
- */
- private void setReminder() {
- // 创建日期时间选择对话框
- DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
- d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
- public void OnDateTimeSet(AlertDialog dialog, long date) {
- // 设置提醒日期
- mWorkingNote.setAlertDate(date, true);
- }
- });
- d.show();
+ // 根据是否有位置提醒设置菜单项显示
+ if (mWorkingNote.hasLocationAlert()) {
+ menu.findItem(R.id.menu_location_alert).setVisible(false);
+ menu.findItem(R.id.menu_delete_location_remind).setVisible(true);
+ } else {
+ menu.findItem(R.id.menu_location_alert).setVisible(true);
+ menu.findItem(R.id.menu_delete_location_remind).setVisible(false);
}
- /**
- * 设置位置提醒
- */
- private void setLocationReminder() {
- // 创建位置提醒对话框
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.location_reminder_dialog_title);
-
- // 创建布局
- LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.VERTICAL);
- layout.setPadding(50, 30, 50, 30);
-
- // 位置名称输入框
- mLocationNameEditText = new EditText(this);
- mLocationNameEditText.setHint(R.string.location_reminder_enter_name);
- LinearLayout.LayoutParams nameParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- nameParams.setMargins(0, 0, 0, 20);
- mLocationNameEditText.setLayoutParams(nameParams);
- layout.addView(mLocationNameEditText);
-
- // 纬度输入框
- mLatitudeEditText = new EditText(this);
- mLatitudeEditText.setHint("纬度 (Latitude)");
- mLatitudeEditText.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
- LinearLayout.LayoutParams latParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- latParams.setMargins(0, 0, 0, 20);
- mLatitudeEditText.setLayoutParams(latParams);
- layout.addView(mLatitudeEditText);
-
- // 经度输入框
- mLongitudeEditText = new EditText(this);
- mLongitudeEditText.setHint("经度 (Longitude)");
- mLongitudeEditText.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
- LinearLayout.LayoutParams lonParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- lonParams.setMargins(0, 0, 0, 20);
- mLongitudeEditText.setLayoutParams(lonParams);
- layout.addView(mLongitudeEditText);
-
- // 选择位置按钮
- final Button selectLocationButton = new Button(this);
- selectLocationButton.setText(R.string.location_reminder_select_location);
- LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- buttonParams.setMargins(0, 0, 0, 20);
- selectLocationButton.setLayoutParams(buttonParams);
- layout.addView(selectLocationButton);
-
- // 提醒半径输入框
- final EditText radiusEditText = new EditText(this);
- radiusEditText.setHint(R.string.location_reminder_radius);
- radiusEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
- radiusEditText.setText("100"); // 默认100米
- LinearLayout.LayoutParams radiusParams = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- radiusEditText.setLayoutParams(radiusParams);
- layout.addView(radiusEditText);
-
- // 选择位置按钮点击事件
- selectLocationButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // 调用地图应用选择位置
- Intent intent = new Intent(Intent.ACTION_VIEW);
-
- // 尝试不同的URI格式
- Uri geoUri = Uri.parse("geo:0,0");
- intent.setData(geoUri);
-
- // 不限制特定地图应用,让系统选择默认的地图应用
- intent.setPackage(null);
+ return true;
+}
+
+/**
+ * 菜单项点击处理
+ */
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_new_note:
+ // 创建新笔记
+ createNewNote();
+ break;
+ case R.id.menu_delete:
+ // 删除笔记,弹出确认对话框
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.alert_title_delete));
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setMessage(getString(R.string.alert_message_delete_note));
+ builder.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ deleteCurrentNote(); // 删除当前笔记
+ finish(); // 结束活动
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.show();
+ break;
+ case R.id.menu_font_size:
+ // 显示字体大小选择器
+ mFontSizeSelector.setVisibility(View.VISIBLE);
+ findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
+ break;
+ case R.id.menu_list_mode:
+ // 切换清单模式/普通模式
+ mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
+ TextNote.MODE_CHECK_LIST : 0);
+ break;
+ case R.id.menu_share:
+ // 分享笔记内容
+ getWorkingText();
+ sendTo(this, mWorkingNote.getContent());
+ break;
+ case R.id.menu_send_to_desktop:
+ // 发送到桌面快捷方式
+ sendToDesktop();
+ break;
+ case R.id.menu_alert:
+ // 设置提醒
+ setReminder();
+ break;
+ case R.id.menu_delete_remind:
+ // 删除提醒
+ mWorkingNote.setAlertDate(0, false);
+ break;
+ case R.id.menu_location_alert:
+ // 设置位置提醒
+ setLocationReminder();
+ break;
+
+ case R.id.menu_delete_location_remind:
+ // 删除位置提醒
+ mWorkingNote.setAlertLocation(0, 0, 0, "", false);
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * 设置提醒时间
+ */
+private void setReminder() {
+ // 创建日期时间选择对话框
+ DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
+ d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
+ public void OnDateTimeSet(AlertDialog dialog, long date) {
+ // 设置提醒日期
+ mWorkingNote.setAlertDate(date, true);
+ }
+ });
+ d.show();
+}
+
+/**
+ * 设置位置提醒
+ */
+private void setLocationReminder() {
+ // 创建位置提醒对话框
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.location_reminder_dialog_title);
+
+ // 创建布局
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(50, 30, 50, 30);
+
+ // 位置名称输入框
+ mLocationNameEditText = new EditText(this);
+ mLocationNameEditText.setHint(R.string.location_reminder_enter_name);
+ LinearLayout.LayoutParams nameParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ nameParams.setMargins(0, 0, 0, 20);
+ mLocationNameEditText.setLayoutParams(nameParams);
+ layout.addView(mLocationNameEditText);
+
+ // 纬度输入框
+ mLatitudeEditText = new EditText(this);
+ mLatitudeEditText.setHint("纬度 (Latitude)");
+ mLatitudeEditText.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
+ LinearLayout.LayoutParams latParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ latParams.setMargins(0, 0, 0, 20);
+ mLatitudeEditText.setLayoutParams(latParams);
+ layout.addView(mLatitudeEditText);
+
+ // 经度输入框
+ mLongitudeEditText = new EditText(this);
+ mLongitudeEditText.setHint("经度 (Longitude)");
+ mLongitudeEditText.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
+ LinearLayout.LayoutParams lonParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ lonParams.setMargins(0, 0, 0, 20);
+ mLongitudeEditText.setLayoutParams(lonParams);
+ layout.addView(mLongitudeEditText);
+
+ // 选择位置按钮
+ final Button selectLocationButton = new Button(this);
+ selectLocationButton.setText(R.string.location_reminder_select_location);
+ LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ buttonParams.setMargins(0, 0, 0, 20);
+ selectLocationButton.setLayoutParams(buttonParams);
+ layout.addView(selectLocationButton);
+
+ // 提醒半径输入框
+ final EditText radiusEditText = new EditText(this);
+ radiusEditText.setHint(R.string.location_reminder_radius);
+ radiusEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
+ radiusEditText.setText("100"); // 默认100米
+ LinearLayout.LayoutParams radiusParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ radiusEditText.setLayoutParams(radiusParams);
+ layout.addView(radiusEditText);
+
+ // 选择位置按钮点击事件
+ selectLocationButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 调用地图应用选择位置
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+
+ // 尝试不同的URI格式
+ Uri geoUri = Uri.parse("geo:0,0");
+ intent.setData(geoUri);
+ // 不限制特定地图应用,让系统选择默认的地图应用
+ intent.setPackage(null);
+
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(intent, REQUEST_CODE_SELECT_LOCATION);
+ } else {
+ // 如果第一种格式失败,尝试另一种格式
+ intent.setData(Uri.parse("geo:0,0?q=map"));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_CODE_SELECT_LOCATION);
} else {
- // 如果第一种格式失败,尝试另一种格式
- intent.setData(Uri.parse("geo:0,0?q=map"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivityForResult(intent, REQUEST_CODE_SELECT_LOCATION);
+ // 如果还是失败,尝试更通用的方式
+ Intent chooserIntent = Intent.createChooser(intent, "选择地图应用");
+ if (chooserIntent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(chooserIntent, REQUEST_CODE_SELECT_LOCATION);
} else {
- // 如果还是失败,尝试更通用的方式
- Intent chooserIntent = Intent.createChooser(intent, "选择地图应用");
- if (chooserIntent.resolveActivity(getPackageManager()) != null) {
- startActivityForResult(chooserIntent, REQUEST_CODE_SELECT_LOCATION);
- } else {
- Toast.makeText(NoteEditActivity.this, "没有找到地图应用", Toast.LENGTH_SHORT).show();
- }
+ Toast.makeText(NoteEditActivity.this, "没有找到地图应用", Toast.LENGTH_SHORT).show();
}
}
}
- });
-
- builder.setView(layout);
+ }
+ });
- // 确定按钮
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- try {
- String locationName = mLocationNameEditText.getText().toString().trim();
- double latitude = Double.parseDouble(mLatitudeEditText.getText().toString());
- double longitude = Double.parseDouble(mLongitudeEditText.getText().toString());
- float radius = Float.parseFloat(radiusEditText.getText().toString());
-
- // 验证输入
- if (TextUtils.isEmpty(locationName)) {
- locationName = "未知位置";
- }
- if (radius <= 0) {
- radius = 100; // 默认100米
- }
+ builder.setView(layout);
- // 设置位置提醒
- mWorkingNote.setAlertLocation(latitude, longitude, radius, locationName, true);
- } catch (NumberFormatException e) {
- Toast.makeText(NoteEditActivity.this, "请输入有效的位置信息", Toast.LENGTH_SHORT).show();
+ // 确定按钮
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ String locationName = mLocationNameEditText.getText().toString().trim();
+ double latitude = Double.parseDouble(mLatitudeEditText.getText().toString());
+ double longitude = Double.parseDouble(mLongitudeEditText.getText().toString());
+ float radius = Float.parseFloat(radiusEditText.getText().toString());
+
+ // 验证输入
+ if (TextUtils.isEmpty(locationName)) {
+ locationName = "未知位置";
+ }
+ if (radius <= 0) {
+ radius = 100; // 默认100米
}
- }
- });
- // 取消按钮
- builder.setNegativeButton(android.R.string.cancel, null);
+ // 设置位置提醒
+ mWorkingNote.setAlertLocation(latitude, longitude, radius, locationName, true);
+ } catch (NumberFormatException e) {
+ Toast.makeText(NoteEditActivity.this, "请输入有效的位置信息", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
- // 显示对话框
- builder.show();
- }
- /**
- * 分享笔记内容到其他应用
- */
- private void sendTo(Context context, String info) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_TEXT, info); // 设置分享文本
- intent.setType("text/plain"); // 设置MIME类型
- context.startActivity(intent); // 启动分享选择器
- }
+ // 取消按钮
+ builder.setNegativeButton(android.R.string.cancel, null);
- /**
- * 创建新笔记
- */
- private void createNewNote() {
- // 先保存当前编辑的笔记
- saveNote();
+ // 显示对话框
+ builder.show();
+}
+/**
+ * 分享笔记内容到其他应用
+ */
+private void sendTo(Context context, String info) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_TEXT, info); // 设置分享文本
+ intent.setType("text/plain"); // 设置MIME类型
+ context.startActivity(intent); // 启动分享选择器
+}
- // 结束当前活动并启动新的编辑活动
- finish();
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
- startActivity(intent);
- }
+/**
+ * 创建新笔记
+ */
+private void createNewNote() {
+ // 先保存当前编辑的笔记
+ saveNote();
+
+ // 结束当前活动并启动新的编辑活动
+ finish();
+ Intent intent = new Intent(this, NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
+ intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
+ startActivity(intent);
+}
- /**
- * 删除当前笔记
- */
- private void deleteCurrentNote() {
- if (mWorkingNote.existInDatabase()) {
- HashSet ids = new HashSet();
- long id = mWorkingNote.getNoteId();
- if (id != Notes.ID_ROOT_FOLDER) {
- ids.add(id);
- } else {
- Log.d(TAG, "Wrong note id, should not happen");
- }
+/**
+ * 删除当前笔记
+ */
+private void deleteCurrentNote() {
+ if (mWorkingNote.existInDatabase()) {
+ HashSet ids = new HashSet();
+ long id = mWorkingNote.getNoteId();
+ if (id != Notes.ID_ROOT_FOLDER) {
+ ids.add(id);
+ } else {
+ Log.d(TAG, "Wrong note id, should not happen");
+ }
- // 根据同步模式选择删除方式
- if (!isSyncMode()) {
- // 非同步模式:直接删除
- if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
- Log.e(TAG, "Delete Note error");
- }
- } else {
- // 同步模式:移动到垃圾箱
- if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
- Log.e(TAG, "Move notes to trash folder error, should not happens");
- }
+ // 根据同步模式选择删除方式
+ if (!isSyncMode()) {
+ // 非同步模式:直接删除
+ if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
+ Log.e(TAG, "Delete Note error");
+ }
+ } else {
+ // 同步模式:移动到垃圾箱
+ if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) {
+ Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
- mWorkingNote.markDeleted(true); // 标记为已删除
}
+ mWorkingNote.markDeleted(true); // 标记为已删除
+}
- /**
- * 检查是否处于同步模式
- */
- private boolean isSyncMode() {
- return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
- }
+/**
+ * 检查是否处于同步模式
+ */
+private boolean isSyncMode() {
+ return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
+}
- /**
- * 提醒时间改变回调(NoteSettingChangedListener接口方法)
- */
- public void onClockAlertChanged(long date, boolean set) {
- // 对于未保存的笔记,先保存再设置提醒
- if (!mWorkingNote.existInDatabase()) {
- saveNote();
- }
+/**
+ * 提醒时间改变回调(NoteSettingChangedListener接口方法)
+ */
+public void onClockAlertChanged(long date, boolean set) {
+ // 对于未保存的笔记,先保存再设置提醒
+ if (!mWorkingNote.existInDatabase()) {
+ saveNote();
+ }
- if (mWorkingNote.getNoteId() > 0) {
- // 创建闹钟意图
- Intent intent = new Intent(this, AlarmReceiver.class);
- intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
- AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
-
- showAlertHeader(); // 更新提醒显示
- if(!set) {
- // 取消提醒
- alarmManager.cancel(pendingIntent);
- } else {
- // 设置提醒
- alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
- }
+ if (mWorkingNote.getNoteId() > 0) {
+ // 创建闹钟意图
+ Intent intent = new Intent(this, AlarmReceiver.class);
+ intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
+
+ showAlertHeader(); // 更新提醒显示
+ if(!set) {
+ // 取消提醒
+ alarmManager.cancel(pendingIntent);
} else {
- // 笔记为空,无法设置提醒
- Log.e(TAG, "Clock alert setting error");
- showToast(R.string.error_note_empty_for_clock);
+ // 设置提醒
+ alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
+ } else {
+ // 笔记为空,无法设置提醒
+ Log.e(TAG, "Clock alert setting error");
+ showToast(R.string.error_note_empty_for_clock);
}
+}
- /**
- * 位置提醒改变回调(NoteSettingChangedListener接口方法)
- */
- public void onLocationAlertChanged(double latitude, double longitude, float radius, String locationName, boolean set) {
- // 对于未保存的笔记,先保存再设置提醒
- if (!mWorkingNote.existInDatabase()) {
- saveNote();
- }
+/**
+ * 位置提醒改变回调(NoteSettingChangedListener接口方法)
+ */
+public void onLocationAlertChanged(double latitude, double longitude, float radius, String locationName, boolean set) {
+ // 对于未保存的笔记,先保存再设置提醒
+ if (!mWorkingNote.existInDatabase()) {
+ saveNote();
+ }
- if (mWorkingNote.getNoteId() > 0) {
- showAlertHeader(); // 更新提醒显示
- // 位置提醒的地理围栏实现将在后续版本中添加
- if (set) {
- Toast.makeText(this, "位置提醒已设置", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(this, "位置提醒已取消", Toast.LENGTH_SHORT).show();
- }
+ if (mWorkingNote.getNoteId() > 0) {
+ showAlertHeader(); // 更新提醒显示
+ // 位置提醒的地理围栏实现将在后续版本中添加
+ if (set) {
+ Toast.makeText(this, "位置提醒已设置", Toast.LENGTH_SHORT).show();
} else {
- // 笔记为空,无法设置提醒
- Log.e(TAG, "Location alert setting error");
- showToast(R.string.error_note_empty_for_clock);
+ Toast.makeText(this, "位置提醒已取消", Toast.LENGTH_SHORT).show();
}
+ } else {
+ // 笔记为空,无法设置提醒
+ Log.e(TAG, "Location alert setting error");
+ showToast(R.string.error_note_empty_for_clock);
}
+}
- /**
- * 小部件改变回调(NoteSettingChangedListener接口方法)
- */
- public void onWidgetChanged() {
- updateWidget(); // 更新关联的小部件
+/**
+ * 小部件改变回调(NoteSettingChangedListener接口方法)
+ */
+public void onWidgetChanged() {
+ updateWidget(); // 更新关联的小部件
+}
+
+/**
+ * 清单模式下删除编辑项(OnTextViewChangeListener接口方法)
+ */
+public void onEditTextDelete(int index, String text) {
+ int childCount = mEditTextList.getChildCount();
+ if (childCount == 1) {
+ return; // 只有一个项时不删除
}
- /**
- * 清单模式下删除编辑项(OnTextViewChangeListener接口方法)
- */
- public void onEditTextDelete(int index, String text) {
- int childCount = mEditTextList.getChildCount();
- if (childCount == 1) {
- return; // 只有一个项时不删除
- }
+ // 更新后面所有项的索引
+ for (int i = index + 1; i < childCount; i++) {
+ ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
+ .setIndex(i - 1);
+ }
- // 更新后面所有项的索引
- for (int i = index + 1; i < childCount; i++) {
- ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
- .setIndex(i - 1);
- }
+ // 移除指定位置的视图
+ mEditTextList.removeViewAt(index);
+ NoteEditText edit = null;
+ // 决定焦点给哪个编辑框
+ if(index == 0) {
+ edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(R.id.et_edit_text);
+ } else {
+ edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(R.id.et_edit_text);
+ }
+ // 将删除的文本追加到获得焦点的编辑框
+ int length = edit.length();
+ edit.append(text);
+ edit.requestFocus();
+ edit.setSelection(length);
+}
- // 移除指定位置的视图
- mEditTextList.removeViewAt(index);
- NoteEditText edit = null;
- // 决定焦点给哪个编辑框
- if(index == 0) {
- edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(R.id.et_edit_text);
- } else {
- edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(R.id.et_edit_text);
- }
- // 将删除的文本追加到获得焦点的编辑框
- int length = edit.length();
- edit.append(text);
- edit.requestFocus();
- edit.setSelection(length);
+/**
+ * 清单模式下插入新编辑项(OnTextViewChangeListener接口方法)
+ */
+public void onEditTextEnter(int index, String text) {
+ // 安全检查
+ if(index > mEditTextList.getChildCount()) {
+ Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
- /**
- * 清单模式下插入新编辑项(OnTextViewChangeListener接口方法)
- */
- public void onEditTextEnter(int index, String text) {
- // 安全检查
- if(index > mEditTextList.getChildCount()) {
- Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
- }
+ // 创建新列表项并插入到指定位置
+ View view = getListItem(text, index);
+ mEditTextList.addView(view, index);
+ NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
+ edit.requestFocus();
+ edit.setSelection(0);
+
+ // 更新后面所有项的索引
+ for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
+ ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
+ .setIndex(i);
+ }
+}
- // 创建新列表项并插入到指定位置
- View view = getListItem(text, index);
- mEditTextList.addView(view, index);
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- edit.requestFocus();
- edit.setSelection(0);
-
- // 更新后面所有项的索引
- for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
- ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
- .setIndex(i);
+/**
+ * 切换到清单模式
+ */
+private void switchToListMode(String text) {
+ mEditTextList.removeAllViews(); // 清空现有视图
+ String[] items = text.split("\n"); // 按换行符分割文本
+ int index = 0;
+ for (String item : items) {
+ if(!TextUtils.isEmpty(item)) {
+ // 为每个非空段落创建列表项
+ mEditTextList.addView(getListItem(item, index));
+ index++;
}
}
+ // 添加一个空的列表项用于输入新内容
+ mEditTextList.addView(getListItem("", index));
+ mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
- /**
- * 切换到清单模式
- */
- private void switchToListMode(String text) {
- mEditTextList.removeAllViews(); // 清空现有视图
- String[] items = text.split("\n"); // 按换行符分割文本
- int index = 0;
- for (String item : items) {
- if(!TextUtils.isEmpty(item)) {
- // 为每个非空段落创建列表项
- mEditTextList.addView(getListItem(item, index));
- index++;
- }
- }
- // 添加一个空的列表项用于输入新内容
- mEditTextList.addView(getListItem("", index));
- mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
+ // 切换显示模式
+ mNoteEditor.setVisibility(View.GONE);
+ mEditTextList.setVisibility(View.VISIBLE);
+}
- // 切换显示模式
- mNoteEditor.setVisibility(View.GONE);
- mEditTextList.setVisibility(View.VISIBLE);
+/**
+ * 高亮显示搜索关键词
+ */
+private Spannable getHighlightQueryResult(String fullText, String userQuery) {
+ SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
+ if (!TextUtils.isEmpty(userQuery)) {
+ mPattern = Pattern.compile(userQuery);
+ Matcher m = mPattern.matcher(fullText);
+ int start = 0;
+ // 为所有匹配的文本设置背景色
+ while (m.find(start)) {
+ spannable.setSpan(
+ new BackgroundColorSpan(this.getResources().getColor(
+ R.color.user_query_highlight)), m.start(), m.end(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ start = m.end();
+ }
}
+ return spannable;
+}
- /**
- * 高亮显示搜索关键词
- */
- private Spannable getHighlightQueryResult(String fullText, String userQuery) {
- SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
- if (!TextUtils.isEmpty(userQuery)) {
- mPattern = Pattern.compile(userQuery);
- Matcher m = mPattern.matcher(fullText);
- int start = 0;
- // 为所有匹配的文本设置背景色
- while (m.find(start)) {
- spannable.setSpan(
- new BackgroundColorSpan(this.getResources().getColor(
- R.color.user_query_highlight)), m.start(), m.end(),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
- start = m.end();
+/**
+ * 创建清单模式下的列表项视图
+ */
+private View getListItem(String item, int index) {
+ View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
+ final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
+ edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
+
+ // 设置复选框状态改变监听
+ CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
+ cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ // 选中时添加删除线
+ edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ } else {
+ // 取消选中时恢复正常样式
+ edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
- return spannable;
+ });
+
+ // 解析清单标记符号
+ if (item.startsWith(TAG_CHECKED)) {
+ cb.setChecked(true); // 已完成的项
+ edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ item = item.substring(TAG_CHECKED.length(), item.length()).trim();
+ } else if (item.startsWith(TAG_UNCHECKED)) {
+ cb.setChecked(false); // 未完成的项
+ edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
+ item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
- /**
- * 创建清单模式下的列表项视图
- */
- private View getListItem(String item, int index) {
- View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
- final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
-
- // 设置复选框状态改变监听
- CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
- cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (isChecked) {
- // 选中时添加删除线
- edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
- } else {
- // 取消选中时恢复正常样式
- edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
- }
- }
- });
-
- // 解析清单标记符号
- if (item.startsWith(TAG_CHECKED)) {
- cb.setChecked(true); // 已完成的项
- edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
- item = item.substring(TAG_CHECKED.length(), item.length()).trim();
- } else if (item.startsWith(TAG_UNCHECKED)) {
- cb.setChecked(false); // 未完成的项
- edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
- item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
- }
+ edit.setOnTextViewChangeListener(this);
+ edit.setIndex(index);
+ edit.setText(getHighlightQueryResult(item, mUserQuery));
+ return view;
+}
- edit.setOnTextViewChangeListener(this);
- edit.setIndex(index);
- edit.setText(getHighlightQueryResult(item, mUserQuery));
- return view;
+/**
+ * 文本变化回调(OnTextViewChangeListener接口方法)
+ */
+public void onTextChange(int index, boolean hasText) {
+ if (index >= mEditTextList.getChildCount()) {
+ Log.e(TAG, "Wrong index, should not happen");
+ return;
+ }
+ // 根据是否有文本显示或隐藏复选框
+ if(hasText) {
+ mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
+ } else {
+ mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
+}
- /**
- * 文本变化回调(OnTextViewChangeListener接口方法)
- */
- public void onTextChange(int index, boolean hasText) {
- if (index >= mEditTextList.getChildCount()) {
- Log.e(TAG, "Wrong index, should not happen");
- return;
+/**
+ * 清单模式改变回调(NoteSettingChangedListener接口方法)
+ */
+public void onCheckListModeChanged(int oldMode, int newMode) {
+ if (newMode == TextNote.MODE_CHECK_LIST) {
+ // 切换到清单模式
+ switchToListMode(mNoteEditor.getText().toString());
+
+ // 隐藏富文本格式工具栏
+ if (mFormatToolbar != null) {
+ mFormatToolbar.setVisibility(View.GONE);
}
- // 根据是否有文本显示或隐藏复选框
- if(hasText) {
- mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
- } else {
- mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
+ } else {
+ // 切换到普通模式
+ if (!getWorkingText()) {
+ // 清理清单标记符号
+ mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
+ ""));
}
- }
+ mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
+ mEditTextList.setVisibility(View.GONE);
+ mNoteEditor.setVisibility(View.VISIBLE);
- /**
- * 清单模式改变回调(NoteSettingChangedListener接口方法)
- */
- public void onCheckListModeChanged(int oldMode, int newMode) {
- if (newMode == TextNote.MODE_CHECK_LIST) {
- // 切换到清单模式
- switchToListMode(mNoteEditor.getText().toString());
-
- // 隐藏富文本格式工具栏
- if (mFormatToolbar != null) {
- mFormatToolbar.setVisibility(View.GONE);
- }
- } else {
- // 切换到普通模式
- if (!getWorkingText()) {
- // 清理清单标记符号
- mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
- ""));
- }
- mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
- mEditTextList.setVisibility(View.GONE);
- mNoteEditor.setVisibility(View.VISIBLE);
-
- // 显示富文本格式工具栏
- if (mFormatToolbar != null) {
- mFormatToolbar.setVisibility(View.VISIBLE);
- }
+ // 显示富文本格式工具栏
+ if (mFormatToolbar != null) {
+ mFormatToolbar.setVisibility(View.VISIBLE);
}
}
+}
- /**
- * 从UI获取当前编辑的文本内容
- * @return 是否存在已完成的项
- */
- boolean getWorkingText() {
- boolean hasChecked = false;
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- // 清单模式:构建格式化的文本
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mEditTextList.getChildCount(); i++) {
- View view = mEditTextList.getChildAt(i);
- NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
- if (!TextUtils.isEmpty(edit.getText())) {
- if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
- // 已完成的项
- sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
- hasChecked = true;
- } else {
- // 未完成的项
- sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
- }
+/**
+ * 从UI获取当前编辑的文本内容
+ * @return 是否存在已完成的项
+ */
+boolean getWorkingText() {
+ boolean hasChecked = false;
+ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
+ // 清单模式:构建格式化的文本
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mEditTextList.getChildCount(); i++) {
+ View view = mEditTextList.getChildAt(i);
+ NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
+ if (!TextUtils.isEmpty(edit.getText())) {
+ if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
+ // 已完成的项
+ sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
+ hasChecked = true;
+ } else {
+ // 未完成的项
+ sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
}
}
- mWorkingNote.setWorkingText(sb.toString());
- } else {
- // 普通模式:获取带格式的文本
- mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
-
- // 保存格式化信息到工作文本
- String formatInfo = RichTextFormatUtils.serializeFormatInfo(mNoteEditor.getText());
- mWorkingNote.setRichTextFormat(formatInfo);
-
- // TODO: 保存图片信息到工作笔记 - 这需要更复杂的数据结构来存储图片位置和路径
- // 当前实现会在普通文本模式中保留图片,但在清单模式中会丢失图片
}
- return hasChecked;
- }
+ mWorkingNote.setWorkingText(sb.toString());
+ } else {
+ // 普通模式:获取带格式的文本
+ mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
- /**
- * 保存笔记到数据库
- * @return 保存是否成功
- */
- private boolean saveNote() {
- getWorkingText(); // 从UI获取最新文本
- boolean saved = mWorkingNote.saveNote(); // 保存到数据库
- if (saved) {
- setResult(RESULT_OK); // 设置活动结果
- }
- return saved;
+ // 保存格式化信息到工作文本
+ String formatInfo = RichTextFormatUtils.serializeFormatInfo(mNoteEditor.getText());
+ mWorkingNote.setRichTextFormat(formatInfo);
+
+ // TODO: 保存图片信息到工作笔记 - 这需要更复杂的数据结构来存储图片位置和路径
+ // 当前实现会在普通文本模式中保留图片,但在清单模式中会丢失图片
}
+ return hasChecked;
+}
- /**
- * 发送笔记到桌面快捷方式
- */
- private void sendToDesktop() {
- // 确保笔记已保存到数据库
- if (!mWorkingNote.existInDatabase()) {
- saveNote();
- }
+/**
+ * 保存笔记到数据库
+ * @return 保存是否成功
+ */
+private boolean saveNote() {
+ getWorkingText(); // 从UI获取最新文本
+ boolean saved = mWorkingNote.saveNote(); // 保存到数据库
+ if (saved) {
+ setResult(RESULT_OK); // 设置活动结果
+ }
+ return saved;
+}
- if (mWorkingNote.getNoteId() > 0) {
- Intent sender = new Intent();
- // 创建快捷方式意图
- Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
- shortcutIntent.setAction(Intent.ACTION_VIEW);
- shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
-
- sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
- makeShortcutIconTitle(mWorkingNote.getContent()));
- sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
- sender.putExtra("duplicate", true); // 允许重复创建
- sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
-
- showToast(R.string.info_note_enter_desktop);
- sendBroadcast(sender);
- } else {
- // 笔记为空,无法创建快捷方式
- Log.e(TAG, "Send to desktop error");
- showToast(R.string.error_note_empty_for_send_to_desktop);
- }
+/**
+ * 发送笔记到桌面快捷方式
+ */
+private void sendToDesktop() {
+ // 确保笔记已保存到数据库
+ if (!mWorkingNote.existInDatabase()) {
+ saveNote();
}
- /**
- * 生成桌面快捷方式的标题
- */
- private String makeShortcutIconTitle(String content) {
- // 清理清单标记符号
- content = content.replace(TAG_CHECKED, "");
- content = content.replace(TAG_UNCHECKED, "");
- // 截取标题长度
- return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
- SHORTCUT_ICON_TITLE_MAX_LEN) : content;
+ if (mWorkingNote.getNoteId() > 0) {
+ Intent sender = new Intent();
+ // 创建快捷方式意图
+ Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
+ shortcutIntent.setAction(Intent.ACTION_VIEW);
+ shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
+
+ sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
+ makeShortcutIconTitle(mWorkingNote.getContent()));
+ sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+ Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
+ sender.putExtra("duplicate", true); // 允许重复创建
+ sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
+
+ showToast(R.string.info_note_enter_desktop);
+ sendBroadcast(sender);
+ } else {
+ // 笔记为空,无法创建快捷方式
+ Log.e(TAG, "Send to desktop error");
+ showToast(R.string.error_note_empty_for_send_to_desktop);
}
+}
- /**
- * 处理富文本格式化
- * @param v 点击的按钮
- */
- private void handleRichTextFormatting(View v) {
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- // 清单模式下暂不支持富文本编辑
- Toast.makeText(this, "Rich text formatting is not supported in checklist mode", Toast.LENGTH_SHORT).show();
- return;
- }
-
- int selectionStart = mNoteEditor.getSelectionStart();
- int selectionEnd = mNoteEditor.getSelectionEnd();
-
- // 确保有选中的文本
- if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
- Spannable str = mNoteEditor.getText();
-
- switch (v.getId()) {
- case R.id.btn_bold:
- toggleStyleSpan(str, selectionStart, selectionEnd, android.graphics.Typeface.BOLD);
- break;
- case R.id.btn_italic:
- toggleStyleSpan(str, selectionStart, selectionEnd, android.graphics.Typeface.ITALIC);
- break;
- case R.id.btn_underline:
- toggleUnderlineSpan(str, selectionStart, selectionEnd);
- break;
- case R.id.btn_strikethrough:
- toggleStrikethroughSpan(str, selectionStart, selectionEnd);
- break;
- }
- } else {
- Toast.makeText(this, "Please select text first", Toast.LENGTH_SHORT).show();
- }
+/**
+ * 生成桌面快捷方式的标题
+ */
+private String makeShortcutIconTitle(String content) {
+ // 清理清单标记符号
+ content = content.replace(TAG_CHECKED, "");
+ content = content.replace(TAG_UNCHECKED, "");
+ // 截取标题长度
+ return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
+ SHORTCUT_ICON_TITLE_MAX_LEN) : content;
+}
+
+/**
+ * 处理富文本格式化
+ * @param v 点击的按钮
+ */
+private void handleRichTextFormatting(View v) {
+ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
+ // 清单模式下暂不支持富文本编辑
+ Toast.makeText(this, "Rich text formatting is not supported in checklist mode", Toast.LENGTH_SHORT).show();
+ return;
}
-
- /**
- * 显示图片选择对话框
- */
- private void showImageSelectionDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("选择图片"); // 标题
-
- // 创建垂直排列的按钮布局
- LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.VERTICAL);
- layout.setPadding(50, 30, 50, 30);
-
- // 相册按钮
- Button albumBtn = new Button(this);
- albumBtn.setText("相册");
- albumBtn.setAllCaps(false); // 不要大写
- albumBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- openGallery();
- }
- });
- // 系统图片按钮
- Button systemBtn = new Button(this);
- systemBtn.setText("系统图片");
- systemBtn.setAllCaps(false);
- systemBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showSystemImagesDialog();
- }
- });
+ int selectionStart = mNoteEditor.getSelectionStart();
+ int selectionEnd = mNoteEditor.getSelectionEnd();
- // 添加按钮到布局
- layout.addView(albumBtn);
- layout.addView(systemBtn);
+ // 确保有选中的文本
+ if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
+ Spannable str = mNoteEditor.getText();
- builder.setView(layout);
- builder.setNegativeButton("取消", null);
- builder.show();
+ switch (v.getId()) {
+ case R.id.btn_bold:
+ toggleStyleSpan(str, selectionStart, selectionEnd, android.graphics.Typeface.BOLD);
+ break;
+ case R.id.btn_italic:
+ toggleStyleSpan(str, selectionStart, selectionEnd, android.graphics.Typeface.ITALIC);
+ break;
+ case R.id.btn_underline:
+ toggleUnderlineSpan(str, selectionStart, selectionEnd);
+ break;
+ case R.id.btn_strikethrough:
+ toggleStrikethroughSpan(str, selectionStart, selectionEnd);
+ break;
+ }
+ } else {
+ Toast.makeText(this, "Please select text first", Toast.LENGTH_SHORT).show();
}
+}
- /**
- * 显示系统图片选择对话框
- */
- private void showSystemImagesDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("选择系统图片");
+/**
+ * 显示图片选择对话框
+ */
+private void showImageSelectionDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("选择图片"); // 标题
+
+ // 创建垂直排列的按钮布局
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(50, 30, 50, 30);
+
+ // 相册按钮
+ Button albumBtn = new Button(this);
+ albumBtn.setText("相册");
+ albumBtn.setAllCaps(false); // 不要大写
+ albumBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ openGallery();
+ }
+ });
+
+ // 系统图片按钮
+ Button systemBtn = new Button(this);
+ systemBtn.setText("系统图片");
+ systemBtn.setAllCaps(false);
+ systemBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showSystemImagesDialog();
+ }
+ });
- // 创建水平排列的图片布局
- LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.HORIZONTAL);
- layout.setPadding(20, 20, 20, 20);
+ // 添加按钮到布局
+ layout.addView(albumBtn);
+ layout.addView(systemBtn);
- // 创建猫图片
- ImageView catImageView = new ImageView(this);
- try {
- // 检查资源是否存在
- catImageView.setImageResource(R.drawable.cat); // 引用res/drawable-hdpi/cat.png
- } catch (Exception e) {
- // 如果资源不存在,显示占位符或提示信息
- catImageView.setImageResource(android.R.drawable.ic_menu_gallery); // 使用系统默认图标作为占位符
- Toast.makeText(this, "cat.png 图片资源未找到", Toast.LENGTH_SHORT).show();
- }
- catImageView.setLayoutParams(new LinearLayout.LayoutParams(
- 300, 300, 1.0f)); // 设置尺寸和权重
- catImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- catImageView.setPadding(10, 10, 10, 10);
- catImageView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // 使用猫图片的资源ID创建Uri
- try {
- Uri catUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.drawable.cat);
- insertImageToEditor(catUri);
- } catch (Exception e) {
- Toast.makeText(NoteEditActivity.this, "无法加载cat.png图片", Toast.LENGTH_SHORT).show();
- }
- }
- });
+ builder.setView(layout);
+ builder.setNegativeButton("取消", null);
+ builder.show();
+}
- // 创建狗图片
- ImageView dogImageView = new ImageView(this);
- try {
- // 检查资源是否存在
- dogImageView.setImageResource(R.drawable.dog); // 引用res/drawable-hdpi/dog.png
- } catch (Exception e) {
- // 如果资源不存在,显示占位符或提示信息
- dogImageView.setImageResource(android.R.drawable.ic_menu_gallery); // 使用系统默认图标作为占位符
- Toast.makeText(this, "dog.png 图片资源未找到", Toast.LENGTH_SHORT).show();
+/**
+ * 显示系统图片选择对话框
+ */
+private void showSystemImagesDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("选择系统图片");
+
+ // 创建水平排列的图片布局
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setPadding(20, 20, 20, 20);
+
+ // 创建猫图片
+ ImageView catImageView = new ImageView(this);
+ try {
+ // 检查资源是否存在
+ catImageView.setImageResource(R.drawable.cat); // 引用res/drawable-hdpi/cat.png
+ } catch (Exception e) {
+ // 如果资源不存在,显示占位符或提示信息
+ catImageView.setImageResource(android.R.drawable.ic_menu_gallery); // 使用系统默认图标作为占位符
+ Toast.makeText(this, "cat.png 图片资源未找到", Toast.LENGTH_SHORT).show();
+ }
+ catImageView.setLayoutParams(new LinearLayout.LayoutParams(
+ 300, 300, 1.0f)); // 设置尺寸和权重
+ catImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ catImageView.setPadding(10, 10, 10, 10);
+ catImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 使用猫图片的资源ID创建Uri
+ try {
+ Uri catUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.drawable.cat);
+ insertImageToEditor(catUri);
+ } catch (Exception e) {
+ Toast.makeText(NoteEditActivity.this, "无法加载cat.png图片", Toast.LENGTH_SHORT).show();
+ }
}
- dogImageView.setLayoutParams(new LinearLayout.LayoutParams(
- 300, 300, 1.0f)); // 设置尺寸和权重
- dogImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- dogImageView.setPadding(10, 10, 10, 10);
- dogImageView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // 使用狗图片的资源ID创建Uri
- try {
- Uri dogUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.drawable.dog);
- insertImageToEditor(dogUri);
- } catch (Exception e) {
- Toast.makeText(NoteEditActivity.this, "无法加载dog.png图片", Toast.LENGTH_SHORT).show();
- }
+ });
+
+ // 创建狗图片
+ ImageView dogImageView = new ImageView(this);
+ try {
+ // 检查资源是否存在
+ dogImageView.setImageResource(R.drawable.dog); // 引用res/drawable-hdpi/dog.png
+ } catch (Exception e) {
+ // 如果资源不存在,显示占位符或提示信息
+ dogImageView.setImageResource(android.R.drawable.ic_menu_gallery); // 使用系统默认图标作为占位符
+ Toast.makeText(this, "dog.png 图片资源未找到", Toast.LENGTH_SHORT).show();
+ }
+ dogImageView.setLayoutParams(new LinearLayout.LayoutParams(
+ 300, 300, 1.0f)); // 设置尺寸和权重
+ dogImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ dogImageView.setPadding(10, 10, 10, 10);
+ dogImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 使用狗图片的资源ID创建Uri
+ try {
+ Uri dogUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.drawable.dog);
+ insertImageToEditor(dogUri);
+ } catch (Exception e) {
+ Toast.makeText(NoteEditActivity.this, "无法加载dog.png图片", Toast.LENGTH_SHORT).show();
}
- });
+ }
+ });
- // 添加图片到布局
- layout.addView(catImageView);
- layout.addView(dogImageView);
+ // 添加图片到布局
+ layout.addView(catImageView);
+ layout.addView(dogImageView);
- builder.setView(layout);
- builder.setNegativeButton("取消", null);
- builder.show();
+ builder.setView(layout);
+ builder.setNegativeButton("取消", null);
+ builder.show();
+}
+
+/**
+ * 打开相册选择图片
+ */
+private void openGallery() {
+ Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ intent.setType("image/*");
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
+ } else {
+ Toast.makeText(this, "设备不支持图片选择功能", Toast.LENGTH_SHORT).show();
}
+}
- /**
- * 打开相册选择图片
- */
- private void openGallery() {
- Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
- intent.setType("image/*");
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
- } else {
- Toast.makeText(this, "设备不支持图片选择功能", Toast.LENGTH_SHORT).show();
- }
+/**
+ * 打开系统图片选择器
+ */
+private void openSystemImagePicker() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(intent, REQUEST_CODE_CHOOSE_IMAGE);
+ } else {
+ Toast.makeText(this, "设备不支持图片选择功能", Toast.LENGTH_SHORT).show();
}
+}
- /**
- * 打开系统图片选择器
- */
- private void openSystemImagePicker() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType("image/*");
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivityForResult(intent, REQUEST_CODE_CHOOSE_IMAGE);
- } else {
- Toast.makeText(this, "设备不支持图片选择功能", Toast.LENGTH_SHORT).show();
+/**
+ * 切换样式(粗体、斜体)
+ */
+private void toggleStyleSpan(Spannable str, int start, int end, int style) {
+ // 检查选中区域内是否已有相同的样式
+ StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class);
+ boolean hasStyle = false;
+
+ for (StyleSpan span : spans) {
+ if (span.getStyle() == style) {
+ str.removeSpan(span);
+ hasStyle = true;
}
}
- /**
- * 切换样式(粗体、斜体)
- */
- private void toggleStyleSpan(Spannable str, int start, int end, int style) {
- // 检查选中区域内是否已有相同的样式
- StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class);
- boolean hasStyle = false;
-
- for (StyleSpan span : spans) {
- if (span.getStyle() == style) {
- str.removeSpan(span);
- hasStyle = true;
- }
- }
+ // 如果没有对应样式,则添加
+ if (!hasStyle) {
+ str.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+}
+
+/**
+ * 切换下划线
+ */
+private void toggleUnderlineSpan(Spannable str, int start, int end) {
+ UnderlineSpan[] spans = str.getSpans(start, end, UnderlineSpan.class);
- // 如果没有对应样式,则添加
- if (!hasStyle) {
- str.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (spans.length > 0) {
+ // 如果已存在下划线,则移除
+ for (UnderlineSpan span : spans) {
+ str.removeSpan(span);
}
+ } else {
+ // 如果不存在下划线,则添加
+ str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+}
- /**
- * 切换下划线
- */
- private void toggleUnderlineSpan(Spannable str, int start, int end) {
- UnderlineSpan[] spans = str.getSpans(start, end, UnderlineSpan.class);
+/**
+ * 切换删除线
+ */
+private void toggleStrikethroughSpan(Spannable str, int start, int end) {
+ StrikethroughSpan[] spans = str.getSpans(start, end, StrikethroughSpan.class);
- if (spans.length > 0) {
- // 如果已存在下划线,则移除
- for (UnderlineSpan span : spans) {
- str.removeSpan(span);
- }
- } else {
- // 如果不存在下划线,则添加
- str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (spans.length > 0) {
+ // 如果已存在删除线,则移除
+ for (StrikethroughSpan span : spans) {
+ str.removeSpan(span);
}
+ } else {
+ // 如果不存在删除线,则添加
+ str.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+}
- /**
- * 切换删除线
- */
- private void toggleStrikethroughSpan(Spannable str, int start, int end) {
- StrikethroughSpan[] spans = str.getSpans(start, end, StrikethroughSpan.class);
+/**
+ * 显示Toast提示
+ */
+private void showToast(int resId) {
+ showToast(resId, Toast.LENGTH_SHORT);
+}
+
+private void showToast(int resId, int duration) {
+ Toast.makeText(this, resId, duration).show();
+}
- if (spans.length > 0) {
- // 如果已存在删除线,则移除
- for (StrikethroughSpan span : spans) {
- str.removeSpan(span);
+/**
+ * 处理编辑器中的按键事件,包括删除图片
+ */
+private void setupImageDeletionHandling() {
+ mNoteEditor.setOnKeyListener(new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
+ if (event.getAction() == android.view.KeyEvent.ACTION_DOWN &&
+ keyCode == android.view.KeyEvent.KEYCODE_DEL) {
+
+ // 检查光标前是否是图片
+ int cursorPos = mNoteEditor.getSelectionStart();
+ if (cursorPos > 0) {
+ // 这里可以添加删除图片的逻辑
+ // 检查当前位置是否有图片Span
+ android.text.style.ImageSpan[] imageSpans =
+ mNoteEditor.getText().getSpans(cursorPos - 1, cursorPos,
+ android.text.style.ImageSpan.class);
+
+ if (imageSpans.length > 0) {
+ // 删除图片Span
+ for (android.text.style.ImageSpan span : imageSpans) {
+ int start = mNoteEditor.getText().getSpanStart(span);
+ int end = mNoteEditor.getText().getSpanEnd(span);
+
+ // 移除图片Span
+ mNoteEditor.getText().removeSpan(span);
+
+ // 删除图片占位符文本
+ mNoteEditor.getText().delete(start, end);
+ }
+
+ return true; // 消费此事件
+ }
+ }
}
- } else {
- // 如果不存在删除线,则添加
- str.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return false;
}
+ });
+}
+
+ /**
+ * 显示解锁对话框
+ */
+ private void showUnlockDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_unlock, null);
+ builder.setView(dialogView);
+
+ TextView tvTitle = dialogView.findViewById(R.id.tv_unlock_title);
+ TextView tvInstruction = dialogView.findViewById(R.id.tv_unlock_instruction);
+ Button btnUnlockPassword = dialogView.findViewById(R.id.btn_unlock_password);
+ Button btnUnlockGesture = dialogView.findViewById(R.id.btn_unlock_gesture);
+ TextView tvAttempts = dialogView.findViewById(R.id.tv_unlock_attempts);
+ TextView tvError = dialogView.findViewById(R.id.tv_unlock_error);
+
+ tvTitle.setText("解锁");
+ tvInstruction.setText("便签已被锁定,请解锁");
+
+ // 更新尝试次数显示
+ tvAttempts.setText("尝试次数: " + mUnlockAttempts + "/" + MAX_UNLOCK_ATTEMPTS);
+
+ AlertDialog dialog = builder.create();
+
+ // 密码解锁按钮
+ btnUnlockPassword.setOnClickListener(v -> {
+ showPasswordUnlockDialog(dialog);
+ });
+
+ // 手势解锁按钮
+ btnUnlockGesture.setOnClickListener(v -> {
+ showGestureUnlockDialog(dialog);
+ });
+
+ dialog.show();
}
/**
- * 显示Toast提示
+ * 显示密码解锁对话框
*/
- private void showToast(int resId) {
- showToast(resId, Toast.LENGTH_SHORT);
- }
+ private void showPasswordUnlockDialog(AlertDialog parentDialog) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null);
+ builder.setView(dialogView);
+
+ TextView tvTitle = dialogView.findViewById(R.id.tv_password_title);
+ EditText etPassword = dialogView.findViewById(R.id.et_password);
+ EditText etConfirmPassword = dialogView.findViewById(R.id.et_confirm_password);
+ CheckBox cbShowPassword = dialogView.findViewById(R.id.cb_show_password);
+ TextView tvHint = dialogView.findViewById(R.id.tv_password_hint);
+ TextView tvErrorMessage = dialogView.findViewById(R.id.tv_error_message);
+ Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
+ Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
+
+ // 隐藏确认密码框和相关元素,因为我们只需要输入一次密码
+ etConfirmPassword.setVisibility(View.GONE);
+ tvHint.setText("输入解锁密码");
+ tvTitle.setText("密码解锁");
+
+ AlertDialog dialog = builder.create();
+
+ // 显示/隐藏密码
+ cbShowPassword.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ int inputType = isChecked ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
+ etPassword.setInputType(inputType);
+ etPassword.setSelection(etPassword.getText().length());
+ });
+
+ // 确认按钮点击
+ btnConfirm.setOnClickListener(v -> {
+ String password = etPassword.getText().toString().trim();
+
+ if (mPrivacyLockManager.verifyPassword(mWorkingNote.getNoteId(), password)) {
+ // 密码正确,解锁成功
+ dialog.dismiss();
+ parentDialog.dismiss();
+
+ // 重新初始化界面
+ initResources();
+ initNoteScreen();
+ } else {
+ // 密码错误
+ mUnlockAttempts++;
+ if (mUnlockAttempts >= MAX_UNLOCK_ATTEMPTS) {
+ Toast.makeText(this, "解锁失败次数过多,无法继续解锁", Toast.LENGTH_SHORT).show();
+ finish(); // 关闭活动
+ return;
+ }
+
+ tvErrorMessage.setText("密码错误 (" + mUnlockAttempts + "/" + MAX_UNLOCK_ATTEMPTS + ")");
+ tvErrorMessage.setVisibility(View.VISIBLE);
+ etPassword.setText(""); // 清空密码框
+ }
+ });
- private void showToast(int resId, int duration) {
- Toast.makeText(this, resId, duration).show();
+ // 取消按钮点击
+ btnCancel.setOnClickListener(v -> dialog.dismiss());
+
+ dialog.show();
}
-
+
/**
- * 处理编辑器中的按键事件,包括删除图片
+ * 显示手势解锁对话框
*/
- private void setupImageDeletionHandling() {
- mNoteEditor.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
- if (event.getAction() == android.view.KeyEvent.ACTION_DOWN &&
- keyCode == android.view.KeyEvent.KEYCODE_DEL) {
-
- // 检查光标前是否是图片
- int cursorPos = mNoteEditor.getSelectionStart();
- if (cursorPos > 0) {
- // 这里可以添加删除图片的逻辑
- // 检查当前位置是否有图片Span
- android.text.style.ImageSpan[] imageSpans =
- mNoteEditor.getText().getSpans(cursorPos - 1, cursorPos,
- android.text.style.ImageSpan.class);
-
- if (imageSpans.length > 0) {
- // 删除图片Span
- for (android.text.style.ImageSpan span : imageSpans) {
- int start = mNoteEditor.getText().getSpanStart(span);
- int end = mNoteEditor.getText().getSpanEnd(span);
-
- // 移除图片Span
- mNoteEditor.getText().removeSpan(span);
-
- // 删除图片占位符文本
- mNoteEditor.getText().delete(start, end);
- }
-
- return true; // 消费此事件
- }
- }
+ private void showGestureUnlockDialog(AlertDialog parentDialog) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_gesture_lock, null);
+ builder.setView(dialogView);
+
+ TextView tvTitle = dialogView.findViewById(R.id.tv_gesture_title);
+ TextView tvInstruction = dialogView.findViewById(R.id.tv_gesture_instruction);
+ GestureLockView gestureLockView = dialogView.findViewById(R.id.gesture_lock_view);
+ TextView tvStatus = dialogView.findViewById(R.id.tv_gesture_status);
+ Button btnReset = dialogView.findViewById(R.id.btn_gesture_reset);
+
+ tvTitle.setText("手势解锁");
+ tvInstruction.setText("请绘制解锁手势");
+ tvStatus.setText("");
+
+ AlertDialog dialog = builder.create();
+
+ // 获取存储的手势锁类型
+ int lockType = mPrivacyLockManager.getLockType(mWorkingNote.getNoteId());
+ if (lockType != PrivacyLockManager.LOCK_TYPE_GESTURE) {
+ Toast.makeText(this, "该便签不是手势锁", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ return;
+ }
+
+ // 设置手势完成监听器
+ gestureLockView.setOnGestureCompleteListener(selectedPoints -> {
+ // 验证手势
+ if (mPrivacyLockManager.verifyGesture(mWorkingNote.getNoteId(), selectedPoints)) {
+ // 手势正确,解锁成功
+ dialog.dismiss();
+ parentDialog.dismiss();
+
+ // 重新初始化界面
+ initResources();
+ initNoteScreen();
+ } else {
+ // 手势错误
+ mUnlockAttempts++;
+ if (mUnlockAttempts >= MAX_UNLOCK_ATTEMPTS) {
+ Toast.makeText(this, "解锁失败次数过多,无法继续解锁", Toast.LENGTH_SHORT).show();
+ finish(); // 关闭活动
+ return;
}
- return false;
+
+ tvStatus.setText("验证失败 (" + mUnlockAttempts + "/" + MAX_UNLOCK_ATTEMPTS + ")");
+ gestureLockView.clearGesture();
}
});
+
+ // 重置按钮点击
+ btnReset.setOnClickListener(v -> {
+ gestureLockView.clearGesture();
+ tvStatus.setText("");
+ });
+
+ dialog.show();
}
}
\ No newline at end of file
diff --git a/src/notes/ui/NotesListActivity.java b/src/notes/ui/NotesListActivity.java
index f1b1a47..2971194 100644
--- a/src/notes/ui/NotesListActivity.java
+++ b/src/notes/ui/NotesListActivity.java
@@ -32,6 +32,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
+import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
@@ -54,7 +55,9 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
+import android.widget.CheckBox;
import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
@@ -63,6 +66,7 @@ import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.data.UserDatabaseHelper;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
@@ -72,12 +76,15 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import net.micode.notes.tool.UserManager;
+import net.micode.notes.tool.PrivacyLockManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
/**
* 笔记列表主活动,显示笔记和文件夹的列表,是应用的入口界面之一。
@@ -119,6 +126,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private long mCurrentFolderId; // 当前显示的文件夹ID
private ContentResolver mContentResolver; // 内容解析器
private ModeCallback mModeCallBack; // 多选模式的回调(动作模式)
+ private PrivacyLockManager mPrivacyLockManager; // 隐私锁管理器
private static final String TAG = "NotesListActivity"; // 日志标签
@@ -994,6 +1002,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
startPreferenceActivity(); // 跳转到设置界面
break;
}
+ case R.id.menu_add_user: {
+ showAddUserDialog(); // 添加用户
+ break;
+ }
case R.id.menu_logout: {
logout(); // 退出登录
break;
@@ -1088,7 +1100,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public void onClick(DialogInterface dialog, int which) {
// 清除登录状态
UserManager userManager = UserManager.getInstance(NotesListActivity.this);
- userManager.logout();
+ if (userManager != null) {
+ userManager.logout();
+ }
// 跳转到登录界面
Intent intent = new Intent(NotesListActivity.this, LoginRegisterActivity.class);
@@ -1103,7 +1117,101 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
/**
* 启动设置活动
+ * 显示添加用户对话框
*/
+ private void showAddUserDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ LayoutInflater inflater = LayoutInflater.from(this);
+ View dialogView = inflater.inflate(R.layout.dialog_add_user, null);
+
+ final EditText etNewUsername = dialogView.findViewById(R.id.et_new_username);
+ final EditText etNewPassword = dialogView.findViewById(R.id.et_new_password);
+ final EditText etConfirmPassword = dialogView.findViewById(R.id.et_confirm_password);
+ final TextView tvError = dialogView.findViewById(R.id.tv_error_message);
+
+ builder.setView(dialogView);
+ builder.setTitle("添加用户");
+
+ final AlertDialog dialog = builder.create();
+
+ // 取消按钮
+ Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
+ btnCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+
+ // 确认按钮
+ Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
+ btnConfirm.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String username = etNewUsername.getText().toString().trim();
+ String password = etNewPassword.getText().toString().trim();
+ String confirmPassword = etConfirmPassword.getText().toString().trim();
+
+ // 验证输入
+ if (TextUtils.isEmpty(username)) {
+ tvError.setText("请输入用户名");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (username.length() < 3) {
+ tvError.setText("用户名长度不能少于3位");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (TextUtils.isEmpty(password)) {
+ tvError.setText("请输入密码");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (password.length() < 6) {
+ tvError.setText("密码长度不能少于6位");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (TextUtils.isEmpty(confirmPassword)) {
+ tvError.setText("请确认密码");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (!password.equals(confirmPassword)) {
+ tvError.setText("两次输入的密码不一致");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ // 检查用户名是否已存在
+ UserDatabaseHelper dbHelper = UserDatabaseHelper.getInstance(NotesListActivity.this);
+ if (dbHelper.isUsernameExists(username)) {
+ tvError.setText("该用户名已存在,请更换用户名");
+ tvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ // 注册用户
+ boolean success = dbHelper.registerUser(username, password);
+ if (success) {
+ Toast.makeText(NotesListActivity.this, "添加用户成功", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ } else {
+ tvError.setText("添加用户失败,请重试");
+ tvError.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
+ dialog.show();
+ }
+
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
@@ -1190,16 +1298,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData(); // 记录长按的数据项
- // 如果长按的是笔记且不在多选模式下,启动多选模式
+
+ // 如果长按的是笔记且不在多选模式下,显示隐私锁菜单
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
- if (mNotesListView.startActionMode(mModeCallBack) != null) {
- // 启动成功,设置当前项为选中状态
- mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
- // 提供触觉反馈(震动)
- mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- } else {
- Log.e(TAG, "startActionMode fails");
- }
+ showPrivacyLockMenu(mFocusNoteDataItem.getId());
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
// 如果长按的是文件夹,设置上下文菜单监听器
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
@@ -1207,4 +1309,240 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
return false; // 返回false,让系统继续处理长按事件(显示上下文菜单等)
}
+
+ /**
+ * 显示隐私锁菜单
+ * @param noteId 便签ID
+ */
+ private void showPrivacyLockMenu(long noteId) {
+ boolean isLocked = mPrivacyLockManager.isNoteLocked(noteId);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(isLocked ? "移除隐私锁" : "添加隐私锁");
+
+ if (isLocked) {
+ builder.setMessage("确定要移除隐私锁吗?");
+ builder.setPositiveButton("移除", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // 直接移除隐私锁
+ if (mPrivacyLockManager.removePrivacyLock(noteId)) {
+ Toast.makeText(NotesListActivity.this, "成功移除隐私锁", Toast.LENGTH_SHORT).show();
+ // 刷新列表
+ mNotesListAdapter.changeCursor(null);
+ startAsyncNotesListQuery();
+ } else {
+ Toast.makeText(NotesListActivity.this, "移除隐私锁失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ } else {
+ // 显示隐私锁类型选择对话框
+ showPrivacyLockTypeSelectionDialog(noteId);
+ return; // 不需要显示确认对话框
+ }
+
+ builder.setNegativeButton("取消", null);
+ builder.show();
+ }
+
+ /**
+ * 显示隐私锁类型选择对话框
+ * @param noteId 便签ID
+ */
+ private void showPrivacyLockTypeSelectionDialog(long noteId) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("添加隐私锁");
+
+ String[] options;
+
+ // 检查Android版本是否支持手势锁
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ // Android 9.0 (API 28)及以上版本,支持手势锁
+ options = new String[]{"密码锁", "手势锁"};
+ } else {
+ // 版本低于Android 9.0 (API 28),不支持手势锁,显示提示
+ Toast.makeText(this, "当前设备系统版本过低,不支持手势锁功能", Toast.LENGTH_SHORT).show();
+ options = new String[]{"密码锁"};
+ }
+
+ builder.setItems(options, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == 0) {
+ // 密码锁
+ showPasswordInputDialog(noteId);
+ } else if (which == 1 && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ // 手势锁
+ showGestureLockDialog(noteId);
+ }
+ }
+ });
+
+ builder.setNegativeButton("取消", null);
+ builder.show();
+ }
+
+ /**
+ * 显示密码输入对话框
+ * @param noteId 便签ID
+ */
+ private void showPasswordInputDialog(long noteId) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null);
+ builder.setView(dialogView);
+
+ TextView tvTitle = dialogView.findViewById(R.id.tv_password_title);
+ EditText etPassword = dialogView.findViewById(R.id.et_password);
+ EditText etConfirmPassword = dialogView.findViewById(R.id.et_confirm_password);
+ CheckBox cbShowPassword = dialogView.findViewById(R.id.cb_show_password);
+ TextView tvHint = dialogView.findViewById(R.id.tv_password_hint);
+ TextView tvErrorMessage = dialogView.findViewById(R.id.tv_error_message);
+ Button btnCancel = dialogView.findViewById(R.id.btn_cancel);
+ Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
+
+ tvTitle.setText("设置密码锁");
+
+ AlertDialog dialog = builder.create();
+
+ // 显示/隐藏密码
+ cbShowPassword.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ int inputType = isChecked ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ : InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
+ etPassword.setInputType(inputType);
+ etConfirmPassword.setInputType(inputType);
+ etPassword.setSelection(etPassword.getText().length());
+ etConfirmPassword.setSelection(etConfirmPassword.getText().length());
+ });
+
+ // 确认按钮点击
+ btnConfirm.setOnClickListener(v -> {
+ String password = etPassword.getText().toString().trim();
+ String confirmPassword = etConfirmPassword.getText().toString().trim();
+
+ // 验证密码
+ if (password.length() < 8) {
+ tvErrorMessage.setText("输入密码不得少于8位字母、数字");
+ tvErrorMessage.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (!password.matches("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]{8,}$")) {
+ tvErrorMessage.setText("密码必须包含至少8位字母和数字");
+ tvErrorMessage.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (!password.equals(confirmPassword)) {
+ tvErrorMessage.setText("两次输入密码不一致");
+ tvErrorMessage.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ // 加密密码并保存
+ String encryptedPassword = PrivacyLockManager.encryptPassword(password);
+ if (mPrivacyLockManager.addPrivacyLock(noteId, PrivacyLockManager.LOCK_TYPE_PASSWORD, encryptedPassword)) {
+ Toast.makeText(NotesListActivity.this, "成功添加隐私锁", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ // 刷新列表
+ mNotesListAdapter.changeCursor(null);
+ startAsyncNotesListQuery();
+ } else {
+ Toast.makeText(NotesListActivity.this, "添加隐私锁失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ // 取消按钮点击
+ btnCancel.setOnClickListener(v -> dialog.dismiss());
+
+ dialog.show();
+ }
+
+ /**
+ * 显示手势锁对话框
+ * @param noteId 便签ID
+ */
+ private void showGestureLockDialog(long noteId) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_gesture_lock, null);
+ builder.setView(dialogView);
+
+ TextView tvTitle = dialogView.findViewById(R.id.tv_gesture_title);
+ TextView tvInstruction = dialogView.findViewById(R.id.tv_gesture_instruction);
+ GestureLockView gestureLockView = dialogView.findViewById(R.id.gesture_lock_view);
+ TextView tvStatus = dialogView.findViewById(R.id.tv_gesture_status);
+ Button btnReset = dialogView.findViewById(R.id.btn_gesture_reset);
+
+ tvTitle.setText("设置手势锁");
+ tvInstruction.setText("请绘制连续手势(至少4个点)");
+ tvStatus.setText("");
+
+ AlertDialog dialog = builder.create();
+
+ // 记录当前绘制状态
+ final List[] firstGesture = new List[]{null};
+ final int[] attemptCount = {0};
+ final int MAX_ATTEMPTS = 3;
+
+ // 设置手势完成监听器
+ gestureLockView.setOnGestureCompleteListener(selectedPoints -> {
+ if (firstGesture[0] == null) {
+ // 首次绘制
+ if (selectedPoints.size() < 4) {
+ tvStatus.setText("手势需包含至少4个连续点,请重新绘制");
+ gestureLockView.clearGesture();
+ return;
+ }
+
+ firstGesture[0] = new ArrayList<>(selectedPoints);
+ tvInstruction.setText("再次绘制手势确认");
+ tvStatus.setText("");
+ gestureLockView.clearGesture();
+ } else {
+ // 二次绘制验证
+ if (selectedPoints.equals(firstGesture[0])) {
+ // 手势一致,设置成功
+ String gestureString = PrivacyLockManager.gestureToString(selectedPoints);
+ if (mPrivacyLockManager.addPrivacyLock(noteId, PrivacyLockManager.LOCK_TYPE_GESTURE, gestureString)) {
+ Toast.makeText(NotesListActivity.this, "成功添加隐私锁", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ // 刷新列表
+ mNotesListAdapter.changeCursor(null);
+ startAsyncNotesListQuery();
+ } else {
+ Toast.makeText(NotesListActivity.this, "添加隐私锁失败", Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ // 手势不一致
+ attemptCount[0]++;
+ if (attemptCount[0] >= MAX_ATTEMPTS) {
+ Toast.makeText(NotesListActivity.this, "3次绘制手势不一致,已取消设置", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ return;
+ }
+
+ tvStatus.setText("两次绘制手势不一致,请重试 (" + attemptCount[0] + "/" + MAX_ATTEMPTS + ")");
+ firstGesture[0] = null;
+ tvInstruction.setText("请绘制连续手势(至少4个点)");
+ gestureLockView.clearGesture();
+ }
+ }
+ });
+
+ // 重置按钮点击
+ btnReset.setOnClickListener(v -> {
+ gestureLockView.clearGesture();
+ if (firstGesture[0] != null) {
+ firstGesture[0] = null;
+ tvInstruction.setText("请绘制连续手势(至少4个点)");
+ }
+ tvStatus.setText("");
+ });
+
+ dialog.setOnCancelListener(d -> {
+ // 取消时不做任何操作
+ });
+
+ dialog.show();
+ }
}
\ No newline at end of file
diff --git a/src/notes/ui/NotesListItem.java b/src/notes/ui/NotesListItem.java
index 52a530e..20037c9 100644
--- a/src/notes/ui/NotesListItem.java
+++ b/src/notes/ui/NotesListItem.java
@@ -27,6 +27,7 @@ import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.PrivacyLockManager;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
@@ -35,6 +36,7 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
*/
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 提醒图标(时钟或通话记录图标)
+ private ImageView mLock; // 锁图标
private TextView mTitle; // 标题/内容文本
private TextView mTime; // 修改时间文本
private TextView mCallName; // 通话记录联系人姓名
@@ -51,6 +53,7 @@ public class NotesListItem extends LinearLayout {
inflate(context, R.layout.note_item, this);
// 初始化各个子视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
+ mLock = (ImageView) findViewById(R.id.iv_lock_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
@@ -129,12 +132,15 @@ public class NotesListItem extends LinearLayout {
// 设置相对时间显示(如"2分钟前"、"昨天"等)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
+ // 显示锁图标(如果便签被锁定)
+ showLockIcon(context, data);
+
// 根据位置和类型设置背景
setBackground(data);
}
/**
- * 根据数据项的位置和类型设置不同的背景
+ * 根据位置和类型设置不同的背景
* 实现列表项的分组视觉效果(第一个、最后一个、中间项等)
* @param data 笔记数据项
*/
@@ -161,6 +167,20 @@ public class NotesListItem extends LinearLayout {
}
}
+ /**
+ * 显示锁图标(如果便签被锁定)
+ * @param context 上下文环境
+ * @param data 笔记数据项
+ */
+ private void showLockIcon(Context context, NoteItemData data) {
+ PrivacyLockManager privacyLockManager = new PrivacyLockManager(context);
+ if (privacyLockManager.isNoteLocked(data.getId())) {
+ mLock.setVisibility(View.VISIBLE);
+ } else {
+ mLock.setVisibility(View.GONE);
+ }
+ }
+
/**
* 获取当前绑定的数据对象
* @return NoteItemData对象