diff --git a/src/Notes-master/res/drawable/action_bg_color.xml b/src/Notes-master/res/drawable/action_bg_color.xml
new file mode 100644
index 0000000..2a1d558
--- /dev/null
+++ b/src/Notes-master/res/drawable/action_bg_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Notes-master/res/drawable/action_bold.xml b/src/Notes-master/res/drawable/action_bold.xml
new file mode 100644
index 0000000..6f6af0e
--- /dev/null
+++ b/src/Notes-master/res/drawable/action_bold.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Notes-master/res/drawable/action_italic.xml b/src/Notes-master/res/drawable/action_italic.xml
new file mode 100644
index 0000000..3efecea
--- /dev/null
+++ b/src/Notes-master/res/drawable/action_italic.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Notes-master/res/drawable/action_undo.xml b/src/Notes-master/res/drawable/action_undo.xml
new file mode 100644
index 0000000..a19c057
--- /dev/null
+++ b/src/Notes-master/res/drawable/action_undo.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/Notes-master/res/layout/note_edit.xml b/src/Notes-master/res/layout/note_edit.xml
index abec1dc..503fcb1 100644
--- a/src/Notes-master/res/layout/note_edit.xml
+++ b/src/Notes-master/res/layout/note_edit.xml
@@ -67,11 +67,21 @@
android:layout_marginRight="8dip" />
+
+
+
-
-
-
-
-
-
-
-
-
+ android:textAppearance="@style/TextAppearancePrimaryItem"
+ android:lineSpacingMultiplier="1.2" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Notes-master/res/values-zh-rCN/strings.xml b/src/Notes-master/res/values-zh-rCN/strings.xml
index 45a06cd..96599ab 100644
--- a/src/Notes-master/res/values-zh-rCN/strings.xml
+++ b/src/Notes-master/res/values-zh-rCN/strings.xml
@@ -84,8 +84,6 @@
要查看的便签不存在
不能为空便签设置闹钟提醒
不能将空便签发送到桌面
- 清单模式下不支持格式化
- 请先选择文本
导出成功
导出失败
已将文本文件(%1$s)输出至SD卡(%2$s)目录
@@ -139,14 +137,6 @@
请输入密码
密码错误,请重试
密码不能为空
-
- 加粗
- 斜体
- 下划线
- 符号列表
- 编号列表
- 文字颜色
- 背景颜色
- %1$s 条符合“%2$s”的搜索结果
diff --git a/src/Notes-master/res/values/strings.xml b/src/Notes-master/res/values/strings.xml
index 9889716..170e4f0 100644
--- a/src/Notes-master/res/values/strings.xml
+++ b/src/Notes-master/res/values/strings.xml
@@ -88,8 +88,6 @@
The note is not exist
Sorry, can not set clock on empty note
Sorry, can not send and empty note to home
- Formatting is not supported in list mode
- Please select text first
Export successful
Export fail
Export text file (%1$s) to SD (%2$s) directory
@@ -146,14 +144,6 @@
Enter password please
Error, try again
The password can not be empty
-
- Bold
- Italic
- Underline
- Bullet list
- Numbered list
- Text color
- Background color
- %1$s result for \"%2$s\"
diff --git a/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java
index e87dbbc..c81fa27 100644
--- a/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java
+++ b/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java
@@ -30,7 +30,7 @@ import net.micode.notes.data.Notes.NoteColumns;
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;
public interface TABLE {
public static final String NOTE = "note";
@@ -39,6 +39,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
public static final String TRASH = "trash_note";
+ public static final String TRASH_DATA = "trash_data";
+
public static final String ENCRYPTED_NOTE_PASSWORD = "encrypted_note_password";
}
@@ -110,6 +112,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
"deleted_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)" +
")";
+ private static final String CREATE_TRASH_DATA_TABLE_SQL =
+ "CREATE TABLE " + TABLE.TRASH_DATA + "(" +
+ DataColumns.ID + " INTEGER PRIMARY KEY," +
+ DataColumns.MIME_TYPE + " TEXT NOT NULL," +
+ DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
+ NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
+ NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
+ DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.DATA1 + " INTEGER," +
+ DataColumns.DATA2 + " INTEGER," +
+ DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
+ ")";
+
private static final String CREATE_ENCRYPTED_NOTE_PASSWORD_TABLE_SQL =
"CREATE TABLE " + TABLE.ENCRYPTED_NOTE_PASSWORD + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
@@ -317,6 +334,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
Log.d(TAG, "trash_note table has been created");
}
+ public void createTrashDataTable(SQLiteDatabase db) {
+ db.execSQL(CREATE_TRASH_DATA_TABLE_SQL);
+ Log.d(TAG, "trash_data table has been created");
+ }
+
private void reCreateDataTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
@@ -339,6 +361,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createNoteTable(db);
createDataTable(db);
createTrashTable(db);
+ createTrashDataTable(db);
createEncryptedNotePasswordTable(db);
}
@@ -374,6 +397,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
+ if (oldVersion == 6) {
+ upgradeToV7(db);
+ oldVersion++;
+ }
+
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
@@ -434,6 +462,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
Log.d(TAG, "Upgraded to version 6: added is_encrypted column and encrypted_note_password table");
}
+ private void upgradeToV7(SQLiteDatabase db) {
+ // 创建trash_data表用于存储被删除便签的DATA数据
+ createTrashDataTable(db);
+ Log.d(TAG, "Upgraded to version 7: created trash_data table");
+ }
+
public void createEncryptedNotePasswordTable(SQLiteDatabase db) {
db.execSQL(CREATE_ENCRYPTED_NOTE_PASSWORD_TABLE_SQL);
Log.d(TAG, "encrypted_note_password table has been created");
diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java
index 3dd7cfb..c1f12ea 100644
--- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java
+++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java
@@ -379,7 +379,7 @@ public class GTaskManager {
// one
// clear local delete table
if (!mCancelled) {
- if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
+ if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap, mContext)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
diff --git a/src/Notes-master/src/net/micode/notes/tool/DataUtils.java b/src/Notes-master/src/net/micode/notes/tool/DataUtils.java
index 8754e57..717c14a 100644
--- a/src/Notes-master/src/net/micode/notes/tool/DataUtils.java
+++ b/src/Notes-master/src/net/micode/notes/tool/DataUtils.java
@@ -21,15 +21,20 @@ import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
+import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TrashColumns;
+import net.micode.notes.data.NotesDatabaseHelper;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
@@ -42,13 +47,31 @@ public class DataUtils {
* Move notes to trash table instead of directly deleting them
*/
public static boolean batchMoveToTrash(ContentResolver resolver, HashSet ids) {
+ // 为了保持向后兼容,如果没有Context,使用旧逻辑(不备份DATA数据)
+ return batchMoveToTrash(resolver, ids, null);
+ }
+
+ /**
+ * Move notes to trash table instead of directly deleting them
+ * @param resolver ContentResolver
+ * @param ids Note IDs to move to trash
+ * @param context Context (optional, needed for backing up DATA table)
+ */
+ public static boolean batchMoveToTrash(ContentResolver resolver, HashSet ids, Context context) {
if (ids == null || ids.size() == 0) {
Log.d(TAG, "the ids is null or empty");
return true;
}
Cursor cursor = null;
+ Cursor dataCursor = null;
+ SQLiteDatabase db = null;
try {
+ // 如果提供了Context,获取数据库实例用于备份DATA数据
+ if (context != null) {
+ db = NotesDatabaseHelper.getInstance(context).getWritableDatabase();
+ }
+
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER || id <= 0) {
Log.e(TAG, "Don't move system folder root or invalid id: " + id);
@@ -93,7 +116,51 @@ public class DataUtils {
// Insert into trash table
resolver.insert(Notes.CONTENT_TRASH_URI, trashValues);
- // Delete from note table
+ // 在删除note之前,先备份DATA表的数据
+ if (db != null) {
+ // 查询该note的所有DATA数据
+ dataCursor = db.query(NotesDatabaseHelper.TABLE.DATA, null,
+ DataColumns.NOTE_ID + "=?",
+ new String[]{String.valueOf(id)},
+ null, null, null);
+
+ if (dataCursor != null && dataCursor.moveToFirst()) {
+ do {
+ // 复制DATA数据到trash_data表
+ ContentValues trashDataValues = new ContentValues();
+ for (String column : dataCursor.getColumnNames()) {
+ int columnIndex = dataCursor.getColumnIndex(column);
+ if (columnIndex >= 0) {
+ int type = dataCursor.getType(columnIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_NULL:
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ trashDataValues.put(column, dataCursor.getLong(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ trashDataValues.put(column, dataCursor.getDouble(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ trashDataValues.put(column, dataCursor.getString(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ trashDataValues.put(column, dataCursor.getBlob(columnIndex));
+ break;
+ }
+ }
+ }
+ // 插入到trash_data表
+ db.insert(NotesDatabaseHelper.TABLE.TRASH_DATA, null, trashDataValues);
+ } while (dataCursor.moveToNext());
+ }
+ if (dataCursor != null) {
+ dataCursor.close();
+ dataCursor = null;
+ }
+ }
+
+ // Delete from note table (this will trigger deletion of DATA table via trigger)
resolver.delete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), null, null);
}
@@ -110,12 +177,20 @@ public class DataUtils {
if (cursor != null) {
cursor.close();
}
+ if (dataCursor != null) {
+ dataCursor.close();
+ }
}
}
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) {
// Instead of directly deleting, move to trash
- return batchMoveToTrash(resolver, ids);
+ return batchMoveToTrash(resolver, ids, null);
+ }
+
+ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids, Context context) {
+ // Instead of directly deleting, move to trash
+ return batchMoveToTrash(resolver, ids, context);
}
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
@@ -293,8 +368,26 @@ public class DataUtils {
* Restore note from trash table back to note table
*/
public static boolean restoreNoteFromTrash(ContentResolver resolver, long trashId) {
+ // 为了保持向后兼容,如果没有Context,使用旧逻辑(不恢复DATA数据)
+ return restoreNoteFromTrash(resolver, trashId, null);
+ }
+
+ /**
+ * Restore note from trash table back to note table
+ * @param resolver ContentResolver
+ * @param trashId Trash note ID to restore
+ * @param context Context (optional, needed for restoring DATA table)
+ */
+ public static boolean restoreNoteFromTrash(ContentResolver resolver, long trashId, Context context) {
Cursor cursor = null;
+ Cursor dataCursor = null;
+ SQLiteDatabase db = null;
try {
+ // 如果提供了Context,获取数据库实例用于恢复DATA数据
+ if (context != null) {
+ db = NotesDatabaseHelper.getInstance(context).getWritableDatabase();
+ }
+
// Query the note from trash table
cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_TRASH_URI, trashId),
null, null, null, null);
@@ -302,6 +395,7 @@ public class DataUtils {
if (cursor != null && cursor.moveToFirst()) {
// Read all columns from the trash note
ContentValues noteValues = new ContentValues();
+ long originalNoteId = -1;
// Copy all columns from trash to note (except deleted_date)
for (String column : cursor.getColumnNames()) {
@@ -315,7 +409,12 @@ public class DataUtils {
case Cursor.FIELD_TYPE_NULL:
break;
case Cursor.FIELD_TYPE_INTEGER:
- noteValues.put(column, cursor.getLong(columnIndex));
+ long value = cursor.getLong(columnIndex);
+ noteValues.put(column, value);
+ // 保存原始note ID
+ if (NoteColumns.ID.equals(column)) {
+ originalNoteId = value;
+ }
break;
case Cursor.FIELD_TYPE_FLOAT:
noteValues.put(column, cursor.getDouble(columnIndex));
@@ -331,7 +430,73 @@ public class DataUtils {
}
// Insert back to note table
- resolver.insert(Notes.CONTENT_NOTE_URI, noteValues);
+ Uri restoredUri = resolver.insert(Notes.CONTENT_NOTE_URI, noteValues);
+ if (restoredUri == null) {
+ Log.e(TAG, "Failed to restore note to note table");
+ return false;
+ }
+
+ // 获取恢复后的note ID(可能是新的ID)
+ long restoredNoteId = ContentUris.parseId(restoredUri);
+ if (restoredNoteId <= 0) {
+ Log.e(TAG, "Invalid restored note ID: " + restoredNoteId);
+ return false;
+ }
+
+ // 恢复DATA表的数据
+ if (db != null && originalNoteId > 0) {
+ // 查询trash_data表中该note的所有数据
+ dataCursor = db.query(NotesDatabaseHelper.TABLE.TRASH_DATA, null,
+ DataColumns.NOTE_ID + "=?",
+ new String[]{String.valueOf(originalNoteId)},
+ null, null, null);
+
+ if (dataCursor != null && dataCursor.moveToFirst()) {
+ do {
+ // 复制DATA数据回data表,但使用新的note ID
+ ContentValues dataValues = new ContentValues();
+ for (String column : dataCursor.getColumnNames()) {
+ int columnIndex = dataCursor.getColumnIndex(column);
+ if (columnIndex >= 0) {
+ int type = dataCursor.getType(columnIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_NULL:
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ long value = dataCursor.getLong(columnIndex);
+ // 如果是NOTE_ID列,使用恢复后的note ID
+ if (DataColumns.NOTE_ID.equals(column)) {
+ dataValues.put(column, restoredNoteId);
+ } else {
+ dataValues.put(column, value);
+ }
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ dataValues.put(column, dataCursor.getDouble(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ dataValues.put(column, dataCursor.getString(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ dataValues.put(column, dataCursor.getBlob(columnIndex));
+ break;
+ }
+ }
+ }
+ // 插入到data表
+ db.insert(NotesDatabaseHelper.TABLE.DATA, null, dataValues);
+ } while (dataCursor.moveToNext());
+
+ // 删除trash_data表中的数据
+ db.delete(NotesDatabaseHelper.TABLE.TRASH_DATA,
+ DataColumns.NOTE_ID + "=?",
+ new String[]{String.valueOf(originalNoteId)});
+ }
+ if (dataCursor != null) {
+ dataCursor.close();
+ dataCursor = null;
+ }
+ }
// Delete from trash table
resolver.delete(ContentUris.withAppendedId(Notes.CONTENT_TRASH_URI, trashId), null, null);
@@ -346,6 +511,9 @@ public class DataUtils {
if (cursor != null) {
cursor.close();
}
+ if (dataCursor != null) {
+ dataCursor.close();
+ }
}
}
@@ -353,7 +521,25 @@ public class DataUtils {
* Permanently delete note from trash table
*/
public static boolean permanentlyDeleteFromTrash(ContentResolver resolver, long trashId) {
+ return permanentlyDeleteFromTrash(resolver, trashId, null);
+ }
+
+ /**
+ * Permanently delete note from trash table
+ * @param resolver ContentResolver
+ * @param trashId Trash note ID to permanently delete
+ * @param context Context (optional, needed for deleting trash_data table)
+ */
+ public static boolean permanentlyDeleteFromTrash(ContentResolver resolver, long trashId, Context context) {
try {
+ // 如果提供了Context,先删除trash_data表中的数据
+ if (context != null) {
+ SQLiteDatabase db = NotesDatabaseHelper.getInstance(context).getWritableDatabase();
+ db.delete(NotesDatabaseHelper.TABLE.TRASH_DATA,
+ DataColumns.NOTE_ID + "=?",
+ new String[]{String.valueOf(trashId)});
+ }
+
int count = resolver.delete(ContentUris.withAppendedId(Notes.CONTENT_TRASH_URI, trashId), null, null);
return count > 0;
} catch (Exception e) {
@@ -366,7 +552,22 @@ public class DataUtils {
* Clear all notes from trash table
*/
public static boolean clearAllTrash(ContentResolver resolver) {
+ return clearAllTrash(resolver, null);
+ }
+
+ /**
+ * Clear all notes from trash table
+ * @param resolver ContentResolver
+ * @param context Context (optional, needed for clearing trash_data table)
+ */
+ public static boolean clearAllTrash(ContentResolver resolver, Context context) {
try {
+ // 如果提供了Context,先清空trash_data表
+ if (context != null) {
+ SQLiteDatabase db = NotesDatabaseHelper.getInstance(context).getWritableDatabase();
+ db.delete(NotesDatabaseHelper.TABLE.TRASH_DATA, null, null);
+ }
+
int count = resolver.delete(Notes.CONTENT_TRASH_URI, null, null);
Log.d(TAG, "Cleared " + count + " notes from trash");
// Return true even if count is 0, as it might mean the trash was already empty
diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java
index 944d043..7cfd52d 100644
--- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java
@@ -33,12 +33,8 @@ import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
-import android.text.Html;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
-import android.graphics.Typeface;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -68,7 +64,16 @@ import android.content.ContentResolver;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Color;
import java.io.InputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import android.os.Environment;
+import android.text.format.DateUtils;
+import android.view.ViewGroup;
+import android.content.ActivityNotFoundException;
+
+import jp.wasabeef.richeditor.RichEditor;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
@@ -161,22 +166,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private static final String TAG = "NoteEditActivity"; // 日志标签
private static final int REQUEST_CODE_PICK_IMAGE = 100; // 图片选择请求码 OMO
+ private static final int PHOTO_REQUEST = 100; // 请求照片
private HeadViewHolder mNoteHeaderHolder; // 头部视图持有者
private View mHeadViewPanel; // 头部视图面板
private View mNoteBgColorSelector; // 背景颜色选择器
private View mFontSizeSelector; // 字体大小选择器
- private EditText mNoteEditor; // 笔记编辑器
+ private RichEditor mNoteEditor; // 富文本编辑器
private View mNoteEditorPanel; // 笔记编辑器面板
private WorkingNote mWorkingNote; // 工作笔记对象
private SharedPreferences mSharedPrefs; // 共享偏好设置
private int mFontSizeId; // 字体大小ID
private ImageButton mBtnInsertImage; // 插入图片按钮 OMO
-
- // 富文本工具栏按钮
- private ImageButton mBtnBold; // 加粗按钮
- private ImageButton mBtnItalic; // 斜体按钮
- private ImageButton mBtnUnderline; // 下划线按钮
+ private String mText; // 用于存储富文本内容
+ private int mNoteLength; // 文本长度
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷图标标题最大长度
@@ -348,96 +351,64 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* 设置笔记编辑器的文本外观、内容和选择模式
*/
private void initNoteScreen() {
- // 设置笔记编辑器的文本外观
- mNoteEditor.setTextAppearance(this, TextAppearanceResources
- .getTexAppearanceResource(mFontSizeId));
+ // 检查必要的视图是否已初始化
+ if (mHeadViewPanel == null || mNoteEditorPanel == null || mNoteEditor == null) {
+ Log.e(TAG, "Some views are not initialized! Check initResources method.");
+ return;
+ }
+
+ // 设置富文本编辑器字体大小
+ setRichEditorFontSize(mFontSizeId);
// 根据笔记模式设置显示方式
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent()); // 切换到清单模式
+ mNoteEditor.setVisibility(View.GONE);
+ mEditTextList.setVisibility(View.VISIBLE);
} else {
- // 设置笔记内容并高亮查询结果
- String content = mWorkingNote.getContent();
- Log.d(TAG, "Initializing note content: " + content);
+ // 切换到富文本模式
+ mEditTextList.setVisibility(View.GONE);
+ mNoteEditor.setVisibility(View.VISIBLE);
+
+ // 获取笔记原始内容(为空则赋空字符串,避免空指针)
+ String content = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent();
- if (TextUtils.isEmpty(content)) {
- mNoteEditor.setText("");
- mNoteEditor.setSelection(0);
- } else {
- Spannable spannable;
-
- // 检查是否是HTML格式(包含HTML标签)
- if (content.contains("<") && content.contains(">")) {
- try {
- // 从HTML格式恢复SpannableString,保留样式(加粗、斜体、下划线)
- // Html.fromHtml会自动将HTML标签转换为对应的Span
- spannable = (Spannable) Html.fromHtml(content);
- } catch (Exception e) {
- // 如果HTML解析失败,使用普通文本
- Log.e(TAG, "Error parsing HTML content: " + e.getMessage());
- spannable = new SpannableString(content);
- }
- } else {
- // 普通文本格式,直接创建SpannableString
- spannable = new SpannableString(content);
- }
-
- // 处理图片:将包含[IMAGE]标签的文本转换为包含ImageSpan的SpannableString
- // 注意:convertTextToSpannableWithImages会处理[IMAGE]标记,但会保留文本内容
- String textContent = spannable.toString();
- SpannableString finalSpannable = convertTextToSpannableWithImages(textContent);
-
- // 将样式从原始Spannable复制到处理后的SpannableString
- // 由于convertTextToSpannableWithImages可能改变了文本长度(用空格替换了[IMAGE]标签),
- // 我们需要基于文本内容来映射样式位置
- if (textContent.length() == finalSpannable.length()) {
- // 文本长度相同,直接复制样式
- StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
- UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
-
- for (StyleSpan span : styleSpans) {
- int start = spannable.getSpanStart(span);
- int end = spannable.getSpanEnd(span);
- if (start >= 0 && end <= finalSpannable.length() && start < end) {
- finalSpannable.setSpan(new StyleSpan(span.getStyle()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
-
- for (UnderlineSpan span : underlineSpans) {
- int start = spannable.getSpanStart(span);
- int end = spannable.getSpanEnd(span);
- if (start >= 0 && end <= finalSpannable.length() && start < end) {
- finalSpannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- } else {
- // 文本长度不同,说明有图片被处理了
- // 这种情况下,样式位置需要重新计算
- // 为了简化,我们基于原始文本位置映射样式(可能会有偏差,但应该能处理大部分情况)
- StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
- UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
-
- for (StyleSpan span : styleSpans) {
- int start = Math.max(0, Math.min(spannable.getSpanStart(span), finalSpannable.length()));
- int end = Math.max(start, Math.min(spannable.getSpanEnd(span), finalSpannable.length()));
- if (start < end) {
- finalSpannable.setSpan(new StyleSpan(span.getStyle()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
-
- for (UnderlineSpan span : underlineSpans) {
- int start = Math.max(0, Math.min(spannable.getSpanStart(span), finalSpannable.length()));
- int end = Math.max(start, Math.min(spannable.getSpanEnd(span), finalSpannable.length()));
- if (start < end) {
- finalSpannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ // 检查内容是否已经是HTML格式,如果是则直接加载
+ String finalHtml = content;
+
+ // 如果内容不是HTML格式(比如旧笔记),则处理图片标记
+ if (!TextUtils.isEmpty(content) && (!content.startsWith("<") || !content.contains(""))) {
+ // 处理 [IMAGE] 标签,转换为 HTML img 标签
+ Pattern imgPattern = Pattern.compile("\\[IMAGE\\]([^\\[]+)\\[/IMAGE\\]");
+ Matcher imgMatcher = imgPattern.matcher(content);
+ StringBuffer htmlContent = new StringBuffer();
+ // 遍历替换所有[IMAGE]标记为
标签
+ while (imgMatcher.find()) {
+ String imgUri = imgMatcher.group(1); // 提取图片URI
+ // 检查是否为本地文件路径
+ if (imgUri.startsWith("file://") || imgUri.startsWith("/")) {
+ String imgPath = imgUri.startsWith("file://") ? imgUri.substring(7) : imgUri;
+ File imgFile = new File(imgPath);
+ if (imgFile.exists() && imgFile.isFile()) {
+ String imgHtmlUrl = "file://" + imgPath;
+ String imgHtmlTag = "
";
+ imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
+ } else {
+ // 如果文件不存在,保留原标记
+ imgMatcher.appendReplacement(htmlContent, imgMatcher.group(0));
}
+ } else {
+ // URI格式,直接使用
+ String imgHtmlTag = "
";
+ imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
}
}
-
- mNoteEditor.setText(finalSpannable);
- // 将光标定位到文本末尾
- mNoteEditor.setSelection(finalSpannable.length());
+ imgMatcher.appendTail(htmlContent); // 拼接剩余文本
+ finalHtml = htmlContent.toString();
}
+
+ // 用RichEditor加载HTML内容
+ mNoteEditor.setHtml(finalHtml);
}
// 隐藏所有背景选择的选中状态
@@ -641,7 +612,36 @@ public class NoteEditActivity extends Activity implements OnClickListener,
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);
+ // 初始化富文本编辑器
+ mNoteEditor = (RichEditor) findViewById(R.id.note_edit_view);
+ if (mNoteEditor == null) {
+ Log.e(TAG, "RichEditor is null! Check layout file.");
+ return;
+ }
+
+ // 初始化富文本编辑器配置
+ initRichEditor();
+
+ // 设置富文本编辑器监听器
+ mNoteEditor.setOnTextChangeListener(new RichEditor.OnTextChangeListener() {
+ @Override
+ public void onTextChange(String text) {
+ mText = text;
+ mNoteLength = text.length();
+ // 更新修改时间和字符数显示
+ mNoteHeaderHolder.tvModified.setText(
+ DateUtils.formatDateTime(NoteEditActivity.this,
+ mWorkingNote.getModifiedDate(),
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE
+ | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)
+ + "\n字符数:" + mNoteLength
+ );
+ }
+ });
+
+ // 开启图文混排支持
+ mNoteEditor.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
@@ -680,21 +680,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mBtnInsertImage.setOnClickListener(this);
}
- // 初始化富文本工具栏按钮
- mBtnBold = (ImageButton) findViewById(R.id.btn_bold);
- mBtnItalic = (ImageButton) findViewById(R.id.btn_italic);
- mBtnUnderline = (ImageButton) findViewById(R.id.btn_underline);
-
- // 为富文本工具栏按钮设置点击监听器
- if (mBtnBold != null) {
- mBtnBold.setOnClickListener(this);
- }
- if (mBtnItalic != null) {
- mBtnItalic.setOnClickListener(this);
- }
- if (mBtnUnderline != null) {
- mBtnUnderline.setOnClickListener(this);
- }
+ // 初始化富文本功能按钮
+ initRichEditorButtons();
}
/**
@@ -765,22 +752,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
- mNoteEditor.setTextAppearance(this,
- TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
+ // 设置富文本编辑器字体大小
+ setRichEditorFontSize(mFontSizeId);
}
mFontSizeSelector.setVisibility(View.GONE);
} else if (id == R.id.btn_insert_image) {
// 处理插入图片按钮点击事件 OMO
pickImageFromGallery();
- } else if (id == R.id.btn_bold) {
- // 富文本工具栏 - 加粗按钮
- applyBoldStyle();
- } else if (id == R.id.btn_italic) {
- // 富文本工具栏 - 斜体按钮
- applyItalicStyle();
- } else if (id == R.id.btn_underline) {
- // 富文本工具栏 - 下划线按钮
- applyUnderlineStyle();
}
}
@@ -969,7 +947,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
if (!isSyncMode()) {
// 非同步模式下直接删除
- if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
+ if (!DataUtils.batchDeleteNotes(getContentResolver(), ids, NoteEditActivity.this)) {
Log.e(TAG, "Delete Note error");
}
} else {
@@ -1007,7 +985,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
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, 0);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
@@ -1221,8 +1199,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
String text = "";
if (newMode == TextNote.MODE_CHECK_LIST) {
// 安全获取文本,避免空指针异常(OMO)
- if (mNoteEditor.getText() != null) {
- text = mNoteEditor.getText().toString();
+ String htmlContent = mNoteEditor.getHtml();
+ if (htmlContent != null) {
+ text = htmlContent;
}
switchToListMode(text);
} else {
@@ -1232,12 +1211,35 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mWorkingNote.setWorkingText(content.replace(TAG_UNCHECKED + " ", ""));
}
}
- String content = mWorkingNote.getContent();
- if (content != null) {
- mNoteEditor.setText(getHighlightQueryResult(content, mUserQuery));
- } else {
- mNoteEditor.setText("");
+ String content = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent();
+ // 处理内容为HTML格式
+ String finalHtml = content;
+ if (!TextUtils.isEmpty(content) && (!content.startsWith("<") || !content.contains(""))) {
+ // 处理 [IMAGE] 标签
+ Pattern imgPattern = Pattern.compile("\\[IMAGE\\]([^\\[]+)\\[/IMAGE\\]");
+ Matcher imgMatcher = imgPattern.matcher(content);
+ StringBuffer htmlContent = new StringBuffer();
+ while (imgMatcher.find()) {
+ String imgUri = imgMatcher.group(1);
+ if (imgUri.startsWith("file://") || imgUri.startsWith("/")) {
+ String imgPath = imgUri.startsWith("file://") ? imgUri.substring(7) : imgUri;
+ File imgFile = new File(imgPath);
+ if (imgFile.exists() && imgFile.isFile()) {
+ String imgHtmlUrl = "file://" + imgPath;
+ String imgHtmlTag = "
";
+ imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
+ } else {
+ imgMatcher.appendReplacement(htmlContent, imgMatcher.group(0));
+ }
+ } else {
+ String imgHtmlTag = "
";
+ imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
+ }
+ }
+ imgMatcher.appendTail(htmlContent);
+ finalHtml = htmlContent.toString();
}
+ mNoteEditor.setHtml(finalHtml);
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
@@ -1266,16 +1268,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
mWorkingNote.setWorkingText(sb.toString());
} else {
- // 保存时,将SpannableString转换为HTML格式以保留样式信息
- Spannable spannable = mNoteEditor.getText();
- if (spannable != null && spannable.length() > 0) {
- // 将Spannable转换为HTML格式,保留加粗、斜体、下划线等样式
- // Html.toHtml会自动处理StyleSpan和UnderlineSpan
- String htmlContent = Html.toHtml(spannable);
- mWorkingNote.setWorkingText(htmlContent);
- } else {
- mWorkingNote.setWorkingText("");
- }
+ // 确保获取最新的富文本内容
+ String currentHtml = mNoteEditor.getHtml();
+ mWorkingNote.setWorkingText(currentHtml);
+ mText = currentHtml; // 更新mText变量,确保保存时使用最新内容
}
return hasChecked;
}
@@ -1373,8 +1369,22 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* 从相册选择图片 OMO
*/
private void pickImageFromGallery() {
- Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
- startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
+ try {
+ // 意图:打开系统相册选择图片
+ Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ intent.setType("image/*"); // 只显示图片类型
+ startActivityForResult(intent, PHOTO_REQUEST); // 启动相册,等待返回结果
+ } catch (ActivityNotFoundException e) {
+ // 如果没有相册应用,尝试使用通用选择器
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ try {
+ startActivityForResult(intent, PHOTO_REQUEST);
+ } catch (ActivityNotFoundException ex) {
+ showToast(R.string.error_note_not_exist);
+ Log.e(TAG, "No image picker available", ex);
+ }
+ }
}
/**
@@ -1383,204 +1393,177 @@ public class NoteEditActivity extends Activity implements OnClickListener,
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == RESULT_OK && data != null) {
- Log.d(TAG, "Image selected successfully, data: " + data.toString());
- Uri imageUri = data.getData();
- if (imageUri != null) {
- Log.d(TAG, "Image URI: " + imageUri.toString());
- try {
- // 将图片路径添加到笔记内容中
- String currentContent = mNoteEditor.getText().toString();
- String imagePath = "[IMAGE]" + imageUri.toString() + "[/IMAGE]";
- String newContent = currentContent + (currentContent.isEmpty() ? "" : "\n") + imagePath;
- Log.d(TAG, "New content: " + newContent);
-
- // 直接设置文本,不使用ImageSpan,这样可以确保内容被保存
- mNoteEditor.setText(newContent);
- mNoteEditor.setSelection(newContent.length());
-
- // 保存笔记内容,确保图片路径被正确存储
- saveNote();
- showToast(R.string.info_image_inserted);
- } catch (Exception e) {
- Log.e(TAG, "Error inserting image: " + e.toString());
- showToast(R.string.error_note_not_exist);
+ if (requestCode == PHOTO_REQUEST && resultCode == RESULT_OK && data != null) {
+ Uri uri = data.getData();
+ String localImagePath = saveImageToLocal(uri);
+ if (localImagePath == null) return; // 保存失败就退出
+
+ // 核心修改:适配RichEditor,替换EditText的ImageSpan逻辑
+ // 1. 拼接RichEditor支持的
标签(必须加file://前缀)
+ String imgUrl = "file://" + localImagePath;
+ String imgHtmlTag = "
";
+ // 2. 插入图片到RichEditor
+ String curHtml = mNoteEditor.getHtml(); // 获取当前内容
+ String newHtml = curHtml + imgHtmlTag; // 追加图片标签
+ mNoteEditor.setHtml(newHtml); // 重新设置内容,实现插入
+
+ // 弹窗依然保留
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("图片选择成功!");
+
+ ImageView imageView = new ImageView(this);
+ imageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)); // 加布局参数,避免图片显示不全
+ imageView.setImageURI(Uri.fromFile(new File(localImagePath)));//弹窗也显示本地图片
+ imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); // 适配图片大小
+ builder.setView(imageView);
+
+ builder.setPositiveButton("确认保存", (dialog, which) -> {
+ String currentHtml = mNoteEditor.getHtml(); // 替换原EditText的getText()
+ String newContent = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent();
+ newContent += "\n[IMAGE]" + localImagePath + "[/IMAGE]"; // 保留原有[IMAGE]标记,供后续加载解析
+ Log.d("NoteDebug", "准备保存的内容:" + newContent); // 看Logcat里的输出
+
+ // 执行保存操作
+ mWorkingNote.setWorkingText(newContent);
+ boolean isSaved = mWorkingNote.saveNote();
+
+ // 根据保存结果提示(更友好)
+ if (isSaved) {
+ Toast.makeText(this, "图片信息已保存!", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "保存失败,请重试", Toast.LENGTH_SHORT).show();
}
- } else {
- Log.e(TAG, "Image URI is null");
- showToast(R.string.error_note_not_exist);
- }
- } else {
- Log.d(TAG, "Image selection canceled or failed, resultCode: " + resultCode);
- }
- }
+ });
- /**
- * 应用文本样式(加粗、斜体、下划线)
- * @param styleType 样式类型:Typeface.BOLD, Typeface.ITALIC, 或 -1 表示下划线
- */
- private void applyTextStyle(int styleType) {
- if (mNoteEditor == null) {
- return;
- }
-
- // 检查是否在清单模式,如果是则提示用户切换到普通模式
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- showToast(R.string.error_formatting_not_supported_in_list_mode);
- return;
+ builder.show();
}
+ }
- Editable editable = mNoteEditor.getText();
- if (editable == null) {
- return;
- }
-
- int selectionStart = mNoteEditor.getSelectionStart();
- int selectionEnd = mNoteEditor.getSelectionEnd();
-
- // 如果没有选中文本,则选中光标所在的单词或当前字符
- if (selectionStart == selectionEnd) {
- // 尝试选中光标所在的单词
- String text = editable.toString();
- int start = selectionStart;
- int end = selectionEnd;
-
- // 向前查找单词开始位置
- while (start > 0 && Character.isLetterOrDigit(text.charAt(start - 1))) {
- start--;
- }
-
- // 向后查找单词结束位置
- while (end < text.length() && Character.isLetterOrDigit(text.charAt(end))) {
- end++;
- }
-
- // 如果找到了单词,则选中它;否则选中当前字符
- if (start < end) {
- selectionStart = start;
- selectionEnd = end;
- mNoteEditor.setSelection(selectionStart, selectionEnd);
- } else {
- // 如果没有找到单词,提示用户先选择文本
- showToast(R.string.error_please_select_text);
- return;
+ // 新增工具方法:把临时URI的图片复制到应用私有目录,返回真实路径
+ private String saveImageToLocal(Uri uri) {
+ try {
+ // 1. 创建应用专属图片目录(不会被系统清理)
+ File appDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "note_images");
+ if (!appDir.exists()) appDir.mkdirs();
+
+ // 2. 生成唯一文件名(避免重复)
+ String fileName = "note_" + System.currentTimeMillis() + ".jpg";
+ File targetFile = new File(appDir, fileName);
+
+ // 3. 复制图片文件(从临时URI到本地目录)
+ InputStream is = getContentResolver().openInputStream(uri);
+ OutputStream os = new FileOutputStream(targetFile);
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = is.read(buffer)) > 0) {
+ os.write(buffer, 0, len);
}
+ is.close();
+ os.close();
+
+ // 返回图片的真实本地路径(不是URI)
+ return targetFile.getAbsolutePath();
+ } catch (Exception e) {
+ Log.e("NoteEdit", "保存图片失败", e);
+ Toast.makeText(this, "图片保存失败", Toast.LENGTH_SHORT).show();
+ return null;
}
+ }
- // 确保选择范围有效
- if (selectionStart < 0 || selectionEnd > editable.length() || selectionStart >= selectionEnd) {
- showToast(R.string.error_please_select_text);
- return;
+ // 自定义方法:给RichEditor设置字体大小(对应原EditText的setTextAppearance)
+ private void setRichEditorFontSize(int fontSizeId) {
+ switch (fontSizeId) {
+ case ResourceParser.TEXT_SMALL:
+ mNoteEditor.setEditorFontSize(14); // 小字体
+ break;
+ case ResourceParser.TEXT_MEDIUM:
+ mNoteEditor.setEditorFontSize(18); // 中字体(默认)
+ break;
+ case ResourceParser.TEXT_LARGE:
+ mNoteEditor.setEditorFontSize(22); // 大字体
+ break;
+ case ResourceParser.TEXT_SUPER:
+ mNoteEditor.setEditorFontSize(26); // 超大字体
+ break;
+ default:
+ mNoteEditor.setEditorFontSize(18); // 默认值
}
+ }
- // 应用样式
- if (styleType == -1) {
- // 下划线样式
- UnderlineSpan[] underlineSpans = editable.getSpans(selectionStart, selectionEnd, UnderlineSpan.class);
- if (underlineSpans.length > 0) {
- // 移除下划线
- for (UnderlineSpan span : underlineSpans) {
- int spanStart = editable.getSpanStart(span);
- int spanEnd = editable.getSpanEnd(span);
- editable.removeSpan(span);
- // 如果移除的span范围大于选中范围,需要重新应用样式到剩余部分
- if (spanStart < selectionStart) {
- editable.setSpan(new UnderlineSpan(), spanStart, selectionStart, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- if (spanEnd > selectionEnd) {
- editable.setSpan(new UnderlineSpan(), selectionEnd, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- } else {
- // 添加下划线
- editable.setSpan(new UnderlineSpan(), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else {
- // 加粗或斜体样式
- StyleSpan[] styleSpans = editable.getSpans(selectionStart, selectionEnd, StyleSpan.class);
-
- // 检查选中范围内是否已经有该样式
- boolean hasStyle = false;
- int existingStyle = 0;
-
- // 收集选中范围内的所有样式
- for (StyleSpan span : styleSpans) {
- int spanStart = editable.getSpanStart(span);
- int spanEnd = editable.getSpanEnd(span);
-
- // 如果span与选中范围重叠
- if (spanStart < selectionEnd && spanEnd > selectionStart) {
- int spanStyle = span.getStyle();
- existingStyle |= spanStyle;
-
- // 检查是否包含目标样式
- if ((spanStyle & styleType) == styleType) {
- hasStyle = true;
- }
- }
+ // 初始化富文本编辑器配置
+ private void initRichEditor() {
+ mNoteEditor.setEditorHeight(600); // 设置编辑器高度
+ mNoteEditor.setEditorFontSize(16); // 字体大小
+ mNoteEditor.setEditorFontColor(Color.BLACK); // 字体颜色
+ mNoteEditor.setPadding(10, 10, 10, 10); // 内边距
+ mNoteEditor.setPlaceholder("请输入笔记内容..."); // 占位提示
+ mNoteEditor.setInputEnabled(true); // 允许输入
+ }
+
+ // 添加富文本功能按钮初始化方法
+ private void initRichEditorButtons() {
+ // 撤销功能
+ findViewById(R.id.action_undo).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mNoteEditor.undo();
}
-
- // 移除选中范围内的所有StyleSpan,以便重新应用
- for (int i = styleSpans.length - 1; i >= 0; i--) {
- StyleSpan span = styleSpans[i];
- int spanStart = editable.getSpanStart(span);
- int spanEnd = editable.getSpanEnd(span);
-
- // 如果span与选中范围重叠
- if (spanStart < selectionEnd && spanEnd > selectionStart) {
- editable.removeSpan(span);
-
- // 处理span范围大于选中范围的情况,保留未选中部分的样式
- int spanStyle = span.getStyle();
- if (spanStart < selectionStart) {
- // 保留选中范围之前的样式
- editable.setSpan(new StyleSpan(spanStyle), spanStart, selectionStart, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- if (spanEnd > selectionEnd) {
- // 保留选中范围之后的样式
- editable.setSpan(new StyleSpan(spanStyle), selectionEnd, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
+ });
+
+ // 加粗功能
+ findViewById(R.id.action_bold).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mNoteEditor.setBold();
}
-
- // 切换样式:如果有则移除,如果没有则添加
- int finalStyle = existingStyle;
- if (hasStyle) {
- // 移除目标样式
- finalStyle &= ~styleType;
- } else {
- // 添加目标样式
- finalStyle |= styleType;
+ });
+
+ // 斜体功能
+ findViewById(R.id.action_italic).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mNoteEditor.setItalic();
}
-
- // 应用最终样式
- if (finalStyle != 0) {
- editable.setSpan(new StyleSpan(finalStyle), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ });
+
+ // 背景色功能
+ findViewById(R.id.action_bg_color).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mNoteEditor.focusEditor(); // 获取焦点
+ new AlertDialog.Builder(NoteEditActivity.this)
+ .setTitle("选择字体背景颜色")
+ .setItems(new String[]{"红色", "黄色", "蓝色", "绿色", "黑色", "白色"},
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case 0: // 红
+ mNoteEditor.setTextBackgroundColor(Color.RED);
+ break;
+ case 1: // 黄
+ mNoteEditor.setTextBackgroundColor(Color.YELLOW);
+ break;
+ case 2: // 蓝
+ mNoteEditor.setTextBackgroundColor(Color.BLUE);
+ break;
+ case 3: // 绿
+ mNoteEditor.setTextBackgroundColor(Color.GREEN);
+ break;
+ case 4: // 黑
+ mNoteEditor.setTextBackgroundColor(Color.BLACK);
+ break;
+ case 5: // 白
+ mNoteEditor.setTextBackgroundColor(Color.WHITE);
+ break;
+ }
+ dialog.dismiss(); // 选择后关闭对话框
+ }
+ })
+ .show();
}
- }
-
- // 保持选中状态
- mNoteEditor.setSelection(selectionStart, selectionEnd);
- }
-
- /**
- * 应用加粗样式
- */
- private void applyBoldStyle() {
- applyTextStyle(Typeface.BOLD);
- }
-
- /**
- * 应用斜体样式
- */
- private void applyItalicStyle() {
- applyTextStyle(Typeface.ITALIC);
- }
-
- /**
- * 应用下划线样式
- */
- private void applyUnderlineStyle() {
- applyTextStyle(-1); // -1 表示下划线
+ });
}
-}
+}
\ No newline at end of file
diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java
index 5277d75..1e14db4 100644
--- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java
+++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java
@@ -684,13 +684,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
private void batchDelete() {
+ final NotesListActivity activity = this;
new AsyncTask>() {
protected HashSet doInBackground(Void... unused) {
HashSet widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
- .getSelectedItemIds())) {
+ .getSelectedItemIds(), activity)) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
@@ -736,7 +737,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
folderId);
if (!isSyncMode()) {
// if not synced, delete folder directly
- DataUtils.batchDeleteNotes(mContentResolver, ids);
+ DataUtils.batchDeleteNotes(mContentResolver, ids, this);
} else {
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
diff --git a/src/Notes-master/src/net/micode/notes/ui/TrashActivity.java b/src/Notes-master/src/net/micode/notes/ui/TrashActivity.java
index cf07235..1b80376 100644
--- a/src/Notes-master/src/net/micode/notes/ui/TrashActivity.java
+++ b/src/Notes-master/src/net/micode/notes/ui/TrashActivity.java
@@ -140,7 +140,7 @@ public class TrashActivity extends Activity implements OnClickListener {
}
private void permanentlyDeleteNote(long noteId) {
- if (DataUtils.permanentlyDeleteFromTrash(mContentResolver, noteId)) {
+ if (DataUtils.permanentlyDeleteFromTrash(mContentResolver, noteId, this)) {
Toast.makeText(this, getString(R.string.menu_delete) + " " + getString(R.string.menu_success),
Toast.LENGTH_SHORT).show();
startAsyncTrashListQuery(); // Refresh list
@@ -151,7 +151,7 @@ public class TrashActivity extends Activity implements OnClickListener {
}
private void recoverNote(long noteId) {
- if (DataUtils.restoreNoteFromTrash(mContentResolver, noteId)) {
+ if (DataUtils.restoreNoteFromTrash(mContentResolver, noteId, this)) {
Toast.makeText(this, getString(R.string.button_recovery) + " " + getString(R.string.menu_success),
Toast.LENGTH_SHORT).show();
startAsyncTrashListQuery(); // Refresh list
@@ -177,7 +177,7 @@ public class TrashActivity extends Activity implements OnClickListener {
}
private void clearAllTrash() {
- if (DataUtils.clearAllTrash(mContentResolver)) {
+ if (DataUtils.clearAllTrash(mContentResolver, this)) {
Toast.makeText(this, getString(R.string.button_clean_all) + " " + getString(R.string.menu_success),
Toast.LENGTH_SHORT).show();
startAsyncTrashListQuery(); // Refresh list to clear UI