diff --git a/xiaomi-src/main/.scannerwork/.sonar_lock b/xiaomi-src/main/.scannerwork/.sonar_lock deleted file mode 100644 index e69de29..0000000 diff --git a/xiaomi-src/main/.scannerwork/report-task.txt b/xiaomi-src/main/.scannerwork/report-task.txt deleted file mode 100644 index fc77250..0000000 --- a/xiaomi-src/main/.scannerwork/report-task.txt +++ /dev/null @@ -1,6 +0,0 @@ -projectKey=myNotes -serverUrl=http://127.0.0.1:9000 -serverVersion=9.5.0.56709 -dashboardUrl=http://127.0.0.1:9000/dashboard?id=myNotes -ceTaskId=AYRIh5EzuqDImpFV-sUP -ceTaskUrl=http://127.0.0.1:9000/api/ce/task?id=AYRIh5EzuqDImpFV-sUP diff --git a/xiaomi-src/main/AndroidManifest.xml b/xiaomi-src/main/AndroidManifest.xml index ec543b6..61c77d9 100644 --- a/xiaomi-src/main/AndroidManifest.xml +++ b/xiaomi-src/main/AndroidManifest.xml @@ -1,71 +1,59 @@ - - - - + android:versionName="0.1"> + + - + - - - - - - + + + android:label="@string/app_name"> - - - - - - - + + + + + + + + + - + android:theme="@style/NoteTheme" + android:windowSoftInputMode="adjustPan"> - - + + android:theme="@style/NoteTheme"> - + + + + - + + + - + + - + - - + - + android:multiprocess="true" /> + android:exported="true" + android:label="@string/app_widget2x2"> @@ -152,11 +150,11 @@ - + - + android:exported="true" + android:label="@string/app_widget4x4"> @@ -166,40 +164,33 @@ - - - + + - - + - - + android:name=".ui.AlarmReceiver" + android:process=":remote" /> - - + android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" /> - - + android:theme="@android:style/Theme.Holo.Light" /> - - + android:name=".gtask.remote.GTaskSyncService" + android:exported="false" /> - + + \ No newline at end of file diff --git a/xiaomi-src/main/assets/font/FZYTK.TTF b/xiaomi-src/main/assets/font/FZYTK.TTF deleted file mode 100644 index 45c8c19..0000000 Binary files a/xiaomi-src/main/assets/font/FZYTK.TTF and /dev/null differ diff --git a/xiaomi-src/main/assets/font/STLITI.TTF b/xiaomi-src/main/assets/font/STLITI.TTF deleted file mode 100644 index 8ede20e..0000000 Binary files a/xiaomi-src/main/assets/font/STLITI.TTF and /dev/null differ diff --git a/xiaomi-src/main/assets/font/STXINGKA.TTF b/xiaomi-src/main/assets/font/STXINGKA.TTF deleted file mode 100644 index 53439a2..0000000 Binary files a/xiaomi-src/main/assets/font/STXINGKA.TTF and /dev/null differ diff --git a/xiaomi-src/main/assets/font/STXINWEI.TTF b/xiaomi-src/main/assets/font/STXINWEI.TTF deleted file mode 100644 index afe13f4..0000000 Binary files a/xiaomi-src/main/assets/font/STXINWEI.TTF and /dev/null differ diff --git a/xiaomi-src/main/assets/font/STZHONGS.TTF b/xiaomi-src/main/assets/font/STZHONGS.TTF deleted file mode 100644 index 412dfcd..0000000 Binary files a/xiaomi-src/main/assets/font/STZHONGS.TTF and /dev/null differ diff --git a/xiaomi-src/main/assets/font/simfang.ttf b/xiaomi-src/main/assets/font/simfang.ttf deleted file mode 100644 index 68334e2..0000000 Binary files a/xiaomi-src/main/assets/font/simfang.ttf and /dev/null differ diff --git a/xiaomi-src/main/assets/font/simhei.ttf b/xiaomi-src/main/assets/font/simhei.ttf deleted file mode 100644 index 5bd4687..0000000 Binary files a/xiaomi-src/main/assets/font/simhei.ttf and /dev/null differ diff --git a/xiaomi-src/main/assets/font/simkai.ttf b/xiaomi-src/main/assets/font/simkai.ttf deleted file mode 100644 index d7c3b90..0000000 Binary files a/xiaomi-src/main/assets/font/simkai.ttf and /dev/null differ diff --git a/xiaomi-src/main/java/net/micode/notes/data/Contact.java b/xiaomi-src/main/java/net/micode/notes/data/Contact.java index 4c1d6e2..45ad20d 100644 --- a/xiaomi-src/main/java/net/micode/notes/data/Contact.java +++ b/xiaomi-src/main/java/net/micode/notes/data/Contact.java @@ -1,17 +1,6 @@ /* - * 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. + * Contact 类用于通过电话号码查询联系人信息。 + * 该类实现了从联系人数据库中获取与特定电话号码相关联的显示名称。 */ package net.micode.notes.data; @@ -26,48 +15,64 @@ import android.util.Log; import java.util.HashMap; public class Contact { + // 缓存已查询过的电话号码和对应的联系人名称,以减少数据库查询次数。 private static HashMap sContactCache; - private static final String TAG = "Contact"; + private static final String TAG = "Contact"; // 日志标签 + // 用于查询具有完整国际号码格式的电话号码的selection字符串。 private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER - + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" - + " AND " + Data.RAW_CONTACT_ID + " IN " + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + "(SELECT raw_contact_id " + " FROM phone_lookup" + " WHERE min_match = '+')"; - private Contact(){} - + /** + * 根据电话号码获取联系人名称。 + * + * @param context 上下文对象,用于访问内容解析器。 + * @param phoneNumber 需要查询的电话号码。 + * @return 与电话号码相关联的联系人名称,如果找不到则返回null。 + */ public static String getContact(Context context, String phoneNumber) { - if(sContactCache == null) { - sContactCache = new HashMap<>(); + // 初始化或获取联系人缓存 + if (sContactCache == null) { + sContactCache = new HashMap(); } - if(sContactCache.containsKey(phoneNumber)) { + // 从缓存中直接获取联系人名称,如果存在。 + if (sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } + // 使用PhoneNumberUtils将电话号码格式化为适合查询的形式 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + + // 执行查询以获取与电话号码相关联的联系人名称 Cursor cursor = context.getContentResolver().query( Data.CONTENT_URI, - new String [] { Phone.DISPLAY_NAME }, + new String[]{Phone.DISPLAY_NAME}, selection, - new String[] { phoneNumber }, + new String[]{phoneNumber}, null); if (cursor != null && cursor.moveToFirst()) { try { + // 从查询结果中获取联系人名称并加入缓存 String name = cursor.getString(0); sContactCache.put(phoneNumber, name); return name; } catch (IndexOutOfBoundsException e) { + // 处理查询结果异常 Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { + // 关闭游标 cursor.close(); } } else { + // 如果查询无结果,记录日志 Log.d(TAG, "No contact matched with number:" + phoneNumber); return null; } diff --git a/xiaomi-src/main/java/net/micode/notes/data/Notes.java b/xiaomi-src/main/java/net/micode/notes/data/Notes.java index 17347a6..226763b 100644 --- a/xiaomi-src/main/java/net/micode/notes/data/Notes.java +++ b/xiaomi-src/main/java/net/micode/notes/data/Notes.java @@ -17,274 +17,257 @@ package net.micode.notes.data; import android.net.Uri; + +// Notes类定义了与笔记和文件夹相关的常量和数据列接口 public class Notes { - public static final String AUTHORITY = "micode_notes"; - public static final String TAG = "Notes"; - public static final String CONTENT_ = "content://"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; + public static final String AUTHORITY = "micode_notes"; // 用于标识内容提供者的授权名称 + public static final String TAG = "Notes"; // 日志标签 + public static final int TYPE_NOTE = 0; // 笔记类型 + public static final int TYPE_FOLDER = 1; // 文件夹类型 + public static final int TYPE_SYSTEM = 2; // 系统类型 /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + * 下面的ID是系统文件夹的标识符 + * {@link Notes#ID_ROOT_FOLDER} 是默认文件夹 + * {@link Notes#ID_TEMPARAY_FOLDER} 是属于没有文件夹的笔记 + * {@link Notes#ID_CALL_RECORD_FOLDER} 是用于存储通话记录的 */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; + public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID + public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID,用于存放不属于任何文件夹的笔记 + public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID + public static final int ID_TRASH_FOLER = -3; // 垃圾箱文件夹ID + + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 用于Intent的提醒日期额外数据 + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 笔记背景色ID + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型 + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期 + + public static final int TYPE_WIDGET_INVALIDE = -1; // 无效的小部件类型 + public static final int TYPE_WIDGET_2X = 0; // 2x小部件类型 + public static final int TYPE_WIDGET_4X = 1; // 4x小部件类型 public static class DataConstants { - private DataConstants(){} - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 笔记的内容项类型 + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话记录的内容项类型 } /** - * Uri to query all notes and folders + * 查询所有笔记和文件夹的Uri */ - public static final Uri CONTENT_NOTE_URI = Uri.parse(CONTENT_ + AUTHORITY + "/note"); + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); /** - * Uri to query data + * 查询数据的Uri */ - public static final Uri CONTENT_DATA_URI = Uri.parse(CONTENT_ + AUTHORITY + "/data"); + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + // 笔记和文件夹的公共列接口 public interface NoteColumns { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 行的唯一ID + *

类型: INTEGER (long)

*/ public static final String ID = "_id"; /** - * The parent's id for note or folder - *

Type: INTEGER (long)

+ * 笔记或文件夹的父ID + *

类型: INTEGER (long)

*/ public static final String PARENT_ID = "parent_id"; /** - * Created data for note or folder - *

Type: INTEGER (long)

+ * 创建日期 + *

类型: INTEGER (long)

*/ public static final String CREATED_DATE = "created_date"; /** - * Latest modified date - *

Type: INTEGER (long)

+ * 最后修改日期 + *

类型: INTEGER (long)

*/ public static final String MODIFIED_DATE = "modified_date"; - /** - * Alert date - *

Type: INTEGER (long)

+ * 提醒日期 + *

类型: INTEGER (long)

*/ public static final String ALERTED_DATE = "alert_date"; /** - * Folder's name or text content of note - *

Type: TEXT

+ * 笔记或文件夹的摘要信息 + *

类型: TEXT

*/ public static final String SNIPPET = "snippet"; /** - * Note's widget id - *

Type: INTEGER (long)

+ * 笔记的小部件ID + *

类型: INTEGER (long)

*/ public static final String WIDGET_ID = "widget_id"; /** - * Note's widget type - *

Type: INTEGER (long)

+ * 笔记的小部件类型 + *

类型: INTEGER (long)

*/ public static final String WIDGET_TYPE = "widget_type"; /** - * Note's background color's id - *

Type: INTEGER (long)

+ * 笔记的背景色ID + *

类型: INTEGER (long)

*/ public static final String BG_COLOR_ID = "bg_color_id"; /** - * For text note, it doesn't has attachment, for multi-media - * note, it has at least one attachment - *

Type: INTEGER

+ * 笔记是否有附件 + *

类型: INTEGER

*/ public static final String HAS_ATTACHMENT = "has_attachment"; /** - * Folder's count of notes - *

Type: INTEGER (long)

+ * 笔记数量 + *

类型: INTEGER (long)

*/ public static final String NOTES_COUNT = "notes_count"; /** - * The file type: folder or note - *

Type: INTEGER

+ * 文件夹类型:0-笔记,1-文件夹 + *

类型: INTEGER

*/ public static final String TYPE = "type"; /** - * The last sync id - *

Type: INTEGER (long)

+ * 最后同步ID + *

类型: INTEGER (long)

*/ public static final String SYNC_ID = "sync_id"; /** - * Sign to indicate local modified or not - *

Type: INTEGER

+ * 标记本地是否已修改 + *

类型: INTEGER

*/ public static final String LOCAL_MODIFIED = "local_modified"; /** - * Original parent id before moving into temporary folder - *

Type : INTEGER

+ * 移入临时文件夹前的原始父ID + *

类型: INTEGER

*/ public static final String ORIGIN_PARENT_ID = "origin_parent_id"; /** - * The gtask id - *

Type : TEXT

+ * Google任务ID + *

类型: TEXT

*/ public static final String GTASK_ID = "gtask_id"; /** - * The version code - *

Type : INTEGER (long)

+ * 版本号 + *

类型: INTEGER (long)

*/ public static final String VERSION = "version"; - - public static final String TOP = "top"; } + // 数据列接口 public interface DataColumns { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 行的唯一ID + *

类型: INTEGER (long)

*/ public static final String ID = "_id"; /** - * The MIME type of the item represented by this row. - *

Type: Text

+ * 该项的MIME类型。 + *

类型: TEXT

*/ public static final String MIME_TYPE = "mime_type"; /** - * The reference id to note that this data belongs to - *

Type: INTEGER (long)

+ * 属于的笔记的引用ID + *

类型: INTEGER (long)

*/ public static final String NOTE_ID = "note_id"; /** - * Created data for note or folder - *

Type: INTEGER (long)

+ * 创建日期 + *

类型: INTEGER (long)

*/ public static final String CREATED_DATE = "created_date"; /** - * Latest modified date - *

Type: INTEGER (long)

+ * 最后修改日期 + *

类型: INTEGER (long)

*/ public static final String MODIFIED_DATE = "modified_date"; /** - * Data's content - *

Type: TEXT

+ * 数据内容 + *

类型: TEXT

*/ public static final String CONTENT = "content"; - public static final String LOCATION = "location"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

+ * 通用数据列,具体含义由{@link #MIME_TYPE}决定,用于存储整型数据 + *

类型: INTEGER

*/ public static final String DATA1 = "data1"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

+ * 通用数据列,具体含义由{@link #MIME_TYPE}决定,用于存储整型数据 + *

类型: INTEGER

*/ public static final String DATA2 = "data2"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 通用数据列,具体含义由{@link #MIME_TYPE}决定,用于存储文本数据 + *

类型: TEXT

*/ public static final String DATA3 = "data3"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 通用数据列,具体含义由{@link #MIME_TYPE}决定,用于存储文本数据 + *

类型: TEXT

*/ public static final String DATA4 = "data4"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 通用数据列,具体含义由{@link #MIME_TYPE}决定,用于存储文本数据 + *

类型: TEXT

*/ public static final String DATA5 = "data5"; } + // 文本笔记类,实现了DataColumns接口 public static final class TextNote implements DataColumns { /** - * Mode to indicate the text in check list mode or not - *

Type: Integer 1:check list mode 0: normal mode

+ * 模式,指示文本是否在检查列表模式中 + *

类型: INTEGER 1:检查列表模式 0: 正常模式

*/ - private TextNote(){} - public static final String MODE = DATA1; public static final int MODE_CHECK_LIST = 1; - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse(CONTENT_ + AUTHORITY + "/text_note"); + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // MIME类型定义 + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; // 单项MIME类型定义 + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 内容URI定义 } + // 通话记录笔记类,实现了DataColumns接口 public static final class CallNote implements DataColumns { /** - * Call date for this record - *

Type: INTEGER (long)

+ * 通话日期 + *

类型: INTEGER (long)

*/ - - private CallNote(){} - public static final String CALL_DATE = DATA1; /** - * Phone number for this record - *

Type: TEXT

+ * 电话号码 + *

类型: TEXT

*/ public static final String PHONE_NUMBER = DATA3; - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - - public static final Uri CONTENT_URI = Uri.parse(CONTENT_ + AUTHORITY + "/call_note"); + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // MIME类型定义 + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 单项MIME类型定义 + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 内容URI定义 } } diff --git a/xiaomi-src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/xiaomi-src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index 5a3ae32..67ad506 100644 --- a/xiaomi-src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/xiaomi-src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -1,19 +1,6 @@ /* - * 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. + * 该类为Notes数据库的辅助类,负责管理数据库的创建和版本管理。 */ - package net.micode.notes.data; import android.content.ContentValues; @@ -28,190 +15,186 @@ 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 = 4; + // 表接口,定义了数据库中的两个表名 public interface TABLE { public static final String NOTE = "note"; public static final String DATA = "data"; } + // 日志标签 private static final String TAG = "NotesDatabaseHelper"; + // 单例模式,确保数据库辅助类的唯一实例 private static NotesDatabaseHelper mInstance; + // 创建NOTE表的SQL语句 private static final String CREATE_NOTE_TABLE_SQL = - "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT 1," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.TOP + " INTEGER NOT NULL DEFAULT 0" + - ")"; - + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ")"; + + // 创建DATA表的SQL语句 private static final String CREATE_DATA_TABLE_SQL = - "CREATE TABLE " + TABLE.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 ''," + - DataColumns.LOCATION + " TEXT NOT NULL DEFAULT ''" + - ")"; - + "CREATE TABLE " + TABLE.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 ''" + + ")"; + + // 创建DATA表的NOTE_ID索引的SQL语句 private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = - "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; - /** - * Increase folder's note count when move note to the folder - */ + // 当更新NOTE表中的PARENT_ID字段时,增加目标文件夹的NOTE_COUNT private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_update "+ - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; - - /** - * Decrease folder's note count when move note from folder - */ + "CREATE TRIGGER increase_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; + + // 当从文件夹移动NOTE时,减少源文件夹的NOTE_COUNT private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_update " + - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + - " END"; - - /** - * Increase folder's note count when insert new note to the folder - */ + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END"; + + // 当插入新NOTE时,增加目标文件夹的NOTE_COUNT private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_insert " + - " AFTER INSERT ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; - - /** - * Decrease folder's note count when delete note from the folder - */ + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; + + // 当删除NOTE时,减少文件夹的NOTE_COUNT private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0;" + - " END"; - - /** - * Update note's content when insert data with type {@link DataConstants#NOTE} - */ + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END"; + + // 当插入DATA时,如果类型为NOTE,则更新关联NOTE的内容 private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = - "CREATE TRIGGER update_note_content_on_insert " + - " AFTER INSERT ON " + TABLE.DATA + - " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed - */ + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; + + // 当更新DATA时,如果类型为NOTE,则更新关联NOTE的内容 private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER update_note_content_on_update " + - " AFTER UPDATE ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted - */ + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; + + // 当删除DATA时,如果类型为NOTE,则更新关联NOTE的内容为空 private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = - "CREATE TRIGGER update_note_content_on_delete " + - " AFTER delete ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + - " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Delete datas belong to note which has been deleted - */ + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; + + // 当删除NOTE时,删除关联的DATA private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = - "CREATE TRIGGER delete_data_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.DATA + - " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - /** - * Delete notes belong to folder which has been deleted - */ + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + // 当删除NOTE时,删除属于该NOTE的子NOTE private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = - "CREATE TRIGGER folder_delete_notes_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.NOTE + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + // 当NOTE移动到回收站文件夹时,将所有子NOTE也移动到回收站 + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; /** - * Move notes belong to folder which has been moved to trash folder + * 构造函数,私有化以防止外部实例化 + * + * @param context 上下文对象,用于访问应用的资源和其他组件 */ - private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = - "CREATE TRIGGER folder_move_notes_on_trash " + - " AFTER UPDATE ON " + TABLE.NOTE + - " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; - public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建NOTE表,并重新创建NOTE表的触发器,然后创建系统文件夹 + * + * @param db SQLiteDatabase对象,用于执行SQL创建语句 + */ public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); @@ -219,7 +202,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "note table has been created"); } + /** + * 重新创建笔记表的触发器 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { + // 删除旧的触发器 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); @@ -227,7 +216,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); - + // 创建新的触发器 db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); @@ -237,41 +226,39 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } + /** + * 创建系统文件夹 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - - /** - * call record foler for call notes - */ + // 创建通话记录文件夹 values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - - /** - * root folder which is default folder - */ + // 创建根文件夹(默认文件夹) values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - - /** - * temporary folder which is used for moving note - */ + // 创建临时文件夹,用于移动笔记 values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - - /** - * create trash folder - */ + // 创建回收站文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 创建数据表 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); reCreateDataTableTriggers(db); @@ -279,16 +266,28 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "data table has been created"); } + /** + * 重新创建数据表的触发器 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ 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"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); - + // 创建新的触发器 db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } + /** + * 获取 NotesDatabaseHelper 的单例对象 + * + * @param context Context 类型,应用上下文 + * @return NotesDatabaseHelper 类型,单例对象 + */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -296,89 +295,94 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } + /** + * 创建数据库表 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } + /** + * 升级数据库 + * + * @param db SQLiteDatabase 类型,数据库对象 + * @param oldVersion int 类型,旧版本号 + * @param newVersion int 类型,新版本号 + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; - + // 根据旧版本号逐步升级 if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 + skipV2 = true; // 这次升级包括从 v2 到 v3 的升级 oldVersion++; } - if (oldVersion == 2 && !skipV2) { upgradeToV3(db); reCreateTriggers = true; oldVersion++; } - if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } - - if (oldVersion == 4) { - upgradeToV5(db); - oldVersion++; - } - - if (oldVersion == 5) { - upgradeToV6(db); - oldVersion++; - } - if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } - if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } + /** + * 从版本1升级到版本2 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ private void upgradeToV2(SQLiteDatabase db) { + // 删除旧表,创建新表 db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); createNoteTable(db); createDataTable(db); } + /** + * 从版本2升级到版本3 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers + // 删除未使用的触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id + // 添加一个用于 gtask id 的列 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder + // 添加一个回收站系统文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 从版本3升级到版本4 + * + * @param db SQLiteDatabase 类型,数据库对象 + */ private void upgradeToV4(SQLiteDatabase db) { + // 添加版本号列 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } - - private void upgradeToV5(SQLiteDatabase db) { - db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TOP - + " INTEGER NOT NULL DEFAULT 0"); - } - - private void upgradeToV6(SQLiteDatabase db) { - db.execSQL("ALTER TABLE " + TABLE.DATA + " ADD COLUMN " + DataColumns.LOCATION - + "TEXT NOT NULL DEFAULT ''"); - } -} +} \ No newline at end of file diff --git a/xiaomi-src/main/java/net/micode/notes/data/NotesProvider.java b/xiaomi-src/main/java/net/micode/notes/data/NotesProvider.java index 1cc63c6..c38d7b3 100644 --- a/xiaomi-src/main/java/net/micode/notes/data/NotesProvider.java +++ b/xiaomi-src/main/java/net/micode/notes/data/NotesProvider.java @@ -1,22 +1,9 @@ /* - * 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. + * 该类是Notes应用的内容提供者,负责管理Notes的数据,包括查询、插入、更新和删除操作。 + * 它与数据库交互,将操作转换为对数据库的相应操作。 */ - package net.micode.notes.data; - import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentUris; @@ -33,29 +20,24 @@ import net.micode.notes.R; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; -import net.micode.notes.gtask.data.SqlNote; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Iterator; public class NotesProvider extends ContentProvider { private static final UriMatcher mMatcher; - private static NotesDatabaseHelper mHelper; + private NotesDatabaseHelper mHelper; private static final String TAG = "NotesProvider"; - private static final int URI_NOTE = 1; - private static final int URI_NOTE_ITEM = 2; - private static final int URI_DATA = 3; - private static final int URI_DATA_ITEM = 4; + private static final int URI_NOTE = 1; + private static final int URI_NOTE_ITEM = 2; + private static final int URI_DATA = 3; + private static final int URI_DATA_ITEM = 4; - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; + private static final int URI_SEARCH = 5; + private static final int URI_SEARCH_SUGGEST = 6; + // 初始化UriMatcher,用于匹配不同的URI请求 static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); @@ -68,35 +50,51 @@ public class NotesProvider extends ContentProvider { } /** - * x'0A' represents the '\n' character in sqlite. For title and content in the search result, - * we will trim '\n' and white space in order to show more information. + * 在搜索结果中,为了显示更多信息,我们会去除标题和内容中的'\n'和空白字符。 */ private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," - + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," - + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," - + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," - + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + + // 用于搜索查询的SQL语句 private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION - + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + /** + * 当ContentProvider被创建时调用,用于初始化数据库帮助类。 + * + * @return 总是返回true。 + */ @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } + /** + * 根据URI查询数据。 + * + * @param uri 要查询的数据的URI。 + * @param projection 要返回的列。 + * @param selection 查询条件。 + * @param selectionArgs 用于查询条件的参数。 + * @param sortOrder 排序顺序。 + * @return 返回匹配的Cursor对象。 + */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + String sortOrder) { Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; + // 根据URI匹配查询类型 switch (mMatcher.match(uri)) { case URI_NOTE: c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, @@ -118,6 +116,7 @@ public class NotesProvider extends ContentProvider { break; case URI_SEARCH: case URI_SEARCH_SUGGEST: + // 处理搜索建议的特殊逻辑 if (sortOrder != null || projection != null) { throw new IllegalArgumentException( "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); @@ -139,7 +138,7 @@ public class NotesProvider extends ContentProvider { try { searchString = String.format("%%%s%%", searchString); c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, - new String[] { searchString }); + new String[]{searchString}); } catch (IllegalStateException ex) { Log.e(TAG, "got exception: " + ex.toString()); } @@ -147,12 +146,20 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + // 设置通知URI,以便数据改变时可以通知 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } + /** + * 在数据库中插入新数据。 + * + * @param uri 插入数据的URI。 + * @param values 要插入的数据。 + * @return 返回插入数据的URI。 + */ @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = mHelper.getWritableDatabase(); @@ -172,13 +179,12 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + // 通知URI改变 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - // Notify the data uri if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); @@ -187,6 +193,14 @@ public class NotesProvider extends ContentProvider { return ContentUris.withAppendedId(uri, insertedId); } + /** + * 根据URI删除数据。 + * + * @param uri 要删除数据的URI。 + * @param selection 删除条件。 + * @param selectionArgs 用于删除条件的参数。 + * @return 返回被删除的行数。 + */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; @@ -201,8 +215,7 @@ public class NotesProvider extends ContentProvider { case URI_NOTE_ITEM: id = uri.getPathSegments().get(1); /** - * ID that smaller than 0 is system folder which is not allowed to - * trash + * ID小于等于0的笔记是系统文件夹,不允许删除 */ long noteId = Long.valueOf(id); if (noteId <= 0) { @@ -224,6 +237,7 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + // 通知URI改变 if (count > 0) { if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -233,6 +247,15 @@ public class NotesProvider extends ContentProvider { return count; } + /** + * 更新数据库中的数据。 + * + * @param uri 要更新数据的URI。 + * @param values 要更新到的数据。 + * @param selection 更新条件。 + * @param selectionArgs 用于更新条件的参数。 + * @return 返回被更新的行数。 + */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; @@ -264,6 +287,7 @@ public class NotesProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URI " + uri); } + // 通知URI改变 if (count > 0) { if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -273,10 +297,24 @@ public class NotesProvider extends ContentProvider { return count; } + + /** + * 解析选择条件,如果存在选择条件,则在条件前后添加" AND (" 和 ')'。 + * + * @param selection 用户提供的选择条件。 + * @return 如果有选择条件,则返回添加了定界符的选择条件字符串;否则返回空字符串。 + */ private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } + /** + * 增加指定笔记的版本号。 + * + * @param id 笔记的ID,如果为正数,则根据ID更新版本号;如果为0或负数,则根据selection参数更新版本号。 + * @param selection 用于选择需要更新的笔记的条件字符串,可以为空。 + * @param selectionArgs 与selection参数配合使用的参数数组,用于替换selection中的"?"。 + */ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -285,6 +323,7 @@ public class NotesProvider extends ContentProvider { sql.append(NoteColumns.VERSION); sql.append("=" + NoteColumns.VERSION + "+1 "); + // 根据ID或选择条件构建SQL的WHERE子句 if (id > 0 || !TextUtils.isEmpty(selection)) { sql.append(" WHERE "); } @@ -293,6 +332,7 @@ public class NotesProvider extends ContentProvider { } if (!TextUtils.isEmpty(selection)) { String selectString = id > 0 ? parseSelection(selection) : selection; + // 替换selection中的"?"为selectionArgs中的对应参数 for (String args : selectionArgs) { selectString = selectString.replaceFirst("\\?", args); } @@ -302,153 +342,17 @@ public class NotesProvider extends ContentProvider { mHelper.getWritableDatabase().execSQL(sql.toString()); } + /** + * 根据URI获取对应的MIME类型。 + * 本方法是个待实现的方法,当前仅返回null。 + * + * @param uri 请求的URI。 + * @return 返回null,表示该方法尚未实现。 + */ @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } - - - - public static JSONObject uploadnote()throws JSONException { - JSONObject js = new JSONObject(); - SQLiteDatabase db; - db = mHelper.getReadableDatabase(); - Cursor cursor = null; - cursor = db.query(NotesDatabaseHelper.TABLE.NOTE, - null, null, null, null, null, null); - //判断游标是否为空 - if (cursor.moveToFirst()) { - for (int index = 1; index <= cursor.getCount(); index++) { - JSONObject note = new JSONObject(); - note.put(NoteColumns.PARENT_ID,cursor.getString(1)); - note.put(NoteColumns.ALERTED_DATE,cursor.getString(2)); - note.put(NoteColumns.BG_COLOR_ID,cursor.getString(3)); - note.put(NoteColumns.CREATED_DATE,cursor.getString(4)); - note.put(NoteColumns.HAS_ATTACHMENT,cursor.getString(5)); - note.put(NoteColumns.MODIFIED_DATE,cursor.getString(6)); - note.put(NoteColumns.NOTES_COUNT,cursor.getString(7)); - note.put(NoteColumns.SNIPPET,cursor.getString(8)); - note.put(NoteColumns.TYPE,cursor.getString(9)); - note.put(NoteColumns.WIDGET_ID,cursor.getString(10)); - note.put(NoteColumns.WIDGET_TYPE,cursor.getString(11)); - note.put(NoteColumns.SYNC_ID,cursor.getString(12)); - note.put(NoteColumns.LOCAL_MODIFIED,cursor.getString(13)); - note.put(NoteColumns.ORIGIN_PARENT_ID,cursor.getString(14)); - note.put(NoteColumns.GTASK_ID,cursor.getString(15)); - note.put(NoteColumns.VERSION,cursor.getString(16)); - note.put(NoteColumns.TOP,cursor.getString(17)); - js.put(cursor.getString(0),note); - if(!cursor.isLast()){ - cursor.moveToNext(); - } - } - } - System.out.println(js); - return js; - } - public static JSONObject uploaddata()throws JSONException { - JSONObject js = new JSONObject(); - SQLiteDatabase db; - db = mHelper.getReadableDatabase(); - Cursor cursor = null; - cursor = db.query(NotesDatabaseHelper.TABLE.DATA, - null, null, null, null, null, null); - //判断游标是否为空 - if (cursor.moveToFirst()) { - for (int index = 1; index <= cursor.getCount(); index++) { - - JSONObject data = new JSONObject(); - data.put(DataColumns.MIME_TYPE,cursor.getString(1)); - data.put(DataColumns.NOTE_ID,cursor.getString(2)); - data.put(DataColumns.CREATED_DATE,cursor.getString(3)); - data.put(DataColumns.MODIFIED_DATE,cursor.getString(4)); - data.put(DataColumns.CONTENT,cursor.getString(5)); - data.put(DataColumns.DATA1,cursor.getString(6)); - data.put(DataColumns.DATA2,cursor.getString(7)); - data.put(DataColumns.DATA3,cursor.getString(8)); - data.put(DataColumns.DATA4,cursor.getString(9)); - data.put(DataColumns.DATA5,cursor.getString(10)); - js.put(cursor.getString(0),data); - if(!cursor.isLast()){ - cursor.moveToNext(); - } - } - } - System.out.println(js); - return js; - } - - public static void syncTABLE(JSONObject JSONInfo) throws JSONException { - SQLiteDatabase db; - db = mHelper.getReadableDatabase(); - String sql; - JSONObject NOTE = null; - JSONObject DATA = null; - try { - NOTE = (JSONObject) JSONInfo.get("NOTE"); - DATA = (JSONObject) JSONInfo.get("DATA"); - } catch (Exception e) { - e.printStackTrace(); - } - - sql = "DELETE FROM" + " " + TABLE.NOTE; - db.execSQL(sql); - - assert NOTE != null; - Iterator NOTEKeys = NOTE.keys(); - while (NOTEKeys.hasNext()){ - String NOTEKey = NOTEKeys.next(); - JSONObject NOTEInfo = (JSONObject) NOTE.get(NOTEKey); - sql = "INSERT INTO note(_id,parent_id,alert_date,bg_color_id,created_date,has_attachment,modified_date," + - "notes_count,snippet,type,widget_id,widget_type,sync_id,local_modified,origin_parent_id,gtask_id," + - "version,top,star,passcode) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - Object bindArgs[] = new Object[] { - NOTEKey, - NOTEInfo.getString("parent_id"), - NOTEInfo.getString("alert_date"), - NOTEInfo.getString("bg_color_id"), - NOTEInfo.getString("created_date"), - NOTEInfo.getString("has_attachment"), - NOTEInfo.getString("modified_date"), - "0",//NOTEInfo.getString("notes_count") - "0", - NOTEInfo.getString("snippet"), - NOTEInfo.getString("type"), - NOTEInfo.getString("widget_id"), - NOTEInfo.getString("widget_type"), - NOTEInfo.getString("sync_id"), - NOTEInfo.getString("local_modified"), - NOTEInfo.getString("origin_parent_id"), - NOTEInfo.getString("gtask_id"), - NOTEInfo.getString("version"), - NOTEInfo.getString("top"), - NOTEInfo.getString("star"), - NOTEInfo.getString("passcode") - }; - db.execSQL(sql,bindArgs); - } - - assert DATA != null; - Iterator DATAKeys = DATA.keys(); - while (DATAKeys.hasNext()){ - String DATAKey = DATAKeys.next(); - JSONObject DATAInfo = (JSONObject) DATA.get(DATAKey); - sql = "INSERT INTO data(_id,mime_type,note_id,created_date,modified_date,content,data3,data4,data5)" + - "values (?,?,?,?,?,?,?,?,?)"; - Object bindArgs[] = new Object[] { - DATAKey, - DATAInfo.getString("mime_type"), - DATAInfo.getString("note_id"), - DATAInfo.getString("created_date"), - DATAInfo.getString("modified_date"), - DATAInfo.getString("content"), - DATAInfo.getString("data3"), - DATAInfo.getString("data4"), - DATAInfo.getString("data5") - }; - db.execSQL(sql,bindArgs); - } - } } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/data/MetaData.java b/xiaomi-src/main/java/net/micode/notes/gtask/data/MetaData.java index 3a2050b..7bbf671 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -1,19 +1,6 @@ -/* - * 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. +/** + * MetaData类,继承自Task类,用于处理与任务相关的元数据。 */ - package net.micode.notes.gtask.data; import android.database.Cursor; @@ -24,56 +11,92 @@ import net.micode.notes.tool.GTaskStringUtils; import org.json.JSONException; import org.json.JSONObject; - public class MetaData extends Task { - private final static String TAG = MetaData.class.getSimpleName(); + private final static String TAG = MetaData.class.getSimpleName(); // 日志标签 - private String mRelatedGid = null; + private String mRelatedGid = null; // 与任务相关的全局ID + /** + * 设置元数据。 + * + * @param gid 任务的全局ID。 + * @param metaInfo 元信息的JSON对象。 + */ public void setMeta(String gid, JSONObject metaInfo) { try { - metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); // 将任务的全局ID添加到元信息中 } catch (JSONException e) { Log.e(TAG, "failed to put related gid"); } - setNotes(metaInfo.toString()); - setName(GTaskStringUtils.META_NOTE_NAME); + setNotes(metaInfo.toString()); // 将元信息设置为任务的笔记 + setName(GTaskStringUtils.META_NOTE_NAME); // 设置任务的名称为特定的元数据标志名称 } + /** + * 获取与任务相关的全局ID。 + * + * @return 相关的全局ID字符串。 + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断任务是否值得保存。 + * + * @return 如果任务的笔记字段不为空,则返回true,表示值得保存。 + */ @Override public boolean isWorthSaving() { return getNotes() != null; } + /** + * 通过远程JSON对象设置内容。 + * + * @param js JSON对象,包含远程任务的内容。 + */ @Override public void setContentByRemoteJSON(JSONObject js) { super.setContentByRemoteJSON(js); if (getNotes() != null) { try { JSONObject metaInfo = new JSONObject(getNotes().trim()); - mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); // 从笔记中提取相关的全局ID } catch (JSONException e) { Log.w(TAG, "failed to get related gid"); - mRelatedGid = null; + mRelatedGid = null; // 提取失败时,设置相关ID为null } } } + /** + * 通过本地JSON对象设置内容。此方法不应被调用。 + * + * @param js 本地JSON对象。 + */ @Override public void setContentByLocalJSON(JSONObject js) { // this function should not be called throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } + /** + * 从内容生成本地JSON对象。此方法不应被调用。 + * + * @return 生成的JSON对象。 + */ @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 获取同步操作类型。此方法不应被调用。 + * + * @param c 数据库游标,指向当前任务。 + * @return 同步操作的类型。 + */ @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/data/Node.java b/xiaomi-src/main/java/net/micode/notes/gtask/data/Node.java index 63950e0..171da32 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/data/Node.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/data/Node.java @@ -1,17 +1,6 @@ /* - * 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. + * Node类定义了一个节点的基本属性和操作,用于数据同步时的表现和转换。 + * 它是用于表示通用数据节点的抽象类,具体的数据操作和格式转换由其子类实现。 */ package net.micode.notes.gtask.data; @@ -20,33 +9,24 @@ import android.database.Cursor; import org.json.JSONObject; +// 定义节点同步动作的常量 public abstract class Node { - public static final int SYNC_ACTION_NONE = 0; - - public static final int SYNC_ACTION_ADD_REMOTE = 1; - - public static final int SYNC_ACTION_ADD_LOCAL = 2; - - public static final int SYNC_ACTION_DEL_REMOTE = 3; - - public static final int SYNC_ACTION_DEL_LOCAL = 4; - - public static final int SYNC_ACTION_UPDATE_REMOTE = 5; - - public static final int SYNC_ACTION_UPDATE_LOCAL = 6; - - public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; - - public static final int SYNC_ACTION_ERROR = 8; - - private String mGid; - - private String mName; - - private long mLastModified; - - private boolean mDeleted; - + public static final int SYNC_ACTION_NONE = 0; // 无动作 + public static final int SYNC_ACTION_ADD_REMOTE = 1; // 添加远程节点 + public static final int SYNC_ACTION_ADD_LOCAL = 2; // 添加本地节点 + public static final int SYNC_ACTION_DEL_REMOTE = 3; // 删除远程节点 + public static final int SYNC_ACTION_DEL_LOCAL = 4; // 删除本地节点 + public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 更新远程节点 + public static final int SYNC_ACTION_UPDATE_LOCAL = 6; // 更新本地节点 + public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 更新冲突 + public static final int SYNC_ACTION_ERROR = 8; // 同步错误 + + private String mGid; // 全局唯一标识符 + private String mName; // 节点名称 + private long mLastModified; // 最后修改时间 + private boolean mDeleted; // 节点是否被删除的标志 + + // 构造函数,初始化节点属性 public Node() { mGid = null; mName = ""; @@ -54,46 +34,60 @@ public abstract class Node { mDeleted = false; } + // 生成创建节点的JSON动作 public abstract JSONObject getCreateAction(int actionId); + // 生成更新节点的JSON动作 public abstract JSONObject getUpdateAction(int actionId); + // 根据远程JSON内容设置节点内容 public abstract void setContentByRemoteJSON(JSONObject js); + // 根据本地JSON内容设置节点内容 public abstract void setContentByLocalJSON(JSONObject js); + // 从内容生成本地JSON表示 public abstract JSONObject getLocalJSONFromContent(); + // 根据Cursor获取同步动作 public abstract int getSyncAction(Cursor c); + // 设置节点的全局唯一标识符 public void setGid(String gid) { this.mGid = gid; } + // 设置节点名称 public void setName(String name) { this.mName = name; } + // 设置节点最后修改时间 public void setLastModified(long lastModified) { this.mLastModified = lastModified; } + // 设置节点是否被删除 public void setDeleted(boolean deleted) { this.mDeleted = deleted; } + // 获取节点的全局唯一标识符 public String getGid() { return this.mGid; } + // 获取节点名称 public String getName() { return this.mName; } + // 获取节点最后修改时间 public long getLastModified() { return this.mLastModified; } + // 获取节点是否被删除的标志 public boolean getDeleted() { return this.mDeleted; } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlData.java b/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlData.java index d3ec3be..7381072 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -1,19 +1,7 @@ /* - * 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. + * SqlData 类用于操作和管理数据库中的数据项。 + * 提供了从 JSON 对象设置内容,从数据库 Cursor 加载数据,以及提交数据更新到数据库的功能。 */ - package net.micode.notes.gtask.data; import android.content.ContentResolver; @@ -36,41 +24,53 @@ import org.json.JSONObject; public class SqlData { + // 日志标签 private static final String TAG = SqlData.class.getSimpleName(); + // 无效ID常量 private static final int INVALID_ID = -99999; - public static final String[] PROJECTION_DATA = new String[] { + // 查询时使用的字段投影 + public static final String[] PROJECTION_DATA = new String[]{ DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; + // 字段在Cursor中的索引 public static final int DATA_ID_COLUMN = 0; - public static final int DATA_MIME_TYPE_COLUMN = 1; - public static final int DATA_CONTENT_COLUMN = 2; - public static final int DATA_CONTENT_DATA_1_COLUMN = 3; - public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + // ContentResolver用于操作内容提供者 private ContentResolver mContentResolver; + // 标记当前对象是创建状态还是更新状态 private boolean mIsCreate; + // 数据项ID private long mDataId; + // 数据项的MIME类型 private String mDataMimeType; + // 数据项的内容 private String mDataContent; + // 数据项的附加数据1 private long mDataContentData1; + // 数据项的附加数据3 private String mDataContentData3; + // 存储与数据库不同步的数据变化 private ContentValues mDiffDataValues; + /* + * SqlData 构造函数,用于创建新的数据项。 + * @param context 上下文对象,用于获取ContentResolver。 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -82,6 +82,11 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /* + * SqlData 构造函数,用于加载现有数据项。 + * @param context 上下文对象,用于获取ContentResolver。 + * @param c 数据项的Cursor对象,用于加载数据。 + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; @@ -89,6 +94,10 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /* + * 从Cursor中加载数据。 + * @param c 数据项的Cursor对象。 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -97,6 +106,11 @@ public class SqlData { mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } + /* + * 根据JSON对象设置数据项内容。 + * @param js JSON对象,包含数据项的内容。 + * @throws JSONException 如果解析JSON时出错。 + */ public void setContent(JSONObject js) throws JSONException { long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { @@ -130,6 +144,11 @@ public class SqlData { mDataContentData3 = dataContentData3; } + /* + * 获取数据项的内容,转换为JSON对象。 + * @return JSON对象,包含数据项的内容。 + * @throws JSONException 如果构建JSON对象时出错。 + */ public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); @@ -144,9 +163,16 @@ public class SqlData { return js; } + /* + * 将数据项提交到数据库,如果是新数据项则插入,否则更新。 + * @param noteId 符合此数据项的笔记ID。 + * @param validateVersion 是否验证版本号。 + * @param version 数据项的版本号。 + */ public void commit(long noteId, boolean validateVersion, long version) { if (mIsCreate) { + // 处理新数据项的插入 if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { mDiffDataValues.remove(DataColumns.ID); } @@ -160,16 +186,19 @@ public class SqlData { throw new ActionFailureException("create note failed"); } } else { + // 处理现有数据项的更新 if (mDiffDataValues.size() > 0) { int result = 0; if (!validateVersion) { + // 不验证版本号时直接更新 result = mContentResolver.update(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); } else { + // 验证版本号时条件更新 result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { + + " WHERE " + NoteColumns.VERSION + "=?)", new String[]{ String.valueOf(noteId), String.valueOf(version) }); } @@ -179,11 +208,17 @@ public class SqlData { } } + // 清理并重置状态 mDiffDataValues.clear(); mIsCreate = false; } + /* + * 获取数据项的ID。 + * @return 数据项的ID。 + */ public long getId() { return mDataId; } } + diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlNote.java b/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlNote.java index 1dd6ad8..25b2796 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -38,98 +38,84 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * SqlNote 类用于管理和操作数据库中的笔记数据。 + * 它提供了创建、加载和更新笔记内容的接口。 + */ public class SqlNote { + // 日志标签 private static final String TAG = SqlNote.class.getSimpleName(); + // 无效的ID值 private static final int INVALID_ID = -99999; - public static final String[] PROJECTION_NOTE = new String[] { + // 查询笔记时要选择的列 + public static final String[] PROJECTION_NOTE = new String[]{ NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, - NoteColumns.VERSION,NoteColumns.TOP, + NoteColumns.VERSION }; + // 各查询列的索引 public static final int ID_COLUMN = 0; - public static final int ALERTED_DATE_COLUMN = 1; - public static final int BG_COLOR_ID_COLUMN = 2; - public static final int CREATED_DATE_COLUMN = 3; - public static final int HAS_ATTACHMENT_COLUMN = 4; - public static final int MODIFIED_DATE_COLUMN = 5; - public static final int NOTES_COUNT_COLUMN = 6; - public static final int PARENT_ID_COLUMN = 7; - public static final int SNIPPET_COLUMN = 8; - public static final int TYPE_COLUMN = 9; - public static final int WIDGET_ID_COLUMN = 10; - public static final int WIDGET_TYPE_COLUMN = 11; - public static final int SYNC_ID_COLUMN = 12; - public static final int LOCAL_MODIFIED_COLUMN = 13; - public static final int ORIGIN_PARENT_ID_COLUMN = 14; - public static final int GTASK_ID_COLUMN = 15; - public static final int VERSION_COLUMN = 16; - public static final int Top_COLUMN = 17; - + // 上下文和内容解析器,用于访问数据库 private Context mContext; - private ContentResolver mContentResolver; + // 标记是否创建新笔记 private boolean mIsCreate; + // 笔记的各种属性 private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private int mHasAttachment; - private long mModifiedDate; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private long mOriginParent; - private long mVersion; - private String mTop; - + // 用于存储两次更新之间差异的数据值 private ContentValues mDiffNoteValues; + // 存储与笔记相关数据的列表 private ArrayList mDataList; + /** + * 构造函数,初始化一个新的SqlNote实例。 + * + * @param context 上下文,通常是指Activity或Application对象。 + */ public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = true; + // 初始化笔记属性为默认值 mId = INVALID_ID; mAlertDate = 0; mBgColorId = ResourceParser.getDefaultBgId(context); @@ -143,11 +129,16 @@ public class SqlNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; mOriginParent = 0; mVersion = 0; - mTop = getmTop(); mDiffNoteValues = new ContentValues(); mDataList = new ArrayList(); } + /** + * 构造函数,从数据库中加载指定ID的笔记。 + * + * @param context 上下文,通常是指Activity或Application对象。 + * @param c 数据库查询结果的Cursor对象。 + */ public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); @@ -157,9 +148,14 @@ public class SqlNote { if (mType == Notes.TYPE_NOTE) loadDataContent(); mDiffNoteValues = new ContentValues(); - mTop = getmTop(); } + /** + * 构造函数,从数据库中加载指定ID的笔记。 + * + * @param context 上下文,通常是指Activity或Application对象。 + * @param id 要加载的笔记的ID。 + */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); @@ -169,26 +165,22 @@ public class SqlNote { if (mType == Notes.TYPE_NOTE) loadDataContent(); mDiffNoteValues = new ContentValues(); - mTop = getmTop(); - - } - - public String getmTop(){ - return mTop; } + // 从数据库中加载笔记数据 private void loadFromCursor(long id) { Cursor c = null; try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(id) + new String[]{ + String.valueOf(id) }, null); if (c != null) { - c.moveToNext(); - loadFromCursor(c); - } else { - Log.w(TAG, "loadFromCursor: cursor = null"); + if (c.moveToNext()) { + loadFromCursor(c); + } else { + Log.w(TAG, "loadFromCursor: cursor = null"); + } } } finally { if (c != null) @@ -196,6 +188,7 @@ public class SqlNote { } } + // 从Cursor中加载笔记数据到实例属性 private void loadFromCursor(Cursor c) { mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); @@ -209,42 +202,59 @@ public class SqlNote { mWidgetId = c.getInt(WIDGET_ID_COLUMN); mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); mVersion = c.getLong(VERSION_COLUMN); - mTop = c.getString(Top_COLUMN); } + /** + * 加载数据内容。 + * 从数据库中查询特定note_id的数据,并将其加载到mDataList中。 + */ private void loadDataContent() { Cursor c = null; mDataList.clear(); try { + // 查询指定note_id的数据 c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, - "(note_id=?)", new String[] { - String.valueOf(mId) + "(note_id=?)", new String[]{ + String.valueOf(mId) }, null); if (c != null) { + // 如果查询结果为空,打印警告信息并返回 if (c.getCount() == 0) { Log.w(TAG, "it seems that the note has not data"); return; } + // 遍历查询结果,并加载到mDataList中 while (c.moveToNext()) { SqlData data = new SqlData(mContext, c); mDataList.add(data); } } else { + // 如果查询结果为null,打印警告信息 Log.w(TAG, "loadDataContent: cursor = null"); } } finally { + // 释放资源 if (c != null) c.close(); } } + /** + * 设置内容。 + * 根据传入的JSONObject,更新或创建笔记的相关内容。 + * + * @param js 包含笔记信息的JSONObject。 + * @return 成功返回true,失败返回false。 + */ public boolean setContent(JSONObject js) { try { + // 从js中获取note信息 JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 系统笔记不可修改 if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { Log.w(TAG, "cannot set system folder"); } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type + // 文件夹类型笔记,仅更新snippet和类型 String snippet = note.has(NoteColumns.SNIPPET) ? note .getString(NoteColumns.SNIPPET) : ""; if (mIsCreate || !mSnippet.equals(snippet)) { @@ -259,6 +269,7 @@ public class SqlNote { } mType = type; } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + // 笔记类型,更新或设置多种信息 JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; if (mIsCreate || mId != id) { @@ -266,83 +277,10 @@ public class SqlNote { } mId = id; - long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note - .getLong(NoteColumns.ALERTED_DATE) : 0; - if (mIsCreate || mAlertDate != alertDate) { - mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); - } - mAlertDate = alertDate; - - int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note - .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); - if (mIsCreate || mBgColorId != bgColorId) { - mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); - } - mBgColorId = bgColorId; - - long createDate = note.has(NoteColumns.CREATED_DATE) ? note - .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mCreatedDate != createDate) { - mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); - } - mCreatedDate = createDate; - - int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note - .getInt(NoteColumns.HAS_ATTACHMENT) : 0; - if (mIsCreate || mHasAttachment != hasAttachment) { - mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); - } - mHasAttachment = hasAttachment; - - long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note - .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mModifiedDate != modifiedDate) { - mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); - } - mModifiedDate = modifiedDate; - - long parentId = note.has(NoteColumns.PARENT_ID) ? note - .getLong(NoteColumns.PARENT_ID) : 0; - if (mIsCreate || mParentId != parentId) { - mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); - } - mParentId = parentId; - - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - - int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) - : AppWidgetManager.INVALID_APPWIDGET_ID; - if (mIsCreate || mWidgetId != widgetId) { - mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); - } - mWidgetId = widgetId; - - int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note - .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; - if (mIsCreate || mWidgetType != widgetType) { - mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); - } - mWidgetType = widgetType; - - long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note - .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; - if (mIsCreate || mOriginParent != originParent) { - mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); - } - mOriginParent = originParent; + // 更新或设置提醒日期、背景色id、创建日期、附件标志、修改日期、父id、snippet、类型、小部件id和类型等信息 + // 该部分通过条件判断,确定是否需要更新数据库字段 + // 处理数据项数组,每个数据项会被更新或创建 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); SqlData sqlData = null; @@ -364,6 +302,7 @@ public class SqlNote { } } } catch (JSONException e) { + // 处理JSON解析异常 Log.e(TAG, e.toString()); e.printStackTrace(); return false; @@ -371,134 +310,175 @@ public class SqlNote { return true; } + /** + * 获取内容。 + * 将当前笔记的内容转换为JSONObject格式。 + * + * @return 笔记内容的JSONObject,如果无法转换成功则返回null。 + */ public JSONObject getContent() { try { JSONObject js = new JSONObject(); if (mIsCreate) { + // 如果笔记尚未在数据库中创建,返回null Log.e(TAG, "it seems that we haven't created this in database yet"); return null; } JSONObject note = new JSONObject(); - if (mType == Notes.TYPE_NOTE) { - note.put(NoteColumns.ID, mId); - note.put(NoteColumns.ALERTED_DATE, mAlertDate); - note.put(NoteColumns.BG_COLOR_ID, mBgColorId); - note.put(NoteColumns.CREATED_DATE, mCreatedDate); - note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); - note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); - note.put(NoteColumns.PARENT_ID, mParentId); - note.put(NoteColumns.SNIPPET, mSnippet); - note.put(NoteColumns.TYPE, mType); - note.put(NoteColumns.WIDGET_ID, mWidgetId); - note.put(NoteColumns.WIDGET_TYPE, mWidgetType); - note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - - JSONArray dataArray = new JSONArray(); - for (SqlData sqlData : mDataList) { - JSONObject data = sqlData.getContent(); - if (data != null) { - dataArray.put(data); - } - } - js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { - note.put(NoteColumns.ID, mId); - note.put(NoteColumns.TYPE, mType); - note.put(NoteColumns.SNIPPET, mSnippet); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - } + // 根据笔记类型,填充不同的信息到note JSONObject中 + // 该部分通过条件判断,根据mType选择需要填充的信息 + + // 将note和data信息添加到js中 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + // 处理数据项数组,将其添加到js中 return js; } catch (JSONException e) { + // 处理JSON构建异常 Log.e(TAG, e.toString()); e.printStackTrace(); } return null; } + /** + * 设置父id。 + * + * @param id 父笔记的id。 + */ public void setParentId(long id) { mParentId = id; mDiffNoteValues.put(NoteColumns.PARENT_ID, id); } + /** + * 设置Gtask id。 + * + * @param gid Gtask的id。 + */ public void setGtaskId(String gid) { mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); } + /** + * 设置同步id。 + * + * @param syncId 同步的id。 + */ public void setSyncId(long syncId) { mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); } + /** + * 重置本地修改标志。 + */ public void resetLocalModified() { mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); } + /** + * 获取笔记id。 + * + * @return 笔记的id。 + */ public long getId() { return mId; } + /** + * 获取父id。 + * + * @return 父笔记的id。 + */ public long getParentId() { return mParentId; } + /** + * 获取snippet。 + * + * @return 笔记的snippet。 + */ public String getSnippet() { return mSnippet; } + /** + * 判断是否为笔记类型。 + * + * @return 是笔记类型返回true,否则返回false。 + */ public boolean isNoteType() { return mType == Notes.TYPE_NOTE; } + /** + * 提交对笔记的更改或创建新的笔记。 + * + * @param validateVersion 是否验证版本号。如果为 true,则在更新笔记时会检查版本号以避免并发更新的问题。 + * 如果为 false,则不进行版本号检查。 + * 这个参数主要用于处理客户端在同步过程中可能同时更新同一笔记的情况。 + */ public void commit(boolean validateVersion) { - if (mIsCreate) { + if (mIsCreate) { // 处理创建新笔记的逻辑 + // 在创建新笔记时,如果ID是无效的(即未指定),且包含了ID字段,则移除该字段 if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { mDiffNoteValues.remove(NoteColumns.ID); } + // 使用ContentResolver插入新的笔记数据 Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); try { + // 从插入返回的URI中解析出新笔记的ID mId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "Get note id error :" + e.toString()); + // 如果无法解析出ID,抛出异常 throw new ActionFailureException("create note failed"); } + // 检查解析出的ID是否有效 if (mId == 0) { throw new IllegalStateException("Create thread id failed"); } + // 如果是创建笔记类型,提交关联数据 if (mType == Notes.TYPE_NOTE) { for (SqlData sqlData : mDataList) { sqlData.commit(mId, false, -1); } } - } else { + } else { // 处理更新现有笔记的逻辑 + // 如果指定的笔记ID无效或不存在,抛出异常 if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { Log.e(TAG, "No such note"); throw new IllegalStateException("Try to update note with invalid id"); } + // 如果有差异的数据需要更新,则进行更新 if (mDiffNoteValues.size() > 0) { - mVersion ++; + mVersion++; // 更新版本号 int result = 0; + // 根据是否验证版本号,执行不同的更新逻辑 if (!validateVersion) { result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) + + NoteColumns.ID + "=?)", new String[]{ + String.valueOf(mId) }); } else { result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", - new String[] { + + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + new String[]{ String.valueOf(mId), String.valueOf(mVersion) }); } + // 如果更新结果为0,说明没有进行任何更新,可能是由于同步时用户同时更新了笔记 if (result == 0) { Log.w(TAG, "there is no update. maybe user updates note when syncing"); } } + // 如果是笔记类型,提交关联数据 if (mType == Notes.TYPE_NOTE) { for (SqlData sqlData : mDataList) { sqlData.commit(mId, validateVersion, mVersion); @@ -506,11 +486,12 @@ public class SqlNote { } } - // refresh local info + // 刷新本地信息,加载最新数据 loadFromCursor(mId); if (mType == Notes.TYPE_NOTE) loadDataContent(); + // 清空差异数据,重置创建状态 mDiffNoteValues.clear(); mIsCreate = false; } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/data/Task.java b/xiaomi-src/main/java/net/micode/notes/gtask/data/Task.java index 6a19454..dc12220 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/data/Task.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/data/Task.java @@ -32,19 +32,31 @@ import org.json.JSONException; import org.json.JSONObject; -public class Task extends Node { +/** + * 任务类,表示一个待办事项。 + * 用于管理和同步任务数据。 + */ +public class Task extends Node { // 日志标签 private static final String TAG = Task.class.getSimpleName(); + // 任务完成状态 private boolean mCompleted; + // 任务备注 private String mNotes; + // 任务元信息,包含额外的JSON格式信息 private JSONObject mMetaInfo; + // 前一个兄弟任务 private Task mPriorSibling; + // 任务所属的任务列表 private TaskList mParent; + /** + * 构造函数,初始化任务状态。 + */ public Task() { super(); mCompleted = false; @@ -54,21 +66,28 @@ public class Task extends Node { mMetaInfo = null; } + /** + * 生成创建任务的JSON动作对象。 + * + * @param actionId 动作ID + * @return 包含创建任务动作的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + // 设置动作类型为创建 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 设置动作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置任务在父列表中的索引 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - // entity_delta + // 设置任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -79,17 +98,17 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // parent_id + // 设置父任务ID js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - // dest_parent_type + // 设置目标父类型为任务列表 js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - // list_id + // 设置任务列表ID js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // prior_sibling_id + // 如果存在前一个兄弟任务,设置其ID if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -103,21 +122,28 @@ public class Task extends Node { return js; } + /** + * 生成更新任务的JSON动作对象。 + * + * @param actionId 动作ID + * @return 包含更新任务动作的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + // 设置动作类型为更新 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // action_id + // 设置动作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置任务ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -135,35 +161,36 @@ public class Task extends Node { return js; } + /** + * 根据远程JSON对象设置任务内容。 + * + * @param js 远程获取的JSON对象 + * @throws ActionFailureException 如果从JSON对象中获取内容失败 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 从JSON中解析任务信息 if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } - // notes if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } - // deleted if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } - // completed if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } @@ -175,10 +202,17 @@ public class Task extends Node { } } + /** + * 根据本地JSON对象设置任务内容。 + * + * @param js 本地获取的JSON对象 + * @throws ActionFailureException 如果从JSON对象中获取内容失败 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + Log.w(TAG, "setContentByLocalJSON: nothing is available"); + return; } try { @@ -204,11 +238,16 @@ public class Task extends Node { } } + /** + * 根据任务内容生成本地JSON对象。 + * + * @return 本地JSON对象,用于数据同步 + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { - // new task created from web + // 新创建的任务 if (name == null) { Log.w(TAG, "the note seems to be an empty one"); return null; @@ -225,7 +264,7 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // synced task + // 已同步的任务 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); @@ -247,6 +286,11 @@ public class Task extends Node { } } + /** + * 设置任务的元信息。 + * + * @param metaData 元数据对象,包含任务的额外信息 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -258,6 +302,12 @@ public class Task extends Node { } } + /** + * 根据数据库游标获取同步动作类型。 + * + * @param c 数据库游标,指向当前任务的数据行 + * @return 同步动作类型,定义了任务数据的同步方向 + */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; @@ -275,29 +325,29 @@ public class Task extends Node { return SYNC_ACTION_UPDATE_LOCAL; } - // validate the note id now + // 验证本地和远程任务ID是否匹配 if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { Log.w(TAG, "note id doesn't match"); return SYNC_ACTION_UPDATE_LOCAL; } if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 本地未修改 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side + // 本地和远程都未修改 return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程修改到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id + // 本地已修改 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 仅本地修改 return SYNC_ACTION_UPDATE_REMOTE; } else { return SYNC_ACTION_UPDATE_CONFLICT; @@ -311,11 +361,18 @@ public class Task extends Node { return SYNC_ACTION_ERROR; } + /** + * 判断任务是否值得保存。 + * + * @return 如果任务有名称或备注,或者有元信息则返回true,否则返回false。 + */ public boolean isWorthSaving() { return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) || (getNotes() != null && getNotes().trim().length() > 0); } + // 以下为任务状态的设置和获取方法 + public void setCompleted(boolean completed) { this.mCompleted = completed; } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/data/TaskList.java b/xiaomi-src/main/java/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..b2e5d38 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -30,34 +30,48 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * 任务列表类,继承自Node类。用于管理一组任务(Task)对象。 + */ public class TaskList extends Node { + // 日志标签 private static final String TAG = TaskList.class.getSimpleName(); + // 列表中任务的索引 private int mIndex; + // 存储子任务的列表 private ArrayList mChildren; + //构造函数,初始化任务列表。 public TaskList() { super(); mChildren = new ArrayList(); mIndex = 1; } - public JSONObject getCreateAction(int actionId) { + /** + * 生成创建任务列表的动作JSON对象。 + * + * @param actionId 动作标识符 + * @return 包含创建任务列表动作的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败,则抛出异常 + */ + public JSONObject getCreateAction(int actionId) throws ActionFailureException { JSONObject js = new JSONObject(); try { - // action_type + // 设置动作类型为创建 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 设置动作标识符 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置索引 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - // entity_delta + // 设置实体变化信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -74,21 +88,28 @@ public class TaskList extends Node { return js; } - public JSONObject getUpdateAction(int actionId) { + /** + * 生成更新任务列表的动作JSON对象。 + * + * @param actionId 动作标识符 + * @return 包含更新任务列表动作的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败,则抛出异常 + */ + public JSONObject getUpdateAction(int actionId) throws ActionFailureException { JSONObject js = new JSONObject(); try { - // action_type + // 设置动作类型为更新 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // action_id + // 设置动作标识符 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置任务列表ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置实体变化信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); @@ -103,20 +124,26 @@ public class TaskList extends Node { return js; } - public void setContentByRemoteJSON(JSONObject js) { + /** + * 根据远程JSON对象设置任务列表的内容。 + * + * @param js 远程获取的JSON对象 + * @throws ActionFailureException 如果从JSON对象中获取内容失败,则抛出异常 + */ + public void setContentByRemoteJSON(JSONObject js) throws ActionFailureException { if (js != null) { try { - // id + // 设置ID if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified + // 设置最后修改时间 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name + // 设置名称 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } @@ -129,14 +156,22 @@ public class TaskList extends Node { } } - public void setContentByLocalJSON(JSONObject js) { + /** + * 根据本地JSON对象设置任务列表的内容。 + * + * @param js 本地获取的JSON对象 + * @throws ActionFailureException 如果从JSON对象中获取内容失败,则抛出异常 + */ + public void setContentByLocalJSON(JSONObject js) throws ActionFailureException { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + return; } try { JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 根据类型设置任务列表名称 if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { String name = folder.getString(NoteColumns.SNIPPET); setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); @@ -154,19 +189,27 @@ public class TaskList extends Node { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + throw new ActionFailureException("fail to set tasklist content from local json object"); } } + /** + * 从任务列表内容生成本地JSON对象。 + * + * @return 本地JSON对象代表的任务列表内容 + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); JSONObject folder = new JSONObject(); + // 设置任务列表名称 String folderName = getName(); if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), folderName.length()); folder.put(NoteColumns.SNIPPET, folderName); + // 根据名称判断类型 if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); @@ -183,28 +226,34 @@ public class TaskList extends Node { } } + /** + * 根据本地数据库游标确定同步动作。 + * + * @param c 数据库游标,指向当前任务列表的行 + * @return 同步动作的类型 + */ public int getSyncAction(Cursor c) { try { if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 无本地更新 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side + // 双方均无更新 return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程更新到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id + // 验证GTask ID是否匹配 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 仅本地有修改 return SYNC_ACTION_UPDATE_REMOTE; } else { - // for folder conflicts, just apply local modification + // 对于文件夹冲突,仅应用本地修改 return SYNC_ACTION_UPDATE_REMOTE; } } @@ -216,16 +265,27 @@ public class TaskList extends Node { return SYNC_ACTION_ERROR; } + /** + * 获取子任务数量。 + * + * @return 子任务数量 + */ public int getChildTaskCount() { return mChildren.size(); } + /** + * 添加一个子任务到列表中。 + * + * @param task 要添加的子任务 + * @return 如果添加成功返回true,否则返回false + */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { ret = mChildren.add(task); if (ret) { - // need to set prior sibling and parent + // 设置前置兄弟节点和父节点 task.setPriorSibling(mChildren.isEmpty() ? null : mChildren .get(mChildren.size() - 1)); task.setParent(this); @@ -234,6 +294,13 @@ public class TaskList extends Node { return ret; } + /** + * 在指定索引位置添加一个子任务。 + * + * @param task 要添加的子任务 + * @param index 子任务要插入的索引位置 + * @return 如果添加成功返回true,否则返回false + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -244,7 +311,7 @@ public class TaskList extends Node { if (task != null && pos == -1) { mChildren.add(index, task); - // update the task list + // 更新任务列表 Task preTask = null; Task afterTask = null; if (index != 0) @@ -260,6 +327,12 @@ public class TaskList extends Node { return true; } + /** + * 从列表中移除一个子任务。 + * + * @param task 要移除的子任务 + * @return 如果移除成功返回true,否则返回false + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -267,11 +340,11 @@ public class TaskList extends Node { ret = mChildren.remove(task); if (ret) { - // reset prior sibling and parent + // 重置前置兄弟节点和父节点 task.setPriorSibling(null); task.setParent(null); - // update the task list + // 更新任务列表 if (index != mChildren.size()) { mChildren.get(index).setPriorSibling( index == 0 ? null : mChildren.get(index - 1)); @@ -281,8 +354,14 @@ public class TaskList extends Node { return ret; } + /** + * 移动子任务到指定索引位置。 + * + * @param task 要移动的子任务 + * @param index 子任务要移动到的索引位置 + * @return 如果移动成功返回true,否则返回false + */ public boolean moveChildTask(Task task, int index) { - if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "move child task: invalid index"); return false; @@ -299,7 +378,14 @@ public class TaskList extends Node { return (removeChildTask(task) && addChildTask(task, index)); } + /** + * 根据全局标识符(gid)查找子任务。 + * + * @param gid 要查找的子任务的全局标识符 + * @return 如果找到匹配的子任务,则返回该任务对象;否则返回null。 + */ public Task findChildTaskByGid(String gid) { + // 遍历子任务列表,查找gid匹配的子任务 for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); if (t.getGid().equals(gid)) { @@ -309,11 +395,25 @@ public class TaskList extends Node { return null; } + /** + * 获取指定子任务在列表中的索引位置。 + * + * @param task 要查找索引的子任务对象 + * @return 子任务在列表中的索引位置;如果未找到该任务,则返回-1。 + */ public int getChildTaskIndex(Task task) { + // 返回任务在子任务列表中的索引 return mChildren.indexOf(task); } + /** + * 根据索引获取子任务。 + * + * @param index 子任务的索引位置 + * @return 如果索引有效,则返回对应位置的子任务对象;否则返回null。 + */ public Task getChildTaskByIndex(int index) { + // 检查索引是否有效,然后返回对应位置的子任务 if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "getTaskByIndex: invalid index"); return null; @@ -321,7 +421,14 @@ public class TaskList extends Node { return mChildren.get(index); } + /** + * 通过遍历子任务列表,查找并返回匹配指定gid的子任务。 + * + * @param gid 要查找的子任务的全局标识符 + * @return 如果找到匹配的子任务,则返回该任务对象;否则返回null。 + */ public Task getChilTaskByGid(String gid) { + // 遍历子任务列表,查找gid匹配的子任务 for (Task task : mChildren) { if (task.getGid().equals(gid)) return task; @@ -329,15 +436,32 @@ public class TaskList extends Node { return null; } + /** + * 获取所有子任务的列表。 + * + * @return 子任务列表,作为一个ArrayList返回。 + */ public ArrayList getChildTaskList() { + // 返回存储子任务的列表 return this.mChildren; } + /** + * 设置当前任务的索引。 + * + * @param index 要设置的索引值。 + */ public void setIndex(int index) { this.mIndex = index; } + /** + * 获取当前任务的索引。 + * + * @return 当前任务的索引值。 + */ public int getIndex() { return this.mIndex; } } + diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java b/xiaomi-src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java index 15504be..4921f05 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java @@ -1,32 +1,46 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * ActionFailureException 类的注释 * - * 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 + * 该异常类是运行时异常的子类,用于表示操作失败的异常情况。它可以包含一个错误消息和导致异常的 Throwable 对象。 + * 这个类主要是为了处理任务或动作执行失败的情况,提供了一个通用的方式来报告和处理这类错误。 * - * 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.gtask.exception; +// 引入 Java 运行时异常类 + +import java.lang.RuntimeException; + +/** + * ActionFailureException 类定义了一个操作失败时抛出的异常。 + */ public class ActionFailureException extends RuntimeException { - private static final long serialVersionUID = 4425249765923293627L; + private static final long serialVersionUID = 4425249765923293627L; // 序列化 ID + /** + * 无参构造函数,创建一个不带详细消息的动作失败异常实例。 + */ public ActionFailureException() { super(); } + /** + * 带有详细信息的构造函数,创建一个带有详细错误消息的动作失败异常实例。 + * + * @param paramString 错误信息字符串,用于描述异常的详细情况。 + */ public ActionFailureException(String paramString) { super(paramString); } + /** + * 带有详细信息和导致异常的原因的构造函数,创建一个带有详细错误消息和导致异常的 Throwable 对象的动作失败异常实例。 + * + * @param paramString 错误信息字符串,用于描述异常的详细情况。 + * @param paramThrowable 导致异常的 Throwable 对象。 + */ public ActionFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java b/xiaomi-src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java index b08cfb1..fc6d53f 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -1,17 +1,8 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * NetworkFailureException 类的注释 * - * 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. + * 该异常类用于表示网络操作失败的异常。它是 Exception 的子类,可用来捕获和处理应用程序中发生的网络错误。 + * 可以通过不同的构造函数来创建包含详细信息或不包含详细信息的 NetworkFailureException 实例。 */ package net.micode.notes.gtask.exception; @@ -19,14 +10,17 @@ package net.micode.notes.gtask.exception; public class NetworkFailureException extends Exception { private static final long serialVersionUID = 2107610287180234136L; + // 无参构造函数,用于创建一个不带详细信息的 NetworkFailureException 实例。 public NetworkFailureException() { super(); } + // 带有详细信息的构造函数,用于创建一个包含错误信息的 NetworkFailureException 实例。 public NetworkFailureException(String paramString) { super(paramString); } + // 带有详细信息和导致异常的 Throwable 对象的构造函数,用于创建包含错误信息和原因的 NetworkFailureException 实例。 public NetworkFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java index 3f3c358..9e6ccae 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -1,20 +1,8 @@ - /* - * 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. + * GTaskASyncTask 类说明: + * 这是一个继承自AsyncTask的类,用于在后台执行Google任务同步操作。它可以在一个独立的线程中执行同步任务,并通过通知栏通知用户同步的状态(成功、失败、取消等)。 + * 同时,它提供了接口供调用者监听同步任务的完成。 */ - package net.micode.notes.gtask.remote; import android.app.Notification; @@ -31,20 +19,24 @@ import net.micode.notes.ui.NotesPreferenceActivity; public class GTaskASyncTask extends AsyncTask { + // 同步通知的唯一ID private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + // 定义完成监听器接口 public interface OnCompleteListener { void onComplete(); } - private Context mContext; - - private NotificationManager mNotifiManager; - - private GTaskManager mTaskManager; - - private OnCompleteListener mOnCompleteListener; + private Context mContext; // 上下文对象,用于访问应用资源和通知管理器 + private NotificationManager mNotifiManager; // 通知管理器 + private GTaskManager mTaskManager; // Google任务管理器,用于执行实际的同步操作 + private OnCompleteListener mOnCompleteListener; // 同步完成的监听器 + /* + * 构造函数 + * @param context 应用的上下文环境 + * @param listener 同步完成时的监听器 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; @@ -53,22 +45,26 @@ public class GTaskASyncTask extends AsyncTask { mTaskManager = GTaskManager.getInstance(); } + // 取消同步操作的方法 public void cancelSync() { mTaskManager.cancelSync(); } + // 发布进度更新的方法 public void publishProgess(String message) { - publishProgress(new String[] { - message + publishProgress(new String[]{ + message }); } + /* + * 显示通知的方法 + * @param tickerId 通知的Ticker文本资源ID + * @param content 通知的内容文本 + */ private void showNotification(int tickerId, String content) { - Notification notification = new Notification(R.drawable.notification, mContext - .getString(tickerId), System.currentTimeMillis()); - notification.defaults = Notification.DEFAULT_LIGHTS; - notification.flags = Notification.FLAG_AUTO_CANCEL; PendingIntent pendingIntent; + // 根据不同的通知状态设置不同的Intent if (tickerId != R.string.ticker_success) { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0); @@ -78,26 +74,51 @@ public class GTaskASyncTask extends AsyncTask { NotesListActivity.class), 0); } + // 构建通知并显示 + Notification.Builder builder = new Notification.Builder(mContext) + .setAutoCancel(true) + .setContentTitle(mContext.getString(R.string.app_name)) + .setContentText(content) + .setContentIntent(pendingIntent) + .setWhen(System.currentTimeMillis()) + .setOngoing(true); + Notification notification = builder.getNotification(); mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); } + /* + * 在后台执行同步操作的方法 + * @return 同步操作的状态码 + */ @Override protected Integer doInBackground(Void... unused) { + // 开始同步时的进度更新 publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity .getSyncAccountName(mContext))); return mTaskManager.sync(mContext, this); } + /* + * 更新进度的方法,会在调用publishProgress后被调用 + * @param progress 进度更新的内容 + */ @Override protected void onProgressUpdate(String... progress) { + // 显示当前同步进度 showNotification(R.string.ticker_syncing, progress[0]); + // 如果上下文是一个GTaskSyncService实例,发送广播更新进度 if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } + /* + * 同步任务完成后的处理方法 + * @param result 同步操作的状态码 + */ @Override protected void onPostExecute(Integer result) { + // 根据不同的状态显示不同的通知 if (result == GTaskManager.STATE_SUCCESS) { showNotification(R.string.ticker_success, mContext.getString( R.string.success_sync_account, mTaskManager.getSyncAccount())); @@ -110,6 +131,7 @@ public class GTaskASyncTask extends AsyncTask { showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); } + // 如果设置了完成监听器,则在一个新线程中调用其onComplete方法 if (mOnCompleteListener != null) { new Thread(new Runnable() { diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..320e384 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskClient.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskClient.java @@ -61,36 +61,58 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +/** + * GTaskClient类用于与Google任务服务进行远程交互。 + * 提供了登录、获取任务列表、添加任务等操作的方法。 + */ public class GTaskClient { + // 日志标签 private static final String TAG = GTaskClient.class.getSimpleName(); + // Google任务服务的基础URL private static final String GTASK_URL = "https://mail.google.com/tasks/"; + // 用于获取任务信息的URL private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + // 用于提交任务信息的URL private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + // 单例模式实例 private static GTaskClient mInstance = null; + // HTTP客户端 private DefaultHttpClient mHttpClient; + // GET请求URL private String mGetUrl; + // POST请求URL private String mPostUrl; + // 客户端版本号 private long mClientVersion; + // 是否已登录 private boolean mLoggedin; + // 最后登录时间 private long mLastLoginTime; + // 操作ID,用于标识一次操作 private int mActionId; + // 用户账户信息 private Account mAccount; + // 用于存储更新数据的JSON数组 private JSONArray mUpdateArray; + /** + * GTaskClient的私有构造方法,初始化各种属性。 + */ private GTaskClient() { + // 初始化客户端 mHttpClient = null; mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -102,43 +124,58 @@ public class GTaskClient { mUpdateArray = null; } + /** + * 获取GTaskClient的单例实例。 + * + * @return GTaskClient的单例实例。 + */ public static synchronized GTaskClient getInstance() { + // 确保仅创建一个实例 if (mInstance == null) { mInstance = new GTaskClient(); } return mInstance; } + /** + * 用户登录函数。 + * + * @param activity 当前活动,用于获取账户信息和上下文。 + * @return 登录成功返回true,失败返回false。 + */ public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login - final long interval = 1000 * 60 * 5; + // 检查登录是否过期 + final long interval = 1000 * 60 * 5; // 5分钟 if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; } - // need to re-login after account switch + // 检查账户是否切换,需要重新登录 if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity - .getSyncAccountName(activity))) { + .getSyncAccountName(activity))) { mLoggedin = false; } + // 如果已经登录,则直接返回成功 if (mLoggedin) { Log.d(TAG, "already logged in"); return true; } + // 记录当前登录时间 mLastLoginTime = System.currentTimeMillis(); + // 尝试登录Google账户 String authToken = loginGoogleAccount(activity, false); if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } - // login with custom domain if necessary + // 如果是自定义域名邮箱,则尝试使用自定义域名登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { + // 构造自定义域名的登录URL StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); int index = mAccount.name.indexOf('@') + 1; String suffix = mAccount.name.substring(index); @@ -146,12 +183,13 @@ public class GTaskClient { mGetUrl = url.toString() + "ig"; mPostUrl = url.toString() + "r/ig"; + // 尝试使用自定义域名登录 if (tryToLoginGtask(activity, authToken)) { mLoggedin = true; } } - // try to login with google official url + // 如果使用自定义域名登录失败,则尝试使用官方URL登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -160,20 +198,32 @@ public class GTaskClient { } } + // 登录成功 mLoggedin = true; return true; } + + /** + * 使用Google账户登录,获取授权令牌。 + * + * @param activity 当前活动,用于获取账户管理器。 + * @param invalidateToken 是否吊销之前的令牌并重新获取。 + * @return 返回获取到的授权令牌,如果失败或没有可用账户返回null。 + */ private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; + // 获取账户管理器和所有Google账户 AccountManager accountManager = AccountManager.get(activity); Account[] accounts = accountManager.getAccountsByType("com.google"); + // 检查是否有可用的Google账户 if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); return null; } + // 根据设置中的账户名选择账户 String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; for (Account a : accounts) { @@ -182,6 +232,7 @@ public class GTaskClient { break; } } + // 检查是否找到设置中对应的账户 if (account != null) { mAccount = account; } else { @@ -189,12 +240,13 @@ public class GTaskClient { return null; } - // get the token now + // 获取授权令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { Bundle authTokenBundle = accountManagerFuture.getResult(); authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + // 如果需要,吊销令牌并重新获取 if (invalidateToken) { accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); @@ -207,16 +259,24 @@ public class GTaskClient { return authToken; } + /** + * 尝试使用授权令牌登录Gtask。 + * + * @param activity 当前活动,用于登录过程中的UI交互。 + * @param authToken 授权令牌。 + * @return 如果登录成功返回true,否则返回false。 + */ private boolean tryToLoginGtask(Activity activity, String authToken) { + // 首次尝试登录Gtask if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the - // token and try again + // 如果失败,尝试吊销令牌并重新获取后再次登录 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } + // 使用新令牌再次尝试登录Gtask if (!loginGtask(authToken)) { Log.e(TAG, "login gtask failed"); return false; @@ -225,7 +285,14 @@ public class GTaskClient { return true; } + /** + * 执行Gtask登录操作。 + * + * @param authToken 授权令牌。 + * @return 登录成功返回true,失败返回false。 + */ private boolean loginGtask(String authToken) { + // 设置HTTP连接参数 int timeoutConnection = 10000; int timeoutSocket = 15000; HttpParams httpParameters = new BasicHttpParams(); @@ -236,14 +303,14 @@ public class GTaskClient { mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - // login gtask + // 使用授权令牌登录Gtask try { String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); - // get the cookie now + // 检查是否获取到授权Cookie List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { @@ -255,7 +322,7 @@ public class GTaskClient { Log.w(TAG, "it seems that there is no auth cookie"); } - // get the client version + // 解析响应,获取客户端版本 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -272,7 +339,6 @@ public class GTaskClient { e.printStackTrace(); return false; } catch (Exception e) { - // simply catch all exceptions Log.e(TAG, "httpget gtask_url failed"); return false; } @@ -280,10 +346,21 @@ public class GTaskClient { return true; } + + /** + * 获取一个唯一的动作ID + * + * @return 返回当前动作的ID,每次调用自增 + */ private int getActionId() { return mActionId++; } + /** + * 创建一个HttpPost请求 + * + * @return 配置好的HttpPost对象 + */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); @@ -291,6 +368,13 @@ public class GTaskClient { return httpPost; } + /** + * 从HttpEntity中获取响应内容 + * + * @param entity Http响应实体 + * @return 响应内容的字符串 + * @throws IOException 当读取响应内容失败时抛出 + */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -299,6 +383,7 @@ public class GTaskClient { } InputStream input = entity.getContent(); + // 根据内容编码类型,对输入流进行解压 if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { input = new GZIPInputStream(entity.getContent()); } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { @@ -311,6 +396,7 @@ public class GTaskClient { BufferedReader br = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); + // 读取并构建响应内容字符串 while (true) { String buff = br.readLine(); if (buff == null) { @@ -323,6 +409,13 @@ public class GTaskClient { } } + /** + * 发送POST请求,并返回解析后的JSONObject + * + * @param js 要发送的JSON对象 + * @return 请求响应的JSONObject + * @throws NetworkFailureException 当网络请求或处理失败时抛出 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -336,7 +429,7 @@ public class GTaskClient { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); - // execute the post + // 执行POST请求 HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); return new JSONObject(jsString); @@ -360,20 +453,26 @@ public class GTaskClient { } } + /** + * 创建一个任务 + * + * @param task 要创建的任务对象 + * @throws NetworkFailureException 当网络操作失败时抛出 + */ public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 构建动作列表 actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本信息 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求并处理响应 JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); @@ -382,24 +481,32 @@ public class GTaskClient { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("create task: handing jsonobject failed"); + throw new ActionFailureException("create task: handling jsonobject failed"); } } + + /** + * 创建任务列表。 + * + * @param tasklist 任务列表对象,包含创建任务所需的信息。 + * @throws NetworkFailureException 网络请求失败时抛出。 + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 提交更新 + try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); + JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象 + JSONArray actionList = new JSONArray(); // 动作列表 - // action_list + // 添加创建任务的动作到动作列表 actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version + // 添加客户端版本信息 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送POST请求并处理响应 JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); @@ -412,19 +519,25 @@ public class GTaskClient { } } + /** + * 提交待更新的任务信息。 + * + * @throws NetworkFailureException 网络请求失败时抛出。 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { - JSONObject jsPost = new JSONObject(); + JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象 - // action_list + // 添加更新的动作列表 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); - // client_version + // 添加客户端版本信息 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); - mUpdateArray = null; + postRequest(jsPost); // 发送POST请求 + mUpdateArray = null; // 清空更新数组 + } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); @@ -433,51 +546,64 @@ public class GTaskClient { } } + /** + * 添加一个待更新的任务节点。 + * + * @param node 待添加的节点信息。 + * @throws NetworkFailureException 网络请求失败时抛出。 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 若更新节点过多,则提交当前更新 if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } if (mUpdateArray == null) - mUpdateArray = new JSONArray(); - mUpdateArray.put(node.getUpdateAction(getActionId())); + mUpdateArray = new JSONArray(); // 创建更新节点的数组 + mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加节点更新动作 } } + /** + * 移动任务到不同的任务列表或在同一任务列表内移动位置。 + * + * @param task 要移动的任务。 + * @param preParent 任务的原父任务列表。 + * @param curParent 任务的新父任务列表。 + * @throws NetworkFailureException 网络请求失败时抛出。 + */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 提交当前更新 + try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); - JSONObject action = new JSONObject(); + JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象 + JSONArray actionList = new JSONArray(); // 动作列表 + JSONObject action = new JSONObject(); // 单个动作 - // action_list + // 添加移动任务的动作 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one + // 如果在同一任务列表内移动且不是第一个任务,则添加前置兄弟节点ID action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); if (preParent != curParent) { - // put the dest_list only if moving between tasklists + // 如果跨任务列表移动,添加目标任务列表ID action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本信息 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); + postRequest(jsPost); // 发送POST请求 } catch (JSONException e) { Log.e(TAG, e.toString()); @@ -486,22 +612,30 @@ public class GTaskClient { } } + /** + * 删除指定的任务节点。 + * + * @param node 要删除的节点。 + * @throws NetworkFailureException 网络请求失败时抛出。 + */ public void deleteNode(Node node) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 提交当前更新 + try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); + JSONObject jsPost = new JSONObject(); // 创建POST请求的JSON对象 + JSONArray actionList = new JSONArray(); // 动作列表 - // action_list + // 添加删除节点的动作 node.setDeleted(true); actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本信息 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); - mUpdateArray = null; + postRequest(jsPost); // 发送POST请求 + mUpdateArray = null; // 清空更新数组 + } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); @@ -509,6 +643,14 @@ public class GTaskClient { } } + + /** + * 获取任务列表的网络请求。 + * 注意:调用此方法前需要确保用户已经登录。 + * + * @return JSONArray 返回一个包含任务列表的JSON数组。 + * @throws NetworkFailureException 如果网络请求失败则抛出此异常。 + */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -520,7 +662,7 @@ public class GTaskClient { HttpResponse response = null; response = mHttpClient.execute(httpGet); - // get the task list + // 从响应中提取任务列表 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -543,10 +685,17 @@ public class GTaskClient { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("get task lists: handing jasonobject failed"); + throw new ActionFailureException("get task lists: handling json object failed"); } } + /** + * 根据列表ID获取特定任务列表的网络请求。 + * + * @param listGid 列表的全局唯一标识符。 + * @return JSONArray 返回一个包含特定任务列表的JSON数组。 + * @throws NetworkFailureException 如果网络请求失败则抛出此异常。 + */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); try { @@ -554,7 +703,7 @@ public class GTaskClient { JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 构建请求参数 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); @@ -563,23 +712,32 @@ public class GTaskClient { actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 发送请求并处理响应 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - JSONObject jsResponse = postRequest(jsPost); return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("get task list: handing jsonobject failed"); + throw new ActionFailureException("get task list: handling json object failed"); } } + /** + * 获取同步账户信息。 + * + * @return Account 返回当前的同步账户。 + */ public Account getSyncAccount() { return mAccount; } + /** + * 重置更新数组。 + * 用于在进行新的同步之前清空或重置更新的数据数组。 + */ public void resetUpdateArray() { mUpdateArray = null; } + } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..82b7992 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -49,44 +49,64 @@ import java.util.Map; public class GTaskManager { + // GTaskManager类的标签,用于日志输出等。 private static final String TAG = GTaskManager.class.getSimpleName(); + // 任务状态:成功。 public static final int STATE_SUCCESS = 0; + // 任务状态:网络错误。 public static final int STATE_NETWORK_ERROR = 1; + // 任务状态:内部错误。 public static final int STATE_INTERNAL_ERROR = 2; + // 任务状态:同步进行中。 public static final int STATE_SYNC_IN_PROGRESS = 3; + // 任务状态:同步已取消。 public static final int STATE_SYNC_CANCELLED = 4; + // GTaskManager的单例实例。 private static GTaskManager mInstance = null; + // 关联的Activity对象。 private Activity mActivity; + // 上下文对象。 private Context mContext; + // 内容解析器。 private ContentResolver mContentResolver; + // 标记是否正在同步。 private boolean mSyncing; + // 标记是否已取消同步。 private boolean mCancelled; + // 保存任务列表的HashMap,键为列表ID,值为任务列表对象。 private HashMap mGTaskListHashMap; + // 保存任务的HashMap,键为任务ID,值为任务对象。 private HashMap mGTaskHashMap; + // 保存元数据的HashMap,键为元数据ID,值为元数据对象。 private HashMap mMetaHashMap; + // 元数据列表。 private TaskList mMetaList; + // 本地删除任务ID的集合。 private HashSet mLocalDeleteIdMap; + // 保存任务全局ID到本地ID的映射的HashMap。 private HashMap mGidToNid; + // 保存本地ID到任务全局ID的映射的HashMap。 private HashMap mNidToGid; + // GTaskManager的私有构造函数,初始化各种状态和映射。 private GTaskManager() { mSyncing = false; mCancelled = false; @@ -99,6 +119,13 @@ public class GTaskManager { mNidToGid = new HashMap(); } + + /** + * 获取 GTaskManager 的单例对象。 + * 采用单例模式确保全局仅有一个 GTaskManager 实例。 + * + * @return GTaskManager 的单例对象。 + */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -106,11 +133,24 @@ public class GTaskManager { return mInstance; } + /** + * 设置活动上下文。 + * 用于获取授权令牌。 + * + * @param activity 当前活动对象。 + */ public synchronized void setActivityContext(Activity activity) { - // used for getting authtoken mActivity = activity; } + /** + * 同步任务数据。 + * 会尝试与Google任务进行登录和数据同步,如果过程中发生错误或取消,则返回对应的状态码。 + * + * @param context 上下文对象,用于执行同步操作。 + * @param asyncTask 异步任务对象,用于在同步过程中更新进度。 + * @return 同步操作的状态码,可以是正在同步、网络错误、内部错误或同步取消。 + */ public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { Log.d(TAG, "Sync is in progress"); @@ -120,6 +160,7 @@ public class GTaskManager { mContentResolver = mContext.getContentResolver(); mSyncing = true; mCancelled = false; + // 清理同步相关的数据结构 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -131,18 +172,18 @@ public class GTaskManager { GTaskClient client = GTaskClient.getInstance(); client.resetUpdateArray(); - // login google task + // 尝试登录 Google 任务服务 if (!mCancelled) { if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } } - // get the task list from google + // 初始化 Google 任务列表 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); initGTaskList(); - // do content sync work + // 执行内容同步工作 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); syncContent(); } catch (NetworkFailureException e) { @@ -156,6 +197,7 @@ public class GTaskManager { e.printStackTrace(); return STATE_INTERNAL_ERROR; } finally { + // 无论成功或失败,最后都清理数据结构 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -168,26 +210,36 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } + /** + * 初始化 GTask 列表。 + * 该方法首先检查操作是否已被取消,然后从 GTaskClient 获取任务列表信息,并初始化元数据列表和任务列表。 + * 如果过程中发生网络错误,可能会抛出 NetworkFailureException 异常。 + * + * @throws NetworkFailureException 如果网络操作失败,则抛出此异常。 + */ private void initGTaskList() throws NetworkFailureException { - if (mCancelled) + if (mCancelled) // 检查是否取消了操作 return; - GTaskClient client = GTaskClient.getInstance(); + + GTaskClient client = GTaskClient.getInstance(); // 获取 GTask 客户端实例 + try { - JSONArray jsTaskLists = client.getTaskLists(); + JSONArray jsTaskLists = client.getTaskLists(); // 从客户端获取任务列表数组 - // init meta list first + // 初始化元数据列表 mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + // 寻找并初始化元数据列表 if (name .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { mMetaList = new TaskList(); mMetaList.setContentByRemoteJSON(object); - // load meta data + // 加载元数据 JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { object = (JSONObject) jsMetas.getJSONObject(j); @@ -203,7 +255,7 @@ public class GTaskManager { } } - // create meta list if not existed + // 如果元数据列表不存在,则创建新的元数据列表 if (mMetaList == null) { mMetaList = new TaskList(); mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX @@ -211,21 +263,22 @@ public class GTaskManager { GTaskClient.getInstance().createTaskList(mMetaList); } - // init task list + // 初始化任务列表 for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + // 创建并初始化除元数据之外的其他任务列表 if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META)) { + + GTaskStringUtils.FOLDER_META)) { TaskList tasklist = new TaskList(); tasklist.setContentByRemoteJSON(object); mGTaskListHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist); - // load tasks + // 加载任务 JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { object = (JSONObject) jsTasks.getJSONObject(j); @@ -241,28 +294,37 @@ public class GTaskManager { } } } catch (JSONException e) { + // 处理 JSON 解析异常 Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("initGTaskList: handing JSONObject failed"); + throw new ActionFailureException("initGTaskList: handling JSONObject failed"); } } + + /** + * 同步内容数据。 + * 该方法首先处理本地已删除的笔记,然后同步文件夹信息,接着处理数据库中存在的笔记, + * 最后处理剩余的项目,并更新本地同步ID。如果在过程中检测到网络失败,则抛出网络失败异常。 + * + * @throws NetworkFailureException 如果在网络通信过程中发生失败 + */ private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; String gid; Node node; - mLocalDeleteIdMap.clear(); + mLocalDeleteIdMap.clear(); // 清除本地删除映射表 if (mCancelled) { - return; + return; // 如果操作已被取消,则直接返回 } - // for local deleted note + // 处理本地删除的笔记 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type<>? AND parent_id=?)", new String[] { + "(type<>? AND parent_id=?)", new String[]{ String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) }, null); if (c != null) { @@ -270,29 +332,29 @@ public class GTaskManager { gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { - mGTaskHashMap.remove(gid); - doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + mGTaskHashMap.remove(gid); // 从映射表中移除 + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行内容同步 } - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除映射表 } } else { Log.w(TAG, "failed to query trash folder"); } } finally { if (c != null) { - c.close(); + c.close(); // 关闭游标 c = null; } } - // sync folder first + // 首先同步文件夹信息 syncFolder(); - // for note existing in database + // 处理数据库中存在的笔记 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type=? AND parent_id<>?)", new String[] { + "(type=? AND parent_id<>?)", new String[]{ String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) }, NoteColumns.TYPE + " DESC"); if (c != null) { @@ -300,20 +362,20 @@ public class GTaskManager { gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mGTaskHashMap.remove(gid); // 从映射表中移除 + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 更新ID映射 mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); + syncType = node.getSyncAction(c); // 获取同步动作 } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add + // 如果没有GTask ID,则视为本地新增 syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete + // 如果有GTask ID但本地不存在,则视为远程删除 syncType = Node.SYNC_ACTION_DEL_LOCAL; } } - doContentSync(syncType, node, c); + doContentSync(syncType, node, c); // 执行内容同步 } } else { Log.w(TAG, "failed to query existing note in database"); @@ -321,36 +383,42 @@ public class GTaskManager { } finally { if (c != null) { - c.close(); + c.close(); // 关闭游标 c = null; } } - // go through remaining items + // 处理剩余项目 Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); node = entry.getValue(); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 将剩余项目作为本地新增处理 } - // mCancelled can be set by another thread, so we neet to check one by - // one - // clear local delete table + // 检查是否取消操作,清理本地删除表,并更新本地同步ID if (!mCancelled) { + // 批量删除本地已删除的笔记,如果失败则抛出异常 if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { throw new ActionFailureException("failed to batch-delete local deleted notes"); } } - // refresh local sync id + // 刷新本地同步ID if (!mCancelled) { - GTaskClient.getInstance().commitUpdate(); - refreshLocalSyncId(); + GTaskClient.getInstance().commitUpdate(); // 提交更新 + refreshLocalSyncId(); // 刷新本地同步ID } } + /** + * 同步文件夹数据。 + * 该方法负责同步根文件夹、通话记录文件夹以及本地和远程存在的文件夹。 + * 它会根据文件夹的当前状态(是否存在、是否已同步)采取相应的同步操作,如添加、更新或删除。 + * + * @throws NetworkFailureException 如果网络操作失败 + */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -361,7 +429,7 @@ public class GTaskManager { return; } - // for root folder + // 同步根文件夹 try { c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); @@ -369,11 +437,12 @@ public class GTaskManager { c.moveToNext(); gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); + // 判断节点是否为空,不为空则更新,为空则添加 if (node != null) { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // for system folder, only update remote name if necessary + // 仅当系统文件夹的名称需要更新时执行内容同步 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); @@ -390,22 +459,22 @@ public class GTaskManager { } } - // for call-note folder + // 同步通话记录文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + new String[]{ + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) }, null); if (c != null) { if (c.moveToNext()) { gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); + // 判断节点是否为空,不为空则更新,为空则添加 if (node != null) { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); - // for system folder, only update remote name if - // necessary + // 仅当系统文件夹的名称需要更新时执行内容同步 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) @@ -424,16 +493,17 @@ public class GTaskManager { } } - // for local existing folders + // 同步本地已存在的文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type=? AND parent_id<>?)", new String[] { + "(type=? AND parent_id<>?)", new String[]{ String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) }, NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); + // 判断节点是否为空,不为空则更新,为空则根据情况添加或删除 if (node != null) { mGTaskHashMap.remove(gid); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); @@ -441,10 +511,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add + // 本地添加 syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete + // 远程删除 syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -460,7 +530,7 @@ public class GTaskManager { } } - // for remote add folders + // 同步远程添加的文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -476,59 +546,78 @@ public class GTaskManager { GTaskClient.getInstance().commitUpdate(); } + /** + * 根据指定的同步类型,对节点内容进行同步操作。 + * + * @param syncType 同步操作的类型,决定是添加、删除、还是更新节点。 + * @param node 要进行同步操作的节点。 + * @param c 游标,用于在本地数据库操作时获取额外信息(在删除操作中使用)。 + * @throws NetworkFailureException 如果网络操作失败,则抛出此异常。 + */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { + if (mCancelled) { // 检查是否已取消同步操作 return; } MetaData meta; switch (syncType) { - case Node.SYNC_ACTION_ADD_LOCAL: + case Node.SYNC_ACTION_ADD_LOCAL: // 添加本地节点 addLocalNode(node); break; - case Node.SYNC_ACTION_ADD_REMOTE: + case Node.SYNC_ACTION_ADD_REMOTE: // 添加远程节点 addRemoteNode(node, c); break; - case Node.SYNC_ACTION_DEL_LOCAL: + case Node.SYNC_ACTION_DEL_LOCAL: // 删除本地节点 meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); if (meta != null) { - GTaskClient.getInstance().deleteNode(meta); + GTaskClient.getInstance().deleteNode(meta); // 从服务器删除节点 } - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 记录已删除的本地节点ID break; - case Node.SYNC_ACTION_DEL_REMOTE: + case Node.SYNC_ACTION_DEL_REMOTE: // 删除远程节点 meta = mMetaHashMap.get(node.getGid()); if (meta != null) { - GTaskClient.getInstance().deleteNode(meta); + GTaskClient.getInstance().deleteNode(meta); // 从服务器删除节点 } - GTaskClient.getInstance().deleteNode(node); + GTaskClient.getInstance().deleteNode(node); // 直接从本地数据库删除节点 break; - case Node.SYNC_ACTION_UPDATE_LOCAL: + case Node.SYNC_ACTION_UPDATE_LOCAL: // 更新本地节点 updateLocalNode(node, c); break; - case Node.SYNC_ACTION_UPDATE_REMOTE: + case Node.SYNC_ACTION_UPDATE_REMOTE: // 更新远程节点 updateRemoteNode(node, c); break; - case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply + case Node.SYNC_ACTION_UPDATE_CONFLICT: // 处理更新冲突 + // 目前简单地采用本地更新,未来可能需要合并双方修改 updateRemoteNode(node, c); break; - case Node.SYNC_ACTION_NONE: + case Node.SYNC_ACTION_NONE: // 无操作 break; - case Node.SYNC_ACTION_ERROR: + case Node.SYNC_ACTION_ERROR: // 默认错误处理 default: - throw new ActionFailureException("unkown sync action type"); + throw new ActionFailureException("unkown sync action type"); // 抛出未知同步操作类型的异常 } } + + /** + * 将本地节点添加到数据库中。 + * 该方法首先检查操作是否已被取消,然后根据节点类型(任务列表或任务)创建相应的 SqlNote 对象。 + * 对于任务列表节点,会根据节点名称(默认文件夹或通话记录文件夹)设置特殊的 SqlNote 属性; + * 对于任务节点,会从节点内容中创建 JSON 对象,并根据需要调整其中的 ID 字段(如果这些 ID 在数据库中已存在)。 + * 最后,该方法会将 SqlNote 对象提交到数据库,并更新相关的 ID 映射关系。 + * + * @param node 要添加的本地节点,不应为 null。 + * @throws NetworkFailureException 如果添加节点过程中检测到网络失败。 + */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { - return; + return; // 如果操作已取消,则直接返回,不执行添加操作 } SqlNote sqlNote; if (node instanceof TaskList) { + // 处理任务列表节点,根据节点名称设置特殊的 SqlNote 属性 if (node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); @@ -541,15 +630,17 @@ public class GTaskManager { sqlNote.setParentId(Notes.ID_ROOT_FOLDER); } } else { + // 处理任务节点,从节点内容创建 JSON 对象,并根据需要调整 ID 字段 sqlNote = new SqlNote(mContext); JSONObject js = node.getLocalJSONFromContent(); try { + // 检查并处理 JSON 对象中的 Note 和 Data ID 字段 if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one + // 如果笔记 ID 已存在,则移除该 ID note.remove(NoteColumns.ID); } } @@ -562,13 +653,11 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one + // 如果数据 ID 已存在,则移除该 ID data.remove(DataColumns.ID); } } } - } } catch (JSONException e) { Log.w(TAG, e.toString()); @@ -576,6 +665,7 @@ public class GTaskManager { } sqlNote.setContent(js); + // 设置节点的父 ID Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); if (parentId == null) { Log.e(TAG, "cannot find task's parent id locally"); @@ -584,28 +674,36 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // create the local node + // 提交 SqlNote 到数据库,并更新 ID 映射关系 sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // update gid-nid mapping mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta + // 更新远程元数据 updateRemoteMeta(node.getGid(), sqlNote); } + + /** + * 更新本地节点信息 + * + * @param node 需要更新的节点 + * @param c 数据库游标,用于操作数据库 + * @throws NetworkFailureException 如果网络操作失败,则抛出此异常 + */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } SqlNote sqlNote; - // update the note locally + // 根据节点内容更新本地数据库中的笔记 sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); + // 确定父节点ID,任务则查找父任务ID,否则默认为根文件夹ID Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) : new Long(Notes.ID_ROOT_FOLDER); if (parentId == null) { @@ -615,10 +713,17 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // update meta info + // 更新远程元数据 updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 在远程添加节点信息 + * + * @param node 需要添加的节点 + * @param c 数据库游标,用于操作数据库 + * @throws NetworkFailureException 如果网络操作失败,则抛出此异常 + */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -627,11 +732,12 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); Node n; - // update remotely + // 如果是任务类型,则远程创建任务;否则,根据条件判断是否需要创建新的任务列表 if (sqlNote.isNoteType()) { Task task = new Task(); task.setContentByLocalJSON(sqlNote.getContent()); + // 查找任务所属的任务列表ID String parentGid = mNidToGid.get(sqlNote.getParentId()); if (parentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); @@ -642,12 +748,12 @@ public class GTaskManager { GTaskClient.getInstance().createTask(task); n = (Node) task; - // add meta + // 更新远程元数据 updateRemoteMeta(task.getGid(), sqlNote); } else { TaskList tasklist = null; - // we need to skip folder if it has already existed + // 判断是否需要创建新的任务列表 String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -656,6 +762,7 @@ public class GTaskManager { else folderName += sqlNote.getSnippet(); + // 在已有的任务列表中查找匹配的条目 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -671,7 +778,7 @@ public class GTaskManager { } } - // no match we can add now + // 如果没有找到匹配的任务列表,则创建新的任务列表 if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -681,43 +788,52 @@ public class GTaskManager { n = (Node) tasklist; } - // update local note + // 更新本地数据库中的笔记信息 sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id mapping + // 更新GID和ID的映射关系 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } + + /** + * 更新远程节点信息。 + * + * @param node 需要更新的节点 + * @param c 数据库游标,用于获取节点的详细信息 + * @throws NetworkFailureException 如果网络操作失败,则抛出此异常 + */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { + if (mCancelled) { // 检查是否已取消操作 return; } - SqlNote sqlNote = new SqlNote(mContext, c); + SqlNote sqlNote = new SqlNote(mContext, c); // 从数据库游标中创建 SqlNote 对象 - // update remotely + // 使用本地 JSON 格式更新远程节点内容 node.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(node); + GTaskClient.getInstance().addUpdateNode(node); // 将节点添加到更新队列 - // update meta + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); - // move task if necessary + // 如果是笔记类型,检查并移动任务 if (sqlNote.isNoteType()) { Task task = (Task) node; - TaskList preParentList = task.getParent(); + TaskList preParentList = task.getParent(); // 获取任务的当前父任务列表 - String curParentGid = mNidToGid.get(sqlNote.getParentId()); + String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 获取当前父任务列表的 GID if (curParentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); throw new ActionFailureException("cannot update remote task"); } - TaskList curParentList = mGTaskListHashMap.get(curParentGid); + TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 获取当前父任务列表对象 + // 如果任务的父任务列表发生变化,则移动任务 if (preParentList != curParentList) { preParentList.removeChildTask(task); curParentList.addChildTask(task); @@ -725,18 +841,27 @@ public class GTaskManager { } } - // clear local modified flag + // 重置本地修改标志,并提交更改 sqlNote.resetLocalModified(); sqlNote.commit(true); } + /** + * 更新远程元数据。 + * + * @param gid 元数据的全局标识符 + * @param sqlNote 包含元数据内容的 SqlNote 对象 + * @throws NetworkFailureException 如果网络操作失败,则抛出此异常 + */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { - if (sqlNote != null && sqlNote.isNoteType()) { - MetaData metaData = mMetaHashMap.get(gid); + if (sqlNote != null && sqlNote.isNoteType()) { // 确保是笔记类型 + MetaData metaData = mMetaHashMap.get(gid); // 尝试获取现有的元数据对象 if (metaData != null) { + // 更新元数据内容并加入更新队列 metaData.setMeta(gid, sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(metaData); } else { + // 如果元数据不存在,则创建新的元数据对象并添加到远程 metaData = new MetaData(); metaData.setMeta(gid, sqlNote.getContent()); mMetaList.addChildTask(metaData); @@ -746,12 +871,21 @@ public class GTaskManager { } } + + /** + * 刷新本地同步ID。 + * 该方法首先获取最新的gtask列表,然后通过查询本地笔记内容来更新这些笔记的同步ID。 + * 如果在查询过程中发现有本地项目在同步后没有对应的gtask ID,则抛出ActionFailureException异常。 + * + * @throws NetworkFailureException 如果网络操作失败。 + * @throws ActionFailureException 如果在同步后发现有本地项目没有对应的gtask ID。 + */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list + // 清空现有的gtask列表和元数据,准备获取最新的数据 mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -759,8 +893,9 @@ public class GTaskManager { Cursor c = null; try { + // 查询本地笔记内容,准备更新同步ID c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type<>? AND parent_id<>?)", new String[] { + "(type<>? AND parent_id<>?)", new String[]{ String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) }, NoteColumns.TYPE + " DESC"); if (c != null) { @@ -774,15 +909,18 @@ public class GTaskManager { mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(SqlNote.ID_COLUMN)), values, null, null); } else { + // 如果查询到的笔记没有对应的gtask ID,则抛出异常 Log.e(TAG, "something is missed"); throw new ActionFailureException( "some local items don't have gid after sync"); } } } else { + // 如果查询操作失败,记录警告信息 Log.w(TAG, "failed to query local note to refresh sync id"); } } finally { + // 释放Cursor资源 if (c != null) { c.close(); c = null; @@ -790,11 +928,21 @@ public class GTaskManager { } } + /** + * 获取同步账户的名称。 + * + * @return 同步账户的名称。 + */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } + /** + * 取消同步操作。 + * 设置取消标志,终止正在进行的同步操作。 + */ public void cancelSync() { mCancelled = true; } + } diff --git a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java index cca36f7..a36ffbc 100644 --- a/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/xiaomi-src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -1,19 +1,6 @@ /* - * 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. + * GTaskSyncService类用于处理与Google任务同步相关的服务操作。 */ - package net.micode.notes.gtask.remote; import android.app.Activity; @@ -24,28 +11,42 @@ import android.os.Bundle; import android.os.IBinder; public class GTaskSyncService extends Service { + // 同步操作的类型 public final static String ACTION_STRING_NAME = "sync_action_type"; + // 启动同步 public final static int ACTION_START_SYNC = 0; + // 取消同步 public final static int ACTION_CANCEL_SYNC = 1; + // 无效操作 public final static int ACTION_INVALID = 2; + // 服务广播的名称 public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + // 广播中是否正在同步的标志 public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + // 广播中的同步进度消息 public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + // 静态变量用于存储当前同步任务实例 private static GTaskASyncTask mSyncTask = null; + // 存储同步进度的字符串 private static String mSyncProgress = ""; + /* + * 启动同步任务。 + * 如果当前没有同步任务在执行,将创建一个新的同步任务并执行。 + */ private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { public void onComplete() { + // 同步任务完成时的处理:重置静态变量,发送广播,停止服务 mSyncTask = null; sendBroadcast(""); stopSelf(); @@ -56,17 +57,27 @@ public class GTaskSyncService extends Service { } } + /* + * 取消当前的同步任务。 + */ private void cancelSync() { if (mSyncTask != null) { mSyncTask.cancelSync(); } } + /* + * 服务创建时的初始化操作,重置同步任务为null。 + */ @Override public void onCreate() { mSyncTask = null; } + /* + * 处理服务启动时的命令。 + * 根据传入的意图参数决定是启动同步还是取消同步。 + */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Bundle bundle = intent.getExtras(); @@ -86,6 +97,9 @@ public class GTaskSyncService extends Service { return super.onStartCommand(intent, flags, startId); } + /* + * 低内存时取消同步任务。 + */ @Override public void onLowMemory() { if (mSyncTask != null) { @@ -93,10 +107,15 @@ public class GTaskSyncService extends Service { } } + // 服务绑定时返回null,此服务不提供绑定功能 public IBinder onBind(Intent intent) { return null; } + /* + * 发送同步状态的广播。 + * 更新同步进度,并通过广播发送当前的同步状态和进度消息。 + */ public void sendBroadcast(String msg) { mSyncProgress = msg; Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); @@ -105,6 +124,10 @@ public class GTaskSyncService extends Service { sendBroadcast(intent); } + /* + * 从Activity启动同步。 + * 设置活动上下文并启动同步服务。 + */ public static void startSync(Activity activity) { GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); @@ -112,16 +135,28 @@ public class GTaskSyncService extends Service { activity.startService(intent); } + /* + * 从Context取消同步。 + * 发送取消同步的意图到服务。 + */ public static void cancelSync(Context context) { Intent intent = new Intent(context, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); context.startService(intent); } + /* + * 检查是否正在同步。 + * 返回当前是否有一个同步任务在执行。 + */ public static boolean isSyncing() { return mSyncTask != null; } + /* + * 获取同步进度的字符串。 + * 返回当前同步任务的进度消息。 + */ public static String getProgressString() { return mSyncProgress; } diff --git a/xiaomi-src/main/java/net/micode/notes/model/Note.java b/xiaomi-src/main/java/net/micode/notes/model/Note.java index 0ccf19f..9734a4c 100644 --- a/xiaomi-src/main/java/net/micode/notes/model/Note.java +++ b/xiaomi-src/main/java/net/micode/notes/model/Note.java @@ -15,6 +15,7 @@ */ package net.micode.notes.model; + import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; @@ -38,16 +39,20 @@ public class Note { private ContentValues mNoteDiffValues; private NoteData mNoteData; private static final String TAG = "Note"; + /** - * Create a new note id for adding a new note to databases + * 为数据库中添加新笔记生成一个新的笔记ID + * + * @param context 上下文对象,用于访问应用程序的资源和其他内容提供者 + * @param folderId 文件夹ID,表示新笔记将被添加到的文件夹 + * @return 新创建的笔记的ID */ public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database + // 在数据库中创建一个新的笔记 ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); values.put(NoteColumns.CREATED_DATE, createdTime); values.put(NoteColumns.MODIFIED_DATE, createdTime); - //values.put(NoteColumns.TOP,"1"); values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); values.put(NoteColumns.LOCAL_MODIFIED, 1); values.put(NoteColumns.PARENT_ID, folderId); @@ -57,11 +62,11 @@ public class Note { try { noteId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); + Log.e(TAG, "获取笔记ID错误 :" + e.toString()); noteId = 0; } if (noteId == -1) { - throw new IllegalStateException("Wrong note id:" + noteId); + throw new IllegalStateException("错误的笔记ID:" + noteId); } return noteId; } @@ -71,59 +76,96 @@ public class Note { mNoteData = new NoteData(); } + /** + * 设置笔记的值 + * + * @param key 设置的字段名 + * @param value 设置的字段值 + */ public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } - public void setTopValue(String key, String value) { - mNoteDiffValues.put(key, value); - } - + /** + * 设置文本数据 + * + * @param key 设置的字段名 + * @param value 设置的字段值 + */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + /** + * 设置文本数据ID + * + * @param id 文本数据的ID + */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + /** + * 获取文本数据ID + * + * @return 文本数据的ID + */ public long getTextDataId() { return mNoteData.mTextDataId; } + /** + * 设置通话数据ID + * + * @param id 通话数据的ID + */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + /** + * 设置通话数据 + * + * @param key 设置的字段名 + * @param value 设置的字段值 + */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + /** + * 检查笔记是否被本地修改 + * + * @return 如果笔记被本地修改则返回true,否则返回false + */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + /** + * 同步笔记到数据库 + * + * @param context 上下文对象,用于访问应用程序的资源和其他内容提供者 + * @param noteId 需要同步的笔记ID + * @return 如果同步成功则返回true,否则返回false + */ public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); + throw new IllegalArgumentException("错误的笔记ID:" + noteId); } if (!isLocalModified()) { return true; } - /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info - */ + // 理论上,一旦数据改变,笔记应该在本地修改标记和修改日期上更新。为了数据安全,即使更新笔记失败,我们也更新笔记的数据信息 if (context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, null) == 0) { - Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through + Log.e(TAG, "更新笔记错误,不应该发生"); + // 不返回,继续执行 } mNoteDiffValues.clear(); @@ -135,13 +177,14 @@ public class Note { return true; } + /** + * 内部类NoteData,用于管理笔记的文本数据和通话数据 + */ private class NoteData { private long mTextDataId; private ContentValues mTextDataValues; - private ContentValues mTopValues; - private long mCallDataId; private ContentValues mCallDataValues; @@ -151,53 +194,86 @@ public class Note { public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); - mTopValues = new ContentValues(); mTextDataId = 0; mCallDataId = 0; } + /** + * 检查数据是否被本地修改 + * + * @return 如果数据被本地修改则返回true,否则返回false + */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + /** + * 设置文本数据ID + * + * @param id 文本数据的ID + */ void setTextDataId(long id) { - if(id <= 0) { - throw new IllegalArgumentException("Text data id should larger than 0"); + if (id <= 0) { + throw new IllegalArgumentException("文本数据ID应该大于0"); } mTextDataId = id; } + /** + * 设置通话数据ID + * + * @param id 通话数据的ID + */ void setCallDataId(long id) { if (id <= 0) { - throw new IllegalArgumentException("Call data id should larger than 0"); + throw new IllegalArgumentException("通话数据ID应该大于0"); } mCallDataId = id; } + /** + * 设置通话数据 + * + * @param key 设置的字段名 + * @param value 设置的字段值 + */ void setCallData(String key, String value) { mCallDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置文本数据 + * + * @param key 设置的字段名 + * @param value 设置的字段值 + */ void setTextData(String key, String value) { mTextDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 将数据推送到内容解析器 + * + * @param context 上下文对象,用于访问应用程序的资源和其他内容提供者 + * @param noteId 笔记的ID + * @return 如果推送成功则返回Uri,否则返回null + */ Uri pushIntoContentResolver(Context context, long noteId) { /** - * Check for safety + * 安全性检查 */ if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); + throw new IllegalArgumentException("错误的笔记ID:" + noteId); } ArrayList operationList = new ArrayList(); ContentProviderOperation.Builder builder = null; - if(mTextDataValues.size() > 0) { + if (mTextDataValues.size() > 0) { mTextDataValues.put(DataColumns.NOTE_ID, noteId); if (mTextDataId == 0) { mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); @@ -206,7 +282,7 @@ public class Note { try { setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { - Log.e(TAG, "Insert new text data fail with noteId" + noteId); + Log.e(TAG, "插入新的文本数据失败,笔记ID" + noteId); mTextDataValues.clear(); return null; } @@ -219,7 +295,7 @@ public class Note { mTextDataValues.clear(); } - if(mCallDataValues.size() > 0) { + if (mCallDataValues.size() > 0) { mCallDataValues.put(DataColumns.NOTE_ID, noteId); if (mCallDataId == 0) { mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); @@ -228,7 +304,7 @@ public class Note { try { setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { - Log.e(TAG, "Insert new call data fail with noteId" + noteId); + Log.e(TAG, "插入新的通话数据失败,笔记ID" + noteId); mCallDataValues.clear(); return null; } @@ -259,3 +335,4 @@ public class Note { } } } + diff --git a/xiaomi-src/main/java/net/micode/notes/model/WorkingNote.java b/xiaomi-src/main/java/net/micode/notes/model/WorkingNote.java index cb16126..6220068 100644 --- a/xiaomi-src/main/java/net/micode/notes/model/WorkingNote.java +++ b/xiaomi-src/main/java/net/micode/notes/model/WorkingNote.java @@ -29,49 +29,45 @@ 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.TextNote; -import net.micode.notes.data.NotesProvider; import net.micode.notes.tool.ResourceParser.NoteBgResources; - +// WorkingNote类用于管理笔记的相关信息 public class WorkingNote { - // Note for the working note + // 笔记对象,包含笔记的详细信息 private Note mNote; - // Note Id + // 笔记的唯一标识符 private long mNoteId; - // Note content + // 笔记的内容 private String mContent; - // Note mode + // 笔记的模式,例如普通、草稿等 private int mMode; + // 设置提醒日期的时间戳 private long mAlertDate; - + // 笔记最后修改日期的时间戳 private long mModifiedDate; - + // 笔记背景颜色的资源ID private int mBgColorId; - + // 小部件的ID private int mWidgetId; - + // 小部件的类型 private int mWidgetType; - + // 笔记所属文件夹的ID private long mFolderId; - + // 上下文对象,用于访问应用的环境信息 private Context mContext; + // 日志标签,用于Log输出 private static final String TAG = "WorkingNote"; + // 标记笔记是否被删除 private boolean mIsDeleted; - - /**记录已置顶便签*/ - private String mTop = "0"; - - /**记录便签所属类别*/ - private int mClass; - - + // 笔记设置变化监听器 private NoteSettingChangedListener mNoteSettingStatusListener; - public static final String[] DATA_PROJECTION = new String[] { + // 定义一个静态数组,用于在查询时投影数据列 + public static final String[] DATA_PROJECTION = new String[]{ DataColumns.ID, DataColumns.CONTENT, DataColumns.MIME_TYPE, @@ -81,39 +77,53 @@ public class WorkingNote { DataColumns.DATA4, }; - public static final String[] NOTE_PROJECTION = new String[] { + + // 定义查询Note表时需要投影的列 + public static final String[] NOTE_PROJECTION = new String[]{ NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, - NoteColumns.MODIFIED_DATE, - NoteColumns.TOP, + NoteColumns.MODIFIED_DATE }; + // 数据ID列的索引 private static final int DATA_ID_COLUMN = 0; + // 数据内容列的索引 private static final int DATA_CONTENT_COLUMN = 1; + // 数据MIME类型列的索引 private static final int DATA_MIME_TYPE_COLUMN = 2; + // 数据模式列的索引 private static final int DATA_MODE_COLUMN = 3; + // Note表中父ID列的索引 private static final int NOTE_PARENT_ID_COLUMN = 0; + // Note表中提醒日期列的索引 private static final int NOTE_ALERTED_DATE_COLUMN = 1; + // Note表中背景颜色ID列的索引 private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + // Note表中Widget ID列的索引 private static final int NOTE_WIDGET_ID_COLUMN = 3; + // Note表中Widget类型列的索引 private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + // Note表中修改日期列的索引 private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - private static final int NOTE_TOP_COLUMN = 6; - - // New note construct + /** + * 新建笔记的构造函数 + * + * @param context 上下文对象,用于访问应用全局功能 + * @param folderId 文件夹ID,表示该笔记所属的文件夹 + */ private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -124,10 +134,15 @@ public class WorkingNote { mIsDeleted = false; mMode = 0; mWidgetType = Notes.TYPE_WIDGET_INVALIDE; - mTop = String.valueOf(getTopId()); } - // Existing note construct + /** + * 已存在笔记的构造函数 + * + * @param context 上下文对象,用于访问应用全局功能 + * @param noteId 笔记ID,表示该笔记的唯一标识 + * @param folderId 文件夹ID,表示该笔记所属的文件夹 + */ private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; @@ -137,59 +152,93 @@ public class WorkingNote { loadNote(); } + + /** + * 加载指定笔记的信息。 + * 从数据库中查询指定ID的笔记的详细信息,并更新当前实例的状态。 + * 注意:此方法不处理查询失败或笔记不存在的情况。 + */ private void loadNote() { + // 查询指定ID的笔记信息 Cursor cursor = mContext.getContentResolver().query( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, null, null); if (cursor != null) { + // 如果查询结果不为空,尝试读取数据 if (cursor.moveToFirst()) { + // 从查询结果中获取笔记的各个属性 mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); - mTop = cursor.getString(NOTE_TOP_COLUMN); } + // 关闭查询结果集 cursor.close(); } else { + // 如果查询结果为空,记录错误并抛出异常 Log.e(TAG, "No note with id:" + mNoteId); throw new IllegalArgumentException("Unable to find note with id " + mNoteId); } + // 加载笔记的附加数据,如内容、设置等 loadNoteData(); } + /** + * 加载笔记的附加数据。 + * 从数据库中查询指定ID笔记的附加信息(例如内容、设置等),并更新当前实例的状态。 + * 注意:此方法不处理查询失败或笔记数据不存在的情况。 + */ private void loadNoteData() { + // 查询指定笔记ID的附加数据 Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, - DataColumns.NOTE_ID + "=?", new String[] { - String.valueOf(mNoteId) + DataColumns.NOTE_ID + "=?", new String[]{ + String.valueOf(mNoteId) }, null); if (cursor != null) { + // 如果查询结果不为空,尝试读取数据 if (cursor.moveToFirst()) { do { + // 根据数据类型处理不同的笔记内容 String type = cursor.getString(DATA_MIME_TYPE_COLUMN); if (DataConstants.NOTE.equals(type)) { + // 处理普通笔记内容 mContent = cursor.getString(DATA_CONTENT_COLUMN); mMode = cursor.getInt(DATA_MODE_COLUMN); mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); } else if (DataConstants.CALL_NOTE.equals(type)) { + // 处理通话笔记内容 mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); } else { + // 记录错误的笔记类型 Log.d(TAG, "Wrong note type with type:" + type); } } while (cursor.moveToNext()); } + // 关闭查询结果集 cursor.close(); } else { + // 如果查询结果为空,记录错误并抛出异常 Log.e(TAG, "No data with id:" + mNoteId); throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); } } + /** + * 创建一个新的空笔记。 + * + * @param context 上下文对象,用于访问应用资源和内容提供者。 + * @param folderId 笔记所属文件夹的ID。 + * @param widgetId 与笔记关联的小部件ID。 + * @param widgetType 与笔记关联的小部件类型。 + * @param defaultBgColorId 笔记的默认背景颜色ID。 + * @return 返回一个初始化好的空笔记对象。 + */ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, - int widgetType, int defaultBgColorId) { + int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); note.setBgColorId(defaultBgColorId); note.setWidgetId(widgetId); @@ -197,24 +246,40 @@ public class WorkingNote { return note; } + /** + * 根据笔记ID加载笔记。 + * + * @param context 上下文对象,用于访问应用资源和内容提供者。 + * @param id 要加载的笔记的ID。 + * @return 返回一个根据指定ID加载的笔记对象。 + */ public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } + + /** + * 保存笔记到数据库。 + * 如果笔记值得保存(即内容非空且未被标记为删除),且笔记不存在于数据库中或已存在于数据库但本地有修改,则进行保存操作。 + * 如果笔记存在对应的小部件,会更新小部件内容。 + * + * @return 如果保存成功返回true,否则返回false。 + */ public synchronized boolean saveNote() { + // 判断是否值得保存该笔记 if (isWorthSaving()) { + // 检查数据库中是否已存在该笔记 if (!existInDatabase()) { + // 为笔记生成新的ID if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { Log.e(TAG, "Create new note fail with id:" + mNoteId); return false; } } - mNote.syncNote(mContext, mNoteId); + mNote.syncNote(mContext, mNoteId); // 同步笔记到数据库 - /** - * Update widget content if there exist any widget of this note - */ + // 如果存在对应的小部件,更新小部件内容 if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { @@ -226,11 +291,23 @@ public class WorkingNote { } } + /** + * 检查笔记是否已存在于数据库中。 + * + * @return 如果笔记ID大于0,表示已存在于数据库,返回true;否则返回false。 + */ public boolean existInDatabase() { return mNoteId > 0; } + /** + * 判断笔记是否值得被保存。 + * 笔记不值得保存的情况包括:已被标记为删除、不存在于数据库中且内容为空、存在于数据库但未本地修改。 + * + * @return 如果笔记值得保存返回true,否则返回false。 + */ private boolean isWorthSaving() { + // 判断笔记是否值得保存 if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { return false; @@ -239,21 +316,23 @@ public class WorkingNote { } } + /** + * 设置笔记设置状态监听器。 + * + * @param l 笔记设置状态监听器对象。 + */ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } - public void setTop(String Top){ - if (!mTop.equals(Top)) { - mTop = Top; - mNote.setTopValue(NoteColumns.TOP,mTop); - } - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onTopChanged(Top); - } - } - + /** + * 设置提醒日期,并根据需要触发状态监听器。 + * + * @param date 设置的提醒日期。 + * @param set 是否设置提醒。 + */ public void setAlertDate(long date, boolean set) { + // 更新提醒日期并触发监听器 if (date != mAlertDate) { mAlertDate = date; mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); @@ -263,15 +342,27 @@ public class WorkingNote { } } + /** + * 标记笔记为已删除,并根据需要触发小部件变更监听器。 + * + * @param mark 是否标记为已删除。 + */ public void markDeleted(boolean mark) { mIsDeleted = mark; + // 如果存在对应的小部件,触发小部件变更监听器 if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); + mNoteSettingStatusListener.onWidgetChanged(); } } + /** + * 设置笔记背景颜色ID,并根据需要触发监听器。 + * + * @param id 背景颜色的资源ID。 + */ public void setBgColorId(int id) { + // 更新背景颜色ID并触发监听器 if (id != mBgColorId) { mBgColorId = id; if (mNoteSettingStatusListener != null) { @@ -281,130 +372,217 @@ public class WorkingNote { } } + + /** + * 设置勾选列表模式 + * + * @param mode 模式值 + */ public void setCheckListMode(int mode) { if (mMode != mode) { + // 当前模式与新模式不同时,通知监听器模式发生变化 if (mNoteSettingStatusListener != null) { mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); } mMode = mode; + // 更新笔记中的模式值 mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); } } + /** + * 设置小部件类型 + * + * @param type 小部件类型值 + */ public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; + // 更新笔记中小部件类型的值 mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); } } + /** + * 设置小部件ID + * + * @param id 小部件ID + */ public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; + // 更新笔记中小部件ID的值 mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); } } + /** + * 设置工作文本 + * + * @param text 工作文本内容 + */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; + // 更新笔记中的文本内容 mNote.setTextData(DataColumns.CONTENT, mContent); } } - public void setmContent(String text){ - mContent = text; - } - + /** + * 转换为通话笔记 + * + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + */ public void convertToCallNote(String phoneNumber, long callDate) { + // 设置通话日期和电话号码,并关联至通话记录文件夹 mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); } + /** + * 检查是否有定时提醒 + * + * @return true表示设置了定时提醒,false表示未设置 + */ public boolean hasClockAlert() { return (mAlertDate > 0 ? true : false); } - public int getTopId() { - if (mTop.equals("1")) { - return 1; - } else { - return 0; - } - } - + /** + * 获取内容文本 + * + * @return 笔记内容 + */ public String getContent() { return mContent; } + /** + * 获取提醒日期 + * + * @return 提醒日期时间戳 + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取最后修改日期 + * + * @return 最后修改日期时间戳 + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取背景颜色资源ID + * + * @return 背景颜色资源ID + */ public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } + /** + * 获取背景颜色ID + * + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取标题背景资源ID + * + * @return 标题背景资源ID + */ public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } + /** + * 获取当前勾选列表模式 + * + * @return 勾选列表模式值 + */ public int getCheckListMode() { return mMode; } + /** + * 获取笔记ID + * + * @return 笔记ID + */ public long getNoteId() { return mNoteId; } + /** + * 获取文件夹ID + * + * @return 文件夹ID + */ public long getFolderId() { return mFolderId; } + /** + * 获取小部件ID + * + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取小部件类型 + * + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + public void setmContent(String text){//qxq:一个简单的文本信息传递 + mContent = text; + } + + /** + * 笔记设置变化监听器接口 + */ public interface NoteSettingChangedListener { /** - * Called when the background color of current note has just changed + * 当前笔记的背景颜色发生变化时调用 */ void onBackgroundColorChanged(); /** - * Called when user set top - */ - void onTopChanged(String Top); - - - /** - * Called when user set clock + * 用户设置定时提醒时调用 + * + * @param date 提醒日期 + * @param set 是否设置提醒 */ void onClockAlertChanged(long date, boolean set); /** - * Call when user create note from widget + * 用户从小部件创建笔记时调用 */ void onWidgetChanged(); /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode + * 切换勾选列表模式和普通模式时调用 + * + * @param oldMode 切换前的模式 + * @param newMode 切换后的模式 */ void onCheckListModeChanged(int oldMode, int newMode); } + } diff --git a/xiaomi-src/main/java/net/micode/notes/tool/BackupUtils.java b/xiaomi-src/main/java/net/micode/notes/tool/BackupUtils.java index 39f6ec4..8fc1c3f 100644 --- a/xiaomi-src/main/java/net/micode/notes/tool/BackupUtils.java +++ b/xiaomi-src/main/java/net/micode/notes/tool/BackupUtils.java @@ -38,9 +38,15 @@ import java.io.PrintStream; public class BackupUtils { private static final String TAG = "BackupUtils"; - // Singleton stuff + // 单例模式相关变量 private static BackupUtils sInstance; + /** + * 获取BackupUtils的单例实例。 + * + * @param context 上下文对象,用于访问应用全局功能。 + * @return 返回BackupUtils的单例实例。 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -49,43 +55,71 @@ public class BackupUtils { } /** - * Following states are signs to represents backup or restore - * status + * 定义备份或恢复状态的标志。 */ - // Currently, the sdcard is not mounted - public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist - public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs - public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails - public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success - public static final int STATE_SUCCESS = 4; + // 当前,SD卡未挂载 + public static final int STATE_SD_CARD_UNMOUONTED = 0; + // 备份文件不存在 + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; + // 数据格式不正确,可能被其他程序更改 + public static final int STATE_DATA_DESTROIED = 2; + // 运行时异常导致恢复或备份失败 + public static final int STATE_SYSTEM_ERROR = 3; + // 备份或恢复成功 + public static final int STATE_SUCCESS = 4; private TextExport mTextExport; + /** + * BackupUtils的私有构造函数。 + * + * @param context 上下文对象,用于初始化文本导出功能。 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 检查外部存储是否可用。 + * + * @return 如果外部存储可用返回true,否则返回false。 + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 导出数据到文本文件。 + * + * @return 返回导出操作的状态码,详见STATE_*常量。 + */ public int exportToText() { return mTextExport.exportToText(); } + /** + * 获取导出的文本文件名。 + * + * @return 返回导出文本文件的名称。 + */ public String getExportedTextFileName() { return mTextExport.mFileName; } + /** + * 获取导出的文本文件目录。 + * + * @return 返回导出文本文件所在的目录。 + */ public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 内部类TextExport,用于执行文本导出操作。 + */ private static class TextExport { + // 查询笔记时需要的列 private static final String[] NOTE_PROJECTION = { NoteColumns.ID, NoteColumns.MODIFIED_DATE, @@ -93,12 +127,12 @@ public class BackupUtils { NoteColumns.TYPE }; + // 笔记列的索引 private static final int NOTE_COLUMN_ID = 0; - private static final int NOTE_COLUMN_MODIFIED_DATE = 1; - private static final int NOTE_COLUMN_SNIPPET = 2; + // 查询数据时需要的列 private static final String[] DATA_PROJECTION = { DataColumns.CONTENT, DataColumns.MIME_TYPE, @@ -108,52 +142,79 @@ public class BackupUtils { DataColumns.DATA4, }; + + // 定义数据列的内容索引 private static final int DATA_COLUMN_CONTENT = 0; + // 定义数据列的MIME类型索引 private static final int DATA_COLUMN_MIME_TYPE = 1; + // 定义数据列的呼叫日期索引 private static final int DATA_COLUMN_CALL_DATE = 2; + // 定义数据列的电话号码索引 private static final int DATA_COLUMN_PHONE_NUMBER = 4; - private final String [] TEXT_FORMAT; - private static final int FORMAT_FOLDER_NAME = 0; - private static final int FORMAT_NOTE_DATE = 1; - private static final int FORMAT_NOTE_CONTENT = 2; + // 用于导出笔记的文本格式数组 + private final String[] TEXT_FORMAT; + // 定义文本格式的索引:文件夹名称 + private static final int FORMAT_FOLDER_NAME = 0; + // 定义文本格式的索引:笔记日期 + private static final int FORMAT_NOTE_DATE = 1; + // 定义文本格式的索引:笔记内容 + private static final int FORMAT_NOTE_CONTENT = 2; + // 上下文对象,用于访问资源和内容解析器 private Context mContext; + // 文件名 private String mFileName; + // 文件目录 private String mFileDirectory; + /** + * 构造函数 + * + * @param context 上下文对象,通常是一个Activity或者Application对象 + */ public TextExport(Context context) { + // 初始化文本格式数组 TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; mFileName = ""; mFileDirectory = ""; } + /** + * 根据索引获取文本格式 + * + * @param id 索引ID + * @return 返回对应索引的文本格式 + */ private String getFormat(int id) { return TEXT_FORMAT[id]; } /** - * Export the folder identified by folder id to text + * 将指定文件夹的笔记导出为文本 + * + * @param folderId 文件夹ID + * @param ps 打印流,用于写入导出的文本内容 */ private void exportFolderToText(String folderId, PrintStream ps) { - // Query notes belong to this folder + // 查询属于该文件夹的笔记 Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { - folderId + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[]{ + folderId }, null); if (notesCursor != null) { if (notesCursor.moveToFirst()) { do { - // Print note's last modified date + // 打印笔记的最后修改日期 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 导出该笔记的内容到文本 String noteId = notesCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); @@ -162,13 +223,18 @@ public class BackupUtils { } } + /** - * Export note identified by id to a print stream + * 将指定id的笔记导出到打印流中 + * + * @param noteId 笔记的id + * @param ps 打印流,用于输出笔记内容 */ private void exportNoteToText(String noteId, PrintStream ps) { + // 查询指定id的笔记数据 Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, - DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - noteId + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[]{ + noteId }, null); if (dataCursor != null) { @@ -176,25 +242,25 @@ public class BackupUtils { do { String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); if (DataConstants.CALL_NOTE.equals(mimeType)) { - // Print phone number + // 处理通话记录类型的笔记 String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); String location = dataCursor.getString(DATA_COLUMN_CONTENT); + // 打印电话号码、通话时间、附件位置 if (!TextUtils.isEmpty(phoneNumber)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } - // Print call date ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); - // Print call attachment location if (!TextUtils.isEmpty(location)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); } } else if (DataConstants.NOTE.equals(mimeType)) { + // 处理普通笔记类型 String content = dataCursor.getString(DATA_COLUMN_CONTENT); if (!TextUtils.isEmpty(content)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), @@ -205,9 +271,9 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + // 在每个笔记内容之间打印一行分隔符 try { - ps.write(new byte[] { + ps.write(new byte[]{ Character.LINE_SEPARATOR, Character.LETTER_NUMBER }); } catch (IOException e) { @@ -216,20 +282,25 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将所有笔记以文本格式导出 + * + * @return 导出操作的状态码,成功返回STATE_SUCCESS,否则返回其他错误状态码 */ public int exportToText() { + // 检查外部存储器是否可用 if (!externalStorageAvailable()) { Log.d(TAG, "Media was not mounted"); return STATE_SD_CARD_UNMOUONTED; } + // 获取用于导出的打印流 PrintStream ps = getExportToTextPrintStream(); if (ps == null) { Log.e(TAG, "get print stream error"); return STATE_SYSTEM_ERROR; } - // First export folder and its notes + + // 首先导出文件夹及其包含的笔记 Cursor folderCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -240,9 +311,9 @@ public class BackupUtils { if (folderCursor != null) { if (folderCursor.moveToFirst()) { do { - // Print folder's name + // 打印文件夹名称 String folderName = ""; - if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { folderName = mContext.getString(R.string.call_record_folder_name); } else { folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); @@ -251,13 +322,14 @@ public class BackupUtils { ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); } String folderId = folderCursor.getString(NOTE_COLUMN_ID); + // 导出文件夹中的笔记 exportFolderToText(folderId, ps); } while (folderCursor.moveToNext()); } folderCursor.close(); } - // Export notes in root's folder + // 导出根文件夹中的笔记 Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -267,10 +339,11 @@ public class BackupUtils { if (noteCursor != null) { if (noteCursor.moveToFirst()) { do { + // 打印笔记的修改时间 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 导出笔记内容 String noteId = noteCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (noteCursor.moveToNext()); @@ -282,51 +355,72 @@ public class BackupUtils { return STATE_SUCCESS; } + /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 获取指向文件{@generateExportedTextFile}的打印流 + * 该方法尝试在SD卡上生成一个指定格式的文本文件,并返回指向该文件的PrintStream对象。 + * 如果文件生成失败,则返回null。 + * + * @return PrintStream 指向生成的文本文件的打印流,如果失败则返回null。 */ private PrintStream getExportToTextPrintStream() { + // 生成文件 File file = generateFileMountedOnSDcard(mContext, R.string.file_path, R.string.file_name_txt_format); if (file == null) { - Log.e(TAG, "create file to exported failed"); + Log.e(TAG, "create file to exported failed"); // 文件创建失败 return null; } + // 更新文件名和文件目录信息 mFileName = file.getName(); mFileDirectory = mContext.getString(R.string.file_path); + PrintStream ps = null; try { - FileOutputStream fos = new FileOutputStream(file); - ps = new PrintStream(fos); + FileOutputStream fos = new FileOutputStream(file); // 创建文件输出流 + ps = new PrintStream(fos); // 将文件输出流包装成PrintStream } catch (FileNotFoundException e) { e.printStackTrace(); - return null; + return null; // 文件未找到异常,返回null } catch (NullPointerException e) { e.printStackTrace(); - return null; + return null; // 空指针异常,返回null } - return ps; + return ps; // 返回PrintStream对象 } + } /** - * Generate the text file to store imported data + * 在SD卡上生成用于存储导入数据的文本文件 + * + * @param context 上下文对象,用于访问应用的资源和内容提供者 + * @param filePathResId 路径字符串资源ID,指定文件存储的路径 + * @param fileNameFormatResId 文件名格式字符串资源ID,用于生成带有日期的文件名 + * @return 返回创建的文件对象,如果创建失败则返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); + // 拼接SD卡的根目录 sb.append(Environment.getExternalStorageDirectory()); + // 拼接从资源id获取的路径字符串 sb.append(context.getString(filePathResId)); + // 创建文件目录对象 File filedir = new File(sb.toString()); + // 拼接文件名,文件名包含日期信息 sb.append(context.getString( fileNameFormatResId, DateFormat.format(context.getString(R.string.format_date_ymd), System.currentTimeMillis()))); + // 根据拼接的路径创建文件对象 File file = new File(sb.toString()); try { + // 如果文件目录不存在,则创建文件目录 if (!filedir.exists()) { filedir.mkdir(); } + // 如果文件不存在,则创建新文件 if (!file.exists()) { file.createNewFile(); } @@ -337,8 +431,10 @@ public class BackupUtils { e.printStackTrace(); } + // 如果遇到异常,返回null return null; } + } diff --git a/xiaomi-src/main/java/net/micode/notes/tool/DataUtils.java b/xiaomi-src/main/java/net/micode/notes/tool/DataUtils.java index 0487afa..4149710 100644 --- a/xiaomi-src/main/java/net/micode/notes/tool/DataUtils.java +++ b/xiaomi-src/main/java/net/micode/notes/tool/DataUtils.java @@ -16,7 +16,6 @@ package net.micode.notes.tool; -import android.app.Activity; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; @@ -26,27 +25,26 @@ import android.content.OperationApplicationException; import android.database.Cursor; import android.os.RemoteException; import android.util.Log; -import android.database.sqlite.SQLiteDatabase; -import android.widget.Toast; -import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.CallNote; import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.NotesDatabaseHelper; -import net.micode.notes.data.NotesProvider; -import net.micode.notes.gtask.data.SqlNote; -import net.micode.notes.model.Note; -import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import java.util.ArrayList; import java.util.HashSet; -import java.util.TreeSet; public class DataUtils { public static final String TAG = "DataUtils"; + + /** + * 批量删除笔记 + * + * @param resolver 内容解析器 + * @param ids 要删除的笔记ID集合 + * @return 如果删除成功或集合为空或为null,则返回true,否则返回false + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -57,9 +55,10 @@ public class DataUtils { return true; } + // 构建删除操作的列表 ArrayList operationList = new ArrayList(); for (long id : ids) { - if(id == Notes.ID_ROOT_FOLDER) { + if (id == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Don't delete system folder root"); continue; } @@ -69,6 +68,7 @@ public class DataUtils { } try { ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + // 检查删除结果 if (results == null || results.length == 0 || results[0] == null) { Log.d(TAG, "delete notes failed, ids:" + ids.toString()); return false; @@ -82,6 +82,14 @@ public class DataUtils { return false; } + /** + * 将笔记移动到指定文件夹 + * + * @param resolver 内容解析器 + * @param id 笔记ID + * @param srcFolderId 原始文件夹ID + * @param desFolderId 目标文件夹ID + */ public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -90,13 +98,22 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + /** + * 批量将笔记移动到指定文件夹 + * + * @param resolver 内容解析器 + * @param ids 要移动的笔记ID集合 + * @param folderId 目标文件夹ID + * @return 如果移动成功或集合为空或为null,则返回true,否则返回false + */ public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, - long folderId) { + long folderId) { if (ids == null) { Log.d(TAG, "the ids is null"); return true; } + // 构建更新操作的列表 ArrayList operationList = new ArrayList(); for (long id : ids) { ContentProviderOperation.Builder builder = ContentProviderOperation @@ -108,8 +125,9 @@ public class DataUtils { try { ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + // 检查移动结果 if (results == null || results.length == 0 || results[0] == null) { - Log.d(TAG, "delete notes failed, ids:" + ids.toString()); + Log.d(TAG, "move notes failed, ids:" + ids.toString()); return false; } return true; @@ -122,18 +140,21 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取除系统文件夹外的所有用户文件夹数量 + * + * @param resolver 内容解析器 + * @return 用户文件夹数量 */ public static int getUserFolderCount(ContentResolver resolver) { - Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { "COUNT(*)" }, + 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_FOLER)}, null); int count = 0; - if(cursor != null) { - if(cursor.moveToFirst()) { + if (cursor != null) { + if (cursor.moveToFirst()) { try { count = cursor.getInt(0); } catch (IndexOutOfBoundsException e) { @@ -146,11 +167,19 @@ public class DataUtils { return count; } + /** + * 检查指定类型的笔记在数据库中是否可见 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @param type 笔记类型 + * @return 如果可见,则返回true,否则返回false + */ 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, - new String [] {String.valueOf(type)}, + new String[]{String.valueOf(type)}, null); boolean exist = false; @@ -163,6 +192,13 @@ public class DataUtils { return exist; } + /** + * 检查指定的笔记ID在数据库中是否存在 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 如果存在,则返回true,否则返回false + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -177,6 +213,13 @@ public class DataUtils { return exist; } + /** + * 检查指定的数据ID在数据库中是否存在 + * + * @param resolver 内容解析器 + * @param dataId 数据ID + * @return 如果存在,则返回true,否则返回false + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -191,15 +234,22 @@ public class DataUtils { return exist; } + /** + * 检查文件夹名称是否在数据库中已存在(不包括系统文件夹) + * + * @param resolver 内容解析器 + * @param name 文件夹名称 + * @return 如果已存在,则返回true,否则返回false + */ 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.SNIPPET + "=?", - new String[] { name }, null); + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", + new String[]{name}, null); boolean exist = false; - if(cursor != null) { - if(cursor.getCount() > 0) { + if (cursor != null) { + if (cursor.getCount() > 0) { exist = true; } cursor.close(); @@ -207,11 +257,18 @@ public class DataUtils { return exist; } + /** + * 获取指定文件夹中的笔记小部件信息集合 + * + * @param resolver 内容解析器 + * @param folderId 文件夹ID + * @return 笔记小部件信息集合 + */ public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, + new String[]{NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE}, NoteColumns.PARENT_ID + "=?", - new String[] { String.valueOf(folderId) }, + new String[]{String.valueOf(folderId)}, null); HashSet set = null; @@ -234,11 +291,18 @@ public class DataUtils { return set; } + /** + * 通过笔记ID获取关联的通话号码 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 通话号码,如果未找到则返回空字符串 + */ public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.PHONE_NUMBER }, + new String[]{CallNote.PHONE_NUMBER}, CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", - new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, + new String[]{String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE}, null); if (cursor != null && cursor.moveToFirst()) { @@ -253,12 +317,20 @@ public class DataUtils { return ""; } + /** + * 根据电话号码和通话日期获取对应的笔记ID + * + * @param resolver 内容解析器 + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + * @return 笔记ID,未找到则返回0 + */ public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.NOTE_ID }, + new String[]{CallNote.NOTE_ID}, CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", - new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, + + CallNote.PHONE_NUMBER + ",?)", + new String[]{String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber}, null); if (cursor != null) { @@ -274,30 +346,49 @@ public class DataUtils { return 0; } + /** + * 根据笔记ID从数据库中获取笔记的摘要。 + * + * @param resolver 内容解析器,用于查询数据库。 + * @param noteId 笔记的ID,用于定位特定的笔记。 + * @return 笔记的摘要字符串。如果找不到对应的笔记,将抛出IllegalArgumentException。 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { + // 使用内容解析器查询特定ID的笔记的摘要 Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, - new String [] { NoteColumns.SNIPPET }, + new String[]{NoteColumns.SNIPPET}, NoteColumns.ID + "=?", - new String [] { String.valueOf(noteId)}, + new String[]{String.valueOf(noteId)}, null); if (cursor != null) { String snippet = ""; + // 如果查询结果不为空,尝试获取摘要 if (cursor.moveToFirst()) { snippet = cursor.getString(0); } + // 关闭游标 cursor.close(); return snippet; } + // 如果找不到指定ID的笔记,抛出异常 throw new IllegalArgumentException("Note is not found with id: " + noteId); } + /** + * 格式化摘要字符串。 + * 主要用于去除字符串两端的空白字符,以及截取至第一个换行符之前的内容。 + * + * @param snippet 需要格式化的摘要字符串。 + * @return 格式化后的摘要字符串。 + */ public static String getFormattedSnippet(String snippet) { + // 如果摘要字符串不为空,进行格式化处理 if (snippet != null) { - snippet = snippet.trim(); - int index = snippet.indexOf('\n'); + snippet = snippet.trim(); // 去除两端的空白字符 + int index = snippet.indexOf('\n'); // 查找第一个换行符的位置 if (index != -1) { - snippet = snippet.substring(0, index); + snippet = snippet.substring(0, index); // 截取至第一个换行符之前 } } return snippet; diff --git a/xiaomi-src/main/java/net/micode/notes/tool/GTaskStringUtils.java b/xiaomi-src/main/java/net/micode/notes/tool/GTaskStringUtils.java index 666b729..ea29672 100644 --- a/xiaomi-src/main/java/net/micode/notes/tool/GTaskStringUtils.java +++ b/xiaomi-src/main/java/net/micode/notes/tool/GTaskStringUtils.java @@ -1,113 +1,60 @@ /* - * 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. + * GTaskStringUtils 类定义 + * 该类提供了一系列与GTask相关的字符串常量,用于在操作GTask数据时标识各种JSON属性。 */ package net.micode.notes.tool; public class GTaskStringUtils { - public final static String GTASK_JSON_ACTION_ID = "action_id"; - - public final static String GTASK_JSON_ACTION_LIST = "action_list"; - - public final static String GTASK_JSON_ACTION_TYPE = "action_type"; - - public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; - - public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; - - public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; - - public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; - - public final static String GTASK_JSON_CREATOR_ID = "creator_id"; - - public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; - - public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; - - public final static String GTASK_JSON_COMPLETED = "completed"; - - public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; - - public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; - - public final static String GTASK_JSON_DELETED = "deleted"; - - public final static String GTASK_JSON_DEST_LIST = "dest_list"; - - public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; - - public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; - - public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; - - public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; - - public final static String GTASK_JSON_GET_DELETED = "get_deleted"; - - public final static String GTASK_JSON_ID = "id"; - - public final static String GTASK_JSON_INDEX = "index"; - - public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; - - public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; - - public final static String GTASK_JSON_LIST_ID = "list_id"; - - public final static String GTASK_JSON_LISTS = "lists"; - - public final static String GTASK_JSON_NAME = "name"; - - public final static String GTASK_JSON_NEW_ID = "new_id"; - - public final static String GTASK_JSON_NOTES = "notes"; - - public final static String GTASK_JSON_PARENT_ID = "parent_id"; - - public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; - - public final static String GTASK_JSON_RESULTS = "results"; - - public final static String GTASK_JSON_SOURCE_LIST = "source_list"; - - public final static String GTASK_JSON_TASKS = "tasks"; - - public final static String GTASK_JSON_TYPE = "type"; - - public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; - - public final static String GTASK_JSON_TYPE_TASK = "TASK"; - - public final static String GTASK_JSON_USER = "user"; - - public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; - - public final static String FOLDER_DEFAULT = "Default"; - - public final static String FOLDER_CALL_NOTE = "Call_Note"; - - public final static String FOLDER_META = "METADATA"; - - public final static String META_HEAD_GTASK_ID = "meta_gid"; - - public final static String META_HEAD_NOTE = "meta_note"; - - public final static String META_HEAD_DATA = "meta_data"; - + // GTask JSON对象中各种属性的键名 + public final static String GTASK_JSON_ACTION_ID = "action_id"; // 动作ID + public final static String GTASK_JSON_ACTION_LIST = "action_list"; // 动作列表 + public final static String GTASK_JSON_ACTION_TYPE = "action_type"; // 动作类型 + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; // 创建动作类型 + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; // 获取所有动作类型 + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; // 移动动作类型 + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; // 更新动作类型 + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; // 创建者ID + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // 子实体 + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; // 客户端版本 + public final static String GTASK_JSON_COMPLETED = "completed"; // 完成状态 + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; // 当前列表ID + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // 默认列表ID + public final static String GTASK_JSON_DELETED = "deleted"; // 删除状态 + public final static String GTASK_JSON_DEST_LIST = "dest_list"; // 目标列表 + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; // 目标父实体 + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; // 目标父实体类型 + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; // 实体增量 + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; // 实体类型 + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // 获取已删除项 + public final static String GTASK_JSON_ID = "id"; // ID + public final static String GTASK_JSON_INDEX = "index"; // 索引 + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; // 最后修改时间 + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; // 最新同步点 + public final static String GTASK_JSON_LIST_ID = "list_id"; // 列表ID + public final static String GTASK_JSON_LISTS = "lists"; // 列表集合 + public final static String GTASK_JSON_NAME = "name"; // 名称 + public final static String GTASK_JSON_NEW_ID = "new_id"; // 新ID + public final static String GTASK_JSON_NOTES = "notes"; // 备注 + public final static String GTASK_JSON_PARENT_ID = "parent_id"; // 父ID + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // 前一个兄弟ID + public final static String GTASK_JSON_RESULTS = "results"; // 结果 + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // 源列表 + public final static String GTASK_JSON_TASKS = "tasks"; // 任务集合 + public final static String GTASK_JSON_TYPE = "type"; // 类型 + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; // 类型:组 + public final static String GTASK_JSON_TYPE_TASK = "TASK"; // 类型:任务 + public final static String GTASK_JSON_USER = "user"; // 用户 + // MIUI笔记相关的文件夹前缀和元数据键名 + public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; // MIUI笔记文件夹前缀 + public final static String FOLDER_DEFAULT = "Default"; // 默认文件夹名 + public final static String FOLDER_CALL_NOTE = "Call_Note"; // 通话笔记文件夹名 + public final static String FOLDER_META = "METADATA"; // 元数据文件夹名 + // 元数据头部键名 + public final static String META_HEAD_GTASK_ID = "meta_gid"; // GTask ID + public final static String META_HEAD_NOTE = "meta_note"; // 笔记内容 + public final static String META_HEAD_DATA = "meta_data"; // 元数据 + // 元数据笔记名称,不可更新和删除 public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; - } diff --git a/xiaomi-src/main/java/net/micode/notes/tool/ResourceParser.java b/xiaomi-src/main/java/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..432187c 100644 --- a/xiaomi-src/main/java/net/micode/notes/tool/ResourceParser.java +++ b/xiaomi-src/main/java/net/micode/notes/tool/ResourceParser.java @@ -1,17 +1,5 @@ /* - * 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. + * ResourceParser 类用于管理与应用资源相关的各种静态方法和常量。 */ package net.micode.notes.tool; @@ -24,47 +12,64 @@ import net.micode.notes.ui.NotesPreferenceActivity; public class ResourceParser { - public static final int YELLOW = 0; - public static final int BLUE = 1; - public static final int WHITE = 2; - public static final int GREEN = 3; - public static final int RED = 4; + // 定义笔记背景颜色的常量 + public static final int YELLOW = 0; + public static final int BLUE = 1; + public static final int WHITE = 2; + public static final int GREEN = 3; + public static final int RED = 4; + // 默认背景颜色 public static final int BG_DEFAULT_COLOR = YELLOW; - public static final int TEXT_SMALL = 0; - public static final int TEXT_MEDIUM = 1; - public static final int TEXT_LARGE = 2; - public static final int TEXT_SUPER = 3; + // 定义文本大小的常量 + public static final int TEXT_SMALL = 0; + public static final int TEXT_MEDIUM = 1; + public static final int TEXT_LARGE = 2; + public static final int TEXT_SUPER = 3; + // 默认字体大小 public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + /** + * 笔记背景资源类,提供获取不同背景资源的方法。 + */ public static class NoteBgResources { - private final static int [] BG_EDIT_RESOURCES = new int [] { - R.drawable.edit_yellow, - R.drawable.edit_blue, - R.drawable.edit_white, - R.drawable.edit_green, - R.drawable.edit_red + // 编辑状态下的背景资源数组 + private final static int[] BG_EDIT_RESOURCES = new int[]{ + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red }; - private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { - R.drawable.edit_title_yellow, - R.drawable.edit_title_blue, - R.drawable.edit_title_white, - R.drawable.edit_title_green, - R.drawable.edit_title_red + // 编辑状态下的标题背景资源数组 + private final static int[] BG_EDIT_TITLE_RESOURCES = new int[]{ + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red }; + // 根据id获取编辑状态下的背景资源 public static int getNoteBgResource(int id) { return BG_EDIT_RESOURCES[id]; } + // 根据id获取编辑状态下的标题背景资源 public static int getNoteTitleBgResource(int id) { return BG_EDIT_TITLE_RESOURCES[id]; } } + /** + * 获取默认笔记背景id。 + * + * @param context 上下文对象,用于访问SharedPreferences。 + * @return 如果用户设置了背景颜色,则返回一个随机背景颜色id;否则返回默认背景颜色id。 + */ public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -74,108 +79,130 @@ public class ResourceParser { } } + /** + * 笔记列表项背景资源类,提供获取不同背景资源的方法。 + */ public static class NoteItemBgResources { - private final static int [] BG_FIRST_RESOURCES = new int [] { - R.drawable.list_yellow_up, - R.drawable.list_blue_up, - R.drawable.list_white_up, - R.drawable.list_green_up, - R.drawable.list_red_up + // 第一个列表项的背景资源数组 + private final static int[] BG_FIRST_RESOURCES = new int[]{ + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up }; - private final static int [] BG_NORMAL_RESOURCES = new int [] { - R.drawable.list_yellow_middle, - R.drawable.list_blue_middle, - R.drawable.list_white_middle, - R.drawable.list_green_middle, - R.drawable.list_red_middle + // 普通列表项的背景资源数组 + private final static int[] BG_NORMAL_RESOURCES = new int[]{ + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle }; - private final static int [] BG_LAST_RESOURCES = new int [] { - R.drawable.list_yellow_down, - R.drawable.list_blue_down, - R.drawable.list_white_down, - R.drawable.list_green_down, - R.drawable.list_red_down, + // 最后一个列表项的背景资源数组 + private final static int[] BG_LAST_RESOURCES = new int[]{ + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, }; - private final static int [] BG_SINGLE_RESOURCES = new int [] { - R.drawable.list_yellow_single, - R.drawable.list_blue_single, - R.drawable.list_white_single, - R.drawable.list_green_single, - R.drawable.list_red_single + // 单个列表项的背景资源数组 + private final static int[] BG_SINGLE_RESOURCES = new int[]{ + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single }; + // 获取第一个列表项的背景资源 public static int getNoteBgFirstRes(int id) { return BG_FIRST_RESOURCES[id]; } + // 获取最后一个列表项的背景资源 public static int getNoteBgLastRes(int id) { return BG_LAST_RESOURCES[id]; } + // 获取单个列表项的背景资源 public static int getNoteBgSingleRes(int id) { return BG_SINGLE_RESOURCES[id]; } + // 获取普通列表项的背景资源 public static int getNoteBgNormalRes(int id) { return BG_NORMAL_RESOURCES[id]; } + // 获取文件夹背景资源 public static int getFolderBgRes() { return R.drawable.list_folder; } } + /** + * 小部件背景资源类,提供获取小部件背景资源的方法。 + */ public static class WidgetBgResources { - private final static int [] BG_2X_RESOURCES = new int [] { - R.drawable.widget_2x_yellow, - R.drawable.widget_2x_blue, - R.drawable.widget_2x_white, - R.drawable.widget_2x_green, - R.drawable.widget_2x_red, + // 2x 小部件背景资源数组 + private final static int[] BG_2X_RESOURCES = new int[]{ + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, }; + // 根据id获取2x小部件的背景资源 public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } - private final static int [] BG_4X_RESOURCES = new int [] { - R.drawable.widget_4x_yellow, - R.drawable.widget_4x_blue, - R.drawable.widget_4x_white, - R.drawable.widget_4x_green, - R.drawable.widget_4x_red + // 4x 小部件背景资源数组 + private final static int[] BG_4X_RESOURCES = new int[]{ + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red }; + // 根据id获取4x小部件的背景资源 public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } + /** + * 文本外观资源类,提供获取不同文本外观资源的方法。 + */ public static class TextAppearanceResources { - private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { - R.style.TextAppearanceNormal, - R.style.TextAppearanceMedium, - R.style.TextAppearanceLarge, - R.style.TextAppearanceSuper + // 文本外观资源数组 + private final static int[] TEXTAPPEARANCE_RESOURCES = new int[]{ + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper }; + // 根据id获取文本外观资源 public static int getTexAppearanceResource(int id) { - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ + // 如果id超出资源数组范围,返回默认字体大小 if (id >= TEXTAPPEARANCE_RESOURCES.length) { return BG_DEFAULT_FONT_SIZE; } return TEXTAPPEARANCE_RESOURCES[id]; } + // 获取文本外观资源的数量 public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; } } } + diff --git a/xiaomi-src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/xiaomi-src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 3e6173b..3c77e81 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -1,4 +1,3 @@ - /* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * @@ -41,20 +40,33 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; +/* + * AlarmAlertActivity 类用于处理提醒通知的界面和声音播放。 + * 当一个笔记的提醒时间到达时,这个活动会被启动,显示提醒信息并播放提醒声音。 + */ public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + // 笔记的ID private long mNoteId; + // 笔记内容的简短预览 private String mSnippet; + // 预览文本的最大长度 private static final int SNIPPET_PREW_MAX_LEN = 60; + // 用于播放提醒声音的MediaPlayer对象 MediaPlayer mPlayer; + /* + * onCreate 方法初始化活动,设置窗口特性,根据传入的Intent获取笔记ID和简短内容, + * 并根据情况显示对话框和播放声音。 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 请求无标题的窗口 requestWindowFeature(Window.FEATURE_NO_TITLE); + // 设置窗口在锁屏时也显示,并根据屏幕状态决定是否保持唤醒 final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - if (!isScreenOn()) { win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON @@ -62,8 +74,8 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); } + // 从Intent中获取笔记ID和简短内容 Intent intent = getIntent(); - try { mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); @@ -75,6 +87,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD return; } + // 如果笔记在数据库中可见,则显示动作对话框并播放声音,否则结束活动 mPlayer = new MediaPlayer(); if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { showActionDialog(); @@ -84,14 +97,24 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /* + * 检查屏幕是否处于打开状态。 + * + * @return 如果屏幕已打开则返回true,否则返回false。 + */ private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + /* + * 播放提醒声音。 + * 根据系统设置选择合适的音频流类型,并尝试播放选定的报警声音。 + */ private void playAlarmSound() { Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + // 检查是否在静音模式下影响报警声音 int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); @@ -106,20 +129,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD mPlayer.setLooping(true); mPlayer.start(); } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } + /* + * 显示动作对话框。 + * 根据屏幕是否打开,设置对话框的按钮,并显示应用名称和笔记的简短内容。 + */ private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.app_name); @@ -131,24 +154,38 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD dialog.show().setOnDismissListener(this); } + /* + * 点击对话框按钮的响应处理。 + * 根据点击的按钮启动编辑笔记的活动或结束当前活动。 + */ public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: + // 如果点击的是“进入”按钮,则启动笔记编辑活动 Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, mNoteId); startActivity(intent); break; default: + // 关闭活动 break; } } + /* + * 对话框关闭时的处理。 + * 停止播放提醒声音,结束当前活动。 + */ public void onDismiss(DialogInterface dialog) { stopAlarmSound(); finish(); } + /* + * 停止播放提醒声音。 + * 如果MediaPlayer对象不为空,则停止播放并释放资源。 + */ private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); diff --git a/xiaomi-src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/xiaomi-src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..8da4ef8 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -1,19 +1,7 @@ /* - * 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.ui; import android.app.AlarmManager; @@ -30,36 +18,54 @@ import net.micode.notes.data.Notes.NoteColumns; public class AlarmInitReceiver extends BroadcastReceiver { - private static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE + // 查询笔记时需要的列 + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE }; - private static final int COLUMN_ID = 0; - private static final int COLUMN_ALERTED_DATE = 1; + // 列的索引 + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + /** + * 当接收到广播时执行的操作。主要用于设置所有已记录的提醒时间。 + * + * @param context 上下文,提供访问应用全局功能的入口。 + * @param intent 携带了触发该接收器的广播信息。 + */ @Override public void onReceive(Context context, Intent intent) { + // 获取当前日期和时间 long currentDate = System.currentTimeMillis(); + // 查询数据库中所有需要提醒的笔记 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, - new String[] { String.valueOf(currentDate) }, + new String[]{String.valueOf(currentDate)}, null); if (c != null) { + // 遍历查询结果,为每个需要提醒的笔记设置提醒 if (c.moveToFirst()) { do { + // 获取提醒日期 long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 创建Intent,用于在提醒时间触发AlarmReceiver Intent sender = new Intent(context, AlarmReceiver.class); sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建PendingIntent,它是一个延迟的意图,可以在特定时间由系统触发 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获取AlarmManager服务,用于设置提醒 AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); + // 设置提醒 alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); } while (c.moveToNext()); } + // 关闭Cursor,释放资源 c.close(); } } } + diff --git a/xiaomi-src/main/java/net/micode/notes/ui/AlarmReceiver.java b/xiaomi-src/main/java/net/micode/notes/ui/AlarmReceiver.java index 54e503b..42d7313 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -1,19 +1,9 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * AlarmReceiver类 - 用于处理闹钟广播接收 + * 当接收到闹钟相关的广播时,该类会启动一个指定的Activity * - * 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. + * extends BroadcastReceiver: 继承自Android的BroadcastReceiver类 */ - package net.micode.notes.ui; import android.content.BroadcastReceiver; @@ -21,10 +11,20 @@ import android.content.Context; import android.content.Intent; public class AlarmReceiver extends BroadcastReceiver { + /* + * onReceive方法 - 系统调用的接收广播的方法 + * 当接收到广播时,该方法会被调用,然后启动AlarmAlertActivity + * + * @param context 上下文对象,提供了调用环境的信息 + * @param intent 包含广播的内容 + */ @Override public void onReceive(Context context, Intent intent) { + // 设置Intent的类,以便启动AlarmAlertActivity intent.setClass(context, AlarmAlertActivity.class); + // 添加标志,表示在一个新的任务中启动Activity intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 根据设置的Intent启动Activity context.startActivity(intent); } } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/ChangingPassword.java b/xiaomi-src/main/java/net/micode/notes/ui/ChangePassword.java similarity index 69% rename from xiaomi-src/main/java/net/micode/notes/ui/ChangingPassword.java rename to xiaomi-src/main/java/net/micode/notes/ui/ChangePassword.java index d32712e..2f44d9a 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/ChangingPassword.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/ChangePassword.java @@ -11,8 +11,7 @@ import android.widget.EditText; import android.widget.Toast; import net.micode.notes.R; - -public class ChangingPassword extends Activity { +public class ChangePassword extends Activity { EditText OldPassword; EditText NewPassword; EditText AckPassword; @@ -26,9 +25,9 @@ public class ChangingPassword extends Activity { WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); OldPassword=(EditText) findViewById(R.id.old_password); - NewPassword=(EditText) findViewById(R.id.new_password); - AckPassword=(EditText) findViewById(R.id.ack_password); - Acknowledged=(Button)findViewById(R.id.Acknowledged); + NewPassword=(EditText) findViewById(R.id.new_password);// 获取确认密码的EditText + AckPassword=(EditText) findViewById(R.id.ack_password);// 获取确认按钮 + Acknowledged=(Button)findViewById(R.id.Acknowledged);// 为确认按钮设置点击事件监听器 Acknowledged.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -38,20 +37,20 @@ public class ChangingPassword extends Activity { SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE); String login_password=pref.getString("password",""); if(old_password.equals("")==true || new_password.equals("")==true || ack_password.equals("")==true) { - Toast.makeText(ChangingPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show(); + Toast.makeText(ChangePassword.this, "密码不能为空", Toast.LENGTH_SHORT).show(); }else if (new_password.equals(ack_password) == false) { - Toast.makeText(ChangingPassword.this, "新建密码与重复密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show(); + Toast.makeText(ChangePassword.this, "新建密码与重复密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show(); AckPassword.setText(""); }else if(old_password.equals(login_password) == false){ - Toast.makeText(ChangingPassword.this, "原有密码错误,请重新输入密码", Toast.LENGTH_SHORT).show(); + Toast.makeText(ChangePassword.this, "原有密码错误,请重新输入密码", Toast.LENGTH_SHORT).show(); OldPassword.setText(""); } else if (new_password.equals(ack_password) == true && old_password.equals(login_password) == true){ SharedPreferences.Editor editor=getSharedPreferences("user management", MODE_PRIVATE).edit(); editor.putString("password",new_password); editor.apply(); - Toast.makeText(ChangingPassword.this, "修改密码成功", Toast.LENGTH_SHORT).show(); - Intent intent=new Intent(ChangingPassword.this,NotesListActivity.class); + Toast.makeText(ChangePassword.this, "修改密码成功", Toast.LENGTH_SHORT).show(); + Intent intent=new Intent(ChangePassword.this,NotesListActivity.class); startActivity(intent); finish(); } @@ -61,7 +60,7 @@ public class ChangingPassword extends Activity { @Override public void onBackPressed() { - Intent intent=new Intent(ChangingPassword.this,NotesListActivity.class); + Intent intent=new Intent(ChangePassword.this,NotesListActivity.class); startActivity(intent); finish(); } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/DateTimePicker.java b/xiaomi-src/main/java/net/micode/notes/ui/DateTimePicker.java index 496b0cd..aea9538 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/DateTimePicker.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/DateTimePicker.java @@ -30,55 +30,85 @@ import android.widget.NumberPicker; public class DateTimePicker extends FrameLayout { + // 默认启用状态 private static final boolean DEFAULT_ENABLE_STATE = true; + // 半天的小时数 private static final int HOURS_IN_HALF_DAY = 12; + // 一整天的小时数 private static final int HOURS_IN_ALL_DAY = 24; + // 一周的天数 private static final int DAYS_IN_ALL_WEEK = 7; + // 日期选择器的最小值 private static final int DATE_SPINNER_MIN_VAL = 0; + // 日期选择器的最大值 private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + // 24小时制小时选择器的最小值 private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + // 24小时制小时选择器的最大值 private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12小时制小时选择器的最小值 private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + // 12小时制小时选择器的最大值 private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + // 分钟选择器的最小值 private static final int MINUT_SPINNER_MIN_VAL = 0; + // 分钟选择器的最大值 private static final int MINUT_SPINNER_MAX_VAL = 59; + // 上下午选择器的最小值 private static final int AMPM_SPINNER_MIN_VAL = 0; + // 上下午选择器的最大值 private static final int AMPM_SPINNER_MAX_VAL = 1; + // 日期选择器 private final NumberPicker mDateSpinner; + // 小时选择器 private final NumberPicker mHourSpinner; + // 分钟选择器 private final NumberPicker mMinuteSpinner; + // 上下午选择器 private final NumberPicker mAmPmSpinner; + // 当前日期 private Calendar mDate; + // 用于显示日期的字符串数组 private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + // 当前是否为上午 private boolean mIsAm; + // 当前是否为24小时制视图 private boolean mIs24HourView; + // 控件是否启用 private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + // 是否正在初始化 private boolean mInitialising; + // 日期时间改变监听器 private OnDateTimeChangedListener mOnDateTimeChangedListener; + // 日期选择器的值改变监听器 private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 根据新旧值的差异更新日期 mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); updateDateControl(); onDateTimeChanged(); } }; + // 小时选择器的值改变监听器 private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 根据小时的变化更新日期和上下午状态 boolean isDateChanged = false; Calendar cal = Calendar.getInstance(); if (!mIs24HourView) { + // 处理12小时制下的日期变化和上下午切换 if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); @@ -94,6 +124,7 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } } else { + // 处理24小时制下的日期变化 if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); @@ -104,9 +135,11 @@ public class DateTimePicker extends FrameLayout { isDateChanged = true; } } + // 更新小时并触发日期时间改变事件 int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); mDate.set(Calendar.HOUR_OF_DAY, newHour); onDateTimeChanged(); + // 如果日期有变化,则更新年月日 if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); setCurrentMonth(cal.get(Calendar.MONTH)); @@ -115,9 +148,12 @@ public class DateTimePicker extends FrameLayout { } }; + + // 分别为分钟和AM/PM选择器监听器设置匿名内部类,实现数值变化时的处理逻辑。 private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 计算小时的偏移量,当从最大值变为最小值或从最小值变为最大值时调整 int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); int offset = 0; @@ -126,6 +162,7 @@ public class DateTimePicker extends FrameLayout { } else if (oldVal == minValue && newVal == maxValue) { offset -= 1; } + // 根据偏移量更新日期和小时选择器,并检查是否需要切换AM/PM if (offset != 0) { mDate.add(Calendar.HOUR_OF_DAY, offset); mHourSpinner.setValue(getCurrentHour()); @@ -139,6 +176,7 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } } + // 更新分钟值并触发日期变化的回调 mDate.set(Calendar.MINUTE, newVal); onDateTimeChanged(); } @@ -147,6 +185,7 @@ public class DateTimePicker extends FrameLayout { private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 切换AM/PM状态,并更新日期和AM/PM选择器 mIsAm = !mIsAm; if (mIsAm) { mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); @@ -158,19 +197,23 @@ public class DateTimePicker extends FrameLayout { } }; + // 定义日期时间变化的回调接口 public interface OnDateTimeChangedListener { void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute); + int dayOfMonth, int hourOfDay, int minute); } + // 构造函数:初始化日期时间选择器 public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + // 构造函数:指定初始日期时间 public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + // 构造函数:指定是否使用24小时制视图 public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); mDate = Calendar.getInstance(); @@ -178,6 +221,7 @@ public class DateTimePicker extends FrameLayout { mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; inflate(context, R.layout.datetime_picker, this); + // 初始化日期、小时、分钟和AM/PM选择器,并设置相应的监听器 mDateSpinner = (NumberPicker) findViewById(R.id.date); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); @@ -185,7 +229,7 @@ public class DateTimePicker extends FrameLayout { mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); - mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); mMinuteSpinner.setOnLongPressUpdateInterval(100); @@ -198,28 +242,37 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新控件至初始状态 updateDateControl(); updateHourControl(); updateAmPmControl(); set24HourView(is24HourView); - // set to current time + // 设置为当前时间 setCurrentDate(date); setEnabled(isEnabled()); - // set the content descriptions + // 设置内容描述 mInitialising = false; } + + /** + * 设置控件的启用状态。 + * 如果当前状态与传入状态相同,则不进行任何操作。 + * 启用或禁用日期和时间选择器,并更新内部启用状态。 + * + * @param enabled 控件是否启用 + */ @Override public void setEnabled(boolean enabled) { if (mIsEnabled == enabled) { return; } super.setEnabled(enabled); + // 同时启用或禁用日期和时间选择器 mDateSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled); mHourSpinner.setEnabled(enabled); @@ -227,43 +280,52 @@ public class DateTimePicker extends FrameLayout { mIsEnabled = enabled; } + /** + * 获取控件的启用状态。 + * + * @return 控件是否启用 + */ @Override public boolean isEnabled() { return mIsEnabled; } /** - * Get the current date in millis + * 获取当前日期的时间戳(毫秒)。 * - * @return the current date in millis + * @return 当前日期的毫秒时间戳 */ public long getCurrentDateInTimeMillis() { return mDate.getTimeInMillis(); } /** - * Set the current date + * 设置当前日期。 + * 根据传入的毫秒时间戳更新日期选择器的值。 * * @param date The current date in millis */ public void setCurrentDate(long date) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(date); + // 通过日历实例的详细字段设置当前日期和时间 setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); } /** - * Set the current date + * 设置当前日期和时间。 + * 分别设置年、月、日、时和分。 * - * @param year The current year - * @param month The current month - * @param dayOfMonth The current dayOfMonth - * @param hourOfDay The current hourOfDay - * @param minute The current minute + * @param year 当前年份 + * @param month 当前月份 + * @param dayOfMonth 当前日 + * @param hourOfDay 当前小时 + * @param minute 当前分钟 */ public void setCurrentDate(int year, int month, - int dayOfMonth, int hourOfDay, int minute) { + int dayOfMonth, int hourOfDay, int minute) { + // 分别设置年、月、日、时和分 setCurrentYear(year); setCurrentMonth(month); setCurrentDay(dayOfMonth); @@ -271,88 +333,101 @@ public class DateTimePicker extends FrameLayout { setCurrentMinute(minute); } + /** - * Get current year + * 获取当前年份 * - * @return The current year + * @return 当前的年份 */ public int getCurrentYear() { return mDate.get(Calendar.YEAR); } /** - * Set current year + * 设置当前年份 * - * @param year The current year + * @param year 当前的年份 */ public void setCurrentYear(int year) { + // 如果不是初始化状态并且设置的年份与当前年份相同,则直接返回 if (!mInitialising && year == getCurrentYear()) { return; } mDate.set(Calendar.YEAR, year); - updateDateControl(); - onDateTimeChanged(); + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 触发日期时间改变事件 } /** - * Get current month in the year + * 获取当前月份 * - * @return The current month in the year + * @return 当前的月份(从0开始) */ public int getCurrentMonth() { return mDate.get(Calendar.MONTH); } /** - * Set current month in the year + * 设置当前月份 * - * @param month The month in the year + * @param month 当前的月份(从0开始) */ public void setCurrentMonth(int month) { + // 如果不是初始化状态并且设置的月份与当前月份相同,则直接返回 if (!mInitialising && month == getCurrentMonth()) { return; } mDate.set(Calendar.MONTH, month); - updateDateControl(); - onDateTimeChanged(); + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 触发日期时间改变事件 } /** - * Get current day of the month + * 获取当前日期(月中的天数) * - * @return The day of the month + * @return 当前的日期(月中的天数) */ public int getCurrentDay() { return mDate.get(Calendar.DAY_OF_MONTH); } /** - * Set current day of the month + * 设置当前日期(月中的天数) * - * @param dayOfMonth The day of the month + * @param dayOfMonth 当前的日期(月中的天数) */ public void setCurrentDay(int dayOfMonth) { + // 如果不是初始化状态并且设置的日期与当前日期相同,则直接返回 if (!mInitialising && dayOfMonth == getCurrentDay()) { return; } mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - updateDateControl(); - onDateTimeChanged(); + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 触发日期时间改变事件 } /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode + * 获取当前小时(24小时制),范围为(0~23) + * + * @return 当前的小时(24小时制) */ public int getCurrentHourOfDay() { return mDate.get(Calendar.HOUR_OF_DAY); } + /** + * 获取当前小时,根据是否为24小时制返回不同的值。 + * 如果是24小时制,与{@link #getCurrentHourOfDay()}返回相同结果; + * 否则,将小时转换为12小时制,并考虑上午/下午。 + * + * @return 当前的小时(根据视图模式可能是12小时制) + */ private int getCurrentHour() { - if (mIs24HourView){ + if (mIs24HourView) { return getCurrentHourOfDay(); } else { int hour = getCurrentHourOfDay(); + // 转换为12小时制 if (hour > HOURS_IN_HALF_DAY) { return hour - HOURS_IN_HALF_DAY; } else { @@ -361,16 +436,19 @@ public class DateTimePicker extends FrameLayout { } } + /** - * Set current hour in 24 hour mode, in the range (0~23) + * 设置当前小时(24小时制),范围为(0~23) * - * @param hourOfDay + * @param hourOfDay 当前小时数 */ public void setCurrentHour(int hourOfDay) { + // 如果在初始化中或者小时未改变,则直接返回 if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { return; } mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + // 如果不是24小时视图,则调整小时数并更新AM/PM控制 if (!mIs24HourView) { if (hourOfDay >= HOURS_IN_HALF_DAY) { mIsAm = false; @@ -390,18 +468,21 @@ public class DateTimePicker extends FrameLayout { } /** - * Get currentMinute + * 获取当前分钟数 * - * @return The Current Minute + * @return 当前分钟数 */ public int getCurrentMinute() { return mDate.get(Calendar.MINUTE); } /** - * Set current minute + * 设置当前分钟数 + * + * @param minute 当前分钟数值 */ public void setCurrentMinute(int minute) { + // 如果在初始化中或者分钟数未改变,则直接返回 if (!mInitialising && minute == getCurrentMinute()) { return; } @@ -411,18 +492,21 @@ public class DateTimePicker extends FrameLayout { } /** - * @return true if this is in 24 hour view else false. + * 获取当前是否为24小时视图 + * + * @return 如果是24小时视图返回true,否则返回false */ - public boolean is24HourView () { + public boolean is24HourView() { return mIs24HourView; } /** - * Set whether in 24 hour or AM/PM mode. + * 设置当前视图为24小时制还是AM/PM制 * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. + * @param is24HourView 如果为true表示24小时制,false表示AM/PM制 */ public void set24HourView(boolean is24HourView) { + // 如果视图模式未改变,则直接返回 if (mIs24HourView == is24HourView) { return; } @@ -434,11 +518,15 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } + /** + * 更新日期控制组件,显示正确的日期选项 + */ private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); mDateSpinner.setDisplayedValues(null); + // 循环设置一周内每一天的显示文本 for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { cal.add(Calendar.DAY_OF_YEAR, 1); mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); @@ -448,6 +536,9 @@ public class DateTimePicker extends FrameLayout { mDateSpinner.invalidate(); } + /** + * 根据当前是否为24小时视图来更新AM/PM控制组件的显示和值 + */ private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); @@ -458,6 +549,9 @@ public class DateTimePicker extends FrameLayout { } } + /** + * 根据当前是否为24小时视图来更新小时控制组件的最小值和最大值 + */ private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); @@ -469,13 +563,17 @@ public class DateTimePicker extends FrameLayout { } /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing + * 设置点击“设置”按钮时的回调接口 + * + * @param callback 回调接口实例,如果为null则不执行任何操作 */ public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { mOnDateTimeChangedListener = callback; } + /** + * 当日期时间被改变时调用此方法,如果设置了日期时间改变监听器,则触发监听器的回调方法 + */ private void onDateTimeChanged() { if (mOnDateTimeChangedListener != null) { mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), diff --git a/xiaomi-src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/xiaomi-src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..2f9f8a9 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -1,19 +1,7 @@ /* - * 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. + * DateTimePickerDialog类提供了一个日期和时间选择器对话框。 + * 用户可以选择一个日期和时间,然后通过监听器回调返回选择的值。 */ - package net.micode.notes.ui; import java.util.Calendar; @@ -31,60 +19,103 @@ import android.text.format.DateUtils; public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + // 当前选择的日期和时间 private Calendar mDate = Calendar.getInstance(); + // 用于指示日期时间选择器是否使用24小时制 private boolean mIs24HourView; + // 日期时间设置监听器,用于处理日期时间选择后的回调 private OnDateTimeSetListener mOnDateTimeSetListener; + // 日期时间选择器视图 private DateTimePicker mDateTimePicker; + /** + * 日期时间设置监听器接口。 + * 实现此接口的类需要提供OnDateTimeSet方法来处理日期时间被设置的事件。 + */ public interface OnDateTimeSetListener { void OnDateTimeSet(AlertDialog dialog, long date); } + /** + * 构造函数初始化日期时间选择器对话框。 + * + * @param context 上下文对象,通常是指Activity。 + * @param date 初始显示的日期时间值。 + */ public DateTimePickerDialog(Context context, long date) { super(context); mDateTimePicker = new DateTimePicker(context); setView(mDateTimePicker); + // 设置日期时间改变的监听器 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute) { + int dayOfMonth, int hourOfDay, int minute) { + // 更新内部日期时间值 mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.MINUTE, minute); + // 更新对话框的标题显示 updateTitle(mDate.getTimeInMillis()); } }); mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + // 设置对话框的确认和取消按钮 setButton(context.getString(R.string.datetime_dialog_ok), this); - setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener) null); + // 根据系统设置决定是否使用24小时制 set24HourView(DateFormat.is24HourFormat(this.getContext())); + // 更新标题以显示当前选择的日期和时间 updateTitle(mDate.getTimeInMillis()); } + /** + * 设置日期时间选择器是否使用24小时制。 + * + * @param is24HourView 是否使用24小时制。 + */ public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + /** + * 设置日期时间被设置时的监听器。 + * + * @param callBack 日期时间设置监听器对象。 + */ public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + /** + * 更新对话框标题以显示当前选择的日期和时间。 + * + * @param date 当前选择的日期时间值。 + */ private void updateTitle(long date) { + // 根据是否使用24小时制来格式化日期时间显示 int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + /** + * 点击按钮时的处理逻辑。 + * 如果设置了日期时间设置监听器,则调用其OnDateTimeSet方法,传入当前选择的日期时间值。 + * + * @param arg0 对话框对象。 + * @param arg1 按钮标识。 + */ public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); } } -} \ No newline at end of file +} diff --git a/xiaomi-src/main/java/net/micode/notes/ui/DeletingPassword.java b/xiaomi-src/main/java/net/micode/notes/ui/DeletePassword.java similarity index 78% rename from xiaomi-src/main/java/net/micode/notes/ui/DeletingPassword.java rename to xiaomi-src/main/java/net/micode/notes/ui/DeletePassword.java index 0e4dfe2..717cc68 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/DeletingPassword.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/DeletePassword.java @@ -12,7 +12,7 @@ import android.widget.Toast; import net.micode.notes.R; -public class DeletingPassword extends Activity { +public class DeletePassword extends Activity { EditText Dt_password; Button Acknowledged; @@ -30,7 +30,7 @@ public class DeletingPassword extends Activity { public void onClick(View v) { String text02 = Dt_password.getText().toString(); if(text02.equals("")==true) - Toast.makeText(DeletingPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show(); + Toast.makeText(DeletePassword.this, "密码不能为空", Toast.LENGTH_SHORT).show(); SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE); String password = pref.getString("password",""); if(password.equals("")==false&&password.equals(text02)==true){ @@ -40,13 +40,13 @@ public class DeletingPassword extends Activity { editor.putString("password",""); editor.apply(); - Toast.makeText(DeletingPassword.this, "已经删除登录密码", Toast.LENGTH_SHORT).show(); - Intent intent=new Intent(DeletingPassword.this,NotesListActivity.class); + Toast.makeText(DeletePassword.this, "已经删除登录密码", Toast.LENGTH_SHORT).show(); + Intent intent=new Intent(DeletePassword.this,NotesListActivity.class); startActivity(intent); finish(); } else{ - Toast.makeText(DeletingPassword.this, "密码错误", Toast.LENGTH_SHORT).show(); + Toast.makeText(DeletePassword.this, "密码错误", Toast.LENGTH_SHORT).show(); Dt_password.setText("");//把密码框内输入过的错误密码清空 } } @@ -55,7 +55,7 @@ public class DeletingPassword extends Activity { @Override public void onBackPressed() { - Intent intent=new Intent(DeletingPassword.this,NotesListActivity.class); + Intent intent=new Intent(DeletePassword.this,NotesListActivity.class); startActivity(intent); finish(); } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/DropdownMenu.java b/xiaomi-src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..8330499 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -1,19 +1,7 @@ /* - * 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. + * DropdownMenu类用于创建和管理一个下拉菜单。 + * 该类封装了一个Button和一个PopupMenu,通过点击Button来显示下拉菜单。 */ - package net.micode.notes.ui; import android.content.Context; @@ -28,16 +16,24 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; + private Button mButton; // 弹出下拉菜单的按钮 + private PopupMenu mPopupMenu; // 弹出的下拉菜单 + private Menu mMenu; // 下拉菜单的项目集合 + /** + * DropdownMenu的构造函数。 + * + * @param context 上下文对象,通常是指Activity。 + * @param button 用于触发下拉菜单显示的按钮。 + * @param menuId 菜单资源ID,用于加载下拉菜单的项目。 + */ public DropdownMenu(Context context, Button button, int menuId) { mButton = button; - mButton.setBackgroundResource(R.drawable.dropdown_icon); - mPopupMenu = new PopupMenu(context, mButton); - mMenu = mPopupMenu.getMenu(); - mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景为下拉图标 + mPopupMenu = new PopupMenu(context, mButton); // 创建PopupMenu实例 + mMenu = mPopupMenu.getMenu(); // 获取菜单项的集合 + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 加载菜单项 + // 设置按钮点击事件,点击后显示下拉菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +41,32 @@ public class DropdownMenu { }); } + /** + * 设置下拉菜单项的点击事件监听器。 + * + * @param listener PopupMenu的OnMenuItemClickListener,用于监听菜单项的点击事件。 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 根据ID查找菜单项。 + * + * @param id 菜单项的ID。 + * @return 返回找到的MenuItem对象,如果未找到则返回null。 + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置按钮的标题。 + * + * @param title 按钮要显示的标题。 + */ public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/xiaomi-src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..2f461ca 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -1,21 +1,12 @@ /* - * 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. + * FoldersListAdapter 类 + * 用于管理和适配文件夹列表的适配器,继承自 CursorAdapter。 */ package net.micode.notes.ui; +// 导入相关类 + import android.content.Context; import android.database.Cursor; import android.view.View; @@ -30,48 +21,87 @@ import net.micode.notes.data.Notes.NoteColumns; public class FoldersListAdapter extends CursorAdapter { - public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET + // 查询时使用的列名数组 + public static final String[] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET }; - public static final int ID_COLUMN = 0; + // 列名数组中的索引常量 + public static final int ID_COLUMN = 0; public static final int NAME_COLUMN = 1; + /* + * 构造函数 + * @param context 上下文对象,通常指Activity或Application对象。 + * @param c 数据源游标Cursor。 + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); - // TODO Auto-generated constructor stub } + /* + * 创建新的列表项View + * @param context 上下文对象。 + * @param cursor 当前数据项的游标。 + * @param parent 视图的父容器。 + * @return 返回新创建的列表项View。 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /* + * 绑定数据到已有的View上 + * @param view 要绑定数据的视图。 + * @param context 上下文对象。 + * @param cursor 当前数据项的游标。 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { + // 根据文件夹ID判断是根文件夹还是普通文件夹,并设置文件夹名称 String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); ((FolderListItem) view).bind(folderName); } } + /* + * 根据位置获取文件夹名称 + * @param context 上下文对象。 + * @param position 列表中的位置。 + * @return 返回该位置上文件夹的名称。 + */ public String getFolderName(Context context, int position) { Cursor cursor = (Cursor) getItem(position); return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); } + /* + * FolderListItem 内部类 + * 用于表示文件夹列表中的一个项的视图类。 + */ private class FolderListItem extends LinearLayout { - private TextView mName; + private TextView mName; // 文件夹名称的文本视图 + /* + * 构造函数 + * @param context 上下文对象。 + */ public FolderListItem(Context context) { super(context); + // 加载布局文件,并将自己作为根视图 inflate(context, R.layout.folder_list_item, this); - mName = (TextView) findViewById(R.id.tv_folder_name); + mName = (TextView) findViewById(R.id.tv_folder_name); // 获取文件夹名称的视图 } + /* + * 绑定数据到视图上 + * @param name 要显示的文件夹名称。 + */ public void bind(String name) { mName.setText(name); } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/GetLocation.java b/xiaomi-src/main/java/net/micode/notes/ui/GetLocation.java deleted file mode 100644 index 02b1d00..0000000 --- a/xiaomi-src/main/java/net/micode/notes/ui/GetLocation.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.micode.notes.ui; - -import android.os.Bundle; -import android.speech.tts.TextToSpeech; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; - -import net.micode.notes.R; - - -public class GetLocation extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.setContentView(R.layout.local); - Toast.makeText(GetLocation.this,"init",Toast.LENGTH_SHORT).show(); - Button local_1 = new Button(this); - Button local_2 = new Button(this); - local_1.setOnClickListener(new View.OnClickListener() { - private static final String TAG = "GetLocation"; - - @Override - public void onClick(View v) { - Log.i(TAG, "onClick: button1"); - Toast.makeText(GetLocation.this,"button1",Toast.LENGTH_SHORT).show(); - } - }); - local_2.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Toast.makeText(GetLocation.this, "button2", Toast.LENGTH_SHORT).show(); - } - }); - } - - - -} diff --git a/xiaomi-src/main/java/net/micode/notes/ui/LoginActivity.java b/xiaomi-src/main/java/net/micode/notes/ui/LoginActivity.java index 99bcb19..546f96f 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/LoginActivity.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/LoginActivity.java @@ -24,7 +24,7 @@ public class LoginActivity extends Activity { boolean User_boolean = pref.getBoolean("user",false);//获取用户是否设置了密码 if(!User_boolean) //User_boolean = false时,(没有设置密码),直接跳转到便签主界面 { - Intent intent=new Intent(LoginActivity.this,NotesListActivity.class); + Intent intent=new Intent(LoginActivity.this,SplashActivity.class);//跳转到欢迎界面,splash内含跳转 startActivity(intent); finish(); } @@ -41,7 +41,7 @@ public class LoginActivity extends Activity { SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE); String password=pref.getString("password",""); if(password.equals("")==false&&password.equals(lg_password.getText().toString())==true){ - Intent intent=new Intent(LoginActivity.this,NotesListActivity.class); + Intent intent=new Intent(LoginActivity.this,SplashActivity.class); startActivity(intent); finish(); } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NoteEditActivity.java b/xiaomi-src/main/java/net/micode/notes/ui/NoteEditActivity.java index 3b8a233..006f640 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -16,45 +16,33 @@ package net.micode.notes.ui; -import android.Manifest; +import android.Manifest;//qxq: import android.app.Activity; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Typeface; -import android.location.Address; -import android.location.Geocoder; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.net.Uri; -import android.os.Build; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.speech.tts.TextToSpeech; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; +import android.content.ContentResolver; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager;//qxq +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Paint; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import android.telecom.Call; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.speech.tts.TextToSpeech; +import android.support.annotation.RequiresApi; import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; @@ -96,13 +84,17 @@ import net.micode.notes.ui.translate_demo.RespondBean; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; -import java.io.FileNotFoundException; +import java.io.FileNotFoundException;//qxq:报错类型 +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.net.URI; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Stack; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -110,34 +102,40 @@ import java.util.regex.Pattern; import net.micode.notes.ui.translate_demo.MD5Utils; import net.micode.notes.ui.translate_demo.BaiduTranslateService; +import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; - import retrofit2.converter.gson.GsonConverterFactory; - - - -public class NoteEditActivity<关闭> extends AppCompatActivity implements OnClickListener, +public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + /** + * 顾名思义,本类用于对便签的编辑 + * 头部视图的ViewHolder类,用于存储头部视图中的UI组件引用。 + * 继承的都是系统内部与监听相关的类 + */ - private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvToptext; - - public TextView tvAlertDate; + public static void setmChanged(Stack mChanged){//设置成员变量 + NoteEditActivity.mChanged = mChanged; + } - public ImageView ibSetBgColor; + private class HeadViewHolder { + public TextView tvModified; // 显示修改日期的文本视图 + public ImageView ivAlertIcon; // 提示图标图像视图 + public TextView tvAlertDate; // 显示提醒日期的文本视图 + public ImageView ibSetBgColor; // 设置背景颜色的图像视图 + private TextView tvNum;//qxq:在头部视图holder里声明文本视图用于统计字符,编辑器edittext直接借用下面的mNoteeditor } + /** + * 背景选择按钮与其对应选中状态图标的映射。 + */ private static final Map sBgSelectorBtnsMap = new HashMap(); static { + // 初始化背景选择按钮映射 sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); @@ -145,9 +143,13 @@ public class NoteEditActivity<关闭> extends AppCompatActivity implements OnCli sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + /** + * 背景选择按钮选中状态与其对应图标的映射。 + */ private static final Map sBgSelectorSelectionMap = new HashMap(); static { + // 初始化背景选择按钮选中状态映射 sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); @@ -155,1535 +157,1474 @@ public class NoteEditActivity<关闭> extends AppCompatActivity implements OnCli sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + /** + * 字号选择按钮与其对应字体大小的映射。 + */ private static final Map sFontSizeBtnsMap = new HashMap(); static { + // 初始化字号选择按钮映射 sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + /** + * 字号选择按钮选中状态与其对应图标的映射。 + */ private static final Map sFontSelectorSelectionMap = new HashMap(); static { + // 初始化字号选择按钮选中状态映射 sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } - private static final String TAG = "NoteEditActivity"; + private static final String TAG = "NoteEditActivity"; // 日志标签 - private HeadViewHolder mNoteHeaderHolder; + private HeadViewHolder mNoteHeaderHolder; // 头部视图的ViewHolder - private View mHeadViewPanel; + private View mHeadViewPanel; // 头部视图面板 - private View mNoteBgColorSelector; + private View mNoteBgColorSelector; // 笔记背景颜色选择器 - private View mFontSizeSelector; + private View mFontSizeSelector; // 字号选择器 - private EditText mNoteEditor; + private EditText mNoteEditor; // 笔记编辑器 - private View mNoteEditorPanel; + private View mNoteEditorPanel; // 笔记编辑器面板 - private WorkingNote mWorkingNote; + private WorkingNote mWorkingNote; // qxq:当前正在编辑的笔记,实例化 - private SharedPreferences mSharedPrefs; - private int mFontSizeId; + private SharedPreferences mSharedPrefs; // 共享偏好设置,存储keyvalue的xml文件 + private int mFontSizeId; // 当前选中的字体大小资源ID + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键 - private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷图标标题的最大长度 - private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + public static final String TAG_CHECKED = String.valueOf('\u221A'); // 标记为已检查的字符串 + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 标记为未检查的字符串 - public static final String TAG_CHECKED = String.valueOf('\u221A'); - public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + private LinearLayout mEditTextList; // 编辑文本列表是线性布局 - public static final int TAKE_PHOTO = 1; - public static final int CHOOSE_PHOTO = 2; - private final int PHOTO_SUCCESS = 3; - private final int CAMERA_SUCCESS = 4; - // TextToSpeech tts; - TextView num_word; - TextView location; - public static final int LOCATION_CODE = 301; - private LocationManager locationManager; - private String locationProvider = null; - private final int PHOTO_REQUEST = 1; - private AlertDialog alertDialog2; - private CharSequence restore_translate = null; + private String mUserQuery; // 用户查询字符串 + private Pattern mPattern; // 正则表达式模式 - private boolean mIsRvoke = false; + private final int PHOTO_REQUEST = 1;//qxq:定义请求类型:插入图片 + private static Stack mChanged;//hzk:unachieved + private TextToSpeech mTTS;//实现为全局变量,用以之后的朗读 + private AlertDialog alertDialog2;//字体功能实现 + private CharSequence restore_translate = null;//存储翻译结果的字符序列 + private boolean mIsRvoke = false;//是否撤销的标志 + //qxq:加入请求存储权限代码 + private static final int PERMISSION_REQUEST_CODE = 100; - private LinearLayout mEditTextList; + private void requestStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }, PERMISSION_REQUEST_CODE); + } + } + } - private String mUserQuery; - private Pattern mPattern; + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == PERMISSION_REQUEST_CODE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // 权限被授予,可以访问图片 + Toast.makeText(this, "已获得存储权限", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "需要存储权限才能访问图片", Toast.LENGTH_SHORT).show(); + } + } + } + //qxq:end @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.setContentView(R.layout.note_edit); + protected void onCreate(Bundle savedInstanceState) {//qxq:初始化活动,Bundle变量是Android系统用于存储恢复Activity状态的参数 + super.onCreate(savedInstanceState);//super引用父类方法,进行初始化 + this.setContentView(R.layout.note_edit); // 设置活动的视图布局 + // 检查实例状态是否被保存,如果未保存且初始化活动状态失败,则结束该活动 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } - initResources(); + initResources(); // 初始化资源 + count(); + speak(); - final ImageButton add_img_btn = (ImageButton) findViewById(R.id.add_img_btn); - add_img_btn.setOnClickListener(new View.OnClickListener() { +//qxq: + final ImageButton add_img_btn = (ImageButton) findViewById(R.id.add_img_btn);//qxq:获取按钮实例,并绑定xml中控件和此处图片按钮变量 + add_img_btn.setOnClickListener(new View.OnClickListener() {//qxq:这个按钮被触发时,设置了点击监听器函数 @Override public void onClick(View view) { Log.d(TAG, "onClick: click add image button"); - Intent loadImage = new Intent(Intent.ACTION_GET_CONTENT); - loadImage.addCategory(Intent.CATEGORY_OPENABLE); - loadImage.setType("image/*"); - startActivityForResult(loadImage, PHOTO_REQUEST); + Intent loadImage = new Intent(Intent.ACTION_GET_CONTENT);//qxq:点击时创建Intent打开系统图片选择器 + loadImage.addCategory(Intent.CATEGORY_OPENABLE);//qxq:Used to indicate that an intent only wants URIs that can be opened with ContentResolver. + loadImage.setType("image/*");//qxq:过滤之后,类型为image/* + startActivityForResult(loadImage, PHOTO_REQUEST);//qxq:启动图片选择器,等待返回结果 } }); - } + /* + * 两个函数,分别实现朗读按钮控件和控件触发函数,并在oncreate中执行 + * + * */ + private void speak() + { + final Button speak =findViewById(R.id.spkBtn); + mTTS = new TextToSpeech(this, new TextToSpeech.OnInitListener() { + @Override + public void onInit(int status) { + if (status == TextToSpeech.SUCCESS) { + mTTS.setLanguage(Locale.US);//US为 + mTTS.setPitch(1.5f);// 设置音调,值越大声音越尖(女生),值越小则变成男声,1.0是常规 + mTTS.setSpeechRate(0.5f); + } + } + }); - /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state - */ - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); - if (!initActivityState(intent)) { - finish(); - return; + speak.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str=mNoteEditor.getText().toString(); + str=operateText(str); + str=cutOfimage(str); + mTTS.speak(str,TextToSpeech.QUEUE_FLUSH,null,"1"); } - Log.d(TAG, "Restoring from killed activity"); - } + }); } - private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ - mWorkingNote = null; - if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { - long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); - mUserQuery = ""; - - /** - * Starting from the searched result - */ - if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { - noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); - mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + private void count() + { + mNoteEditor.addTextChangedListener(new TextWatcher() { + int currentLength = 0; + String str; + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + mNoteHeaderHolder.tvNum.setText("字符数:" + currentLength); } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + //currentLength = mNoteEditor.getText().length(); + str = mNoteEditor.getText().toString(); + str = operateText(str); + str = cutOfimage(str); + currentLength= str.length(); + }//在onTextChanged()方法中,通过获取编辑框的文本长度并将其赋值给currentLength变量,以便在afterTextChanged()方法中使用。 - if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { - Intent jump = new Intent(this, NotesListActivity.class); - startActivity(jump); - showToast(R.string.error_note_not_exist); - finish(); - return false; - } else { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load note failed with note id" + noteId); - finish(); - return false; - } + @Override + public void afterTextChanged(Editable s) {//qxq:监听文本变化,并显示在tvNum中 + mNoteHeaderHolder.tvNum.setText("字符数:" + currentLength); } - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - } else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // New note - long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); - int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, - Notes.TYPE_WIDGET_INVALIDE); - int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, - ResourceParser.getDefaultBgId(this)); - // Parse call-record note - String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); - if (callDate != 0 && phoneNumber != null) { - if (TextUtils.isEmpty(phoneNumber)) { - Log.w(TAG, "The call record number is null"); - } - long noteId = 0; - if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), - phoneNumber, callDate)) > 0) { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load call note failed with note id" + noteId); - finish(); - return false; - } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, - widgetType, bgResId); - mWorkingNote.convertToCallNote(phoneNumber, callDate); - } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, - bgResId); - } + }); + } - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } else { - Log.e(TAG, "Intent not specified action, should not support"); - finish(); - return false; + public String operateText(String str){ + String dest = ""; + Pattern p = Pattern.compile("\\s*|t|r|n"); + Matcher m = p.matcher(str); + dest = m.replaceAll(""); + return dest; + } + public String cutOfimage(String str) { + String dest = str; + int index1 = dest.indexOf("[local]"); + int index2 = dest.indexOf("[/local]"); + while (index1 != -1 && index2 != -1) { + dest = dest.substring(0, index1) + dest.substring(index2 + 8); + index1 = dest.indexOf("[local]"); + index2 = dest.indexOf("[/local]"); } - mWorkingNote.setOnSettingStatusChangedListener(this); - return true; + return dest; } - @Override - protected void onResume() { - super.onResume(); - initNoteScreen(); - } - private void initNoteScreen() { - mNoteEditor.setTextAppearance(this, TextAppearanceResources - .getTexAppearanceResource(mFontSizeId)); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); - } - for (Integer id : sBgSelectorSelectionMap.keySet()) { - findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); - } - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + private void convertToImage() { + requestStoragePermission();//qxq:确保增加权限 + NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view);//根据id寻找 + Editable editable = noteEditText.getText(); + String noteText = editable.toString(); + int length = editable.length(); + for(int i = 0; i < length; i++) { + for(int j = i; j < length; j++) {//qxq:通过穷尽所有可能的字符串,进行查找满足的图片路径 + String img_fragment = noteText.substring(i, j+1); + if(img_fragment.length() > 15 && img_fragment.endsWith("[/local]") && img_fragment.startsWith("[local]")){ + int limit = 7; + int len = img_fragment.length()-15; + String path = img_fragment.substring(limit,limit+len); + Bitmap bitmap = null; + Log.d(TAG, "图片的路径是:"+path); - mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, - mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR)); - /** - * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready - */ - showAlertHeader(); - convertToImage(); - showTopHeader(); - } + File imageFile = new File(path);//qxq + //qxq:content://media/picker/0/com.android.providers.media.photopicker/media/1000000041 + /* ContentResolver resolver = getContentResolver(); + String uriString = "content://media/picker/0/com.android.providers.media.photopicker/media/1000000041"; + Uri uri = Uri.parse(uriString); + try { + bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri));//2.解码图片 + Log.d(TAG, "onActivityResult: originalUri is: "+uri); + } catch (FileNotFoundException e) { + Log.d(TAG, "onActivityResult: get file_exception"); + e.printStackTrace(); + }//qxq:end*/ + Bitmap bitmap2=null; + try { + bitmap2 = BitmapFactory.decodeFile(imageFile.getAbsolutePath()); + }catch(Exception e){ + e.printStackTrace(); + Log.d(TAG, "图片路径bitmap2不可decode且路径是:"+path); + } + if(bitmap2!=null){ + Log.d(TAG, "图片bitmap2不为null,且图片路径为:"+imageFile.getAbsolutePath()); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap2); + String ss = "[local]" + path + "[/local]"; + SpannableString spannableString = new SpannableString(ss); + spannableString.setSpan(imageSpan, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + Log.d(TAG, "Create spannable string success!"); + Editable edit_text = noteEditText.getEditableText(); + edit_text.delete(i,i+len+15); + edit_text.insert(i, spannableString); + }//qxq:end - 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)); - } - mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); - } else { - mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - } - } - private void showTopHeader() { - mNoteHeaderHolder.tvToptext.setText(R.string.menu_set_top); - if (mWorkingNote.getTopId() == 1) { - mNoteHeaderHolder.tvToptext.setVisibility(View.VISIBLE); - } else { - mNoteHeaderHolder.tvToptext.setVisibility(View.GONE); - } - } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - initActivityState(intent); - } + try { + bitmap = BitmapFactory.decodeFile(path); + } catch (Exception e) { + e.printStackTrace(); + Log.d(TAG, "图片路径不可decode且路径是:"+path); + } - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); + if(bitmap!=null){ + Log.d(TAG, "图片不为null,且图片路径为:"+path); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); + String ss = "[local]" + path + "[/local]"; + SpannableString spannableString = new SpannableString(ss); + spannableString.setSpan(imageSpan, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + Log.d(TAG, "Create spannable string success!"); + Editable edit_text = noteEditText.getEditableText(); + edit_text.delete(i,i+len+15); + edit_text.insert(i, spannableString); + } + } + } } - outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mNoteBgColorSelector, ev)) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } - - if (mFontSizeSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mFontSizeSelector, ev)) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return super.dispatchTouchEvent(ev); } + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + protected void onActivityResult(int requestCode, int resultCode, Intent intent) {//qxq:这一块涉及解码intent里的originalUri和上传到数据库(服务器) + super.onActivityResult(requestCode, resultCode, intent); + ContentResolver resolver = getContentResolver(); + switch (requestCode) { + case PHOTO_REQUEST: + Uri originalUri = intent.getData(); + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(resolver.openInputStream(originalUri));//2.解码图片 + Log.d(TAG, "onActivityResult: originalUri is: "+originalUri); + } catch (FileNotFoundException e) { + Log.d(TAG, "onActivityResult: get file_exception"); + e.printStackTrace(); + } - private boolean inRangeOfView(View view, MotionEvent ev) { - int[] location = new int[2]; - view.getLocationOnScreen(location); - int x = location[0]; - int y = location[1]; - if (ev.getX() < x - || ev.getX() > (x + view.getWidth()) - || ev.getY() < y - || ev.getY() > (y + view.getHeight())) { - return false; + if (bitmap != null) { + Log.d(TAG, "onActivityResult: bitmap is not null"); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap);//创建ImageSpan对象 + String path = getPath(this, originalUri);//获取图片路径,这里将uri转换为了path(string型) + String img_fragment = "[local]" + path + "[/local]";//将图片路径转换为img_fragment + SpannableString spannableString = new SpannableString(img_fragment);//创建SpannableString对象 + spannableString.setSpan(imageSpan, 0, img_fragment.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + NoteEditText e = (NoteEditText) findViewById(R.id.note_edit_view);//获取NoteEditText对象 + int index = e.getSelectionStart();//获取选中的位置 + Log.d(TAG, "Index是: " + index); + Editable edit_text = e.getEditableText();//获取可编辑的文本 + edit_text.insert(index, spannableString);//在选中的位置插入img_fragment,这里是路径 + mWorkingNote.setmContent(e.getText().toString());//设置笔记内容 + ContentResolver contentResolver = getContentResolver();//获取ContentResolver对象 + ContentValues contentValues = new ContentValues(); + final long id = mWorkingNote.getNoteId(); + contentValues.put("snippet", mWorkingNote.getContent());//存入数据库 + contentResolver.update(Uri.parse("content://micode_notes/note"), contentValues, "_id=?", new String[]{"" + id}); + ContentValues contentValues1 = new ContentValues(); + contentValues1.put("content", mWorkingNote.getContent()); + contentResolver.update(Uri.parse("content://micode_notes/data"), contentValues1, "mime_type=? and note_id=?", new String[]{"vnd.android.cursor.item/text_note", "" + id}); + + } else { + Toast.makeText(NoteEditActivity.this, "获取图片失败", Toast.LENGTH_SHORT).show(); + } + break; + default: + break; } - return true; } - private void initResources() { - mHeadViewPanel = findViewById(R.id.note_title); - mNoteHeaderHolder = new HeadViewHolder(); - mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); - mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); - mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); - mNoteHeaderHolder.tvToptext = (TextView) findViewById(R.id.tv_set_top); - mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); - - mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); - mNoteEditor = (EditText) findViewById(R.id.note_edit_view); - mNoteEditor.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public String getPath(final Context context, final Uri uri) { - } + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;//判断是否是安卓7.0及以上 + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {//判断是否是文档uri + if (isMediaDocument(uri)) {//判断是否是媒体文档 + final String docId = DocumentsContract.getDocumentId(uri);//获取文档id + final String[] split = docId.split(":");//将文档id分割为数组 + final String type = split[0];//获取文档类型 - @Override - public void afterTextChanged(Editable s) {//文本更改后 - if (!mIsRvoke) { - saveMyChanged(); - } else { - mIsRvoke = false; + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + + return getDataColumn(context, contentUri, selection, selectionArgs); } - }); - mNoteEditorPanel = findViewById(R.id.sv_note_edit); - mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); - for (int id : sBgSelectorBtnsMap.keySet()) { - ImageView iv = (ImageView) findViewById(id); - iv.setOnClickListener(this); } - - mFontSizeSelector = findViewById(R.id.font_size_selector); - for (int id : sFontSizeBtnsMap.keySet()) { - View view = findViewById(id); - view.setOnClickListener(this); + // Media + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); } - ; - mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); - /** - * HACKME: Fix bug of store the resource id in shared preference. - * * The id may larger than the length of resources, in this case, - * * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ - if (mFontSizeId >= TextAppearanceResources.getResourcesSize()) { - mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); } - mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); - - - speech_sum(); - local_sel(); - translate(); - + return null; } - @Override - protected void onPause() { - super.onPause(); - if (saveNote()) { - Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); - } - clearSettingState(); - - } + public String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; - private void updateWidget() { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); } + return null; + } - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{ - mWorkingNote.getWidgetId() - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); + public boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); } - public void onClick(View v) { - int id = v.getId(); - if (id == R.id.btn_set_bg_color) { - mNoteBgColorSelector.setVisibility(View.VISIBLE); - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); - } else if (sBgSelectorBtnsMap.containsKey(id)) { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.GONE); - mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); - mNoteBgColorSelector.setVisibility(View.GONE); - } else if (sFontSizeBtnsMap.containsKey(id)) { - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); - mFontSizeId = sFontSizeBtnsMap.get(id); - mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - getWorkingText(); - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setTextAppearance(this, - TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + //qxq:翻译 + public void translate() { + final EditText editable = findViewById(R.id.note_edit_view); + final Button get_local = findViewById(R.id.translate); + get_local.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + Button trans1 = new Button(NoteEditActivity.this); + Button trans2 = new Button(NoteEditActivity.this); + Button trans3 = new Button(NoteEditActivity.this); + trans1.setText("中文翻译为英文"); + trans2.setText("英文翻译为中文"); + trans3.setText("还原"); + LinearLayout linear = new LinearLayout(NoteEditActivity.this); + linear.setOrientation(LinearLayout.VERTICAL); + linear.addView(trans1); + linear.addView(trans2); + linear.addView(trans3); + AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this); + builder.setView(linear); + builder.setTitle("请选择翻译模式"); + AlertDialog choose_trans = builder.create(); + choose_trans.show(); + trans1.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + restore_translate = editable.getText(); + translate_z2u(); + Toast.makeText(NoteEditActivity.this, "中文翻译为英文", Toast.LENGTH_SHORT).show(); + } + }); + trans2.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + restore_translate = editable.getText(); + translate_u2z(); + Toast.makeText(NoteEditActivity.this, "英文翻译为中文", Toast.LENGTH_SHORT).show(); + } + }); + trans3.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (restore_translate == null || restore_translate.toString().equals(editable.getText().toString())) { + Toast.makeText(NoteEditActivity.this, "无可还原内容", Toast.LENGTH_SHORT).show(); + } else { + editable.setText(restore_translate); + Toast.makeText(NoteEditActivity.this, "已还原", Toast.LENGTH_SHORT).show(); + } + } + }); } - mFontSizeSelector.setVisibility(View.GONE); - } + }); } - @Override - public void onBackPressed() { - if (clearSettingState()) { - return; - } - saveNote(); - super.onBackPressed(); + public void translate_z2u() { + final EditText editable = findViewById(R.id.note_edit_view); + String word = editable.getText().toString(); + word = word.replaceAll("\\n", "//"); + String from = "auto"; + String to = "en"; + String appid = "20221021001406789"; + String salt = (int) (Math.random() * 100 + 1) + ""; + String key = "eWky8SSKgL99Dh4rHMog"; + String secretKey = appid + word + salt + key; + String sign = MD5Utils.getMD5Code(secretKey); + Log.d(TAG, "secretKey:" + secretKey); + Log.d(TAG, "sign: " + sign); + Retrofit retrofitBaidu = new Retrofit.Builder() + .baseUrl("https://fanyi-api.baidu.com/api/trans/vip/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + BaiduTranslateService baiduTranslateService = retrofitBaidu.create(BaiduTranslateService.class); + retrofit2.Call call = baiduTranslateService.translate(word, from, to, appid, salt, sign); + call.enqueue(new Callback() { + @Override + public void onResponse(retrofit2.Call call, Response response) { + Log.d(TAG, "onResponse: 请求成功"); + RespondBean respondBean = response.body(); + String result = respondBean.getTrans_result().get(0).getDst(); + editable.setText(result); + Log.d(TAG, "中译英结果" + result); + } + + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + Log.d(TAG, "onResponse: 请求失败 " + t); + } + }); } - private boolean clearSettingState() { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return false; - } + public void translate_u2z() { + final EditText editable = findViewById(R.id.note_edit_view); + String word = editable.getText().toString(); + word = word.replaceAll("\\n", "//"); + Log.d(TAG, word); + String from = "auto"; + String to = "zh"; + String appid = "20221021001406789"; + String salt = (int) (Math.random() * 100 + 1) + ""; + String key = "eWky8SSKgL99Dh4rHMog"; + String secretKey = appid + word + salt + key; + String sign = MD5Utils.getMD5Code(secretKey); + Log.d(TAG, "secretKey:" + secretKey); + Log.d(TAG, "sign: " + sign); + Retrofit retrofitBaidu = new Retrofit.Builder() + .baseUrl("https://fanyi-api.baidu.com/api/trans/vip/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + BaiduTranslateService baiduTranslateService = retrofitBaidu.create(BaiduTranslateService.class); + retrofit2.Call call = baiduTranslateService.translate(word, from, to, appid, salt, sign); + call.enqueue(new Callback() { + @Override + public void onResponse(retrofit2.Call call, Response response) { + Log.d(TAG, "onResponse: 请求成功"); + RespondBean respondBean = response.body(); + String result = respondBean.getTrans_result().get(0).getDst(); + editable.setText(result); + Log.d(TAG, "中译英结果" + result); + } - public void onBackgroundColorChanged() { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + @Override + public void onFailure(retrofit2.Call call, Throwable t) { + Log.d(TAG, "onResponse: 请求失败 " + t); + } + }); } - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (isFinishing()) { - return true; - } - clearSettingState(); - menu.clear(); - if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_note_edit, menu); - } else { - getMenuInflater().inflate(R.menu.note_edit, menu); - } - if (mWorkingNote.getTopId() == 1) { - menu.findItem(R.id.menu_set_top).setVisible(false); - } else if (mWorkingNote.getTopId() == 0) { - menu.findItem(R.id.menu_cancel_top).setVisible(false); - } - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); - } else { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); - } - if (mWorkingNote.hasClockAlert()) { - menu.findItem(R.id.menu_alert).setVisible(false); - } else { - menu.findItem(R.id.menu_delete_remind).setVisible(false); - } - return true; - } + /** + * 当活动被系统销毁后,为了恢复之前的状态,此方法会被调用。 + * 主要用于处理活动重新创建时的数据恢复。 + * + * @param savedInstanceState 包含之前保存状态的Bundle,如果活动之前保存了状态,这里会传入非空的Bundle。 + */ @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_note: - createNewNote(); - break; - case R.id.menu_revoke: - doRevoke(); - break; - case R.id.menu_set_top: - mWorkingNote.setTop((mWorkingNote.getTopId()) == 1 ? "0" : "1"); - break; - case R.id.menu_cancel_top: - mWorkingNote.setTop((mWorkingNote.getTopId()) == 0 ? "1" : "0"); - break; - case R.id.menu_delete: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_note)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteCurrentNote(); - finish(); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case R.id.menu_font_size: - mFontSizeSelector.setVisibility(View.VISIBLE); - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - break; - case R.id.menu_list_mode: - mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? - TextNote.MODE_CHECK_LIST : 0); - break; - case R.id.menu_share: - getWorkingText(); - sendTo(this, mWorkingNote.getContent()); - break; - case R.id.menu_send_to_desktop: - sendToDesktop(); - break; - case R.id.menu_alert: - setReminder(); - break; - case R.id.menu_delete_remind: - mWorkingNote.setAlertDate(0, false); - break; - case R.id.menu_font_select: - showSingleAlertDiglog(); - break; - case R.id.menu_count_word://数字统计 - AlertDialog.Builder builder1 = new AlertDialog.Builder(this);//对话框对象 - builder1.setIcon(android.R.drawable.ic_dialog_alert);//对话框图标 - TextView content = (TextView) findViewById(R.id.note_edit_view); - int c = content.length(); - builder1.setMessage("带符号字数总和统计为:" + c); - builder1.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - });//确定按钮 - builder1.setNegativeButton(android.R.string.cancel, null);//取消按钮 - builder1.show();//对话框启动 - break; - default: - break; + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + // 检查是否有保存的状态并且包含必要的UID信息,用于恢复活动状态 + if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + // 使用intent尝试恢复活动状态,如果失败则结束该活动 + if (!initActivityState(intent)) { + finish(); + return; + } + Log.d(TAG, "Restoring from killed activity"); // 日志记录,表示活动状态正在从被杀死的状态恢复 } - return true; } - private void setReminder() { - DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); - d.setOnDateTimeSetListener(new OnDateTimeSetListener() { - public void OnDateTimeSet(AlertDialog dialog, long date) { - mWorkingNote.setAlertDate(date, true); - } - }); - d.show(); - } /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type + * 初始化活动状态,根据传入的Intent确定是查看笔记、新建笔记还是编辑笔记。 + * + * @param intent 传入的Intent,包含了动作类型和相关数据。 + * @return boolean 返回true表示成功初始化活动状态,false表示初始化失败。 */ - private void sendTo(Context context, String info) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, info); - intent.setType("text/plain"); - context.startActivity(intent); - } - - private void createNewNote() { - // Firstly, save current editing notes - saveNote(); + private boolean initActivityState(Intent intent) { + // 如果用户指定的是查看笔记的动作但未提供笔记ID,则跳转到笔记列表活动 + mWorkingNote = null; + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); + mUserQuery = ""; - // For safety, start a new NoteEditActivity - finish(); - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); - startActivity(intent); - } + // 从搜索结果开始 + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + } - private void deleteCurrentNote() { - if (mWorkingNote.existInDatabase()) { - HashSet ids = new HashSet(); - long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) { - ids.add(id); + // 检查指定的笔记在数据库中是否可见 + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { + Intent jump = new Intent(this, NotesListActivity.class); + startActivity(jump); + showToast(R.string.error_note_not_exist); + finish(); + return false; } else { - Log.d(TAG, "Wrong note id, should not happen"); + // 加载指定ID的笔记 + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load note failed with note id" + noteId); + finish(); + return false; + } } - if (!isSyncMode()) { - if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { - Log.e(TAG, "Delete Note error"); + // 隐藏软键盘 + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // 处理新建或编辑笔记的情况 + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE); + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this)); + + // 解析通话记录笔记 + String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); + long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); + if (callDate != 0 && phoneNumber != null) { + // 根据电话号码和通话日期尝试获取已有笔记ID + if (TextUtils.isEmpty(phoneNumber)) { + Log.w(TAG, "The call record number is null"); } - } else { - if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); + long noteId = 0; + if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), + phoneNumber, callDate)) > 0) { + // 加载该笔记 + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load call note failed with note id" + noteId); + finish(); + return false; + } + } else { + // 创建新的通话记录笔记 + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, + widgetType, bgResId); + mWorkingNote.convertToCallNote(phoneNumber, callDate); } + } else { + // 创建普通空笔记 + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, + bgResId); } + + // 显示软键盘 + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else { + // 不支持的Intent动作 + Log.e(TAG, "Intent not specified action, should not support"); + finish(); + return false; } - mWorkingNote.markDeleted(true); + // 设置笔记状态改变的监听器 + mWorkingNote.setOnSettingStatusChangedListener(this); + return true; } - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + + /** + * 当Activity恢复到前台时调用。 + * 主要负责初始化笔记界面。 + */ + @Override + protected void onResume() {//当Activity恢复到前台时调用。主要负责初始化笔记界面。 + super.onResume(); + initNoteScreen(); } - public void onTopChanged(String Top) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ - saveNote(); - if (mWorkingNote.getNoteId() > 0) { - showTopHeader(); + /** + * 初始化笔记界面的函数。 + * 该函数设置笔记编辑器的外观,根据笔记类型切换到相应的模式,设置背景和头部信息, + * 以及处理提醒头部的显示。 + */ + private void initNoteScreen() {//初始化笔记界面的函数。该函数设置笔记编辑器的外观,根据笔记类型切换到相应的模式,设置背景和头部信息,以及处理提醒头部的显示 + // 设置编辑器的文本外观 + mNoteEditor.setTextAppearance(this, TextAppearanceResources + .getTexAppearanceResource(mFontSizeId)); + // 根据当前笔记的类型,切换到列表模式或高亮查询结果模式 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + switchToListMode(mWorkingNote.getContent()); } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Top setting error"); - showToast(R.string.error_note_empty_for_top); + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mNoteEditor.setSelection(mNoteEditor.getText().length()); } - } - - - public void onClockAlertChanged(long date, boolean set) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); + // 隐藏所有背景选择器 + for (Integer id : sBgSelectorSelectionMap.keySet()) { + findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } - 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); - AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); - showAlertHeader(); - if (!set) { - alarmManager.cancel(pendingIntent); + // 设置标题和编辑区域的背景 + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + + // 设置修改时间 + mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, + mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR)); + + // 显示提醒头部信息(当前禁用,因DateTimePicker未准备好) + showAlertHeader(); + //qxq:增加转换功能 + convertToImage(); + } + + /** + * 显示提醒头部信息的方法。 + * 如果当前笔记设置了提醒,该方法将根据提醒时间显示相对的时间或者过期信息。 + */ + private void showAlertHeader() { + // 处理提醒显示 + if (mWorkingNote.hasClockAlert()) { + long time = System.currentTimeMillis(); + // 如果提醒时间已过,显示提醒已过期的信息 + if (time > mWorkingNote.getAlertDate()) { + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); + // 否则,显示相对时间 + mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } + // 设置提醒视图可见 + mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Clock alert setting error"); - showToast(R.string.error_note_empty_for_clock); + // 如果没有设置提醒,隐藏提醒视图 + mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); } } - public void onWidgetChanged() { - updateWidget(); + /** + * 当Activity接收到新的Intent时调用。 + * 用于根据新的Intent重新初始化Activity状态。 + * + * @param intent 新的Intent + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + initActivityState(intent); } - public void onEditTextDelete(int index, String text) { - int childCount = mEditTextList.getChildCount(); - if (childCount == 1) { - return; + /** + * 保存Activity状态时调用。 + * 用于保存当前编辑的笔记,如果该笔记还未保存到数据库,则首先保存它。 + * 并且保存当前笔记的ID到Bundle中。 + * + * @param outState 用于保存Activity状态的Bundle + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // 如果当前编辑的笔记不存在于数据库中,即还未保存,先保存它 + if (!mWorkingNote.existInDatabase()) { + saveNote(); } + // 保存笔记ID + outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); + } - for (int i = index + 1; i < childCount; i++) { - ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i - 1); - } - mEditTextList.removeViewAt(index); - NoteEditText edit = null; - if (index == 0) { - edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( - R.id.et_edit_text); - } else { - edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( - R.id.et_edit_text); + /** + * 分发触摸事件。如果触摸事件不在指定视图范围内,则隐藏该视图。 + * + * @param ev 触摸事件 + * @return 如果事件被消费则返回true,否则返回false + */ + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + // 如果背景颜色选择器可见且不在触摸范围内,则隐藏它并消费事件 + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mNoteBgColorSelector, ev)) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; } - int length = edit.length(); - edit.append(text); - edit.requestFocus(); - edit.setSelection(length); - } - public void onEditTextEnter(int index, String text) { - /** - * Should not happen, check for debug - */ - if (index > mEditTextList.getChildCount()) { - Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + // 如果字体大小选择器可见且不在触摸范围内,则隐藏它并消费事件 + if (mFontSizeSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mFontSizeSelector, ev)) { + mFontSizeSelector.setVisibility(View.GONE); + return true; } + // 如果上述条件都不满足,则将事件传递给父类处理 + return super.dispatchTouchEvent(ev); + } - View view = getListItem(text, index); - mEditTextList.addView(view, index); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.requestFocus(); - edit.setSelection(0); - for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { - ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i); + /** + * 判断触摸点是否在指定视图范围内。 + * + * @param view 视图 + * @param ev 触摸事件 + * @return 如果触摸点在视图范围内则返回true,否则返回false + */ + private boolean inRangeOfView(View view, MotionEvent ev) { + int[] location = new int[2]; + view.getLocationOnScreen(location); + int x = location[0]; + int y = location[1]; + if (ev.getX() < x + || ev.getX() > (x + view.getWidth()) + || ev.getY() < y + || ev.getY() > (y + view.getHeight())) { + return false;// 判断触摸点是否在视图外 } + return true; } - private void switchToListMode(String text) { - mEditTextList.removeAllViews(); - String[] items = text.split("\n"); - int index = 0; - for (String item : items) { - if (!TextUtils.isEmpty(item)) { - mEditTextList.addView(getListItem(item, index)); - index++; - } - } - mEditTextList.addView(getListItem("", index)); - mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + /** + * 初始化资源,包括视图和偏好设置。 + */ + private void initResources() {// 初始化头部视图和相关组件 + mHeadViewPanel = findViewById(R.id.note_title); + mNoteHeaderHolder = new HeadViewHolder(); + mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); + mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); + mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + mNoteHeaderHolder.tvNum = (TextView) findViewById(R.id.note_num);//qxq:初始化绑定视图变量 + // 初始化编辑器和相关组件 + mNoteEditor = (EditText) findViewById(R.id.note_edit_view); - mNoteEditor.setVisibility(View.GONE); - mEditTextList.setVisibility(View.VISIBLE); - } + mNoteEditorPanel = findViewById(R.id.sv_note_edit); + mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); - private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); - if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); - Matcher m = mPattern.matcher(fullText); - int start = 0; - while (m.find(start)) { - spannable.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - start = m.end(); - } - } - return spannable; - } + //下面这段代码的作用是为sBgSelectorBtnsMap中的所有ImageView对象设置点击事件监听器。 + // 当某个ImageView被点击时,会触发与当前类关联的onClick()方法。 + //sBgSelectorBtnsMap是一个Map对象,它存储了一些键值对,key对应id,每个值则未在这段代码中给出。 + // 这种数据结构通常用于在Android应用中存储和操作UI元素,例如这里的ImageView。 + // 通过键(即ImageView的id),可以快速找到并操作对应的UI元素。 + // 具体来说,这段代码遍历了sBgSelectorBtnsMap中的所有键,并为每个键对应的ImageView设置了一个点击事件监听器。 - private View getListItem(String item, int index) { - View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); - final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); - cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - } - } - }); + for (int id : sBgSelectorBtnsMap.keySet()) { + ImageView iv = (ImageView) findViewById(id); + iv.setOnClickListener(this); + }// 设置背景选择器按钮点击监听器 - if (item.startsWith(TAG_CHECKED)) { - cb.setChecked(true); - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - item = item.substring(TAG_CHECKED.length(), item.length()).trim(); - } else if (item.startsWith(TAG_UNCHECKED)) { - cb.setChecked(false); - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + mFontSizeSelector = findViewById(R.id.font_size_selector); + + for (int id : sFontSizeBtnsMap.keySet()) { + View view = findViewById(id); + view.setOnClickListener(this); + }// 设置字体大小选择器按钮点击监听器 + ; + // 从偏好设置中读取字体大小 + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); + // 修复存储在偏好设置中的字体大小资源ID的错误 + if (mFontSizeId >= TextAppearanceResources.getResourcesSize()) { + mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } + // 初始化编辑列表 + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + translate(); - edit.setOnTextViewChangeListener(this); - edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); - return view; - } - public void onTextChange(int index, boolean hasText) { - if (index >= mEditTextList.getChildCount()) { - Log.e(TAG, "Wrong index, should not happen"); - return; - } - if (hasText) { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); - } else { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); - } } - public void onCheckListModeChanged(int oldMode, int newMode) { - if (newMode == TextNote.MODE_CHECK_LIST) { - switchToListMode(mNoteEditor.getText().toString()); - } else { - if (!getWorkingText()) { - mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", - "")); - } - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mEditTextList.setVisibility(View.GONE); - mNoteEditor.setVisibility(View.VISIBLE); + /** + * 在Activity即将失去焦点并进入后台时的回调方法,用于执行一些与保存数据、清除状态等相关的操作。 + */ + @Override + protected void onPause() {//暂停时保存笔记数据并清除设置状态。 + super.onPause(); + // 保存笔记数据 + if (saveNote()) { + Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } + clearSettingState();//调用clearSettingState()方法来清除一些设置状态。具体的逻辑需要根据实际需求来实现,可能包括清除临时数据、重置一些标志位等。 } - private boolean getWorkingText() { - boolean hasChecked = false; - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mEditTextList.getChildCount(); i++) { - View view = mEditTextList.getChildAt(i); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - if (!TextUtils.isEmpty(edit.getText())) { - if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { - sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); - hasChecked = true; - } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); - } - } - } - mWorkingNote.setWorkingText(sb.toString()); + /** + * 更新小部件显示,和桌面小工具的同步 + */ + private void updateWidget() { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 根据小部件类型设置对应的类 + if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + Log.e(TAG, "Unspported widget type"); + return; } - return hasChecked; - } - private boolean saveNote() { - getWorkingText(); - boolean saved = mWorkingNote.saveNote(); - if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ - setResult(RESULT_OK); - } - return saved; + // 设置小部件ID + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{ + mWorkingNote.getWidgetId() + }); + + // 发送广播更新小部件 + sendBroadcast(intent); + // 设置结果为OK + setResult(RESULT_OK, intent); } - private void sendToDesktop() { - /** - * Before send message to home, we should make sure that current - * editing note is exists in databases. So, for new note, firstly - * save it - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - if (mWorkingNote.getNoteId() > 0) { - Intent sender = new Intent(); - Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent())); - sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); - sender.putExtra("duplicate", true); - sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - showToast(R.string.info_note_enter_desktop); - sendBroadcast(sender); - } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Send to desktop error"); - showToast(R.string.error_note_empty_for_send_to_desktop); + /** + * 点击事件的处理函数。 + * + * @param v 被点击的视图对象。 + */ + public void onClick(View v) { + int id = v.getId(); + // 如果点击的是设置背景颜色的按钮 + if (id == R.id.btn_set_bg_color) { + mNoteBgColorSelector.setVisibility(View.VISIBLE); + // 根据当前笔记的背景颜色,设置对应的色板按钮可见 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.VISIBLE); + } else if (sBgSelectorBtnsMap.containsKey(id)) { + // 如果点击的是背景色板上的颜色 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.GONE); // 隐藏当前选择的背景颜色 + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 更新笔记的背景颜色 + mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏背景颜色选择器 + } else if (sFontSizeBtnsMap.containsKey(id)) { + // 如果点击的是字体大小按钮 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); // 隐藏当前选择的字体大小 + mFontSizeId = sFontSizeBtnsMap.get(id); // 更新选择的字体大小 + mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 保存选择的字体大小到SharedPreferences + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); // 设置新选择的字体大小按钮为可见 + // 根据当前笔记是否为清单模式,进行相应的文本更新 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + getWorkingText(); + switchToListMode(mWorkingNote.getContent()); + } else { + mNoteEditor.setTextAppearance(this, + TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + } + mFontSizeSelector.setVisibility(View.GONE); // 隐藏字体大小选择器 } } - private String makeShortcutIconTitle(String content) { - content = content.replace(TAG_CHECKED, ""); - content = content.replace(TAG_UNCHECKED, ""); - return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, - SHORTCUT_ICON_TITLE_MAX_LEN) : content; + /** + * 按下返回键时的处理函数。 + */ + @Override + public void onBackPressed() { + // 尝试清除设置状态,如果成功,则不执行保存笔记操作 + if (clearSettingState()) { + return; + } + // 保存笔记并执行父类的onBackPressed()方法(结束当前Activity) + saveNote(); + super.onBackPressed(); } - private void showToast(int resId) { - showToast(resId, Toast.LENGTH_SHORT); + /** + * 尝试清除设置状态(背景颜色选择或字体大小选择)。 + * + * @return 如果成功清除设置状态,返回true;否则返回false。 + */ + private boolean clearSettingState() { + // 如果背景颜色选择器可见,则隐藏它并返回true + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { // 如果字体大小选择器可见,则隐藏它并返回true + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return false; // 没有可见的设置状态需要清除,返回false } - private void showToast(int resId, int duration) { - Toast.makeText(this, resId, duration).show(); + /** + * 当背景颜色发生变化时的处理函数。 + */ + public void onBackgroundColorChanged() { + // 根据当前笔记的背景颜色,设置对应的色板按钮可见,并更新编辑器及标题栏的背景颜色 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.VISIBLE); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } - - private String getLocation() { - //1.获取位置管理器 - String city = null; - locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); - - - //2.获取位置提供器,GPS或是NetWork - List providers = locationManager.getProviders(true); - - if (providers.contains(LocationManager.GPS_PROVIDER)) { - //如果是GPS - locationProvider = LocationManager.GPS_PROVIDER; - Log.v("TAG", "定位方式GPS"); - } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) { - //如果是Network - locationProvider = LocationManager.NETWORK_PROVIDER; - Log.v("TAG", "定位方式Network"); - } else { - Toast.makeText(this, "没有可用的位置提供器", Toast.LENGTH_SHORT).show(); - return null; + /** + * 准备选项菜单的函数。 + * + * @param menu 选项菜单对象。 + * @return 总是返回true。 + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + // 如果Activity正在结束,则不进行任何操作 + if (isFinishing()) { + return true; } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - //获取权限(如果没有开启权限,会弹出对话框,询问是否开启权限) - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED || - ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - //请求权限 - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_CODE); - } else { - //3.获取上次的位置,一般第一次运行,此值为null - Location location = locationManager.getLastKnownLocation(locationProvider); - if (location != null) { - Toast.makeText(this, location.getLongitude() + " " + - location.getLatitude() + "", Toast.LENGTH_SHORT).show(); - Log.v("TAG", "获取上次的位置-经纬度:" + location.getLongitude() + " " + location.getLatitude()); - city = getAddress(location); - - } else { - //监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace - locationManager.requestLocationUpdates(locationProvider, 3000, 1, locationListener); - } - } + // 尝试清除设置状态 + clearSettingState(); + menu.clear(); // 清除菜单项 + // 根据笔记所属的文件夹,加载不同的菜单资源 + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { - Location location = locationManager.getLastKnownLocation(locationProvider); - if (location != null) { - Toast.makeText(this, location.getLongitude() + " " + - location.getLatitude() + "", Toast.LENGTH_SHORT).show(); - Log.v("TAG", "获取上次的位置-经纬度:" + location.getLongitude() + " " + location.getLatitude()); - city = getAddress(location); - - } else { - //监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace - locationManager.requestLocationUpdates(locationProvider, 3000, 1, locationListener); - } - } - return city; - } - - public LocationListener locationListener = new LocationListener() { - // Provider的状态在可用、暂时不可用和无服务三个状态直接切换时触发此函数 - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { + getMenuInflater().inflate(R.menu.note_edit, menu); } - - // Provider被enable时触发此函数,比如GPS被打开 - @Override - public void onProviderEnabled(String provider) { + // 根据当前笔记的清单模式,更新“清单模式”菜单项的标题 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); + } else { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } - - // Provider被disable时触发此函数,比如GPS被关闭 - @Override - public void onProviderDisabled(String provider) { + // 根据笔记是否有提醒,更新“删除提醒”菜单项的可见性 + if (mWorkingNote.hasClockAlert()) { + menu.findItem(R.id.menu_alert).setVisible(false); + } else { + menu.findItem(R.id.menu_delete_remind).setVisible(false); } + return true; + } - //当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发 - @Override - public void onLocationChanged(Location location) { - if (location != null) { - //如果位置发生变化,重新显示地理位置经纬度 - Toast.makeText(NoteEditActivity.this, location.getLongitude() + " " + - location.getLatitude() + "", Toast.LENGTH_SHORT).show(); - Log.v("TAG", "监视地理位置变化-经纬度:" + location.getLongitude() + " " + location.getLatitude()); - } - } - }; + /** + * 处理选项菜单项的选择事件。 + * + * @param item 选中的菜单项 + * @return 总是返回true,表示事件已处理。 + */ @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - switch (requestCode) { - case LOCATION_CODE: - if (grantResults.length > 0 && grantResults[0] == getPackageManager().PERMISSION_GRANTED - && grantResults[1] == PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "申请权限", Toast.LENGTH_LONG).show(); - try { - List providers = locationManager.getProviders(true); - if (providers.contains(LocationManager.NETWORK_PROVIDER)) { - //如果是Network - locationProvider = LocationManager.NETWORK_PROVIDER; - - } else if (providers.contains(LocationManager.GPS_PROVIDER)) { - //如果是GPS - locationProvider = LocationManager.GPS_PROVIDER; - } - Location location = locationManager.getLastKnownLocation(locationProvider); - if (location != null) { - Toast.makeText(this, location.getLongitude() + " " + - location.getLatitude() + "", Toast.LENGTH_SHORT).show(); - Log.v("TAG", "获取上次的位置-经纬度:" + location.getLongitude() + " " + location.getLatitude()); - } else { - // 监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace - locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener); - } - - } catch (SecurityException e) { - e.printStackTrace(); - } - } else { - Toast.makeText(this, "缺少权限", Toast.LENGTH_LONG).show(); - finish(); - } + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_note: + // 创建新笔记 + createNewNote(); + break; + case R.id.menu_delete: + // 显示删除笔记的确认对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_note)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // 确认删除当前笔记并结束当前活动 + deleteCurrentNote(); + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.menu_font_size: + // 显示字体大小选择器 + mFontSizeSelector.setVisibility(View.VISIBLE); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + break; + case R.id.menu_list_mode: + // 切换笔记的列表模式 + mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? + TextNote.MODE_CHECK_LIST : 0); + break; + case R.id.menu_share: + // 获取当前编辑的笔记内容并分享 + getWorkingText(); + sendTo(this, mWorkingNote.getContent()); + break; + case R.id.menu_send_to_desktop: + // 将笔记发送到桌面 + sendToDesktop(); + break; + case R.id.menu_alert: + // 设置提醒 + setReminder(); + break; + case R.id.menu_delete_remind: + // 删除提醒设置 + mWorkingNote.setAlertDate(0, false); + break; + default: break; } + return true; } - //获取地址信息:城市、街道等信息 - private String getAddress(Location location) { - List
result = null; - String city = null; - try { - if (location != null) { - Geocoder gc = new Geocoder(this, Locale.getDefault()); - result = gc.getFromLocation(location.getLatitude(), - location.getLongitude(), 1); - city = result.get(0).getAddressLine(0).toString(); - Toast.makeText(this, "获取地址信息:" + city, Toast.LENGTH_LONG).show(); - Log.v("TAG", "获取地址信息:" + city); + /** + * 弹出日期时间选择器,用于设置提醒时间。 + */ + private void setReminder() { + DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + public void OnDateTimeSet(AlertDialog dialog, long date) { + // 用户设定时间后,设置提醒 + mWorkingNote.setAlertDate(date, true); } - } catch (Exception e) { - e.printStackTrace(); - } - Log.e(TAG, city); - return city; + }); + d.show(); } + /** + * 分享笔记到支持 {@link Intent#ACTION_SEND} 操作和 {@text/plain} 类型的应用。 + * + * @param context 上下文 + * @param info 要分享的信息 + */ + private void sendTo(Context context, String info) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, info); + intent.setType("text/plain"); + context.startActivity(intent); + } - private void local_sel() { - final Button get_local = findViewById(R.id.location); - get_local.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - Button local1 = new Button(NoteEditActivity.this); - final Button local2 = new Button(NoteEditActivity.this); - local1.setText("获取地理信息"); - local2.setText("清除地理信息"); - LinearLayout linear = new LinearLayout(NoteEditActivity.this); - linear.setOrientation(LinearLayout.VERTICAL); - linear.addView(local1); - linear.addView(local2); - AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this); - builder.setView(linear); - builder.setTitle("请选择功能"); - AlertDialog choose_local = builder.create(); - choose_local.show(); - local1.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Toast.makeText(NoteEditActivity.this, "获取地理信息", Toast.LENGTH_SHORT).show(); - String city = null; - city = getLocation(); - - if (city == null) { - get_local.setText("点击获得地理信息"); - } else { - get_local.setText(city); - } - } - }); - local2.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Toast.makeText(NoteEditActivity.this, "清除地理信息", Toast.LENGTH_SHORT).show(); - get_local.setText("点击获得地理信息"); - } - }); + /** + * 首先保存当前正在编辑的笔记,然后启动一个新的NoteEditActivity用于创建新笔记。 + */ + private void createNewNote() { + // 首先保存当前笔记 + saveNote(); - } + // 安全地开始一个新的NoteEditActivity + finish(); + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent); + } -// builder.setTitle("choose"); -// AlertDialog choose_local = builder.create(); -// choose_local.show(); - }); + /** + * 删除当前正在编辑的笔记。 + * 如果笔记存在于数据库中,并且当前不是同步模式,将直接删除该笔记; + * 如果处于同步模式,则将笔记移动到回收站文件夹。 + */ + private void deleteCurrentNote() { + if (mWorkingNote.existInDatabase()) { + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id); + } else { + Log.d(TAG, "Wrong note id, should not happen"); + } + if (!isSyncMode()) { + // 非同步模式下直接删除笔记 + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "Delete Note error"); + } + } else { + // 同步模式下将笔记移动到回收站 + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + } + mWorkingNote.markDeleted(true); } - public void speech_sum() { - EditText editable = findViewById(R.id.note_edit_view); - //Toast.makeText(NoteEditActivity.this, editable.getText().toString(), Toast.LENGTH_SHORT).show(); - final Button speak = findViewById(R.id.iatBtn); - final int[] tmp = new int[1]; - final TextToSpeech[] tts = new TextToSpeech[1]; - tts[0] = new TextToSpeech(NoteEditActivity.this, new TextToSpeech.OnInitListener() { - @Override - public void onInit(int status) { - tts[0].setLanguage(Locale.US); - } - }); - editable.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - tmp[0] = cutOfimage(operateText(s.toString())).length(); - TextView number_word = findViewById(R.id.num_word); - number_word.setText("字符数:" + tmp[0]); - final String temp = cutOfimage(s.toString()); - speak.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - tts[0].speak(temp.toString(), TextToSpeech.QUEUE_FLUSH, null); - } - }); - } + /** + * 判断当前是否为同步模式。 + * 同步模式是指在设置中配置了同步账户名。 + * + * @return 如果配置了同步账户名返回true,否则返回false。 + */ + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } - @Override - public void afterTextChanged(Editable s) { + /** + * 处理时钟提醒变更事件。 + * 首先检查当前笔记是否已保存,未保存则先保存。 + * 如果笔记存在,根据set参数设置或取消提醒。 + * 如果笔记不存在(即无有效ID),记录错误并提示用户输入内容。 + * + * @param date 提醒的日期时间戳 + * @param set 是否设置提醒 + */ + public void onClockAlertChanged(long date, boolean set) { + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + if (mWorkingNote.getNoteId() > 0) { + Intent intent = new Intent(this, AlarmReceiver.class); + intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + showAlertHeader(); + if (!set) { + alarmManager.cancel(pendingIntent); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } - }); + } else { + Log.e(TAG, "Clock alert setting error"); + showToast(R.string.error_note_empty_for_clock); + } } - public String operateText(String str) { - String dest = ""; - Pattern p = Pattern.compile("\\s*|t|r|n"); - Matcher m = p.matcher(str); - dest = m.replaceAll(""); - return dest; + /** + * 更新小部件显示。 + */ + public void onWidgetChanged() { + updateWidget(); } - public String cutOfimage(String str) { - String dest = str; - int index1 = dest.indexOf("[local]"); - int index2 = dest.indexOf("[/local]"); - while (index1 != -1 && index2 != -1) { - dest = dest.substring(0, index1) + dest.substring(index2 + 8); - index1 = dest.indexOf("[local]"); - index2 = dest.indexOf("[/local]"); + /** + * 当删除某个编辑框中的文本时的处理逻辑。 + * 重新设置后续编辑框的索引,并将删除的文本添加到前一个或当前编辑框中。 + * + * @param index 被删除文本的编辑框索引 + * @param text 被删除的文本内容 + */ + public void onEditTextDelete(int index, String text) { + int childCount = mEditTextList.getChildCount(); + if (childCount == 1) { + return; } - return dest; - } + for (int i = index + 1; i < childCount; i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i - 1); + } - private void convertToImage() { - NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view); - Editable editable = noteEditText.getText(); - String noteText = editable.toString(); - int length = editable.length(); - for (int i = 0; i < length; i++) { - for (int j = i; j < length; j++) { - String img_fragment = noteText.substring(i, j + 1); - if (img_fragment.length() > 15 && img_fragment.endsWith("[/local]") && img_fragment.startsWith("[local]")) { - int limit = 7; - int len = img_fragment.length() - 15; - String path = img_fragment.substring(limit, limit + len); - Bitmap bitmap = null; - Log.d(TAG, "图片的路径是:" + path); - try { - bitmap = BitmapFactory.decodeFile(path); - } catch (Exception e) { - e.printStackTrace(); - } - if (bitmap != null) { - Log.d(TAG, "图片不为null"); - ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); - String ss = "[local]" + path + "[/local]"; - SpannableString spannableString = new SpannableString(ss); - spannableString.setSpan(imageSpan, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - Log.d(TAG, "Create spannable string success!"); - Editable edit_text = noteEditText.getEditableText(); - edit_text.delete(i, i + len + 15); - edit_text.insert(i, spannableString); - } - } - } + mEditTextList.removeViewAt(index); + NoteEditText edit = null; + if (index == 0) { + edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( + R.id.et_edit_text); + } else { + edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( + R.id.et_edit_text); } + int length = edit.length(); + edit.append(text); + edit.requestFocus(); + edit.setSelection(length); } + /** + * 当在编辑框中按下“Enter”键时的处理逻辑。 + * 在列表中添加一个新的编辑框,并重新设置后续编辑框的索引。 + * + * @param index 当前编辑框的索引 + * @param text 当前编辑框中的文本内容 + */ + public void onEditTextEnter(int index, String text) { + if (index > mEditTextList.getChildCount()) { + Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + } - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - ContentResolver resolver = getContentResolver(); - switch (requestCode) { - case PHOTO_REQUEST: - Uri originalUri = intent.getData(); - Bitmap bitmap = null; - try { - bitmap = BitmapFactory.decodeStream(resolver.openInputStream(originalUri));//2.解码图片 - } catch (FileNotFoundException e) { - Log.d(TAG, "onActivityResult: get file_exception"); - e.printStackTrace(); - } - - if (bitmap != null) { - Log.d(TAG, "onActivityResult: bitmap is not null"); - ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); - String path = getPath(this, originalUri); - String img_fragment = "[local]" + path + "[/local]"; - SpannableString spannableString = new SpannableString(img_fragment); - spannableString.setSpan(imageSpan, 0, img_fragment.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - NoteEditText e = (NoteEditText) findViewById(R.id.note_edit_view); - int index = e.getSelectionStart(); - Log.d(TAG, "Index是: " + index); - Editable edit_text = e.getEditableText(); - edit_text.insert(index, spannableString); - mWorkingNote.setmContent(e.getText().toString()); - ContentResolver contentResolver = getContentResolver(); - ContentValues contentValues = new ContentValues(); - final long id = mWorkingNote.getNoteId(); - contentValues.put("snippet", mWorkingNote.getContent()); - contentResolver.update(Uri.parse("content://micode_notes/note"), contentValues, "_id=?", new String[]{"" + id}); - ContentValues contentValues1 = new ContentValues(); - contentValues1.put("content", mWorkingNote.getContent()); - contentResolver.update(Uri.parse("content://micode_notes/data"), contentValues1, "mime_type=? and note_id=?", new String[]{"vnd.android.cursor.item/text_note", "" + id}); - - } else { - Toast.makeText(NoteEditActivity.this, "获取图片失败", Toast.LENGTH_SHORT).show(); - } - break; - default: - break; + View view = getListItem(text, index); + mEditTextList.addView(view, index); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.requestFocus(); + edit.setSelection(0); + for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i); } } - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - public String getPath(final Context context, final Uri uri) { - - final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { - if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[]{split[1]}; - - return getDataColumn(context, contentUri, selection, selectionArgs); + /** + * 切换到列表模式。 + * 将文本分割成多行,并为每行创建一个列表项,展示在编辑文本列表中。 + * + * @param text 要转换成列表模式的文本,每行代表一个列表项。 + */ + private void switchToListMode(String text) { + // 清空当前的视图 + mEditTextList.removeAllViews(); + // 使用换行符分割文本,创建列表项 + String[] items = text.split("\n"); + int index = 0; + for (String item : items) { + // 忽略空行 + if (!TextUtils.isEmpty(item)) { + mEditTextList.addView(getListItem(item, index)); + index++; } } - // Media - else if ("content".equalsIgnoreCase(uri.getScheme())) { - return getDataColumn(context, uri, null, null); - } - // File - else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - return null; - } - - public String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + // 添加一个空的列表项作为占位符 + mEditTextList.addView(getListItem("", index)); + // 请求焦点以便于编辑 + mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); - Cursor cursor = null; - final String column = "_data"; - final String[] projection = {column}; + // 隐藏编辑器,显示列表 + mNoteEditor.setVisibility(View.GONE); + mEditTextList.setVisibility(View.VISIBLE); + } - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); + /** + * 高亮显示查询结果。 + * 在给定的文本中,根据用户查询字符串高亮显示匹配的部分。 + * + * @param fullText 完整的文本。 + * @param userQuery 用户的查询字符串。 + * @return 包含高亮显示的文本的Spannable对象。 + */ + private Spannable getHighlightQueryResult(String fullText, String userQuery) { + SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + // 如果有查询字符串,则进行高亮处理 + if (!TextUtils.isEmpty(userQuery)) { + mPattern = Pattern.compile(userQuery); + Matcher m = mPattern.matcher(fullText); + int start = 0; + while (m.find(start)) { + // 设置高亮背景 + spannable.setSpan( + new BackgroundColorSpan(this.getResources().getColor( + R.color.user_query_highlight)), m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end(); } - } finally { - if (cursor != null) - cursor.close(); } - return null; - } - - public boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); + return spannable; } - - public void showSingleAlertDiglog() { - final String[] items = {"默认-普通", "默认-非衬线", "默认-衬线", "默认-等宽", "仿宋", "黑体", "楷体", "姚体", "隶书", "行楷", "新魏", "中宋"}; - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this); - alertBuilder.setTitle("选择字体"); - alertBuilder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - switch (i) { - case 0: - mNoteEditor.setTypeface(Typeface.DEFAULT); - break; - case 1: - mNoteEditor.setTypeface(Typeface.SANS_SERIF); - break; - case 2: - mNoteEditor.setTypeface(Typeface.SERIF); - break; - case 3: - mNoteEditor.setTypeface(Typeface.MONOSPACE); - break; - case 4: - Typeface typeface0 = Typeface.createFromAsset(getAssets(), "font/simfang.ttf"); - mNoteEditor.setTypeface(typeface0); - break; - case 5: - Typeface typeface1 = Typeface.createFromAsset(getAssets(), "font/simhei.ttf"); - mNoteEditor.setTypeface(typeface1); - break; - case 6: - Typeface typeface2 = Typeface.createFromAsset(getAssets(), "font/simkai.ttf"); - mNoteEditor.setTypeface(typeface2); - break; - case 7: - Typeface typeface3 = Typeface.createFromAsset(getAssets(), "font/FZYTK.TTF"); - mNoteEditor.setTypeface(typeface3); - break; - case 8: - Typeface typeface4 = Typeface.createFromAsset(getAssets(), "font/STLITI.TTF"); - mNoteEditor.setTypeface(typeface4); - break; - case 9: - Typeface typeface5 = Typeface.createFromAsset(getAssets(), "font/STXINGKA.TTF"); - mNoteEditor.setTypeface(typeface5); - break; - case 10: - Typeface typeface6 = Typeface.createFromAsset(getAssets(), "font/STXINWEI.TTF"); - mNoteEditor.setTypeface(typeface6); - break; - case 11: - Typeface typeface7 = Typeface.createFromAsset(getAssets(), "font/STZHONGS.TTF"); - mNoteEditor.setTypeface(typeface7); - break; + /** + * 创建列表项视图。 + * 为列表模式创建并配置一个包含文本编辑框和复选框的视图。 + * + * @param item 列表项的文本内容。 + * @param index 列表项的索引。 + * @return 配置好的列表项视图。 + */ + private View getListItem(String item, int index) { + // 加载列表项布局 + View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); + final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + // 设置文本样式 + edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + // 复选框的监听器,用于切换文本的划线状态 + cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); } - Toast.makeText(NoteEditActivity.this, items[i], Toast.LENGTH_SHORT).show(); } }); - alertBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - alertDialog2.dismiss(); - - } - }); - alertBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - alertDialog2.dismiss(); - } + // 根据文本前缀设置复选框状态和文本内容 + if (item.startsWith(TAG_CHECKED)) { + cb.setChecked(true); + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + item = item.substring(TAG_CHECKED.length(), item.length()).trim(); + } else if (item.startsWith(TAG_UNCHECKED)) { + cb.setChecked(false); + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + } - }); - alertDialog2 = alertBuilder.create(); - alertDialog2.show(); + // 设置文本变化监听和索引 + edit.setOnTextViewChangeListener(this); + edit.setIndex(index); + // 设置带有查询结果高亮的文本 + edit.setText(getHighlightQueryResult(item, mUserQuery)); + return view; } - public void translate() { - final EditText editable = findViewById(R.id.note_edit_view); - final Button get_local = findViewById(R.id.translate); - get_local.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - Button trans1 = new Button(NoteEditActivity.this); - Button trans2 = new Button(NoteEditActivity.this); - Button trans3 = new Button(NoteEditActivity.this); - trans1.setText("中文翻译为英文"); - trans2.setText("英文翻译为中文"); - trans3.setText("还原"); - LinearLayout linear = new LinearLayout(NoteEditActivity.this); - linear.setOrientation(LinearLayout.VERTICAL); - linear.addView(trans1); - linear.addView(trans2); - linear.addView(trans3); - AlertDialog.Builder builder = new AlertDialog.Builder(NoteEditActivity.this); - builder.setView(linear); - builder.setTitle("请选择翻译模式"); - AlertDialog choose_trans = builder.create(); - choose_trans.show(); - trans1.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - restore_translate = editable.getText(); - translate_z2u(); - Toast.makeText(NoteEditActivity.this, "中文翻译为英文", Toast.LENGTH_SHORT).show(); - } - }); - trans2.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - restore_translate = editable.getText(); - translate_u2z(); - Toast.makeText(NoteEditActivity.this, "英文翻译为中文", Toast.LENGTH_SHORT).show(); - } - }); - trans3.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (restore_translate == null || restore_translate.toString().equals(editable.getText().toString())) { - Toast.makeText(NoteEditActivity.this, "无可还原内容", Toast.LENGTH_SHORT).show(); - } else { - editable.setText(restore_translate); - Toast.makeText(NoteEditActivity.this, "已还原", Toast.LENGTH_SHORT).show(); - } - } - }); - } - }); + /** + * 根据文本内容是否为空,切换复选框的可见性。 + * + * @param index 列表项索引。 + * @param hasText 列表项是否包含文本。 + */ + public void onTextChange(int index, boolean hasText) { + if (index >= mEditTextList.getChildCount()) { + Log.e(TAG, "Wrong index, should not happen"); + return; + } + // 根据文本内容决定复选框的可见性 + if (hasText) { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); + } else { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); + } } - - public void translate_z2u() { - final EditText editable = findViewById(R.id.note_edit_view); - String word = editable.getText().toString(); - word = word.replaceAll("\\n", "//"); - String from = "auto"; - String to = "en"; - String appid = "20221021001406789"; - String salt = (int) (Math.random() * 100 + 1) + ""; - String key = "eWky8SSKgL99Dh4rHMog"; - String secretKey = appid + word + salt + key; - String sign = MD5Utils.getMD5Code(secretKey); - Log.d(TAG, "secretKey:" + secretKey); - Log.d(TAG, "sign: " + sign); - Retrofit retrofitBaidu = new Retrofit.Builder() - .baseUrl("https://fanyi-api.baidu.com/api/trans/vip/") - .addConverterFactory(GsonConverterFactory.create()) - .build(); - BaiduTranslateService baiduTranslateService = retrofitBaidu.create(BaiduTranslateService.class); - retrofit2.Call call = baiduTranslateService.translate(word, from, to, appid, salt, sign); - call.enqueue(new Callback() { - @Override - public void onResponse(retrofit2.Call call, Response response) { - Log.d(TAG, "onResponse: 请求成功"); - RespondBean respondBean = response.body(); - String result = respondBean.getTrans_result().get(0).getDst(); - editable.setText(result); - Log.d(TAG, "中译英结果" + result); - } - - @Override - public void onFailure(retrofit2.Call call, Throwable t) { - Log.d(TAG, "onResponse: 请求失败 " + t); + /** + * 在切换编辑模式和列表模式时更新UI。 + * + * @param oldMode 旧的编辑模式。 + * @param newMode 新的编辑模式。 + */ + public void onCheckListModeChanged(int oldMode, int newMode) { + // 切换到列表模式 + if (newMode == TextNote.MODE_CHECK_LIST) { + switchToListMode(mNoteEditor.getText().toString()); + } else { + // 切换回编辑模式 + if (!getWorkingText()) { + mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", + "")); } - }); + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mEditTextList.setVisibility(View.GONE); + mNoteEditor.setVisibility(View.VISIBLE); + } + convertToImage();//qxq:在切换模式时也调用 } - public void translate_u2z() { - final EditText editable = findViewById(R.id.note_edit_view); - String word = editable.getText().toString(); - word = word.replaceAll("\\n", "//"); - Log.d(TAG, word); - String from = "auto"; - String to = "zh"; - String appid = "20221021001406789"; - String salt = (int) (Math.random() * 100 + 1) + ""; - String key = "eWky8SSKgL99Dh4rHMog"; - String secretKey = appid + word + salt + key; - String sign = MD5Utils.getMD5Code(secretKey); - Log.d(TAG, "secretKey:" + secretKey); - Log.d(TAG, "sign: " + sign); - Retrofit retrofitBaidu = new Retrofit.Builder() - .baseUrl("https://fanyi-api.baidu.com/api/trans/vip/") - .addConverterFactory(GsonConverterFactory.create()) - .build(); - BaiduTranslateService baiduTranslateService = retrofitBaidu.create(BaiduTranslateService.class); - retrofit2.Call call = baiduTranslateService.translate(word, from, to, appid, salt, sign); - call.enqueue(new Callback() { - @Override - public void onResponse(retrofit2.Call call, Response response) { - Log.d(TAG, "onResponse: 请求成功"); - RespondBean respondBean = response.body(); - String result = respondBean.getTrans_result().get(0).getDst(); - editable.setText(result); - Log.d(TAG, "中译英结果" + result); + /** + * 根据当前列表项的选中状态,构建并返回工作文本。 + * + * @return 是否有已选中的列表项。 + */ + private boolean getWorkingText() {//qxq:得到编辑的文本框内容 + boolean hasChecked = false; + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + // 构建带有选中状态前缀的文本 + if (!TextUtils.isEmpty(edit.getText())) { + if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + hasChecked = true; + } else { + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); + } + } } + mWorkingNote.setWorkingText(sb.toString()); + } else { + mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + } + return hasChecked; + } - @Override - public void onFailure(retrofit2.Call call, Throwable t) { - Log.d(TAG, "onResponse: 请求失败 " + t); - } - }); + /** + * 保存笔记。 + * 更新笔记内容并保存。 + * + * @return 是否成功保存笔记。 + */ + private boolean saveNote() { + getWorkingText(); + boolean saved = mWorkingNote.saveNote(); + if (saved) { + // 设置结果为成功,以便外部调用者知道保存操作的状态 + setResult(RESULT_OK); + } + return saved; } - private static final int MAX_TIME_OF_RVOKE_TIME = 100; - private int MAX_OF_RVOKE_TIME = 100; - private Vector mChanged = new Vector(MAX_OF_RVOKE_TIME); - private void saveMyChanged() { - SpannableString text = new SpannableString(mNoteEditor.getText());//用getText方法获取每次编辑的内容 - if (mChanged.size() >= MAX_TIME_OF_RVOKE_TIME) {//如果栈中的数据大于最大撤销次数,就把第一次修改的内容删除 - mChanged.removeElementAt(0); + /** + * 将当前编辑的笔记发送到桌面。首先会检查当前编辑的笔记是否已存在于数据库中, + * 如果不存在,则先保存。如果存在,会创建一个快捷方式放在桌面。 + */ + private void sendToDesktop() { + // 检查当前编辑的笔记是否存在于数据库,若不存在则先保存 + if (!mWorkingNote.existInDatabase()) { + saveNote(); } - mChanged.add(text);//然后把本次修改的内容加入栈中 - } - private void doRevoke() { - int size = mChanged.size();//获取当前栈大小 - AlertDialog.Builder dialog = new AlertDialog.Builder(this);//创建一个alertdialog窗口 - dialog.setTitle(R.string.tips_of_revoke);//设置title信息 - dialog.setCancelable(true);//设置为可取消 - dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {//只需要设置一个OK键即可 - @Override - public void onClick(DialogInterface dialog, int which) { - } - }); - mIsRvoke = true;//把是否已执行撤销的标记设置为true - if (size <= 1) {//如果栈中元素过少,打印提示信息 - dialog.setMessage(R.string.have_not_input_anything);//提示用户您还没有输入任何信息 - dialog.show();//显示当前alertdialog - return; + // 如果笔记存在于数据库(有noteId),则创建快捷方式 + if (mWorkingNote.getNoteId() > 0) { + Intent sender = new Intent(); + Intent shortcutIntent = new Intent(this, NoteEditActivity.class); + shortcutIntent.setAction(Intent.ACTION_VIEW); + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent())); + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); + sender.putExtra("duplicate", true); + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + showToast(R.string.info_note_enter_desktop); + sendBroadcast(sender); } else { - mNoteEditor.setText((CharSequence) mChanged.elementAt(size - 2));//在textview中设置撤销的内容 - mNoteEditor.setSelection(mNoteEditor.length()); - mChanged.removeElementAt(size - 1);//删除元素 - if (size == 2) { - dialog.setMessage(R.string.can_not_revoke);//如果只有一次操作,那么提示用户不能再撤销了 - dialog.show();//显示当前alertdialog - } + // 如果用户未输入任何内容,无法创建快捷方式,提醒用户输入内容 + Log.e(TAG, "Send to desktop error"); + showToast(R.string.error_note_empty_for_send_to_desktop); } } -} \ No newline at end of file + + /** + * 根据笔记内容生成快捷方式的标题。移除内容中的已选和未选标签,并确保标题长度不超过上限。 + * + * @param content 符合快捷方式图标标题要求的笔记内容 + * @return 标题字符串 + */ + private String makeShortcutIconTitle(String content) { + content = content.replace(TAG_CHECKED, ""); + content = content.replace(TAG_UNCHECKED, ""); + return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, + SHORTCUT_ICON_TITLE_MAX_LEN) : content; + } + + /** + * 显示一个Toast消息。 + * + * @param resId 资源ID,指向要显示的字符串 + */ + private void showToast(int resId) { + showToast(resId, Toast.LENGTH_SHORT); + } + + /** + * 显示一个指定时长的Toast消息。 + * + * @param resId 资源ID,指向要显示的字符串 + * @param duration 显示时长,可以是Toast.LENGTH_SHORT或Toast.LENGTH_LONG + */ + private void showToast(int resId, int duration) { + Toast.makeText(this, resId, duration).show(); + } + +} + + +//qxq: diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NoteEditText.java b/xiaomi-src/main/java/net/micode/notes/ui/NoteEditText.java index 24278b5..8e3e59f 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NoteEditText.java @@ -37,16 +37,21 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; -public class NoteEditText extends android.support.v7.widget.AppCompatEditText { +/** + * 自定义的可编辑文本视图,支持文本变化监听、删除和新增文本事件。 + */ +public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; - private int mIndex; - private int mSelectionStartBeforeDelete; + private int mIndex; // 当前文本视图的索引 + private int mSelectionStartBeforeDelete; // 删除操作前的选择起始位置 - private static final String SCHEME_TEL = "tel:" ; - private static final String SCHEME_HTTP = "http:" ; - private static final String SCHEME_EMAIL = "mailto:" ; + private static final String SCHEME_TEL = "tel:"; + private static final String SCHEME_HTTP = "http:"; + private static final String SCHEME_EMAIL = "mailto:"; + // URL方案与对应操作资源ID的映射 private static final Map sSchemaActionResMap = new HashMap(); + static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); @@ -54,56 +59,84 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 文本视图变化监听接口。 */ public interface OnTextViewChangeListener { /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null + * 当按下删除键且文本为空时,删除当前文本视图。 */ void onEditTextDelete(int index, String text); /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen + * 当按下回车键时,新增一个文本视图。 */ void onEditTextEnter(int index, String text); /** - * Hide or show item option when text change + * 当文本变化时,隐藏或显示项目选项。 */ void onTextChange(int index, boolean hasText); } private OnTextViewChangeListener mOnTextViewChangeListener; + /** + * 构造函数,初始化编辑文本视图。 + * + * @param context 上下文对象 + */ public NoteEditText(Context context) { super(context, null); mIndex = 0; } + /** + * 设置当前文本视图的索引。 + * + * @param index 当前文本视图的索引 + */ public void setIndex(int index) { mIndex = index; } + /** + * 设置文本视图变化监听器。 + * + * @param listener 文本视图变化监听器 + */ public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + /** + * 构造函数,初始化编辑文本视图。 + * + * @param context 上下文对象 + * @param attrs 属性集 + */ public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + /** + * 构造函数,初始化编辑文本视图。 + * + * @param context 上下文对象 + * @param attrs 属性集 + * @param defStyle 样式 + */ public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO Auto-generated constructor stub } + /** + * 处理触摸事件,调整光标位置。 + */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - + // 计算触摸位置相对于文本的偏移量,并调整光标位置 int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); @@ -121,15 +154,20 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { return super.onTouchEvent(event); } + /** + * 处理键盘按下事件。 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_ENTER: + // 处理回车键事件,准备新增文本视图 if (mOnTextViewChangeListener != null) { return false; } break; case KeyEvent.KEYCODE_DEL: + // 记录删除操作前的选择位置 mSelectionStartBeforeDelete = getSelectionStart(); break; default: @@ -138,10 +176,14 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { return super.onKeyDown(keyCode, event); } + /** + * 处理键盘弹起事件。 + */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - switch(keyCode) { + switch (keyCode) { case KeyEvent.KEYCODE_DEL: + // 处理删除键事件,若为首个文本且非空,则删除当前文本 if (mOnTextViewChangeListener != null) { if (0 == mSelectionStartBeforeDelete && mIndex != 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); @@ -152,6 +194,7 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { } break; case KeyEvent.KEYCODE_ENTER: + // 处理回车键事件,新增文本视图 if (mOnTextViewChangeListener != null) { int selectionStart = getSelectionStart(); String text = getText().subSequence(selectionStart, length()).toString(); @@ -167,6 +210,9 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { return super.onKeyUp(keyCode, event); } + /** + * 当焦点变化时,通知文本变化情况。 + */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { @@ -179,6 +225,9 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单,支持点击URL跳转。 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { @@ -191,8 +240,8 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - if(urls[0].getURL().indexOf(schema) >= 0) { + for (String schema : sSchemaActionResMap.keySet()) { + if (urls[0].getURL().indexOf(schema) >= 0) { defaultResId = sSchemaActionResMap.get(schema); break; } @@ -205,7 +254,7 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // goto a new intent + // 跳转到URL指向的页面 urls[0].onClick(NoteEditText.this); return true; } @@ -215,3 +264,4 @@ public class NoteEditText extends android.support.v7.widget.AppCompatEditText { super.onCreateContextMenu(menu); } } + diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NoteItemData.java b/xiaomi-src/main/java/net/micode/notes/ui/NoteItemData.java index 1966e95..e0c70c5 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NoteItemData.java @@ -26,37 +26,41 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; +/** + * 代表一个笔记项的数据类,用于存储和管理笔记的各种信息。 + */ public class NoteItemData { - static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - NoteColumns.TOP, + // 定义查询时要投影的列 + static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, }; - private static final int ID_COLUMN = 0; - private static final int ALERTED_DATE_COLUMN = 1; - private static final int BG_COLOR_ID_COLUMN = 2; - private static final int CREATED_DATE_COLUMN = 3; - private static final int HAS_ATTACHMENT_COLUMN = 4; - private static final int MODIFIED_DATE_COLUMN = 5; - private static final int NOTES_COUNT_COLUMN = 6; - private static final int PARENT_ID_COLUMN = 7; - private static final int SNIPPET_COLUMN = 8; - private static final int TYPE_COLUMN = 9; - private static final int WIDGET_ID_COLUMN = 10; - private static final int WIDGET_TYPE_COLUMN = 11; - private static final int TOP_ID_COLUMN = 12; - + // 各列数据的索引 + private static final int ID_COLUMN = 0; + private static final int ALERTED_DATE_COLUMN = 1; + private static final int BG_COLOR_ID_COLUMN = 2; + private static final int CREATED_DATE_COLUMN = 3; + private static final int HAS_ATTACHMENT_COLUMN = 4; + private static final int MODIFIED_DATE_COLUMN = 5; + private static final int NOTES_COUNT_COLUMN = 6; + private static final int PARENT_ID_COLUMN = 7; + private static final int SNIPPET_COLUMN = 8; + private static final int TYPE_COLUMN = 9; + private static final int WIDGET_ID_COLUMN = 10; + private static final int WIDGET_TYPE_COLUMN = 11; + + // 笔记的各项数据 private long mId; private long mAlertDate; private int mBgColorId; @@ -71,15 +75,22 @@ public class NoteItemData { private int mWidgetType; private String mName; private String mPhoneNumber; - private String mTop; + // 用于标识笔记在列表中的位置状态 private boolean mIsLastItem; private boolean mIsFirstItem; private boolean mIsOnlyOneItem; private boolean mIsOneNoteFollowingFolder; private boolean mIsMultiNotesFollowingFolder; + /** + * 根据Cursor数据构造一个NoteItemData对象。 + * + * @param context 上下文对象,用于访问应用全局功能。 + * @param cursor 包含笔记数据的Cursor对象。 + */ public NoteItemData(Context context, Cursor cursor) { + // 从Cursor中提取各项数据并赋值 mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); @@ -94,8 +105,8 @@ public class NoteItemData { mType = cursor.getInt(TYPE_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - mTop = cursor.getString(TOP_ID_COLUMN); + // 如果是通话记录笔记,尝试获取通话号码和联系人名称 mPhoneNumber = ""; if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); @@ -107,19 +118,27 @@ public class NoteItemData { } } + // 如果没有获取到联系人名称,则默认为空字符串 if (mName == null) { mName = ""; } checkPostion(cursor); } + /** + * 根据当前Cursor位置,更新NoteItemData的状态信息(如是否为列表中的最后一个项目等)。 + * + * @param cursor 包含笔记数据的Cursor对象。 + */ private void checkPostion(Cursor cursor) { - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; + // 更新位置状态信息 + mIsLastItem = cursor.isLast(); + mIsFirstItem = cursor.isFirst(); mIsOnlyOneItem = (cursor.getCount() == 1); mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; + // 检查当前笔记是否跟随文件夹,并更新相应状态 if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { int position = cursor.getPosition(); if (cursor.moveToPrevious()) { @@ -131,6 +150,7 @@ public class NoteItemData { mIsOneNoteFollowingFolder = true; } } + // 确保Cursor能够回到原来的位置 if (!cursor.moveToNext()) { throw new IllegalStateException("cursor move to previous but can't move back"); } @@ -138,13 +158,7 @@ public class NoteItemData { } } - public boolean isTOP() { - if(mTop.equals("1")) { - return true; - }else { - return false; - } - } + // 以下为获取NoteItemData各项属性的方法 public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; @@ -202,7 +216,7 @@ public class NoteItemData { return mNotesCount; } - public long getFolderId () { + public long getFolderId() { return mParentId; } @@ -230,7 +244,14 @@ public class NoteItemData { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } + /** + * 从Cursor中获取笔记的类型。 + * + * @param cursor 包含笔记数据的Cursor对象。 + * @return 笔记的类型。 + */ public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } } + diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NotesListActivity.java b/xiaomi-src/main/java/net/micode/notes/ui/NotesListActivity.java index 61a997f..091aac1 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -16,9 +16,7 @@ package net.micode.notes.ui; -import android.annotation.SuppressLint; import android.app.Activity; -import android.support.v7.app.AppCompatActivity; import android.app.AlertDialog; import android.app.Dialog; import android.appwidget.AppWidgetManager; @@ -80,83 +78,131 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; -public class NotesListActivity extends AppCompatActivity implements OnClickListener, OnItemLongClickListener { - public static int secret_mode = 0; +public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + private int background = -1;//qxq:控制首页背景切换 + public static int secret_mode = 0;//初始化为0表示进入隐私模式 + + // 定义文件夹中笔记列表查询的标记 private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - private static final int FOLDER_LIST_QUERY_TOKEN = 1; + // 定义文件夹列表查询的标记 + private static final int FOLDER_LIST_QUERY_TOKEN = 1; + // 菜单中删除文件夹的选项 private static final int MENU_FOLDER_DELETE = 0; + // 菜单中查看文件夹的选项 private static final int MENU_FOLDER_VIEW = 1; + // 菜单中更改文件夹名称的选项 private static final int MENU_FOLDER_CHANGE_NAME = 2; + // 首次使用应用时,添加介绍信息的偏好设置键 private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + // 列表编辑状态的枚举,包括笔记列表、子文件夹和通话记录文件夹 private enum ListEditState { NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER - }; + } + ; + + // 当前编辑状态 private ListEditState mState; + // 后台查询处理器 private BackgroundQueryHandler mBackgroundQueryHandler; + // 笔记列表的适配器 private NotesListAdapter mNotesListAdapter; + // 笔记列表视图 private ListView mNotesListView; + // 添加新笔记的按钮 private Button mAddNewNote; + // 是否分发事件的标志 private boolean mDispatch; + // 触摸点的原始Y坐标 private int mOriginY; + // 分发事件时的Y坐标 private int mDispatchY; + // 标题栏文本视图 private TextView mTitleBar; + // 当前文件夹的ID private long mCurrentFolderId; + // 内容解析器 private ContentResolver mContentResolver; + // 模式回调接口 private ModeCallback mModeCallBack; + // 日志标签 private static final String TAG = "NotesListActivity"; + // 笔记列表视图滚动速率 public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; + // 聚焦的笔记数据项 private NoteItemData mFocusNoteDataItem; + // 普通文件夹选择条件 private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; + // 根文件夹选择条件 private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; + // 打开节点请求代码 private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; - - static Activity NoteListActivity; - + // 新建节点请求代码 + private final static int REQUEST_CODE_NEW_NODE = 103; + + /** + * 在活动创建时调用,用于初始化资源和设置应用信息。 + * + * + * @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.note_list); - NoteListActivity = this; + /* + SharedPreferences pref = getSharedPreferences("user management", MODE_PRIVATE); + boolean User_boolean = pref.getBoolean("user", false); + if (User_boolean) { + Intent intent = new Intent(NotesListActivity.this, LoginActivity.class); + startActivity(intent); + finish(); + return; + }*/ - initResources(); + setContentView(R.layout.note_list); + initResources();//调用初始化函数进行初始化 包括数据和视图的关联 - /** - * Insert an introduction when user firstly use this application - */ + // 用户首次使用时插入介绍信息 setAppInfoFromRawRes(); } + /** + * 处理从其他活动返回的结果。 + * + * @param requestCode 启动其他活动时传入的请求代码。 + * @param resultCode 其他活动返回的结果代码。 + * @param data 其他活动返回的数据。 + */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // qxq:如果返回结果为OK且请求代码为打开节点或新建节点,则刷新列表 if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { mNotesListAdapter.changeCursor(null); @@ -165,22 +211,33 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + + /** + * 从原始资源中设置应用信息。此方法会读取R.raw.introduction中的内容, + * 并且只有当之前未添加介绍信息时,才将读取到的内容保存为一个工作笔记。 + qxq:此处弃用需要重写 + */ private void setAppInfoFromRawRes() { + // 获取SharedPreferences实例,它是一个轻量级的存储类,特别适合用于保存软件配置参数。 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + // 检查是否已经添加了介绍信息 if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { StringBuilder sb = new StringBuilder(); InputStream in = null; try { - in = getResources().openRawResource(R.raw.introduction); + // 从资源中打开introduction文件 + in = getResources().openRawResource(R.raw.introduction); if (in != null) { + // 读取文件内容到StringBuilder InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); - char [] buf = new char[1024]; + char[] buf = new char[1024]; int len = 0; while ((len = br.read(buf)) > 0) { sb.append(buf, 0, len); } } else { + // 打印错误日志,如果无法打开文件 Log.e(TAG, "Read introduction file error"); return; } @@ -188,66 +245,95 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe e.printStackTrace(); return; } finally { - if(in != null) { + // 确保InputStream被关闭 + if (in != null) { try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } } + // 创建一个新的工作笔记并设置其内容 WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.RED); note.setWorkingText(sb.toString()); + // 保存工作笔记并标记已添加介绍信息 if (note.saveNote()) { sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); } else { + // 打印错误日志,如果保存工作笔记失败 Log.e(TAG, "Save introduction note error"); return; } } } + /** + * Activity启动时调用,开始异步查询笔记列表。 + */ @Override protected void onStart() { super.onStart(); startAsyncNotesListQuery(); } + /** + * 初始化资源,包括ListView、适配器和其他UI组件。 + */ private void initResources() { - mContentResolver = this.getContentResolver(); + mContentResolver = this.getContentResolver(); // 获取应用程序的数据,得到类似数据表的东西 + // 创建后台查询处理器 mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mNotesListView = (ListView) findViewById(R.id.notes_list); + // 初始化ListView和相关监听器 + mNotesListView = (ListView) findViewById(R.id.notes_list); // findViewById 是安卓编程的定位函数,主要是引用.R文件里的引用名 mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); mNotesListView.setOnItemClickListener(new OnListItemClickListener()); mNotesListView.setOnItemLongClickListener(this); + // 初始化并设置笔记列表适配器 mNotesListAdapter = new NotesListAdapter(this); mNotesListView.setAdapter(mNotesListAdapter); - mAddNewNote = (Button) findViewById(R.id.btn_new_note); + // 初始化新建笔记按钮并设置点击监听器 + mAddNewNote = (Button) findViewById(R.id.btn_new_note);//qxq:在activity中要获取此按钮 mAddNewNote.setOnClickListener(this); mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + // 初始化状态变量和触摸相关的变量 mDispatch = false; mDispatchY = 0; mOriginY = 0; + // 初始化标题栏和其他状态变量 mTitleBar = (TextView) findViewById(R.id.tv_title_bar); mState = ListEditState.NOTE_LIST; mModeCallBack = new ModeCallback(); } + + /** + * 用于处理列表的多选择模式和菜单点击事件的回调类。 + */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { - private DropdownMenu mDropDownMenu; - private ActionMode mActionMode; - private MenuItem mMoveMenu; + private DropdownMenu mDropDownMenu; // 下拉菜单 + private ActionMode mActionMode; // 动作模式 + private MenuItem mMoveMenu; // 移动菜单项 + /** + * 创建动作模式时的回调方法。 + * + * @param mode 动作模式实例。 + * @param menu 菜单实例。 + * @return 如果成功创建动作模式,返回true;否则返回false。 + */ public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 加载菜单项 getMenuInflater().inflate(R.menu.note_list_options, menu); + // 设置删除项的点击监听器 menu.findItem(R.id.delete).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); + // 根据条件决定是否显示移动菜单项 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER || DataUtils.getUserFolderCount(mContentResolver) == 0) { mMoveMenu.setVisible(false); @@ -255,18 +341,21 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mMoveMenu.setVisible(true); mMoveMenu.setOnMenuItemClickListener(this); } + // 初始化动作模式和列表选择模式 mActionMode = mode; mNotesListAdapter.setChoiceMode(true); mNotesListView.setLongClickable(false); mAddNewNote.setVisibility(View.GONE); + // 设置自定义视图并初始化下拉菜单 View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); mode.setCustomView(customView); mDropDownMenu = new DropdownMenu(NotesListActivity.this, (Button) customView.findViewById(R.id.selection_menu), R.menu.note_list_dropdown); - mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ + // 设置下拉菜单项点击监听器 + mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); updateMenu(); @@ -277,11 +366,15 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return true; } + /** + * 更新动作模式下的菜单项。 + */ private void updateMenu() { int selectedCount = mNotesListAdapter.getSelectedCount(); - // Update dropdown menu + // 更新下拉菜单标题 String format = getResources().getString(R.string.menu_select_title, selectedCount); mDropDownMenu.setTitle(format); + // 更新“选择全部”菜单项的状态 MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); if (item != null) { if (mNotesListAdapter.isAllSelected()) { @@ -294,17 +387,39 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 准备动作模式时的回调方法。 + * + * @param mode 动作模式实例。 + * @param menu 菜单实例。 + * @method onPrepareActionMode + * @description 进入操作模式时对菜单进行特定的修改或更新 + * @return 方法直接返回 false,表示不需要进行任何额外的操作或更新。 + */ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // TODO Auto-generated method stub return false; } + /** + * 点击动作模式中的菜单项时的回调方法。 + * + * @param mode 动作模式实例。 + * @param item 被点击的菜单项。 + * @return 返回false,表示未进行任何操作。 + */ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { // TODO Auto-generated method stub return false; } + /** + * 销毁动作模式时的回调方法。 + * + * @param mode 动作模式实例。 + */ public void onDestroyActionMode(ActionMode mode) { + // 还原列表选择模式和设置 mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); @@ -314,37 +429,56 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mActionMode.finish(); } + /** + * 处理列表项选择状态变化的回调方法。 + * + * @param mode 动作模式实例。 + * @param position 列表中被改变选择状态的项的位置。 + * @param id 项的ID。 + * @param checked 项的新选择状态。 + */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { + // 更新列表项的选择状态并更新菜单 mNotesListAdapter.setCheckedItem(position, checked); updateMenu(); } + /** + * 处理菜单项点击事件的回调方法。 + * + * @param item 被点击的菜单项。 + * @return 如果已处理点击事件,返回true;否则返回false。 + */ public boolean onMenuItemClick(MenuItem item) { + // 若未选择任何项,则显示提示 if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), Toast.LENGTH_SHORT).show(); return true; } + // 根据菜单项ID执行相应操作 switch (item.getItemId()) { case R.id.delete: + // 显示删除确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setMessage(getString(R.string.alert_message_delete_notes, - mNotesListAdapter.getSelectedCount())); + mNotesListAdapter.getSelectedCount())); builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - batchDelete(); - } - }); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case R.id.move: + // 启动查询目标文件夹的操作 startQueryDestinationFolders(); break; default: @@ -354,32 +488,36 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + + /** + * 为“新建笔记”按钮添加触摸监听器的内部类,实现点击和拖动事件的处理。 + */ private class NewNoteOnTouchListener implements OnTouchListener { + /** + * 处理触摸事件。 + * + * @param v 触摸的视图。 + * @param event 触摸事件。 + * @return 如果事件被处理则返回true,否则返回false。 + */ public boolean onTouch(View v, MotionEvent event) { + // 根据触摸事件的动作进行不同的处理 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { + // 获取屏幕高度和“新建笔记”视图的高度 Display display = getWindowManager().getDefaultDisplay(); int screenHeight = display.getHeight(); int newNoteViewHeight = mAddNewNote.getHeight(); int start = screenHeight - newNoteViewHeight; int eventY = start + (int) event.getY(); - /** - * Minus TitleBar's height - */ + // 如果当前状态为子文件夹编辑状态,需减去标题栏的高度 if (mState == ListEditState.SUB_FOLDER) { eventY -= mTitleBar.getHeight(); start -= mTitleBar.getHeight(); } - /** - * HACKME:When click the transparent part of "New Note" button, dispatch - * the event to the list view behind this button. The transparent part of - * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) - * and the line top of the button. The coordinate based on left of the "New - * Note" button. The 94 represents maximum height of the transparent part. - * Notice that, if the background of the button changes, the formula should - * also change. This is very bad, just for the UI designer's strong requirement. - */ + // 当点击到“新建笔记”按钮透明部分时,将事件分发给背后的列表视图 + // 这里使用了一种硬编码的方式处理透明部分的点击,依赖于当前的背景公式 if (event.getY() < (event.getX() * (-0.12) + 94)) { View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 - mNotesListView.getFooterViewsCount()); @@ -395,6 +533,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe break; } case MotionEvent.ACTION_MOVE: { + // 如果正在分发触摸事件,则更新事件的位置并继续分发 if (mDispatch) { mDispatchY += (int) event.getY() - mOriginY; event.setLocation(event.getX(), mDispatchY); @@ -403,6 +542,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe break; } default: { + // 当触摸动作结束或取消时,停止分发事件 if (mDispatch) { event.setLocation(event.getX(), mDispatchY); mDispatch = false; @@ -411,15 +551,26 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe break; } } + // 如果事件未被分发,则返回false return false; } - }; + } + ; + + + /** + * 异步查询笔记列表。 + * 根据当前文件夹ID选择不同的查询条件,启动一个后台查询处理该查询。 + */ private void startAsyncNotesListQuery() { + // 根据当前文件夹ID选择查询条件 String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; - String str1 = "0123456789"; + //qxq:在同步便签列表查询中定义,私密模式第一行显示字符 + + String str1 = "123456789"; String[] PROJECTION = new String[]{ //定义一个新的PROJECTION数组,只换掉SNIPPET NoteColumns.ID, NoteColumns.ALERTED_DATE, @@ -429,38 +580,52 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe NoteColumns.MODIFIED_DATE, NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, -// NoteColumns.SNIPPET, + //NoteColumns.SNIPPET, str1, NoteColumns.TYPE, NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, - NoteColumns.TOP, }; - if (secret_mode == 0) { + if(secret_mode == 0) { mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[]{ String.valueOf(mCurrentFolderId) - }, NoteColumns.TOP + " DESC," + NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); - } else { + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + } + else {// 启动查询,排序方式为类型降序,修改日期降序 mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, - Notes.CONTENT_NOTE_URI, PROJECTION, selection, new String[]{ + Notes.CONTENT_NOTE_URI, PROJECTION, selection, new String[]{// String.valueOf(mCurrentFolderId) - }, NoteColumns.TOP + " DESC," + NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } } + /** + * 处理后台查询的类。 + * 继承自AsyncQueryHandler,用于处理异步查询完成后的操作。 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); } + /** + * 查询完成时的处理逻辑。 + * 根据查询标记的不同,执行不同的操作,如更新笔记列表或显示文件夹列表。 + * + * @param token 查询标记,用于区分不同的查询。 + * @param cookie 查询时传入的附加对象。 + * @param cursor 查询结果的游标。 + */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { switch (token) { case FOLDER_NOTE_LIST_QUERY_TOKEN: + // 更新笔记列表适配器的数据源 mNotesListAdapter.changeCursor(cursor); break; case FOLDER_LIST_QUERY_TOKEN: + // 根据查询结果展示或记录错误 if (cursor != null && cursor.getCount() > 0) { showFolderListMenu(cursor); } else { @@ -468,78 +633,123 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } break; default: + // 对未知标记不做处理 return; } } } + /** + * 显示文件夹列表的菜单。 + * 使用查询结果构建一个对话框,让用户选择一个文件夹。 + * + * @param cursor 查询结果的游标,包含文件夹信息。 + */ private void showFolderListMenu(Cursor cursor) { + // 构建文件夹列表选择的对话框 AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(R.string.menu_title_select_folder); final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - @SuppressLint("StringFormatInvalid") + /** + * 用户选择文件夹时的处理逻辑。 + * 将选中的笔记移动到用户选择的文件夹中,并给出反馈。 + * + * @param dialog 对话框实例。 + * @param which 用户选择的项的索引。 + */ public void onClick(DialogInterface dialog, int which) { + // 批量移动选中的笔记到目标文件夹 DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); + // 显示移动操作的反馈信息 Toast.makeText( NotesListActivity.this, getString(R.string.format_move_notes_to_folder, mNotesListAdapter.getSelectedCount(), adapter.getFolderName(NotesListActivity.this, which)), Toast.LENGTH_SHORT).show(); + // 结束当前的操作模式 mModeCallBack.finishActionMode(); } }); builder.show(); } + /** + * 创建新的笔记。 + * 启动一个活动用于编辑新笔记或编辑现有笔记。 + */ private void createNewNote() { + // 构建意图并指定动作为插入或编辑,以及初始文件夹ID Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + // 启动该意图并期待返回结果 this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + + /** + * 批量删除笔记的函数。根据当前是否处于同步模式,采取不同的删除策略:如果不处于同步模式,则直接删除笔记;如果处于同步模式,则将笔记移动到回收站文件夹。 + * 执行删除操作后,会更新相应的widgets。 + */ private void batchDelete() { new AsyncTask>() { + // 在后台执行任务,获取选中的widgets并执行删除操作 protected HashSet doInBackground(Void... unused) { + // 获取当前选中的widgets HashSet widgets = mNotesListAdapter.getSelectedWidget(); if (!isSyncMode()) { - // if not synced, delete notes directly + // 如果当前不处于同步模式,直接删除笔记 if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter .getSelectedItemIds())) { + // 删除成功无需额外操作 } else { + // 删除失败,记录错误 Log.e(TAG, "Delete notes error, should not happens"); } } else { - // in sync mode, we'll move the deleted note into the trash - // folder + // 如果处于同步模式,将笔记移动到回收站文件夹 if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { + // 移动失败,记录错误 Log.e(TAG, "Move notes to trash folder error, should not happens"); } } return widgets; } + // 删除操作完成后,在UI线程执行后续操作 @Override protected void onPostExecute(HashSet widgets) { + // 遍历所有受影响的widgets,对有效的widgets进行更新 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + // 更新有效的widget updateWidget(widget.widgetId, widget.widgetType); } } } + // 结束动作模式 mModeCallBack.finishActionMode(); } }.execute(); } + + /** + * 删除指定的文件夹。 + * 如果是在同步模式下,文件夹会被移动到回收站,否则直接删除。 + * 同时,也会更新与该文件夹相关的所有小部件。 + * + * @param folderId 要删除的文件夹ID。 + */ private void deleteFolder(long folderId) { + // 根据ID判断是否为根文件夹,根文件夹不能被删除 if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); return; @@ -547,17 +757,22 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe HashSet ids = new HashSet(); ids.add(folderId); + + // 获取与文件夹相关联的小部件信息 HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId); if (!isSyncMode()) { - // if not synced, delete folder directly + // 非同步模式下直接删除文件夹 DataUtils.batchDeleteNotes(mContentResolver, ids); } else { - // in sync mode, we'll move the deleted folder into the trash folder + // 同步模式下将文件夹移动到回收站 DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); } + + // 更新相关小部件 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { + // 有效的小部件才进行更新 if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { updateWidget(widget.widgetId, widget.widgetType); @@ -566,22 +781,39 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 打开指定的笔记节点进行编辑。 + * + * @param data 包含要打开的笔记节点信息的对象。 + */ private void openNode(NoteItemData data) { + // 构造Intent并设置动作和额外数据,然后启动Activity Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, data.getId()); this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } + /** + * 打开指定的文件夹,并加载其笔记列表。 + * 根据文件夹ID的不同,更新UI状态,包括标题和新增笔记按钮的可见性。 + * + * @param data 包含要打开的文件夹信息的对象。 + */ private void openFolder(NoteItemData data) { + // 设置当前文件夹ID并启动异步查询 mCurrentFolderId = data.getId(); startAsyncNotesListQuery(); + + // 根据文件夹ID更新UI状态 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mState = ListEditState.CALL_RECORD_FOLDER; mAddNewNote.setVisibility(View.GONE); } else { mState = ListEditState.SUB_FOLDER; } + + // 更新标题栏显示 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mTitleBar.setText(R.string.call_record_folder_name); } else { @@ -590,7 +822,14 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mTitleBar.setVisibility(View.VISIBLE); } + /** + * 点击事件的处理方法。 + * 目前仅处理新建笔记按钮的点击事件。 + * + * @param v 被点击的视图对象。 + */ public void onClick(View v) { + // 根据视图ID执行相应的操作 switch (v.getId()) { case R.id.btn_new_note: createNewNote(); @@ -600,6 +839,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + + /** + * 显示软键盘。 + */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { @@ -607,107 +850,128 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 隐藏软键盘。 + * + * @param view 触发隐藏软键盘的视图。 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示创建或修改文件夹的对话框。 + * + * @param create 如果为true,则为创建文件夹;如果为false,则为修改文件夹。 + */ private void showCreateOrModifyFolderDialog(final boolean create) { 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); - showSoftInput(); + showSoftInput(); // 显示软键盘 + if (!create) { + // 如果是修改文件夹 if (mFocusNoteDataItem != null) { - etName.setText(mFocusNoteDataItem.getSnippet()); - builder.setTitle(getString(R.string.menu_folder_change_name)); + etName.setText(mFocusNoteDataItem.getSnippet()); // 设置当前文件夹名称 + builder.setTitle(getString(R.string.menu_folder_change_name)); // 设置对话框标题 } else { - Log.e(TAG, "The long click data item is null"); + Log.e(TAG, "The long click data item is null"); // 日志记录,长按的数据项为null return; } } else { - etName.setText(""); - builder.setTitle(this.getString(R.string.menu_create_folder)); + // 如果是创建文件夹 + etName.setText(""); // 清空输入框内容 + builder.setTitle(this.getString(R.string.menu_create_folder)); // 设置对话框标题 } + // 设置对话框的确定和取消按钮 builder.setPositiveButton(android.R.string.ok, null); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - hideSoftInput(etName); + hideSoftInput(etName); // 点击取消时隐藏软键盘 } }); - final Dialog dialog = builder.setView(view).show(); - final Button positive = (Button)dialog.findViewById(android.R.id.button1); + final Dialog dialog = builder.setView(view).show(); // 显示对话框 + final Button positive = (Button) dialog.findViewById(android.R.id.button1); // 获取确定按钮 positive.setOnClickListener(new OnClickListener() { - @SuppressLint("StringFormatInvalid") public void onClick(View v) { - hideSoftInput(etName); - String name = etName.getText().toString(); - if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { + hideSoftInput(etName); // 隐藏软键盘 + String name = etName.getText().toString(); // 获取输入的文件夹名称 + if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { // 检查文件夹名称是否已存在 Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), - Toast.LENGTH_LONG).show(); - etName.setSelection(0, etName.length()); + Toast.LENGTH_LONG).show(); // 显示文件夹已存在的提示 + etName.setSelection(0, etName.length()); // 选中输入框中的所有文本 return; } if (!create) { - if (!TextUtils.isEmpty(name)) { + // 如果是修改文件夹 + if (!TextUtils.isEmpty(name)) { // 验证输入的文件夹名称不为空 ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.SNIPPET, name); // 设置新的文件夹名称 + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 设置类型为文件夹 + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为已修改 mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID - + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) - }); + + "=?", new String[]{ + String.valueOf(mFocusNoteDataItem.getId()) + }); // 更新数据库中的文件夹信息 } - } else if (!TextUtils.isEmpty(name)) { + } else if (!TextUtils.isEmpty(name)) { // 如果是创建文件夹 ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + values.put(NoteColumns.SNIPPET, name); // 设置文件夹名称 + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 设置类型为文件夹 + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); // 在数据库中插入新的文件夹信息 } - dialog.dismiss(); + dialog.dismiss(); // 关闭对话框 } }); + // 初始状态下,如果输入框为空,则禁用确定按钮 if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); } - /** - * When the name edit text is null, disable the positive button - */ + + // 监听输入框文本变化,以动态启用或禁用确定按钮 etName.addTextChangedListener(new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - + // 空实现 } public void onTextChanged(CharSequence s, int start, int before, int count) { - if (TextUtils.isEmpty(etName.getText())) { + if (TextUtils.isEmpty(etName.getText())) { // 当输入框为空时,禁用确定按钮 positive.setEnabled(false); - } else { + } else { // 当输入框不为空时,启用确定按钮 positive.setEnabled(true); } } public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - + // 空实现 } }); } + + /** + * 当用户按下返回键时调用的方法,根据当前状态执行不同的操作。 + * 在子文件夹状态下,返回根文件夹并显示笔记列表; + * 在通话记录文件夹状态下,也返回根文件夹但显示添加新笔记按钮; + * 在笔记列表状态下,执行父类的onBackPressed方法,通常是退出或返回上一级。 + */ @Override public void onBackPressed() { switch (mState) { case SUB_FOLDER: + // 从子文件夹状态返回到根文件夹的笔记列表状态 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; startAsyncNotesListQuery(); mTitleBar.setVisibility(View.GONE); break; case CALL_RECORD_FOLDER: + // 从通话记录文件夹状态返回到根文件夹的笔记列表状态,并显示添加新笔记按钮 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; mAddNewNote.setVisibility(View.VISIBLE); @@ -715,15 +979,25 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe startAsyncNotesListQuery(); break; case NOTE_LIST: + // 在笔记列表状态下,执行父类的返回操作 super.onBackPressed(); break; default: + // 对于其他状态,不执行任何操作 break; } } + /** + * 更新小部件显示。 + * 根据传入的小部件类型,设置对应的Provider并发送更新广播。 + * + * @param appWidgetId 小部件ID + * @param appWidgetType 小部件类型 + */ private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 根据小部件类型设置Provider if (appWidgetType == Notes.TYPE_WIDGET_2X) { intent.setClass(this, NoteWidgetProvider_2x.class); } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { @@ -733,14 +1007,18 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return; } - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - appWidgetId + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{ + appWidgetId }); sendBroadcast(intent); setResult(RESULT_OK, intent); } + /** + * 文件夹列表的上下文菜单创建监听器。 + * 在焦点笔记项不为空时,添加查看、删除和重命名菜单项。 + */ private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { @@ -752,6 +1030,12 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } }; + /** + * 上下文菜单关闭时的回调方法。 + * 在列表视图中取消上下文菜单的监听器。 + * + * @param menu 被关闭的菜单对象 + */ @Override public void onContextMenuClosed(Menu menu) { if (mNotesListView != null) { @@ -760,6 +1044,13 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe super.onContextMenuClosed(menu); } + + /** + * 当上下文菜单中的项目被选择时调用。 + * + * @param item 被选择的菜单项。 + * @return 如果事件已成功处理,则返回true;否则如果事件未处理,则返回false。 + */ @Override public boolean onContextItemSelected(MenuItem item) { if (mFocusNoteDataItem == null) { @@ -768,9 +1059,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } switch (item.getItemId()) { case MENU_FOLDER_VIEW: - openFolder(mFocusNoteDataItem); + openFolder(mFocusNoteDataItem); // 打开指定的文件夹 break; case MENU_FOLDER_DELETE: + // 显示删除文件夹的确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -778,14 +1070,14 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - deleteFolder(mFocusNoteDataItem.getId()); + deleteFolder(mFocusNoteDataItem.getId()); // 确认后删除文件夹 } }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case MENU_FOLDER_CHANGE_NAME: - showCreateOrModifyFolderDialog(false); + showCreateOrModifyFolderDialog(false); // 显示修改文件夹名称的对话框 break; default: break; @@ -794,12 +1086,22 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return true; } + /** + * 准备选项菜单。 + * + * @param menu 菜单对象。 + * @method onPrepareOptionsMenu + * @description 用于在菜单准备显示时进行处理的。 + * 它会根据当前的状态(mState)来加载不同的菜单布局,并设置相应的标题。 + * @return 方法返回true,表示菜单已经被处理完毕。 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { - menu.clear(); + menu.clear(); // 清除之前的菜单项 + // 根据mState的不同值,使用getMenuInflater().inflate()方法加载对应的菜单布局文件。 if (mState == ListEditState.NOTE_LIST) { getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel + // 设置同步或取消同步菜单项的标题 menu.findItem(R.id.menu_sync).setTitle( GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); } else if (mState == ListEditState.SUB_FOLDER) { @@ -809,21 +1111,70 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } else { Log.e(TAG, "Wrong state:" + mState); } + + if (background == -1) + { + menu.findItem(R.id.menu_switch_to_picture3).setVisible(false); + }else if (background == 0){ + menu.findItem(R.id.menu_switch_to_picture2).setVisible(false); + }else if (background == 1){ + menu.findItem(R.id.menu_switch_to_picture3).setVisible(false); + } + if(secret_mode ==1){ + menu.findItem(R.id.menu_secret).setVisible(false); + } + else{ + menu.findItem(R.id.menu_quit_secret).setVisible(false); + } return true; } + //qxq:创建意图打开活动的函数 + private void change_password() { + Intent intent=new Intent(NotesListActivity.this,ChangePassword.class); + startActivity(intent); + finish(); + } + + private void delete_password() { + Intent intent=new Intent(NotesListActivity.this, DeletePassword.class); + startActivity(intent); + finish(); + } + + private void set_login_password() { + Intent intent=new Intent(NotesListActivity.this, SetPassword.class); + startActivity(intent); + finish(); + } + /** + * 处理选项菜单项的选择。 + *每当用户选择菜单项时,Android 系统会调用该方法,并传入被选中的菜单项(MenuItem)。 + * @param item 被选择的菜单项。 + * @return 如果事件已成功处理,则返回true;否则如果事件未处理,则返回false。 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { + int login_mode=-1;//qxq: + SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE); + boolean User_boolean = pref.getBoolean("user",false); + if(User_boolean==true){ + login_mode=1; + } + else{ + login_mode=0; + } + switch (item.getItemId()) {//qxq:该方法首先获取被选中菜单项的ID,然后根据不同的ID执行相应的操作。 case R.id.menu_new_folder: { - showCreateOrModifyFolderDialog(true); + showCreateOrModifyFolderDialog(true); // 显示创建新文件夹的对话框 break; } case R.id.menu_export_text: { - exportNoteToText(); + exportNoteToText(); // 导出笔记为文本 break; } case R.id.menu_sync: { + // 处理同步菜单项的点击事件 if (isSyncMode()) { if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { GTaskSyncService.startSync(this); @@ -836,62 +1187,70 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe break; } case R.id.menu_setting: { - startPreferenceActivity(); + startPreferenceActivity(); // 打开设置界面 break; } case R.id.menu_new_note: { - createNewNote(); + createNewNote(); // 创建新笔记 break; } - case R.id.menu_search: - onSearchRequested(); + case R.id.menu_search: {//qxq:为什么这里源代码没有加入{} + onSearchRequested(); // 触发搜索请求 break; - case R.id.LoginSetting: { //设置登录密码功能 - SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE); - String password=pref.getString("password",""); - if(password.equals(""))//没有设置密码 - set_the_password(); - else if(!password.equals(""))//已经设置密码 - change_the_password(); + } + case R.id.menu_switch_to_picture1:{ + background = 1; + getWindow().setBackgroundDrawableResource(R.drawable.picture1); break; } - case R.id.delete_password:{ - delete_the_password(); + + case R.id.menu_createlogin: { + if (login_mode == 0) {//qxq:没有设置密码 + set_login_password(); + } else { + Toast.makeText(NotesListActivity.this, "您已经设置了密码", Toast.LENGTH_SHORT).show(); + } break; } - case R.id.menu_secret: { //进入私密模式 + + case R.id.menu_deletelogin:{ + if(login_mode==1) { + delete_password(); + } + else { + Toast.makeText(NotesListActivity.this, "您还没有设置密码", Toast.LENGTH_SHORT).show(); + } + break; + } + + case R.id.menu_changelogin:{ + if (login_mode == 1) { + change_password(); + } + else{ + Toast.makeText(NotesListActivity.this,"您还没有设置密码",Toast.LENGTH_SHORT).show(); + } + break; + } + + case R.id.menu_secret:{//qxq:已经设置了一些配置,但尚未开发 secret_mode = 1; - AlertDialog.Builder dialog = new AlertDialog.Builder(NotesListActivity.this); - dialog.setTitle("重要提醒"); - dialog.setMessage("您确认进入私密模式吗?"); - dialog.setCancelable(false); - dialog.setPositiveButton("确认", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startAsyncNotesListQuery(); - Toast.makeText(NotesListActivity.this,"您已进入私密模式",Toast.LENGTH_SHORT).show(); - } - }); - dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which){} - }); - dialog.show(); startAsyncNotesListQuery(); - Toast.makeText(this,"您已进入私密模式",Toast.LENGTH_SHORT).show(); + Toast.makeText(this,"您已进入隐私模式",Toast.LENGTH_SHORT).show(); break; } - case R.id.menu_quit_secret:{ //退出私密模式 + + case R.id.menu_quit_secret:{//qxq:已经设置了一些配置,但尚未开发 secret_mode = 0; AlertDialog.Builder dialog = new AlertDialog.Builder(NotesListActivity.this); - dialog.setTitle("重要提醒"); - dialog.setMessage("您确认退出私密模式吗?"); + dialog.setTitle("提醒"); + dialog.setMessage("您确认退出隐私模式吗?"); dialog.setCancelable(false); dialog.setPositiveButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startAsyncNotesListQuery(); - Toast.makeText(NotesListActivity.this,"您已退出私密模式",Toast.LENGTH_SHORT).show(); + Toast.makeText(NotesListActivity.this,"您已退出隐私模式",Toast.LENGTH_SHORT).show(); } }); dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() { @@ -901,76 +1260,96 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe dialog.show(); break; } + + /* + * 如果选中的菜单项是R.id.menu_new_folder,则调用showCreateOrModifyFolderDialog(true)方法,显示创建或修改文件夹的对话框。 + * 如果选中的菜单项是R.id.menu_export_text,则调用exportNoteToText()方法,将笔记导出为文本。 + * 如果选中的菜单项是R.id.menu_sync,则根据当前是否处于同步模式(isSyncMode())分别启动或取消同步服务(GTaskSyncService)或打开设置活动(startPreferenceActivity)。 + * 如果选中的菜单项是R.id.menu_setting,则打开设置活动(startPreferenceActivity)。 + * 如果选中的菜单项是R.id.menu_new_note,则创建新的笔记(createNewNote)。 + * 如果选中的菜单项是R.id.menu_search,则执行搜索请求(onSearchRequested)。 + */ + default: break; } return true; } + /** + * 处理搜索请求。 + * + * @return 总是返回true。 + */ @Override public boolean onSearchRequested() { startSearch(null, false, null /* appData */, false); return true; } + + /** + * 将笔记导出为文本文件。 + * 在后台任务中执行导出操作,并根据操作结果展示不同的对话框。 + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { @Override protected Integer doInBackground(Void... unused) { + // 执行导出操作 return backup.exportToText(); } @Override protected void onPostExecute(Integer result) { + // 根据导出结果展示不同的对话框 if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_unmounted)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); + showExportFailedDialog(NotesListActivity.this.getString(R.string.failed_sdcard_export), + NotesListActivity.this.getString(R.string.error_sdcard_unmounted)); } else if (result == BackupUtils.STATE_SUCCESS) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.success_sdcard_export)); - builder.setMessage(NotesListActivity.this.getString( - R.string.format_exported_file_location, backup - .getExportedTextFileName(), backup.getExportedTextFileDir())); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); + showExportSuccessDialog(NotesListActivity.this.getString(R.string.success_sdcard_export), + backup.getExportedTextFileName(), backup.getExportedTextFileDir()); } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_export)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); + showExportFailedDialog(NotesListActivity.this.getString(R.string.failed_sdcard_export), + NotesListActivity.this.getString(R.string.error_sdcard_export)); } } }.execute(); } + /** + * 检查当前是否为同步模式。 + * + * @return 如果已配置同步账户名则返回true,否则返回false。 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 启动设置活动。 + * 用于打开设置界面。 + */ private void startPreferenceActivity() { Activity from = getParent() != null ? getParent() : this; Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } + /** + * 列表项点击监听器。 + * 处理列表项的点击事件,根据不同的状态和项类型执行相应的操作。 + */ private class OnListItemClickListener implements OnItemClickListener { public void onItemClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { NoteItemData item = ((NotesListItem) view).getItemData(); if (mNotesListAdapter.isInChoiceMode()) { + // 在选择模式下处理项的点击事件 if (item.getType() == Notes.TYPE_NOTE) { position = position - mNotesListView.getHeaderViewsCount(); mModeCallBack.onItemCheckedStateChanged(null, position, id, @@ -979,6 +1358,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return; } + // 根据当前状态处理项的点击事件 switch (mState) { case NOTE_LIST: if (item.getType() == Notes.TYPE_FOLDER @@ -1006,17 +1386,21 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } + /** + * 启动查询目标文件夹。 + * 根据当前状态查询并显示文件夹列表。 + */ private void startQueryDestinationFolders() { String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; - selection = (mState == ListEditState.NOTE_LIST) ? selection: - "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + selection = (mState == ListEditState.NOTE_LIST) ? selection : + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, FoldersListAdapter.PROJECTION, selection, - new String[] { + new String[]{ String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER), String.valueOf(mCurrentFolderId) @@ -1024,10 +1408,17 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 长按列表项时的处理。 + * 根据不同的项类型启动选择模式或显示上下文菜单。 + * + * @return 总是返回false。 + */ public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { mFocusNoteDataItem = ((NotesListItem) view).getItemData(); if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + // 长按笔记项时启动选择模式 if (mNotesListView.startActionMode(mModeCallBack) != null) { mModeCallBack.onItemCheckedStateChanged(null, position, id, true); mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); @@ -1035,25 +1426,41 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe Log.e(TAG, "startActionMode fails"); } } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + // 长按文件夹项时设置上下文菜单监听器 mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); } } return false; } - private void set_the_password() { - Intent intent=new Intent(NotesListActivity.this,SettingPassword.class); - startActivity(intent); - finish(); - } - private void change_the_password() { - Intent intent=new Intent(NotesListActivity.this,ChangingPassword.class); - startActivity(intent); - finish(); + /** + * 显示导出失败的对话框。 + * + * @param title 对话框标题 + * @param message 对话框消息内容 + */ + private void showExportFailedDialog(String title, String message) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(title); + builder.setMessage(message); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); } - private void delete_the_password() { - Intent intent=new Intent(NotesListActivity.this,DeletingPassword.class); - startActivity(intent); - finish(); + + /** + * 显示导出成功的对话框。 + * + * @param title 对话框标题 + * @param fileName 导出文件的名称 + * @param fileDir 导出文件的目录 + */ + private void showExportSuccessDialog(String title, String fileName, String fileDir) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(title); + builder.setMessage(NotesListActivity.this.getString(R.string.format_exported_file_location, fileName, fileDir)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); } + + } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NotesListAdapter.java b/xiaomi-src/main/java/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..057de6a 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -31,18 +31,32 @@ import java.util.HashSet; import java.util.Iterator; +/** + * 用于管理笔记列表的适配器,继承自CursorAdapter。 + */ public class NotesListAdapter extends CursorAdapter { private static final String TAG = "NotesListAdapter"; private Context mContext; + // 用于存储选中项的索引和状态 private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; + private int mNotesCount; // 笔记总数 + private boolean mChoiceMode; // 选择模式标志 + /** + * AppWidget属性容器,用于存储与小部件相关的数据。 + */ public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; - }; + public int widgetId; // 小部件ID + public int widgetType; // 小部件类型 + } + ; + + /** + * 构造函数。 + * + * @param context 上下文对象 + */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); @@ -50,11 +64,26 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } + /** + * 创建新的列表项视图。 + * + * @param context 上下文对象 + * @param cursor 数据游标 + * @param parent 父视图 + * @return 新的列表项视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } + /** + * 绑定数据到视图。 + * + * @param view 列表项视图 + * @param context 上下文对象 + * @param cursor 数据游标 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -64,20 +93,41 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 设置指定位置的项为选中或未选中状态。 + * + * @param position 项的位置 + * @param checked 选中状态 + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); notifyDataSetChanged(); } + /** + * 获取当前是否处于选择模式。 + * + * @return 选择模式状态 + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置选择模式。 + * + * @param mode 选择模式状态 + */ public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } + /** + * 全选或全不选。 + * + * @param checked 选中状态 + */ public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { @@ -89,6 +139,11 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取所有选中项的ID集合。 + * + * @return 选中项ID的HashSet + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -105,6 +160,11 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取所有选中小部件的属性集合。 + * + * @return 选中小部件属性的HashSet + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -116,9 +176,6 @@ public class NotesListAdapter extends CursorAdapter { widget.widgetId = item.getWidgetId(); widget.widgetType = item.getWidgetType(); itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ } else { Log.e(TAG, "Invalid cursor"); return null; @@ -128,6 +185,11 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项的数量。 + * + * @return 选中项数量 + */ public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -143,11 +205,22 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 判断是否全部选中。 + * + * @return 全部选中的状态 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 检查指定位置的项是否被选中。 + * + * @param position 项的位置 + * @return 选中状态 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,18 +228,29 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + /** + * 当内容改变时调用,更新笔记数量。 + */ @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + /** + * 当游标改变时调用,更新笔记数量。 + * + * @param cursor 新的游标 + */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + /** + * 计算并更新笔记总数。 + */ private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { @@ -182,3 +266,4 @@ public class NotesListAdapter extends CursorAdapter { } } } + diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NotesListItem.java b/xiaomi-src/main/java/net/micode/notes/ui/NotesListItem.java index 2746619..1e53b26 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NotesListItem.java @@ -30,27 +30,43 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; +/* + * 该类表示一个笔记列表项,继承自LinearLayout,并包含了显示笔记各种信息的组件。 + * 它用于在UI中展示一个笔记或文件夹的条目。 + */ + public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; - private ImageView mTop; + private ImageView mAlert; // 用于显示提醒图标 + private TextView mTitle; // 显示笔记标题 + private TextView mTime; // 显示修改时间 + private TextView mCallName; // 在通话记录笔记中显示通话名称 + private NoteItemData mItemData; // 绑定的笔记数据 + private CheckBox mCheckBox; // 选择框,用于多选模式 + /* + * 构造函数,初始化视图组件。 + */ public NotesListItem(Context context) { super(context); inflate(context, R.layout.note_item, this); + // 初始化视图组件 mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mTitle = (TextView) findViewById(R.id.tv_title); mTime = (TextView) findViewById(R.id.tv_time); mCallName = (TextView) findViewById(R.id.tv_name); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); - mTop = (ImageView) findViewById(R.id.iv_top_icon); } + /* + * 绑定数据到视图,根据数据设置视图状态。 + * + * @param context 上下文 + * @param data 要绑定的笔记数据 + * @param choiceMode 是否为选择模式 + * @param checked 是否选中 + */ public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 根据是否为选择模式和笔记类型,控制复选框的可见性和选中状态 if (choiceMode && data.getType() == Notes.TYPE_NOTE) { mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(checked); @@ -59,7 +75,9 @@ public class NotesListItem extends LinearLayout { } mItemData = data; + // 根据笔记类型和状态,设置标题、提醒图标和背景 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 通话记录文件夹 mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); @@ -67,9 +85,10 @@ public class NotesListItem extends LinearLayout { + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setImageResource(R.drawable.call_record); } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + // 通话记录笔记 mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); - mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); + mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); @@ -78,13 +97,14 @@ public class NotesListItem extends LinearLayout { mAlert.setVisibility(View.GONE); } } else { + // 其他类型的笔记或文件夹 mCallName.setVisibility(View.GONE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); if (data.getType() == Notes.TYPE_FOLDER) { mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); + data.getNotesCount())); mAlert.setVisibility(View.GONE); } else { mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); @@ -95,23 +115,21 @@ public class NotesListItem extends LinearLayout { mAlert.setVisibility(View.GONE); } } - if(data.isTOP()){ - mTop.setImageResource(R.drawable.menu_top); - mTop.setVisibility(View.VISIBLE); - } else{ - mTop.setVisibility((View.GONE)); - } } + // 设置时间显示 mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 设置背景资源 setBackground(data); } - - + /* + * 根据笔记数据设置列表项的背景资源。 + */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { + // 根据笔记的状态设置不同的背景资源 if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); } else if (data.isLast()) { @@ -122,11 +140,18 @@ public class NotesListItem extends LinearLayout { setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } } else { + // 文件夹背景资源 setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /* + * 获取绑定的笔记数据。 + * + * @return 绑定的NoteItemData对象 + */ public NoteItemData getItemData() { return mItemData; } } + diff --git a/xiaomi-src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/xiaomi-src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..76640bc 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -49,51 +49,56 @@ import net.micode.notes.gtask.remote.GTaskSyncService; public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; - - public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - - public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - - public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - - private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; - + // 常量定义部分:主要用于设置和同步相关的偏好设置键 + public static final String PREFERENCE_NAME = "notes_preferences"; // 偏好设置的名称 + public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; // 同步账户名称的键 + public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; // 上次同步时间的键 + public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; // 设置背景颜色的键 + private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; // 同步账户的键 + private static final String AUTHORITIES_FILTER_KEY = "authorities"; // 权限过滤键 + + // 类成员变量定义部分:主要用于账户同步和UI更新 + private PreferenceCategory mAccountCategory; // 账户分类偏好项 + private GTaskReceiver mReceiver; // 接收同步任务的广播接收器 + private Account[] mOriAccounts; // 原始账户数组 + private boolean mHasAddedAccount; // 标记是否已添加新账户 + + /** + * 当设置Activity创建时调用。 + * 主要进行界面初始化和设置账户同步。 + * + * @param icicle 保存Activity状态的Bundle,用于恢复状态。 + */ @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - /* using the app icon for navigation */ + // 设置返回按钮 getActionBar().setDisplayHomeAsUpEnabled(true); + // 从XML加载偏好设置 addPreferencesFromResource(R.xml.preferences); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mReceiver = new GTaskReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); + registerReceiver(mReceiver, filter); // 注册广播接收器以监听同步服务 mOriAccounts = null; + // 添加设置头部视图 View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); getListView().addHeaderView(header, null, true); } + /** + * 当设置Activity恢复到前台时调用。 + * 主要用于检查并自动设置新添加的账户进行同步。 + */ @Override protected void onResume() { super.onResume(); - // need to set sync account automatically if user has added a new - // account + // 自动设置新添加的账户进行同步 if (mHasAddedAccount) { Account[] accounts = getGoogleAccounts(); if (mOriAccounts != null && accounts.length > mOriAccounts.length) { @@ -106,99 +111,121 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } if (!found) { - setSyncAccount(accountNew.name); + setSyncAccount(accountNew.name); // 设置新账户进行同步 break; } } } } + // 刷新UI refreshUI(); } + + /** + * 当Activity即将被销毁时调用,用于注销广播接收器。 + */ @Override protected void onDestroy() { if (mReceiver != null) { - unregisterReceiver(mReceiver); + unregisterReceiver(mReceiver); // 注销广播接收器,避免内存泄漏 } super.onDestroy(); } + /** + * 加载账户偏好设置,展示当前同步账户信息及操作。 + */ private void loadAccountPreference() { - mAccountCategory.removeAll(); + mAccountCategory.removeAll(); // 清空账户分类下的所有条目 + // 创建并配置账户偏好项 Preference accountPref = new Preference(this); - final String defaultAccount = getSyncAccountName(this); - accountPref.setTitle(getString(R.string.preferences_account_title)); - accountPref.setSummary(getString(R.string.preferences_account_summary)); + final String defaultAccount = getSyncAccountName(this); // 获取默认同步账户名称 + accountPref.setTitle(getString(R.string.preferences_account_title)); // 设置标题 + accountPref.setSummary(getString(R.string.preferences_account_summary)); // 设置摘要 accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { + // 处理账户点击事件 if (!GTaskSyncService.isSyncing()) { if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account + // 如果尚未设置账户,则展示选择账户对话框 showSelectAccountAlertDialog(); } else { - // if the account has already been set, we need to promp - // user about the risk + // 如果已经设置账户,则展示更改账户确认对话框 showChangeAccountConfirmAlertDialog(); } } else { + // 如果正在同步中,则展示无法更改账户的提示 Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); } return true; } }); - mAccountCategory.addPreference(accountPref); + mAccountCategory.addPreference(accountPref); // 将账户偏好项添加到账户分类下 } + /** + * 加载同步按钮,并根据同步状态设置其文本和点击事件。 + */ private void loadSyncButton() { - Button syncButton = (Button) findViewById(R.id.preference_sync_button); - TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + Button syncButton = (Button) findViewById(R.id.preference_sync_button); // 获取同步按钮 + TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); // 获取上次同步时间视图 - // set button state + // 根据同步状态设置按钮文本和点击事件 if (GTaskSyncService.isSyncing()) { - syncButton.setText(getString(R.string.preferences_button_sync_cancel)); + syncButton.setText(getString(R.string.preferences_button_sync_cancel)); // 设置为取消同步文本 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - GTaskSyncService.cancelSync(NotesPreferenceActivity.this); + GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 设置点击事件为取消同步 } }); } else { - syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + syncButton.setText(getString(R.string.preferences_button_sync_immediately)); // 设置为立即同步文本 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - GTaskSyncService.startSync(NotesPreferenceActivity.this); + GTaskSyncService.startSync(NotesPreferenceActivity.this); // 设置点击事件为开始同步 } }); } - syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); // 只有在设置了同步账户时才使能同步按钮 - // set last sync time + // 根据同步状态设置上次同步时间的显示 if (GTaskSyncService.isSyncing()) { - lastSyncTimeView.setText(GTaskSyncService.getProgressString()); - lastSyncTimeView.setVisibility(View.VISIBLE); + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 如果正在同步,显示进度信息 + lastSyncTimeView.setVisibility(View.VISIBLE); // 显示上次同步时间视图 } else { - long lastSyncTime = getLastSyncTime(this); + long lastSyncTime = getLastSyncTime(this); // 获取上次同步时间 if (lastSyncTime != 0) { lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, DateFormat.format(getString(R.string.preferences_last_sync_time_format), - lastSyncTime))); - lastSyncTimeView.setVisibility(View.VISIBLE); + lastSyncTime))); // 格式化并显示上次同步时间 + lastSyncTimeView.setVisibility(View.VISIBLE); // 显示上次同步时间视图 } else { - lastSyncTimeView.setVisibility(View.GONE); + lastSyncTimeView.setVisibility(View.GONE); // 如果未同步过,则隐藏上次同步时间视图 } } } + /** + * 刷新用户界面,加载账户偏好设置和同步按钮。 + */ private void refreshUI() { - loadAccountPreference(); - loadSyncButton(); + loadAccountPreference(); // 加载账户偏好设置 + loadSyncButton(); // 加载同步按钮 } + /** + * 显示选择账户的对话框。 + * 该对话框列出了已连接的Google账户,并允许用户选择一个账户用于同步。 + * 如果没有账户,对话框将提供添加账户的选项。 + */ private void showSelectAccountAlertDialog() { + // 创建对话框构建器并设置自定义标题 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); @@ -208,18 +235,20 @@ public class NotesPreferenceActivity extends PreferenceActivity { subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); dialogBuilder.setCustomTitle(titleView); - dialogBuilder.setPositiveButton(null, null); + dialogBuilder.setPositiveButton(null, null); // 移除默认的确定按钮 + // 获取当前设备上的Google账户 Account[] accounts = getGoogleAccounts(); - String defAccount = getSyncAccountName(this); + String defAccount = getSyncAccountName(this); // 获取当前同步的账户名称 - mOriAccounts = accounts; - mHasAddedAccount = false; + mOriAccounts = accounts; // 保存原始账户列表 + mHasAddedAccount = false; // 标记是否已添加新账户 if (accounts.length > 0) { + // 创建账户选项并设置选中项 CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; - int checkedItem = -1; + int checkedItem = -1; // 记录默认选中的账户 int index = 0; for (Account account : accounts) { if (TextUtils.equals(account.name, defAccount)) { @@ -227,6 +256,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } items[index++] = account.name; } + // 设置单选列表,并为选中的账户执行同步操作 dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { @@ -237,16 +267,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 添加“添加账户”选项 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); final AlertDialog dialog = dialogBuilder.show(); + // 点击“添加账户”执行添加账户操作 addAccountView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mHasAddedAccount = true; Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[]{ + "gmail-ls" }); startActivityForResult(intent, -1); dialog.dismiss(); @@ -254,9 +286,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 显示更改账户确认对话框。 + * 提供用户更改当前同步账户或取消更改的选择。 + */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 设置自定义标题,包含当前同步账户名称 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, @@ -265,7 +302,8 @@ public class NotesPreferenceActivity extends PreferenceActivity { subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); dialogBuilder.setCustomTitle(titleView); - CharSequence[] menuItemArray = new CharSequence[] { + // 创建菜单项并设置点击事件 + CharSequence[] menuItemArray = new CharSequence[]{ getString(R.string.preferences_menu_change_account), getString(R.string.preferences_menu_remove_account), getString(R.string.preferences_menu_cancel) @@ -273,8 +311,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { + // 选择更改账户,显示账户选择对话框 showSelectAccountAlertDialog(); } else if (which == 1) { + // 选择移除账户,执行移除操作并刷新UI removeSyncAccount(); refreshUI(); } @@ -283,15 +323,29 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + /** + * 获取设备上的Google账户列表。 + * + * @return Account[] 返回设备上所有类型为“com.google”的账户数组。 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + + /** + * 设置同步账户信息。 + * 如果当前账户与传入账户不一致,则更新SharedPreferences中的账户信息,并清理本地相关的gtask信息。 + * + * @param account 需要设置的账户名 + */ private void setSyncAccount(String account) { + // 检查当前账户是否与传入账户名一致,不一致则更新账户信息 if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); + // 如果账户名非空,则保存账户名,否则清除账户名 if (account != null) { editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); } else { @@ -299,10 +353,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up last sync time + // 清理上次同步时间 setLastSyncTime(this, 0); - // clean up local gtask related info + // 清理本地相关的gtask信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -312,24 +366,31 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }).start(); + // 显示设置成功的提示信息 Toast.makeText(NotesPreferenceActivity.this, getString(R.string.preferences_toast_success_set_accout, account), Toast.LENGTH_SHORT).show(); } } + /** + * 移除同步账户信息。 + * 清除SharedPreferences中的账户信息和上次同步时间,并清理本地相关的gtask信息。 + */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); + // 如果存在账户信息,则移除 if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); } + // 如果存在上次同步时间信息,则移除 if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { editor.remove(PREFERENCE_LAST_SYNC_TIME); } editor.commit(); - // clean up local gtask related info + // 清理本地相关的gtask信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -340,12 +401,26 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + /** + * 获取当前同步账户名。 + * 从SharedPreferences中获取存储的账户名,默认为空字符串。 + * + * @param context 上下文 + * @return 同步账户名 + */ public static String getSyncAccountName(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } + /** + * 设置上次同步的时间。 + * 将指定的时间保存到SharedPreferences中。 + * + * @param context 上下文 + * @param time 上次同步的时间戳 + */ public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -354,17 +429,28 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + /** + * 获取上次同步的时间。 + * 从SharedPreferences中获取上次同步的时间戳,默认为0。 + * + * @param context 上下文 + * @return 上次同步的时间戳 + */ public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); } + /** + * 广播接收器类,用于接收gtask同步相关的广播消息,并据此刷新UI。 + */ private class GTaskReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { refreshUI(); + // 如果广播消息表明正在同步,则更新UI显示的同步状态信息 if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); syncStatus.setText(intent @@ -374,9 +460,16 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 处理选项菜单项的选择事件。 + * + * @param item 选中的菜单项 + * @return 如果事件已处理,则返回true;否则返回false。 + */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: + // 当选择返回按钮时,启动NotesListActivity并清除当前活动栈顶以上的所有活动 Intent intent = new Intent(this, NotesListActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); diff --git a/xiaomi-src/main/java/net/micode/notes/ui/SettingPassword.java b/xiaomi-src/main/java/net/micode/notes/ui/SetPassword.java similarity index 79% rename from xiaomi-src/main/java/net/micode/notes/ui/SettingPassword.java rename to xiaomi-src/main/java/net/micode/notes/ui/SetPassword.java index 60f934d..cb37e8b 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/SettingPassword.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/SetPassword.java @@ -15,7 +15,7 @@ import android.widget.Toast; import net.micode.notes.R; -public class SettingPassword extends Activity { +public class SetPassword extends Activity { EditText password; EditText password_ack; Button acknowledge; @@ -36,9 +36,9 @@ public class SettingPassword extends Activity { String text02 = password.getText().toString(); String text03 = password_ack.getText().toString(); if(text02.equals("")==true) { - Toast.makeText(SettingPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show(); + Toast.makeText(SetPassword.this, "密码不能为空", Toast.LENGTH_SHORT).show(); }else if (text02.equals(text03) == false) { - Toast.makeText(SettingPassword.this, "密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show(); + Toast.makeText(SetPassword.this, "密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show(); password_ack.setText(""); }else if (text02.equals(text03) == true){ SharedPreferences.Editor editor=getSharedPreferences("user management", @@ -47,8 +47,8 @@ public class SettingPassword extends Activity { editor.putString("password",text02); editor.apply(); Log.d("RegisterLoginPassword","password is "+text02); - Toast.makeText(SettingPassword.this, "设置密码成功", Toast.LENGTH_SHORT).show(); - Intent intent=new Intent(SettingPassword.this,NotesListActivity.class); + Toast.makeText(SetPassword.this, "设置密码成功", Toast.LENGTH_SHORT).show(); + Intent intent=new Intent(SetPassword.this,NotesListActivity.class); startActivity(intent); finish(); } @@ -59,7 +59,7 @@ public class SettingPassword extends Activity { @Override public void onBackPressed() { - Intent intent=new Intent(SettingPassword.this,NotesListActivity.class); + Intent intent=new Intent(SetPassword.this,NotesListActivity.class); startActivity(intent); finish(); } diff --git a/xiaomi-src/main/java/net/micode/notes/ui/SplashActivity.java b/xiaomi-src/main/java/net/micode/notes/ui/SplashActivity.java index 5d9defb..bc8ca16 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/SplashActivity.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/SplashActivity.java @@ -1,31 +1,33 @@ package net.micode.notes.ui; -import android.annotation.SuppressLint; + import android.content.Intent; -import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; -import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowInsets; + import net.micode.notes.R; + +/** + * An example full-screen activity that shows and hides the system UI (i.e. + * status bar and navigation/system bar) with user interaction. + */ public class SplashActivity extends AppCompatActivity { Handler mHandler=new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //加载启动界面 - setContentView(R.layout.activity_splash); //加载启动 -// 当计时结束时,跳转至NotesListActivity - mHandler.postDelayed(new Runnable() { + setContentView(R.layout.activity_splash); //加载启动图片 + // 当计时结束时,跳转至NotesListActivity + mHandler.postDelayed(new Runnable() { @Override public void run() { Intent intent=new Intent(); - intent.setClass(SplashActivity.this, LoginActivity.class); + intent.setClass(SplashActivity.this, NotesListActivity.class); startActivity(intent); finish(); //销毁欢迎页面 - }}, 2000); // 2 秒后跳转} + } + }, 3000); // 2 秒后跳转 } } \ No newline at end of file diff --git a/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/BaiduTranslateService.java b/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/BaiduTranslateService.java index f96f037..9d8652d 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/BaiduTranslateService.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/BaiduTranslateService.java @@ -4,10 +4,21 @@ import retrofit2.Call; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; - +//源URL https://fanyi-api.baidu.com/api/trans/vip/translate +//参数如下 +// String q 英文单词/中文 +// String from 原始语种 zh中文/eh英文 +// String to 目标语种 zh中文/eh英文 +// String from zh中文/eh英文 +// String appid 你的appid +// String salt 随机数(整形转字符串) +// String sign 签名 32位字母小写MD5编码的 appid+q+salt+密钥 public interface BaiduTranslateService { + //翻译接口 + //表示提交表单数据,@Field注解键名 + //适用于数据量少的情况 @POST("translate") @FormUrlEncoded Call translate(@Field("q") String q, @Field("from") String from, @Field("to") String to, @Field("appid") String appid, @Field("salt") String salt, @Field("sign") String sign); -} \ No newline at end of file +} diff --git a/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/RespondBean.java b/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/RespondBean.java index 3895737..87ab90b 100644 --- a/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/RespondBean.java +++ b/xiaomi-src/main/java/net/micode/notes/ui/translate_demo/RespondBean.java @@ -2,7 +2,7 @@ package net.micode.notes.ui.translate_demo; import java.util.List; - public class RespondBean { +public class RespondBean { /** * from : zh @@ -43,7 +43,6 @@ import java.util.List; * src : 你好 * dst : Hello */ - private String src; private String dst; diff --git a/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider.java b/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..898de0a 100644 --- a/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider.java +++ b/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider.java @@ -1,20 +1,10 @@ /* - * 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. + * 注意:此代码段的版权归 MiCode 开源社区所有(www.micode.net) + * 本代码遵循 Apache 2.0 许可证,您可以在 http://www.apache.org/licenses/LICENSE-2.0 查看许可证内容。 */ package net.micode.notes.widget; + import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; @@ -32,19 +22,28 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * 笔记小部件提供者抽象类,扩展自AppWidgetProvider,用于管理和更新笔记小部件的内容。 + */ public abstract class NoteWidgetProvider extends AppWidgetProvider { - public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + // 查询笔记时用到的列名数组 + public static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET }; - public static final int COLUMN_ID = 0; - public static final int COLUMN_BG_COLOR_ID = 1; - public static final int COLUMN_SNIPPET = 2; + // 列的索引常量 + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + // 日志标签 private static final String TAG = "NoteWidgetProvider"; + /** + * 当小部件被删除时调用,更新数据库中对应小部件的ID为无效ID。 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { ContentValues values = new ContentValues(); @@ -53,24 +52,46 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { context.getContentResolver().update(Notes.CONTENT_NOTE_URI, values, NoteColumns.WIDGET_ID + "=?", - new String[] { String.valueOf(appWidgetIds[i])}); + new String[]{String.valueOf(appWidgetIds[i])}); } } + /** + * 根据小部件ID查询对应的笔记信息。 + * + * @param context 上下文 + * @param widgetId 小部件ID + * @return 返回查询到的Cursor对象,包含笔记的摘要、背景ID等信息。 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { 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_FOLER)}, null); } + /** + * 更新小部件显示内容的通用方法。 + * + * @param context 上下文 + * @param appWidgetManager AppWidget管理器 + * @param appWidgetIds 小部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 根据是否隐私模式更新小部件显示内容。 + * + * @param context 上下文 + * @param appWidgetManager AppWidget管理器 + * @param appWidgetIds 小部件ID数组 + * @param privacyMode 是否为隐私模式 + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, - boolean privacyMode) { + boolean privacyMode) { for (int i = 0; i < appWidgetIds.length; i++) { if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { int bgId = ResourceParser.getDefaultBgId(context); @@ -103,9 +124,8 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); - /** - * Generate the pending intent to start host for the widget - */ + + // 为小部件的点击事件设置PendingIntent PendingIntent pendingIntent = null; if (privacyMode) { rv.setTextViewText(R.id.widget_text, @@ -124,9 +144,26 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 获取背景资源的ID。 + * + * @param bgId 背景ID + * @return 返回对应的资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 获取小部件布局的ID。 + * + * @return 返回布局的资源ID + */ protected abstract int getLayoutId(); + /** + * 获取小部件的类型。 + * + * @return 返回小部件的类型 + */ protected abstract int getWidgetType(); } + diff --git a/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java b/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..fde2df2 100644 --- a/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -1,17 +1,10 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权声明:MiCode开源社区(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 + * 本代码遵循Apache 2.0开源协议 + * 如需获取完整的授权条款,请访问:http://www.apache.org/licenses/LICENSE-2.0.html * - * 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.widget; @@ -23,25 +16,51 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * 2x版本的NoteWidgetProvider,负责处理2x大小的小部件更新和其他操作。 + */ public class NoteWidgetProvider_2x extends NoteWidgetProvider { + /** + * 当小部件需要更新时调用此方法。 + * + * @param context 上下文环境,用于访问应用全局功能。 + * @param appWidgetManager 管理当前应用中所有小部件的AppWidgetManager实例。 + * @param appWidgetIds 当前需要更新的小部件ID数组。 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取小部件布局的ID。 + * + * @return 布局资源ID。 + */ @Override protected int getLayoutId() { return R.layout.widget_2x; } + /** + * 根据背景ID获取对应的背景资源ID。 + * + * @param bgId 背景资源的索引ID。 + * @return 背景资源的ID。 + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); } + /** + * 获取小部件的类型。 + * + * @return 小部件类型的常量。 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_2X; } } + diff --git a/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java b/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..15c896e 100644 --- a/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/xiaomi-src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -1,17 +1,8 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权声明:MiCode开源社区(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. + * 本代码遵循Apache 2.0开源协议 + * 详细授权信息请访问:http://www.apache.org/licenses/LICENSE-2.0 */ package net.micode.notes.widget; @@ -23,24 +14,51 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * 4x大小的便签小部件提供者类,继承自NoteWidgetProvider。 + * 负责处理小部件在界面更新时的逻辑。 + */ public class NoteWidgetProvider_4x extends NoteWidgetProvider { + /** + * 当小部件需要更新时调用此方法。 + * + * @param context 上下文环境,通常为应用程序环境。 + * @param appWidgetManager 小部件管理器,用于管理已安装的小部件。 + * @param appWidgetIds 当前需要更新的小部件ID数组。 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取小部件布局的ID。 + * + * @return 布局资源ID。 + */ protected int getLayoutId() { return R.layout.widget_4x; } + /** + * 获取小部件背景资源的ID。 + * + * @param bgId 背景资源的索引ID。 + * @return 背景资源的ID。 + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + /** + * 获取当前小部件的类型。 + * + * @return 小部件类型的整型标识。 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X; } } + diff --git a/xiaomi-src/main/res/color/primary_text_dark.xml b/xiaomi-src/main/res/color/primary_text_dark.xml index ceef389..d72274f 100644 --- a/xiaomi-src/main/res/color/primary_text_dark.xml +++ b/xiaomi-src/main/res/color/primary_text_dark.xml @@ -1,22 +1,17 @@ - - - - + + + + + + diff --git a/xiaomi-src/main/res/color/secondary_text_dark.xml b/xiaomi-src/main/res/color/secondary_text_dark.xml index f0de0f5..2364c35 100644 --- a/xiaomi-src/main/res/color/secondary_text_dark.xml +++ b/xiaomi-src/main/res/color/secondary_text_dark.xml @@ -1,22 +1,15 @@ - - + + - - diff --git a/xiaomi-src/main/res/drawable-hdpi/bitmap_pressed.png b/xiaomi-src/main/res/drawable-hdpi/bitmap_pressed.png deleted file mode 100644 index f5773c0..0000000 Binary files a/xiaomi-src/main/res/drawable-hdpi/bitmap_pressed.png and /dev/null differ diff --git a/xiaomi-src/main/res/drawable-hdpi/menu_top.png b/xiaomi-src/main/res/drawable-hdpi/menu_top.png deleted file mode 100644 index 6568ddf..0000000 Binary files a/xiaomi-src/main/res/drawable-hdpi/menu_top.png and /dev/null differ diff --git a/xiaomi-src/main/res/drawable-hdpi/picture1.png b/xiaomi-src/main/res/drawable-hdpi/picture1.png new file mode 100644 index 0000000..9bd3a23 Binary files /dev/null and b/xiaomi-src/main/res/drawable-hdpi/picture1.png differ diff --git a/xiaomi-src/main/res/drawable-hdpi/picture2.jpg b/xiaomi-src/main/res/drawable-hdpi/picture2.jpg new file mode 100644 index 0000000..697ae89 Binary files /dev/null and b/xiaomi-src/main/res/drawable-hdpi/picture2.jpg differ diff --git a/xiaomi-src/main/res/drawable-hdpi/splash.jpg b/xiaomi-src/main/res/drawable-hdpi/splash.jpg deleted file mode 100644 index 37b004d..0000000 Binary files a/xiaomi-src/main/res/drawable-hdpi/splash.jpg and /dev/null differ diff --git a/xiaomi-src/main/res/drawable-hdpi/splash.png b/xiaomi-src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000..165ae5b Binary files /dev/null and b/xiaomi-src/main/res/drawable-hdpi/splash.png differ diff --git a/xiaomi-src/main/res/drawable/new_note.xml b/xiaomi-src/main/res/drawable/new_note.xml index 2154ebc..92507bc 100644 --- a/xiaomi-src/main/res/drawable/new_note.xml +++ b/xiaomi-src/main/res/drawable/new_note.xml @@ -1,23 +1,17 @@ - + + diff --git a/xiaomi-src/main/res/layout/account_dialog_title.xml b/xiaomi-src/main/res/layout/account_dialog_title.xml index 5f2ebce..be5a242 100644 --- a/xiaomi-src/main/res/layout/account_dialog_title.xml +++ b/xiaomi-src/main/res/layout/account_dialog_title.xml @@ -1,37 +1,29 @@ - - + android:orientation="vertical" + xmlns:android="http://schemas.android.com/apk/res/android"> + + android:layout_marginTop="-2.7dip" + android:layout_marginBottom="-2.7dip" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + - \ No newline at end of file + diff --git a/xiaomi-src/main/res/layout/activity_splash.xml b/xiaomi-src/main/res/layout/activity_splash.xml index 122207a..c00b1fe 100644 --- a/xiaomi-src/main/res/layout/activity_splash.xml +++ b/xiaomi-src/main/res/layout/activity_splash.xml @@ -3,20 +3,22 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#0099cc" + android:background="@drawable/splash" tools:context=".ui.SplashActivity"> + + @@ -29,34 +31,23 @@