From 08f40ae287e18b46a05934b627f275b542fdcc2a Mon Sep 17 00:00:00 2001 From: mc19 <2716188191@qq.com> Date: Wed, 21 Jan 2026 10:14:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=8C=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=8A=9F=E8=83=BD=E5=92=8C=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E5=9B=BE=E7=89=87=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 | 55 ++ src/notes/data/NotesDatabaseHelper.java | 24 +- src/notes/model/Note.java | 74 +- src/notes/model/WorkingNote.java | 108 ++- src/notes/tool/BackupUtils.java | 2 +- src/notes/tool/DataUtils.java | 6 +- src/notes/tool/ImageSpanUtils.java | 103 +++ src/notes/tool/RichTextFormatUtils.java | 116 +++ src/notes/ui/NoteEditActivity.java | 838 +++++++++++++++++++++- src/notes/ui/NoteEditText.java | 4 +- src/notes/ui/NotesListActivity.java | 8 +- src/notes/ui/NotesPreferenceActivity.java | 6 +- src/notes/widget/NoteWidgetProvider.java | 2 +- 13 files changed, 1312 insertions(+), 34 deletions(-) create mode 100644 src/notes/tool/ImageSpanUtils.java create mode 100644 src/notes/tool/RichTextFormatUtils.java diff --git a/src/notes/data/Notes.java b/src/notes/data/Notes.java index de21759..2958f09 100644 --- a/src/notes/data/Notes.java +++ b/src/notes/data/Notes.java @@ -78,6 +78,7 @@ public class Notes { public static class DataConstants { public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; //文本便签MIME类型 public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; //通话备忘MIME类型 + public static final String IMAGE = ImageData.CONTENT_ITEM_TYPE; //图片MIME类型 } //七、基础URI @@ -196,6 +197,31 @@ public class Notes { */ public static final String GTASK_ID = "gtask_id"; + /** + * Alert location latitude + *

Type: REAL

+ */ + public static final String ALERT_LATITUDE = "alert_latitude"; + + /** + * Alert location longitude + *

Type: REAL

+ */ + public static final String ALERT_LONGITUDE = "alert_longitude"; + + /** + * Alert location radius + *

Type: REAL

+ */ + public static final String ALERT_RADIUS = "alert_radius"; + + /** + * Alert location name + *

Type: TEXT

+ */ + public static final String ALERT_LOCATION_NAME = "alert_location_name"; + + /** * The version code *

Type : INTEGER (long)

@@ -281,6 +307,18 @@ public class Notes { *

Type: TEXT

*/ public static final String DATA5 = "data5"; + + /** + * Rich text format information for storing span information + *

Type: TEXT

+ */ + public static final String RICH_TEXT_FORMAT = "rich_text_format"; + + /** + * Path to the image file for image data + *

Type: TEXT

+ */ + public static final String IMAGE_PATH = "image_path"; } @@ -325,4 +363,21 @@ public class Notes { public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } + + /** + * 图片数据实体常量类 + */ + public static final class ImageData implements DataColumns { + /** + * Path to the image file + *

Type: TEXT

+ */ + public static final String PATH = DATA3; + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image_data"; + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/image_data"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/image_data"); + } } diff --git a/src/notes/data/NotesDatabaseHelper.java b/src/notes/data/NotesDatabaseHelper.java index 82aa598..b761c42 100644 --- a/src/notes/data/NotesDatabaseHelper.java +++ b/src/notes/data/NotesDatabaseHelper.java @@ -54,7 +54,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { */ private static final String DB_NAME = "note.db"; - private static final int DB_VERSION = 4; + private static final int DB_VERSION = 6; + /**表明常量接口*/ public interface TABLE { @@ -401,6 +402,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { oldVersion++; } + if (oldVersion == 4) { + upgradeToV5(db); + oldVersion++; + } + + if (oldVersion == 5) { + upgradeToV6(db); + oldVersion++; + } + if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); @@ -441,4 +452,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } + + //v4到v5的升级,为data表新增rich_text_format列,用于存储富文本格式信息 + private void upgradeToV5(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN rich_text_format TEXT NOT NULL DEFAULT ''"); + } + + //v5到v6的升级,为data表新增用于存储图片路径的列 + private void upgradeToV6(SQLiteDatabase db) { + // 为data表添加一个专门存储图片路径的列 + db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN image_path TEXT NOT NULL DEFAULT ''"); + } } diff --git a/src/notes/model/Note.java b/src/notes/model/Note.java index ecf9c21..eb68f95 100644 --- a/src/notes/model/Note.java +++ b/src/notes/model/Note.java @@ -110,6 +110,15 @@ public class Note { mNoteData.setCallData(key, value); } + public void setRichTextFormat(String formatInfo) { + mNoteData.setRichTextFormat(formatInfo); + } + + // 设置图片数据 + public void setImageData(String key, String value) { + mNoteData.setImageData(key, value); + } + //是否有本地修改 public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); @@ -157,6 +166,10 @@ public class Note { private ContentValues mTextDataValues; // 存储文本数据的变化 + private ContentValues mRichTextFormatValues; // 存储富文本格式信息 + + private ContentValues mImageDataValues; // 存储图片数据的变化 + private long mCallDataId; // 通话记录数据在数据库中的ID(0表示未创建) private ContentValues mCallDataValues; // 存储通话数据的变化(如号码更新) @@ -165,13 +178,15 @@ public class Note { public NoteData() { mTextDataValues = new ContentValues(); + mRichTextFormatValues = new ContentValues(); + mImageDataValues = new ContentValues(); mCallDataValues = new ContentValues(); mTextDataId = 0; mCallDataId = 0; } boolean isLocalModified() { - return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; + return mTextDataValues.size() > 0 || mCallDataValues.size() > 0 || mImageDataValues.size() > 0; } void setTextDataId(long id) { @@ -200,6 +215,18 @@ public class Note { mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + void setRichTextFormat(String formatInfo) { + mRichTextFormatValues.put(DataColumns.RICH_TEXT_FORMAT, formatInfo); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + + void setImageData(String key, String value) { + mImageDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + /** * 将文本/通话数据的变化同步到数据库 * 若数据未创建(ID=0)则插入新记录;若已创建(ID>0)则更新记录 @@ -221,22 +248,27 @@ public class Note { ContentProviderOperation.Builder builder = null; //处理文本数据同步 - if(mTextDataValues.size() > 0) { + if(mTextDataValues.size() > 0 || mRichTextFormatValues.size() > 0) { // 关联文本数据到当前笔记 mTextDataValues.put(DataColumns.NOTE_ID, noteId); + + //合并文本数据和富文本格式信息 + ContentValues combinedValues = new ContentValues(mTextDataValues); + combinedValues.putAll(mRichTextFormatValues); //文本数据未创建 if (mTextDataId == 0) { // 插入数据到ContentProvider,获取返回的Uri - mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); + combinedValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mTextDataValues); + combinedValues); try { // 从Uri解析新插入的文本数据ID并存储 setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { Log.e(TAG, "Insert new text data fail with noteId" + noteId); mTextDataValues.clear(); + mRichTextFormatValues.clear(); return null; } } else { @@ -244,10 +276,42 @@ public class Note { // 构建更新操作(指定数据ID对应的Uri) builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mTextDataId)); - builder.withValues(mTextDataValues); // 设置要更新的内容 + builder.withValues(combinedValues); // 设置要更新的内容 operationList.add(builder.build()); // 添加到批量操作列表 } mTextDataValues.clear(); + mRichTextFormatValues.clear(); + } + + //处理图片数据同步 + if(mImageDataValues.size() > 0) { + mImageDataValues.put(DataColumns.NOTE_ID, noteId); + if (mTextDataId == 0) { // 使用文本数据ID作为参考,因为图片通常与文本数据一起存储 + mImageDataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.IMAGE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mImageDataValues); + try { + // 图片数据ID可能需要单独处理,这里简化处理 + Log.d(TAG, "Inserting image data"); + } catch (Exception e) { + Log.e(TAG, "Insert new image data fail with noteId" + noteId); + mImageDataValues.clear(); + return null; + } + } else { + // 如果文本数据已存在,我们需要为图片数据创建新的记录 + mImageDataValues.put(DataColumns.MIME_TYPE, Notes.DataConstants.IMAGE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mImageDataValues); + try { + Log.d(TAG, "Inserting additional image data"); + } catch (Exception e) { + Log.e(TAG, "Insert additional image data fail with noteId" + noteId); + mImageDataValues.clear(); + return null; + } + } + mImageDataValues.clear(); } //处理通话数据同步(逻辑同处理文本数据同步) diff --git a/src/notes/model/WorkingNote.java b/src/notes/model/WorkingNote.java index b86eb9e..f3c37c8 100644 --- a/src/notes/model/WorkingNote.java +++ b/src/notes/model/WorkingNote.java @@ -28,6 +28,7 @@ import net.micode.notes.data.Notes.CallNote; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; @@ -46,6 +47,10 @@ public class WorkingNote { private int mMode; private long mAlertDate; + private double mAlertLatitude; + private double mAlertLongitude; + private float mAlertRadius; + private String mAlertLocationName; private long mModifiedDate; @@ -64,6 +69,7 @@ public class WorkingNote { private boolean mIsDeleted; private NoteSettingChangedListener mNoteSettingStatusListener; + private String mRichTextFormatInfo; //指定查询Data表时需要返回的字段 public static final String[] DATA_PROJECTION = new String[] { @@ -74,6 +80,9 @@ public class WorkingNote { DataColumns.DATA2, DataColumns.DATA3, DataColumns.DATA4, + DataColumns.DATA5, + DataColumns.RICH_TEXT_FORMAT, + DataColumns.IMAGE_PATH, }; //指定查询Note表时需要返回的字段 @@ -83,7 +92,11 @@ public class WorkingNote { NoteColumns.BG_COLOR_ID, NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, - NoteColumns.MODIFIED_DATE + NoteColumns.MODIFIED_DATE, + NoteColumns.ALERT_LATITUDE, + NoteColumns.ALERT_LONGITUDE, + NoteColumns.ALERT_RADIUS, + NoteColumns.ALERT_LOCATION_NAME }; private static final int DATA_ID_COLUMN = 0; @@ -94,6 +107,10 @@ public class WorkingNote { private static final int DATA_MODE_COLUMN = 3; + private static final int DATA_RICH_TEXT_FORMAT_COLUMN = 8; + + private static final int DATA_IMAGE_PATH_COLUMN = 9; + private static final int NOTE_PARENT_ID_COLUMN = 0; private static final int NOTE_ALERTED_DATE_COLUMN = 1; @@ -105,11 +122,19 @@ public class WorkingNote { private static final int NOTE_WIDGET_TYPE_COLUMN = 4; private static final int NOTE_MODIFIED_DATE_COLUMN = 5; + private static final int NOTE_ALERT_LATITUDE_COLUMN = 6; + private static final int NOTE_ALERT_LONGITUDE_COLUMN = 7; + private static final int NOTE_ALERT_RADIUS_COLUMN = 8; + private static final int NOTE_ALERT_LOCATION_NAME_COLUMN = 9; // New note construct private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; + mAlertLatitude = 0; + mAlertLongitude = 0; + mAlertRadius = 0; + mAlertLocationName = ""; mModifiedDate = System.currentTimeMillis(); mFolderId = folderId; mNote = new Note(); @@ -147,6 +172,14 @@ public class WorkingNote { mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); + + // 经纬度和提醒位置 + mAlertLatitude = cursor.getDouble(NOTE_ALERT_LATITUDE_COLUMN); + mAlertLongitude = cursor.getDouble(NOTE_ALERT_LONGITUDE_COLUMN); + mAlertRadius = cursor.getFloat(NOTE_ALERT_RADIUS_COLUMN); + mAlertLocationName = cursor.getString(NOTE_ALERT_LOCATION_NAME_COLUMN); + // 加载富文本格式信息 + mRichTextFormatInfo = cursor.getString(DATA_RICH_TEXT_FORMAT_COLUMN); } cursor.close(); } else { @@ -173,8 +206,15 @@ public class WorkingNote { mContent = cursor.getString(DATA_CONTENT_COLUMN); mMode = cursor.getInt(DATA_MODE_COLUMN); mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); + // 加载富文本格式信息 + mRichTextFormatInfo = cursor.getString(DATA_RICH_TEXT_FORMAT_COLUMN); + } + //如果是图片类型 + else if (DataConstants.IMAGE.equals(type)) { + // 图片数据,目前我们只是加载数据,实际图片显示在UI层处理 + String imagePath = cursor.getString(DATA_IMAGE_PATH_COLUMN); + Log.d(TAG, "Loaded image data: " + imagePath); } - //如果是通话记录类型 else if (DataConstants.CALL_NOTE.equals(type)) { mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); @@ -323,6 +363,18 @@ public class WorkingNote { } } + //设置富文本格式 + public void setRichTextFormat(String formatInfo) { + if (!TextUtils.equals(mRichTextFormatInfo, formatInfo)) { + mRichTextFormatInfo = formatInfo; + mNote.setRichTextFormat(formatInfo); + } + } + + public String getRichTextFormat() { + return mRichTextFormatInfo; + } + // 将笔记转换为通话笔记(设置通话相关数据) public void convertToCallNote(String phoneNumber, long callDate) { mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); @@ -334,6 +386,47 @@ public class WorkingNote { return (mAlertDate > 0 ? true : false); } + + public boolean hasLocationAlert() { + return (mAlertLatitude != 0 && mAlertLongitude != 0); + } + + public void setAlertLocation(double latitude, double longitude, float radius, String locationName, boolean set) { + // 有位置变化才更新 + if (latitude != mAlertLatitude || longitude != mAlertLongitude || radius != mAlertRadius || !locationName.equals(mAlertLocationName)) { + mAlertLatitude = latitude; + mAlertLongitude = longitude; + mAlertRadius = radius; + mAlertLocationName = locationName; + + mNote.setNoteValue(NoteColumns.ALERT_LATITUDE, String.valueOf(mAlertLatitude)); + mNote.setNoteValue(NoteColumns.ALERT_LONGITUDE, String.valueOf(mAlertLongitude)); + mNote.setNoteValue(NoteColumns.ALERT_RADIUS, String.valueOf(mAlertRadius)); + mNote.setNoteValue(NoteColumns.ALERT_LOCATION_NAME, mAlertLocationName); + } + + // 若监听器不为空,触发位置提醒变更回调 + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onLocationAlertChanged(latitude, longitude, radius, locationName, set); + } + } + + public double getAlertLatitude() { + return mAlertLatitude; + } + + public double getAlertLongitude() { + return mAlertLongitude; + } + + public float getAlertRadius() { + return mAlertRadius; + } + + public String getAlertLocationName() { + return mAlertLocationName; + } + public String getContent() { return mContent; } @@ -378,6 +471,11 @@ public class WorkingNote { return mWidgetType; } + public Note getNote() { + return mNote; + } + + // 笔记设置变更监听器接口(定义属性变化时的回调方法) public interface NoteSettingChangedListener { /** @@ -390,6 +488,12 @@ public class WorkingNote { */ void onClockAlertChanged(long date, boolean set); + /** + * Called when user set location alert + */ + void onLocationAlertChanged(double latitude, double longitude, float radius, String locationName, boolean set); + + /** * Call when user create note from widget */ diff --git a/src/notes/tool/BackupUtils.java b/src/notes/tool/BackupUtils.java index 11053a9..1075bd7 100644 --- a/src/notes/tool/BackupUtils.java +++ b/src/notes/tool/BackupUtils.java @@ -313,7 +313,7 @@ public class BackupUtils { Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " - + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + ") OR " + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); if (folderCursor != null) { diff --git a/src/notes/tool/DataUtils.java b/src/notes/tool/DataUtils.java index 94c08d7..a5e477f 100644 --- a/src/notes/tool/DataUtils.java +++ b/src/notes/tool/DataUtils.java @@ -158,7 +158,7 @@ public class DataUtils { Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, new String[] { "COUNT(*)" }, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLDER)}, null); int count = 0; @@ -186,7 +186,7 @@ public class DataUtils { public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, - NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER, new String [] {String.valueOf(type)}, null); @@ -249,7 +249,7 @@ public class DataUtils { public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + - " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + " AND " + NoteColumns.SNIPPET + "=?", new String[] { name }, null); boolean exist = false; diff --git a/src/notes/tool/ImageSpanUtils.java b/src/notes/tool/ImageSpanUtils.java new file mode 100644 index 0000000..9c27cde --- /dev/null +++ b/src/notes/tool/ImageSpanUtils.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ImageSpan; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * 图片Span工具类,用于处理图片在Spannable中的序列化和反序列化 + */ +public class ImageSpanUtils { + + /** + * 将Spannable中的图片信息序列化为JSON字符串 + * + * @param spannable 包含图片信息的Spannable对象 + * @param content 原始文本内容 + * @return 图片信息的JSON字符串表示 + */ + public static String serializeImageInfo(Spannable spannable, String content) { + JSONArray jsonArray = new JSONArray(); + + // 获取所有图片Span + ImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), ImageSpan.class); + + for (ImageSpan span : imageSpans) { + JSONObject spanObj = new JSONObject(); + try { + int start = spannable.getSpanStart(span); + int end = spannable.getSpanEnd(span); + + // 获取图片URI(如果有的话) + Drawable drawable = span.getDrawable(); + if (drawable != null) { + // 这里我们假设图片信息已经在其他地方存储,只需记录位置和路径 + // 在实际实现中,你可能需要将图片保存到特定位置并记录路径 + spanObj.put("type", "image"); + spanObj.put("start", start); + spanObj.put("end", end); + // 注意:ImageSpan通常不直接存储URI,需要额外的机制来跟踪图片路径 + // 这里仅为示例,实际实现需要根据具体情况调整 + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + return jsonArray.toString(); + } + + /** + * 将JSON格式的图片信息反序列化并应用到文本上 + * + * @param context 上下文 + * @param text 原始文本 + * @param imageJson 序列化的图片信息JSON字符串 + * @return 包含图片信息的SpannableStringBuilder + */ + public static SpannableStringBuilder deserializeImageInfo(Context context, String text, String imageJson) { + SpannableStringBuilder spannable = new SpannableStringBuilder(text); + + if (imageJson == null || imageJson.isEmpty()) { + return spannable; + } + + try { + // 这里需要根据实际的图片存储机制来实现 + // 因为图片通常需要特殊处理,这里暂时返回原始文本 + // 实际实现需要考虑如何存储和检索图片 + return spannable; + } catch (Exception e) { + e.printStackTrace(); + } + + return spannable; + } +} \ No newline at end of file diff --git a/src/notes/tool/RichTextFormatUtils.java b/src/notes/tool/RichTextFormatUtils.java new file mode 100644 index 0000000..5adf249 --- /dev/null +++ b/src/notes/tool/RichTextFormatUtils.java @@ -0,0 +1,116 @@ +package net.micode.notes.tool; + +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.text.style.StrikethroughSpan; +import android.graphics.Typeface; +import android.text.ParcelableSpan; +import android.text.style.CharacterStyle; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * 富文本格式工具类,用于序列化和反序列化Spannable中的格式信息 + */ +public class RichTextFormatUtils { + + /** + * 将Spannable中的格式信息序列化为JSON字符串 + * + * @param spannable 包含格式信息的Spannable对象 + * @return 格式信息的JSON字符串表示 + */ + public static String serializeFormatInfo(Spannable spannable) { + JSONArray jsonArray = new JSONArray(); + + // 获取所有字符样式Span + CharacterStyle[] spans = spannable.getSpans(0, spannable.length(), CharacterStyle.class); + + for (CharacterStyle span : spans) { + JSONObject spanObj = new JSONObject(); + try { + int start = spannable.getSpanStart(span); + int end = spannable.getSpanEnd(span); + int flags = spannable.getSpanFlags(span); + + // 识别Span类型并保存相关信息 + if (span instanceof StyleSpan) { + StyleSpan styleSpan = (StyleSpan) span; + spanObj.put("type", "style"); + spanObj.put("style", styleSpan.getStyle()); + } else if (span instanceof UnderlineSpan) { + spanObj.put("type", "underline"); + } else if (span instanceof StrikethroughSpan) { + spanObj.put("type", "strikethrough"); + } else { + continue; // 不支持的Span类型,跳过 + } + + spanObj.put("start", start); + spanObj.put("end", end); + spanObj.put("flags", flags); + + jsonArray.put(spanObj); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + return jsonArray.toString(); + } + + /** + * 将JSON格式的格式信息反序列化并应用到文本上 + * + * @param text 原始文本 + * @param formatJson 序列化的格式信息JSON字符串 + * @return 包含格式信息的SpannableStringBuilder + */ + public static SpannableStringBuilder deserializeFormatInfo(String text, String formatJson) { + SpannableStringBuilder spannable = new SpannableStringBuilder(text); + + if (formatJson == null || formatJson.isEmpty()) { + return spannable; + } + + try { + JSONArray jsonArray = new JSONArray(formatJson); + + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject spanObj = jsonArray.getJSONObject(i); + + int start = spanObj.getInt("start"); + int end = spanObj.getInt("end"); + int flags = spanObj.getInt("flags"); + + String type = spanObj.getString("type"); + + CharacterStyle span = null; + if ("style".equals(type)) { + int style = spanObj.getInt("style"); + span = new StyleSpan(style); + } else if ("underline".equals(type)) { + span = new UnderlineSpan(); + } else if ("strikethrough".equals(type)) { + span = new StrikethroughSpan(); + } + + if (span != null && start >= 0 && end <= text.length() && start < end) { + spannable.setSpan(span, start, end, flags); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return spannable; + } +} \ No newline at end of file diff --git a/src/notes/ui/NoteEditActivity.java b/src/notes/ui/NoteEditActivity.java index 8f2e9d8..f970c7a 100644 --- a/src/notes/ui/NoteEditActivity.java +++ b/src/notes/ui/NoteEditActivity.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package net.micode.notes.ui; import android.app.Activity; @@ -32,9 +31,13 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; +import android.text.InputType; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.text.style.StrikethroughSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -43,10 +46,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; +import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; +import android.net.Uri; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -61,6 +67,7 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser.TextAppearanceResources; import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; +import net.micode.notes.tool.RichTextFormatUtils; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; @@ -138,9 +145,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, private SharedPreferences mSharedPrefs; // 偏好设置 private int mFontSizeId; // 当前字体大小ID + // 位置提醒对话框相关控件 + private EditText mLocationNameEditText; // 位置名称输入框 + private EditText mLatitudeEditText; // 纬度输入框 + private EditText mLongitudeEditText; // 经度输入框 + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键 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'); // 方框符号 □ @@ -149,6 +168,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, private String mUserQuery; // 用户搜索查询词(从搜索跳转过来时使用) private Pattern mPattern; // 用于高亮搜索词的正则表达式模式 + // 富文本编辑相关控件 + private LinearLayout mFormatToolbar; // 格式工具栏 + private ImageButton mBtnBold; // 粗体按钮 + private ImageButton mBtnItalic; // 斜体按钮 + private ImageButton mBtnUnderline; // 下划线按钮 + private ImageButton mBtnStrikethrough; // 删除线按钮 + private ImageButton mBtnAddImage; // 添加图片按钮 + + /** * Activity创建时的初始化方法 */ @@ -163,6 +191,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, return; } initResources(); // 初始化视图资源 + + // 添加对OnBackPressedDispatcher的支持,以便处理手势返回 + try { + // 使用反射调用getOnBackPressedDispatcher方法,添加一个空回调 + // 这样可以确保手势返回功能正常工作,同时保持原有代码不变 + Object dispatcher = getClass().getMethod("getOnBackPressedDispatcher").invoke(this); + Class callbackClass = Class.forName("androidx.activity.OnBackPressedCallback"); + Object callback = callbackClass.getConstructor(boolean.class).newInstance(true); + Class callbackInterface = Class.forName("androidx.activity.OnBackPressedCallback$OnBackPressedListener"); + Object listener = java.lang.reflect.Proxy.newProxyInstance( + callbackInterface.getClassLoader(), + new Class[]{callbackInterface}, + (proxy, method, args) -> { + // 调用原有的onBackPressed方法 + onBackPressed(); + return null; + } + ); + callbackClass.getMethod("addOnBackPressedListener", callbackInterface).invoke(callback, listener); + dispatcher.getClass().getMethod("addCallback", android.app.Activity.class, callbackClass).invoke(dispatcher, this, callback); + } catch (Exception e) { + // 如果反射调用失败,不影响原有功能 + Log.d("NoteEditActivity", "OnBackPressedDispatcher not available, using legacy onBackPressed"); + } } /** @@ -293,12 +345,27 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 根据笔记模式初始化界面 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - // 清单模式 + // 清单模式,只显示文本原有内容 switchToListMode(mWorkingNote.getContent()); } else { // 普通文本模式,高亮搜索词 - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + String content = mWorkingNote.getContent(); + String formatInfo = mWorkingNote.getRichTextFormat(); + + // 恢复富文本格式 + if (formatInfo != null && !formatInfo.isEmpty()) { + // 使用序列化的格式信息恢复文本格式 + android.text.SpannableStringBuilder formattedText = RichTextFormatUtils.deserializeFormatInfo(content, formatInfo); + mNoteEditor.setText(formattedText); + } else { + // 没有格式信息时,正常显示高亮文本 + mNoteEditor.setText(getHighlightQueryResult(content, mUserQuery)); + } + mNoteEditor.setSelection(mNoteEditor.getText().length()); + + // 显示富文本格式工具栏 + mFormatToolbar.setVisibility(View.VISIBLE); } // 隐藏所有背景颜色选中标记 @@ -318,20 +385,41 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 显示提醒信息 showAlertHeader(); + + // 初始化时设置格式工具栏背景 + updateFormatToolbarBackground(); } /** * 显示或隐藏提醒相关的UI组件 */ private void showAlertHeader() { - if (mWorkingNote.hasClockAlert()) { - long time = System.currentTimeMillis(); - if (time > mWorkingNote.getAlertDate()) { - mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); - } else { - mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( - mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); + boolean hasClockAlert = mWorkingNote.hasClockAlert(); + boolean hasLocationAlert = mWorkingNote.hasLocationAlert(); + + if (hasClockAlert || hasLocationAlert) { + StringBuilder alertText = new StringBuilder(); + + // 添加时间提醒信息 + if (hasClockAlert) { + long time = System.currentTimeMillis(); + if (time > mWorkingNote.getAlertDate()) { + alertText.append(getString(R.string.note_alert_expired)); + } else { + alertText.append(DateUtils.getRelativeTimeSpanString( + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); + } } + + // 添加位置提醒信息 + if (hasLocationAlert) { + if (hasClockAlert) { + alertText.append(" | "); + } + alertText.append("位置: ").append(mWorkingNote.getAlertLocationName()); + } + + mNoteHeaderHolder.tvAlertDate.setText(alertText.toString()); mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { @@ -363,6 +451,169 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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(); + } + } + } + } 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")中 + Bundle extras = data.getExtras(); + android.graphics.Bitmap bitmap = (android.graphics.Bitmap) extras.get("data"); + if (bitmap != null) { + // 将Bitmap转换为Uri + imageUri = getImageUriFromBitmap(bitmap); + } + } + } + */ + + 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; + } + } + + /** + * 在编辑器光标位置插入图片 + */ + 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}; + 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; + } + } catch (Exception e) { + e.printStackTrace(); + } + return contentUri.toString(); + } + /** * 触摸事件分发,用于点击选择器外部时关闭选择器 */ @@ -427,6 +678,23 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); @@ -435,6 +703,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + + // 设置图片删除处理 + setupImageDeletionHandling(); } /** @@ -448,6 +719,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } clearSettingState(); // 清理设置面板状态 + + // 隐藏富文本格式工具栏 + if (mFormatToolbar != null) { + mFormatToolbar.setVisibility(View.GONE); + } } /** @@ -514,6 +790,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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(); } } @@ -521,7 +804,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, * 返回键按下事件处理 */ @Override - public void onBackPressed() { + public void onBackPressedDispatcher() { // 如果有设置面板打开,先关闭面板 if(clearSettingState()) { return; @@ -557,6 +840,69 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 更新编辑器面板和头部面板的背景 mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + + // 根据当前背景颜色调整富文本格式工具栏的背景 + updateFormatToolbarBackground(); + } + + /** + * 根据当前便签背景颜色更新格式工具栏的背景 + */ + private void updateFormatToolbarBackground() { + if (mFormatToolbar != null) { + // 获取当前背景颜色资源ID + int bgColorResId = mWorkingNote.getBgColorResId(); + + // 根据背景颜色计算合适的工具栏背景色 + int toolbarBgColor = calculateToolbarBackgroundColor(bgColorResId); + + // 设置工具栏背景 + mFormatToolbar.setBackgroundColor(toolbarBgColor); + } + } + + /** + * 根据便签背景资源计算工具栏合适的背景颜色 + * @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; } /** @@ -592,6 +938,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, menu.findItem(R.id.menu_alert).setVisible(true); menu.findItem(R.id.menu_delete_remind).setVisible(false); } + + // 根据是否有位置提醒设置菜单项显示 + 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); + } + return true; } @@ -648,6 +1004,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 删除提醒 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; } @@ -669,6 +1034,135 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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 chooserIntent = Intent.createChooser(intent, "选择地图应用"); + if (chooserIntent.resolveActivity(getPackageManager()) != null) { + startActivityForResult(chooserIntent, REQUEST_CODE_SELECT_LOCATION); + } else { + 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米 + } + + // 设置位置提醒 + mWorkingNote.setAlertLocation(latitude, longitude, radius, locationName, true); + } catch (NumberFormatException e) { + Toast.makeText(NoteEditActivity.this, "请输入有效的位置信息", Toast.LENGTH_SHORT).show(); + } + } + }); + + // 取消按钮 + builder.setNegativeButton(android.R.string.cancel, null); + + // 显示对话框 + builder.show(); + } /** * 分享笔记内容到其他应用 */ @@ -715,7 +1209,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } else { // 同步模式:移动到垃圾箱 - if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLDER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); } } @@ -743,7 +1237,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 创建闹钟意图 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(); // 更新提醒显示 @@ -761,6 +1255,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 位置提醒改变回调(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(); + } + } else { + // 笔记为空,无法设置提醒 + Log.e(TAG, "Location alert setting error"); + showToast(R.string.error_note_empty_for_clock); + } + } + /** * 小部件改变回调(NoteSettingChangedListener接口方法) */ @@ -928,6 +1446,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (newMode == TextNote.MODE_CHECK_LIST) { // 切换到清单模式 switchToListMode(mNoteEditor.getText().toString()); + + // 隐藏富文本格式工具栏 + if (mFormatToolbar != null) { + mFormatToolbar.setVisibility(View.GONE); + } } else { // 切换到普通模式 if (!getWorkingText()) { @@ -938,6 +1461,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mEditTextList.setVisibility(View.GONE); mNoteEditor.setVisibility(View.VISIBLE); + + // 显示富文本格式工具栏 + if (mFormatToolbar != null) { + mFormatToolbar.setVisibility(View.VISIBLE); + } } } @@ -945,7 +1473,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, * 从UI获取当前编辑的文本内容 * @return 是否存在已完成的项 */ - private boolean getWorkingText() { + boolean getWorkingText() { boolean hasChecked = false; if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 清单模式:构建格式化的文本 @@ -966,8 +1494,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, } mWorkingNote.setWorkingText(sb.toString()); } else { - // 普通模式:直接获取编辑器文本 + // 普通模式:获取带格式的文本 mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + + // 保存格式化信息到工作文本 + String formatInfo = RichTextFormatUtils.serializeFormatInfo(mNoteEditor.getText()); + mWorkingNote.setRichTextFormat(formatInfo); + + // TODO: 保存图片信息到工作笔记 - 这需要更复杂的数据结构来存储图片位置和路径 + // 当前实现会在普通文本模式中保留图片,但在清单模式中会丢失图片 } return hasChecked; } @@ -1030,6 +1565,238 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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; + } + + 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 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(); + } + }); + + // 添加按钮到布局 + layout.addView(albumBtn); + layout.addView(systemBtn); + + builder.setView(layout); + builder.setNegativeButton("取消", null); + builder.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(); + } + } + }); + + // 创建狗图片 + 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); + + 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 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; + } + } + // 如果没有对应样式,则添加 + 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 (spans.length > 0) { + // 如果已存在下划线,则移除 + for (UnderlineSpan span : spans) { + str.removeSpan(span); + } + } else { + // 如果不存在下划线,则添加 + str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + /** + * 切换删除线 + */ + private void toggleStrikethroughSpan(Spannable str, int start, int end) { + StrikethroughSpan[] spans = str.getSpans(start, end, StrikethroughSpan.class); + if (spans.length > 0) { + // 如果已存在删除线,则移除 + for (StrikethroughSpan span : spans) { + str.removeSpan(span); + } + } else { + // 如果不存在删除线,则添加 + str.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + /** * 显示Toast提示 */ @@ -1040,4 +1807,45 @@ public class NoteEditActivity extends Activity implements OnClickListener, private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).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; // 消费此事件 + } + } + } + return false; + } + }); + } } \ No newline at end of file diff --git a/src/notes/ui/NoteEditText.java b/src/notes/ui/NoteEditText.java index 8122993..d27fdda 100644 --- a/src/notes/ui/NoteEditText.java +++ b/src/notes/ui/NoteEditText.java @@ -41,7 +41,7 @@ import java.util.Map; * 自定义编辑文本框,用于笔记应用的清单模式 * 支持特殊的删除和回车逻辑,以及链接处理功能 */ -public class NoteEditText extends EditText { +public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText { private static final String TAG = "NoteEditText"; private int mIndex; // 当前编辑框在清单列表中的索引位置 private int mSelectionStartBeforeDelete; // 记录删除操作前光标起始位置 @@ -64,6 +64,8 @@ public class NoteEditText extends EditText { * 由NoteEditActivity实现,用于处理清单模式下的特殊编辑逻辑 */ public interface OnTextViewChangeListener { + void onBackPressedDispatcher(); + /** * 当在行首按删除键且文本为空时,删除当前编辑框 * @param index 被删除的编辑框索引 diff --git a/src/notes/ui/NotesListActivity.java b/src/notes/ui/NotesListActivity.java index 56ecd10..2e62b01 100644 --- a/src/notes/ui/NotesListActivity.java +++ b/src/notes/ui/NotesListActivity.java @@ -599,7 +599,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } else { // 同步模式:将笔记移动到垃圾箱文件夹(而不是直接删除) if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter - .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { + .getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); } } @@ -643,7 +643,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt DataUtils.batchDeleteNotes(mContentResolver, ids); } else { // 同步模式:将文件夹移动到垃圾箱 - DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLDER); } // 更新关联的小部件 if (widgets != null) { @@ -730,7 +730,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt final AlertDialog.Builder builder = new AlertDialog.Builder(this); // 加载对话框布局 View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); - final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); + final EditText etName = (EditText) view.findViewById(R.id.et_folder_name); showSoftInput(); // 显示软键盘 // 根据是创建还是修改设置不同的初始文本和标题 if (!create) { @@ -1146,7 +1146,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt selection, new String[] { String.valueOf(Notes.TYPE_FOLDER), // 类型:文件夹 - String.valueOf(Notes.ID_TRASH_FOLER), // 排除垃圾箱 + String.valueOf(Notes.ID_TRASH_FOLDER), // 排除垃圾箱 String.valueOf(mCurrentFolderId) // 排除当前文件夹 }, NoteColumns.MODIFIED_DATE + " DESC"); // 按修改日期降序排序 diff --git a/src/notes/ui/NotesPreferenceActivity.java b/src/notes/ui/NotesPreferenceActivity.java index f67f37b..820fb69 100644 --- a/src/notes/ui/NotesPreferenceActivity.java +++ b/src/notes/ui/NotesPreferenceActivity.java @@ -27,6 +27,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.os.Build; import android.os.Bundle; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; @@ -42,6 +43,8 @@ import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; @@ -74,6 +77,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { /** * Activity创建时的初始化方法 */ + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -89,7 +93,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { mReceiver = new GTaskReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); + registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED); mOriAccounts = null; // 加载设置页面的自定义头部布局 diff --git a/src/notes/widget/NoteWidgetProvider.java b/src/notes/widget/NoteWidgetProvider.java index fa5c64a..5fbb34c 100644 --- a/src/notes/widget/NoteWidgetProvider.java +++ b/src/notes/widget/NoteWidgetProvider.java @@ -94,7 +94,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, // 查询的列 NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", // 查询条件 - new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLDER) }, null); // 排序方式(无) }