diff --git a/doc/小米代码泛读报告(一)软件功能介绍.docx b/doc/小米代码泛读报告(一)软件功能介绍.docx deleted file mode 100644 index f4ae9bb..0000000 Binary files a/doc/小米代码泛读报告(一)软件功能介绍.docx and /dev/null differ diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx new file mode 100644 index 0000000..d886111 Binary files /dev/null and b/doc/小米便签开源代码的泛读报告.docx differ diff --git a/src/main/java/net/micode/notes/MainActivity.java b/src/main/java/net/micode/notes/MainActivity.java index 8091753..105fc40 100644 --- a/src/main/java/net/micode/notes/MainActivity.java +++ b/src/main/java/net/micode/notes/MainActivity.java @@ -1,5 +1,13 @@ + package net.micode.notes; +/** + * 笔记应用的主活动类,作为应用的入口点。 + *

+ * 该类是小米笔记应用的主活动,负责初始化应用界面和处理窗口边距设置。 + *

+ */ + import android.os.Bundle; import androidx.activity.EdgeToEdge; @@ -10,6 +18,15 @@ import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { + /** + * 活动创建时调用的方法,初始化应用界面和窗口设置。 + *

+ * 该方法启用了边缘到边缘显示模式,设置了活动布局,并处理了系统栏 + * 的边距问题,确保内容不会被系统UI遮挡。 + *

+ * + * @param savedInstanceState 保存的实例状态,用于恢复活动状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/main/java/net/micode/notes/model/Note.java b/src/main/java/net/micode/notes/model/Note.java index 6706cf6..4c7931d 100644 --- a/src/main/java/net/micode/notes/model/Note.java +++ b/src/main/java/net/micode/notes/model/Note.java @@ -34,12 +34,38 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; +/** + * 笔记核心数据模型类,负责笔记的创建、更新和同步操作。 + *

+ * 该类是笔记应用的核心数据模型,提供了笔记的创建、更新和同步功能, + * 支持文本笔记和通话笔记两种类型,并通过ContentResolver与数据库进行交互。 + *

+ */ public class Note { + /** + * 用于存储笔记基本属性的变更值 + */ private ContentValues mNoteDiffValues; + + /** + * 用于存储笔记具体数据(文本或通话数据) + */ private NoteData mNoteData; - private static final String TAG = "Note"; + + /** + * 日志标签 + */ + private static final String TAG = Note.class.getSimpleName(); /** - * Create a new note id for adding a new note to databases + * 创建新笔记并返回其ID + *

+ * 在数据库中创建一个新的笔记记录,并返回生成的笔记ID。 + * 新笔记将包含默认的创建时间、修改时间、类型等信息。 + *

+ * + * @param context 应用上下文,用于获取ContentResolver + * @param folderId 笔记所属文件夹ID + * @return 新创建的笔记ID */ public static synchronized long getNewNoteId(Context context, long folderId) { // Create a new note in the database @@ -65,41 +91,114 @@ public class Note { return noteId; } + /** + * 构造方法,初始化笔记数据模型 + */ public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } + /** + * 设置笔记的基本属性值 + *

+ * 更新笔记的基本属性,并标记为本地修改状态,同时更新修改时间。 + *

+ * + * @param key 属性键名,对应NoteColumns中的字段名 + * @param value 属性值 + */ public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置笔记文本数据 + *

+ * 更新笔记的文本内容数据,并标记为本地修改状态。 + *

+ * + * @param key 文本数据键名,对应TextNote中的字段名 + * @param value 文本数据值 + */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + /** + * 设置文本数据ID + *

+ * 设置与当前笔记关联的文本数据记录ID。 + *

+ * + * @param id 文本数据ID + */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + /** + * 获取文本数据ID + *

+ * 获取与当前笔记关联的文本数据记录ID。 + *

+ * + * @return 文本数据ID + */ public long getTextDataId() { return mNoteData.mTextDataId; } + /** + * 设置通话数据ID + *

+ * 设置与当前笔记关联的通话数据记录ID。 + *

+ * + * @param id 通话数据ID + */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + /** + * 设置通话数据 + *

+ * 更新笔记的通话内容数据,并标记为本地修改状态。 + *

+ * + * @param key 通话数据键名,对应CallNote中的字段名 + * @param value 通话数据值 + */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + /** + * 检查笔记是否在本地被修改 + *

+ * 检查笔记的基本属性或具体数据是否在本地被修改过。 + *

+ * + * @return 如果有本地修改则返回true,否则返回false + */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + /** + * 执行笔记同步操作,将本地修改提交到数据库 + *

+ * 将本地对笔记的修改同步到数据库中,包括基本属性和具体数据的更新。 + * 如果同步失败,会记录错误日志但仍尝试完成数据更新。 + *

+ * + * @param context 应用上下文,用于获取ContentResolver + * @param noteId 笔记ID + * @return 同步成功返回true,失败返回false + */ public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -130,17 +229,42 @@ public class Note { return true; } + /** + * 笔记数据内部类,负责管理笔记的文本和通话数据。 + *

+ * 该内部类封装了笔记的具体内容数据,包括文本笔记和通话笔记, + * 并提供了数据的设置和持久化方法。 + *

+ */ private class NoteData { + /** + * 文本数据ID + */ private long mTextDataId; + /** + * 文本数据内容 + */ private ContentValues mTextDataValues; + /** + * 通话数据ID + */ private long mCallDataId; + /** + * 通话数据内容 + */ private ContentValues mCallDataValues; - private static final String TAG = "NoteData"; + /** + * 日志标签 + */ + private static final String TAG = NoteData.class.getSimpleName(); + /** + * 构造方法,初始化笔记数据 + */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -148,10 +272,26 @@ public class Note { mCallDataId = 0; } + /** + * 检查笔记数据是否在本地被修改 + *

+ * 检查文本数据或通话数据是否在本地被修改过。 + *

+ * + * @return 如果有本地修改则返回true,否则返回false + */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + /** + * 设置文本数据ID + *

+ * 设置与当前笔记关联的文本数据记录ID。 + *

+ * + * @param id 文本数据ID + */ void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +299,14 @@ public class Note { mTextDataId = id; } + /** + * 设置通话数据ID + *

+ * 设置与当前笔记关联的通话数据记录ID。 + *

+ * + * @param id 通话数据ID + */ void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,18 +314,47 @@ public class Note { mCallDataId = id; } + /** + * 设置通话数据 + *

+ * 更新笔记的通话内容数据,并标记为本地修改状态。 + *

+ * + * @param key 通话数据键名,对应CallNote中的字段名 + * @param value 通话数据值 + */ void setCallData(String key, String value) { mCallDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置文本数据 + *

+ * 更新笔记的文本内容数据,并标记为本地修改状态。 + *

+ * + * @param key 文本数据键名,对应TextNote中的字段名 + * @param value 文本数据值 + */ void setTextData(String key, String value) { mTextDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 将笔记数据推送到ContentResolver进行持久化 + *

+ * 将本地修改的文本数据或通话数据持久化到数据库中。 + * 如果数据ID为0,则执行插入操作;否则执行更新操作。 + *

+ * + * @param context 应用上下文,用于获取ContentResolver + * @param noteId 笔记ID + * @return 成功返回笔记URI,失败返回null + */ Uri pushIntoContentResolver(Context context, long noteId) { /** * Check for safety diff --git a/src/main/java/net/micode/notes/model/WorkingNote.java b/src/main/java/net/micode/notes/model/WorkingNote.java index be081e4..36003cc 100644 --- a/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/src/main/java/net/micode/notes/model/WorkingNote.java @@ -32,36 +32,87 @@ import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; +/** + * 工作笔记类,负责管理笔记的业务逻辑和UI状态。 + *

+ * 该类是笔记应用的业务逻辑封装类,提供了笔记的创建、加载、保存等功能, + * 负责管理笔记的UI状态和业务逻辑,是UI层与数据层之间的桥梁。 + *

+ */ public class WorkingNote { - // Note for the working note + /** + * 内部笔记数据对象 + */ private Note mNote; - // Note Id + + /** + * 笔记ID + */ private long mNoteId; - // Note content + + /** + * 笔记内容 + */ private String mContent; - // Note mode + + /** + * 笔记模式(普通模式或清单模式) + */ private int mMode; + /** + * 提醒日期时间戳 + */ private long mAlertDate; + /** + * 最后修改日期时间戳 + */ private long mModifiedDate; + /** + * 背景颜色ID + */ private int mBgColorId; + /** + * 小部件ID + */ private int mWidgetId; + /** + * 小部件类型 + */ private int mWidgetType; + /** + * 文件夹ID + */ private long mFolderId; + /** + * 应用上下文 + */ private Context mContext; - private static final String TAG = "WorkingNote"; + /** + * 日志标签 + */ + private static final String TAG = WorkingNote.class.getSimpleName(); + /** + * 删除标记 + */ private boolean mIsDeleted; + /** + * 笔记设置变更监听器 + */ private NoteSettingChangedListener mNoteSettingStatusListener; + /** + * 数据查询投影,用于从ContentResolver获取笔记数据 + */ public static final String[] DATA_PROJECTION = new String[] { DataColumns.ID, DataColumns.CONTENT, @@ -72,6 +123,9 @@ public class WorkingNote { DataColumns.DATA4, }; + /** + * 笔记查询投影,用于从ContentResolver获取笔记基本信息 + */ public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -81,27 +135,66 @@ public class WorkingNote { NoteColumns.MODIFIED_DATE }; + /** + * 数据查询结果列索引:数据ID + */ private static final int DATA_ID_COLUMN = 0; + /** + * 数据查询结果列索引:数据内容 + */ private static final int DATA_CONTENT_COLUMN = 1; + /** + * 数据查询结果列索引:数据类型 + */ private static final int DATA_MIME_TYPE_COLUMN = 2; + /** + * 数据查询结果列索引:数据模式 + */ private static final int DATA_MODE_COLUMN = 3; + /** + * 笔记查询结果列索引:父文件夹ID + */ private static final int NOTE_PARENT_ID_COLUMN = 0; + /** + * 笔记查询结果列索引:提醒日期 + */ private static final int NOTE_ALERTED_DATE_COLUMN = 1; + /** + * 笔记查询结果列索引:背景颜色ID + */ private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + /** + * 笔记查询结果列索引:小部件ID + */ private static final int NOTE_WIDGET_ID_COLUMN = 3; + /** + * 笔记查询结果列索引:小部件类型 + */ private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + /** + * 笔记查询结果列索引:修改日期 + */ private static final int NOTE_MODIFIED_DATE_COLUMN = 5; // New note construct + /** + * 构造新笔记的私有构造方法 + *

+ * 初始化一个新的工作笔记对象,设置默认属性值。 + *

+ * + * @param context 应用上下文 + * @param folderId 笔记所属文件夹ID + */ private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -115,6 +208,16 @@ public class WorkingNote { } // Existing note construct + /** + * 构造现有笔记的私有构造方法 + *

+ * 根据笔记ID初始化一个工作笔记对象,并从数据库加载笔记数据。 + *

+ * + * @param context 应用上下文 + * @param noteId 笔记ID + * @param folderId 笔记所属文件夹ID + */ private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; @@ -124,6 +227,13 @@ public class WorkingNote { loadNote(); } + /** + * 从数据库加载笔记基本信息 + *

+ * 从ContentResolver中查询并加载笔记的基本信息,包括文件夹ID、背景颜色、 + * 小部件信息、提醒日期和修改日期等。 + *

+ */ private void loadNote() { Cursor cursor = mContext.getContentResolver().query( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, @@ -146,6 +256,12 @@ public class WorkingNote { loadNoteData(); } + /** + * 从数据库加载笔记详细数据 + *

+ * 从ContentResolver中查询并加载笔记的详细数据,包括文本内容和模式等。 + *

+ */ private void loadNoteData() { Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { @@ -174,6 +290,19 @@ public class WorkingNote { } } + /** + * 创建空笔记的静态工厂方法 + *

+ * 创建一个新的空工作笔记对象,并设置默认属性值。 + *

+ * + * @param context 应用上下文 + * @param folderId 笔记所属文件夹ID + * @param widgetId 小部件ID + * @param widgetType 小部件类型 + * @param defaultBgColorId 默认背景颜色ID + * @return 创建的空工作笔记 + */ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); @@ -183,10 +312,29 @@ public class WorkingNote { return note; } + /** + * 加载现有笔记的静态工厂方法 + *

+ * 根据笔记ID加载一个现有的工作笔记对象。 + *

+ * + * @param context 应用上下文 + * @param id 笔记ID + * @return 加载的工作笔记 + */ public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } + /** + * 保存笔记到数据库 + *

+ * 将当前工作笔记保存到数据库中。如果笔记不存在,则创建新笔记; + * 如果笔记已存在,则更新现有笔记。 + *

+ * + * @return 保存成功返回true,否则返回false + */ public synchronized boolean saveNote() { if (isWorthSaving()) { if (!existInDatabase()) { @@ -212,10 +360,27 @@ public class WorkingNote { } } + /** + * 检查笔记是否存在于数据库 + *

+ * 检查当前工作笔记是否已存在于数据库中。 + *

+ * + * @return 存在返回true,否则返回false + */ public boolean existInDatabase() { return mNoteId > 0; } + /** + * 检查笔记是否值得保存 + *

+ * 检查当前工作笔记是否值得保存到数据库中。如果笔记已删除、 + * 内容为空且不存在于数据库中,或者存在于数据库但未修改,则不值得保存。 + *

+ * + * @return 值得保存返回true,否则返回false + */ private boolean isWorthSaving() { if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { @@ -225,10 +390,28 @@ public class WorkingNote { } } + /** + * 设置笔记设置变更监听器 + *

+ * 设置笔记设置变更监听器,用于监听笔记设置的变化,如背景颜色、 + * 提醒日期、小部件等的变化。 + *

+ * + * @param l 监听器实例 + */ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } + /** + * 设置提醒日期 + *

+ * 设置笔记的提醒日期,并通知监听器。 + *

+ * + * @param date 提醒日期时间戳 + * @param set 是否设置提醒 + */ public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; @@ -239,6 +422,14 @@ public class WorkingNote { } } + /** + * 标记笔记是否删除 + *

+ * 标记笔记是否已删除,并通知监听器。 + *

+ * + * @param mark 删除标记 + */ public void markDeleted(boolean mark) { mIsDeleted = mark; if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -247,6 +438,14 @@ public class WorkingNote { } } + /** + * 设置笔记背景颜色ID + *

+ * 设置笔记的背景颜色ID,并通知监听器。 + *

+ * + * @param id 背景颜色ID + */ public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; @@ -257,6 +456,14 @@ public class WorkingNote { } } + /** + * 设置笔记的清单模式 + *

+ * 设置笔记的模式(普通模式或清单模式),并通知监听器。 + *

+ * + * @param mode 清单模式 + */ public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -267,6 +474,14 @@ public class WorkingNote { } } + /** + * 设置小部件类型 + *

+ * 设置与当前笔记关联的小部件类型。 + *

+ * + * @param type 小部件类型 + */ public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; @@ -274,6 +489,14 @@ public class WorkingNote { } } + /** + * 设置小部件ID + *

+ * 设置与当前笔记关联的小部件ID。 + *

+ * + * @param id 小部件ID + */ public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; @@ -281,6 +504,14 @@ public class WorkingNote { } } + /** + * 设置笔记内容 + *

+ * 设置笔记的文本内容。 + *

+ * + * @param text 笔记内容 + */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; @@ -288,80 +519,208 @@ public class WorkingNote { } } + /** + * 将笔记转换为通话笔记 + *

+ * 将当前笔记转换为通话笔记,并设置通话相关信息,如电话号码和通话日期。 + *

+ * + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + */ public void convertToCallNote(String phoneNumber, long callDate) { mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); } + /** + * 检查笔记是否有闹钟提醒 + *

+ * 检查当前笔记是否设置了提醒日期。 + *

+ * + * @return 有提醒返回true,否则返回false + */ public boolean hasClockAlert() { return (mAlertDate > 0 ? true : false); } + /** + * 获取笔记内容 + *

+ * 获取当前笔记的文本内容。 + *

+ * + * @return 笔记内容 + */ public String getContent() { return mContent; } + /** + * 获取提醒日期 + *

+ * 获取当前笔记的提醒日期时间戳。 + *

+ * + * @return 提醒日期时间戳 + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取修改日期 + *

+ * 获取当前笔记的最后修改日期时间戳。 + *

+ * + * @return 修改日期时间戳 + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取背景颜色资源ID + *

+ * 根据背景颜色ID获取对应的背景颜色资源ID。 + *

+ * + * @return 背景颜色资源ID + */ public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } + /** + * 获取背景颜色ID + *

+ * 获取当前笔记的背景颜色ID。 + *

+ * + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取标题背景资源ID + *

+ * 根据背景颜色ID获取对应的标题背景资源ID。 + *

+ * + * @return 标题背景资源ID + */ public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } + /** + * 获取清单模式 + *

+ * 获取当前笔记的模式(普通模式或清单模式)。 + *

+ * + * @return 清单模式 + */ public int getCheckListMode() { return mMode; } + /** + * 获取笔记ID + *

+ * 获取当前笔记的ID。 + *

+ * + * @return 笔记ID + */ public long getNoteId() { return mNoteId; } + /** + * 获取文件夹ID + *

+ * 获取当前笔记所属的文件夹ID。 + *

+ * + * @return 文件夹ID + */ public long getFolderId() { return mFolderId; } + /** + * 获取小部件ID + *

+ * 获取与当前笔记关联的小部件ID。 + *

+ * + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取小部件类型 + *

+ * 获取与当前笔记关联的小部件类型。 + *

+ * + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 笔记设置变更监听器,用于监听笔记设置的变化。 + *

+ * 该接口定义了笔记设置变化的回调方法,如背景颜色、提醒日期、 + * 小部件等的变化回调。 + *

+ */ public interface NoteSettingChangedListener { /** - * Called when the background color of current note has just changed + * 当笔记背景颜色改变时调用 + *

+ * 当笔记的背景颜色发生变化时,会调用该方法。 + *

*/ void onBackgroundColorChanged(); /** - * Called when user set clock + * 当笔记闹钟设置改变时调用 + *

+ * 当笔记的提醒日期发生变化时,会调用该方法。 + *

+ * + * @param date 闹钟日期时间戳 + * @param set 是否设置闹钟 */ void onClockAlertChanged(long date, boolean set); /** - * Call when user create note from widget + * 当笔记小部件改变时调用 + *

+ * 当与笔记关联的小部件发生变化时,会调用该方法。 + *

*/ void onWidgetChanged(); /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode + * 当笔记在清单模式和普通模式之间切换时调用 + *

+ * 当笔记的模式在普通模式和清单模式之间切换时,会调用该方法。 + *

+ * + * @param oldMode 切换前的模式 + * @param newMode 切换后的新模式 */ void onCheckListModeChanged(int oldMode, int newMode); } diff --git a/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..fd4c758 100644 --- a/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -40,12 +40,48 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; +/** + * 闹钟提醒活动类 + *

+ * 该类用于处理笔记的闹钟提醒功能,当设置了提醒的笔记到达指定时间时, + * 会显示提醒对话框并播放提醒铃声,用户可以选择关闭提醒或进入笔记编辑界面 + *

+ */ public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + /** + * 触发提醒的笔记ID + */ private long mNoteId; + + /** + * 笔记内容的摘要,用于在提醒对话框中显示 + */ private String mSnippet; + + /** + * 提醒对话框中显示的摘要最大长度 + */ private static final int SNIPPET_PREW_MAX_LEN = 60; + + /** + * 用于播放提醒铃声的媒体播放器 + */ MediaPlayer mPlayer; + /** + * 活动创建时的初始化方法 + *

+ * 该方法完成以下初始化工作: + * 1. 设置窗口特性,隐藏标题栏 + * 2. 配置窗口标志,确保在锁屏状态下也能显示 + * 3. 如果屏幕未开启,则添加额外标志以唤醒屏幕并保持点亮 + * 4. 从Intent中获取笔记ID和摘要信息 + * 5. 创建MediaPlayer实例用于播放提醒铃声 + * 6. 检查笔记是否存在于数据库中 + * 7. 如果笔记存在,则显示提醒对话框并播放提醒铃声 + *

+ * @param savedInstanceState 保存的实例状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -83,11 +119,31 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 检查屏幕是否处于开启状态 + *

+ * 该方法通过PowerManager系统服务获取当前屏幕状态 + *

+ * @return 如果屏幕处于开启状态则返回true,否则返回false + */ private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + /** + * 播放提醒铃声 + *

+ * 该方法完成以下工作: + * 1. 获取系统默认的闹钟铃声URI + * 2. 检查系统的静音模式设置,确定应该使用的音频流类型 + * 3. 根据静音模式设置选择合适的音频流类型 + * 4. 设置MediaPlayer的数据源为获取到的铃声URI + * 5. 准备MediaPlayer并设置为循环播放模式 + * 6. 开始播放提醒铃声 + * 7. 处理可能出现的各种异常 + *

+ */ private void playAlarmSound() { Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); @@ -119,6 +175,17 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 显示提醒对话框 + *

+ * 该方法创建并显示一个提醒对话框,包含以下内容: + * 1. 设置对话框标题为应用名称 + * 2. 设置对话框消息为笔记的摘要内容 + * 3. 添加确定按钮,点击后关闭对话框 + * 4. 如果屏幕处于开启状态,添加进入按钮,点击后进入笔记编辑界面 + * 5. 设置对话框关闭监听器,用于在对话框关闭时停止提醒铃声并结束活动 + *

+ */ private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.app_name); @@ -130,6 +197,18 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD dialog.show().setOnDismissListener(this); } + /** + * 处理提醒对话框中的按钮点击事件 + *

+ * 该方法实现了OnClickListener接口,根据点击的按钮执行不同的操作: + * 1. 如果点击的是负按钮(进入按钮),则创建Intent跳转到NoteEditActivity + * 2. 设置Intent的Action为ACTION_VIEW,并将笔记ID作为额外数据传递 + * 3. 启动NoteEditActivity让用户查看或编辑对应的笔记 + * 4. 如果点击的是其他按钮(如确定按钮),则不执行任何额外操作 + *

+ * @param dialog 触发点击事件的对话框 + * @param which 点击的按钮ID,对应DialogInterface中的按钮常量 + */ public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: @@ -143,11 +222,29 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 处理提醒对话框关闭事件 + *

+ * 该方法实现了OnDismissListener接口,当提醒对话框被关闭时(无论点击了哪个按钮或通过其他方式关闭), + * 会停止提醒铃声并结束当前活动 + *

+ * @param dialog 被关闭的对话框 + */ public void onDismiss(DialogInterface dialog) { stopAlarmSound(); finish(); } + /** + * 停止提醒铃声并释放媒体播放器资源 + *

+ * 该方法完成以下工作: + * 1. 检查媒体播放器是否存在 + * 2. 如果存在,则停止播放铃声 + * 3. 释放媒体播放器占用的资源 + * 4. 将媒体播放器引用置为null,防止内存泄漏 + *

+ */ private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); diff --git a/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..4de50d9 100644 --- a/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -28,16 +28,44 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +/** + * 闹钟初始化广播接收器 + *

+ * 该广播接收器负责在系统启动时重新注册所有未触发的笔记闹钟。 + * 当系统重启后,之前设置的闹钟会丢失,需要通过该接收器重新初始化 + *

+ */ public class AlarmInitReceiver extends BroadcastReceiver { - + /** + * 闹钟数据查询的投影列数组 + *

+ * 定义了从数据库查询笔记闹钟数据时需要获取的列,包括笔记ID和闹钟触发日期 + *

+ */ private static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE }; + /** + * 查询结果中笔记ID列的索引 + */ private static final int COLUMN_ID = 0; + + /** + * 查询结果中闹钟触发日期列的索引 + */ private static final int COLUMN_ALERTED_DATE = 1; + /** + * 接收广播时调用的方法 + *

+ * 当系统发送广播时(通常是系统启动完成),该方法会查询所有设置了未来闹钟的笔记, + * 并使用AlarmManager重新注册这些闹钟 + *

+ * @param context 上下文对象 + * @param intent 接收到的广播意图 + */ @Override public void onReceive(Context context, Intent intent) { long currentDate = System.currentTimeMillis(); @@ -54,8 +82,8 @@ public class AlarmInitReceiver extends BroadcastReceiver { Intent sender = new Intent(context, AlarmReceiver.class); sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); - AlarmManager alermManager = (AlarmManager) context - .getSystemService(Context.ALARM_SERVICE); + AlarmManager alermManager = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); } while (c.moveToNext()); } diff --git a/src/main/java/net/micode/notes/ui/AlarmReceiver.java b/src/main/java/net/micode/notes/ui/AlarmReceiver.java index 54e503b..610716c 100644 --- a/src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -20,7 +20,21 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +/** + * 闹钟触发广播接收器 + *

+ * 该广播接收器负责处理闹钟触发事件,当闹钟时间到达时,它会启动闹钟提醒活动 + *

+ */ public class AlarmReceiver extends BroadcastReceiver { + /** + * 接收闹钟触发广播时调用的方法 + *

+ * 当闹钟时间到达时,该方法会启动AlarmAlertActivity来显示闹钟提醒 + *

+ * @param context 上下文对象 + * @param intent 接收到的广播意图,包含闹钟相关的笔记信息 + */ @Override public void onReceive(Context context, Intent intent) { intent.setClass(context, AlarmAlertActivity.class); diff --git a/src/main/java/net/micode/notes/ui/DateTimePicker.java b/src/main/java/net/micode/notes/ui/DateTimePicker.java index 496b0cd..c805b60 100644 --- a/src/main/java/net/micode/notes/ui/DateTimePicker.java +++ b/src/main/java/net/micode/notes/ui/DateTimePicker.java @@ -28,40 +28,139 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +/** + * 日期时间选择组件 + *

+ * 该类是一个自定义的日期时间选择组件,继承自FrameLayout,用于在小米便签应用中 + * 提供直观的日期时间选择界面。它支持12/24小时制切换,并通过NumberPicker组件 + * 实现日期、小时、分钟和AM/PM的选择 + *

+ */ public class DateTimePicker extends FrameLayout { + /** + * 默认启用状态 + */ private static final boolean DEFAULT_ENABLE_STATE = true; + /** + * 半天的小时数(12小时) + */ private static final int HOURS_IN_HALF_DAY = 12; + + /** + * 全天的小时数(24小时) + */ private static final int HOURS_IN_ALL_DAY = 24; + + /** + * 一周的天数(7天) + */ private static final int DAYS_IN_ALL_WEEK = 7; + + /** + * 日期选择器的最小值 + */ private static final int DATE_SPINNER_MIN_VAL = 0; + + /** + * 日期选择器的最大值 + */ private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + + /** + * 24小时制下小时选择器的最小值 + */ private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + + /** + * 24小时制下小时选择器的最大值 + */ private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + + /** + * 12小时制下小时选择器的最小值 + */ private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + + /** + * 12小时制下小时选择器的最大值 + */ private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + + /** + * 分钟选择器的最小值 + */ private static final int MINUT_SPINNER_MIN_VAL = 0; + + /** + * 分钟选择器的最大值 + */ private static final int MINUT_SPINNER_MAX_VAL = 59; + + /** + * AM/PM选择器的最小值 + */ private static final int AMPM_SPINNER_MIN_VAL = 0; + + /** + * AM/PM选择器的最大值 + */ private static final int AMPM_SPINNER_MAX_VAL = 1; + /** + * 日期选择器组件 + */ private final NumberPicker mDateSpinner; + + /** + * 小时选择器组件 + */ private final NumberPicker mHourSpinner; + + /** + * 分钟选择器组件 + */ private final NumberPicker mMinuteSpinner; + + /** + * AM/PM选择器组件 + */ private final NumberPicker mAmPmSpinner; + + /** + * 当前选择的日期和时间 + */ private Calendar mDate; + /** + * 日期选择器显示的日期值数组 + */ private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + /** + * 是否为上午 + */ private boolean mIsAm; + /** + * 是否为24小时制视图 + */ private boolean mIs24HourView; + /** + * 组件是否启用 + */ private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + /** + * 是否正在初始化 + */ private boolean mInitialising; + /** + * 日期时间变化监听器 + */ private OnDateTimeChangedListener mOnDateTimeChangedListener; private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { @@ -158,19 +257,49 @@ public class DateTimePicker extends FrameLayout { } }; + /** + * 日期时间变化监听器接口 + *

+ * 当日期时间选择器的值发生变化时,会通过此接口通知外部 + *

+ */ public interface OnDateTimeChangedListener { + /** + * 日期时间变化时调用的方法 + * @param view 日期时间选择器实例 + * @param year 选择的年份 + * @param month 选择的月份(0-11) + * @param dayOfMonth 选择的日(1-31) + * @param hourOfDay 选择的小时(0-23) + * @param minute 选择的分钟(0-59) + */ void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute); } + /** + * 构造函数,使用当前时间初始化日期时间选择器 + * @param context 上下文对象 + */ public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + /** + * 构造函数,使用指定时间初始化日期时间选择器,根据系统设置决定是否使用24小时制 + * @param context 上下文对象 + * @param date 初始日期时间,以毫秒为单位 + */ public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + /** + * 构造函数,使用指定时间和时间格式初始化日期时间选择器 + * @param context 上下文对象 + * @param date 初始日期时间,以毫秒为单位 + * @param is24HourView 是否使用24小时制 + */ public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); mDate = Calendar.getInstance(); @@ -348,6 +477,15 @@ public class DateTimePicker extends FrameLayout { return mDate.get(Calendar.HOUR_OF_DAY); } + /** + * 获取当前小时 + *

+ * 根据当前视图模式返回正确的小时值: + * - 24小时制:直接返回当前小时(0-23) + * - 12小时制:返回1-12之间的小时值 + *

+ * @return 当前小时值 + */ private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); @@ -362,9 +500,8 @@ public class DateTimePicker extends FrameLayout { } /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay + * 设置当前小时(24小时制,范围0-23) + * @param hourOfDay 当前小时值(24小时制) */ public void setCurrentHour(int hourOfDay) { if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { @@ -390,16 +527,16 @@ public class DateTimePicker extends FrameLayout { } /** - * Get currentMinute - * - * @return The Current Minute + * 获取当前分钟 + * @return 当前分钟值(0-59) */ public int getCurrentMinute() { return mDate.get(Calendar.MINUTE); } /** - * Set current minute + * 设置当前分钟 + * @param minute 当前分钟值(0-59) */ public void setCurrentMinute(int minute) { if (!mInitialising && minute == getCurrentMinute()) { @@ -411,16 +548,16 @@ public class DateTimePicker extends FrameLayout { } /** - * @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) { @@ -434,6 +571,12 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } + /** + * 更新日期控件 + *

+ * 该方法负责更新日期选择器的显示值,显示当前日期前后几天的日期 + *

+ */ private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); @@ -448,6 +591,12 @@ public class DateTimePicker extends FrameLayout { mDateSpinner.invalidate(); } + /** + * 更新AM/PM控件 + *

+ * 该方法负责根据当前时间和视图模式更新AM/PM选择器的显示 + *

+ */ private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); @@ -458,6 +607,12 @@ public class DateTimePicker extends FrameLayout { } } + /** + * 更新小时控件 + *

+ * 该方法负责根据当前视图模式更新小时选择器的最小值和最大值 + *

+ */ private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); @@ -469,13 +624,19 @@ public class DateTimePicker extends FrameLayout { } /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing + * 设置日期时间变化监听器 + * @param callback 日期时间变化时的回调函数,若为null则不执行任何操作 */ public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { mOnDateTimeChangedListener = callback; } + /** + * 日期时间变化时调用的方法 + *

+ * 当日期或时间发生变化时,该方法会通知注册的监听器 + *

+ */ private void onDateTimeChanged() { if (mOnDateTimeChangedListener != null) { mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), diff --git a/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..b9986ac 100644 --- a/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,17 +29,55 @@ 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(); + + /** + * 是否使用24小时制视图 + */ private boolean mIs24HourView; + + /** + * 日期时间设置监听器,用于通知外部选择结果 + */ private OnDateTimeSetListener mOnDateTimeSetListener; + + /** + * 日期时间选择器组件 + */ private DateTimePicker mDateTimePicker; - + /** + * 日期时间设置监听器接口 + *

+ * 当用户在对话框中确认选择日期时间后,会通过此接口通知外部 + *

+ */ public interface OnDateTimeSetListener { + /** + * 日期时间设置完成时调用的方法 + * @param dialog 对话框实例 + * @param date 选择的日期时间,以毫秒为单位 + */ void OnDateTimeSet(AlertDialog dialog, long date); } + /** + * 构造函数,使用指定日期初始化日期时间选择对话框 + * @param context 上下文对象 + * @param date 初始日期时间,以毫秒为单位 + */ public DateTimePickerDialog(Context context, long date) { super(context); mDateTimePicker = new DateTimePicker(context); @@ -64,14 +102,29 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener 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 | @@ -81,6 +134,15 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + /** + * 点击对话框按钮时调用的方法 + *

+ * 当用户点击对话框的确定按钮时,会调用该方法,通知注册的监听器 + *

+ * @param arg0 对话框实例 + * @param arg1 点击的按钮ID + */ + @Override public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); diff --git a/src/main/java/net/micode/notes/ui/DropdownMenu.java b/src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..4e58bbb 100644 --- a/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -27,11 +27,34 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +/** + * 下拉菜单封装类 + *

+ * 该类封装了Android的PopupMenu,提供了一个简单的接口来创建和管理下拉菜单 + *

+ */ public class DropdownMenu { + /** + * 触发下拉菜单的按钮 + */ private Button mButton; + + /** + * 下拉菜单实例 + */ private PopupMenu mPopupMenu; + + /** + * 菜单对象 + */ private Menu mMenu; + /** + * 构造函数,创建一个下拉菜单 + * @param context 上下文对象 + * @param button 触发下拉菜单的按钮 + * @param menuId 菜单资源ID + */ public DropdownMenu(Context context, Button button, int menuId) { mButton = button; mButton.setBackgroundResource(R.drawable.dropdown_icon); @@ -45,16 +68,29 @@ public class DropdownMenu { }); } + /** + * 设置下拉菜单项点击监听器 + * @param listener 菜单项点击监听器 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 查找指定ID的菜单项 + * @param id 菜单项ID + * @return 指定ID的菜单项,若不存在则返回null + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置下拉菜单按钮的标题 + * @param title 按钮标题 + */ public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..7e05bf2 100644 --- a/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -29,25 +29,62 @@ 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 }; + /** + * 查询结果中文件夹ID列的索引 + */ public static final int ID_COLUMN = 0; + + /** + * 查询结果中文件夹名称列的索引 + */ public static final int NAME_COLUMN = 1; + /** + * 构造函数,创建文件夹列表适配器 + * @param context 上下文对象 + * @param c 包含文件夹数据的游标 + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); // TODO Auto-generated constructor stub } + /** + * 创建新的列表项视图 + * @param context 上下文对象 + * @param cursor 包含文件夹数据的游标 + * @param parent 父视图组 + * @return 新创建的列表项视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 将数据绑定到列表项视图 + * @param view 列表项视图 + * @param context 上下文对象 + * @param cursor 包含文件夹数据的游标 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { @@ -57,21 +94,44 @@ public class FoldersListAdapter extends CursorAdapter { } } + /** + * 获取指定位置的文件夹名称 + * @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); } + /** + * 文件夹列表项视图 + *

+ * 该内部类用于显示单个文件夹列表项 + *

+ */ private class FolderListItem extends LinearLayout { + /** + * 文件夹名称文本视图 + */ private TextView mName; + /** + * 构造函数,创建文件夹列表项视图 + * @param context 上下文对象 + */ public FolderListItem(Context context) { super(context); inflate(context, R.layout.folder_list_item, this); mName = (TextView) findViewById(R.id.tv_folder_name); } + /** + * 绑定文件夹名称到视图 + * @param name 文件夹名称 + */ public void bind(String name) { mName.setText(name); } diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..088dac7 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -72,8 +72,18 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * 笔记编辑活动类,负责处理笔记的创建、编辑和保存操作。 + *

+ * 该类实现了笔记编辑界面的所有功能,包括文本编辑、背景颜色设置、字体大小调整、 + * 提醒设置、分享等功能,同时支持普通文本模式和清单模式的切换。 + *

+ */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + /** + * 头部视图持有者类,用于缓存头部视图的控件引用 + */ private class HeadViewHolder { public TextView tvModified; @@ -84,6 +94,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, public ImageView ibSetBgColor; } + /** 背景颜色选择按钮映射表,将按钮ID映射到颜色ID */ private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +104,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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,6 +114,7 @@ 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); @@ -110,6 +123,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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,37 +132,59 @@ 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 SharedPreferences mSharedPrefs; + /** 字体大小ID */ private int mFontSizeId; + /** 字体大小偏好设置键 */ private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + /** 快捷图标标题最大长度 */ 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'); + /** 清单模式下的编辑文本列表 */ private LinearLayout mEditTextList; + /** 用户查询字符串 */ private String mUserQuery; + /** 查询正则表达式模式 */ private Pattern mPattern; + /** + * 活动创建时调用,初始化界面和状态 + * + * @param savedInstanceState 保存的实例状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -179,6 +215,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 初始化活动状态,根据传入的Intent处理不同的操作 + * + * @param intent 包含操作信息的Intent + * @return 初始化是否成功 + */ private boolean initActivityState(Intent intent) { /** * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, @@ -262,12 +304,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 活动恢复时调用,初始化笔记屏幕 + */ @Override protected void onResume() { super.onResume(); initNoteScreen(); } + /** + * 初始化笔记屏幕显示,包括字体样式、显示模式、背景颜色等 + */ private void initNoteScreen() { mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); @@ -295,6 +343,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, showAlertHeader(); } + /** + * 显示提醒头部信息,包括提醒图标和提醒时间 + */ private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); @@ -312,12 +363,22 @@ public class NoteEditActivity extends Activity implements OnClickListener, }; } + /** + * 处理活动收到的新Intent + * + * @param intent 新的Intent对象 + */ @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } + /** + * 保存活动状态,当活动可能被销毁时调用 + * + * @param outState 用于保存状态的Bundle对象 + */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -333,6 +394,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } + /** + * 分发触摸事件,当点击外部区域时关闭颜色选择器和字体大小选择器 + * + * @param ev 触摸事件对象 + * @return 事件是否被消费 + */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE @@ -349,6 +416,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return super.dispatchTouchEvent(ev); } + /** + * 检查触摸事件是否在指定视图范围内 + * + * @param view 要检查的视图 + * @param ev 触摸事件对象 + * @return 触摸事件是否在视图范围内 + */ private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); @@ -363,6 +437,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 初始化资源,包括视图、事件监听器等 + */ private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); @@ -397,6 +474,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } + /** + * 活动暂停时调用,保存笔记并清除设置状态 + */ @Override protected void onPause() { super.onPause(); @@ -406,6 +486,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, clearSettingState(); } + /** + * 更新小组件,根据笔记的小组件类型发送更新广播 + */ private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { @@ -425,6 +508,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, setResult(RESULT_OK, intent); } + /** + * 处理视图的点击事件 + * + * @param v 被点击的视图对象 + */ public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_set_bg_color) { @@ -452,6 +540,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 处理返回按钮点击事件 + *

+ * 先尝试清除设置状态,如果成功则不执行返回操作,否则保存笔记并执行返回 + *

+ */ @Override public void onBackPressed() { if(clearSettingState()) { @@ -462,6 +556,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, super.onBackPressed(); } + /** + * 清除设置状态,隐藏所有设置选择器 + * + * @return 如果有设置选择器被隐藏,则返回true;否则返回false + */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); @@ -473,6 +572,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + /** + * 背景颜色改变时调用的回调方法 + *

+ * 显示当前选中的背景颜色,并更新头部面板和编辑面板的背景资源 + *

+ */ public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); @@ -480,6 +585,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + /** + * 准备选项菜单,在菜单显示前调用 + *

+ * 根据笔记类型和当前状态动态配置菜单项,包括加载不同的菜单资源、 + * 设置菜单标题和控制菜单项的可见性 + *

+ * + * @param menu 要准备的菜单对象 + * @return 是否成功准备菜单 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { @@ -505,6 +620,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 处理菜单选项选择事件 + *

+ * 该方法处理各种菜单选项的点击事件,包括创建新笔记、删除笔记、设置字体大小、 + * 切换列表模式、分享笔记、发送到桌面、设置提醒等功能 + *

+ * @param item 被选择的菜单项 + * @return 总是返回true,表示事件已被处理 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -553,6 +677,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 设置笔记的提醒时间 + *

+ * 该方法显示一个日期时间选择对话框,让用户选择提醒时间, + * 并将选择的时间设置到当前编辑的笔记中 + *

+ */ private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { @@ -564,8 +695,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type + * 分享笔记到支持{@link Intent#ACTION_SEND}动作和{@text/plain}类型的应用 + *

+ * 该方法创建一个分享意图,将笔记内容发送给其他支持文本分享的应用程序 + *

+ * @param context 上下文对象 + * @param info 要分享的笔记内容 */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); @@ -574,6 +709,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + /** + * 创建新笔记 + *

+ * 该方法先保存当前编辑的笔记,然后结束当前活动并启动一个新的NoteEditActivity + * 来创建新笔记,确保笔记保存在同一文件夹中 + *

+ */ private void createNewNote() { // Firstly, save current editing notes saveNote(); @@ -586,6 +728,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + /** + * 删除当前编辑的笔记 + *

+ * 该方法根据笔记是否存在于数据库中进行处理: + * 1. 如果笔记存在,将其ID添加到待删除列表 + * 2. 如果不是同步模式,直接删除笔记 + * 3. 如果是同步模式,将笔记移动到垃圾桶 + *

+ */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); @@ -608,15 +759,28 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.markDeleted(true); } + /** + * 检查是否启用了同步模式 + *

+ * 该方法通过检查同步账户名称是否存在来判断是否启用了同步模式 + *

+ * @return 如果启用了同步模式返回true,否则返回false + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 处理时钟提醒的变化 + *

+ * 该方法用于设置或取消笔记的时钟提醒, + * 如果笔记未保存,会先保存笔记,然后根据参数设置或取消闹钟 + *

+ * @param date 提醒时间的时间戳 + * @param set 如果为true则设置提醒,为false则取消提醒 + */ 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(); } @@ -642,10 +806,29 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 更新小部件 + *

+ * 该方法调用updateWidget()方法来更新桌面小部件的内容 + *

+ */ public void onWidgetChanged() { updateWidget(); } + /** + * 处理编辑文本的删除操作 + *

+ * 该方法处理用户在编辑文本时按下删除键的操作,特别是在列表模式下: + * 1. 检查是否只有一个编辑文本项,如果是则直接返回 + * 2. 将索引大于当前删除项的所有编辑文本项的索引减1 + * 3. 从列表中移除指定索引的视图 + * 4. 将被删除的文本追加到前一个或第一个编辑文本项中 + * 5. 设置焦点并将光标移动到追加文本的开始位置 + *

+ * @param index 要删除的编辑文本项的索引 + * @param text 被删除的文本内容 + */ public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { @@ -672,10 +855,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.setSelection(length); } + /** + * 处理编辑文本的回车操作 + *

+ * 该方法处理用户在编辑文本时按下回车键的操作,在指定索引位置插入新的编辑文本项 + *

+ * @param index 插入新编辑文本项的索引位置 + * @param text 新编辑文本项的内容 + */ 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"); } @@ -691,6 +880,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 切换到列表模式 + *

+ * 该方法将笔记从普通文本模式切换到列表模式, + * 将文本内容按行分割并创建对应的列表项,最后添加一个空的列表项用于继续输入 + *

+ * @param text 当前笔记的文本内容 + */ private void switchToListMode(String text) { mEditTextList.removeAllViews(); String[] items = text.split("\n"); @@ -708,6 +905,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList.setVisibility(View.VISIBLE); } + /** + * 获取带有高亮查询结果的文本 + *

+ * 该方法在完整文本中查找用户查询的内容,并为匹配项添加背景高亮效果 + *

+ * @param fullText 完整的文本内容 + * @param userQuery 用户查询的内容 + * @return 带有高亮查询结果的Spannable对象 + */ private Spannable getHighlightQueryResult(String fullText, String userQuery) { SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { @@ -725,6 +931,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, return spannable; } + /** + * 获取列表模式下的列表项视图 + *

+ * 该方法创建并配置列表模式下的列表项视图,包括: + * 1. 加载列表项布局 + * 2. 设置编辑文本的字体大小 + * 3. 配置复选框的选中状态变化监听器 + * 4. 根据项目文本的标签设置初始选中状态和文本样式 + * 5. 设置文本变化监听器和索引 + * 6. 应用查询高亮效果 + *

+ * @param item 列表项的文本内容 + * @param index 列表项的索引 + * @return 配置好的列表项视图 + */ private View getListItem(String item, int index) { View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); @@ -756,6 +977,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, return view; } + /** + * 处理文本变化事件 + *

+ * 该方法在列表模式下处理编辑文本的内容变化事件, + * 根据文本是否为空来控制对应列表项中复选框的显示或隐藏 + *

+ * @param index 编辑文本项的索引 + * @param hasText 编辑文本是否包含内容 + */ public void onTextChange(int index, boolean hasText) { if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); @@ -768,6 +998,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 处理检查列表模式的变化 + *

+ * 该方法在笔记的普通文本模式和检查列表模式之间切换时被调用, + * 根据新的模式执行相应的视图切换和数据处理操作 + *

+ * @param oldMode 旧的模式 + * @param newMode 新的模式 + */ public void onCheckListModeChanged(int oldMode, int newMode) { if (newMode == TextNote.MODE_CHECK_LIST) { switchToListMode(mNoteEditor.getText().toString()); @@ -782,6 +1021,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 获取当前工作笔记的文本内容 + *

+ * 该方法根据当前笔记的模式(普通文本或检查列表)获取相应的文本内容: + * 1. 如果是检查列表模式,遍历所有列表项,收集文本内容并添加相应的标签(已完成或未完成) + * 2. 如果是普通文本模式,直接获取编辑框的文本内容 + *

+ * @return 是否包含已完成的列表项 + */ private boolean getWorkingText() { boolean hasChecked = false; if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { @@ -805,6 +1053,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, return hasChecked; } + /** + * 保存当前工作笔记 + *

+ * 该方法首先获取当前工作笔记的最新文本内容,然后调用mWorkingNote的saveNote方法将其保存到数据库中。 + * 如果保存成功,会设置返回结果为RESULT_OK,用于标识创建/编辑状态,以便在返回列表视图时正确定位。 + *

+ * @return 保存是否成功 + */ private boolean saveNote() { getWorkingText(); boolean saved = mWorkingNote.saveNote(); @@ -821,12 +1077,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, return saved; } + /** + * 将当前编辑的笔记发送到桌面创建快捷方式 + *

+ * 该方法执行以下操作: + * 1. 在发送前确保当前编辑的笔记已保存在数据库中,如果是新笔记则先保存 + * 2. 创建一个快捷方式Intent,包含打开该笔记的相关信息 + * 3. 设置快捷方式的名称、图标等属性 + * 4. 发送广播来创建桌面快捷方式 + * 5. 如果笔记为空或保存失败,显示错误提示 + *

+ */ 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(); } @@ -837,7 +1100,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, shortcutIntent.setAction(Intent.ACTION_VIEW); shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, makeShortcutIconTitle(mWorkingNote.getContent())); sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); @@ -856,6 +1119,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 生成快捷方式图标的标题 + *

+ * 该方法将笔记内容转换为适合作为快捷方式标题的格式: + * 1. 移除检查列表模式下的状态标签(已完成和未完成标签) + * 2. 如果内容长度超过最大限制,则截断为指定长度 + *

+ * @param content 笔记的原始内容 + * @return 处理后的快捷方式标题 + */ private String makeShortcutIconTitle(String content) { content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); @@ -863,10 +1136,26 @@ public class NoteEditActivity extends Activity implements OnClickListener, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } + /** + * 显示短时长的Toast提示信息 + *

+ * 该方法是showToast(int resId, int duration)的便捷重载版本, + * 默认使用Toast.LENGTH_SHORT作为显示时长 + *

+ * @param resId 提示信息的字符串资源ID + */ private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } + /** + * 显示Toast提示信息 + *

+ * 该方法使用Android系统的Toast机制显示指定的提示信息 + *

+ * @param resId 提示信息的字符串资源ID + * @param duration 显示时长,可以是Toast.LENGTH_SHORT或Toast.LENGTH_LONG + */ private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } diff --git a/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/main/java/net/micode/notes/ui/NoteEditText.java index 2afe2a8..ae5b78b 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -37,15 +37,47 @@ 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 SCHEME_TEL = "tel:" ; + + /** + * HTTP链接协议 + */ private static final String SCHEME_HTTP = "http:" ; + + /** + * 邮件链接协议 + */ private static final String SCHEME_EMAIL = "mailto:" ; + /** + * 链接协议与资源ID的映射表 + */ private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); @@ -54,51 +86,86 @@ public class NoteEditText extends EditText { } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 文本视图变化监听器接口 + *

+ * 由{@link NoteEditActivity}调用,用于处理编辑框的删除或添加 + *

*/ public interface OnTextViewChangeListener { /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null + * 当按下{@link KeyEvent#KEYCODE_DEL}且文本为空时,删除当前编辑框 */ void onEditTextDelete(int index, String text); /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen + * 当按下{@link KeyEvent#KEYCODE_ENTER}时,在当前编辑框后添加新的编辑框 */ void onEditTextEnter(int index, String text); /** - * Hide or show item option when text change + * 当文本变化时,隐藏或显示项目选项 */ void onTextChange(int index, boolean hasText); } + /** + * 文本视图变化监听器实例 + */ private OnTextViewChangeListener mOnTextViewChangeListener; + /** + * 构造函数,创建笔记编辑文本框 + * @param context 上下文对象 + */ public NoteEditText(Context context) { super(context, null); mIndex = 0; } + /** + * 设置编辑框索引 + * @param index 编辑框索引 + */ public void setIndex(int index) { mIndex = index; } + /** + * 设置文本视图变化监听器 + * @param listener 文本视图变化监听器 + */ public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + /** + * 构造函数,创建笔记编辑文本框 + * @param context 上下文对象 + * @param attrs 属性集合 + */ public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + /** + * 构造函数,创建笔记编辑文本框 + * @param context 上下文对象 + * @param attrs 属性集合 + * @param defStyle 默认样式 + */ public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } + /** + * 处理触摸事件 + *

+ * 当用户点击编辑框时,将光标定位到点击位置 + *

+ * @param event 触摸事件 + * @return 事件是否被处理 + */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { @@ -121,6 +188,15 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + /** + * 处理按键按下事件 + *

+ * 监听回车键和删除键的按下事件 + *

+ * @param keyCode 按键代码 + * @param event 按键事件 + * @return 事件是否被处理 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -138,6 +214,15 @@ 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) { @@ -167,6 +252,16 @@ 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) { @@ -179,6 +274,13 @@ public class NoteEditText extends EditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单 + *

+ * 当选中的文本包含链接时,创建相应的链接操作菜单 + *

+ * @param menu 上下文菜单 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { diff --git a/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/main/java/net/micode/notes/ui/NoteItemData.java index 0f5a878..2ffa01d 100644 --- a/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -26,7 +26,20 @@ 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, @@ -42,40 +55,166 @@ public class NoteItemData { NoteColumns.WIDGET_TYPE, }; + /** + * 查询结果中笔记ID列的索引 + */ private static final int ID_COLUMN = 0; + + /** + * 查询结果中提醒日期列的索引 + */ private static final int ALERTED_DATE_COLUMN = 1; + + /** + * 查询结果中背景颜色ID列的索引 + */ private static final int BG_COLOR_ID_COLUMN = 2; + + /** + * 查询结果中创建日期列的索引 + */ private static final int CREATED_DATE_COLUMN = 3; + + /** + * 查询结果中是否有附件列的索引 + */ private static final int HAS_ATTACHMENT_COLUMN = 4; + + /** + * 查询结果中修改日期列的索引 + */ private static final int MODIFIED_DATE_COLUMN = 5; + + /** + * 查询结果中笔记数量列的索引 + */ private static final int NOTES_COUNT_COLUMN = 6; + + /** + * 查询结果中父文件夹ID列的索引 + */ private static final int PARENT_ID_COLUMN = 7; + + /** + * 查询结果中笔记摘要列的索引 + */ private static final int SNIPPET_COLUMN = 8; + + /** + * 查询结果中笔记类型列的索引 + */ private static final int TYPE_COLUMN = 9; + + /** + * 查询结果中小组件ID列的索引 + */ private static final int WIDGET_ID_COLUMN = 10; + + /** + * 查询结果中小组件类型列的索引 + */ private static final int WIDGET_TYPE_COLUMN = 11; + /** + * 笔记ID + */ private long mId; + + /** + * 提醒日期 + */ private long mAlertDate; + + /** + * 背景颜色ID + */ private int mBgColorId; + + /** + * 创建日期 + */ private long mCreatedDate; + + /** + * 是否有附件 + */ private boolean mHasAttachment; + + /** + * 修改日期 + */ private long mModifiedDate; + + /** + * 笔记数量(用于文件夹) + */ private int mNotesCount; + + /** + * 父文件夹ID + */ private long mParentId; + + /** + * 笔记摘要 + */ private String mSnippet; + + /** + * 笔记类型 + */ private int mType; + + /** + * 小组件ID + */ 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; + /** + * 构造函数,从游标中初始化笔记列表项数据 + * @param context 上下文对象 + * @param cursor 包含笔记数据的游标 + */ public NoteItemData(Context context, Cursor cursor) { mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); @@ -109,6 +248,13 @@ public class NoteItemData { checkPostion(cursor); } + /** + * 检查当前项在列表中的位置 + *

+ * 该方法用于判断当前项是否为第一项、最后一项、唯一一项,以及是否为文件夹后的笔记 + *

+ * @param cursor 包含笔记数据的游标 + */ private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; @@ -134,90 +280,179 @@ public class NoteItemData { } } + /** + * 判断是否为文件夹后的唯一笔记 + * @return 是否为文件夹后的唯一笔记 + */ public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } + /** + * 判断是否为文件夹后的多个笔记 + * @return 是否为文件夹后的多个笔记 + */ public boolean isMultiFollowingFolder() { return mIsMultiNotesFollowingFolder; } + /** + * 判断是否为最后一项 + * @return 是否为最后一项 + */ public boolean isLast() { return mIsLastItem; } + /** + * 获取通话记录联系人名称 + * @return 通话记录联系人名称 + */ public String getCallName() { return mName; } + /** + * 判断是否为第一项 + * @return 是否为第一项 + */ public boolean isFirst() { return mIsFirstItem; } + /** + * 判断是否为唯一一项 + * @return 是否为唯一一项 + */ 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 是否有附件 + */ 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 + * @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 是否有提醒 + */ public boolean hasAlert() { return (mAlertDate > 0); } + /** + * 判断是否为通话记录 + * @return 是否为通话记录 + */ 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); } diff --git a/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/main/java/net/micode/notes/ui/NotesListActivity.java index e843aec..b72aba0 100644 --- a/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -78,63 +78,114 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * 笔记列表活动类 + *

+ * 该类是应用的主界面,负责显示笔记列表,处理笔记的创建、编辑、删除等操作, + * 支持文件夹管理、笔记同步、导入导出等功能,并提供了多选模式、上下文菜单等交互方式 + *

+ */ 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; + /** 文件夹删除菜单ID */ private static final int MENU_FOLDER_DELETE = 0; + /** 文件夹查看菜单ID */ private static final int MENU_FOLDER_VIEW = 1; + /** 文件夹重命名菜单ID */ private static final int MENU_FOLDER_CHANGE_NAME = 2; + /** 偏好设置:是否添加了介绍笔记 */ private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + /** + * 列表编辑状态枚举 + *

+ * 定义了笔记列表的三种状态: + * 1. NOTE_LIST:根目录下的笔记列表 + * 2. SUB_FOLDER:子文件夹中的笔记列表 + * 3. CALL_RECORD_FOLDER:通话记录文件夹 + *

+ */ private enum ListEditState { NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER }; + /** 当前列表编辑状态 */ private ListEditState mState; + /** 后台查询处理器 */ private BackgroundQueryHandler mBackgroundQueryHandler; + /** 笔记列表适配器 */ private NotesListAdapter mNotesListAdapter; + /** 笔记列表视图 */ private ListView mNotesListView; + /** 新建笔记按钮 */ private Button mAddNewNote; + /** 是否分发触摸事件 */ private boolean mDispatch; + /** 原始Y坐标 */ private int mOriginY; + /** 分发Y坐标 */ private int mDispatchY; + /** 标题栏文本视图 */ private TextView mTitleBar; + /** 当前文件夹ID */ private long mCurrentFolderId; + /** 内容解析器 */ private ContentResolver mContentResolver; + /** 模式回调 */ private ModeCallback mModeCallBack; + /** 日志标签 */ private static final String TAG = "NotesListActivity"; + /** 笔记列表视图滚动速率 */ public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; + /** 当前焦点的笔记数据项 */ private NoteItemData mFocusNoteDataItem; + /** 普通文件夹选择条件 */ private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; + /** 根文件夹选择条件 */ private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" - + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + + 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; + /** + * 活动创建时调用 + *

+ * 该方法完成以下工作: + * 1. 设置布局文件 + * 2. 初始化资源 + * 3. 如果是首次使用,添加介绍笔记 + *

+ * @param savedInstanceState 保存的实例状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -147,6 +198,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt setAppInfoFromRawRes(); } + /** + * 处理活动返回结果 + *

+ * 该方法在其他活动返回结果时调用,主要用于处理笔记编辑活动返回的结果 + *

+ * @param requestCode 请求码 + * @param resultCode 结果码 + * @param data 返回的数据 + */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK @@ -203,12 +263,32 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 活动开始时调用 + *

+ * 该方法在活动开始时调用,主要用于启动异步笔记列表查询 + *

+ */ @Override protected void onStart() { super.onStart(); startAsyncNotesListQuery(); } + /** + * 初始化资源 + *

+ * 该方法完成以下工作: + * 1. 初始化内容解析器和后台查询处理器 + * 2. 设置当前文件夹ID为根文件夹ID + * 3. 初始化笔记列表视图,添加页脚,设置点击和长按监听器 + * 4. 初始化笔记列表适配器并设置给列表视图 + * 5. 初始化新建笔记按钮,设置点击和触摸监听器 + * 6. 初始化触摸事件相关变量 + * 7. 初始化标题栏和状态 + * 8. 初始化模式回调 + *

+ */ private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); @@ -231,11 +311,40 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mModeCallBack = new ModeCallback(); } + /** + * 多选模式回调类 + *

+ * 该类实现了ListView.MultiChoiceModeListener和OnMenuItemClickListener接口, + * 用于处理多选模式下的操作,包括创建模式、更新菜单、处理菜单点击事件等 + *

+ */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + /** 下拉菜单 */ private DropdownMenu mDropDownMenu; + /** 动作模式 */ private ActionMode mActionMode; + /** 移动菜单 */ private MenuItem mMoveMenu; + /** + * 创建动作模式 + *

+ * 该方法完成以下工作: + * 1. 加载菜单资源 + * 2. 设置删除菜单的点击监听器 + * 3. 根据当前文件夹ID和用户文件夹数量设置移动菜单的可见性 + * 4. 保存动作模式实例 + * 5. 设置列表适配器为选择模式 + * 6. 禁用列表的长按功能 + * 7. 隐藏新建笔记按钮 + * 8. 设置自定义视图 + * 9. 创建下拉菜单 + * 10. 设置下拉菜单的点击监听器 + *

+ * @param mode 动作模式 + * @param menu 菜单 + * @return 是否创建成功,返回true表示成功 + */ public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.note_list_options, menu); menu.findItem(R.id.delete).setOnMenuItemClickListener(this); @@ -269,6 +378,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 更新菜单 + *

+ * 该方法完成以下工作: + * 1. 获取选中的笔记数量 + * 2. 更新下拉菜单的标题,显示选中数量 + * 3. 更新全选/取消全选菜单项的状态和标题 + *

+ */ private void updateMenu() { int selectedCount = mNotesListAdapter.getSelectedCount(); // Update dropdown menu @@ -286,32 +404,90 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 准备动作模式 + *

+ * 该方法未实现具体功能,返回false + *

+ * @param mode 动作模式 + * @param menu 菜单 + * @return 是否准备成功,返回false + */ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // TODO Auto-generated method stub return false; } + /** + * 处理动作模式菜单项点击事件 + *

+ * 该方法未实现具体功能,返回false + *

+ * @param mode 动作模式 + * @param item 菜单项 + * @return 是否处理成功,返回false + */ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { // TODO Auto-generated method stub return false; } + /** + * 销毁动作模式 + *

+ * 该方法完成以下工作: + * 1. 设置列表适配器为非选择模式 + * 2. 启用列表的长按功能 + * 3. 显示新建笔记按钮 + *

+ * @param mode 动作模式 + */ public void onDestroyActionMode(ActionMode mode) { mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); } + /** + * 结束动作模式 + *

+ * 该方法调用动作模式的finish方法来结束多选模式 + *

+ */ public void finishActionMode() { mActionMode.finish(); } + /** + * 列表项选中状态变化时调用 + *

+ * 该方法完成以下工作: + * 1. 设置列表项的选中状态 + * 2. 更新菜单 + *

+ * @param mode 动作模式 + * @param position 列表项位置 + * @param id 列表项ID + * @param checked 是否选中 + */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { mNotesListAdapter.setCheckedItem(position, checked); updateMenu(); } + /** + * 处理菜单项点击事件 + *

+ * 该方法完成以下工作: + * 1. 检查是否有选中的笔记,如果没有则显示提示 + * 2. 根据菜单项ID处理不同的操作: + * - 删除:显示删除确认对话框,点击确定后调用batchDelete方法 + * - 移动:调用startQueryDestinationFolders方法查询目标文件夹 + *

+ * @param item 菜单项 + * @return 是否处理成功,返回true表示成功 + */ public boolean onMenuItemClick(MenuItem item) { if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), @@ -346,8 +522,34 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 新建笔记按钮触摸监听器 + *

+ * 该类实现了OnTouchListener接口,用于处理新建笔记按钮的触摸事件, + * 特别是处理按钮透明部分的触摸事件,将其分发给下方的列表视图 + *

+ */ private class NewNoteOnTouchListener implements OnTouchListener { + /** + * 处理触摸事件 + *

+ * 该方法完成以下工作: + * 1. 处理ACTION_DOWN事件: + * - 获取屏幕高度和新建笔记按钮高度 + * - 计算触摸事件的Y坐标 + * - 如果当前状态是SUB_FOLDER,减去标题栏高度 + * - 检查触摸点是否在按钮的透明部分(根据公式y=-0.12x+94判断) + * - 如果是,将触摸事件分发给下方的列表视图 + * 2. 处理ACTION_MOVE事件: + * - 如果已经开始分发事件,更新事件坐标并继续分发给列表视图 + * 3. 处理其他事件: + * - 如果已经开始分发事件,结束分发并将事件分发给列表视图 + *

+ * @param v 被触摸的视图 + * @param event 触摸事件 + * @return 是否消费了该事件,返回true表示已消费 + */ public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { @@ -408,6 +610,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }; + /** + * 启动异步笔记列表查询 + *

+ * 该方法根据当前文件夹ID选择合适的查询条件,然后启动异步查询,查询结果将通过BackgroundQueryHandler返回 + *

+ */ private void startAsyncNotesListQuery() { String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; @@ -417,11 +625,35 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 后台查询处理器类 + *

+ * 该类继承自AsyncQueryHandler,用于处理异步查询操作, + * 主要处理两种查询: + * 1. 文件夹笔记列表查询 + * 2. 文件夹列表查询 + *

+ */ private final class BackgroundQueryHandler extends AsyncQueryHandler { + /** + * 构造函数 + * @param contentResolver 内容解析器 + */ public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); } + /** + * 查询完成时调用 + *

+ * 该方法根据查询令牌处理查询结果: + * 1. 如果是文件夹笔记列表查询,将结果游标设置给列表适配器 + * 2. 如果是文件夹列表查询,显示文件夹列表菜单 + *

+ * @param token 查询令牌,用于标识查询类型 + * @param cookie 查询时传递的附加数据,此处未使用 + * @param cursor 查询结果游标 + */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { switch (token) { @@ -462,6 +694,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } + /** + * 创建新笔记 + *

+ * 该方法通过启动NoteEditActivity来创建新笔记,并传递当前文件夹ID作为额外参数 + *

+ */ private void createNewNote() { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -469,6 +707,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + /** + * 批量删除笔记 + *

+ * 该方法在异步任务中完成以下工作: + * 1. 获取选中的部件属性 + * 2. 如果不在同步模式,直接删除选中的笔记 + * 3. 如果在同步模式,将选中的笔记移动到回收站 + * 4. 更新相关部件 + * 5. 结束多选模式 + *

+ */ private void batchDelete() { new AsyncTask>() { protected HashSet doInBackground(Void... unused) { @@ -506,6 +755,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 删除文件夹 + *

+ * 该方法完成以下工作: + * 1. 检查文件夹ID是否为根文件夹ID,如果是则返回错误 + * 2. 获取文件夹的部件属性 + * 3. 如果不在同步模式,直接删除文件夹 + * 4. 如果在同步模式,将文件夹移动到回收站 + * 5. 更新相关部件 + *

+ * @param folderId 要删除的文件夹ID + */ private void deleteFolder(long folderId) { if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); @@ -533,6 +794,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 打开笔记 + *

+ * 该方法通过启动NoteEditActivity来打开指定的笔记,传递笔记ID作为额外参数 + *

+ * @param data 要打开的笔记数据项 + */ private void openNode(NoteItemData data) { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); @@ -540,6 +808,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } + /** + * 打开文件夹 + *

+ * 该方法完成以下工作: + * 1. 设置当前文件夹ID + * 2. 启动异步笔记列表查询 + * 3. 根据文件夹类型设置列表状态: + * - 如果是通话记录文件夹,设置为CALL_RECORD_FOLDER状态并隐藏新建笔记按钮 + * - 否则设置为SUB_FOLDER状态 + * 4. 设置标题栏文本和可见性 + *

+ * @param data 要打开的文件夹数据项 + */ private void openFolder(NoteItemData data) { mCurrentFolderId = data.getId(); startAsyncNotesListQuery(); @@ -557,6 +838,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mTitleBar.setVisibility(View.VISIBLE); } + /** + * 处理点击事件 + *

+ * 该方法处理视图的点击事件,目前只处理新建笔记按钮的点击事件 + *

+ * @param v 被点击的视图 + */ public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: @@ -567,6 +855,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示软键盘 + *

+ * 该方法通过InputMethodManager显示软键盘 + *

+ */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { @@ -574,11 +868,35 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 隐藏软键盘 + *

+ * 该方法通过InputMethodManager隐藏软键盘 + *

+ * @param view 用于获取窗口令牌的视图 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示创建或修改文件夹对话框 + *

+ * 该方法完成以下工作: + * 1. 创建对话框构建器 + * 2. 加载布局文件 + * 3. 显示软键盘 + * 4. 根据create参数设置对话框标题和编辑框内容: + * - 如果是修改文件夹,设置标题为"修改文件夹名称",并显示当前文件夹名称 + * - 如果是创建文件夹,设置标题为"创建文件夹",并清空编辑框 + * 5. 设置对话框按钮和点击事件 + * 6. 显示对话框 + * 7. 为确定按钮设置点击事件,处理文件夹的创建或修改 + * 8. 为编辑框添加文本变化监听器,当文本为空时禁用确定按钮 + *

+ * @param create 是否是创建文件夹,true表示创建,false表示修改 + */ private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); @@ -664,6 +982,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }); } + /** + * 处理返回按钮点击事件 + *

+ * 该方法根据当前状态处理返回按钮点击事件: + * 1. 如果在子文件夹中,返回根文件夹 + * 2. 如果在通话记录文件夹中,返回根文件夹并显示新建笔记按钮 + * 3. 如果在根文件夹中,调用默认的返回处理 + *

+ */ @Override public void onBackPressed() { switch (mState) { @@ -688,6 +1015,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 更新部件 + *

+ * 该方法完成以下工作: + * 1. 创建广播意图 + * 2. 根据部件类型设置广播接收者: + * - 如果是2x2部件,设置接收者为NoteWidgetProvider_2x + * - 如果是4x4部件,设置接收者为NoteWidgetProvider_4x + * - 否则记录错误并返回 + * 3. 添加部件ID到意图中 + * 4. 发送广播 + * 5. 设置结果 + *

+ * @param appWidgetId 部件ID + * @param appWidgetType 部件类型 + */ private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (appWidgetType == Notes.TYPE_WIDGET_2X) { @@ -778,6 +1121,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 处理选项菜单点击事件 + *

+ * 该方法处理以下菜单项的点击事件: + * 1. 新建文件夹:显示创建文件夹对话框 + * 2. 导出文本:调用exportNoteToText方法导出笔记 + * 3. 同步: + * - 如果在同步模式下,根据当前标题判断是开始同步还是取消同步 + * - 否则跳转到设置页面 + * 4. 设置:跳转到设置页面 + * 5. 新建笔记:调用createNewNote方法创建新笔记 + * 6. 搜索:调用onSearchRequested方法启动搜索 + *

+ * @param item 被点击的菜单项 + * @return 是否处理了该事件,返回true表示已处理 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -818,12 +1177,31 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 处理搜索请求 + *

+ * 该方法启动搜索活动 + *

+ * @return 是否处理了搜索请求,返回true表示已处理 + */ @Override public boolean onSearchRequested() { startSearch(null, false, null /* appData */, false); return true; } + /** + * 导出笔记到文本文件 + *

+ * 该方法在异步任务中完成以下工作: + * 1. 获取BackupUtils实例 + * 2. 在后台调用exportToText方法导出笔记 + * 3. 根据导出结果显示不同的对话框: + * - 如果SD卡未挂载,显示失败对话框 + * - 如果导出成功,显示成功对话框并显示导出文件的位置 + * - 如果系统错误,显示失败对话框 + *

+ */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { @@ -866,18 +1244,62 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 检查是否处于同步模式 + *

+ * 该方法通过获取同步账户名来检查是否处于同步模式,如果同步账户名不为空则处于同步模式 + *

+ * @return 是否处于同步模式,true表示处于同步模式,false表示不处于同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 启动偏好设置活动 + *

+ * 该方法完成以下工作: + * 1. 获取当前活动或父活动 + * 2. 创建意图,跳转到NotesPreferenceActivity + * 3. 启动活动 + *

+ */ private void startPreferenceActivity() { Activity from = getParent() != null ? getParent() : this; Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } + /** + * 列表项点击监听器类 + *

+ * 该类实现了OnItemClickListener接口,用于处理列表项的点击事件, + * 根据当前状态和点击的列表项类型执行不同的操作 + *

+ */ private class OnListItemClickListener implements OnItemClickListener { + /** + * 处理列表项点击事件 + *

+ * 该方法完成以下工作: + * 1. 检查被点击的视图是否是NotesListItem + * 2. 获取被点击的笔记数据项 + * 3. 如果在选择模式下: + * - 如果是笔记类型,切换选中状态 + * - 返回 + * 4. 根据当前状态和笔记类型执行不同的操作: + * - NOTE_LIST状态: + * - 如果是文件夹或系统类型,调用openFolder方法打开文件夹 + * - 如果是笔记类型,调用openNode方法打开笔记 + * - SUB_FOLDER或CALL_RECORD_FOLDER状态: + * - 如果是笔记类型,调用openNode方法打开笔记 + *

+ * @param parent 父视图 + * @param view 被点击的视图 + * @param position 被点击的位置 + * @param id 被点击的ID + */ public void onItemClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { NoteItemData item = ((NotesListItem) view).getItemData(); @@ -917,6 +1339,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } + /** + * 启动查询目标文件夹 + *

+ * 该方法完成以下工作: + * 1. 构建查询条件: + * - 基本条件:类型为文件夹,父ID不等于回收站ID,ID不等于当前文件夹ID + * - 如果当前状态不是NOTE_LIST,添加根文件夹ID到条件中 + * 2. 启动异步查询,查询结果将通过BackgroundQueryHandler返回 + *

+ */ private void startQueryDestinationFolders() { String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; selection = (mState == ListEditState.NOTE_LIST) ? selection: @@ -935,6 +1367,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 处理列表项长按事件 + *

+ * 该方法完成以下工作: + * 1. 检查被长按的视图是否是NotesListItem + * 2. 获取被长按的笔记数据项 + * 3. 根据笔记类型处理长按事件: + * - 如果是笔记类型且不在选择模式下,启动多选模式并选中当前项 + * - 如果是文件夹类型,设置上下文菜单监听器 + * 4. 返回false表示事件未被完全处理 + *

+ * @param parent 父视图 + * @param view 被长按的视图 + * @param position 被长按的位置 + * @param id 被长按的ID + * @return 是否处理了该事件,返回false表示事件未被完全处理 + */ public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { mFocusNoteDataItem = ((NotesListItem) view).getItemData(); diff --git a/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/src/main/java/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..e9f5a8a 100644 --- a/src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -31,18 +31,46 @@ import java.util.HashSet; import java.util.Iterator; +/** + * 笔记列表适配器类 + *

+ * 该类是CursorAdapter的子类,用于将数据库中的笔记数据绑定到列表视图上, + * 支持普通浏览模式和选择模式,能够管理选中的项目,并提供了各种方法来处理 + * 列表项的选择状态、获取选中项目ID等功能。 + *

+ */ public class NotesListAdapter extends CursorAdapter { + /** 日志标签 */ private static final String TAG = "NotesListAdapter"; + /** 上下文对象 */ private Context mContext; + /** 保存选中项目的位置和状态 */ private HashMap mSelectedIndex; + /** 笔记数量 */ private int mNotesCount; + /** 是否处于选择模式 */ private boolean mChoiceMode; + /** + * 应用桌面组件属性类 + *

+ * 该类用于存储桌面组件的相关属性,包括组件ID和组件类型 + *

+ */ public static class AppWidgetAttribute { + /** 桌面组件ID */ public int widgetId; + /** 桌面组件类型 */ public int widgetType; }; + /** + * 构造函数 + *

+ * 初始化NotesListAdapter,创建选中项目的映射表,并设置初始上下文和笔记数量 + *

+ * @param context 上下文对象 + */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); @@ -50,11 +78,31 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } + /** + * 创建新的列表项视图 + *

+ * 该方法在需要为新的数据项创建视图时调用,返回一个新的NotesListItem实例 + *

+ * @param context 上下文对象 + * @param cursor 数据游标 + * @param parent 父视图组 + * @return 新创建的列表项视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } + /** + * 将数据绑定到列表项视图 + *

+ * 该方法在需要将数据绑定到现有视图时调用,将游标中的数据转换为NoteItemData对象, + * 并调用NotesListItem的bind方法将数据绑定到视图上 + *

+ * @param view 要绑定数据的视图 + * @param context 上下文对象 + * @param cursor 数据游标 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -64,20 +112,46 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 设置指定位置的项是否被选中 + *

+ * 该方法用于在选择模式下设置指定位置的项的选中状态,并通知数据集变化 + *

+ * @param position 项的位置 + * @param checked 是否被选中 + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); notifyDataSetChanged(); } + /** + * 检查是否处于选择模式 + * @return 是否处于选择模式 + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置是否进入选择模式 + *

+ * 该方法用于切换选择模式,进入选择模式时会清除之前的选中状态 + *

+ * @param mode 是否进入选择模式 + */ public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } + /** + * 全选或取消全选 + *

+ * 该方法用于在选择模式下全选或取消全选所有普通笔记项 + *

+ * @param checked 是否全选 + */ public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { @@ -89,6 +163,13 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取所有选中项的ID + *

+ * 该方法用于获取选择模式下所有选中项的ID集合,排除根文件夹ID + *

+ * @return 选中项的ID集合 + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -105,6 +186,13 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取所有选中的桌面组件属性 + *

+ * 该方法用于获取选择模式下所有选中的桌面组件的属性集合 + *

+ * @return 选中的桌面组件属性集合,若获取失败则返回null + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -128,6 +216,10 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项的数量 + * @return 选中项的数量 + */ public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -143,11 +235,20 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 检查是否所有可选中项都已选中 + * @return 是否所有可选中项都已选中 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 检查指定位置的项是否被选中 + * @param position 项的位置 + * @return 是否被选中 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,18 +256,37 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + /** + * 当数据内容变化时调用 + *

+ * 该方法在数据内容发生变化时被调用,会重新计算笔记数量 + *

+ */ @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + /** + * 更换数据游标 + *

+ * 该方法在更换数据游标时被调用,会重新计算笔记数量 + *

+ * @param cursor 新的数据游标 + */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + /** + * 计算笔记数量 + *

+ * 该方法用于统计当前数据集中的普通笔记数量,并更新mNotesCount成员变量 + *

+ */ private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { diff --git a/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/main/java/net/micode/notes/ui/NotesListItem.java index 1221e80..b6222fd 100644 --- a/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -30,14 +30,35 @@ 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; + /** + * 构造函数 + *

+ * 初始化NotesListItem视图,加载布局文件并获取各个UI组件的引用 + *

+ * @param context 上下文对象 + */ public NotesListItem(Context context) { super(context); inflate(context, R.layout.note_item, this); @@ -48,6 +69,20 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + /** + * 将笔记数据绑定到列表项视图 + *

+ * 该方法根据笔记数据的类型和状态,动态调整列表项的UI显示,包括: + * 1. 根据选择模式和笔记类型决定是否显示复选框 + * 2. 根据笔记类型(通话记录文件夹、普通通话记录、普通笔记、文件夹)设置不同的UI样式 + * 3. 设置标题、时间、提醒图标等内容 + * 4. 调用setBackground方法设置背景样式 + *

+ * @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); @@ -99,6 +134,15 @@ public class NotesListItem extends LinearLayout { setBackground(data); } + /** + * 设置列表项的背景样式 + *

+ * 该方法根据笔记数据的类型和状态,选择并设置合适的背景资源: + * 1. 对于普通笔记,根据其在列表中的位置(单个、第一个、最后一个、中间)选择不同的背景 + * 2. 对于文件夹或其他类型,使用默认的文件夹背景 + *

+ * @param data 笔记列表项数据,包含背景颜色ID、类型和位置信息 + */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { @@ -116,6 +160,10 @@ public class NotesListItem extends LinearLayout { } } + /** + * 获取列表项的数据对象 + * @return 笔记列表项数据对象 + */ public NoteItemData getItemData() { return mItemData; } diff --git a/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..69984ff 100644 --- a/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -48,27 +48,56 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; +/** + * 笔记偏好设置活动类 + *

+ * 该类用于处理笔记应用的设置,包括同步账户管理、背景颜色设置等功能, + * 提供了UI界面让用户进行偏好设置,并与GTaskSyncService进行交互实现同步功能 + *

+ */ public class NotesPreferenceActivity extends PreferenceActivity { + /** 偏好设置文件名 */ public static final String PREFERENCE_NAME = "notes_preferences"; + /** 同步账户名的偏好键 */ public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; + /** 最后同步时间的偏好键 */ public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; + /** 背景颜色随机出现的偏好键 */ public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; + /** 同步账户的偏好键 */ private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; + /** 权限过滤器键 */ private static final String AUTHORITIES_FILTER_KEY = "authorities"; + /** 账户偏好设置分类 */ private PreferenceCategory mAccountCategory; + /** GTask同步服务广播接收器 */ private GTaskReceiver mReceiver; + /** 原始账户列表 */ private Account[] mOriAccounts; + /** 是否添加了新账户 */ private boolean mHasAddedAccount; + /** + * 活动创建时调用 + *

+ * 该方法完成以下工作: + * 1. 设置导航栏显示应用图标 + * 2. 加载偏好设置资源 + * 3. 初始化账户偏好设置分类 + * 4. 注册GTask同步服务广播接收器 + * 5. 为列表视图添加头部 + *

+ * @param icicle 保存的实例状态 + */ @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -88,6 +117,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { getListView().addHeaderView(header, null, true); } + /** + * 活动恢复时调用 + *

+ * 该方法完成以下工作: + * 1. 如果添加了新账户,自动设置同步账户 + * 2. 刷新UI界面 + *

+ */ @Override protected void onResume() { super.onResume(); @@ -116,6 +153,13 @@ public class NotesPreferenceActivity extends PreferenceActivity { refreshUI(); } + /** + * 活动销毁时调用 + *

+ * 该方法完成以下工作: + * 1. 注销GTask同步服务广播接收器 + *

+ */ @Override protected void onDestroy() { if (mReceiver != null) { @@ -124,6 +168,17 @@ public class NotesPreferenceActivity extends PreferenceActivity { super.onDestroy(); } + /** + * 加载账户偏好设置 + *

+ * 该方法完成以下工作: + * 1. 清空账户偏好设置分类中的所有偏好项 + * 2. 创建新的账户偏好项 + * 3. 设置账户偏好项的标题、摘要和点击监听器 + * 4. 根据当前同步状态和账户设置情况,决定点击时显示的对话框 + * 5. 将账户偏好项添加到分类中 + *

+ */ private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -154,6 +209,16 @@ public class NotesPreferenceActivity extends PreferenceActivity { mAccountCategory.addPreference(accountPref); } + /** + * 加载同步按钮和状态 + *

+ * 该方法完成以下工作: + * 1. 获取同步按钮和最后同步时间视图 + * 2. 根据同步状态设置按钮文本和点击事件 + * 3. 根据是否有同步账户设置按钮可用性 + * 4. 设置最后同步时间或同步进度 + *

+ */ private void loadSyncButton() { Button syncButton = (Button) findViewById(R.id.preference_sync_button); TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); @@ -193,11 +258,28 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 刷新UI界面 + *

+ * 该方法调用loadAccountPreference和loadSyncButton方法来刷新UI界面 + *

+ */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + /** + * 显示选择账户对话框 + *

+ * 该方法完成以下工作: + * 1. 创建对话框构建器并设置自定义标题 + * 2. 获取Google账户列表 + * 3. 如果有账户,显示单选列表供用户选择 + * 4. 添加添加新账户的选项 + * 5. 处理用户选择事件 + *

+ */ private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); @@ -254,6 +336,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 显示更改账户确认对话框 + *

+ * 该方法完成以下工作: + * 1. 创建对话框构建器并设置自定义标题 + * 2. 添加选项:更改账户、移除账户、取消 + * 3. 处理用户选择事件 + *

+ */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); @@ -283,11 +374,29 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + /** + * 获取Google账户列表 + *

+ * 该方法通过AccountManager获取设备上的所有Google账户 + *

+ * @return Google账户数组 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + /** + * 设置同步账户 + *

+ * 该方法完成以下工作: + * 1. 更新同步账户偏好设置 + * 2. 清除最后同步时间 + * 3. 清除本地GTask相关信息 + * 4. 显示成功提示 + *

+ * @param account 要设置的同步账户名 + */ private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -318,6 +427,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 移除同步账户 + *

+ * 该方法完成以下工作: + * 1. 移除同步账户偏好设置 + * 2. 移除最后同步时间偏好设置 + * 3. 清除本地GTask相关信息 + *

+ */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -340,12 +458,28 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).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, ""); } + /** + * 设置最后同步时间 + *

+ * 该方法将最后同步时间保存到偏好设置中 + *

+ * @param context 上下文对象 + * @param time 最后同步时间(毫秒) + */ public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -354,14 +488,38 @@ 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); } + /** + * GTask同步服务广播接收器 + *

+ * 该类用于接收GTaskSyncService发送的广播,更新UI界面和同步状态 + *

+ */ private class GTaskReceiver extends BroadcastReceiver { + /** + * 接收广播时调用 + *

+ * 该方法完成以下工作: + * 1. 刷新UI界面 + * 2. 如果正在同步,更新同步状态文本 + *

+ * @param context 上下文对象 + * @param intent 广播意图,包含同步状态和进度信息 + */ @Override public void onReceive(Context context, Intent intent) { refreshUI(); @@ -374,6 +532,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 处理选项菜单点击事件 + *

+ * 该方法处理选项菜单的点击事件,目前只处理了返回按钮的点击事件 + *

+ * @param item 被点击的菜单项 + * @return 是否处理了该事件 + */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: diff --git a/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java b/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..8fffe80 100644 --- a/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java @@ -32,19 +32,56 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * 笔记小组件基类 + *

+ * 该抽象类为所有笔记小组件提供核心功能,包括小组件数据管理、UI更新、点击事件处理 + * 和小组件删除管理。它支持不同的小组件类型和布局,通过抽象方法让子类实现特定的 + * 布局和资源配置 + *

+ */ public abstract class NoteWidgetProvider extends AppWidgetProvider { + /** + * 小组件数据查询的投影列数组 + *

+ * 定义了从数据库查询笔记数据时需要获取的列,包括笔记ID、背景颜色ID和摘要内容 + *

+ */ public static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.BG_COLOR_ID, NoteColumns.SNIPPET }; + /** + * 查询结果中ID列的索引 + */ public static final int COLUMN_ID = 0; + + /** + * 查询结果中背景颜色ID列的索引 + */ public static final int COLUMN_BG_COLOR_ID = 1; + + /** + * 查询结果中摘要内容列的索引 + */ public static final int COLUMN_SNIPPET = 2; + /** + * 日志标签,用于小组件相关的日志输出 + */ private static final String TAG = "NoteWidgetProvider"; + /** + * 当小组件被删除时调用的方法 + *

+ * 该方法将数据库中与被删除小组件关联的笔记的WIDGET_ID字段更新为无效值, + * 解除笔记与已删除小组件的关联关系 + *

+ * @param context 上下文对象 + * @param appWidgetIds 被删除的小组件ID数组 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { ContentValues values = new ContentValues(); @@ -57,6 +94,15 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 查询与特定小组件ID关联的笔记信息 + *

+ * 该方法从数据库中查询与给定小组件ID关联的笔记数据,并排除已删除到回收站的笔记 + *

+ * @param context 上下文对象 + * @param widgetId 要查询的小组件ID + * @return 返回包含笔记信息的Cursor对象,使用PROJECTION中定义的列 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, @@ -65,10 +111,34 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { null); } + /** + * 更新小组件的UI和数据 + *

+ * 该方法是update方法的公共保护接口,默认使用非隐私模式更新小组件 + *

+ * @param context 上下文对象 + * @param appWidgetManager 小组件管理器 + * @param appWidgetIds 要更新的小组件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 更新小组件的UI和数据(私有实现) + *

+ * 该方法负责实际更新小组件的内容,支持隐私模式和正常模式: + * 1. 查询与小组件关联的笔记信息 + * 2. 根据查询结果设置小组件显示的文本和背景 + * 3. 创建点击小组件时的PendingIntent + * 4. 在隐私模式下显示特定提示文本并跳转到笔记列表 + * 5. 在正常模式下显示笔记内容并支持查看或创建笔记 + *

+ * @param context 上下文对象 + * @param appWidgetManager 小组件管理器 + * @param appWidgetIds 要更新的小组件ID数组 + * @param privacyMode 是否处于隐私模式 + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { for (int i = 0; i < appWidgetIds.length; i++) { @@ -124,9 +194,31 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 根据背景颜色ID获取对应的背景资源ID + *

+ * 该方法由子类实现,用于将数据库中的背景颜色ID转换为实际的资源ID + *

+ * @param bgId 数据库中的背景颜色ID + * @return 对应的背景资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 获取小组件的布局ID + *

+ * 该方法由子类实现,返回特定小组件类型的布局资源ID + *

+ * @return 小组件布局资源ID + */ protected abstract int getLayoutId(); + /** + * 获取小组件类型 + *

+ * 该方法由子类实现,返回特定小组件的类型标识 + *

+ * @return 小组件类型标识 + */ protected abstract int getWidgetType(); } diff --git a/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..02bd7f1 100644 --- a/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -24,22 +24,60 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +/** + * 2x尺寸的笔记小组件实现类 + *

+ * 该类继承自NoteWidgetProvider抽象类,实现了2x尺寸笔记小组件的具体功能 + * 包括布局设置、背景资源获取和小组件类型定义 + *

+ */ public class NoteWidgetProvider_2x extends NoteWidgetProvider { + /** + * 更新小组件的UI和数据 + *

+ * 该方法重写了AppWidgetProvider的onUpdate方法,调用父类的update方法来更新2x尺寸小组件 + *

+ * @param context 上下文对象 + * @param appWidgetManager 小组件管理器 + * @param appWidgetIds 要更新的小组件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取2x尺寸小组件的布局ID + *

+ * 返回2x尺寸小组件的布局资源ID + *

+ * @return 2x小组件布局资源ID + */ @Override protected int getLayoutId() { return R.layout.widget_2x; } + /** + * 根据背景颜色ID获取2x小组件对应的背景资源ID + *

+ * 通过ResourceParser获取2x尺寸小组件的背景资源ID + *

+ * @param bgId 数据库中的背景颜色ID + * @return 2x小组件对应的背景资源ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); } + /** + * 获取2x小组件类型 + *

+ * 返回2x尺寸小组件的类型标识 + *

+ * @return 2x小组件类型标识 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_2X; diff --git a/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..6e4a984 100644 --- a/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -24,21 +24,60 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +/** + * 4x尺寸的笔记小组件实现类 + *

+ * 该类继承自NoteWidgetProvider抽象类,实现了4x尺寸笔记小组件的具体功能 + * 包括布局设置、背景资源获取和小组件类型定义 + *

+ */ public class NoteWidgetProvider_4x extends NoteWidgetProvider { + /** + * 更新小组件的UI和数据 + *

+ * 该方法重写了AppWidgetProvider的onUpdate方法,调用父类的update方法来更新4x尺寸小组件 + *

+ * @param context 上下文对象 + * @param appWidgetManager 小组件管理器 + * @param appWidgetIds 要更新的小组件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取4x尺寸小组件的布局ID + *

+ * 返回4x尺寸小组件的布局资源ID + *

+ * @return 4x小组件布局资源ID + */ + @Override protected int getLayoutId() { return R.layout.widget_4x; } + /** + * 根据背景颜色ID获取4x小组件对应的背景资源ID + *

+ * 通过ResourceParser获取4x尺寸小组件的背景资源ID + *

+ * @param bgId 数据库中的背景颜色ID + * @return 4x小组件对应的背景资源ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + /** + * 获取4x小组件类型 + *

+ * 返回4x尺寸小组件的类型标识 + *

+ * @return 4x小组件类型标识 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X;