diff --git a/src/notes/tool/BackupUtils.java b/src/notes/tool/BackupUtils.java index 39f6ec4..b869743 100644 --- a/src/notes/tool/BackupUtils.java +++ b/src/notes/tool/BackupUtils.java @@ -35,10 +35,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; - public class BackupUtils { private static final String TAG = "BackupUtils"; - // Singleton stuff + + // 单例模式实现 private static BackupUtils sInstance; public static synchronized BackupUtils getInstance(Context context) { @@ -49,43 +49,45 @@ 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; - - private TextExport mTextExport; + public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载 + 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; // 文本导出处理器 private BackupUtils(Context context) { mTextExport = new TextExport(context); } + // 检查外部存储是否可用 private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + // 导出到文本文件的主入口方法 public int exportToText() { return mTextExport.exportToText(); } + // 获取导出的文本文件名 public String getExportedTextFileName() { return mTextExport.mFileName; } + // 获取导出的文本文件目录 public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 内部类:处理文本导出的具体逻辑 + */ private static class TextExport { + // 笔记表查询字段 private static final String[] NOTE_PROJECTION = { NoteColumns.ID, NoteColumns.MODIFIED_DATE, @@ -93,12 +95,11 @@ 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 int NOTE_COLUMN_ID = 0; // 笔记ID索引 + 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 +109,54 @@ public class BackupUtils { DataColumns.DATA4, }; - private static final int DATA_COLUMN_CONTENT = 0; - - 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 static final int DATA_COLUMN_CONTENT = 0; // 内容索引 + private static final int DATA_COLUMN_MIME_TYPE = 1; // MIME类型索引 + 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 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; + private String mFileName; // 导出的文件名 + private String mFileDirectory; // 导出的文件目录 public TextExport(Context context) { + // 从资源文件加载导出格式 TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; mFileName = ""; mFileDirectory = ""; } + // 获取指定格式的字符串 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 + 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()); @@ -163,20 +166,25 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 导出指定笔记的内容到文本 + * @param noteId 笔记ID + * @param ps 输出流 */ private void exportNoteToText(String noteId, PrintStream ps) { + // 查询该笔记的所有数据项 Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - noteId + noteId }, null); if (dataCursor != null) { if (dataCursor.moveToFirst()) { do { String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + + // 根据MIME类型处理不同类型的数据 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); @@ -185,16 +193,17 @@ public class BackupUtils { 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,7 +214,8 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + + // 在笔记之间添加分隔符 try { ps.write(new byte[] { Character.LINE_SEPARATOR, Character.LETTER_NUMBER @@ -216,20 +226,25 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 主导出方法:将所有笔记导出为可读的文本格式 + * @return 导出状态码 */ 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,7 +255,7 @@ 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) { folderName = mContext.getString(R.string.call_record_folder_name); @@ -250,6 +265,7 @@ public class BackupUtils { if (!TextUtils.isEmpty(folderName)) { ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); } + // 导出该文件夹下的所有笔记 String folderId = folderCursor.getString(NOTE_COLUMN_ID); exportFolderToText(folderId, ps); } while (folderCursor.moveToNext()); @@ -257,7 +273,7 @@ public class BackupUtils { folderCursor.close(); } - // Export notes in root's folder + // 第二部分:导出根目录下的笔记(不属于任何文件夹的笔记) Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -270,30 +286,36 @@ public class BackupUtils { 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()); } noteCursor.close(); } - ps.close(); + // 关闭输出流 + ps.close(); return STATE_SUCCESS; } /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 创建并获取指向导出文件的输出流 + * @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"); return null; } + + // 记录文件名和目录 mFileName = file.getName(); mFileDirectory = mContext.getString(R.string.file_path); + PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream(file); @@ -310,13 +332,20 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 在SD卡上创建用于存储导出数据的文本文件 + * @param context 上下文 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 创建的File对象,失败返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + // 构建完整文件路径 StringBuilder sb = new StringBuilder(); - sb.append(Environment.getExternalStorageDirectory()); - sb.append(context.getString(filePathResId)); + sb.append(Environment.getExternalStorageDirectory()); // SD卡根目录 + 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), @@ -324,9 +353,11 @@ public class BackupUtils { File file = new File(sb.toString()); try { + // 创建目录(如果不存在) if (!filedir.exists()) { filedir.mkdir(); } + // 创建文件(如果不存在) if (!file.exists()) { file.createNewFile(); } @@ -339,6 +370,4 @@ public class BackupUtils { return null; } -} - - +} \ No newline at end of file diff --git a/src/notes/tool/DataUtils.java b/src/notes/tool/DataUtils.java index 2a14982..b4cdb3d 100644 --- a/src/notes/tool/DataUtils.java +++ b/src/notes/tool/DataUtils.java @@ -34,9 +34,15 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import java.util.ArrayList; import java.util.HashSet; - public class DataUtils { public static final String TAG = "DataUtils"; + + /** + * 批量删除笔记 + * @param resolver ContentResolver实例 + * @param ids 要删除的笔记ID集合 + * @return 删除是否成功 + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -47,17 +53,21 @@ public class DataUtils { return true; } + // 构建批量删除操作列表 ArrayList operationList = new ArrayList(); for (long id : ids) { if(id == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Don't delete system folder root"); + Log.e(TAG, "Don't delete system folder root"); // 禁止删除根文件夹 continue; } + // 为每个笔记ID创建删除操作 ContentProviderOperation.Builder builder = ContentProviderOperation .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); operationList.add(builder.build()); } + 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()); @@ -72,31 +82,48 @@ public class DataUtils { return false; } + /** + * 移动笔记到目标文件夹 + * @param resolver ContentResolver实例 + * @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); - values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); - values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.PARENT_ID, desFolderId); // 设置新的父文件夹ID + values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); // 记录原始父文件夹ID + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为已本地修改 + // 更新笔记记录 resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + /** + * 批量移动笔记到指定文件夹 + * @param resolver ContentResolver实例 + * @param ids 要移动的笔记ID集合 + * @param folderId 目标文件夹ID + * @return 移动是否成功 + */ 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 .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); - builder.withValue(NoteColumns.PARENT_ID, folderId); - builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); + builder.withValue(NoteColumns.PARENT_ID, folderId); // 更新父文件夹ID + builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记为已本地修改 operationList.add(builder.build()); } 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()); @@ -112,9 +139,12 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取用户文件夹数量(排除系统文件夹) + * @param resolver ContentResolver实例 + * @return 用户文件夹数量 */ public static int getUserFolderCount(ContentResolver resolver) { + // 查询类型为文件夹且不在回收站中的记录数量 Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, new String[] { "COUNT(*)" }, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", @@ -125,7 +155,7 @@ public class DataUtils { if(cursor != null) { if(cursor.moveToFirst()) { try { - count = cursor.getInt(0); + count = cursor.getInt(0); // 获取计数结果 } catch (IndexOutOfBoundsException e) { Log.e(TAG, "get folder count failed:" + e.toString()); } finally { @@ -136,7 +166,15 @@ public class DataUtils { return count; } + /** + * 检查指定类型和ID的笔记是否在数据库中可见(不在回收站中) + * @param resolver ContentResolver实例 + * @param noteId 笔记ID + * @param type 笔记类型 + * @return 是否可见 + */ public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { + // 查询指定ID和类型,且不在回收站中的笔记 Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, @@ -145,7 +183,7 @@ public class DataUtils { boolean exist = false; if (cursor != null) { - if (cursor.getCount() > 0) { + if (cursor.getCount() > 0) { // 如果查询结果数量大于0,则表示存在 exist = true; } cursor.close(); @@ -153,7 +191,14 @@ public class DataUtils { return exist; } + /** + * 检查指定ID的笔记是否存在(不考虑是否在回收站中) + * @param resolver ContentResolver实例 + * @param noteId 笔记ID + * @return 是否存在 + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { + // 根据笔记ID直接查询 Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,7 +212,14 @@ public class DataUtils { return exist; } + /** + * 检查指定ID的数据项是否存在 + * @param resolver ContentResolver实例 + * @param dataId 数据项ID + * @return 是否存在 + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { + // 根据数据项ID直接查询 Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,11 +233,18 @@ public class DataUtils { return exist; } + /** + * 检查指定的文件夹名称是否已存在(在可见文件夹中,排除回收站) + * @param resolver ContentResolver实例 + * @param name 文件夹名称 + * @return 是否存在同名文件夹 + */ 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 + "=?", + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", new String[] { name }, null); boolean exist = false; if(cursor != null) { @@ -197,7 +256,14 @@ public class DataUtils { return exist; } + /** + * 获取指定文件夹中所有笔记关联的小部件属性 + * @param resolver ContentResolver实例 + * @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 }, NoteColumns.PARENT_ID + "=?", @@ -211,8 +277,8 @@ public class DataUtils { do { try { AppWidgetAttribute widget = new AppWidgetAttribute(); - widget.widgetId = c.getInt(0); - widget.widgetType = c.getInt(1); + widget.widgetId = c.getInt(0); // 小部件ID + widget.widgetType = c.getInt(1); // 小部件类型 set.add(widget); } catch (IndexOutOfBoundsException e) { Log.e(TAG, e.toString()); @@ -224,7 +290,14 @@ public class DataUtils { return set; } + /** + * 根据笔记ID获取通话记录的电话号码 + * @param resolver ContentResolver实例 + * @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 }, CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", @@ -233,7 +306,7 @@ public class DataUtils { if (cursor != null && cursor.moveToFirst()) { try { - return cursor.getString(0); + return cursor.getString(0); // 返回电话号码 } catch (IndexOutOfBoundsException e) { Log.e(TAG, "Get call number fails " + e.toString()); } finally { @@ -243,18 +316,26 @@ public class DataUtils { return ""; } + /** + * 根据电话号码和通话日期获取对应的笔记ID + * @param resolver ContentResolver实例 + * @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 }, CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", + + CallNote.PHONE_NUMBER + ",?)", new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, null); if (cursor != null) { if (cursor.moveToFirst()) { try { - return cursor.getLong(0); + return cursor.getLong(0); // 返回笔记ID } catch (IndexOutOfBoundsException e) { Log.e(TAG, "Get call note id fails " + e.toString()); } @@ -264,7 +345,15 @@ public class DataUtils { return 0; } + /** + * 根据笔记ID获取笔记摘要 + * @param resolver ContentResolver实例 + * @param noteId 笔记ID + * @return 笔记摘要 + * @throws IllegalArgumentException 如果笔记不存在 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { + // 查询指定笔记的摘要 Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, NoteColumns.ID + "=?", @@ -274,7 +363,7 @@ public class DataUtils { if (cursor != null) { String snippet = ""; if (cursor.moveToFirst()) { - snippet = cursor.getString(0); + snippet = cursor.getString(0); // 获取摘要内容 } cursor.close(); return snippet; @@ -282,14 +371,19 @@ public class DataUtils { 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; } -} +} \ No newline at end of file diff --git a/src/notes/tool/GTaskStringUtils.java b/src/notes/tool/GTaskStringUtils.java index 666b729..6e7159c 100644 --- a/src/notes/tool/GTaskStringUtils.java +++ b/src/notes/tool/GTaskStringUtils.java @@ -17,97 +17,72 @@ 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"; - - public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; - -} + // GTasks API 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(用于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"; // 用户信息 + + // 小米便签同步相关的文件夹命名前缀和常量 + 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"; // Google Task 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"; // 元数据笔记名称(用户不应修改或删除) +} \ No newline at end of file diff --git a/src/notes/tool/ResourceParser.java b/src/notes/tool/ResourceParser.java index 1ad3ad6..f86f4a8 100644 --- a/src/notes/tool/ResourceParser.java +++ b/src/notes/tool/ResourceParser.java @@ -23,150 +23,236 @@ import net.micode.notes.R; 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 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 final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; // 默认字体大小(中号) + /** + * 内部类:笔记编辑界面的背景资源 + * 用于获取不同颜色对应的编辑器和标题栏背景资源ID + */ 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 + 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 + 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 + * @param id 颜色ID(YELLOW, BLUE等) + * @return 对应的Drawable资源ID + */ public static int getNoteBgResource(int id) { return BG_EDIT_RESOURCES[id]; } + /** + * 获取笔记标题栏背景资源ID + * @param id 颜色ID(YELLOW, BLUE等) + * @return 对应的Drawable资源ID + */ public static int getNoteTitleBgResource(int id) { return BG_EDIT_TITLE_RESOURCES[id]; } } + /** + * 获取默认背景颜色ID + * 根据用户偏好设置决定使用随机颜色还是默认颜色 + * @param context 上下文对象 + * @return 颜色ID + */ public static int getDefaultBgId(Context context) { + // 检查用户是否启用了随机背景颜色功能 if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { + // 随机选择一个背景颜色 return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); } else { + // 使用默认的背景颜色(黄色) return BG_DEFAULT_COLOR; } } + /** + * 内部类:笔记列表项的背景资源 + * 用于获取列表项在不同位置(首项、中间项、末项、单一项)的背景资源ID + */ 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 + 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 + 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, + 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 + 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 // 红色列表单项背景 }; + /** + * 获取列表首项背景资源ID + * @param id 颜色ID + * @return 对应的Drawable资源ID + */ public static int getNoteBgFirstRes(int id) { return BG_FIRST_RESOURCES[id]; } + /** + * 获取列表末项背景资源ID + * @param id 颜色ID + * @return 对应的Drawable资源ID + */ public static int getNoteBgLastRes(int id) { return BG_LAST_RESOURCES[id]; } + /** + * 获取列表单项背景资源ID(当列表只有一项时使用) + * @param id 颜色ID + * @return 对应的Drawable资源ID + */ public static int getNoteBgSingleRes(int id) { return BG_SINGLE_RESOURCES[id]; } + /** + * 获取列表中间项背景资源ID + * @param id 颜色ID + * @return 对应的Drawable资源ID + */ public static int getNoteBgNormalRes(int id) { return BG_NORMAL_RESOURCES[id]; } + /** + * 获取文件夹图标的背景资源ID(文件夹使用统一的图标) + * @return 文件夹Drawable资源ID + */ public static int getFolderBgRes() { return R.drawable.list_folder; } } + /** + * 内部类:桌面小部件的背景资源 + * 用于获取不同尺寸小部件的背景资源ID + */ public static class WidgetBgResources { + // 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, + R.drawable.widget_2x_yellow, // 黄色2x小部件背景 + R.drawable.widget_2x_blue, // 蓝色2x小部件背景 + R.drawable.widget_2x_white, // 白色2x小部件背景 + R.drawable.widget_2x_green, // 绿色2x小部件背景 + R.drawable.widget_2x_red, // 红色2x小部件背景 }; + /** + * 获取2x小部件背景资源ID + * @param id 颜色ID + * @return 对应的Drawable资源ID + */ public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } + // 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 + R.drawable.widget_4x_yellow, // 黄色4x小部件背景 + R.drawable.widget_4x_blue, // 蓝色4x小部件背景 + R.drawable.widget_4x_white, // 白色4x小部件背景 + R.drawable.widget_4x_green, // 绿色4x小部件背景 + R.drawable.widget_4x_red // 红色4x小部件背景 }; + /** + * 获取4x小部件背景资源ID + * @param id 颜色ID + * @return 对应的Drawable资源ID + */ public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } + /** + * 内部类:文字外观样式资源 + * 用于获取不同字体大小对应的样式资源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 + R.style.TextAppearanceNormal, // 正常大小文字样式 + R.style.TextAppearanceMedium, // 中等大小文字样式 + R.style.TextAppearanceLarge, // 大号文字样式 + R.style.TextAppearanceSuper // 超大号文字样式 }; + /** + * 获取文字外观样式资源ID + * @param id 字体大小ID(TEXT_SMALL, TEXT_MEDIUM等) + * @return 对应的样式资源ID + * + * 注意:这里有一个修复bug的hack方法。因为在SharedPreferences中可能存储了 + * 超出资源数组范围的ID,这种情况下返回默认字体大小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} + * HACKME: 修复在SharedPreferences中存储资源ID的bug。 + * 当ID大于等于资源数组长度时,返回默认字体大小ID */ if (id >= TEXTAPPEARANCE_RESOURCES.length) { return BG_DEFAULT_FONT_SIZE; @@ -174,8 +260,12 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES[id]; } + /** + * 获取文字外观样式资源的数量 + * @return 可用的文字外观样式数量 + */ public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; } } -} +} \ No newline at end of file diff --git a/src/notes/ui/AlarmAlertActivity.java b/src/notes/ui/AlarmAlertActivity.java index 85723be..a3a56a9 100644 --- a/src/notes/ui/AlarmAlertActivity.java +++ b/src/notes/ui/AlarmAlertActivity.java @@ -39,21 +39,27 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; - +/** + * 闹钟提醒Activity - 当笔记的闹钟触发时显示提醒界面 + * 实现DialogInterface.OnClickListener和OnDismissListener接口,处理对话框事件 + */ public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; + private long mNoteId; // 触发闹钟的笔记ID + private String mSnippet; // 笔记内容摘要 + private static final int SNIPPET_PREW_MAX_LEN = 60; // 摘要最大显示长度 + MediaPlayer mPlayer; // 媒体播放器,用于播放闹钟声音 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 移除标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); + // 获取窗口并设置标志,使Activity在锁屏状态下也能显示 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 @@ -61,98 +67,143 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); } + // 从Intent中获取笔记ID和内容摘要 Intent intent = getIntent(); try { + // 从Intent的URI中解析出笔记ID(URI格式:content://xxx/note/笔记ID) mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 通过笔记ID从数据库获取笔记摘要 mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + // 如果摘要过长,截取前60个字符并添加省略号 mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) : mSnippet; } catch (IllegalArgumentException e) { + // 如果解析失败,打印异常并返回(Activity将结束) e.printStackTrace(); return; } + // 初始化媒体播放器 mPlayer = new MediaPlayer(); + // 检查笔记是否仍然存在且可见(未被删除或移到回收站) if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); + showActionDialog(); // 显示提醒对话框 + playAlarmSound(); // 播放闹钟声音 } else { + // 如果笔记已不存在,直接结束Activity finish(); } } + /** + * 检查屏幕是否亮着 + * @return true表示屏幕亮着,false表示屏幕关闭 + */ private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + /** + * 播放闹钟声音 + * 使用系统默认的闹钟铃声 + */ private void playAlarmSound() { + // 获取系统默认的闹钟铃声URI Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + // 获取当前静音模式影响的音频流设置 int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + // 如果静音模式影响闹钟音频流,则使用静音模式的设置 if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { mPlayer.setAudioStreamType(silentModeStreams); } else { + // 否则使用闹钟音频流 mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); } + try { + // 设置数据源并准备播放 mPlayer.setDataSource(this, url); mPlayer.prepare(); - mPlayer.setLooping(true); - mPlayer.start(); + mPlayer.setLooping(true); // 设置为循环播放 + mPlayer.start(); // 开始播放 } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block + // TODO: 异常处理,自动生成的catch块 e.printStackTrace(); } catch (SecurityException e) { - // TODO Auto-generated catch block + // TODO: 异常处理,自动生成的catch块 e.printStackTrace(); } catch (IllegalStateException e) { - // TODO Auto-generated catch block + // TODO: 异常处理,自动生成的catch块 e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block + // TODO: 异常处理,自动生成的catch块 e.printStackTrace(); } } + /** + * 显示操作对话框,包含笔记摘要和操作按钮 + */ private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(R.string.app_name); - dialog.setMessage(mSnippet); + dialog.setTitle(R.string.app_name); // 设置对话框标题为应用名称 + dialog.setMessage(mSnippet); // 设置对话框内容为笔记摘要 + + // 确定按钮 - 关闭提醒 dialog.setPositiveButton(R.string.notealert_ok, this); + + // 如果屏幕已亮,显示"进入"按钮,用于打开笔记编辑页面 if (isScreenOn()) { dialog.setNegativeButton(R.string.notealert_enter, this); } + + // 显示对话框并设置关闭监听器 dialog.show().setOnDismissListener(this); } + /** + * 对话框按钮点击事件处理 + * @param dialog 触发事件的对话框 + * @param which 被点击的按钮标识 + */ 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); + intent.putExtra(Intent.EXTRA_UID, mNoteId); // 传递笔记ID startActivity(intent); break; default: + // "确定"按钮或其他按钮:不做特殊处理,对话框会自动关闭 break; } } + /** + * 对话框关闭事件处理 + * @param dialog 被关闭的对话框 + */ public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); + stopAlarmSound(); // 停止播放闹钟声音 + finish(); // 结束Activity } + /** + * 停止播放闹钟声音并释放资源 + */ private void stopAlarmSound() { if (mPlayer != null) { - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; + mPlayer.stop(); // 停止播放 + mPlayer.release(); // 释放媒体播放器资源 + mPlayer = null; // 置空引用 } } -} +} \ No newline at end of file diff --git a/src/notes/ui/AlarmInitReceiver.java b/src/notes/ui/AlarmInitReceiver.java index f221202..07d348f 100644 --- a/src/notes/ui/AlarmInitReceiver.java +++ b/src/notes/ui/AlarmInitReceiver.java @@ -27,39 +27,64 @@ import android.database.Cursor; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 闹钟初始化接收器 - 在设备启动或应用安装后重新设置所有未触发的闹钟 + * 这是一个BroadcastReceiver,通常响应系统启动完成的广播 + */ public class AlarmInitReceiver extends BroadcastReceiver { - + // 数据库查询字段投影,只需要笔记ID和提醒时间 private static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE + NoteColumns.ID, // 笔记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; // 笔记ID在查询结果中的列索引 + private static final int COLUMN_ALERTED_DATE = 1; // 提醒时间在查询结果中的列索引 @Override public void onReceive(Context context, Intent intent) { - long currentDate = System.currentTimeMillis(); + long currentDate = System.currentTimeMillis(); // 获取当前系统时间 + + // 查询所有未过期的闹钟笔记: + // 1. 提醒时间大于当前时间(ALERTED_DATE > ?) + // 2. 笔记类型为普通笔记(TYPE = Notes.TYPE_NOTE) 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); + + // 创建指向AlarmReceiver的Intent,用于在闹钟时间触发时发送广播 Intent sender = new Intent(context, AlarmReceiver.class); + // 将笔记URI附加到Intent中,这样AlarmReceiver就能知道是哪个笔记的闹钟 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); + + // 设置闹钟: + // 1. 使用RTC_WAKEUP类型,即使设备休眠也会唤醒CPU并发送PendingIntent + // 2. 在alertDate时间触发 + // 3. 触发pendingIntent,即发送广播给AlarmReceiver alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); - } while (c.moveToNext()); + + } while (c.moveToNext()); // 遍历所有查询结果 } - c.close(); + c.close(); // 关闭Cursor释放资源 } + + // 注意:这个方法没有处理重复闹钟的情况 + // 当设备重启后,所有之前设置的AlarmManager闹钟都会被清除 + // 所以需要这个Receiver来重新设置所有未触发的闹钟 } -} +} \ No newline at end of file diff --git a/src/notes/ui/AlarmReceiver.java b/src/notes/ui/AlarmReceiver.java index 54e503b..93bd92e 100644 --- a/src/notes/ui/AlarmReceiver.java +++ b/src/notes/ui/AlarmReceiver.java @@ -20,11 +20,28 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +/** + * 闹钟接收器 - 当AlarmManager设置的闹钟时间到达时,接收系统广播 + * 这是闹钟系统的中间组件,负责将系统广播转发到AlarmAlertActivity + */ public class AlarmReceiver extends BroadcastReceiver { + @Override public void onReceive(Context context, Intent intent) { + // 修改Intent的目标类为AlarmAlertActivity + // 这样当闹钟触发时,会启动AlarmAlertActivity显示提醒界面 intent.setClass(context, AlarmAlertActivity.class); + + // 添加FLAG_ACTIVITY_NEW_TASK标志 + // 因为BroadcastReceiver不是Activity上下文,需要新任务栈启动Activity intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // 启动AlarmAlertActivity context.startActivity(intent); + + // 注意:这个Intent中已经包含了笔记的URI数据 + // 这个URI是在AlarmInitReceiver中设置的: + // sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))) + // 所以AlarmAlertActivity可以通过getIntent().getData()获取到笔记信息 } -} +} \ No newline at end of file diff --git a/src/notes/ui/DateTimePicker.java b/src/notes/ui/DateTimePicker.java index 496b0cd..49856df 100644 --- a/src/notes/ui/DateTimePicker.java +++ b/src/notes/ui/DateTimePicker.java @@ -21,92 +21,121 @@ import java.util.Calendar; import net.micode.notes.R; - import android.content.Context; import android.text.format.DateFormat; import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +/** + * 自定义日期时间选择器控件 + * 继承自FrameLayout,包含日期、小时、分钟和AM/PM四个选择器 + */ public class DateTimePicker extends FrameLayout { + // 常量定义 + private static final boolean DEFAULT_ENABLE_STATE = true; // 默认启用状态 - private static final boolean DEFAULT_ENABLE_STATE = true; + // 时间相关常量 + private static final int HOURS_IN_HALF_DAY = 12; // 半天的小时数(12小时制) + private static final int HOURS_IN_ALL_DAY = 24; // 全天的小时数(24小时制) + private static final int DAYS_IN_ALL_WEEK = 7; // 一周的天数(用于显示一周的日期) - 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; + // NumberPicker的最小最大值常量 + 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; 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; 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; + + // AM/PM选择器范围 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; + // 控件引用 + private final NumberPicker mDateSpinner; // 日期选择器 + private final NumberPicker mHourSpinner; // 小时选择器 + private final NumberPicker mMinuteSpinner; // 分钟选择器 + private final NumberPicker mAmPmSpinner; // AM/PM选择器 - private boolean mIs24HourView; + private Calendar mDate; // 当前选择的日期时间 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 日期显示值数组 - private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + private boolean mIsAm; // 是否为上午(true=上午,false=下午) + private boolean mIs24HourView; // 是否为24小时制 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 控件是否启用 + private boolean mInitialising; // 是否正在初始化 - private boolean mInitialising; - - private OnDateTimeChangedListener mOnDateTimeChangedListener; + 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(); + updateDateControl(); // 更新日期控件显示 + onDateTimeChanged(); // 触发日期时间变化回调 } }; + // 小时变化监听器(处理复杂的时间逻辑,包括12/24小时制转换和日期跨天) private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - boolean isDateChanged = false; + boolean isDateChanged = false; // 标记日期是否发生变化 Calendar cal = Calendar.getInstance(); + + // 12小时制下的特殊处理 if (!mIs24HourView) { + // 处理从11PM到12AM或12AM到11PM的日期变化 if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + // 从下午11点切换到12点,日期加1天 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + // 从上午12点切换到11点,日期减1天 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } + + // 处理AM/PM切换(当在11和12之间切换时) if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { - mIsAm = !mIsAm; - updateAmPmControl(); + mIsAm = !mIsAm; // 切换AM/PM状态 + updateAmPmControl(); // 更新AM/PM控件 } } else { + // 24小时制下的特殊处理 if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + // 从23点切换到0点,日期加1天 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + // 从0点切换到23点,日期减1天 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } } + + // 计算新的小时(考虑12小时制和AM/PM) int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); mDate.set(Calendar.HOUR_OF_DAY, newHour); - onDateTimeChanged(); + onDateTimeChanged(); // 触发日期时间变化回调 + + // 如果日期发生变化,更新年、月、日 if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); setCurrentMonth(cal.get(Calendar.MONTH)); @@ -115,21 +144,30 @@ public class DateTimePicker extends FrameLayout { } }; + // 分钟变化监听器(处理分钟滚动到边界时的日期变化) 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; + int offset = 0; // 小时偏移量 + + // 处理分钟滚动到边界的情况 if (oldVal == maxValue && newVal == minValue) { + // 从59分滚动到0分,小时加1 offset += 1; } else if (oldVal == minValue && newVal == maxValue) { + // 从0分滚动到59分,小时减1 offset -= 1; } + + // 如果需要调整小时 if (offset != 0) { - mDate.add(Calendar.HOUR_OF_DAY, offset); - mHourSpinner.setValue(getCurrentHour()); - updateDateControl(); + mDate.add(Calendar.HOUR_OF_DAY, offset); // 调整小时 + mHourSpinner.setValue(getCurrentHour()); // 更新小时选择器 + updateDateControl(); // 更新日期控件 + + // 检查并更新AM/PM状态 int newHour = getCurrentHourOfDay(); if (newHour >= HOURS_IN_HALF_DAY) { mIsAm = false; @@ -139,78 +177,110 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } } - mDate.set(Calendar.MINUTE, newVal); - onDateTimeChanged(); + + mDate.set(Calendar.MINUTE, newVal); // 设置新的分钟值 + onDateTimeChanged(); // 触发日期时间变化回调 } }; + // AM/PM变化监听器 private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mIsAm = !mIsAm; + mIsAm = !mIsAm; // 切换AM/PM状态 + // 根据AM/PM状态调整小时 if (mIsAm) { - mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); // 从PM切换到AM,减12小时 } else { - mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // 从AM切换到PM,加12小时 } - updateAmPmControl(); - onDateTimeChanged(); + updateAmPmControl(); // 更新AM/PM控件 + onDateTimeChanged(); // 触发日期时间变化回调 } }; + /** + * 日期时间变化监听器接口 + */ public interface OnDateTimeChangedListener { void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute); + int dayOfMonth, int hourOfDay, int minute); } + /** + * 构造函数1:使用当前时间 + * @param context 上下文 + */ public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + /** + * 构造函数2:使用指定时间和系统默认的24小时制设置 + * @param context 上下文 + * @param date 初始日期时间(毫秒时间戳) + */ public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + /** + * 构造函数3:使用指定时间和指定的24小时制设置 + * @param context 上下文 + * @param date 初始日期时间(毫秒时间戳) + * @param is24HourView 是否为24小时制 + */ public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); - mDate = Calendar.getInstance(); - mInitialising = true; + mDate = Calendar.getInstance(); // 初始化日历对象 + mInitialising = true; // 标记为初始化阶段 + + // 根据当前小时确定AM/PM状态 mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + + // 加载布局 inflate(context, R.layout.datetime_picker, this); + // 初始化日期选择器 mDateSpinner = (NumberPicker) findViewById(R.id.date); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + // 初始化小时选择器 mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + + // 初始化分钟选择器 mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); - mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnLongPressUpdateInterval(100); // 设置长按更新间隔为100毫秒 mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); - String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + // 初始化AM/PM选择器 + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); // 获取AM/PM本地化字符串 mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); - mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 设置显示值 mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新控件到初始状态 updateDateControl(); updateHourControl(); updateAmPmControl(); + // 设置24小时制视图 set24HourView(is24HourView); - // set to current time + // 设置为当前时间 setCurrentDate(date); + // 设置启用状态 setEnabled(isEnabled()); - // set the content descriptions + // 初始化完成 mInitialising = false; } @@ -220,6 +290,7 @@ public class DateTimePicker extends FrameLayout { return; } super.setEnabled(enabled); + // 设置所有子控件的启用状态 mDateSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled); mHourSpinner.setEnabled(enabled); @@ -233,18 +304,16 @@ public class DateTimePicker extends FrameLayout { } /** - * 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 + * 设置当前日期时间(毫秒时间戳) + * @param date 日期时间的毫秒时间戳 */ public void setCurrentDate(long date) { Calendar cal = Calendar.getInstance(); @@ -254,16 +323,15 @@ public class DateTimePicker extends FrameLayout { } /** - * 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 小时(24小时制) + * @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); @@ -272,214 +340,239 @@ public class DateTimePicker extends FrameLayout { } /** - * 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; + 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-11) */ public int getCurrentMonth() { return mDate.get(Calendar.MONTH); } /** - * Set current month in the year - * - * @param month The month in the year + * 设置当前月 + * @param month 月(0-11) */ public void setCurrentMonth(int month) { if (!mInitialising && month == getCurrentMonth()) { - return; + 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; + 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小时制) + * @return 当前小时(0-23) */ public int getCurrentHourOfDay() { return mDate.get(Calendar.HOUR_OF_DAY); } + /** + * 获取当前小时(根据当前视图模式:12或24小时制) + * @return 当前小时(12小时制:1-12,24小时制:0-23) + */ private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); } else { int hour = getCurrentHourOfDay(); if (hour > HOURS_IN_HALF_DAY) { - return hour - HOURS_IN_HALF_DAY; + return hour - HOURS_IN_HALF_DAY; // 下午:13-23转换为1-11 } else { - return hour == 0 ? HOURS_IN_HALF_DAY : hour; + return hour == 0 ? HOURS_IN_HALF_DAY : hour; // 0点转换为12点,1-11点不变 } } } /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay + * 设置当前小时(24小时制) + * @param hourOfDay 小时(0-23) */ public void setCurrentHour(int hourOfDay) { if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { - return; + return; // 如果不是初始化且值未变化,直接返回 } mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + + // 12小时制下的特殊处理 if (!mIs24HourView) { if (hourOfDay >= HOURS_IN_HALF_DAY) { - mIsAm = false; + mIsAm = false; // 下午 if (hourOfDay > HOURS_IN_HALF_DAY) { - hourOfDay -= HOURS_IN_HALF_DAY; + hourOfDay -= HOURS_IN_HALF_DAY; // 13-23转换为1-11 } } else { - mIsAm = true; + mIsAm = true; // 上午 if (hourOfDay == 0) { - hourOfDay = HOURS_IN_HALF_DAY; + hourOfDay = HOURS_IN_HALF_DAY; // 0点转换为12点 } } - updateAmPmControl(); + updateAmPmControl(); // 更新AM/PM控件 } - mHourSpinner.setValue(hourOfDay); - onDateTimeChanged(); + mHourSpinner.setValue(hourOfDay); // 设置小时选择器值 + onDateTimeChanged(); // 触发日期时间变化回调 } /** - * 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; + return; // 如果不是初始化且值未变化,直接返回 } - mMinuteSpinner.setValue(minute); + mMinuteSpinner.setValue(minute); // 设置分钟选择器值 mDate.set(Calendar.MINUTE, minute); - onDateTimeChanged(); + onDateTimeChanged(); // 触发日期时间变化回调 } /** - * @return true if this is in 24 hour view else false. + * 检查是否为24小时制 + * @return true表示24小时制,false表示12小时制 */ public boolean is24HourView () { return mIs24HourView; } /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. + * 设置24小时制或AM/PM模式 + * @param is24HourView true为24小时制,false为AM/PM模式 */ public void set24HourView(boolean is24HourView) { if (mIs24HourView == is24HourView) { - return; + return; // 如果模式未变化,直接返回 } mIs24HourView = is24HourView; + + // 设置AM/PM选择器的可见性 mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); - updateHourControl(); - setCurrentHour(hour); - updateAmPmControl(); + updateHourControl(); // 更新小时选择器范围 + setCurrentHour(hour); // 重新设置小时(考虑模式转换) + updateAmPmControl(); // 更新AM/PM控件 } + /** + * 更新日期控件显示 + * 显示以当前日期为中心的一周日期 + */ private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); + + // 向前调整3天,以便以当前日期为中心显示一周 cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); - mDateSpinner.setDisplayedValues(null); + + mDateSpinner.setDisplayedValues(null); // 清除旧值 + + // 生成一周的日期显示值 for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { cal.add(Calendar.DAY_OF_YEAR, 1); + // 格式化为"MM.dd EEEE"(月.日 星期) mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); } - mDateSpinner.setDisplayedValues(mDateDisplayValues); - mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); - mDateSpinner.invalidate(); + + mDateSpinner.setDisplayedValues(mDateDisplayValues); // 设置显示值 + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置当前选中位置为中间(当前日期) + mDateSpinner.invalidate(); // 刷新控件 } + /** + * 更新AM/PM控件 + */ private void updateAmPmControl() { if (mIs24HourView) { - mAmPmSpinner.setVisibility(View.GONE); + mAmPmSpinner.setVisibility(View.GONE); // 24小时制下隐藏AM/PM选择器 } else { int index = mIsAm ? Calendar.AM : Calendar.PM; - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); + mAmPmSpinner.setValue(index); // 设置AM/PM值 + mAmPmSpinner.setVisibility(View.VISIBLE); // 显示AM/PM选择器 } } + /** + * 更新小时控件范围 + */ private void updateHourControl() { if (mIs24HourView) { + // 24小时制范围:0-23 mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); } else { + // 12小时制范围:1-12 mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); } } /** - * 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(), getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); } } -} +} \ No newline at end of file diff --git a/src/notes/ui/DateTimePickerDialog.java b/src/notes/ui/DateTimePickerDialog.java index 2c47ba4..9fd2ef5 100644 --- a/src/notes/ui/DateTimePickerDialog.java +++ b/src/notes/ui/DateTimePickerDialog.java @@ -29,62 +29,113 @@ import android.content.DialogInterface.OnClickListener; import android.text.format.DateFormat; import android.text.format.DateUtils; +/** + * 日期时间选择对话框 + * 封装DateTimePicker控件,提供完整的日期时间选择界面 + */ public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + private Calendar mDate = Calendar.getInstance(); // 存储当前选择的日期时间 + private boolean mIs24HourView; // 是否为24小时制 + private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置监听器 + private DateTimePicker mDateTimePicker; // 日期时间选择器控件 - private Calendar mDate = Calendar.getInstance(); - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; - + /** + * 日期时间设置监听器接口 + * 当用户点击确定按钮时回调 + */ public interface OnDateTimeSetListener { void OnDateTimeSet(AlertDialog dialog, long date); } + /** + * 构造函数 + * @param context 上下文 + * @param date 初始日期时间(毫秒时间戳) + */ public DateTimePickerDialog(Context context, long date) { super(context); + + // 创建日期时间选择器控件 mDateTimePicker = new DateTimePicker(context); - setView(mDateTimePicker); + 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) { + // 更新内部Calendar对象的各个时间分量 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); + mDate.set(Calendar.SECOND, 0); // 将秒设为0,只精确到分钟 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); // 更新控件显示 + + // 设置对话框按钮 + setButton(context.getString(R.string.datetime_dialog_ok), this); // 确定按钮 + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); // 取消按钮 + + // 根据系统设置决定是否为24小时制 set24HourView(DateFormat.is24HourFormat(this.getContext())); + + // 更新对话框标题 updateTitle(mDate.getTimeInMillis()); } + /** + * 设置24小时制视图 + * @param is24HourView true为24小时制,false为12小时制 + */ public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + /** + * 设置日期时间设置监听器 + * @param callBack 监听器 + */ public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + /** + * 更新对话框标题为当前选择的日期时间 + * @param date 日期时间(毫秒时间戳) + */ private void updateTitle(long date) { + // 设置日期时间显示格式标志 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; // 显示时间 + + // 根据是否24小时制添加相应的标志 + // 注意:这里有代码问题,无论mIs24HourView为何值,都使用FORMAT_24HOUR + // 应该是:mIs24HourView ? DateUtils.FORMAT_24HOUR : 0 flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + + // 使用DateUtils格式化日期时间为字符串,并设置为对话框标题 setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + /** + * 对话框按钮点击事件处理 + * @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/src/notes/ui/DropdownMenu.java b/src/notes/ui/DropdownMenu.java index 613dc74..88a6167 100644 --- a/src/notes/ui/DropdownMenu.java +++ b/src/notes/ui/DropdownMenu.java @@ -27,17 +27,34 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +/** + * 下拉菜单控件封装类 + * 将Button与PopupMenu结合,提供下拉菜单功能 + */ public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; + private Button mButton; // 触发下拉菜单的按钮 + private PopupMenu mPopupMenu; // 弹出式菜单 + private Menu mMenu; // 菜单项容器 + /** + * 构造函数 + * @param context 上下文 + * @param button 用于触发下拉菜单的按钮 + * @param menuId 菜单资源ID(R.menu.xxx) + */ public DropdownMenu(Context context, Button button, int menuId) { mButton = button; + // 设置按钮背景为下拉箭头图标 mButton.setBackgroundResource(R.drawable.dropdown_icon); + + // 创建PopupMenu,以按钮为锚点 mPopupMenu = new PopupMenu(context, mButton); + // 获取PopupMenu的Menu对象 mMenu = mPopupMenu.getMenu(); + // 从资源文件加载菜单项 mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + + // 设置按钮点击监听器,点击时显示下拉菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,17 +62,30 @@ public class DropdownMenu { }); } + /** + * 设置菜单项点击监听器 + * @param listener 菜单项点击监听器 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 根据ID查找菜单项 + * @param id 菜单项ID + * @return 对应的MenuItem对象 + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置按钮显示的文本 + * @param title 按钮文本 + */ public void setTitle(CharSequence title) { mButton.setText(title); } -} +} \ No newline at end of file diff --git a/src/notes/ui/FoldersListAdapter.java b/src/notes/ui/FoldersListAdapter.java index 96b77da..fcab924 100644 --- a/src/notes/ui/FoldersListAdapter.java +++ b/src/notes/ui/FoldersListAdapter.java @@ -28,53 +28,109 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 文件夹列表适配器 + * 继承自CursorAdapter,用于在列表视图中显示文件夹数据 + */ public class FoldersListAdapter extends CursorAdapter { + // 数据库查询字段投影:只需要ID和名称 public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET + NoteColumns.ID, // 文件夹ID + NoteColumns.SNIPPET // 文件夹名称(存储在SNIPPET字段中) }; - public static final int ID_COLUMN = 0; - public static final int NAME_COLUMN = 1; + // 列索引常量定义 + public static final int ID_COLUMN = 0; // ID列索引 + public static final int NAME_COLUMN = 1; // 名称列索引 + /** + * 构造函数 + * @param context 上下文 + * @param c 包含文件夹数据的Cursor + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); - // TODO Auto-generated constructor stub + // TODO: 自动生成的构造函数存根 } + /** + * 创建新的列表项视图 + * @param context 上下文 + * @param cursor 数据Cursor + * @param parent 父视图组 + * @return 新创建的FolderListItem视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 绑定数据到现有视图 + * @param view 要绑定数据的视图 + * @param context 上下文 + * @param cursor 数据Cursor + */ @Override public void bindView(View view, Context context, Cursor cursor) { + // 检查视图类型是否为FolderListItem if (view instanceof FolderListItem) { - String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context - .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + String folderName; + + // 特殊处理根文件夹:如果ID是根文件夹ID,则显示特定的父文件夹名称 + if (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) { + folderName = context.getString(R.string.menu_move_parent_folder); + } else { + folderName = cursor.getString(NAME_COLUMN); + } + + // 调用FolderListItem的bind方法设置文件夹名称 ((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); + Cursor cursor = (Cursor) getItem(position); // 获取对应位置的Cursor数据 + + // 特殊处理根文件夹:如果ID是根文件夹ID,则返回特定的父文件夹名称 + if (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) { + return context.getString(R.string.menu_move_parent_folder); + } else { + return cursor.getString(NAME_COLUMN); + } } + /** + * 内部类:文件夹列表项视图 + * 继承自LinearLayout,表示单个文件夹列表项 + */ private class FolderListItem extends LinearLayout { - private TextView mName; + private TextView mName; // 显示文件夹名称的TextView + /** + * 构造函数 + * @param context 上下文 + */ public FolderListItem(Context context) { super(context); + // 加载布局文件 inflate(context, R.layout.folder_list_item, this); + // 初始化TextView mName = (TextView) findViewById(R.id.tv_folder_name); } + /** + * 绑定文件夹名称到视图 + * @param name 文件夹名称 + */ public void bind(String name) { mName.setText(name); } } - -} +} \ No newline at end of file diff --git a/src/notes/ui/NoteEditActivity.java b/src/notes/ui/NoteEditActivity.java index 96a9ff8..d2c00a0 100644 --- a/src/notes/ui/NoteEditActivity.java +++ b/src/notes/ui/NoteEditActivity.java @@ -71,28 +71,35 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - +/** + * 笔记编辑Activity - 小米便签的核心编辑界面 + * 实现OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener接口 + * 处理笔记的创建、编辑、保存、删除等所有操作 + */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { - private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - public TextView tvAlertDate; - - public ImageView ibSetBgColor; + /** + * 头部视图持有者,用于缓存头部视图的控件引用 + */ + private class HeadViewHolder { + public TextView tvModified; // 显示修改时间的TextView + public ImageView ivAlertIcon; // 闹钟提醒图标 + public TextView tvAlertDate; // 闹钟提醒时间 + public ImageView ibSetBgColor; // 设置背景颜色的按钮 } + // 背景颜色选择器按钮ID到颜色ID的映射 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); - sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); - sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); + 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); // 蓝色背景 + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); // 绿色背景 + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); // 白色背景 } + // 背景颜色ID到选中状态图标ID的映射 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,14 +109,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 字体大小选择器布局ID到字体大小ID的映射 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); + 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); // 超大字体 } + // 字体大小ID到选中状态图标ID的映射 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -118,56 +127,49 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } - private static final String TAG = "NoteEditActivity"; - - private HeadViewHolder mNoteHeaderHolder; - - private View mHeadViewPanel; - - private View mNoteBgColorSelector; - - private View mFontSizeSelector; - - private EditText mNoteEditor; - - private View mNoteEditorPanel; - - private WorkingNote mWorkingNote; + private static final String TAG = "NoteEditActivity"; // 日志标签 - private SharedPreferences mSharedPrefs; - private int mFontSizeId; + private HeadViewHolder mNoteHeaderHolder; // 头部视图持有者 + private View mHeadViewPanel; // 头部视图面板 + private View mNoteBgColorSelector; // 背景颜色选择器 + private View mFontSizeSelector; // 字体大小选择器 + private EditText mNoteEditor; // 笔记编辑框(普通模式) + private View mNoteEditorPanel; // 编辑面板 + private WorkingNote mWorkingNote; // 当前正在编辑的笔记对象 + private SharedPreferences mSharedPrefs; // 共享首选项 + 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; - - private String mUserQuery; - private Pattern mPattern; + private LinearLayout mEditTextList; // 列表模式下的编辑容器 + private String mUserQuery; // 用户搜索查询(用于高亮显示) + private Pattern mPattern; // 用于高亮搜索结果的模式 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.setContentView(R.layout.note_edit); + this.setContentView(R.layout.note_edit); // 设置布局文件 + // 初始化Activity状态,如果初始化失败则结束Activity if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } - initResources(); + initResources(); // 初始化资源 } /** - * 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 + * 当内存不足导致Activity被杀死后恢复状态 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); + // 从保存的状态中恢复笔记ID并重新初始化Activity状态 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)); @@ -179,31 +181,35 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 初始化Activity状态 + * @param intent 启动Activity的Intent + * @return 初始化是否成功 + */ 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); } + // 检查笔记是否存在 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); @@ -211,11 +217,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } } + // 隐藏软键盘 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 + } + // 处理新建或编辑笔记的请求 + 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); @@ -224,7 +233,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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) { @@ -232,6 +241,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); @@ -241,15 +251,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); @@ -258,6 +271,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } + + // 设置笔记设置变化监听器 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } @@ -265,42 +280,54 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onResume() { super.onResume(); - initNoteScreen(); + initNoteScreen(); // 初始化笔记界面 } + /** + * 初始化笔记界面 + */ private void initNoteScreen() { + // 设置字体大小 mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + + // 根据笔记模式设置编辑界面 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - switchToListMode(mWorkingNote.getContent()); + switchToListMode(mWorkingNote.getContent()); // 切换到列表模式 } else { mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); + 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()); + // 设置修改时间 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(); } + /** + * 显示闹钟提醒头部 + */ private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); if (time > mWorkingNote.getAlertDate()) { - mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); // 已过期 } else { + // 显示相对时间(如"5分钟后") mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } @@ -315,32 +342,36 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - initActivityState(intent); + initActivityState(intent); // 处理新的Intent } @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 + * 对于没有ID的新笔记,首先保存它以生成ID + * 如果编辑的笔记不值得保存,则没有ID,相当于创建新笔记 */ if (!mWorkingNote.existInDatabase()) { saveNote(); } - outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 保存笔记ID 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); @@ -349,6 +380,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, return super.dispatchTouchEvent(ev); } + /** + * 判断触摸事件是否在指定视图范围内 + */ private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); @@ -358,11 +392,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, || ev.getX() > (x + view.getWidth()) || ev.getY() < y || ev.getY() > (y + view.getHeight())) { - return false; - } + return false; + } return true; } + /** + * 初始化资源,获取布局中的控件引用 + */ private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); @@ -374,40 +411,52 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteEditor = (EditText) findViewById(R.id.note_edit_view); mNoteEditorPanel = findViewById(R.id.sv_note_edit); mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + + // 设置背景颜色选择器按钮的点击监听器 for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } mFontSizeSelector = findViewById(R.id.font_size_selector); + // 设置字体大小选择器按钮的点击监听器 for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); }; + + // 获取字体大小偏好设置 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} + * HACKME: 修复在SharedPreferences中存储资源ID的bug + * 当ID大于等于资源数组长度时,返回默认字体大小ID */ if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } @Override protected void onPause() { super.onPause(); + // 保存笔记 if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } - clearSettingState(); + clearSettingState(); // 清除设置状态 } + /** + * 更新桌面小部件 + */ 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) { @@ -417,30 +466,46 @@ public class NoteEditActivity extends Activity implements OnClickListener, return; } + // 设置要更新的小部件ID intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - mWorkingNote.getWidgetId() + mWorkingNote.getWidgetId() }); - sendBroadcast(intent); + sendBroadcast(intent); // 发送广播更新小部件 setResult(RESULT_OK, intent); } + /** + * 点击事件处理 + */ 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)) { + 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)) { + } + // 字体大小选择按钮 + 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()); @@ -454,14 +519,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override public void onBackPressed() { - if(clearSettingState()) { + if(clearSettingState()) { // 如果正在显示设置界面,则关闭设置界面 return; } - saveNote(); - super.onBackPressed(); + saveNote(); // 保存笔记 + super.onBackPressed(); // 执行默认返回操作 } + /** + * 清除设置状态(关闭背景颜色或字体大小选择器) + * @return 是否清除了设置状态 + */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); @@ -473,45 +542,65 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + /** + * 背景颜色变化回调 + */ public void onBackgroundColorChanged() { + // 显示新的背景颜色选中状态 findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); + // 更新编辑面板和标题面板的背景颜色 mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + /** + * 准备选项菜单 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { return true; } - clearSettingState(); - menu.clear(); + clearSettingState(); // 清除设置状态 + menu.clear(); // 清除旧菜单 + + // 根据笔记类型加载不同的菜单 if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_note_edit, menu); + getMenuInflater().inflate(R.menu.call_note_edit, menu); // 通话记录笔记菜单 } else { - getMenuInflater().inflate(R.menu.note_edit, menu); + getMenuInflater().inflate(R.menu.note_edit, menu); // 普通笔记菜单 } + + // 设置列表模式/普通模式菜单项标题 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); + menu.findItem(R.id.menu_alert).setVisible(false); // 隐藏设置提醒 + menu.findItem(R.id.menu_delete_remind).setVisible(true); // 显示删除提醒 } else { - menu.findItem(R.id.menu_delete_remind).setVisible(false); + menu.findItem(R.id.menu_alert).setVisible(true); // 显示设置提醒 + menu.findItem(R.id.menu_delete_remind).setVisible(false); // 隐藏删除提醒 } return true; } + /** + * 菜单项选择处理 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_note: - createNewNote(); + 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); @@ -519,33 +608,34 @@ public class NoteEditActivity extends Activity implements OnClickListener, builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - deleteCurrentNote(); - finish(); + deleteCurrentNote(); // 删除当前笔记 + finish(); // 结束Activity } }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case R.id.menu_font_size: - mFontSizeSelector.setVisibility(View.VISIBLE); + 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()); + getWorkingText(); // 获取工作文本 + sendTo(this, mWorkingNote.getContent()); // 分享笔记 break; case R.id.menu_send_to_desktop: - sendToDesktop(); + sendToDesktop(); // 发送到桌面 break; case R.id.menu_alert: - setReminder(); + setReminder(); // 设置提醒 break; case R.id.menu_delete_remind: - mWorkingNote.setAlertDate(0, false); + mWorkingNote.setAlertDate(0, false); // 删除提醒 break; default: break; @@ -553,19 +643,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); + mWorkingNote.setAlertDate(date , true); // 设置提醒时间 } }); - d.show(); + d.show(); // 显示日期时间选择器 } /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type + * 分享笔记到支持ACTION_SEND的应用 */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); @@ -574,11 +666,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + /** + * 创建新笔记 + */ private void createNewNote() { - // Firstly, save current editing notes + // 首先保存当前编辑的笔记 saveNote(); - // For safety, start a new NoteEditActivity + // 启动一个新的NoteEditActivity finish(); Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -586,6 +681,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + /** + * 删除当前笔记 + */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); @@ -595,27 +693,36 @@ public class NoteEditActivity extends Activity implements OnClickListener, } 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); + mWorkingNote.markDeleted(true); // 标记为已删除 } + /** + * 检查是否启用同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 闹钟提醒变化回调 + */ 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(); @@ -625,40 +732,49 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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(); + showAlertHeader(); // 更新提醒头部显示 + if(!set) { - alarmManager.cancel(pendingIntent); + alarmManager.cancel(pendingIntent); // 取消闹钟 } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); // 设置闹钟 } } 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 + * 用户可能没有输入任何内容(笔记不值得保存),我们没有笔记ID, + * 提醒用户需要输入一些内容 */ Log.e(TAG, "Clock alert setting error"); showToast(R.string.error_note_empty_for_clock); } } + /** + * 小部件变化回调 + */ public void onWidgetChanged() { - updateWidget(); + updateWidget(); // 更新小部件 } + /** + * 列表模式中编辑文本被删除的回调 + */ public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); - if (childCount == 1) { + if (childCount == 1) { // 如果只有一个项目,则不删除 return; } + // 更新后面项目的索引 for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i - 1); } - mEditTextList.removeViewAt(index); + mEditTextList.removeViewAt(index); // 移除视图 NoteEditText edit = null; + + // 获取相邻的编辑框并追加被删除的文本 if(index == 0) { edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( R.id.et_edit_text); @@ -667,32 +783,43 @@ public class NoteEditActivity extends Activity implements OnClickListener, R.id.et_edit_text); } int length = edit.length(); - edit.append(text); - edit.requestFocus(); - edit.setSelection(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"); } + // 创建新的列表项并插入到指定位置 View view = getListItem(text, index); mEditTextList.addView(view, index); NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.requestFocus(); - edit.setSelection(0); + edit.requestFocus(); // 请求焦点 + edit.setSelection(0); // 光标移动到开头 + + // 更新后面项目的索引 for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i); } } + /** + * 切换到列表模式 + */ private void switchToListMode(String text) { - mEditTextList.removeAllViews(); + mEditTextList.removeAllViews(); // 清除所有子视图 + + // 按换行符分割文本并创建列表项 String[] items = text.split("\n"); int index = 0; for (String item : items) { @@ -701,19 +828,25 @@ public class NoteEditActivity extends Activity implements OnClickListener, index++; } } + // 添加一个空列表项用于输入 mEditTextList.addView(getListItem("", index)); mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + // 切换视图显示 mNoteEditor.setVisibility(View.GONE); mEditTextList.setVisibility(View.VISIBLE); } + /** + * 获取高亮搜索结果的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( @@ -725,13 +858,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, return spannable; } + /** + * 获取列表项视图 + */ 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 { @@ -740,27 +880,33 @@ public class NoteEditActivity extends Activity implements OnClickListener, } }); + // 解析文本中的待办事项标记 if (item.startsWith(TAG_CHECKED)) { - cb.setChecked(true); + cb.setChecked(true); // 设置为选中状态 edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - item = item.substring(TAG_CHECKED.length(), item.length()).trim(); + item = item.substring(TAG_CHECKED.length(), item.length()).trim(); // 移除标记 } else if (item.startsWith(TAG_UNCHECKED)) { - cb.setChecked(false); + cb.setChecked(false); // 设置为未选中状态 edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); // 移除标记 } + // 设置编辑文本变化监听器 edit.setOnTextViewChangeListener(this); - edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); + 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 { @@ -768,28 +914,39 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 列表模式变化回调 + */ 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); + mEditTextList.setVisibility(View.GONE); // 隐藏列表视图 + mNoteEditor.setVisibility(View.VISIBLE); // 显示普通编辑器 } } + /** + * 获取工作文本(根据当前模式从界面获取文本内容) + */ private boolean getWorkingText() { - boolean hasChecked = false; + 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; @@ -800,74 +957,90 @@ public class NoteEditActivity extends Activity implements OnClickListener, } mWorkingNote.setWorkingText(sb.toString()); } else { + // 普通模式:直接获取编辑框文本 mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); } return hasChecked; } + /** + * 保存笔记 + */ private boolean saveNote() { - getWorkingText(); - boolean saved = mWorkingNote.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 + * 从列表视图到编辑视图有两种模式:打开一个笔记,创建/编辑一个笔记 + * 打开笔记时需要返回到列表中的原始位置,而创建新笔记时需要返回到列表顶部 + * 使用RESULT_OK来标识创建/编辑状态 */ setResult(RESULT_OK); } return saved; } + /** + * 发送到桌面(创建快捷方式) + */ 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 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); + + // 设置快捷方式名称(使用笔记内容的前10个字符) 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); + 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 + * 用户可能没有输入任何内容(笔记不值得保存),我们没有笔记ID, + * 提醒用户需要输入一些内容 */ Log.e(TAG, "Send to desktop error"); showToast(R.string.error_note_empty_for_send_to_desktop); } } + /** + * 创建快捷方式图标标题 + */ private String makeShortcutIconTitle(String content) { - content = content.replace(TAG_CHECKED, ""); - content = content.replace(TAG_UNCHECKED, ""); + content = content.replace(TAG_CHECKED, ""); // 移除选中标记 + content = content.replace(TAG_UNCHECKED, ""); // 移除未选中标记 + // 截取前10个字符或完整内容 return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } + /** + * 显示Toast提示 + */ private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } + /** + * 显示Toast提示(可指定时长) + */ private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } -} +} \ No newline at end of file diff --git a/src/notes/ui/NoteEditText.java b/src/notes/ui/NoteEditText.java index 2afe2a8..fa862f1 100644 --- a/src/notes/ui/NoteEditText.java +++ b/src/notes/ui/NoteEditText.java @@ -37,83 +37,125 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * 自定义EditText,用于笔记编辑功能 + * 支持特殊按键处理(如回车创建新编辑框、删除空编辑框)和链接识别 + */ public class NoteEditText extends EditText { - private static final String TAG = "NoteEditText"; - private int mIndex; - private int mSelectionStartBeforeDelete; + private static final String TAG = "NoteEditText"; // 日志标签 + private int mIndex; // 当前EditText在列表中的索引位置 + 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:"; // HTTP链接协议 + private static final String SCHEME_EMAIL = "mailto:"; // 邮件链接协议 + // 链接协议与对应字符串资源的映射表 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); sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 文本变化监听器接口,由NoteEditActivity实现 + * 用于处理编辑文本的删除、回车和文本变化事件 */ public interface OnTextViewChangeListener { /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null + * 当发生删除操作且文本为空时,删除当前编辑框 + * @param index 当前编辑框的索引 + * @param text 当前编辑框的文本内容 */ void onEditTextDelete(int index, String text); /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen + * 当按下回车键时,在当前编辑框后添加新的编辑框 + * @param index 新编辑框的索引 + * @param text 需要移到新编辑框的文本内容 */ void onEditTextEnter(int index, String text); /** - * Hide or show item option when text change + * 当文本内容变化时,隐藏或显示选项菜单项 + * @param index 当前编辑框的索引 + * @param hasText 当前编辑框是否有文本内容 */ void onTextChange(int index, boolean hasText); } - private OnTextViewChangeListener mOnTextViewChangeListener; + private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器实例 + /** + * 构造函数1 + * @param context 上下文环境 + */ public NoteEditText(Context context) { super(context, null); - mIndex = 0; + mIndex = 0; // 初始化索引为0 } + /** + * 设置当前编辑框在列表中的索引 + * @param index 索引值 + */ public void setIndex(int index) { mIndex = index; } + /** + * 设置文本变化监听器 + * @param listener 监听器实例 + */ public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + /** + * 构造函数2 + * @param context 上下文环境 + * @param attrs 属性集 + */ public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + /** + * 构造函数3 + * @param context 上下文环境 + * @param attrs 属性集 + * @param defStyle 默认样式 + */ public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } + /** + * 处理触摸事件,实现点击文本位置设置光标功能 + * @param event 触摸事件 + * @return 是否处理了该事件 + */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - + case MotionEvent.ACTION_DOWN: // 按下事件 + // 计算点击位置在文本布局中的偏移量 int x = (int) event.getX(); int y = (int) event.getY(); - x -= getTotalPaddingLeft(); + x -= getTotalPaddingLeft(); // 减去内边距 y -= getTotalPaddingTop(); - x += getScrollX(); + x += getScrollX(); // 加上滚动偏移 y += getScrollY(); + // 获取文本布局并计算点击位置的字符偏移 Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); + // 设置光标位置 Selection.setSelection(getText(), off); break; } @@ -121,15 +163,22 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + /** + * 按键按下事件处理 + * @param keyCode 按键代码 + * @param event 按键事件 + * @return 是否处理了该事件 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_ENTER: // 回车键 if (mOnTextViewChangeListener != null) { - return false; + return false; // 返回false让系统继续处理回车事件 } break; - case KeyEvent.KEYCODE_DEL: + case KeyEvent.KEYCODE_DEL: // 删除键 + // 记录删除前的光标起始位置 mSelectionStartBeforeDelete = getSelectionStart(); break; default: @@ -138,27 +187,37 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + /** + * 按键释放事件处理 + * @param keyCode 按键代码 + * @param event 按键事件 + * @return 是否处理了该事件 + */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { - case KeyEvent.KEYCODE_DEL: + case KeyEvent.KEYCODE_DEL: // 删除键释放 if (mOnTextViewChangeListener != null) { + // 如果光标在开头且不是第一个编辑框,则删除当前编辑框 if (0 == mSelectionStartBeforeDelete && mIndex != 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); - return true; + return true; // 消耗事件,不再继续处理 } } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); + Log.d(TAG, "OnTextViewChangeListener was not seted"); // 监听器未设置日志 } break; - case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_ENTER: // 回车键释放 if (mOnTextViewChangeListener != null) { + // 获取光标位置及之后的文本 int selectionStart = getSelectionStart(); String text = getText().subSequence(selectionStart, length()).toString(); + // 截断光标后的文本 setText(getText().subSequence(0, selectionStart)); + // 通知Activity在当前编辑框后添加新编辑框 mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); + Log.d(TAG, "OnTextViewChangeListener was not seted"); // 监听器未设置日志 } break; default: @@ -167,30 +226,46 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + /** + * 焦点变化事件处理 + * @param focused 是否获得焦点 + * @param direction 焦点方向 + * @param previouslyFocusedRect 之前获得焦点的矩形区域 + */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { + // 根据是否有文本内容通知监听器更新UI状态 if (!focused && TextUtils.isEmpty(getText())) { - mOnTextViewChangeListener.onTextChange(mIndex, false); + mOnTextViewChangeListener.onTextChange(mIndex, false); // 无文本 } else { - mOnTextViewChangeListener.onTextChange(mIndex, true); + mOnTextViewChangeListener.onTextChange(mIndex, true); // 有文本 } } super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单(长按菜单) + * @param menu 上下文菜单 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { + // 检查文本是否包含富文本(如链接) if (getText() instanceof Spanned) { - int selStart = getSelectionStart(); - int selEnd = getSelectionEnd(); + int selStart = getSelectionStart(); // 选择起始位置 + int selEnd = getSelectionEnd(); // 选择结束位置 + // 计算选择范围 int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); + // 获取选择范围内的URLSpan对象 final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + // 如果只选择了一个链接 if (urls.length == 1) { - int defaultResId = 0; + int defaultResId = 0; // 默认资源ID + // 遍历协议映射表,匹配链接类型 for(String schema: sSchemaActionResMap.keySet()) { if(urls[0].getURL().indexOf(schema) >= 0) { defaultResId = sSchemaActionResMap.get(schema); @@ -198,14 +273,16 @@ public class NoteEditText extends EditText { } } + // 如果没有匹配到已知协议,使用"其他链接"选项 if (defaultResId == 0) { defaultResId = R.string.note_link_other; } + // 添加菜单项并设置点击监听器 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // goto a new intent + // 触发链接点击事件(跳转到相应应用) urls[0].onClick(NoteEditText.this); return true; } @@ -214,4 +291,4 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } -} +} \ No newline at end of file diff --git a/src/notes/ui/NoteItemData.java b/src/notes/ui/NoteItemData.java index 0f5a878..988bc9c 100644 --- a/src/notes/ui/NoteItemData.java +++ b/src/notes/ui/NoteItemData.java @@ -25,23 +25,29 @@ import net.micode.notes.data.Notes; 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.ID, // 笔记ID + NoteColumns.ALERTED_DATE, // 提醒日期 + NoteColumns.BG_COLOR_ID, // 背景颜色ID + NoteColumns.CREATED_DATE, // 创建日期 + NoteColumns.HAS_ATTACHMENT, // 是否有附件 + NoteColumns.MODIFIED_DATE, // 修改日期 + NoteColumns.NOTES_COUNT, // 笔记数量(对于文件夹类型) + NoteColumns.PARENT_ID, // 父文件夹ID + NoteColumns.SNIPPET, // 内容摘要 + NoteColumns.TYPE, // 类型(笔记、文件夹、系统文件夹等) + NoteColumns.WIDGET_ID, // 小部件ID + NoteColumns.WIDGET_TYPE, // 小部件类型 }; + // 字段索引常量定义,对应PROJECTION数组中的位置 private static final int ID_COLUMN = 0; private static final int ALERTED_DATE_COLUMN = 1; private static final int BG_COLOR_ID_COLUMN = 2; @@ -55,28 +61,38 @@ public class NoteItemData { 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; - private long mCreatedDate; - private boolean mHasAttachment; - private long mModifiedDate; - private int mNotesCount; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private String mName; - private String mPhoneNumber; - - private boolean mIsLastItem; - private boolean mIsFirstItem; - private boolean mIsOnlyOneItem; - private boolean mIsOneNoteFollowingFolder; - private boolean mIsMultiNotesFollowingFolder; - + // 笔记数据字段 + private long mId; // 笔记ID + private long mAlertDate; // 提醒时间 + private int mBgColorId; // 背景颜色ID + private long mCreatedDate; // 创建时间 + private boolean mHasAttachment; // 是否有附件 + private long mModifiedDate; // 修改时间 + private int mNotesCount; // 包含的笔记数量(文件夹类型有效) + private long mParentId; // 父文件夹ID + private String mSnippet; // 内容摘要 + private int mType; // 笔记类型 + private int mWidgetId; // 关联的小部件ID + private int mWidgetType; // 小部件类型 + + // 通话记录相关字段 + private String mName; // 联系人姓名 + private String mPhoneNumber; // 电话号码 + + // 列表位置状态标识 + private boolean mIsLastItem; // 是否为最后一项 + private boolean mIsFirstItem; // 是否为第一项 + private boolean mIsOnlyOneItem; // 是否只有一项 + private boolean mIsOneNoteFollowingFolder; // 是否是单个笔记跟随文件夹后 + private boolean mIsMultiNotesFollowingFolder; // 是否是多个笔记跟随文件夹后 + + /** + * 构造函数,从数据库游标初始化笔记数据 + * @param context 上下文对象 + * @param cursor 数据库查询结果游标 + */ public NoteItemData(Context context, Cursor cursor) { + // 从游标中读取各字段值 mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); @@ -86,47 +102,68 @@ public class NoteItemData { mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); mParentId = cursor.getLong(PARENT_ID_COLUMN); mSnippet = cursor.getString(SNIPPET_COLUMN); + // 移除摘要中的复选框标记(已勾选和未勾选) mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( NoteEditActivity.TAG_UNCHECKED, ""); mType = cursor.getInt(TYPE_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + // 初始化通话记录相关字段 mPhoneNumber = ""; + // 如果是通话记录文件夹中的笔记 if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + // 根据笔记ID获取电话号码 mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) { + // 根据电话号码获取联系人姓名 mName = Contact.getContact(context, mPhoneNumber); if (mName == null) { + // 如果找不到联系人,使用电话号码作为名称 mName = mPhoneNumber; } } } + // 确保名称不为null if (mName == null) { mName = ""; } + + // 检查当前项在列表中的位置状态 checkPostion(cursor); } + /** + * 检查当前项在列表中的位置状态 + * @param cursor 数据库游标 + */ private void checkPostion(Cursor cursor) { + // 设置基础位置标识 mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; mIsOnlyOneItem = (cursor.getCount() == 1); + + // 初始化文件夹相关标识 mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; + // 如果是笔记类型且不是第一项,检查前一项是否为文件夹 if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { int position = cursor.getPosition(); + // 移动到前一项 if (cursor.moveToPrevious()) { + // 检查前一项是否是文件夹或系统文件夹 if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + // 判断是单个笔记还是多个笔记跟随文件夹 if (cursor.getCount() > (position + 1)) { - mIsMultiNotesFollowingFolder = true; + mIsMultiNotesFollowingFolder = true; // 多个笔记跟随文件夹 } else { - mIsOneNoteFollowingFolder = true; + mIsOneNoteFollowingFolder = true; // 单个笔记跟随文件夹 } } + // 移回当前位置 if (!cursor.moveToNext()) { throw new IllegalStateException("cursor move to previous but can't move back"); } @@ -134,91 +171,180 @@ public class NoteItemData { } } + /** + * 判断是否是单个笔记跟随在文件夹后 + * @return 如果是单个笔记跟随文件夹则返回true + */ public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } + /** + * 判断是否是多个笔记跟随在文件夹后 + * @return 如果是多个笔记跟随文件夹则返回true + */ public boolean isMultiFollowingFolder() { return mIsMultiNotesFollowingFolder; } + /** + * 判断是否是列表中的最后一项 + * @return 如果是最后一项则返回true + */ public boolean isLast() { return mIsLastItem; } + /** + * 获取通话记录的联系人姓名 + * @return 联系人姓名 + */ public String getCallName() { return mName; } + /** + * 判断是否是列表中的第一项 + * @return 如果是第一项则返回true + */ public boolean isFirst() { return mIsFirstItem; } + /** + * 判断是否是列表中的唯一项 + * @return 如果只有一项则返回true + */ public boolean isSingle() { return mIsOnlyOneItem; } + /** + * 获取笔记ID + * @return 笔记ID + */ public long getId() { return mId; } + /** + * 获取提醒时间 + * @return 提醒时间戳 + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取创建时间 + * @return 创建时间戳 + */ public long getCreatedDate() { return mCreatedDate; } + /** + * 判断是否有附件 + * @return 有附件返回true + */ public boolean hasAttachment() { return mHasAttachment; } + /** + * 获取修改时间 + * @return 修改时间戳 + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取背景颜色ID + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取父文件夹ID + * @return 父文件夹ID + */ public long getParentId() { return mParentId; } + /** + * 获取笔记数量(对文件夹类型有效) + * @return 包含的笔记数量 + */ public int getNotesCount() { return mNotesCount; } + /** + * 获取文件夹ID(与getParentId相同) + * @return 文件夹ID + */ public long getFolderId () { return mParentId; } + /** + * 获取笔记类型 + * @return 笔记类型(笔记、文件夹、系统文件夹等) + */ public int getType() { return mType; } + /** + * 获取小部件类型 + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 获取小部件ID + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取内容摘要 + * @return 内容摘要字符串 + */ public String getSnippet() { return mSnippet; } + /** + * 判断是否有提醒 + * @return 有提醒返回true + */ public boolean hasAlert() { return (mAlertDate > 0); } + /** + * 判断是否是通话记录 + * @return 如果是通话记录返回true + */ public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } + /** + * 静态方法:从游标获取笔记类型 + * @param cursor 数据库游标 + * @return 笔记类型 + */ public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} +} \ No newline at end of file diff --git a/src/notes/ui/NotesListActivity.java b/src/notes/ui/NotesListActivity.java index e843aec..6bbb470 100644 --- a/src/notes/ui/NotesListActivity.java +++ b/src/notes/ui/NotesListActivity.java @@ -78,92 +78,109 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * 笔记列表主界面Activity + * 显示所有笔记列表,支持笔记的增删改查、文件夹管理、批量操作等功能 + */ public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { - private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - - private static final int FOLDER_LIST_QUERY_TOKEN = 1; - - private static final int MENU_FOLDER_DELETE = 0; + // 查询令牌常量 + private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 查询文件夹内笔记列表的令牌 + private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 查询文件夹列表的令牌 - private static final int MENU_FOLDER_VIEW = 1; - - private static final int MENU_FOLDER_CHANGE_NAME = 2; + // 上下文菜单项ID常量 + 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 + 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; + // 请求码常量 + private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开笔记请求码 + private final static int REQUEST_CODE_NEW_NODE = 103; // 新建笔记请求码 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.note_list); - initResources(); + setContentView(R.layout.note_list); // 设置布局文件 + initResources(); // 初始化资源 - /** - * Insert an introduction when user firstly use this application - */ + // 首次使用时插入应用介绍笔记 setAppInfoFromRawRes(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // 处理笔记编辑返回结果 if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + // 清空适配器游标以触发重新查询 mNotesListAdapter.changeCursor(null); } else { super.onActivityResult(requestCode, resultCode, data); } } + /** + * 从raw资源中读取并创建应用介绍笔记 + */ private void setAppInfoFromRawRes() { 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); + // 从raw资源读取介绍文件 + in = getResources().openRawResource(R.raw.introduction); if (in != null) { InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); @@ -184,17 +201,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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"); @@ -206,14 +224,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onStart() { super.onStart(); + // 启动异步查询笔记列表 startAsyncNotesListQuery(); } + /** + * 初始化资源 + */ private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); - mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 初始化为根文件夹 mNotesListView = (ListView) findViewById(R.id.notes_list); + // 添加列表底部视图 mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); mNotesListView.setOnItemClickListener(new OnListItemClickListener()); @@ -222,24 +245,31 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mNotesListView.setAdapter(mNotesListAdapter); mAddNewNote = (Button) findViewById(R.id.btn_new_note); mAddNewNote.setOnClickListener(this); - mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + 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(); + mState = ListEditState.NOTE_LIST; // 初始状态为普通笔记列表 + mModeCallBack = new ModeCallback(); // 多选模式回调 } + /** + * 多选模式回调类 + * 实现ListView的多选操作模式 + */ 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; // 移动菜单项 + @Override 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); @@ -248,10 +278,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mMoveMenu.setOnMenuItemClickListener(this); } mActionMode = mode; - mNotesListAdapter.setChoiceMode(true); - mNotesListView.setLongClickable(false); - mAddNewNote.setVisibility(View.GONE); + 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); @@ -259,19 +290,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt (Button) customView.findViewById(R.id.selection_menu), R.menu.note_list_dropdown); mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ + @Override public boolean onMenuItemClick(MenuItem item) { + // 全选/取消全选功能 mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); updateMenu(); return true; } - }); 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); @@ -286,32 +321,40 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO Auto-generated method stub return false; } + @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // TODO Auto-generated method stub return false; } + @Override public void onDestroyActionMode(ActionMode mode) { + // 退出多选模式 mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); } + /** + * 结束动作模式 + */ public void finishActionMode() { mActionMode.finish(); } + @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { + // 选中状态变化时更新 mNotesListAdapter.setCheckedItem(position, checked); updateMenu(); } + @Override public boolean onMenuItemClick(MenuItem item) { if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), @@ -321,22 +364,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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() { + @Override + 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: @@ -346,8 +392,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 新建笔记按钮的特殊触摸监听器 + * 处理按钮透明区域的触摸事件分发 + */ private class NewNoteOnTouchListener implements OnTouchListener { - + @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { @@ -356,21 +406,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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. + * HACKME: 当点击"新建笔记"按钮的透明部分时,将事件分发到底层的列表视图 + * 透明部分的表达式:y = -0.12x + 94(单位:像素) + * 94表示透明部分的最大高度 + * 注意:如果按钮背景改变,公式也应相应改变 */ if (event.getY() < (event.getX() * (-0.12) + 94)) { View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 @@ -405,18 +452,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return false; } + } - }; - + /** + * 启动异步笔记列表查询 + */ private void startAsyncNotesListQuery() { + // 根据当前文件夹选择查询条件 String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { - String.valueOf(mCurrentFolderId) + String.valueOf(mCurrentFolderId) }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 后台查询处理器 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); @@ -426,9 +479,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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 { @@ -441,13 +496,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示文件夹列表选择菜单 + */ 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() { - + @Override public void onClick(DialogInterface dialog, int which) { + // 批量移动笔记到选定文件夹 DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); Toast.makeText( @@ -456,12 +515,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mNotesListAdapter.getSelectedCount(), adapter.getFolderName(NotesListActivity.this, which)), Toast.LENGTH_SHORT).show(); - mModeCallBack.finishActionMode(); + mModeCallBack.finishActionMode(); // 结束多选模式 } }); builder.show(); } + /** + * 创建新笔记 + */ private void createNewNote() { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -469,20 +531,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + /** + * 批量删除笔记 + */ private void batchDelete() { new AsyncTask>() { + @Override protected HashSet doInBackground(Void... unused) { + // 获取选中笔记的小部件属性 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"); @@ -493,6 +560,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onPostExecute(HashSet widgets) { + // 更新相关小部件 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -501,11 +569,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } - mModeCallBack.finishActionMode(); + mModeCallBack.finishActionMode(); // 结束多选模式 } }.execute(); } + /** + * 删除文件夹 + */ private void deleteFolder(long folderId) { if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); @@ -514,15 +585,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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 @@ -533,6 +606,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 打开笔记进行编辑 + */ private void openNode(NoteItemData data) { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); @@ -540,15 +616,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } + /** + * 打开文件夹浏览 + */ private void openFolder(NoteItemData data) { mCurrentFolderId = data.getId(); - startAsyncNotesListQuery(); + startAsyncNotesListQuery(); // 查询文件夹内容 + // 根据文件夹类型设置状态 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mState = ListEditState.CALL_RECORD_FOLDER; - mAddNewNote.setVisibility(View.GONE); + 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 { @@ -557,16 +638,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mTitleBar.setVisibility(View.VISIBLE); } + @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: - createNewNote(); + createNewNote(); // 创建新笔记 break; default: break; } } + /** + * 显示软键盘 + */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { @@ -574,16 +659,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 隐藏软键盘 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示创建或修改文件夹对话框 + */ 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()); @@ -599,17 +691,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.setPositiveButton(android.R.string.ok, null); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override 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); positive.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { 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(); @@ -617,6 +712,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return; } if (!create) { + // 修改文件夹名称 if (!TextUtils.isEmpty(name)) { ContentValues values = new ContentValues(); values.put(NoteColumns.SNIPPET, name); @@ -624,10 +720,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt values.put(NoteColumns.LOCAL_MODIFIED, 1); mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) + String.valueOf(mFocusNoteDataItem.getId()) }); } } else if (!TextUtils.isEmpty(name)) { + // 创建新文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.SNIPPET, name); values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); @@ -637,18 +734,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }); + // 初始状态下,如果名称为空,禁用确认按钮 if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); } - /** - * When the name edit text is null, disable the positive button - */ + // 监听文本变化,动态启用/禁用确认按钮 etName.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - } + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); @@ -657,15 +753,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + @Override public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - } }); } @Override public void onBackPressed() { + // 处理返回键,根据当前状态返回上一级或退出 switch (mState) { case SUB_FOLDER: mCurrentFolderId = Notes.ID_ROOT_FOLDER; @@ -688,8 +784,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 更新小部件 + */ 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) { @@ -700,17 +800,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - appWidgetId + appWidgetId }); sendBroadcast(intent); setResult(RESULT_OK, intent); } + /** + * 文件夹长按上下文菜单监听器 + */ private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为文件夹名 menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); @@ -721,7 +825,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override public void onContextMenuClosed(Menu menu) { if (mNotesListView != null) { - mNotesListView.setOnCreateContextMenuListener(null); + mNotesListView.setOnCreateContextMenuListener(null); // 清除上下文菜单监听器 } super.onContextMenuClosed(menu); } @@ -734,24 +838,26 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } 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); builder.setMessage(getString(R.string.alert_message_delete_folder)); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override 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; @@ -763,9 +869,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); + // 根据当前状态加载不同的菜单 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) { @@ -781,36 +888,32 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_new_folder: { - showCreateOrModifyFolderDialog(true); + case R.id.menu_new_folder: + showCreateOrModifyFolderDialog(true); // 创建新文件夹 break; - } - case R.id.menu_export_text: { - exportNoteToText(); + case R.id.menu_export_text: + exportNoteToText(); // 导出笔记为文本 break; - } - case R.id.menu_sync: { + case R.id.menu_sync: + // 同步功能处理 if (isSyncMode()) { if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { - GTaskSyncService.startSync(this); + GTaskSyncService.startSync(this); // 开始同步 } else { - GTaskSyncService.cancelSync(this); + GTaskSyncService.cancelSync(this); // 取消同步 } } else { - startPreferenceActivity(); + startPreferenceActivity(); // 未设置同步账户,跳转到设置 } break; - } - case R.id.menu_setting: { - startPreferenceActivity(); + case R.id.menu_setting: + startPreferenceActivity(); // 打开设置 break; - } - case R.id.menu_new_note: { - createNewNote(); + case R.id.menu_new_note: + createNewNote(); // 新建笔记 break; - } case R.id.menu_search: - onSearchRequested(); + onSearchRequested(); // 触发搜索 break; default: break; @@ -824,17 +927,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 导出笔记为文本文件 + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { - @Override protected Integer doInBackground(Void... unused) { - return backup.exportToText(); + 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 @@ -862,25 +968,34 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } } - }.execute(); } + /** + * 检查是否处于同步模式 + */ 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 { - + @Override 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(); @@ -890,13 +1005,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return; } + // 根据当前状态处理点击事件 switch (mState) { case NOTE_LIST: if (item.getType() == Notes.TYPE_FOLDER || item.getType() == Notes.TYPE_SYSTEM) { - openFolder(item); + openFolder(item); // 打开文件夹 } else if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); + openNode(item); // 打开笔记 } else { Log.e(TAG, "Wrong note type in NOTE_LIST"); } @@ -904,7 +1020,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt case SUB_FOLDER: case CALL_RECORD_FOLDER: if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); + openNode(item); // 打开笔记 } else { Log.e(TAG, "Wrong note type in SUB_FOLDER"); } @@ -914,13 +1030,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } - } + /** + * 查询目标文件夹列表(用于批量移动) + */ 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 + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, null, @@ -935,9 +1055,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt NoteColumns.MODIFIED_DATE + " DESC"); } + @Override 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); @@ -946,9 +1068,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt Log.e(TAG, "startActionMode fails"); } } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + // 文件夹长按:显示上下文菜单 mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); } } return false; } -} +} \ No newline at end of file diff --git a/src/notes/ui/NotesListAdapter.java b/src/notes/ui/NotesListAdapter.java index 51c9cb9..9a6fe82 100644 --- a/src/notes/ui/NotesListAdapter.java +++ b/src/notes/ui/NotesListAdapter.java @@ -30,58 +30,94 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; - +/** + * 笔记列表适配器 + * 用于将数据库游标数据绑定到ListView的适配器 + * 支持多选模式和选中状态管理 + */ public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; - + private static final String TAG = "NotesListAdapter"; // 日志标签 + private Context mContext; // 上下文对象 + private HashMap mSelectedIndex; // 选中项索引映射表(位置->是否选中) + private int mNotesCount; // 笔记类型项的总数(不包含文件夹) + private boolean mChoiceMode; // 是否处于多选模式 + + /** + * 小部件属性类 + * 用于存储笔记关联的小部件信息 + */ 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(); + super(context, null); // 初始游标为null + mSelectedIndex = new HashMap(); // 初始化选中索引映射表 mContext = context; - mNotesCount = 0; + mNotesCount = 0; // 初始化笔记数量为0 } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { + // 创建新的列表项视图 return new NotesListItem(context); } @Override public void bindView(View view, Context context, Cursor cursor) { + // 绑定数据到视图 if (view instanceof NotesListItem) { + // 从游标创建笔记数据项 NoteItemData itemData = new NoteItemData(context, cursor); + // 绑定数据到列表项,传递是否处于多选模式和是否选中状态 ((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition())); } } + /** + * 设置指定位置项的选中状态 + * @param position 列表位置 + * @param checked 是否选中 + */ public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - notifyDataSetChanged(); + mSelectedIndex.put(position, checked); // 更新选中状态映射 + notifyDataSetChanged(); // 通知数据变化,刷新视图 } + /** + * 判断是否处于多选模式 + * @return 如果是多选模式返回true + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置多选模式 + * @param mode true进入多选模式,false退出多选模式 + */ public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); - mChoiceMode = mode; + mSelectedIndex.clear(); // 清空选中状态 + mChoiceMode = mode; // 设置多选模式标志 } + /** + * 全选或取消全选所有笔记项 + * 注意:只对笔记类型有效,文件夹类型不会被选中 + * @param checked true全选,false取消全选 + */ public void selectAll(boolean checked) { - Cursor cursor = getCursor(); + Cursor cursor = getCursor(); // 获取当前游标 + // 遍历所有项 for (int i = 0; i < getCount(); i++) { if (cursor.moveToPosition(i)) { + // 只处理笔记类型,不处理文件夹类型 if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { setCheckedItem(i, checked); } @@ -89,15 +125,21 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取选中项的ID集合 + * @return 选中项的ID集合 + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); + // 遍历所有选中项 for (Integer position : mSelectedIndex.keySet()) { if (mSelectedIndex.get(position) == true) { - Long id = getItemId(position); + Long id = getItemId(position); // 获取项的数据库ID + // 排除根文件夹ID(不应该被选中) if (id == Notes.ID_ROOT_FOLDER) { Log.d(TAG, "Wrong item id, should not happen"); } else { - itemSet.add(id); + itemSet.add(id); // 添加到集合 } } } @@ -105,36 +147,46 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项关联的小部件属性集合 + * @return 小部件属性集合,如果发生错误返回null + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); + // 遍历所有选中项 for (Integer position : mSelectedIndex.keySet()) { if (mSelectedIndex.get(position) == true) { - Cursor c = (Cursor) getItem(position); + Cursor c = (Cursor) getItem(position); // 获取对应位置的游标 if (c != null) { AppWidgetAttribute widget = new AppWidgetAttribute(); NoteItemData item = new NoteItemData(mContext, c); - widget.widgetId = item.getWidgetId(); - widget.widgetType = item.getWidgetType(); + widget.widgetId = item.getWidgetId(); // 设置小部件ID + 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; + return null; // 游标无效时返回null } } } return itemSet; } + /** + * 获取选中项的数量 + * @return 选中项的数量 + */ public int getSelectedCount() { - Collection values = mSelectedIndex.values(); + Collection values = mSelectedIndex.values(); // 获取所有选中状态值 if (null == values) { return 0; } Iterator iter = values.iterator(); int count = 0; + // 统计选中状态为true的数量 while (iter.hasNext()) { if (true == iter.next()) { count++; @@ -143,14 +195,23 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 判断是否所有笔记项都被选中 + * @return 如果所有笔记项都被选中且选中数量不为0,返回true + */ public boolean isAllSelected() { - int checkedCount = getSelectedCount(); - return (checkedCount != 0 && checkedCount == mNotesCount); + int checkedCount = getSelectedCount(); // 获取选中数量 + return (checkedCount != 0 && checkedCount == mNotesCount); // 比较选中数量和笔记总数 } + /** + * 判断指定位置项是否被选中 + * @param position 列表位置 + * @return 如果选中返回true,否则返回false + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { - return false; + return false; // 映射表中不存在该位置,默认为未选中 } return mSelectedIndex.get(position); } @@ -158,27 +219,33 @@ public class NotesListAdapter extends CursorAdapter { @Override protected void onContentChanged() { super.onContentChanged(); - calcNotesCount(); + calcNotesCount(); // 内容变化时重新计算笔记数量 } @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); - calcNotesCount(); + calcNotesCount(); // 游标变化时重新计算笔记数量 } + /** + * 计算笔记类型项的数量(排除文件夹类型) + * 用于全选判断 + */ private void calcNotesCount() { - mNotesCount = 0; + mNotesCount = 0; // 重置计数 + // 遍历所有项 for (int i = 0; i < getCount(); i++) { - Cursor c = (Cursor) getItem(i); + Cursor c = (Cursor) getItem(i); // 获取对应位置的游标 if (c != null) { + // 只统计笔记类型 if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { mNotesCount++; } } else { Log.e(TAG, "Invalid cursor"); - return; + return; // 游标无效时提前返回 } } } -} +} \ No newline at end of file diff --git a/src/notes/ui/NotesListItem.java b/src/notes/ui/NotesListItem.java index 1221e80..a141f91 100644 --- a/src/notes/ui/NotesListItem.java +++ b/src/notes/ui/NotesListItem.java @@ -29,18 +29,26 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - +/** + * 笔记列表项自定义视图 + * 用于在笔记列表中显示单个笔记或文件夹的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 mAlert; // 提醒图标 + private TextView mTitle; // 标题/内容摘要 + private TextView mTime; // 修改时间 + private TextView mCallName; // 通话记录联系人姓名 + private NoteItemData mItemData; // 绑定的笔记数据 + private CheckBox mCheckBox; // 多选模式下的复选框 + /** + * 构造函数 + * @param context 上下文对象 + */ public NotesListItem(Context context) { super(context); - inflate(context, R.layout.note_item, this); + 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); @@ -48,7 +56,15 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + /** + * 绑定数据到列表项视图 + * @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); @@ -56,19 +72,25 @@ public class NotesListItem extends LinearLayout { mCheckBox.setVisibility(View.GONE); } - mItemData = data; + mItemData = data; // 保存数据引用 + + // 根据不同数据类型设置不同的显示内容 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 通话记录文件夹的特殊显示 mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置主标题样式 + // 显示文件夹名称和包含的笔记数量 mTitle.setText(context.getString(R.string.call_record_folder_name) + context.getString(R.string.format_folder_files_count, data.getNotesCount())); - mAlert.setImageResource(R.drawable.call_record); + 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.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + mCallName.setText(data.getCallName()); // 显示联系人姓名 + mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); // 设置副标题样式 + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 显示格式化后的内容摘要 + // 根据是否有提醒设置提醒图标 if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); @@ -76,16 +98,20 @@ public class NotesListItem extends LinearLayout { mAlert.setVisibility(View.GONE); } } else { + // 普通笔记或文件夹的显示 mCallName.setVisibility(View.GONE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + 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())); + // 根据是否有提醒设置提醒图标 if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); @@ -94,29 +120,46 @@ public class NotesListItem extends LinearLayout { } } } + + // 显示相对时间(如"2小时前") mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 根据数据类型和位置设置背景 setBackground(data); } + /** + * 根据笔记数据类型和位置设置不同的背景 + * @param data 笔记数据项 + */ private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); + int id = data.getBgColorId(); // 获取背景颜色ID if (data.getType() == Notes.TYPE_NOTE) { + // 笔记类型的背景设置 if (data.isSingle() || data.isOneFollowingFolder()) { + // 单一条目或紧跟在文件夹后的单个笔记:使用单个笔记背景 setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); } else if (data.isLast()) { + // 最后一项:使用最后一项背景 setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); } else if (data.isFirst() || data.isMultiFollowingFolder()) { + // 第一项或紧跟在文件夹后的多个笔记:使用第一项背景 setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); } else { + // 中间项:使用普通背景 setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } } else { + // 文件夹类型:使用文件夹背景 setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /** + * 获取绑定的笔记数据项 + * @return 笔记数据项 + */ public NoteItemData getItemData() { return mItemData; } -} +} \ No newline at end of file diff --git a/src/notes/ui/NotesPreferenceActivity.java b/src/notes/ui/NotesPreferenceActivity.java index 503b40e..35d0b77 100644 --- a/src/notes/ui/NotesPreferenceActivity.java +++ b/src/notes/ui/NotesPreferenceActivity.java @@ -48,27 +48,28 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; - +/** + * 笔记应用的设置界面Activity + * 主要负责Google Task同步账户的设置和管理 + */ 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; + private PreferenceCategory mAccountCategory; // 账户设置类别 + private GTaskReceiver mReceiver; // 同步服务广播接收器 + private Account[] mOriAccounts; // 原始账户列表(用于检测新添加的账户) + private boolean mHasAddedAccount; // 标记是否添加了新账户 // 在文件顶部添加这个 import // import androidx.core.content.ContextCompat; @@ -77,13 +78,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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(); + mReceiver = new GTaskReceiver(); // 创建广播接收器 IntentFilter filter = new IntentFilter(); +<<<<<<< HEAD + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); // 注册同步服务广播 + registerReceiver(mReceiver, filter); // 注册广播接收器 +======= filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); // ✅ 修复:兼容 Android 13+ 的广播注册方式(无需 if/else) @@ -95,8 +101,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { null, ContextCompat.RECEIVER_NOT_EXPORTED // 内部广播,更安全 ); +>>>>>>> ce8801278409c0c99db42eb507f3f732202b6249 mOriAccounts = null; + // 添加设置界面的头部视图 View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); getListView().addHeaderView(header, null, true); } @@ -104,10 +112,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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) { for (Account accountNew : accounts) { boolean found = false; @@ -117,6 +125,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { break; } } + // 发现新账户,自动设置为同步账户 if (!found) { setSyncAccount(accountNew.name); break; @@ -125,94 +134,112 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } - refreshUI(); + refreshUI(); // 刷新界面 } @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)); accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override 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) - .show(); + 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); - // set button state + // 设置按钮状态和文字 if (GTaskSyncService.isSyncing()) { syncButton.setText(getString(R.string.preferences_button_sync_cancel)); syncButton.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { - GTaskSyncService.cancelSync(NotesPreferenceActivity.this); + GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 取消同步 } }); } else { syncButton.setText(getString(R.string.preferences_button_sync_immediately)); syncButton.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { - GTaskSyncService.startSync(NotesPreferenceActivity.this); + GTaskSyncService.startSync(NotesPreferenceActivity.this); // 开始同步 } }); } + // 只有设置了同步账户才能启用同步按钮 syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - // set last sync time + // 设置上次同步时间显示 if (GTaskSyncService.isSyncing()) { - lastSyncTimeView.setText(GTaskSyncService.getProgressString()); + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 显示同步进度 lastSyncTimeView.setVisibility(View.VISIBLE); } else { 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); } else { - lastSyncTimeView.setVisibility(View.GONE); + lastSyncTimeView.setVisibility(View.GONE); // 从未同步过 } } } + /** + * 刷新整个设置界面 + */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + /** + * 显示选择账户的对话框 + */ private void showSelectAccountAlertDialog() { 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_select_account_title)); @@ -220,119 +247,147 @@ 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); // 不显示确定按钮 Account[] accounts = getGoogleAccounts(); 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 index = 0; + // 填充账户列表 for (Account account : accounts) { if (TextUtils.equals(account.name, defAccount)) { - checkedItem = index; + checkedItem = index; // 标记已选账户 } items[index++] = account.name; } + // 设置单选列表 dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { + // 选择账户并保存 setSyncAccount(itemMapping[which].toString()); dialog.dismiss(); - refreshUI(); + refreshUI(); // 刷新界面 } }); } + // 添加"添加账户"选项 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() { + @Override public void onClick(View v) { - mHasAddedAccount = true; + mHasAddedAccount = true; // 标记添加了新账户 + // 启动添加账户界面 Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" + "gmail-ls" // 限制为Google账户 }); startActivityForResult(intent, -1); - dialog.dismiss(); + dialog.dismiss(); // 关闭对话框 } }); } + /** + * 显示更改账户的确认对话框 + */ 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, - getSyncAccountName(this))); + getSyncAccountName(this))); // 显示当前账户 TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); dialogBuilder.setCustomTitle(titleView); + // 设置操作菜单项 CharSequence[] menuItemArray = new CharSequence[] { - getString(R.string.preferences_menu_change_account), - getString(R.string.preferences_menu_remove_account), - getString(R.string.preferences_menu_cancel) + getString(R.string.preferences_menu_change_account), // 更改账户 + getString(R.string.preferences_menu_remove_account), // 移除账户 + getString(R.string.preferences_menu_cancel) // 取消 }; dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { if (which == 0) { - showSelectAccountAlertDialog(); + showSelectAccountAlertDialog(); // 显示选择账户对话框 } else if (which == 1) { - removeSyncAccount(); - refreshUI(); + removeSyncAccount(); // 移除同步账户 + refreshUI(); // 刷新界面 } + // which == 2 取消,不做任何操作 } }); dialogBuilder.show(); } + /** + * 获取所有Google账户 + * @return Google账户数组 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); - return accountManager.getAccountsByType("com.google"); + return accountManager.getAccountsByType("com.google"); // 获取Google类型账户 } + /** + * 设置同步账户 + * @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); + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); // 保存账户名 } else { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); // 清空账户名 } editor.commit(); - // clean up last sync time + // 清除上次同步时间 setLastSyncTime(this, 0); - // clean up local gtask related info + // 在新线程中清除本地与GTask相关的同步信息 new Thread(new Runnable() { + @Override public void run() { ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); + values.put(NoteColumns.GTASK_ID, ""); // 清空GTask ID + values.put(NoteColumns.SYNC_ID, 0); // 重置同步ID getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); } }).start(); Toast.makeText(NotesPreferenceActivity.this, getString(R.string.preferences_toast_success_set_accout, account), - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); // 显示设置成功提示 } } + /** + * 移除同步账户 + */ 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); } @@ -341,23 +396,34 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up local gtask related info + // 在新线程中清除本地与GTask相关的同步信息 new Thread(new Runnable() { + @Override public void run() { ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); + values.put(NoteColumns.GTASK_ID, ""); // 清空GTask ID + values.put(NoteColumns.SYNC_ID, 0); // 重置同步ID getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); } }).start(); } + /** + * 静态方法:获取同步账户名称 + * @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, ""); + return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); // 默认返回空字符串 } + /** + * 静态方法:设置上次同步时间 + * @param context 上下文 + * @param time 同步时间戳 + */ public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -366,35 +432,45 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + /** + * 静态方法:获取上次同步时间 + * @param context 上下文 + * @return 上次同步时间戳,如果不存在则返回0 + */ public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); + return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); // 默认返回0 } + /** + * GTask同步服务广播接收器 + * 用于接收同步状态变化的广播 + */ private class GTaskReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { - refreshUI(); + refreshUI(); // 刷新界面 + // 如果正在同步,更新同步进度显示 if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); syncStatus.setText(intent .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); } - } } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: + // 点击返回按钮,返回笔记列表界面 Intent intent = new Intent(this, NotesListActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清理Activity栈 startActivity(intent); return true; default: return false; } } -} +} \ No newline at end of file diff --git a/src/notes/widget/NoteWidgetProvider.java b/src/notes/widget/NoteWidgetProvider.java index ec6f819..477e582 100644 --- a/src/notes/widget/NoteWidgetProvider.java +++ b/src/notes/widget/NoteWidgetProvider.java @@ -15,6 +15,7 @@ */ package net.micode.notes.widget; + import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; @@ -32,101 +33,150 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * 笔记小部件提供者抽象基类 + * 提供桌面小部件的基本功能,包括更新、删除和数据查询 + * 具体的小部件类型(如2x、4x)需要继承此类并实现抽象方法 + */ public abstract class NoteWidgetProvider extends AppWidgetProvider { + // 数据库查询字段投影 public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + NoteColumns.ID, // 笔记ID + NoteColumns.BG_COLOR_ID, // 背景颜色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; - private static final String TAG = "NoteWidgetProvider"; + private static final String TAG = "NoteWidgetProvider"; // 日志标签 @Override public void onDeleted(Context context, int[] appWidgetIds) { + // 当小部件从桌面删除时调用 ContentValues values = new ContentValues(); + // 将笔记的小部件ID重置为无效值,解除笔记与小部件的关联 values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); for (int i = 0; i < appWidgetIds.length; i++) { context.getContentResolver().update(Notes.CONTENT_NOTE_URI, values, - NoteColumns.WIDGET_ID + "=?", + NoteColumns.WIDGET_ID + "=?", // 更新条件:找到对应的小部件ID new String[] { String.valueOf(appWidgetIds[i])}); } } + /** + * 获取指定小部件ID关联的笔记信息 + * @param context 上下文 + * @param widgetId 小部件ID + * @return 笔记信息游标,包含ID、背景颜色和摘要 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, + // 查询条件:小部件ID匹配且不在回收站中 NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, null); } + /** + * 更新小部件(默认不处于隐私模式) + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 需要更新的小部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 更新小部件内容 + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @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); - String snippet = ""; + int bgId = ResourceParser.getDefaultBgId(context); // 默认背景颜色ID + String snippet = ""; // 内容摘要 Intent intent = new Intent(context, NoteEditActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // 设置Activity启动模式 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + // 查询与小部件关联的笔记信息 Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); if (c != null && c.moveToFirst()) { + // 正常情况下,一个小部件ID应该只关联一个笔记 if (c.getCount() > 1) { Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); c.close(); - return; + return; // 发现异常情况,直接返回 } - snippet = c.getString(COLUMN_SNIPPET); - bgId = c.getInt(COLUMN_BG_COLOR_ID); - intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); - intent.setAction(Intent.ACTION_VIEW); + snippet = c.getString(COLUMN_SNIPPET); // 获取内容摘要 + bgId = c.getInt(COLUMN_BG_COLOR_ID); // 获取背景颜色ID + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); // 笔记ID + intent.setAction(Intent.ACTION_VIEW); // 查看笔记 } else { + // 没有找到关联的笔记,显示默认提示 snippet = context.getResources().getString(R.string.widget_havenot_content); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 新建或编辑笔记 } if (c != null) { c.close(); } + // 创建RemoteViews对象 RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); - rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + 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 + * 生成启动宿主Activity的PendingIntent */ PendingIntent pendingIntent = null; if (privacyMode) { + // 隐私模式:显示占位文本,点击跳转到笔记列表 rv.setTextViewText(R.id.widget_text, context.getString(R.string.widget_under_visit_mode)); pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); } else { + // 正常模式:显示笔记摘要,点击跳转到笔记编辑界面 rv.setTextViewText(R.id.widget_text, snippet); pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, PendingIntent.FLAG_UPDATE_CURRENT); } - rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); - appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); // 设置点击事件 + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); // 更新小部件 } } } + /** + * 抽象方法:根据背景颜色ID获取对应的资源ID + * @param bgId 背景颜色ID + * @return 背景资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 抽象方法:获取小部件布局资源ID + * @return 布局资源ID + */ protected abstract int getLayoutId(); + /** + * 抽象方法:获取小部件类型 + * @return 小部件类型(如Notes.TYPE_WIDGET_2X, Notes.TYPE_WIDGET_4X) + */ protected abstract int getWidgetType(); -} +} \ No newline at end of file diff --git a/src/notes/widget/NoteWidgetProvider_2x.java b/src/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..8a6aa64 100644 --- a/src/notes/widget/NoteWidgetProvider_2x.java +++ b/src/notes/widget/NoteWidgetProvider_2x.java @@ -23,25 +23,49 @@ 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 小部件管理器,用于更新小部件 + * @param appWidgetIds 需要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); + super.update(context, appWidgetManager, appWidgetIds); // 调用父类的更新方法 } + /** + * 获取2x小部件的布局资源ID + * @return 2x小部件布局资源ID + */ @Override protected int getLayoutId() { - return R.layout.widget_2x; + return R.layout.widget_2x; // 返回2x小部件的布局文件 } + /** + * 根据背景颜色ID获取对应的2x小部件背景资源ID + * @param bgId 背景颜色ID + * @return 2x小部件背景图片资源ID + */ @Override protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); // 从资源解析器获取2x小部件背景资源 } + /** + * 获取小部件类型标识 + * @return 2x小部件类型常量 + */ @Override protected int getWidgetType() { - return Notes.TYPE_WIDGET_2X; + return Notes.TYPE_WIDGET_2X; // 返回2x小部件的类型标识 } -} +} \ No newline at end of file diff --git a/src/notes/widget/NoteWidgetProvider_4x.java b/src/notes/widget/NoteWidgetProvider_4x.java index c12a02e..744b359 100644 --- a/src/notes/widget/NoteWidgetProvider_4x.java +++ b/src/notes/widget/NoteWidgetProvider_4x.java @@ -23,24 +23,49 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * 4x大小笔记小部件提供者实现类 + * 继承自NoteWidgetProvider抽象基类,实现4x大小桌面小部件的具体功能 + */ 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); + super.update(context, appWidgetManager, appWidgetIds); // 调用父类的更新方法 } + /** + * 获取4x小部件的布局资源ID + * @return 4x小部件布局资源ID + */ + @Override protected int getLayoutId() { - return R.layout.widget_4x; + return R.layout.widget_4x; // 返回4x小部件的布局文件 } + /** + * 根据背景颜色ID获取对应的4x小部件背景资源ID + * @param bgId 背景颜色ID + * @return 4x小部件背景图片资源ID + */ @Override protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); // 从资源解析器获取4x小部件背景资源 } + /** + * 获取小部件类型标识 + * @return 4x小部件类型常量 + */ @Override protected int getWidgetType() { - return Notes.TYPE_WIDGET_4X; + return Notes.TYPE_WIDGET_4X; // 返回4x小部件的类型标识 } -} +} \ No newline at end of file diff --git a/uml.puml b/uml.puml deleted file mode 100644 index e749674..0000000 --- a/uml.puml +++ /dev/null @@ -1,50 +0,0 @@ -# 界面层 -class Ui: - """用户界面""" - def display(self): - pass - -class Res: - """资源管理""" - def load_resources(self): - pass - -class Widget: - """界面组件""" - def create_widget(self): - pass - -# 业务层 -class Tool: - """业务工具""" - def process_tool(self): - pass - -class GtaskRemote: - """远程任务接口""" - def fetch_data(self): - pass - -class GtaskException(Exception): - """业务异常""" - pass - -# 模型层 -class Model: - """数据模型""" - def __init__(self): - self.data = None - -class GtaskData: - """任务数据实体""" - def __init__(self, data): - self.data = data - -# 数据层 -class Data: - """数据持久化""" - def save(self, model: Model) -> None: - pass - - def load(self) -> Model: - return Model() \ No newline at end of file diff --git a/所有类.txt b/所有类.txt deleted file mode 100644 index a2e2a48..0000000 --- a/所有类.txt +++ /dev/null @@ -1,133 +0,0 @@ -Contact -DataConstants -NotesDatabaseHelper -Notes -Task -MetaData -Node -SqlData -SqlNote -TaskList -ActionFailureException -NetworkFailureException -GTaskClient -GTaskManager -GTaskSyncService -WorkingNote -Note -DataUtils -BackupUtils -TextExport -GTaskStringUtils -ResourceParser -NoteEditText -AlarmAlertActivity - -前者引用后者 -NotesDatabaseHelper<-Notes -NotesProvider<-Notes -NotesProvider<-NotesDatabaseHelper - -MetaData<-GTaskStringUtils - -SqlData<-Notes -SqlData<-NotesDatabaseHelper -SqlData<-ActionFailureException -SqlNote<-Notes -SqlNote<-ActionFailureException -SqlNote<-GTaskStringUtils -SqlNote<-ResourceParser - -Task<-NOtes -Task<-ActionFailureException -Task<-GTaskStringUtils - -TaskList<-NOtes -TaskList<-ActionFailureException -TaskList<-GTaskStringUtils - -GTaskASyncTask<-NotesListActivity -GTaskASyncTask<-NotesPreferenceActivity - -GTaskClient<-Node -GTaskClient<-Task -GTaskClient<-TaskList -GTaskClient<-ActionFailureException -GTaskClient<-NetworkFailureException -GTaskClient<-GTaskStringUtils -GTaskClient<-NotesPreferenceActivity - -GTaskManager<-Notes -GTaskManager<-MetaData -GTaskManager<-Node -GTaskManager<-SqlNote -GTaskManager<-Task -GTaskManager<-TaskList -GTaskManager<-ActionFailureException -GTaskManager<-NetworkFailureException -GTaskManager<-DataUtils -GTaskManager<-GTaskStringUtils - -Note<-Notes - -WorkingNote<-Notes -WorkingNote<-ResourceParser - -BackupUtils<-Notes - -DataUtils<-Notes -DataUtils<-NotesListAdapter - -ResourceParser<-NotesPreferenceActivity - -AlarmAlertActivity<-Notes -AlarmAlertActivity<-DataUtils - -AlarmInitReceiver<-Notes - -DateTimePickerDialog<-DateTimePicker - -FoldersListAdapter<-Notes - -NoteEditActivity<-Notes -NoteEditActivity<-WorkingNote -NoteEditActivity<-DataUtils -NoteEditActivity<-ResourceParser -NoteEditActivity<-DateTimePickerDialog -NoteEditActivity<-NoteEditText -NoteEditActivity<-NoteWidgetProvider_2x -NoteEditActivity<-NoteWidgetProvider_4x - -NoteItemData<-Contact_ -NoteItemData<-Notes_ -NoteItemData<-DataUtils_ - -NotesListActivity<-Notes_ -NotesListActivity<-GTaskSyncService_ -NotesListActivity<-WorkingNote_ -NotesListActivity<-BackupUtils_ -NotesListActivity<-DataUtils_ -NotesListActivity<-ResourceParser_ -NotesListActivity<-NotesListAdapter_ -NotesListActivity<-NoteWidgetProvider_2x_ -NotesListActivity<-NoteWidgetProvider_4x_ - -NotesListAdapter<-Notes_ - -NotesListAdapter<-Notes_ -NotesListAdapter<-DataUtils_ -NotesListAdapter<-ResourceParser - -NotesPreferenceActivity<-Notes_ -NotesPreferenceActivity<-GTaskSyncService_ - -NoteWidgetProvider<-Notes_ -NoteWidgetProvider<-ResourceParser_ -NoteWidgetProvider<-NoteEditActivity_ -NoteWidgetProvider<-NotesListActivity_ - -NoteWidgetProvider_2x<-Notes_ -NoteWidgetProvider_2x<-ResourceParser_ - -NoteWidgetProvider_4x<-Notes_ -NoteWidgetProvider_4x<-ResourceParser_