diff --git a/doc/小米便签泛读报告.docx b/doc/小米便签泛读报告.docx deleted file mode 100644 index 89b5417..0000000 Binary files a/doc/小米便签泛读报告.docx and /dev/null differ diff --git a/doc/开源软件泛读、标注和维护报告文档.docx b/doc/开源软件泛读、标注和维护报告文档.docx new file mode 100644 index 0000000..c456aab Binary files /dev/null and b/doc/开源软件泛读、标注和维护报告文档.docx differ diff --git a/src/Notes-master/src/net/micode/notes/data/Contact.java b/src/Notes-master/src/net/micode/notes/data/Contact.java index d97ac5d..ba02a64 100644 --- a/src/Notes-master/src/net/micode/notes/data/Contact.java +++ b/src/Notes-master/src/net/micode/notes/data/Contact.java @@ -25,10 +25,16 @@ import android.util.Log; import java.util.HashMap; +/** + * 联系人工具类,用于根据电话号码获取联系人姓名 + */ public class Contact { + // 联系人缓存,避免重复查询 private static HashMap sContactCache; + // 日志标签 private static final String TAG = "Contact"; + // 查询联系人的SQL条件,用于通过电话号码查找联系人 private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.RAW_CONTACT_ID + " IN " @@ -36,6 +42,12 @@ public class Contact { + " FROM phone_lookup" + " WHERE min_match = '+')"; + /** + * 根据电话号码获取联系人姓名 + * @param context 上下文对象 + * @param phoneNumber 电话号码 + * @return 联系人姓名,若未找到则返回null + */ public static String getContact(Context context, String phoneNumber) { if(sContactCache == null) { sContactCache = new HashMap(); diff --git a/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java b/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..794f931 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java +++ b/src/Notes-master/src/net/micode/notes/gtask/data/TaskList.java @@ -30,19 +30,36 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * 表示Google Tasks中的任务列表,继承自Node类 + * 用于管理任务列表的属性和操作,包括创建、更新、同步等功能 + * 同时管理列表中的任务项 + */ public class TaskList extends Node { + // 日志标签 private static final String TAG = TaskList.class.getSimpleName(); + // 任务列表的索引位置 private int mIndex; + // 任务列表中的子任务集合 private ArrayList mChildren; + /** + * 构造函数,初始化任务列表对象 + */ public TaskList() { super(); mChildren = new ArrayList(); mIndex = 1; } + /** + * 获取创建任务列表的JSON操作对象 + * @param actionId 操作ID + * @return 创建任务列表的JSON操作对象 + * @throws ActionFailureException 生成JSON对象失败时抛出 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -74,6 +91,12 @@ public class TaskList extends Node { return js; } + /** + * 获取更新任务列表的JSON操作对象 + * @param actionId 操作ID + * @return 更新任务列表的JSON操作对象 + * @throws ActionFailureException 生成JSON对象失败时抛出 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -103,6 +126,11 @@ public class TaskList extends Node { return js; } + /** + * 根据远程JSON对象设置任务列表内容 + * @param js 远程JSON对象 + * @throws ActionFailureException 解析JSON对象失败时抛出 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { @@ -129,6 +157,10 @@ public class TaskList extends Node { } } + /** + * 根据本地JSON对象设置任务列表内容 + * @param js 本地JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); @@ -157,6 +189,10 @@ public class TaskList extends Node { } } + /** + * 从任务列表内容生成本地JSON对象 + * @return 本地JSON对象,若生成失败则返回null + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); @@ -183,6 +219,11 @@ public class TaskList extends Node { } } + /** + * 获取同步操作类型 + * @param c 数据库游标 + * @return 同步操作类型(SYNC_ACTION_NONE、SYNC_ACTION_UPDATE_REMOTE等) + */ public int getSyncAction(Cursor c) { try { if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { @@ -216,10 +257,19 @@ public class TaskList extends Node { return SYNC_ACTION_ERROR; } + /** + * 获取子任务数量 + * @return 子任务数量 + */ public int getChildTaskCount() { return mChildren.size(); } + /** + * 添加子任务到列表末尾 + * @param task 要添加的任务 + * @return 是否添加成功 + */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { @@ -234,6 +284,12 @@ public class TaskList extends Node { return ret; } + /** + * 在指定位置添加子任务 + * @param task 要添加的任务 + * @param index 添加位置的索引 + * @return 是否添加成功 + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -260,6 +316,11 @@ public class TaskList extends Node { return true; } + /** + * 从列表中移除子任务 + * @param task 要移除的任务 + * @return 是否移除成功 + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -281,6 +342,12 @@ public class TaskList extends Node { return ret; } + /** + * 移动子任务到指定位置 + * @param task 要移动的任务 + * @param index 目标位置的索引 + * @return 是否移动成功 + */ public boolean moveChildTask(Task task, int index) { if (index < 0 || index >= mChildren.size()) { @@ -299,6 +366,11 @@ public class TaskList extends Node { return (removeChildTask(task) && addChildTask(task, index)); } + /** + * 根据Google Task ID查找子任务 + * @param gid Google Task ID + * @return 找到的任务,若未找到则返回null + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -309,10 +381,20 @@ public class TaskList extends Node { return null; } + /** + * 获取子任务在列表中的索引位置 + * @param task 子任务对象 + * @return 子任务的索引位置 + */ public int getChildTaskIndex(Task task) { return mChildren.indexOf(task); } + /** + * 根据索引位置获取子任务 + * @param index 子任务的索引位置 + * @return 找到的任务,若索引无效则返回null + */ public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "getTaskByIndex: invalid index"); @@ -321,6 +403,11 @@ public class TaskList extends Node { return mChildren.get(index); } + /** + * 根据Google Task ID查找子任务(与findChildTaskByGid功能相同) + * @param gid Google Task ID + * @return 找到的任务,若未找到则返回null + */ public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -329,14 +416,26 @@ public class TaskList extends Node { return null; } + /** + * 获取所有子任务的列表 + * @return 子任务列表 + */ public ArrayList getChildTaskList() { return this.mChildren; } + /** + * 设置任务列表的索引位置 + * @param index 索引位置 + */ public void setIndex(int index) { this.mIndex = index; } + /** + * 获取任务列表的索引位置 + * @return 索引位置 + */ public int getIndex() { return this.mIndex; } diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..3107965 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -48,45 +48,73 @@ import java.util.Iterator; import java.util.Map; +/** + * Google Tasks同步管理器类 + * 负责处理本地笔记与Google Tasks之间的同步操作 + * 采用单例模式实现 + */ public class GTaskManager { + // 日志标签 private static final String TAG = GTaskManager.class.getSimpleName(); + // 同步状态:成功 public static final int STATE_SUCCESS = 0; + // 同步状态:网络错误 public static final int STATE_NETWORK_ERROR = 1; + // 同步状态:内部错误 public static final int STATE_INTERNAL_ERROR = 2; + // 同步状态:同步进行中 public static final int STATE_SYNC_IN_PROGRESS = 3; + // 同步状态:同步已取消 public static final int STATE_SYNC_CANCELLED = 4; + // 单例实例 private static GTaskManager mInstance = null; + // 用于获取认证令牌的Activity实例 private Activity mActivity; + // 应用上下文 private Context mContext; + // 内容解析器 private ContentResolver mContentResolver; + // 是否正在同步 private boolean mSyncing; + // 是否取消同步 private boolean mCancelled; + // Google Tasks列表映射表 private HashMap mGTaskListHashMap; + // Google Tasks节点映射表 private HashMap mGTaskHashMap; + // 元数据映射表 private HashMap mMetaHashMap; + // 元数据列表 private TaskList mMetaList; + // 本地删除ID集合 private HashSet mLocalDeleteIdMap; + // GID到本地ID的映射表 private HashMap mGidToNid; + // 本地ID到GID的映射表 private HashMap mNidToGid; + /** + * 私有构造函数 + * 初始化同步管理器的各个集合和映射表 + */ private GTaskManager() { mSyncing = false; mCancelled = false; @@ -99,6 +127,10 @@ public class GTaskManager { mNidToGid = new HashMap(); } + /** + * 获取单例实例 + * @return GTaskManager实例 + */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -106,11 +138,23 @@ public class GTaskManager { return mInstance; } + /** + * 设置Activity上下文 + * 用于获取Google账户认证令牌 + * @param activity Activity实例 + */ public synchronized void setActivityContext(Activity activity) { // used for getting authtoken mActivity = activity; } + /** + * 执行同步操作 + * 处理本地与Google Tasks服务器之间的数据同步 + * @param context 应用上下文 + * @param asyncTask 异步任务实例,用于更新同步进度 + * @return 同步状态码 + */ public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { Log.d(TAG, "Sync is in progress"); @@ -168,6 +212,11 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } + /** + * 初始化Google Tasks列表 + * 从服务器获取所有任务列表和任务 + * @throws NetworkFailureException 如果网络请求失败 + */ private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; @@ -247,6 +296,11 @@ public class GTaskManager { } } + /** + * 同步内容 + * 处理本地与服务器之间的内容同步 + * @throws NetworkFailureException 如果网络请求失败 + */ private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; @@ -351,6 +405,11 @@ public class GTaskManager { } + /** + * 同步文件夹 + * 处理本地与服务器之间的文件夹同步 + * @throws NetworkFailureException 如果网络请求失败 + */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -476,6 +535,14 @@ public class GTaskManager { GTaskClient.getInstance().commitUpdate(); } + /** + * 执行内容同步 + * 根据同步类型处理本地和服务器之间的内容同步 + * @param syncType 同步类型 + * @param node Google Tasks节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 如果网络请求失败 + */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -522,6 +589,12 @@ public class GTaskManager { } } + /** + * 添加本地节点 + * 将Google Tasks节点添加到本地数据库 + * @param node Google Tasks节点 + * @throws NetworkFailureException 如果网络请求失败 + */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -596,6 +669,13 @@ public class GTaskManager { updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 更新本地节点 + * 使用Google Tasks节点更新本地数据库 + * @param node Google Tasks节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 如果网络请求失败 + */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -619,6 +699,13 @@ public class GTaskManager { updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 添加远程节点 + * 将本地节点添加到Google Tasks服务器 + * @param node Google Tasks节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 如果网络请求失败 + */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -692,6 +779,13 @@ public class GTaskManager { mNidToGid.put(sqlNote.getId(), n.getGid()); } + /** + * 更新远程节点 + * 使用本地节点更新Google Tasks服务器 + * @param node Google Tasks节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 如果网络请求失败 + */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -730,6 +824,13 @@ public class GTaskManager { sqlNote.commit(true); } + /** + * 更新远程元数据 + * 将本地节点的元数据更新到Google Tasks服务器 + * @param gid Google Tasks节点ID + * @param sqlNote 本地笔记对象 + * @throws NetworkFailureException 如果网络请求失败 + */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { MetaData metaData = mMetaHashMap.get(gid); @@ -746,6 +847,11 @@ public class GTaskManager { } } + /** + * 刷新本地同步ID + * 更新本地数据库中的同步ID,确保与服务器保持一致 + * @throws NetworkFailureException 如果网络请求失败 + */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; @@ -790,10 +896,18 @@ public class GTaskManager { } } + /** + * 获取当前同步账户 + * @return 同步账户的邮箱地址 + */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } + /** + * 取消同步 + * 设置取消标志,中断正在进行的同步操作 + */ public void cancelSync() { mCancelled = true; } diff --git a/src/Notes-master/src/net/micode/notes/model/Note.java b/src/Notes-master/src/net/micode/notes/model/Note.java index 6706cf6..f150e98 100644 --- a/src/Notes-master/src/net/micode/notes/model/Note.java +++ b/src/Notes-master/src/net/micode/notes/model/Note.java @@ -34,12 +34,25 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; +/** + * 笔记模型类,用于管理笔记的创建、更新和同步操作 + * 支持文本笔记和通话记录笔记,通过ContentResolver与数据库交互 + */ public class Note { + // 日志标签 + private static final String TAG = "Note"; + + // 存储笔记基本信息的变更值 private ContentValues mNoteDiffValues; + + // 存储笔记数据内容的内部对象 private NoteData mNoteData; - private static final String TAG = "Note"; /** - * Create a new note id for adding a new note to databases + * 为添加新笔记到数据库创建一个新的笔记ID + * @param context 上下文对象 + * @param folderId 文件夹ID,新笔记将被添加到该文件夹 + * @return 新创建的笔记ID + * @throws IllegalStateException 如果创建笔记失败 */ public static synchronized long getNewNoteId(Context context, long folderId) { // Create a new note in the database @@ -65,41 +78,82 @@ public class Note { return noteId; } + /** + * 构造方法,初始化笔记对象 + */ public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } + /** + * 设置笔记的基本属性值 + * @param key 属性键名 + * @param value 属性值 + */ public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置笔记的文本数据内容 + * @param key 数据键名 + * @param value 数据值 + */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + /** + * 设置文本数据的ID + * @param id 文本数据ID + */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + /** + * 获取文本数据的ID + * @return 文本数据ID + */ public long getTextDataId() { return mNoteData.mTextDataId; } + /** + * 设置通话记录数据的ID + * @param id 通话记录数据ID + */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + /** + * 设置笔记的通话记录数据内容 + * @param key 数据键名 + * @param value 数据值 + */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + /** + * 检查笔记是否在本地被修改 + * @return 如果笔记被修改返回true,否则返回false + */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + /** + * 将本地修改的笔记数据同步到ContentResolver + * @param context 上下文对象 + * @param noteId 要同步的笔记ID + * @return 如果同步成功返回true,否则返回false + * @throws IllegalArgumentException 如果笔记ID无效 + */ public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -110,15 +164,14 @@ public class Note { } /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info + * 理论上,一旦数据改变,笔记的{@link NoteColumns#LOCAL_MODIFIED}和{@link NoteColumns#MODIFIED_DATE}字段应该被更新 + * 为了数据安全,即使更新笔记失败,我们也会更新笔记数据信息 */ if (context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, null) == 0) { Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through + // 不返回,继续执行 } mNoteDiffValues.clear(); @@ -130,17 +183,29 @@ public class Note { return true; } + /** + * 笔记数据内部类,用于管理笔记的具体数据内容 + * 包括文本数据和通话记录数据 + */ private class NoteData { + // 日志标签 + private static final String TAG = "NoteData"; + + // 文本数据ID private long mTextDataId; + // 文本数据内容的变更值 private ContentValues mTextDataValues; + // 通话记录数据ID private long mCallDataId; + // 通话记录数据内容的变更值 private ContentValues mCallDataValues; - private static final String TAG = "NoteData"; - + /** + * 构造方法,初始化笔记数据对象 + */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -148,10 +213,19 @@ public class Note { mCallDataId = 0; } + /** + * 检查笔记数据是否在本地被修改 + * @return 如果数据被修改返回true,否则返回false + */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + /** + * 设置文本数据ID + * @param id 文本数据ID + * @throws IllegalArgumentException 如果ID无效 + */ void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +233,11 @@ public class Note { mTextDataId = id; } + /** + * 设置通话记录数据ID + * @param id 通话记录数据ID + * @throws IllegalArgumentException 如果ID无效 + */ void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,21 +245,38 @@ public class Note { mCallDataId = id; } + /** + * 设置通话记录数据内容 + * @param key 数据键名 + * @param value 数据值 + */ void setCallData(String key, String value) { mCallDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置文本数据内容 + * @param key 数据键名 + * @param value 数据值 + */ void setTextData(String key, String value) { mTextDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 将笔记数据推送到ContentResolver + * @param context 上下文对象 + * @param noteId 笔记ID + * @return 如果操作成功返回笔记的Uri,否则返回null + * @throws IllegalArgumentException 如果笔记ID无效 + */ Uri pushIntoContentResolver(Context context, long noteId) { /** - * Check for safety + * 安全检查 */ if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -192,6 +288,7 @@ public class Note { if(mTextDataValues.size() > 0) { mTextDataValues.put(DataColumns.NOTE_ID, noteId); if (mTextDataId == 0) { + // 新建文本数据 mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); @@ -203,6 +300,7 @@ public class Note { return null; } } else { + // 更新已有文本数据 builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mTextDataId)); builder.withValues(mTextDataValues); @@ -214,6 +312,7 @@ public class Note { if(mCallDataValues.size() > 0) { mCallDataValues.put(DataColumns.NOTE_ID, noteId); if (mCallDataId == 0) { + // 新建通话记录数据 mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); @@ -225,6 +324,7 @@ public class Note { return null; } } else { + // 更新已有通话记录数据 builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mCallDataId)); builder.withValues(mCallDataValues); @@ -233,6 +333,7 @@ public class Note { mCallDataValues.clear(); } + // 执行批量操作 if (operationList.size() > 0) { try { ContentProviderResult[] results = context.getContentResolver().applyBatch( diff --git a/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java b/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java index 39f6ec4..1658892 100644 --- a/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java +++ b/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java @@ -36,11 +36,21 @@ import java.io.IOException; import java.io.PrintStream; +/** + * 备份工具类,用于将笔记数据导出为文本格式 + * 采用单例模式实现,支持将所有笔记(包括普通笔记和通话记录)导出到SD卡 + */ public class BackupUtils { + // 日志标签 private static final String TAG = "BackupUtils"; // Singleton stuff private static BackupUtils sInstance; + /** + * 获取BackupUtils的单例实例 + * @param context 上下文对象 + * @return BackupUtils的单例实例 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -65,26 +75,54 @@ public class BackupUtils { private TextExport mTextExport; + /** + * 私有构造方法,用于创建BackupUtils实例 + * 初始化TextExport对象,用于实际的文本导出操作 + * @param context 上下文对象 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 检查外部存储(SD卡)是否可用 + * @return 如果SD卡已挂载返回true,否则返回false + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 将所有笔记导出为文本格式 + * @return 导出状态,可能的值包括: + * STATE_SD_CARD_UNMOUONTED - SD卡未挂载 + * STATE_SYSTEM_ERROR - 系统错误 + * STATE_SUCCESS - 导出成功 + */ public int exportToText() { return mTextExport.exportToText(); } + /** + * 获取导出的文本文件名 + * @return 导出的文本文件名 + */ public String getExportedTextFileName() { return mTextExport.mFileName; } + /** + * 获取导出的文本文件所在目录 + * @return 导出的文本文件所在目录路径 + */ public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 文本导出内部类,负责实际的笔记文本导出操作 + * 处理文件夹、普通笔记和通话记录笔记的导出格式 + */ private static class TextExport { private static final String[] NOTE_PROJECTION = { NoteColumns.ID, @@ -125,6 +163,10 @@ public class BackupUtils { private String mFileName; private String mFileDirectory; + /** + * 构造方法,初始化文本导出对象 + * @param context 上下文对象 + */ public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; @@ -132,12 +174,19 @@ public class BackupUtils { mFileDirectory = ""; } + /** + * 获取指定ID的文本格式字符串 + * @param id 格式ID + * @return 格式字符串 + */ private String getFormat(int id) { return TEXT_FORMAT[id]; } /** - * Export the folder identified by folder id to text + * 将指定文件夹ID下的所有笔记导出为文本格式 + * @param folderId 文件夹ID + * @param ps 打印流,用于输出文本内容 */ private void exportFolderToText(String folderId, PrintStream ps) { // Query notes belong to this folder @@ -163,7 +212,9 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 将指定ID的笔记导出到打印流 + * @param noteId 笔记ID + * @param ps 打印流,用于输出文本内容 */ private void exportNoteToText(String noteId, PrintStream ps) { Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, @@ -216,7 +267,11 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将所有笔记导出为用户可读的文本格式 + * @return 导出状态,可能的值包括: + * STATE_SD_CARD_UNMOUONTED - SD卡未挂载 + * STATE_SYSTEM_ERROR - 系统错误 + * STATE_SUCCESS - 导出成功 */ public int exportToText() { if (!externalStorageAvailable()) { @@ -283,7 +338,8 @@ public class BackupUtils { } /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 获取指向导出文本文件的打印流 + * @return 打印流对象,如果创建失败则返回null */ private PrintStream getExportToTextPrintStream() { File file = generateFileMountedOnSDcard(mContext, R.string.file_path, @@ -310,7 +366,11 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 生成存储在SD卡上的导出文件 + * @param context 上下文对象 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 生成的文件对象,如果创建失败则返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); diff --git a/src/Notes-master/src/net/micode/notes/tool/DataUtils.java b/src/Notes-master/src/net/micode/notes/tool/DataUtils.java index 2a14982..805be5a 100644 --- a/src/Notes-master/src/net/micode/notes/tool/DataUtils.java +++ b/src/Notes-master/src/net/micode/notes/tool/DataUtils.java @@ -35,8 +35,19 @@ import java.util.ArrayList; import java.util.HashSet; +/** + * 数据工具类,提供笔记数据的各种操作方法 + * 包括批量删除、移动、查询数量、检查存在性等功能 + */ public class DataUtils { + // 日志标签 public static final String TAG = "DataUtils"; + /** + * 批量删除笔记 + * @param resolver 内容解析器 + * @param ids 要删除的笔记ID集合 + * @return 删除成功返回true,失败返回false + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -72,6 +83,13 @@ public class DataUtils { return false; } + /** + * 将单个笔记移动到指定文件夹 + * @param resolver 内容解析器 + * @param id 要移动的笔记ID + * @param srcFolderId 源文件夹ID + * @param desFolderId 目标文件夹ID + */ public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -80,6 +98,13 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + /** + * 批量将笔记移动到指定文件夹 + * @param resolver 内容解析器 + * @param ids 要移动的笔记ID集合 + * @param folderId 目标文件夹ID + * @return 移动成功返回true,失败返回false + */ public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, long folderId) { if (ids == null) { @@ -112,7 +137,9 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取用户创建的文件夹数量,不包括系统文件夹 + * @param resolver 内容解析器 + * @return 用户文件夹数量 */ public static int getUserFolderCount(ContentResolver resolver) { Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, @@ -136,6 +163,13 @@ public class DataUtils { return count; } + /** + * 检查指定ID和类型的笔记是否在数据库中可见(不在垃圾箱中) + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @param type 笔记类型 + * @return 如果笔记可见返回true,否则返回false + */ public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, @@ -153,6 +187,12 @@ public class DataUtils { return exist; } + /** + * 检查指定ID的笔记是否存在于数据库中 + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 如果笔记存在返回true,否则返回false + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,6 +207,12 @@ public class DataUtils { return exist; } + /** + * 检查指定ID的数据项是否存在于数据库中 + * @param resolver 内容解析器 + * @param dataId 数据项ID + * @return 如果数据项存在返回true,否则返回false + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,6 +227,12 @@ public class DataUtils { return exist; } + /** + * 检查可见文件夹名称是否已存在(不在垃圾箱中) + * @param resolver 内容解析器 + * @param name 文件夹名称 + * @return 如果名称已存在返回true,否则返回false + */ public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + @@ -197,6 +249,12 @@ public class DataUtils { return exist; } + /** + * 获取指定文件夹下所有笔记的小部件属性 + * @param resolver 内容解析器 + * @param folderId 文件夹ID + * @return 小部件属性的哈希集合,如果没有则返回null + */ public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, @@ -224,6 +282,12 @@ public class DataUtils { return set; } + /** + * 根据笔记ID获取通话记录的电话号码 + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 电话号码,如果获取失败返回空字符串 + */ public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.PHONE_NUMBER }, @@ -243,6 +307,13 @@ public class DataUtils { return ""; } + /** + * 根据电话号码和通话日期获取通话记录的笔记ID + * @param resolver 内容解析器 + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + * @return 笔记ID,如果未找到返回0 + */ public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, @@ -264,6 +335,13 @@ public class DataUtils { return 0; } + /** + * 根据笔记ID获取笔记摘要 + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 笔记摘要 + * @throws IllegalArgumentException 如果未找到指定ID的笔记 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, @@ -282,6 +360,11 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + /** + * 格式化笔记摘要,去除首尾空格并只保留第一行 + * @param snippet 原始摘要 + * @return 格式化后的摘要 + */ public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); diff --git a/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java b/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java index 666b729..5923710 100644 --- a/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java +++ b/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java @@ -16,98 +16,147 @@ package net.micode.notes.tool; +/** + * Google任务字符串常量类,定义Google任务同步相关的JSON键和常量 + */ public class GTaskStringUtils { + // JSON操作ID public final static String GTASK_JSON_ACTION_ID = "action_id"; + // JSON操作列表 public final static String GTASK_JSON_ACTION_LIST = "action_list"; + // JSON操作类型 public final static String GTASK_JSON_ACTION_TYPE = "action_type"; + // JSON创建操作类型 public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; + // JSON获取全部操作类型 public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; + // JSON移动操作类型 public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; + // JSON更新操作类型 public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; + // JSON创建者ID public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + // JSON子实体 public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + // JSON客户端版本 public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + // JSON完成状态 public final static String GTASK_JSON_COMPLETED = "completed"; + // JSON当前列表ID public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + // JSON默认列表ID public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + // JSON删除状态 public final static String GTASK_JSON_DELETED = "deleted"; + // JSON目标列表 public final static String GTASK_JSON_DEST_LIST = "dest_list"; + // JSON目标父项 public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + // JSON目标父项类型 public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + // JSON实体变更 public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + // JSON实体类型 public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + // JSON获取已删除项目 public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + // JSON ID public final static String GTASK_JSON_ID = "id"; + // JSON索引 public final static String GTASK_JSON_INDEX = "index"; + // JSON最后修改时间 public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + // JSON最新同步点 public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + // JSON列表ID public final static String GTASK_JSON_LIST_ID = "list_id"; + // JSON列表集合 public final static String GTASK_JSON_LISTS = "lists"; + // JSON名称 public final static String GTASK_JSON_NAME = "name"; + // JSON新ID public final static String GTASK_JSON_NEW_ID = "new_id"; + // JSON笔记内容 public final static String GTASK_JSON_NOTES = "notes"; + // JSON父项ID public final static String GTASK_JSON_PARENT_ID = "parent_id"; + // JSON前一个兄弟项ID public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + // JSON结果集合 public final static String GTASK_JSON_RESULTS = "results"; + // JSON源列表 public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + // JSON任务集合 public final static String GTASK_JSON_TASKS = "tasks"; + // JSON类型 public final static String GTASK_JSON_TYPE = "type"; + // JSON群组类型 public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + // JSON任务类型 public final static String GTASK_JSON_TYPE_TASK = "TASK"; + // JSON用户 public final static String GTASK_JSON_USER = "user"; + // MIUI文件夹前缀 public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + // 默认文件夹 public final static String FOLDER_DEFAULT = "Default"; + // 通话记录文件夹 public final static String FOLDER_CALL_NOTE = "Call_Note"; + // 元数据文件夹 public final static String FOLDER_META = "METADATA"; + // 元数据Google任务ID头 public final static String META_HEAD_GTASK_ID = "meta_gid"; + // 元数据笔记头 public final static String META_HEAD_NOTE = "meta_note"; + // 元数据数据头 public final static String META_HEAD_DATA = "meta_data"; + // 元数据笔记名称 public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; } diff --git a/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java b/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..f814a2e 100644 --- a/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java +++ b/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java @@ -22,24 +22,43 @@ import android.preference.PreferenceManager; import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; +/** + * 资源解析类,管理笔记应用的各种资源 + * 包括背景颜色、字体大小、背景图片等资源的定义和获取方法 + */ public class ResourceParser { + // 背景颜色常量 - 黄色 public static final int YELLOW = 0; + // 背景颜色常量 - 蓝色 public static final int BLUE = 1; + // 背景颜色常量 - 白色 public static final int WHITE = 2; + // 背景颜色常量 - 绿色 public static final int GREEN = 3; + // 背景颜色常量 - 红色 public static final int RED = 4; + // 默认背景颜色 public static final int BG_DEFAULT_COLOR = YELLOW; + // 字体大小常量 - 小 public static final int TEXT_SMALL = 0; + // 字体大小常量 - 中 public static final int TEXT_MEDIUM = 1; + // 字体大小常量 - 大 public static final int TEXT_LARGE = 2; + // 字体大小常量 - 超大 public static final int TEXT_SUPER = 3; + // 默认字体大小 public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + /** + * 笔记背景资源类,管理笔记编辑界面的背景资源 + */ public static class NoteBgResources { + // 编辑界面背景资源数组 private final static int [] BG_EDIT_RESOURCES = new int [] { R.drawable.edit_yellow, R.drawable.edit_blue, @@ -48,6 +67,7 @@ public class ResourceParser { R.drawable.edit_red }; + // 编辑界面标题栏背景资源数组 private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { R.drawable.edit_title_yellow, R.drawable.edit_title_blue, @@ -56,15 +76,30 @@ public class ResourceParser { R.drawable.edit_title_red }; + /** + * 获取笔记编辑界面的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getNoteBgResource(int id) { return BG_EDIT_RESOURCES[id]; } + /** + * 获取笔记编辑界面标题栏的背景资源 + * @param id 背景颜色ID + * @return 标题栏背景资源ID + */ public static int getNoteTitleBgResource(int id) { return BG_EDIT_TITLE_RESOURCES[id]; } } + /** + * 获取默认背景颜色ID + * @param context 上下文对象 + * @return 默认背景颜色ID + */ public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -74,7 +109,11 @@ public class ResourceParser { } } + /** + * 笔记列表项背景资源类,管理笔记列表中各项的背景资源 + */ public static class NoteItemBgResources { + // 列表项第一个元素的背景资源数组 private final static int [] BG_FIRST_RESOURCES = new int [] { R.drawable.list_yellow_up, R.drawable.list_blue_up, @@ -83,6 +122,7 @@ public class ResourceParser { R.drawable.list_red_up }; + // 列表项中间元素的背景资源数组 private final static int [] BG_NORMAL_RESOURCES = new int [] { R.drawable.list_yellow_middle, R.drawable.list_blue_middle, @@ -91,6 +131,7 @@ public class ResourceParser { R.drawable.list_red_middle }; + // 列表项最后一个元素的背景资源数组 private final static int [] BG_LAST_RESOURCES = new int [] { R.drawable.list_yellow_down, R.drawable.list_blue_down, @@ -99,6 +140,7 @@ public class ResourceParser { R.drawable.list_red_down, }; + // 列表项单独元素的背景资源数组 private final static int [] BG_SINGLE_RESOURCES = new int [] { R.drawable.list_yellow_single, R.drawable.list_blue_single, @@ -107,28 +149,56 @@ public class ResourceParser { R.drawable.list_red_single }; + /** + * 获取列表项第一个元素的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getNoteBgFirstRes(int id) { return BG_FIRST_RESOURCES[id]; } + /** + * 获取列表项最后一个元素的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getNoteBgLastRes(int id) { return BG_LAST_RESOURCES[id]; } + /** + * 获取列表项单独元素的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getNoteBgSingleRes(int id) { return BG_SINGLE_RESOURCES[id]; } + /** + * 获取列表项中间元素的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getNoteBgNormalRes(int id) { return BG_NORMAL_RESOURCES[id]; } + /** + * 获取文件夹的背景资源 + * @return 文件夹背景资源ID + */ public static int getFolderBgRes() { return R.drawable.list_folder; } } + /** + * 小部件背景资源类,管理笔记小部件的背景资源 + */ public static class WidgetBgResources { + // 2x尺寸小部件的背景资源数组 private final static int [] BG_2X_RESOURCES = new int [] { R.drawable.widget_2x_yellow, R.drawable.widget_2x_blue, @@ -137,10 +207,16 @@ public class ResourceParser { R.drawable.widget_2x_red, }; + /** + * 获取2x尺寸小部件的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } + // 4x尺寸小部件的背景资源数组 private final static int [] BG_4X_RESOURCES = new int [] { R.drawable.widget_4x_yellow, R.drawable.widget_4x_blue, @@ -149,12 +225,21 @@ public class ResourceParser { R.drawable.widget_4x_red }; + /** + * 获取4x尺寸小部件的背景资源 + * @param id 背景颜色ID + * @return 背景资源ID + */ public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } + /** + * 文本外观资源类,管理笔记文本的各种外观样式 + */ public static class TextAppearanceResources { + // 文本外观资源数组 private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { R.style.TextAppearanceNormal, R.style.TextAppearanceMedium, @@ -162,6 +247,11 @@ public class ResourceParser { R.style.TextAppearanceSuper }; + /** + * 获取文本外观资源 + * @param id 字体大小ID + * @return 文本外观资源ID + */ public static int getTexAppearanceResource(int id) { /** * HACKME: Fix bug of store the resource id in shared preference. @@ -174,6 +264,10 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES[id]; } + /** + * 获取文本外观资源数组的长度 + * @return 文本外观资源数组的长度 + */ public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; } diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..4a78928 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -40,13 +40,26 @@ 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; @Override + /** + * 创建活动实例,初始化窗口参数、获取笔记信息并显示提醒 + * @param savedInstanceState 保存的实例状态 + */ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -83,11 +96,19 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 检查屏幕是否处于点亮状态 + * @return 如果屏幕点亮返回true,否则返回false + */ private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + /** + * 播放闹钟声音 + * 从系统设置中获取默认闹钟铃声并播放 + */ private void playAlarmSound() { Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); @@ -105,20 +126,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD mPlayer.setLooping(true); mPlayer.start(); } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } + /** + * 显示提醒对话框 + * 包含笔记摘要内容和操作按钮 + */ private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.app_name); @@ -130,6 +151,11 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD dialog.show().setOnDismissListener(this); } + /** + * 处理对话框按钮的点击事件 + * @param dialog 对话框对象 + * @param which 被点击的按钮ID + */ public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: @@ -143,11 +169,18 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 处理对话框消失事件 + * @param dialog 对话框对象 + */ public void onDismiss(DialogInterface dialog) { stopAlarmSound(); finish(); } + /** + * 停止播放闹钟声音并释放媒体播放器资源 + */ private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java index f221202..1ea2d9a 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java @@ -28,19 +28,33 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +/** + * 闹钟初始化接收器,用于在系统启动或应用安装时重新设置所有笔记的提醒闹钟 + * 该接收器负责在系统重启后或应用数据清除后,重新为所有设置了提醒时间且 + * 提醒时间尚未到达的笔记设置闹钟,确保提醒功能的完整性。 + */ public class AlarmInitReceiver extends BroadcastReceiver { + // 查询数据库时使用的投影列 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; + /** + * 接收广播时的处理方法 + * @param context 上下文对象 + * @param intent 接收到的广播意图 + */ @Override public void onReceive(Context context, Intent intent) { long currentDate = System.currentTimeMillis(); + // 查询所有设置了提醒时间且提醒时间尚未到达的笔记 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, @@ -50,6 +64,7 @@ public class AlarmInitReceiver extends BroadcastReceiver { if (c != null) { if (c.moveToFirst()) { do { + // 为每个符合条件的笔记设置闹钟 long alertDate = c.getLong(COLUMN_ALERTED_DATE); Intent sender = new Intent(context, AlarmReceiver.class); sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java index 54e503b..2da9454 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java @@ -20,11 +20,24 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +/** + * 闹钟广播接收器,用于处理系统闹钟广播并启动闹钟提醒活动 + * 当设置的笔记提醒时间到达时,系统会发送广播,该接收器会捕获此广播 + * 并启动AlarmAlertActivity来显示提醒对话框和播放闹钟声音。 + */ public class AlarmReceiver extends BroadcastReceiver { + /** + * 接收闹钟广播时的处理方法 + * @param context 上下文对象 + * @param intent 包含闹钟信息的广播意图 + */ @Override public void onReceive(Context context, Intent intent) { + // 将广播意图的目标类设置为AlarmAlertActivity intent.setClass(context, AlarmAlertActivity.class); + // 添加新任务标志,确保在非Activity上下文环境中也能启动活动 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 启动闹钟提醒活动 context.startActivity(intent); } } diff --git a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java index 496b0cd..2f4d9fa 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java @@ -28,6 +28,11 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +/** + * 日期时间选择器控件,用于选择日期和时间 + * 该控件提供了日期、小时、分钟和AM/PM的选择功能,支持12小时制和24小时制显示, + * 并能根据用户的选择自动更新日期和时间。 + */ public class DateTimePicker extends FrameLayout { private static final boolean DEFAULT_ENABLE_STATE = true; @@ -158,39 +163,72 @@ public class DateTimePicker extends FrameLayout { } }; + /** + * 日期时间改变监听器接口,用于监听日期或时间的变化事件 + */ public interface OnDateTimeChangedListener { + /** + * 当日期或时间改变时调用 + * @param view 日期时间选择器控件 + * @param year 选择的年份 + * @param month 选择的月份 + * @param dayOfMonth 选择的日期 + * @param hourOfDay 选择的小时(24小时制) + * @param minute 选择的分钟 + */ void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute); } + /** + * 构造方法,使用当前时间和系统时间格式初始化日期时间选择器 + * @param context 上下文对象 + */ public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + /** + * 构造方法,使用指定时间和系统时间格式初始化日期时间选择器 + * @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(); mInitialising = true; + // 判断当前时间是上午还是下午 mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + // 加载布局文件 inflate(context, R.layout.datetime_picker, this); + // 初始化日期选择器 mDateSpinner = (NumberPicker) findViewById(R.id.date); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + // 初始化小时选择器 mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + // 初始化分钟选择器 mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); mMinuteSpinner.setOnLongPressUpdateInterval(100); mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + // 初始化上午/下午选择器 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); @@ -198,19 +236,21 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新控件到初始状态 updateDateControl(); updateHourControl(); updateAmPmControl(); + // 设置时间格式(12小时制或24小时制) set24HourView(is24HourView); - // set to current time + // 设置初始日期时间 setCurrentDate(date); + // 设置控件是否可用 setEnabled(isEnabled()); - // set the content descriptions + // 设置内容描述 mInitialising = false; } @@ -348,6 +388,10 @@ public class DateTimePicker extends FrameLayout { return mDate.get(Calendar.HOUR_OF_DAY); } + /** + * 获取当前小时(根据时间格式调整) + * @return 12小时制下返回1-12,24小时制下返回0-23 + */ private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); @@ -434,30 +478,47 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } + /** + * 更新日期选择器控件的显示内容 + * 显示当前日期前后一周的日期选项,格式为"MM.dd EEEE" + */ private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); + // 设置起始日期为当前日期的前四天 cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); mDateSpinner.setDisplayedValues(null); + // 生成一周的日期显示值 for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { cal.add(Calendar.DAY_OF_YEAR, 1); mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); } mDateSpinner.setDisplayedValues(mDateDisplayValues); + // 设置默认选中当前日期 mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + // 刷新控件显示 mDateSpinner.invalidate(); } + /** + * 更新上午/下午选择器控件的显示状态 + * 在24小时制下隐藏AM/PM选择器,在12小时制下显示并设置正确的选中状态 + */ private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); } else { + // 根据当前是上午还是下午设置选中状态 int index = mIsAm ? Calendar.AM : Calendar.PM; mAmPmSpinner.setValue(index); mAmPmSpinner.setVisibility(View.VISIBLE); } } + /** + * 更新小时选择器控件的显示范围 + * 在24小时制下显示0-23,在12小时制下显示1-12 + */ private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); diff --git a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..1c9cf7c 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,59 +29,113 @@ import android.content.DialogInterface.OnClickListener; import android.text.format.DateFormat; import android.text.format.DateUtils; +/** + * 日期时间选择对话框,用于在对话框中选择日期和时间 + * 该对话框封装了DateTimePicker控件,提供了一个友好的界面让用户选择日期和时间, + * 并支持12小时制和24小时制显示。 + */ 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); + // 设置对话框的内容视图为日期时间选择器 setView(mDateTimePicker); + // 设置日期时间变化监听器 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute) { + // 更新当前选择的日期时间 mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.MINUTE, minute); + // 更新对话框标题为当前选择的日期时间 updateTitle(mDate.getTimeInMillis()); } }); + // 设置初始日期时间,忽略秒数 mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); + // 设置日期时间选择器的当前日期时间 mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + // 设置确定按钮 setButton(context.getString(R.string.datetime_dialog_ok), this); + // 设置取消按钮 setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + // 设置时间格式为系统默认格式 set24HourView(DateFormat.is24HourFormat(this.getContext())); + // 更新对话框标题 updateTitle(mDate.getTimeInMillis()); } + /** + * 设置是否使用24小时制显示时间 + * @param is24HourView 是否使用24小时制 + */ public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + /** + * 设置日期时间设置监听器 + * @param callBack 日期时间设置监听器 + */ public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + /** + * 更新对话框标题为指定的日期时间 + * @param date 日期时间(毫秒) + */ private void updateTitle(long date) { - int flag = + // 设置日期时间格式为显示年、月、日、时、分 + int flag = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME; + // 设置时间格式为24小时制或12小时制 flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + // 格式化日期时间并设置为对话框标题 setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + /** + * 处理确定按钮的点击事件 + * @param arg0 对话框接口 + * @param arg1 按钮索引 + */ public void onClick(DialogInterface arg0, int arg1) { + // 如果设置了监听器,则调用监听器的方法 if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); } diff --git a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java index 613dc74..177b9b4 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java +++ b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java @@ -27,17 +27,36 @@ 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); + // 创建弹出式菜单 mPopupMenu = new PopupMenu(context, mButton); + // 获取菜单对象 mMenu = mPopupMenu.getMenu(); + // 从资源文件中加载菜单 mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 设置按钮的点击监听器,点击时显示下拉菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +64,29 @@ public class DropdownMenu { }); } + /** + * 设置下拉菜单项的点击监听器 + * @param listener 菜单项点击监听器 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 根据ID查找菜单项 + * @param id 菜单项ID + * @return 找到的菜单项,如果没有找到则返回null + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置下拉菜单按钮的标题 + * @param title 按钮标题 + */ public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..9e6b7b5 100644 --- a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java @@ -29,49 +29,96 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +/** + * 文件夹列表适配器,用于在列表中显示文件夹项 + * 该适配器继承自CursorAdapter,用于将数据库查询结果中的文件夹数据显示在列表中, + * 支持自定义文件夹列表项的布局和数据绑定。 + */ public class FoldersListAdapter extends CursorAdapter { + // 数据库查询的列投影 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 包含文件夹数据的Cursor对象 + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); - // TODO Auto-generated constructor stub } + /** + * 创建新的列表项视图 + * @param context 上下文对象 + * @param cursor 包含文件夹数据的Cursor对象 + * @param parent 父视图组 + * @return 新创建的列表项视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 将文件夹数据绑定到列表项视图 + * @param view 列表项视图 + * @param context 上下文对象 + * @param cursor 包含文件夹数据的Cursor对象 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { + // 根文件夹显示特殊名称,其他文件夹显示实际名称 String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); ((FolderListItem) view).bind(folderName); } } + /** + * 根据位置获取文件夹名称 + * @param context 上下文对象 + * @param position 文件夹在列表中的位置 + * @return 文件夹名称 + */ public String getFolderName(Context context, int position) { Cursor cursor = (Cursor) getItem(position); + // 根文件夹显示特殊名称,其他文件夹显示实际名称 return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); } + /** + * 文件夹列表项视图类,用于显示单个文件夹项 + */ private class FolderListItem extends LinearLayout { + // 显示文件夹名称的TextView private TextView mName; + /** + * 构造方法,创建文件夹列表项视图 + * @param context 上下文对象 + */ public FolderListItem(Context context) { super(context); + // 加载文件夹列表项布局 inflate(context, R.layout.folder_list_item, this); + // 获取文件夹名称TextView mName = (TextView) findViewById(R.id.tv_folder_name); } + /** + * 将文件夹名称绑定到列表项视图 + * @param name 文件夹名称 + */ public void bind(String name) { mName.setText(name); } diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..8d70bd2 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -72,18 +72,44 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * NoteEditActivity - 便签编辑界面活动类 + * 作为便签应用的核心编辑界面,负责便签的创建、编辑、查看和删除等功能。 + * 支持普通文本模式和待办事项列表模式,提供背景颜色和字体大小的自定义设置, + * 以及提醒功能的管理。 + * + * 主要功能: + * - 便签内容编辑(支持普通文本和待办事项列表两种模式) + * - 背景颜色选择 + * - 字体大小调整 + * - 提醒时间设置 + * - 搜索结果高亮显示 + * - 便签快捷方式创建 + * - 便签 widget 支持 + * + * 核心组件: + * - {@link #mWorkingNote} - 处理便签数据的工作类 + * - {@link #mNoteEditor} - 便签内容编辑器 + * - {@link #mNoteBgColorSelector} - 背景颜色选择器 + * - {@link #mFontSizeSelector} - 字体大小选择器 + */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + /** + * HeadViewHolder - 便签头部视图的持有者类 + * 用于缓存便签头部的各个UI组件,提高视图访问效率。 + */ private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvAlertDate; - - public ImageView ibSetBgColor; + public TextView tvModified; // 修改日期文本视图 + public ImageView ivAlertIcon; // 提醒图标 + public TextView tvAlertDate; // 提醒日期文本视图 + public ImageView ibSetBgColor; // 设置背景颜色按钮 } + /** + * 背景选择按钮ID与背景颜色资源ID的映射关系 + * 用于快速查找用户点击的背景选择按钮对应的背景颜色资源。 + */ private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +119,10 @@ 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 +132,10 @@ 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 +144,10 @@ 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,36 +156,29 @@ 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 static final String TAG = "NoteEditActivity"; // 日志标签 - private WorkingNote mWorkingNote; + private HeadViewHolder mNoteHeaderHolder; // 便签头部视图持有者 + private View mHeadViewPanel; // 便签头部面板 + private View mNoteBgColorSelector; // 背景颜色选择器视图 + private View mFontSizeSelector; // 字体大小选择器视图 + private EditText mNoteEditor; // 便签内容编辑器 + private View mNoteEditorPanel; // 便签编辑器面板 + private WorkingNote mWorkingNote; // 工作便签实例 + private SharedPreferences mSharedPrefs; // 共享偏好设置 + private int mFontSizeId; // 当前字体大小ID - private SharedPreferences mSharedPrefs; - private int mFontSizeId; + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好键 - private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷方式图标标题最大长度 - private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + public static final String TAG_CHECKED = String.valueOf('\u221A'); // 待办事项已完成标记 + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 待办事项未完成标记 - public static final String TAG_CHECKED = String.valueOf('\u221A'); - public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + private LinearLayout mEditTextList; // 便签编辑列表容器 - private LinearLayout mEditTextList; - - private String mUserQuery; - private Pattern mPattern; + private String mUserQuery; // 用户搜索查询词 + private Pattern mPattern; // 搜索查询词的正则表达式模式 @Override protected void onCreate(Bundle savedInstanceState) { @@ -179,6 +210,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, @@ -268,6 +305,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, initNoteScreen(); } + /** + * 初始化便签界面 + * 设置便签的字体大小、内容、背景颜色和提醒信息等界面元素。 + * 根据便签模式(普通文本或待办事项列表)显示不同的界面布局。 + */ private void initNoteScreen() { mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); @@ -349,6 +391,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 +412,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 初始化界面资源 + * 初始化并绑定所有界面元素,设置监听器,加载偏好设置等。 + */ private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); @@ -425,6 +478,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, setResult(RESULT_OK, intent); } + /** + * 处理视图点击事件 + * 响应背景颜色选择按钮、字体大小选择按钮等UI元素的点击事件, + * 执行相应的背景颜色和字体大小设置操作。 + * @param v 被点击的视图 + */ public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_set_bg_color) { @@ -452,6 +511,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 处理返回按钮点击事件 + * 先检查是否需要清除当前的设置状态(如背景选择器或字体选择器是否可见), + * 如果不需要则保存便签并返回上一个界面。 + */ @Override public void onBackPressed() { if(clearSettingState()) { @@ -462,6 +526,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, super.onBackPressed(); } + /** + * 清除设置状态 + * 隐藏背景颜色选择器和字体大小选择器等设置界面元素。 + * @return 是否有设置界面元素被隐藏 + */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); @@ -473,6 +542,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + /** + * 背景颜色变化回调方法 + * 当便签的背景颜色发生变化时调用,更新界面上的背景颜色显示。 + */ public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); @@ -553,6 +626,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 设置便签提醒 + * 显示日期时间选择对话框,让用户选择提醒时间,并设置到当前便签上。 + */ private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { @@ -574,6 +651,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + /** + * 创建新便签 + * 保存当前正在编辑的便签,然后启动一个新的NoteEditActivity来创建新便签。 + */ private void createNewNote() { // Firstly, save current editing notes saveNote(); @@ -586,6 +667,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + /** + * 删除当前便签 + * 根据是否处于同步模式,将当前便签直接删除或移到回收站文件夹。 + */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); @@ -608,10 +693,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.markDeleted(true); } + /** + * 检查是否处于同步模式 + * 根据是否设置了Google同步账户来判断是否处于同步模式。 + * @return 是否处于同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 提醒时间变化回调方法 + * 当便签的提醒时间发生变化时调用,更新界面上的提醒信息显示。 + * @param date 提醒时间戳 + * @param set 是否设置了提醒 + */ public void onClockAlertChanged(long date, boolean set) { /** * User could set clock to an unsaved note, so before setting the @@ -642,10 +738,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 小部件变更回调方法 + * 当便签的小部件相关属性发生变化时调用,更新小部件显示。 + */ public void onWidgetChanged() { updateWidget(); } + /** + * 编辑框删除回调方法 + * 当待办事项列表模式下删除某个编辑框时调用,处理列表项的重新排序和内容合并。 + * @param index 删除的编辑框索引 + * @param text 被删除编辑框中的文本内容 + */ public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { @@ -672,6 +778,12 @@ 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 @@ -691,6 +803,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 切换到列表模式 + * 将便签从普通文本模式切换到待办事项列表模式,根据现有文本内容创建待办事项列表项。 + * @param text 便签的当前文本内容 + */ private void switchToListMode(String text) { mEditTextList.removeAllViews(); String[] items = text.split("\n"); @@ -708,6 +825,13 @@ 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 +849,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return spannable; } + /** + * 获取列表项视图 + * 为待办事项列表模式创建并配置一个列表项视图,包含复选框和编辑框。 + * @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 +887,13 @@ 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 +906,13 @@ 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 +927,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 获取当前工作文本 + * 根据当前便签模式(普通文本或待办事项列表)获取编辑框中的文本内容, + * 并更新到WorkingNote对象中。对于待办事项列表模式,会为每个列表项添加 + * 相应的完成或未完成标记。 + * @return 是否有待办事项已完成 + */ private boolean getWorkingText() { boolean hasChecked = false; if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { @@ -805,28 +957,32 @@ public class NoteEditActivity extends Activity implements OnClickListener, return hasChecked; } + /** + * 保存便签 + * 获取当前编辑的文本内容并保存到数据库中。根据保存结果设置返回结果码, + * 用于区分是打开现有便签还是创建新便签的情况。 + * 从列表视图到编辑视图有两种模式:打开现有便签和创建/编辑便签。 + * 打开便签时,返回时需要回到列表中的原始位置; + * 创建新便签时,返回时需要回到列表顶部。 + * 这里使用{@link #RESULT_OK}来标识创建/编辑状态。 + * @return 是否成功保存便签 + */ private boolean saveNote() { getWorkingText(); boolean saved = mWorkingNote.saveNote(); if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ setResult(RESULT_OK); } return saved; } + /** + * 发送便签到桌面 + * 在桌面上创建当前便签的快捷方式,方便用户快速访问。 + * 发送前会确保当前编辑的便签已保存到数据库中。 + */ 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(); } @@ -846,16 +1002,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, showToast(R.string.info_note_enter_desktop); sendBroadcast(sender); } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ + // 用户未输入任何内容(便签不值得保存),没有便签ID Log.e(TAG, "Send to desktop error"); showToast(R.string.error_note_empty_for_send_to_desktop); } } + /** + * 生成快捷方式图标标题 + * 从便签内容中提取快捷方式图标的标题,移除待办事项的标记字符, + * 并确保标题长度不超过最大限制。 + * @param content 便签的完整内容 + * @return 生成的快捷方式图标标题 + */ private String makeShortcutIconTitle(String content) { content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..e301941 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java @@ -37,16 +37,31 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * 自定义的便签编辑框 + * 扩展自EditText,支持链接检测与处理(电话、网页、邮件)、 + * 键盘事件监听(删除键、回车键)以及与NoteEditActivity的交互。 + */ public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; + + /** 当前编辑框在列表中的索引位置 */ private int mIndex; + + /** 删除键按下前的光标位置 */ private int mSelectionStartBeforeDelete; + /** 电话链接协议 */ private static final String SCHEME_TEL = "tel:" ; + /** 网页链接协议 */ private static final String SCHEME_HTTP = "http:" ; + /** 邮件链接协议 */ private static final String SCHEME_EMAIL = "mailto:" ; + /** 链接协议与对应操作资源ID的映射表 */ private static final Map sSchemaActionResMap = new HashMap(); + + /** 初始化链接协议与操作资源ID的映射关系 */ static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); @@ -54,7 +69,8 @@ public class NoteEditText extends EditText { } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 编辑框变化监听器接口 + * 由{@link NoteEditActivity}调用,用于处理编辑框的删除、添加和文本变化事件。 */ public interface OnTextViewChangeListener { /** @@ -75,35 +91,64 @@ public class NoteEditText extends EditText { 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()) { case MotionEvent.ACTION_DOWN: - + // 计算点击位置相对于文本内容的坐标 int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); @@ -111,6 +156,7 @@ public class NoteEditText extends EditText { x += getScrollX(); y += getScrollY(); + // 根据坐标获取对应的行和偏移量,并设置光标位置 Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); @@ -121,15 +167,24 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + /** + * 处理按键按下事件 + * 重写父类方法,对回车键和删除键进行特殊处理。 + * @param keyCode 按键编码 + * @param event 按键事件对象 + * @return 是否消费了该事件 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_ENTER: + // 如果设置了监听器,则不处理回车键按下事件,由onKeyUp处理 if (mOnTextViewChangeListener != null) { return false; } break; case KeyEvent.KEYCODE_DEL: + // 记录删除键按下前的光标位置 mSelectionStartBeforeDelete = getSelectionStart(); break; default: @@ -138,10 +193,19 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + /** + * 处理按键释放事件 + * 重写父类方法,对删除键和回车键释放事件进行特殊处理,实现编辑框的删除和添加功能。 + * + * @param keyCode 按键编码 + * @param event 按键事件对象 + * @return 是否消费了该事件 + */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { case KeyEvent.KEYCODE_DEL: + // 处理删除键释放事件,如果光标在开头且不是第一个编辑框,则删除当前编辑框 if (mOnTextViewChangeListener != null) { if (0 == mSelectionStartBeforeDelete && mIndex != 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); @@ -152,10 +216,14 @@ public class NoteEditText extends EditText { } break; case KeyEvent.KEYCODE_ENTER: + // 处理回车键释放事件,在当前编辑框后添加新的编辑框 if (mOnTextViewChangeListener != null) { int selectionStart = getSelectionStart(); + // 获取光标后的文本内容 String text = getText().subSequence(selectionStart, length()).toString(); + // 截断当前编辑框的文本到光标位置 setText(getText().subSequence(0, selectionStart)); + // 通知监听器添加新的编辑框 mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); } else { Log.d(TAG, "OnTextViewChangeListener was not seted"); @@ -167,29 +235,47 @@ 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) { if (!focused && TextUtils.isEmpty(getText())) { + // 失去焦点且文本为空,通知监听器 mOnTextViewChangeListener.onTextChange(mIndex, false); } else { + // 获得焦点或文本不为空,通知监听器 mOnTextViewChangeListener.onTextChange(mIndex, true); } } super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单 + * 重写父类方法,为链接文本添加上下文菜单项,支持电话、网页和邮件链接的快捷操作。 + * @param menu 上下文菜单对象 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { + // 检查文本是否包含链接 if (getText() instanceof Spanned) { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); + // 获取选择区域的起始和结束位置 int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); + // 获取选择区域内的URLSpan final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { + // 根据链接协议获取对应的操作资源ID int defaultResId = 0; for(String schema: sSchemaActionResMap.keySet()) { if(urls[0].getURL().indexOf(schema) >= 0) { @@ -198,14 +284,16 @@ public class NoteEditText extends EditText { } } + // 如果没有匹配的协议,则使用默认操作 if (defaultResId == 0) { defaultResId = R.string.note_link_other; } + // 添加上下文菜单项并设置点击事件 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // goto a new intent + // 执行链接点击操作 urls[0].onClick(NoteEditText.this); return true; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java index 0f5a878..eef4adb 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java @@ -26,7 +26,16 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; +/** + * 便签列表项数据模型类 + * 用于从数据库Cursor中提取和封装便签数据,提供访问便签各种属性的方法。 + * 包含便签的基本信息、样式设置、位置状态等。 + */ public class NoteItemData { + /** + * 数据库查询的字段投影数组 + * 定义了从Notes表中查询的字段列表,用于构建数据库查询。 + */ static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE, @@ -42,40 +51,77 @@ 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; + /** + * 构造函数 + * 从数据库Cursor中提取便签数据并初始化对象。 + * @param context 上下文对象 + * @param cursor 数据库查询结果Cursor + */ public NoteItemData(Context context, Cursor cursor) { mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); @@ -86,6 +132,7 @@ public class NoteItemData { mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); mParentId = cursor.getLong(PARENT_ID_COLUMN); mSnippet = cursor.getString(SNIPPET_COLUMN); + // 移除待办事项标记 mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( NoteEditActivity.TAG_UNCHECKED, ""); mType = cursor.getInt(TYPE_COLUMN); @@ -93,6 +140,7 @@ public class NoteItemData { mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mPhoneNumber = ""; + // 如果是通话记录便签,获取电话号码和联系人信息 if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) { @@ -106,9 +154,15 @@ public class NoteItemData { if (mName == null) { mName = ""; } + // 检查便签在列表中的位置状态 checkPostion(cursor); } + /** + * 检查便签在列表中的位置状态 + * 确定便签是否为列表的第一项、最后一项、唯一一项,以及是否为文件夹下的单个或多个便签之一。 + * @param cursor 数据库查询结果Cursor + */ private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; @@ -116,17 +170,21 @@ public class NoteItemData { mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; + // 检查是否为文件夹下的便签 if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { int position = cursor.getPosition(); if (cursor.moveToPrevious()) { + // 检查前一项是否为文件夹 if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + // 检查是否为文件夹下的多个便签之一 if (cursor.getCount() > (position + 1)) { mIsMultiNotesFollowingFolder = true; } else { mIsOneNoteFollowingFolder = true; } } + // 恢复Cursor位置 if (!cursor.moveToNext()) { throw new IllegalStateException("cursor move to previous but can't move back"); } @@ -134,90 +192,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(与getParentId相同) + * @return 文件夹ID + */ public long getFolderId () { return mParentId; } + /** + * 获取便签类型 + * @return 便签类型 + */ public int getType() { return mType; } + /** + * 获取小部件类型 + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 获取小部件ID + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取便签摘要文本 + * @return 便签摘要文本 + */ public String getSnippet() { return mSnippet; } + /** + * 是否设置了提醒 + * @return 是否设置了提醒 + */ public boolean hasAlert() { return (mAlertDate > 0); } + /** + * 是否为通话记录便签 + * @return 是否为通话记录便签 + */ public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } + /** + * 从Cursor中获取便签类型 + * @param cursor 数据库查询结果Cursor + * @return 便签类型 + */ public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index e843aec..21459de 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -78,85 +78,119 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * NotesListActivity - 便签应用的主界面活动类 + * 作为应用的核心入口点,负责展示便签和文件夹的列表视图,提供完整的便签管理功能。 + * 实现了MVC架构中的Controller角色,协调数据层和视图层的交互。 + * + * 主要功能: + * - 便签列表的显示与更新(支持不同列表状态:根文件夹、子文件夹、通话记录文件夹) + * - 文件夹的创建、查看、重命名和删除管理 + * - 便签的新建、编辑、删除和批量操作 + * - 搜索功能集成 + * - 与Google Tasks的同步功能 + * - 便签内容的导出功能 + * - 应用小部件(Widget)的更新支持 + * + * 设计特点: + * - 使用AsyncQueryHandler处理异步数据查询,避免UI阻塞 + * - 实现多选操作模式(ActionMode)支持批量处理 + * - 基于状态模式(ListEditState)管理不同的列表显示状态 + * - 通过ContentResolver与NotesProvider交互,实现数据访问抽象 + * - 支持触摸手势识别和自定义视图交互 + */ public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { - private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - - private static final int FOLDER_LIST_QUERY_TOKEN = 1; - - private static final int MENU_FOLDER_DELETE = 0; - - private static final int MENU_FOLDER_VIEW = 1; - - private static final int MENU_FOLDER_CHANGE_NAME = 2; - - private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; - + // 异步查询令牌常量 + private 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; // 重命名文件夹菜单ID + + // SharedPreferences键名常量 + private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; // 首次使用引导标记 + + /** + * ListEditState - 列表编辑状态枚举 + * 定义当前列表视图的显示状态,用于控制不同类型内容的展示和相应的功能可用状态。 + * 实现了简单的状态模式,根据不同状态切换列表行为和UI元素。 + */ private enum ListEditState { - NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + NOTE_LIST, // 根文件夹状态:显示所有顶级文件夹和便签 + SUB_FOLDER, // 子文件夹状态:显示特定文件夹下的便签 + CALL_RECORD_FOLDER // 通话记录文件夹状态:显示与通话记录相关的便签 }; - private ListEditState mState; - - private BackgroundQueryHandler mBackgroundQueryHandler; - - private NotesListAdapter mNotesListAdapter; - - private ListView mNotesListView; - - private Button mAddNewNote; - - private boolean mDispatch; - - private int mOriginY; - - private int mDispatchY; - - private TextView mTitleBar; - - 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 ListEditState mState; // 当前列表编辑状态 + private BackgroundQueryHandler mBackgroundQueryHandler; // 后台查询处理器 + private NotesListAdapter mNotesListAdapter; // 便签列表适配器 + private ListView mNotesListView; // 便签列表视图 + private Button mAddNewNote; // 新建便签按钮 + private boolean mDispatch; // 触摸事件分发标记 + private int mOriginY; // 触摸事件原始Y坐标 + private int mDispatchY; // 触摸事件分发Y坐标 + private TextView mTitleBar; // 标题栏视图 + private long mCurrentFolderId; // 当前文件夹ID + private ContentResolver mContentResolver; // 内容解析器,用于数据访问 + private ModeCallback mModeCallBack; // 多选模式回调 + private static final String TAG = "NotesListActivity"; // 日志标记 + public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // 列表滚动速率 + private NoteItemData mFocusNoteDataItem; // 当前聚焦的便签数据项 + + // 数据库查询条件常量 + private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; // 普通文件夹查询条件 private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " - + NoteColumns.NOTES_COUNT + ">0)"; + + NoteColumns.NOTES_COUNT + ">0)"; // 根文件夹查询条件 - private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; + // Activity请求码常量 + private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开便签的请求码 + private final static int REQUEST_CODE_NEW_NODE = 103; // 新建便签的请求码 + /** + * Activity生命周期方法:创建活动时调用 + * 初始化活动布局、资源和数据,设置首次使用引导信息。 + * @param savedInstanceState 保存的实例状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.note_list); - initResources(); + setContentView(R.layout.note_list); // 设置布局文件 + initResources(); // 初始化资源 /** - * Insert an introduction when user firstly use this application + * 首次使用应用时插入引导信息 */ setAppInfoFromRawRes(); } + /** + * Activity生命周期方法:处理其他活动返回的结果 + * 当从便签编辑活动返回时,刷新便签列表数据。 + * @param requestCode 请求码 + * @param resultCode 结果码 + * @param data 返回的意图数据 + */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + // 当便签编辑完成后,重置列表适配器的游标以刷新数据 mNotesListAdapter.changeCursor(null); } else { super.onActivityResult(requestCode, resultCode, data); } } + /** + * 设置首次使用应用时的引导信息 + * 从raw资源中读取引导文本,创建一个新的便签并保存到数据库中。 + * 通过SharedPreferences标记引导信息已添加,避免重复添加。 + */ private void setAppInfoFromRawRes() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { @@ -184,7 +218,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } @@ -203,12 +236,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * Activity生命周期方法:活动可见时调用 + * 启动异步查询,加载便签列表数据。 + */ @Override protected void onStart() { super.onStart(); startAsyncNotesListQuery(); } + /** + * 初始化活动资源和视图组件 + * 设置所有UI组件、适配器、监听器和初始状态。 + */ private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); @@ -231,10 +272,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mModeCallBack = new ModeCallback(); } + /** + * ModeCallback - 多选操作模式回调类 + * 实现ListView.MultiChoiceModeListener和OnMenuItemClickListener接口, + * 负责处理便签列表的多选操作模式,包括选择状态管理、菜单创建和操作执行。 + */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { - private DropdownMenu mDropDownMenu; - private ActionMode mActionMode; - private MenuItem mMoveMenu; + private DropdownMenu mDropDownMenu; // 下拉菜单组件,用于选择全部/取消选择 + private ActionMode mActionMode; // 当前的操作模式实例 + private MenuItem mMoveMenu; // 移动菜单项,根据条件显示或隐藏 public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.note_list_options, menu); @@ -286,26 +332,62 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 准备操作模式菜单 + * 当操作模式菜单需要更新时调用,目前未实现任何自定义准备逻辑。 + * @param mode 当前操作模式 + * @param menu 要准备的菜单 + * @return false表示不更新菜单,true表示更新菜单 + */ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // TODO Auto-generated method stub return false; } + /** + * 操作模式菜单项点击事件处理 + * 处理操作模式菜单的选项点击事件,目前未实现任何自定义逻辑。 + * 实际操作处理通过onMenuItemClickListener接口实现。 + * @param mode 当前操作模式 + * @param item 选择的菜单项 + * @return false表示未处理事件,true表示已处理事件 + */ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { // TODO Auto-generated method stub return false; } + /** + * 销毁操作模式 + * 当操作模式结束时调用,恢复列表的正常显示状态: + * - 关闭选择模式 + * - 恢复长按功能 + * - 显示新建便签按钮 + * @param mode 要销毁的操作模式 + */ public void onDestroyActionMode(ActionMode mode) { mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); } + /** + * 结束当前操作模式 + * 手动关闭当前的操作模式,触发onDestroyActionMode回调。 + */ public void finishActionMode() { mActionMode.finish(); } + /** + * 列表项选择状态变化事件处理 + * 当列表项的选择状态发生变化时调用,更新适配器中的选择状态 + * 并刷新菜单显示(更新选中项数量和选择状态)。 + * @param mode 当前操作模式 + * @param position 列表项位置 + * @param id 列表项ID + * @param checked 新的选择状态 + */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { mNotesListAdapter.setCheckedItem(position, checked); @@ -346,6 +428,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * NewNoteOnTouchListener - 新建便签按钮触摸事件监听器 + * 自定义触摸事件处理,实现特殊的UI交互效果。 + * 主要功能是将新建便签按钮透明区域的触摸事件分发给下方的列表视图。 + */ private class NewNoteOnTouchListener implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { @@ -408,6 +495,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }; + /** + * 启动异步查询获取便签列表数据 + * 根据当前文件夹ID选择合适的查询条件,使用BackgroundQueryHandler在后台线程执行查询, + * 避免阻塞UI线程。查询结果将通过onQueryComplete回调方法更新到列表适配器。 + */ private void startAsyncNotesListQuery() { String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; @@ -417,6 +509,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * BackgroundQueryHandler - 后台查询处理器 + * 继承自AsyncQueryHandler,用于在后台线程执行数据库查询操作,避免阻塞UI线程。 + * 通过token参数区分不同类型的查询请求,并在查询完成后更新UI或执行相应操作。 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); @@ -426,9 +523,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt protected void onQueryComplete(int token, Object cookie, Cursor cursor) { switch (token) { case FOLDER_NOTE_LIST_QUERY_TOKEN: + // 更新便签列表适配器的数据源 mNotesListAdapter.changeCursor(cursor); break; case FOLDER_LIST_QUERY_TOKEN: + // 显示文件夹选择菜单 if (cursor != null && cursor.getCount() > 0) { showFolderListMenu(cursor); } else { @@ -441,6 +540,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示文件夹选择菜单 + * 创建并显示一个对话框,用于选择目标文件夹来移动选中的便签。 + * 使用FoldersListAdapter将查询结果显示为文件夹列表,并为每个文件夹项设置点击监听器。 + * 当用户选择文件夹后,执行批量移动操作并显示操作结果提示。 + * @param cursor 包含文件夹数据的游标 + */ private void showFolderListMenu(Cursor cursor) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(R.string.menu_title_select_folder); @@ -462,6 +568,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 +581,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + /** + * 批量删除选中的便签 + * 使用异步任务在后台线程执行批量删除操作,避免阻塞UI线程。 + * 根据是否启用同步模式执行不同操作: + * - 非同步模式:直接删除便签 + * - 同步模式:将便签移动到垃圾箱文件夹 + * 删除完成后更新相关的应用小部件并退出多选操作模式。 + */ private void batchDelete() { new AsyncTask>() { protected HashSet doInBackground(Void... unused) { @@ -506,6 +626,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 删除文件夹 + * 根据是否启用同步模式执行不同的文件夹删除操作: + * - 非同步模式:直接删除文件夹 + * - 同步模式:将文件夹移动到垃圾箱文件夹 + * 根文件夹无法删除,会直接返回错误日志。 + * 删除完成后更新相关的应用小部件。 + * @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 +662,12 @@ 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 +675,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } + /** + * 打开文件夹并显示其内容 + * 设置当前文件夹ID为选中的文件夹ID,启动异步查询获取该文件夹下的便签列表。 + * 根据文件夹类型设置不同的列表状态: + * - 通话记录文件夹:设置为CALL_RECORD_FOLDER状态并隐藏新建便签按钮 + * - 普通文件夹:设置为SUB_FOLDER状态 + * 同时更新标题栏显示当前文件夹名称。 + * @param data 要打开的文件夹数据 + */ private void openFolder(NoteItemData data) { mCurrentFolderId = data.getId(); startAsyncNotesListQuery(); @@ -557,6 +701,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mTitleBar.setVisibility(View.VISIBLE); } + /** + * 处理视图点击事件 + * 实现OnClickListener接口,处理UI组件的点击事件。 + * 目前主要处理新建便签按钮的点击事件。 + * @param v 被点击的视图组件 + */ public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: @@ -567,6 +717,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示软键盘 + * 获取系统输入法服务,并强制显示软键盘。 + */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { @@ -574,11 +728,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 隐藏软键盘 + * 获取系统输入法服务,并从指定视图的窗口隐藏软键盘。 + * @param view 用于获取窗口令牌的视图 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示创建或修改文件夹的对话框 + * 根据参数决定是创建新文件夹还是修改现有文件夹: + * - 创建模式:显示空输入框,标题为"创建文件夹" + * - 修改模式:显示现有文件夹名称,标题为"重命名文件夹" + * 验证文件夹名称的唯一性,确保不会创建重复名称的文件夹。 + * 操作完成后更新数据库并关闭对话框。 + * @param create true表示创建新文件夹,false表示修改现有文件夹 + */ private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); @@ -664,6 +832,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }); } + /** + * 处理返回键点击事件 + * 根据当前列表状态执行不同的返回操作: + * - SUB_FOLDER状态:返回到根文件夹视图 + * - CALL_RECORD_FOLDER状态:返回到根文件夹视图并显示新建便签按钮 + * - NOTE_LIST状态:执行默认的返回操作(退出应用) + */ @Override public void onBackPressed() { switch (mState) { @@ -688,6 +863,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 更新应用小部件 + * 根据小部件类型和ID发送广播,通知小部件更新其内容。 + * 支持2x和4x两种小部件类型,分别对应不同的小部件提供者。 + * @param appWidgetId 要更新的小部件ID + * @param appWidgetType 小部件类型(2x或4x) + */ private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (appWidgetType == Notes.TYPE_WIDGET_2X) { @@ -707,6 +889,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt setResult(RESULT_OK, intent); } + /** + * 文件夹上下文菜单创建监听器 + * 当长按文件夹项时创建上下文菜单,提供文件夹的常用操作选项: + * - 查看文件夹内容 + * - 删除文件夹 + * - 重命名文件夹 + */ private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { @@ -718,6 +907,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }; + /** + * 上下文菜单关闭事件处理 + * 当上下文菜单关闭时,移除列表视图的上下文菜单监听器, + * 防止重复创建菜单或内存泄漏。 + * @param menu 关闭的菜单 + */ @Override public void onContextMenuClosed(Menu menu) { if (mNotesListView != null) { @@ -726,6 +921,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt super.onContextMenuClosed(menu); } + /** + * 上下文菜单项选择事件处理 + * 处理文件夹上下文菜单的选项点击事件,执行相应的文件夹操作: + * - 查看文件夹内容 + * - 删除文件夹(显示确认对话框) + * - 重命名文件夹 + * @param item 选择的菜单项 + * @return true表示事件已处理,false表示未处理 + */ @Override public boolean onContextItemSelected(MenuItem item) { if (mFocusNoteDataItem == null) { @@ -760,6 +964,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 准备选项菜单 + * 根据当前列表状态动态加载不同的菜单资源: + * - NOTE_LIST状态:加载主菜单,包括新建文件夹、导出文本、同步等选项 + * - SUB_FOLDER状态:加载子文件夹菜单 + * - CALL_RECORD_FOLDER状态:加载通话记录文件夹菜单 + * 同时根据同步服务状态更新同步菜单项的标题。 + * @param menu 要准备的菜单 + * @return true表示菜单已准备好显示 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); @@ -778,6 +992,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 选项菜单项选择事件处理 + * 处理应用顶部操作栏的选项菜单点击事件,执行相应功能: + * - 创建新文件夹 + * - 导出便签为文本 + * - 同步与Google Tasks的数据 + * - 打开应用设置 + * - 创建新便签 + * - 启动搜索功能 + * @param item 选择的菜单项 + * @return true表示事件已处理,false表示未处理 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -818,12 +1044,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 搜索请求处理方法 + * 启动系统搜索功能,允许用户在便签中进行搜索。 + * @return true表示搜索已启动 + */ @Override public boolean onSearchRequested() { startSearch(null, false, null /* appData */, false); return true; } + /** + * 导出便签为文本文件 + * 使用BackupUtils在后台线程将所有便签导出为文本文件,支持SD卡存储。 + * 导出过程中显示进度对话框,并在完成后根据结果显示不同的提示信息: + * - SD卡未挂载错误 + * - 导出成功(显示文件路径) + * - 系统错误 + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { @@ -866,21 +1105,39 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 检查是否处于同步模式 + * 通过读取SharedPreferences中的同步账户名称判断是否启用了Google Tasks同步功能。 + * @return true表示已启用同步模式,false表示未启用 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 启动设置活动 + * 打开应用的设置界面,允许用户配置应用的各种选项, + * 如同步账户、显示设置等。 + */ private void startPreferenceActivity() { Activity from = getParent() != null ? getParent() : this; Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } + /** + * OnListItemClickListener - 列表项点击事件监听器 + * 处理便签列表项的点击事件,根据当前列表状态和点击的项目类型执行不同操作: + * - 在多选模式下:切换便签的选择状态 + * - 在普通模式下:根据项目类型(文件夹或便签)执行相应操作 + */ private class OnListItemClickListener implements OnItemClickListener { public void onItemClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { NoteItemData item = ((NotesListItem) view).getItemData(); + + // 如果处于多选模式,切换项目选择状态 if (mNotesListAdapter.isInChoiceMode()) { if (item.getType() == Notes.TYPE_NOTE) { position = position - mNotesListView.getHeaderViewsCount(); @@ -890,6 +1147,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return; } + // 根据当前列表状态和项目类型执行不同操作 switch (mState) { case NOTE_LIST: if (item.getType() == Notes.TYPE_FOLDER @@ -917,6 +1175,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } + /** + * 查询目标文件夹列表 + * 异步查询所有可用的文件夹(不包括垃圾箱文件夹和当前文件夹), + * 用于便签移动操作时选择目标文件夹。 + * 在非根文件夹状态下,还会包含根文件夹作为可选目标。 + */ private void startQueryDestinationFolders() { String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; selection = (mState == ListEditState.NOTE_LIST) ? selection: @@ -935,6 +1199,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 列表项长按事件处理 + * 根据长按的项目类型执行不同操作: + * - 便签:启动多选操作模式,允许批量处理便签 + * - 文件夹:为文件夹创建上下文菜单 + * 同时提供触觉反馈(震动)以增强用户体验。 + * @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/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..e5239ba 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java @@ -31,18 +31,39 @@ import java.util.HashSet; import java.util.Iterator; +/** + * NotesListAdapter - 便签列表适配器 + * 继承自CursorAdapter,负责为便签列表提供数据绑定和视图管理功能。 + * 支持多选模式、便签数量统计、选中项目管理以及应用小部件属性处理。 + * + * 主要功能: + * - 创建和绑定NotesListItem视图 + * - 管理多选模式下的选中状态 + * - 统计普通便签的数量 + * - 获取选中项目的ID集合 + * - 处理应用小部件相关的属性 + */ public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; + private static final String TAG = "NotesListAdapter"; // 日志标签 + private Context mContext; // 上下文环境 + private HashMap mSelectedIndex; // 记录选中项目的位置映射 + private int mNotesCount; // 普通便签的数量 + private boolean mChoiceMode; // 是否处于多选模式 + /** + * AppWidgetAttribute - 应用小部件属性类 + * 用于存储应用小部件的ID和类型信息,便于在多选操作中管理相关小部件。 + */ public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; + public int widgetId; // 小部件ID + public int widgetType; // 小部件类型 }; + /** + * 构造方法 - 创建便签列表适配器 + * 初始化适配器,设置上下文环境和选中项目的映射表。 + * @param context 上下文环境 + */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); @@ -50,11 +71,26 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } + /** + * 创建新的视图 - 生成NotesListItem实例 + * 当列表需要显示新的项目时调用此方法,创建一个新的NotesListItem视图。 + * @param context 上下文环境 + * @param cursor 当前位置的游标 + * @param parent 父视图组 + * @return 创建的新视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } + /** + * 绑定视图 - 将游标数据绑定到NotesListItem + * 将当前游标的数据转换为NoteItemData对象,并绑定到NotesListItem视图中。 + * @param view 要绑定的视图 + * @param context 上下文环境 + * @param cursor 包含数据的游标 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -64,20 +100,41 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 设置选中项目 - 更新指定位置的选中状态 + * 更新指定位置的选中状态,并通知数据集合变化。 + * @param position 项目位置 + * @param checked 选中状态 + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); notifyDataSetChanged(); } + /** + * 检查是否处于多选模式 + * 返回当前适配器是否处于多选模式。 + * @return true表示处于多选模式,false表示不处于多选模式 + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置多选模式 - 开启或关闭多选模式 + * 设置适配器的多选模式状态,并清空已选项目的映射表。 + * @param mode true表示开启多选模式,false表示关闭多选模式 + */ public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } + /** + * 全选/取消全选 - 选择或取消选择所有普通便签 + * 遍历所有项目,选择或取消选择所有类型为普通便签的项目。 + * @param checked true表示全选,false表示取消全选 + */ public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { @@ -89,6 +146,11 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取选中项目的ID集合 + * 返回所有选中项目的ID集合,排除根文件夹ID。 + * @return 选中项目的ID集合 + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -105,6 +167,11 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项目的应用小部件属性 + * 返回所有选中项目的应用小部件属性集合,包括小部件ID和类型。 + * @return 选中项目的应用小部件属性集合,无效时返回null + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -128,6 +195,11 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项目的数量 + * 统计当前选中项目的数量。 + * @return 选中项目的数量 + */ public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -143,11 +215,22 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 检查是否全选 + * 检查当前是否所有普通便签都被选中。 + * @return true表示所有普通便签都被选中,false表示不是 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 检查指定位置的项目是否被选中 + * 检查指定位置的项目在选中项目映射表中的状态。 + * @param position 项目位置 + * @return true表示项目被选中,false表示未被选中 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,18 +238,31 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + /** + * 内容变化回调 - 重新计算便签数量 + * 当适配器的内容发生变化时调用此方法,重新计算普通便签的数量。 + */ @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + /** + * 改变游标 - 更新数据集合 + * 更新适配器的游标,并重新计算普通便签的数量。 + * @param cursor 新的游标 + */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + /** + * 计算便签数量 - 统计普通便签的数量 + * 遍历所有项目,统计类型为普通便签的项目数量。 + */ private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java index 1221e80..c4eaf38 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java @@ -30,14 +30,32 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; +/** + * NotesListItem - 便签列表项自定义视图组件 + * 继承自LinearLayout,用于在便签列表中显示单个便签或文件夹项。 + * 负责根据不同类型的便签数据(普通便签、文件夹、通话记录便签)展示不同的UI样式和内容。 + * 支持多选模式下的复选框显示和状态管理。 + * + * 主要功能: + * - 根据便签数据类型(普通便签、文件夹、通话记录)显示不同的UI样式 + * - 支持多选模式下的复选框显示和状态控制 + * - 显示便签的标题、时间、提醒图标等信息 + * - 根据便签背景色ID设置适当的背景样式 + * - 管理通话记录便签的特殊显示需求 + */ public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; + private ImageView mAlert; // 提醒图标,显示便签的提醒状态 + private TextView mTitle; // 便签标题,显示便签的核心内容 + private TextView mTime; // 时间文本,显示便签的修改时间 + private TextView mCallName; // 通话记录名称,仅用于通话记录便签 + private NoteItemData mItemData; // 当前列表项绑定的便签数据 + private CheckBox mCheckBox; // 复选框,用于多选模式 + /** + * 构造方法 - 创建便签列表项视图 + * 初始化视图组件,从布局文件中加载UI元素。 + * @param context 上下文环境,用于加载布局和资源 + */ public NotesListItem(Context context) { super(context); inflate(context, R.layout.note_item, this); @@ -48,7 +66,17 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + /** + * 绑定便签数据到视图组件 + * 根据便签数据的类型和属性,配置视图组件的显示内容和样式。 + * 支持处理普通便签、文件夹和通话记录便签三种类型的数据。 + * @param context 上下文环境,用于获取字符串资源和样式 + * @param data 要绑定的便签数据对象 + * @param choiceMode 是否处于多选模式 + * @param checked 多选模式下的选中状态 + */ public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 配置多选模式下的复选框显示 if (choiceMode && data.getType() == Notes.TYPE_NOTE) { mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(checked); @@ -57,6 +85,8 @@ public class NotesListItem extends LinearLayout { } mItemData = data; + + // 处理通话记录文件夹的特殊显示 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); @@ -64,7 +94,9 @@ public class NotesListItem extends LinearLayout { mTitle.setText(context.getString(R.string.call_record_folder_name) + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setImageResource(R.drawable.call_record); - } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + } + // 处理通话记录便签的特殊显示 + else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); @@ -75,7 +107,9 @@ public class NotesListItem extends LinearLayout { } else { mAlert.setVisibility(View.GONE); } - } else { + } + // 处理普通便签和文件夹的显示 + else { mCallName.setVisibility(View.GONE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); @@ -94,13 +128,24 @@ public class NotesListItem extends LinearLayout { } } } + + // 设置便签的修改时间(相对时间格式) mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - + + // 设置背景样式 setBackground(data); } + /** + * 设置列表项的背景样式 + * 根据便签数据的类型、背景色ID和位置(首项、末项、单项等)选择合适的背景资源。 + * 普通便签和文件夹使用不同的背景资源,普通便签还会根据位置和数量选择不同的边角样式。 + * @param data 便签数据对象,包含背景色ID、类型和位置信息 + */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); + + // 为普通便签设置背景 if (data.getType() == Notes.TYPE_NOTE) { if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); @@ -111,11 +156,18 @@ public class NotesListItem extends LinearLayout { } else { setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } - } else { + } + // 为文件夹设置背景 + else { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /** + * 获取当前列表项绑定的便签数据 + * 返回当前视图组件所绑定的NoteItemData对象,用于获取便签的详细信息。 + * @return 当前列表项绑定的便签数据对象 + */ public NoteItemData getItemData() { return mItemData; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..45e58dd 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -48,27 +48,48 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; +/** + * NotesPreferenceActivity - 便签应用的设置界面 + * 继承自PreferenceActivity,用于管理应用的各种设置选项。 + * 主要提供Google账户同步设置和应用外观设置功能,支持账户添加、切换和删除操作。 + * 通过广播接收器监听同步服务状态更新,实时刷新UI。 + * + * 主要功能: + * - Google账户同步设置(添加、切换、删除账户) + * - 手动同步控制和同步状态显示 + * - 背景颜色随机显示设置 + * - 同步时间记录和显示 + */ public class NotesPreferenceActivity extends PreferenceActivity { + // 偏好设置文件名常量 public static final String PREFERENCE_NAME = "notes_preferences"; + // 同步账户名称偏好键 public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; + // 最后同步时间偏好键 public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; + // 背景颜色设置偏好键 public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; + // 同步账户设置分类键 private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; + // 账户权限过滤器键 private static final String AUTHORITIES_FILTER_KEY = "authorities"; - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; - + private PreferenceCategory mAccountCategory; // 账户设置分类 + private GTaskReceiver mReceiver; // Google任务同步广播接收器 + private Account[] mOriAccounts; // 原始账户列表 + private boolean mHasAddedAccount; // 是否已添加新账户标记 + + /** + * Activity生命周期方法:创建活动时调用 + * 初始化设置界面,加载偏好设置资源,注册同步服务广播接收器, + * 设置导航栏图标和添加自定义头部视图。 + * @param icicle 保存的实例状态 + */ @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -88,6 +109,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { getListView().addHeaderView(header, null, true); } + /** + * Activity生命周期方法:活动可见时调用 + * 检查是否添加了新账户,如果是则自动设置为同步账户。 + * 刷新UI界面以显示最新的同步状态和账户信息。 + */ @Override protected void onResume() { super.onResume(); @@ -116,6 +142,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { refreshUI(); } + /** + * Activity生命周期方法:活动销毁时调用 + * 取消注册同步服务广播接收器,释放资源。 + */ @Override protected void onDestroy() { if (mReceiver != null) { @@ -124,6 +154,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { super.onDestroy(); } + /** + * 加载账户偏好设置项 + * 移除现有的账户设置项,创建新的账户设置偏好项,并设置点击事件监听器。 + * 根据当前是否已设置同步账户,决定显示账户选择对话框还是账户更改确认对话框。 + */ private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -133,16 +168,17 @@ public class NotesPreferenceActivity extends PreferenceActivity { accountPref.setSummary(getString(R.string.preferences_account_summary)); accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { + // 检查是否正在同步 if (!GTaskSyncService.isSyncing()) { if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account + // 首次设置账户 showSelectAccountAlertDialog(); } else { - // if the account has already been set, we need to promp - // user about the risk + // 已设置账户,需要提示用户更改账户的风险 showChangeAccountConfirmAlertDialog(); } } else { + // 正在同步,无法更改账户 Toast.makeText(NotesPreferenceActivity.this, R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); @@ -154,12 +190,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { mAccountCategory.addPreference(accountPref); } + /** + * 加载同步按钮和同步状态显示 + * 根据当前同步状态配置同步按钮的文本和点击事件,设置按钮的可用性。 + * 显示最后一次同步时间或当前同步进度。 + */ private void loadSyncButton() { Button syncButton = (Button) findViewById(R.id.preference_sync_button); TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - // set button state + // 设置按钮状态 if (GTaskSyncService.isSyncing()) { + // 正在同步,显示取消按钮 syncButton.setText(getString(R.string.preferences_button_sync_cancel)); syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -167,6 +209,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); } else { + // 未同步,显示立即同步按钮 syncButton.setText(getString(R.string.preferences_button_sync_immediately)); syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -174,9 +217,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); } + // 根据是否已设置账户启用或禁用同步按钮 syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - // set last sync time + // 设置最后同步时间或同步进度 if (GTaskSyncService.isSyncing()) { lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setVisibility(View.VISIBLE); @@ -193,14 +237,24 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 刷新用户界面 + * 重新加载账户偏好设置和同步按钮状态,确保UI显示最新的设置信息和同步状态。 + */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + /** + * 显示账户选择对话框 + * 创建并显示一个自定义对话框,列出所有可用的Google账户供用户选择。 + * 支持添加新账户功能,保存当前账户列表用于后续检测新添加的账户。 + */ private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 设置对话框标题和提示信息 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); @@ -213,9 +267,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { Account[] accounts = getGoogleAccounts(); String defAccount = getSyncAccountName(this); + // 保存当前账户列表和状态 mOriAccounts = accounts; mHasAddedAccount = false; + // 如果有可用账户,显示单选列表 if (accounts.length > 0) { CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; @@ -230,6 +286,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { + // 设置选择的账户并刷新UI setSyncAccount(itemMapping[which].toString()); dialog.dismiss(); refreshUI(); @@ -237,10 +294,12 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 添加新账户视图 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); final AlertDialog dialog = dialogBuilder.show(); + // 设置添加账户点击事件 addAccountView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mHasAddedAccount = true; @@ -254,9 +313,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 显示账户更改确认对话框 + * 创建并显示一个确认对话框,提示用户更改或删除当前同步账户的风险。 + * 提供更改账户、删除账户和取消三个选项。 + */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 设置对话框标题和警告信息 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, @@ -265,6 +330,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); dialogBuilder.setCustomTitle(titleView); + // 设置对话框选项 CharSequence[] menuItemArray = new CharSequence[] { getString(R.string.preferences_menu_change_account), getString(R.string.preferences_menu_remove_account), @@ -273,21 +339,35 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { + // 更改账户 showSelectAccountAlertDialog(); } else if (which == 1) { + // 删除账户 removeSyncAccount(); refreshUI(); } + // which == 2 为取消操作,不做处理 } }); dialogBuilder.show(); } + /** + * 获取设备上所有的Google账户 + * 通过AccountManager获取设备上注册的所有Google类型账户。 + * @return Google账户数组 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + /** + * 设置同步账户 + * 更新偏好设置中的同步账户名称,清除上次同步时间, + * 并在后台线程中清除本地便签的Google任务相关信息。 + * @param account 要设置的Google账户名称 + */ private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -318,6 +398,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 移除同步账户 + * 从偏好设置中删除同步账户信息和最后同步时间, + * 并在后台线程中清除所有本地便签的Google任务相关信息。 + */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -340,12 +425,24 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + /** + * 获取同步账户名称 + * 从应用偏好设置中读取当前设置的Google同步账户名称。 + * @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,12 +451,22 @@ 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); } + /** + * Google任务同步广播接收器 + * 监听Google任务同步服务发出的广播,更新同步状态显示。 + */ private class GTaskReceiver extends BroadcastReceiver { @Override @@ -374,6 +481,12 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 处理选项菜单点击事件 + * 当前仅处理Home键点击事件,返回到便签列表主界面。 + * @param item 被点击的菜单项 + * @return 是否消费了该事件 + */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..c61fa01 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -32,19 +32,42 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * NoteWidgetProvider - 便签小部件抽象基类 + * 继承自AppWidgetProvider,为所有便签小部件提供通用功能。 + * 负责管理小部件的更新、删除等操作,以及获取和显示便签数据。 + * 子类需要实现特定的抽象方法来提供小部件的布局和背景资源。 + * + * 主要功能: + * - 管理小部件的生命周期事件(删除、更新) + * - 从数据库获取便签数据并显示在小部件上 + * - 处理小部件点击事件,启动相应的活动 + * - 支持隐私模式下的小部件显示 + * - 提供抽象方法供子类实现特定的小部件样式 + */ 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"; + private static final String TAG = "NoteWidgetProvider"; // 日志标签 + /** + * 小部件删除回调 - 处理小部件删除事件 + * 当用户删除小部件时,更新数据库中相关便签的小部件ID为无效值。 + * @param context 上下文环境 + * @param appWidgetIds 被删除的小部件ID数组 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { ContentValues values = new ContentValues(); @@ -57,6 +80,13 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 获取小部件关联的便签信息 + * 根据小部件ID从数据库中查询关联的便签数据,排除已删除的便签。 + * @param context 上下文环境 + * @param widgetId 小部件ID + * @return 包含便签信息的游标 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, @@ -65,10 +95,25 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { null); } + /** + * 更新小部件 - 默认隐私模式 + * 更新指定的小部件,不使用隐私模式。 + * @param context 上下文环境 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 更新小部件 - 核心实现 + * 更新指定的小部件,根据隐私模式决定显示内容和点击行为。 + * @param context 上下文环境 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + * @param privacyMode 是否处于隐私模式 + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { for (int i = 0; i < appWidgetIds.length; i++) { @@ -124,9 +169,25 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 获取背景资源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/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..c7e1d34 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -24,22 +24,50 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +/** + * NoteWidgetProvider_2x - 2x大小的便签小部件实现类 + * 继承自NoteWidgetProvider,实现2x大小的便签小部件功能。 + * 负责处理2x大小小部件的更新、布局和背景资源。 + */ public class NoteWidgetProvider_2x extends NoteWidgetProvider { + /** + * 小部件更新回调 - 更新2x大小的小部件 + * 重写父类的onUpdate方法,调用父类的update方法更新小部件。 + * @param context 上下文环境 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取布局ID - 返回2x小部件的布局 + * 实现父类的抽象方法,返回2x大小小部件的布局资源ID。 + * @return 2x小部件的布局资源ID + */ @Override protected int getLayoutId() { return R.layout.widget_2x; } + /** + * 获取背景资源ID - 返回2x小部件的背景 + * 实现父类的抽象方法,根据背景色ID返回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/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..3888214 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -24,21 +24,44 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +/** + * 4x大小的便签小部件实现类 + * 继承自NoteWidgetProvider,负责4x尺寸小部件的布局、背景和类型设置 + */ public class NoteWidgetProvider_4x extends NoteWidgetProvider { + /** + * 更新小部件时调用,实际调用父类的update方法执行更新逻辑 + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取4x小部件的布局资源ID + * @return 4x小部件布局资源ID + */ protected int getLayoutId() { return R.layout.widget_4x; } + /** + * 根据背景ID获取4x小部件的背景资源ID + * @param bgId 背景ID + * @return 4x小部件背景资源ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + /** + * 获取4x小部件的类型标识 + * @return 4x小部件类型标识Notes.TYPE_WIDGET_4X + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X;