From 3b39ffddeba6e05b4b6dad4f9c933a200841385d Mon Sep 17 00:00:00 2001
From: 13365576176 <392059170@qq.com>
Date: Thu, 18 Jan 2024 08:48:26 +0800
Subject: [PATCH] =?UTF-8?q?=E7=BB=B4=E6=8A=A41.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
MIUInote | 1 +
src/main/AndroidManifest.xml | 36 +++-
.../java/net/micode/notes/data/Contact.java | 8 +-
.../net/micode/notes/gtask/data/MetaData.java | 4 +-
.../net/micode/notes/gtask/data/Node.java | 9 +-
.../net/micode/notes/gtask/data/SqlData.java | 30 ++--
.../net/micode/notes/gtask/data/SqlNote.java | 60 ++++---
.../net/micode/notes/gtask/data/Task.java | 21 ++-
.../net/micode/notes/gtask/data/TaskList.java | 7 +
.../notes/gtask/remote/GTaskASyncTask.java | 20 ++-
.../notes/gtask/remote/GTaskClient.java | 40 ++++-
.../notes/gtask/remote/GTaskManager.java | 24 ++-
.../notes/gtask/remote/GTaskSyncService.java | 9 +-
.../java/net/micode/notes/model/Note.java | 14 +-
.../net/micode/notes/model/WorkingNote.java | 18 +-
.../net/micode/notes/tool/ResourceParser.java | 21 ++-
.../notes/ui/ChangeLoginPasswordActivity.java | 127 ++++++++++++++
.../net/micode/notes/ui/LoginActivity.java | 163 +++++++++++++++++
.../java/net/micode/notes/ui/LoginView.java | 76 ++++++++
.../net/micode/notes/ui/NoteEditActivity.java | 30 +++-
.../micode/notes/ui/NotesListActivity.java | 164 ++++++++++++------
.../res/drawable-hdpi/edit_title_red.9.png | Bin 5061 -> 1979 bytes
.../drawable-hdpi/font_size_selector_bg.9.png | Bin 4101 -> 1852 bytes
src/main/res/drawable-hdpi/login_photo.png | Bin 0 -> 1389 bytes
.../res/drawable-hdpi/login_view_photo.png | Bin 0 -> 166902 bytes
.../res/drawable-hdpi/login_view_photo_2.jpg | Bin 0 -> 19712 bytes
.../note_background_photo_duck.jpg | Bin 0 -> 807974 bytes
.../note_background_photo_wangyi.png | Bin 0 -> 413747 bytes
.../drawable-hdpi/note_bg_photo_wangyi.png | Bin 0 -> 12065 bytes
.../note_edit_color_selector_panel_test.png | Bin 0 -> 59611 bytes
.../layout/change_login_password_activity.xml | 72 ++++++++
src/main/res/layout/login_activity.xml | 81 +++++++++
src/main/res/layout/login_view.xml | 23 +++
src/main/res/layout/note_edit.xml | 22 ++-
src/main/res/values-zh-rCN/strings.xml | 1 +
src/main/res/values-zh-rTW/strings.xml | 1 +
src/main/res/values/strings.xml | 11 ++
src/main/res/values/styles.xml | 4 +-
38 files changed, 956 insertions(+), 141 deletions(-)
create mode 160000 MIUInote
create mode 100644 src/main/java/net/micode/notes/ui/ChangeLoginPasswordActivity.java
create mode 100644 src/main/java/net/micode/notes/ui/LoginActivity.java
create mode 100644 src/main/java/net/micode/notes/ui/LoginView.java
create mode 100644 src/main/res/drawable-hdpi/login_photo.png
create mode 100644 src/main/res/drawable-hdpi/login_view_photo.png
create mode 100644 src/main/res/drawable-hdpi/login_view_photo_2.jpg
create mode 100644 src/main/res/drawable-hdpi/note_background_photo_duck.jpg
create mode 100644 src/main/res/drawable-hdpi/note_background_photo_wangyi.png
create mode 100644 src/main/res/drawable-hdpi/note_bg_photo_wangyi.png
create mode 100644 src/main/res/drawable-hdpi/note_edit_color_selector_panel_test.png
create mode 100644 src/main/res/layout/change_login_password_activity.xml
create mode 100644 src/main/res/layout/login_activity.xml
create mode 100644 src/main/res/layout/login_view.xml
diff --git a/MIUInote b/MIUInote
new file mode 160000
index 0000000..4f9b537
--- /dev/null
+++ b/MIUInote
@@ -0,0 +1 @@
+Subproject commit 4f9b53791b00b089343a4919af9ad7ac765b751f
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index e5c7d47..9594881 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -20,7 +20,7 @@
android:versionCode="1"
android:versionName="0.1" >
-
+
@@ -36,12 +36,12 @@
android:icon="@drawable/icon_app"
android:label="@string/app_name" >
@@ -50,6 +50,36 @@
+
+
+
+
+
+
+
+
+
sContactCache;
+ private static HashMap sContactCache;//联系人缓存
private static final String TAG = "Contact";
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
@@ -34,7 +34,7 @@ public class Contact {
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
- + " WHERE min_match = '+')";
+ + " WHERE min_match = '+')";//规定SQL查询语句
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
@@ -52,8 +52,8 @@ public class Contact {
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
- null);
-
+ null);//进行缓存查询。如果缓存没有,填充SQL查询语言
+//进行查询并返回结果
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0);
diff --git a/src/main/java/net/micode/notes/gtask/data/MetaData.java b/src/main/java/net/micode/notes/gtask/data/MetaData.java
index 3a2050b..40cf000 100644
--- a/src/main/java/net/micode/notes/gtask/data/MetaData.java
+++ b/src/main/java/net/micode/notes/gtask/data/MetaData.java
@@ -32,7 +32,7 @@ public class MetaData extends Task {
public void setMeta(String gid, JSONObject metaInfo) {
try {
- metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
+ metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);//将gid放进去
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
@@ -55,7 +55,7 @@ public class MetaData extends Task {
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
- mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
+ mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);//从metaInfo中获取该字段的值。metaInfo应该是一个类似于数据集合的东西
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
diff --git a/src/main/java/net/micode/notes/gtask/data/Node.java b/src/main/java/net/micode/notes/gtask/data/Node.java
index 63950e0..86e2db5 100644
--- a/src/main/java/net/micode/notes/gtask/data/Node.java
+++ b/src/main/java/net/micode/notes/gtask/data/Node.java
@@ -19,8 +19,9 @@ package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
-
+//本地及云端数据更新、同步
public abstract class Node {
+ //下述常量用于表示对本地及云端数据的操作号
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
@@ -43,7 +44,7 @@ public abstract class Node {
private String mName;
- private long mLastModified;
+ private long mLastModified;//最后一次修改时间
private boolean mDeleted;
@@ -54,7 +55,7 @@ public abstract class Node {
mDeleted = false;
}
- public abstract JSONObject getCreateAction(int actionId);
+ public abstract JSONObject getCreateAction(int actionId);//actionId 可能是用于标识不同类型的创建操作的参数
public abstract JSONObject getUpdateAction(int actionId);
@@ -64,7 +65,7 @@ public abstract class Node {
public abstract JSONObject getLocalJSONFromContent();
- public abstract int getSyncAction(Cursor c);
+ public abstract int getSyncAction(Cursor c);//获取同步操作
public void setGid(String gid) {
this.mGid = gid;
diff --git a/src/main/java/net/micode/notes/gtask/data/SqlData.java b/src/main/java/net/micode/notes/gtask/data/SqlData.java
index d3ec3be..de68d0b 100644
--- a/src/main/java/net/micode/notes/gtask/data/SqlData.java
+++ b/src/main/java/net/micode/notes/gtask/data/SqlData.java
@@ -34,11 +34,11 @@ import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
-
+//小米便签底层数据库相关操作
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName();
- private static final int INVALID_ID = -99999;
+ private static final int INVALID_ID = -99999;//无效ID
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
@@ -57,7 +57,7 @@ public class SqlData {
private ContentResolver mContentResolver;
- private boolean mIsCreate;
+ private boolean mIsCreate;//用于表示sqldata初始化的两种方式
private long mDataId;
@@ -71,6 +71,7 @@ public class SqlData {
private ContentValues mDiffDataValues;
+ //第一种初始化形式
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
@@ -96,7 +97,7 @@ public class SqlData {
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
-
+ //设置共享数据
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
@@ -125,7 +126,7 @@ public class SqlData {
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
- mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
+ mDiffDataValues.put(DataColumns.DATA3, dataContentData3);//键值对
}
mDataContentData3 = dataContentData3;
}
@@ -143,29 +144,34 @@ public class SqlData {
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
-
+//将当前所做修改保存至数据库
+ //下面创建及更新两种不同操作有由IsCreate控制,也对应了sqlData初始化的两种方式:创建新的,从cursor更新
+ //创建新数据笔记并将其同步至数据库
+ //感觉在这里,mDiffdataValues代表需要新写的键值对(记录已发生更改的属性?),通常与数据库交互,保存数据变化,就是用户新编辑的。
+ // ContentResolver 是用于访问应用程序的数据存储区域的关键类之一。与应用程序的数据提供者(如数据库、内容提供器等)进行交互
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
- }
+ }//如果ID无效以及mDiffdataValues包含这个ID键值对,移除这个键值对
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
- Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
+ Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);//把mDiffdataValues插到这个URI的位置
try {
- mDataId = Long.valueOf(uri.getPathSegments().get(1));
+ mDataId = Long.valueOf(uri.getPathSegments().get(1));//再把这个uri对应的数据的ID取出来。如果提取失败说明创建失败
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
- if (mDiffDataValues.size() > 0) {
+ //笔记更新
+ if (mDiffDataValues.size() > 0) { //说明有需要更新的
int result = 0;
- if (!validateVersion) {
+ if (!validateVersion) { //如果版本不需要更新,则直接对指定URI进行更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
- } else {
+ } else { //版本与指定内容一起更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
diff --git a/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/src/main/java/net/micode/notes/gtask/data/SqlNote.java
index 79a4095..a34949e 100644
--- a/src/main/java/net/micode/notes/gtask/data/SqlNote.java
+++ b/src/main/java/net/micode/notes/gtask/data/SqlNote.java
@@ -37,12 +37,13 @@ import org.json.JSONObject;
import java.util.ArrayList;
-
+//底层数据库操作,是SqlData的父集。相对而言是真正意义上的数据
public class SqlNote {
private static final String TAG = SqlNote.class.getSimpleName();
private static final int INVALID_ID = -99999;
+ //PROJECTION_NOTE 里面包括了许多列的名字,在后面数据库查询中用到
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
@@ -51,7 +52,7 @@ public class SqlNote {
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
-
+ //下面是17个列的编号
public static final int ID_COLUMN = 0;
public static final int ALERTED_DATE_COLUMN = 1;
@@ -120,18 +121,19 @@ public class SqlNote {
private ContentValues mDiffNoteValues;
- private ArrayList mDataList;
+ private ArrayList mDataList;//ArrayList是动态数组,用于储存类型为SqlData的数据。
+ //SqlNote一种构造方式。这里为新建一个
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true;
mId = INVALID_ID;
mAlertDate = 0;
- mBgColorId = ResourceParser.getDefaultBgId(context);
- mCreatedDate = System.currentTimeMillis();
+ mBgColorId = ResourceParser.getDefaultBgId(context);//获取颜色、图片等元素ID用以渲染界面
+ mCreatedDate = System.currentTimeMillis();//当前系统时间
mHasAttachment = 0;
- mModifiedDate = System.currentTimeMillis();
+ mModifiedDate = System.currentTimeMillis();//最后一次数据更新时间
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
@@ -142,7 +144,7 @@ public class SqlNote {
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList();
}
-
+ //初始化方式。通过cursor c初始化
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
@@ -150,31 +152,34 @@ public class SqlNote {
loadFromCursor(c);
mDataList = new ArrayList();
if (mType == Notes.TYPE_NOTE)
- loadDataContent();
+ loadDataContent();//数据加载等操作
mDiffNoteValues = new ContentValues();
}
-
+ //第三种初始化方式,只不过这次也不是新建的。通过id初始化
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
+ //ContentResolver 是用于访问应用程序的数据存储区域的关键类之一。与应用程序的数据提供者(如数据库、内容提供器等)进行交互
mIsCreate = false;
- loadFromCursor(id);
+ loadFromCursor(id);//将cursor数据下载到SqlNote
mDataList = new ArrayList();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
- mDiffNoteValues = new ContentValues();
+ mDiffNoteValues = new ContentValues();//创建一个空的 ContentValues 对象,用于存储键值对数据,通常用于与数据库进行交互或保存数据的变化。
}
-
+ //从数据库中查询笔记数据,将结果加载到对象中(cursor c)
private void loadFromCursor(long id) {
Cursor c = null;
try {
+ //query:查询函数。
+ //下面函数先匹配给的id与需要查询的id是否相同,然后查询PROJECTION_NOTE里列的数据,将其赋给c
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
- c.moveToNext();
+ c.moveToNext();//移动游标读取数据
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
@@ -199,7 +204,7 @@ public class SqlNote {
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
-
+ //查询笔记数据内容
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
@@ -228,6 +233,7 @@ public class SqlNote {
public boolean setContent(JSONObject js) {
try {
+ //根据不同的便签类型操作。
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
@@ -253,7 +259,9 @@ public class SqlNote {
mDiffNoteValues.put(NoteColumns.ID, id);
}
mId = id;
-
+ //下面的操作是根据获得的新数据更新一些变量值。
+ // 其中m开的头的变量(eg mBgColorId 表示当前的属性值)下面的代码就是根据新数据来更新这些值
+ //更新笔记提醒时间属性
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
@@ -262,7 +270,7 @@ public class SqlNote {
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
- .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
+ .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);//后面这个指获取默认的背景信息
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
@@ -330,12 +338,13 @@ public class SqlNote {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
-
+ //从mDataList中寻找与dataArray中ID相同的元素,将其放入sqldata中
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
+ //这个for循环为增强型,即每次遍历mDataList中的一个数组,并将这个数组赋给temp
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
@@ -358,7 +367,7 @@ public class SqlNote {
}
return true;
}
-
+ //将笔记的属性及数据以json形式返回,以便后续使用
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
@@ -367,7 +376,7 @@ public class SqlNote {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
-
+ //下面是获得单个(或多个)便签对象的数据
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
note.put(NoteColumns.ID, mId);
@@ -392,7 +401,7 @@ public class SqlNote {
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
- } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
+ } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { //这是获得文件夹的数据
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
@@ -440,7 +449,9 @@ public class SqlNote {
return mType == Notes.TYPE_NOTE;
}
+ //数据提交或更新(将当前更改保存到数据库)
public void commit(boolean validateVersion) {
+ //创建新笔记
if (mIsCreate) {
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
@@ -462,7 +473,7 @@ public class SqlNote {
sqlData.commit(mId, false, -1);
}
}
- } else {
+ } else { //更新现有笔记
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
@@ -486,7 +497,7 @@ public class SqlNote {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
-
+ //提交关联数据
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
@@ -495,9 +506,10 @@ public class SqlNote {
}
// refresh local info
- loadFromCursor(mId);
+ //刷新本地信息
+ loadFromCursor(mId);//加载数据
if (mType == Notes.TYPE_NOTE)
- loadDataContent();
+ loadDataContent();//加载数据内容
mDiffNoteValues.clear();
mIsCreate = false;
diff --git a/src/main/java/net/micode/notes/gtask/data/Task.java b/src/main/java/net/micode/notes/gtask/data/Task.java
index 6a19454..7be7323 100644
--- a/src/main/java/net/micode/notes/gtask/data/Task.java
+++ b/src/main/java/net/micode/notes/gtask/data/Task.java
@@ -49,8 +49,8 @@ public class Task extends Node {
super();
mCompleted = false;
mNotes = null;
- mPriorSibling = null;
- mParent = null;
+ mPriorSibling = null;//TaskList中当前task的前一个task
+ mParent = null;//当前task所在点List
mMetaInfo = null;
}
@@ -66,8 +66,8 @@ public class Task extends Node {
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
- js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
-
+ js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));//当前task在其父级task的索引
+ //构建一个包含任务的名称、创建者ID和实体类型信息的 JSON 对象(entity)
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
@@ -123,7 +123,7 @@ public class Task extends Node {
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
- entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
+ entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());//实体的删除状态
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
@@ -135,6 +135,7 @@ public class Task extends Node {
return js;
}
+ //remote指云端(数据库),反正不是本地
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
@@ -192,6 +193,7 @@ public class Task extends Node {
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
+ //如果这个data的mime_type与NOTE常量相同,则将this所指代的对象的名字设为内容(CONTENT)
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));
break;
@@ -225,7 +227,7 @@ public class Task extends Node {
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
- // synced task
+ // synced task 同步任务
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
@@ -247,17 +249,18 @@ public class Task extends Node {
}
}
+ //将metaData对象中的笔记信息(假设是一个JSON字符串)解析为一个JSON对象,并将其赋值给mMetaInfo
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
- mMetaInfo = new JSONObject(metaData.getNotes());
+ mMetaInfo = new JSONObject(metaData.getNotes());//用getNodes()中的信息初始化mMetaInfo
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
-
+ //数据库与本地数据同步
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
@@ -312,7 +315,7 @@ public class Task extends Node {
}
public boolean isWorthSaving() {
- return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
+ return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)//trim()作用为去掉空格
|| (getNotes() != null && getNotes().trim().length() > 0);
}
diff --git a/src/main/java/net/micode/notes/gtask/data/TaskList.java b/src/main/java/net/micode/notes/gtask/data/TaskList.java
index 4ea21c5..a35a694 100644
--- a/src/main/java/net/micode/notes/gtask/data/TaskList.java
+++ b/src/main/java/net/micode/notes/gtask/data/TaskList.java
@@ -216,6 +216,8 @@ public class TaskList extends Node {
return SYNC_ACTION_ERROR;
}
+
+ //任务队列大小
public int getChildTaskCount() {
return mChildren.size();
}
@@ -226,6 +228,7 @@ public class TaskList extends Node {
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
+ //如果子任务链表为空,则前一个兄弟任务为null,如果不为空,则将前一个子任务设置为当前任务末端。
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
@@ -234,6 +237,7 @@ public class TaskList extends Node {
return ret;
}
+ //在某个位置插入一个子任务
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
@@ -281,6 +285,8 @@ public class TaskList extends Node {
return ret;
}
+
+ //移动某子任务到index位置
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
@@ -321,6 +327,7 @@ public class TaskList extends Node {
return mChildren.get(index);
}
+ //和308行函数的区别在于,308行新建一个Task返回,下面的直接把原本的Task返回
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
diff --git a/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java
index b3b61e7..cb9f3f3 100644
--- a/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java
+++ b/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java
@@ -57,6 +57,7 @@ public class GTaskASyncTask extends AsyncTask {
mTaskManager.cancelSync();
}
+ //通知异步任务更新进度信息。
public void publishProgess(String message) {
publishProgress(new String[] {
message
@@ -64,10 +65,15 @@ public class GTaskASyncTask extends AsyncTask {
}
private void showNotification(int tickerId, String content) {
+ //创建一个Notification对象,并设置其图标、文本和时间戳等属性。其中,R.drawable.notification表示通知的图标,
+ // mContext.getString(tickerId)表示通知的文本,System.currentTimeMillis()表示通知的时间戳。
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
+ //将通知设置为默认显示灯光效果。
notification.defaults = Notification.DEFAULT_LIGHTS;
+ //将通知设置为默认显示灯光效果。
notification.flags = Notification.FLAG_AUTO_CANCEL;
+
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
@@ -77,17 +83,21 @@ public class GTaskASyncTask extends AsyncTask {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
}
- notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
- pendingIntent);
+ //设置通知的最新事件信息,包括应用程序名称、通知内容和要启动的pendingIntent。
+ //notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
+ // pendingIntent);
+ //显示通知,并指定通知的标识符为GTASK_SYNC_NOTIFICATION_ID。
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
+ //更新进度信息,并执行后台的同步任务
@Override
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
}
+ //后台任务执行过程中更新进度信息,并通过通知和广播将进度信息传递给其他组件
@Override
protected void onProgressUpdate(String... progress) {
@@ -96,7 +106,7 @@ public class GTaskASyncTask extends AsyncTask {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
-
+ //异步任务执行完成后,根据任务执行的结果执行不同的操作,并通过通知和回调将结果传递给其他组件.
@Override
protected void onPostExecute(Integer result) {
if (result == GTaskManager.STATE_SUCCESS) {
@@ -111,13 +121,15 @@ public class GTaskASyncTask extends AsyncTask {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
+ //新开线程回调监听器
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
+ //线程创建后执行以下操作
mOnCompleteListener.onComplete();
}
- }).start();
+ }).start();//启动线程
}
}
}
diff --git a/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java
index c67dfdf..c2ae647 100644
--- a/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java
+++ b/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java
@@ -110,9 +110,9 @@ public class GTaskClient {
}
public boolean login(Activity activity) {
- // we suppose that the cookie would expire after 5 minutes
+ // we suppose that the cookie would expire(失效) after 5 minutes
// then we need to re-login
- final long interval = 1000 * 60 * 5;
+ final long interval = 1000 * 60 * 5;//5min的ms
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
@@ -130,13 +130,14 @@ public class GTaskClient {
}
mLastLoginTime = System.currentTimeMillis();
- String authToken = loginGoogleAccount(activity, false);
+ String authToken = loginGoogleAccount(activity, false);//谷歌账号登陆
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
- // login with custom domain if necessary
+ // login with custom domain if necessary 反正也是管登陆的.
+ //toLowerCase 字符串转成小写
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
@@ -176,6 +177,7 @@ public class GTaskClient {
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
+ //遍历所有账户列表,看有没有和所给的一样的
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
@@ -189,7 +191,12 @@ public class GTaskClient {
return null;
}
- // get the token now
+ //下面是获得目标账户认证令牌。
+ //调用accountManager.getAuthToken函数异步获取认证令牌,返回的是一个AccountManagerFuture对象。
+ // 然后,调用getResult方法尝试获取包含认证令牌的Bundle对象。如果成功获得该Bundle,就从中提取出认证令牌并赋值给authToken。
+ // 如果invalidateToken标志为true,那么就使当前的认证令牌失效,并且再次调用loginGoogleAccount方法获取新的认证令牌。
+ // 如果在这个过程中捕获到任何异常,就打印错误日志并将authToken设置为null。
+ // get the token now token有认证的意思
AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
@@ -207,6 +214,7 @@ public class GTaskClient {
return authToken;
}
+ //如果刚开始没登录成功GTask,那么我再取一次授权令牌,再试一次,如果还没成功,则登陆gtask失败
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
@@ -225,10 +233,13 @@ public class GTaskClient {
return true;
}
+
+ //使用给出的authToken进行登录操作,并获取客户端版本信息
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
- HttpParams httpParameters = new BasicHttpParams();
+ //下面是鼓捣http连接的。HttpClient是Apache HttpClient库提供的一个类,用于设立HTTP连接,发送HTTP请求并处理HTTP响应。
+ HttpParams httpParameters = new BasicHttpParams();//param参数
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
@@ -239,7 +250,7 @@ public class GTaskClient {
// login gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
- HttpGet httpGet = new HttpGet(loginUrl);
+ HttpGet httpGet = new HttpGet(loginUrl);//用于发送 HTTP GET 请求到服务器并获取响应
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
@@ -255,7 +266,7 @@ public class GTaskClient {
Log.w(TAG, "it seems that there is no auth cookie");
}
- // get the client version
+ // get the client version 这在切割js代码,把字符串提出来
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}";
@@ -284,6 +295,7 @@ public class GTaskClient {
return mActionId++;
}
+ //请求头信息
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
@@ -291,21 +303,24 @@ public class GTaskClient {
return httpPost;
}
+ //获取HTTP响应的内容(内容编码方式—)
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
+ //根据响应的内容编码方式对输入流进行处理
InputStream input = entity.getContent();
- if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
+ if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {//是否经过gzip文件压缩
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
+ //将输入流转换为字符串
try {
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
@@ -323,6 +338,7 @@ public class GTaskClient {
}
}
+ //服务器发送JSON格式的数据,并返回服务器响应的JSON格式数据
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
@@ -331,6 +347,7 @@ public class GTaskClient {
HttpPost httpPost = createHttpPost();
try {
+ //将json转为字符串往列表里放
LinkedList list = new LinkedList();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
@@ -360,6 +377,8 @@ public class GTaskClient {
}
}
+ //创建新任务的方法,它构建了一个包含任务操作和客户端版本号的JSON对象,并将其作为POST请求发送给服务器。
+ // 然后,从服务器响应中解析出新创建任务的ID,并将其设置给task对象
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
@@ -412,6 +431,8 @@ public class GTaskClient {
}
}
+ //提交任务更新的方法
+ //mUpdateArray是否为null。如果是,表示没有需要提交的更新,直接退出方法
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
@@ -447,6 +468,7 @@ public class GTaskClient {
}
}
+ //移动任务
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
diff --git a/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java
index d2b4082..4391995 100644
--- a/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java
+++ b/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java
@@ -168,6 +168,8 @@ public class GTaskManager {
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
+ //init初始化。先初始化metalist,再 搞tasklist,在搞具体的task
+
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
@@ -247,6 +249,8 @@ public class GTaskManager {
}
}
+ //从本地的备忘录应用中查询未被删除的备忘录条目,并与Google Tasks服务同步。
+
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
@@ -288,6 +292,7 @@ public class GTaskManager {
// sync folder first
syncFolder();
+ //从本地的备忘录应用中查询已存在的备忘录条目,并与Google Tasks服务同步
// for note existing in database
try {
@@ -326,7 +331,7 @@ public class GTaskManager {
}
}
- // go through remaining items
+ // go through remaining items 同步在Google Tasks服务中存在、但在本地备忘录应用中不存在的备忘录条目
Iterator> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = iter.next();
@@ -336,7 +341,9 @@ public class GTaskManager {
// mCancelled can be set by another thread, so we neet to check one by
// one
- // clear local delete table
+ // clear local delete table在同步完成后,对于本地备忘录应用中已删除的备忘录条目,进行批量删除操作,
+ // 并刷新本地备忘录应用与Google Tasks服务之间的同步ID。
+ // batch 批量
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
@@ -362,6 +369,7 @@ public class GTaskManager {
}
// for root folder
+ //Google Tasks服务中的备忘录条目是否需要更新名称,并决定调用何种类型的同步操作
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
@@ -374,6 +382,7 @@ public class GTaskManager {
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary
+ //下面是Googletask服务备忘录与本地 的是否有?或不存在。有就是更新,没有就是增加
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
@@ -391,6 +400,8 @@ public class GTaskManager {
}
// for call-note folder
+ //查询特定系统文件夹(ID为Notes.ID_CALL_RECORD_FOLDER,表示通话记录文件夹)对应的备忘录条目,
+ // 并根据查询结果执行相应的同步操作
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
@@ -424,7 +435,8 @@ public class GTaskManager {
}
}
- // for local existing folders
+ // for local existing folders 查询本地备忘录应用中所有非"垃圾箱"文件夹类型的备忘录条目,
+ // 并根据查询结果执行相应的同步操作
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
@@ -440,6 +452,8 @@ public class GTaskManager {
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
+ //当node为null且GTASK_ID列为空时,说明该备忘录条目是本地新增的,需要将其添加到远程服务器进行同步。
+ //当node为null且GTASK_ID列不为空时,说明该备忘录条目是远程删除的,需要将其从本地进行同步删除。
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
@@ -645,6 +659,7 @@ public class GTaskManager {
// add meta
updateRemoteMeta(task.getGid(), sqlNote);
} else {
+ //查找是否已经存在一个与给定的文件夹名称匹配的远程任务列表
TaskList tasklist = null;
// we need to skip folder if it has already existed
@@ -670,6 +685,7 @@ public class GTaskManager {
break;
}
}
+ //所以,上面的操作是找到了匹配的远程任务列表,下面是没找到,我们创个新的
// no match we can add now
if (tasklist == null) {
@@ -746,6 +762,8 @@ public class GTaskManager {
}
}
+ //确保本地笔记和GTask上的笔记同步,并更新本地笔记的同步ID
+
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
diff --git a/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java
index cca36f7..b9ffa42 100644
--- a/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java
+++ b/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java
@@ -69,8 +69,10 @@ public class GTaskSyncService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- Bundle bundle = intent.getExtras();
+ Bundle bundle = intent.getExtras();//获取附加数据
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
+ //从Bundle中获取与指定键对应的整数值(ACTION_STRING_NAME)。如果指定键存在且对应的值为整数类型,则返回该整数值;
+ // 否则,返回默认的整数值ACTION_INVALID。
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync();
@@ -87,6 +89,7 @@ public class GTaskSyncService extends Service {
}
@Override
+ //内存不足时取消同步任务
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
@@ -105,13 +108,17 @@ public class GTaskSyncService extends Service {
sendBroadcast(intent);
}
+ //在指定的Activity中开启同步任务,通过调用GTaskSyncService服务来执行同步操作。
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
+ //增加键值对
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
+
+ //取消同步操作
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
diff --git a/src/main/java/net/micode/notes/model/Note.java b/src/main/java/net/micode/notes/model/Note.java
index 6706cf6..e094779 100644
--- a/src/main/java/net/micode/notes/model/Note.java
+++ b/src/main/java/net/micode/notes/model/Note.java
@@ -41,6 +41,8 @@ public class Note {
/**
* Create a new note id for adding a new note to databases
*/
+
+ //在数据库中创建一条新的笔记记录,并返回新创建的笔记的ID
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
ContentValues values = new ContentValues();
@@ -49,11 +51,14 @@ public class Note {
values.put(NoteColumns.MODIFIED_DATE, createdTime);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ //这个新笔记的父节点就是文件夹ID。
values.put(NoteColumns.PARENT_ID, folderId);
+ //将values对象插入到数据库中,并返回插入的记录的URI
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
+ //解析返回的uri得到新笔记ID值
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
@@ -114,6 +119,7 @@ public class Note {
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
+ //估计是看update的过程有没有出现错误。如果有就报错了
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
@@ -178,6 +184,7 @@ public class Note {
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
+ //该方法用于将笔记数据推送到内容解析器(Content Resolver)中。
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
@@ -186,11 +193,12 @@ public class Note {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
- ArrayList operationList = new ArrayList();
+ ArrayList operationList = new ArrayList();//操作列表
ContentProviderOperation.Builder builder = null;
-
+ //是否有文本数据需要更新
if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
+ //mTextDataId=0表示需要新插入文本数据
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
@@ -205,6 +213,7 @@ public class Note {
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
+ //设置需要更新的字段及值
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
@@ -235,6 +244,7 @@ public class Note {
if (operationList.size() > 0) {
try {
+ //将操作列表operationList应用到内容解析器
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
diff --git a/src/main/java/net/micode/notes/model/WorkingNote.java b/src/main/java/net/micode/notes/model/WorkingNote.java
index be081e4..0f4dc8a 100644
--- a/src/main/java/net/micode/notes/model/WorkingNote.java
+++ b/src/main/java/net/micode/notes/model/WorkingNote.java
@@ -101,6 +101,9 @@ public class WorkingNote {
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
+
+
+ //创建新的笔记
// New note construct
private WorkingNote(Context context, long folderId) {
mContext = context;
@@ -114,6 +117,7 @@ public class WorkingNote {
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
+ //构建已存在的笔记
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
@@ -124,6 +128,8 @@ public class WorkingNote {
loadNote();
}
+ //加载笔记数据
+
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
@@ -146,6 +152,7 @@ public class WorkingNote {
loadNoteData();
}
+ //加载笔记具体数据
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
@@ -174,6 +181,7 @@ public class WorkingNote {
}
}
+ //创建新的空白笔记
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
@@ -183,6 +191,8 @@ public class WorkingNote {
return note;
}
+
+ //加载工作笔记
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
@@ -228,7 +238,7 @@ public class WorkingNote {
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
-
+ //设置提醒日期
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
@@ -288,6 +298,9 @@ public class WorkingNote {
}
}
+ //将一条电话通话记录转换为一个笔记
+ //电话通话记录转换为笔记的功能,将电话号码、通话日期和父文件夹ID存储到笔记对象中,
+ // 以便后续可以在应用程序中显示和管理这些通话记录笔记
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
@@ -310,6 +323,7 @@ public class WorkingNote {
return mModifiedDate;
}
+ //res事件
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
@@ -341,7 +355,7 @@ public class WorkingNote {
public int getWidgetType() {
return mWidgetType;
}
-
+ //下面这个类是一个侦听器,用于检测用户是否发生改变背景颜色、设置时钟等操作
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
diff --git a/src/main/java/net/micode/notes/tool/ResourceParser.java b/src/main/java/net/micode/notes/tool/ResourceParser.java
index 1ad3ad6..6f8b4b0 100644
--- a/src/main/java/net/micode/notes/tool/ResourceParser.java
+++ b/src/main/java/net/micode/notes/tool/ResourceParser.java
@@ -30,6 +30,8 @@ public class ResourceParser {
public static final int GREEN = 3;
public static final int RED = 4;
+ public static final int DUCK = 5;
+
public static final int BG_DEFAULT_COLOR = YELLOW;
public static final int TEXT_SMALL = 0;
@@ -45,7 +47,10 @@ public class ResourceParser {
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
- R.drawable.edit_red
+ R.drawable.edit_red,
+ R.drawable.note_bg_photo_wangyi
+
+
};
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
@@ -53,7 +58,9 @@ public class ResourceParser {
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
- R.drawable.edit_title_red
+ R.drawable.edit_title_red,
+ R.drawable.note_bg_photo_wangyi
+
};
public static int getNoteBgResource(int id) {
@@ -80,7 +87,8 @@ public class ResourceParser {
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
- R.drawable.list_red_up
+ R.drawable.list_red_up,
+ R.drawable.note_bg_photo_wangyi
};
private final static int [] BG_NORMAL_RESOURCES = new int [] {
@@ -88,7 +96,8 @@ public class ResourceParser {
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
- R.drawable.list_red_middle
+ R.drawable.list_red_middle,
+ R.drawable.note_bg_photo_wangyi
};
private final static int [] BG_LAST_RESOURCES = new int [] {
@@ -97,6 +106,7 @@ public class ResourceParser {
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
+ R.drawable.note_bg_photo_wangyi
};
private final static int [] BG_SINGLE_RESOURCES = new int [] {
@@ -104,7 +114,8 @@ public class ResourceParser {
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
- R.drawable.list_red_single
+ R.drawable.list_red_single,
+ R.drawable.note_bg_photo_wangyi
};
public static int getNoteBgFirstRes(int id) {
diff --git a/src/main/java/net/micode/notes/ui/ChangeLoginPasswordActivity.java b/src/main/java/net/micode/notes/ui/ChangeLoginPasswordActivity.java
new file mode 100644
index 0000000..6925781
--- /dev/null
+++ b/src/main/java/net/micode/notes/ui/ChangeLoginPasswordActivity.java
@@ -0,0 +1,127 @@
+package net.micode.notes.ui;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.style.BackgroundColorSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.TextNote;
+import net.micode.notes.model.WorkingNote;
+import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
+import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.ResourceParser;
+import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
+import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
+import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
+import net.micode.notes.widget.NoteWidgetProvider_2x;
+import net.micode.notes.widget.NoteWidgetProvider_4x;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+public class ChangeLoginPasswordActivity extends Activity {
+ EditText OldPassword;
+ EditText NewPassword;
+ EditText ReWritePassword;
+ Button Acknowledged;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.change_login_password_activity);
+ getWindow().setSoftInputMode(
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+ | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+ OldPassword=(EditText) findViewById(R.id.old_password);
+ NewPassword=(EditText) findViewById(R.id.new_password);
+ ReWritePassword=(EditText) findViewById(R.id.ack_password);
+ Acknowledged=(Button)findViewById(R.id.Bt_Acknowledged);
+ Acknowledged.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String old_password = OldPassword.getText().toString();
+ String new_password = NewPassword.getText().toString();
+ String ack_password = ReWritePassword.getText().toString();
+
+ SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE);
+
+ String login_password=pref.getString("password","");
+
+ if(old_password.equals("")==true || new_password.equals("")==true || ack_password.equals("")==true) {
+ Toast.makeText(ChangeLoginPasswordActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show();
+ }else if (new_password.equals(ack_password) == false) {
+ Toast.makeText(ChangeLoginPasswordActivity.this, "新建密码与重复密码不匹配,请重新输入密码", Toast.LENGTH_SHORT).show();
+ ReWritePassword.setText("");
+ }else if(old_password.equals(login_password) == false){
+ Toast.makeText(ChangeLoginPasswordActivity.this, "原有密码错误,请重新输入密码", Toast.LENGTH_SHORT).show();
+ OldPassword.setText("");
+ }
+ else if (new_password.equals(ack_password) == true && old_password.equals(login_password) == true){
+ SharedPreferences.Editor editor=getSharedPreferences("user management", MODE_PRIVATE).edit();
+ editor.putString("password",new_password);
+ editor.apply();
+ Toast.makeText(ChangeLoginPasswordActivity.this, "修改密码成功", Toast.LENGTH_SHORT).show();
+ Intent intent=new Intent(ChangeLoginPasswordActivity.this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ Intent intent=new Intent(ChangeLoginPasswordActivity.this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ }
+}
+
diff --git a/src/main/java/net/micode/notes/ui/LoginActivity.java b/src/main/java/net/micode/notes/ui/LoginActivity.java
new file mode 100644
index 0000000..caae81f
--- /dev/null
+++ b/src/main/java/net/micode/notes/ui/LoginActivity.java
@@ -0,0 +1,163 @@
+package net.micode.notes.ui;
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.style.BackgroundColorSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.TextNote;
+import net.micode.notes.model.WorkingNote;
+import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
+import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.ResourceParser;
+import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
+import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
+import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
+import net.micode.notes.widget.NoteWidgetProvider_2x;
+import net.micode.notes.widget.NoteWidgetProvider_4x;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+public class LoginActivity extends Activity{
+ private EditText accountEdit;
+
+ private EditText passwordEdit;
+
+ private Button login;
+
+ private Button cancel;
+
+ private Button change;
+
+ private long last_login_time = 0 ;
+
+ public static final long MAX_LOGIN_TIME = 9999999;
+
+
+
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+
+ SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE);
+ boolean user_boolean = pref.getBoolean("user",false);//获取用户是否设置了密码
+ if(!user_boolean) //User_boolean = false时,(没有设置密码),直接跳转到便签主界面
+ {
+ SharedPreferences.Editor editor=getSharedPreferences("user management", MODE_PRIVATE).edit();
+ editor.putString("password","123456");
+ editor.putBoolean("user",true);
+ editor.apply();
+ }
+
+ SharedPreferences login_time_get = getSharedPreferences("login time",MODE_PRIVATE);
+ boolean is_first_login = login_time_get.getBoolean("is_first_login",false);
+
+ setContentView(R.layout.login_activity);
+ accountEdit = (EditText) findViewById(R.id.account);
+ passwordEdit = (EditText) findViewById(R.id.password);
+ login = (Button) findViewById(R.id.login);
+ cancel = (Button) findViewById(R.id.cancel);
+ change = (Button) findViewById(R.id.change_password);
+
+
+ long current_time = System.currentTimeMillis();
+
+ cancel.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Toast.makeText(LoginActivity.this, R.string.app_already_quit, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ });
+
+ Long current_login_time = login_time_get.getLong("current login time",0);
+ if((is_first_login)&&(current_time - current_login_time <= MAX_LOGIN_TIME)){
+ SharedPreferences.Editor editor=getSharedPreferences("login time", MODE_PRIVATE).edit();
+ editor.putLong("current login time",System.currentTimeMillis());
+ editor.apply();
+ Intent intent = new Intent(LoginActivity.this,NotesListActivity.class);
+ startActivity(intent);
+ finish();
+ }
+
+ else{
+ if(!is_first_login){
+ SharedPreferences.Editor editor=getSharedPreferences("login time", MODE_PRIVATE).edit();
+ editor.putLong("current login time",System.currentTimeMillis());
+ editor.putBoolean("is_first_login",true);
+ editor.apply();
+ }
+
+ login.setOnClickListener(new View.OnClickListener(){
+ public void onClick(View v) {
+ String account = accountEdit.getText().toString();
+ String password = passwordEdit.getText().toString();
+
+ SharedPreferences pref=getSharedPreferences("user management",MODE_PRIVATE);
+ String share_password=pref.getString("password","");
+
+ if(account.equals("hei") && password.equals(share_password)){
+ ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this);
+ progressDialog.setTitle(R.string.Loading);
+ progressDialog.setMessage("Loading...");
+ progressDialog.setCancelable(true);
+ progressDialog.show();;
+ Intent intent = new Intent(LoginActivity.this,NotesListActivity.class);
+ startActivity(intent);
+ finish();
+ }else {
+ Toast.makeText(LoginActivity.this, R.string.invalid,Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ change.setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(LoginActivity.this,ChangeLoginPasswordActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/net/micode/notes/ui/LoginView.java b/src/main/java/net/micode/notes/ui/LoginView.java
new file mode 100644
index 0000000..deca847
--- /dev/null
+++ b/src/main/java/net/micode/notes/ui/LoginView.java
@@ -0,0 +1,76 @@
+package net.micode.notes.ui;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.style.BackgroundColorSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.TextNote;
+import net.micode.notes.model.WorkingNote;
+import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
+import net.micode.notes.tool.DataUtils;
+import net.micode.notes.tool.ResourceParser;
+import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
+import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
+import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
+import net.micode.notes.widget.NoteWidgetProvider_2x;
+import net.micode.notes.widget.NoteWidgetProvider_4x;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class LoginView extends Activity
+{
+ Handler mHandler=new Handler();
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.login_view);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent(LoginView.this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ },2000);
+ }
+}
diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java
index 862883b..0a90e43 100644
--- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java
@@ -84,6 +84,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public ImageView ibSetBgColor;
}
+ //静态Map。用于设置背景选择器,点击相关按钮选选择相关颜色。这是选择便签颜色的选择器
private static final Map sBgSelectorBtnsMap = new HashMap();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
@@ -91,6 +92,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
+ sBgSelectorBtnsMap.put(R.id.iv_ph_duck, ResourceParser.DUCK);
+
}
private static final Map sBgSelectorSelectionMap = new HashMap();
@@ -100,8 +103,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
- }
+ sBgSelectorSelectionMap.put(ResourceParser.DUCK, R.id.iv_ph_duck_select);
+ }//
+ //选择便签字体大小的界选择器的吧
private static final Map sFontSizeBtnsMap = new HashMap();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
@@ -149,6 +154,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private String mUserQuery;
private Pattern mPattern;
+ //创建Activity,启动其生命周期
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -165,6 +171,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
+ //Activity状态恢复函数。当Activity因内存不足被杀死后,再次加载该活动
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
@@ -179,6 +186,19 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
+ //下面是具体执行intent的函数。这个函数会判断接收的intent是干啥的,从而执行相关的操作。比如,我们的intent是新建便签。定义的CreateNewNote函数
+ //只是为我们设置好了intent,而intent的具体执行还要在initActivityState里。
+ //对于创建便签,他会看是普通的文字便签还是通讯记录,对于文字便签,新建便签的操作是调用了workingNote.createEmptyNote函数。而这个该函数
+ //又会调用Note.Note函数。故猜测:Note是对应的便签的操作包,里面定义了对一个便签的所有操作(比如创建等),workingNote是Note的一个子集,即当前正在被操作的note。
+ // Notes定义了
+ //(根)文件夹和便签的顶层,即具体样式信息与数据等信息。DataColumn是便签具体的和数据直接相关的,NoteColumn是定义了便签或文件夹的其他信息,
+ // 比如用了什么小组件,创建时间,样式,背景颜色等信息。而NoteitemData是一个便签或文件夹的具体实例,其中包括了DataColumn及NoteColum等其他信息。
+ //故对于一个便签来说,NoteItemData是他的数据及自身信息实例,Note是其操作定义。
+
+
+ //初始化Activity状态函数,形参为intent,根据传入的intent执行相关活动。
+ //该函数通过if-else,判断intent.getAction()是哪种活动,从而执行相应函数。
+ //通过intent.getExtra来获取活动执行对象的附加信息。
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
@@ -262,12 +282,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
+ //回调函数,使得Activity与用户互动。这里不断初始化便签界面
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
+ //初始化便签界面
private void initNoteScreen() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
@@ -295,6 +317,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
showAlertHeader();
}
+ //闹钟提醒功能实现。
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis();
@@ -430,13 +453,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- - View.VISIBLE);
+ View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
- } else if (sFontSizeBtnsMap.containsKey(id)) {
+ }
+ else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
diff --git a/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/main/java/net/micode/notes/ui/NotesListActivity.java
index 6db3761..88549a7 100644
--- a/src/main/java/net/micode/notes/ui/NotesListActivity.java
+++ b/src/main/java/net/micode/notes/ui/NotesListActivity.java
@@ -78,6 +78,11 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
+/**
+ * NotesListActivty类实现小米便签主界面。
+ * 覆盖点击事件监听类及长按事件监听类
+ * 该类为main Activity,主要通过intent与NoteEditActivity交互
+ */
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
@@ -113,6 +118,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private TextView mTitleBar;
+
private long mCurrentFolderId;
private ContentResolver mContentResolver;
@@ -135,6 +141,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
+ //创建Activity,启动其生命周期。完成设置界面样式、初始化资源、且在用户第一次使用App时进行介绍
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -147,6 +154,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
setAppInfoFromRawRes();
}
+ //intent返回数据接收函数。形参为请求码、结果码、其他intent返回的数据。主要与NoteEditActivity交互。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
@@ -157,6 +165,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
+ //第一次使用App时调用,显示introduction。
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
@@ -202,13 +211,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
}
-
+ //onCreate调用后回调,此Activity对用户可见。
@Override
protected void onStart() {
super.onStart();
startAsyncNotesListQuery();
}
+ //初始化资源
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
@@ -231,11 +241,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mModeCallBack = new ModeCallback();
}
+ /**
+ * 定义了ActionMode的使用。便签长按事件通过ActionMode执行。
+ * 便签长按后出现选择(多个)便签删除的菜单栏,其在ActionBar中显示。
+ */
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
+ //ActionMode创建函数,形参为创建的ActionMode及需要通过该ActionMode显示的Menu对象
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
@@ -269,6 +284,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
+ //用于进行多选(全选)时菜单栏界面的更新
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// Update dropdown menu
@@ -286,32 +302,39 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
+
+ //重新刷新菜单栏时调用.
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
+ //菜单栏点击动作按钮时调用,形参为ActionMode、点击的菜单项.
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
+ //销毁或退出ActionMode时调用。
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}
+ //结束ActionMode时调用
public void finishActionMode() {
mActionMode.finish();
}
+ //检查当前菜单选择情况(针对多选)并进行菜单更新。
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
+ //菜单项点击时调用。
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
@@ -330,7 +353,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
- batchDelete();
+ batchDelete();//批处理删除
}
});
builder.setNegativeButton(android.R.string.cancel, null);
@@ -344,61 +367,58 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
+ /**
+ * 新便签触摸事件监听类。根据触摸事件(比如手在屏幕滑动)修正视图的位置参数
+ */
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- Display display = getWindowManager().getDefaultDisplay();
- int screenHeight = display.getHeight();
- int newNoteViewHeight = mAddNewNote.getHeight();
- int start = screenHeight - newNoteViewHeight;
- int eventY = start + (int) event.getY();
- /**
- * Minus TitleBar's height
- */
- if (mState == ListEditState.SUB_FOLDER) {
- eventY -= mTitleBar.getHeight();
- start -= mTitleBar.getHeight();
- }
- /**
- * HACKME:When click the transparent part of "New Note" button, dispatch
- * the event to the list view behind this button. The transparent part of
- * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel)
- * and the line top of the button. The coordinate based on left of the "New
- * Note" button. The 94 represents maximum height of the transparent part.
- * Notice that, if the background of the button changes, the formula should
- * also change. This is very bad, just for the UI designer's strong requirement.
- */
- if (event.getY() < (event.getX() * (-0.12) + 94)) {
- View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- - mNotesListView.getFooterViewsCount());
- if (view != null && view.getBottom() > start
- && (view.getTop() < (start + 94))) {
- mOriginY = (int) event.getY();
- mDispatchY = eventY;
- event.setLocation(event.getX(), mDispatchY);
- mDispatch = true;
- return mNotesListView.dispatchTouchEvent(event);
- }
- }
- break;
+ int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN) {
+ Display display = getWindowManager().getDefaultDisplay();
+ int screenHeight = display.getHeight();
+ int newNoteViewHeight = mAddNewNote.getHeight();
+ int start = screenHeight - newNoteViewHeight;
+ int eventY = start + (int) event.getY();
+ /**
+ * Minus TitleBar's height
+ */
+ if (mState == ListEditState.SUB_FOLDER) {
+ eventY -= mTitleBar.getHeight();
+ start -= mTitleBar.getHeight();
}
- case MotionEvent.ACTION_MOVE: {
- if (mDispatch) {
- mDispatchY += (int) event.getY() - mOriginY;
+ /**
+ * HACKME:When click the transparent part of "New Note" button, dispatch
+ * the event to the list view behind this button. The transparent part of
+ * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel)
+ * and the line top of the button. The coordinate based on left of the "New
+ * Note" button. The 94 represents maximum height of the transparent part.
+ * Notice that, if the background of the button changes, the formula should
+ * also change. This is very bad, just for the UI designer's strong requirement.
+ */
+ if (event.getY() < (event.getX() * (-0.12) + 94)) {
+ View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
+ - mNotesListView.getFooterViewsCount());
+ if (view != null && view.getBottom() > start
+ && (view.getTop() < (start + 94))) {
+ mOriginY = (int) event.getY();
+ mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
+ mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
}
- break;
}
- default: {
- if (mDispatch) {
- event.setLocation(event.getX(), mDispatchY);
- mDispatch = false;
- return mNotesListView.dispatchTouchEvent(event);
- }
- break;
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ if (mDispatch) {
+ mDispatchY += (int) event.getY() - mOriginY;
+ event.setLocation(event.getX(), mDispatchY);
+ return mNotesListView.dispatchTouchEvent(event);
+ }
+ } else {
+ if (mDispatch) {
+ event.setLocation(event.getX(), mDispatchY);
+ mDispatch = false;
+ return mNotesListView.dispatchTouchEvent(event);
}
}
return false;
@@ -406,6 +426,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
};
+ /*
+ 已同步便签查询。当进入一个新文件夹时会向数据库发送查询当前文件夹下的便签请求。返回查询结果。
+ 为异步查询进程
+ */
+
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
@@ -415,11 +440,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
+ /**
+ * 背景查询消息处理类。
+ * 主要功能是将查询(startAsyncNoteListQuery)后得到的便签在界面显示
+ */
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
+ //下面就是查询结束后对返回数据的处理,完成界面更新。
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
@@ -439,6 +469,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
+
+ //通过游标显示文件夹列表菜单,功能为移动便签至某个文件夹
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
@@ -460,6 +492,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
builder.show();
}
+ //创建新便签。主要是创建intent并启动。
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
@@ -467,6 +500,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
+
+ //批处理删除。用于多个标签删除时.
private void batchDelete() {
new AsyncTask>() {
protected HashSet doInBackground(Void... unused) {
@@ -504,6 +539,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}.execute();
}
+ //文件夹删除功能。形参为文件夹ID号
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
@@ -531,6 +567,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
+ //打开便签功能。形参为NoteItemData,其为文件夹或便签的实例。
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
@@ -538,6 +575,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
+ //打开文件夹功能
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
@@ -555,24 +593,28 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mTitleBar.setVisibility(View.VISIBLE);
}
+ //视图点击事件。实现创建便签。
+
public void onClick(View v) {
if (v.getId() == R.id.btn_new_note) {
createNewNote();
}
}
+ //显示软键盘
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
-
+ //不显示软键盘
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
+ //文件夹新建、查看及重命名功能。
private void showCreateOrModifyFolderDialog(final boolean create) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
@@ -657,7 +699,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
});
}
-
+ //用于处理子Acitivity返回父Activity。相当于对按下“返回箭头”的处理。即返回NotesListActivity活动。
@Override
public void onBackPressed() {
switch (mState) {
@@ -682,6 +724,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
+ //更新小组件。
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
@@ -701,7 +744,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
setResult(RESULT_OK, intent);
}
+ //查看、删除、修改文件夹名称的浮动菜单栏(ContextMenu)创建
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
+ //创建ContextMenu,形参为创建的菜单、长按事件绑定的视图、视图元素信息
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
@@ -712,6 +757,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
};
+ //下面都是对浮动菜单栏的一些函数
+ //浮动菜单栏关闭
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
@@ -719,6 +766,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
super.onContextMenuClosed(menu);
}
+ //浮动菜单栏点击按钮后事件处理。
@Override
public boolean onContextItemSelected(MenuItem item) {
@@ -754,6 +802,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
+ //下面是OptionsMenu菜单创建的有关函数。
+ //OptionsMenu创建
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
@@ -772,6 +822,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
+ //OptionsMenu点击按钮事件处理
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
@@ -799,12 +850,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
+ //搜索栏功能实现(就是最上面那个搜索便签得的)
+ //(内部调用了搜索的ui组件)
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
return true;
}
+ //下面使用了异步线程类AsynTask。可在类中可以直接进行UI操作,并将后台计算的结果及时的交给UI线程进行UI界面显示。
+ //将便签内容输出为文本
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask() {
@@ -851,14 +906,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
+ //用于跳转到保存的设置界面。这个maybe在isSyncMode==0时调用。作用在没进行过保存设置时。
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
}
+ /**
+ * 点击事件监听类
+ */
private class OnListItemClickListener implements OnItemClickListener {
+ //点击事件处理。将点击事件其与视图绑定。
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
@@ -898,6 +958,7 @@ 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:
@@ -916,6 +977,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
NoteColumns.MODIFIED_DATE + " DESC");
}
+ //处理长按事件。
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
diff --git a/src/main/res/drawable-hdpi/edit_title_red.9.png b/src/main/res/drawable-hdpi/edit_title_red.9.png
index 9c430e5a6a6d596962b5a93b60def02063e81316..794d8e0a4c8b7144f4f4d76d11231da4ea5702ed 100644
GIT binary patch
delta 1966
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
literal 4101
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
diff --git a/src/main/res/drawable-hdpi/login_photo.png b/src/main/res/drawable-hdpi/login_photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb6b8c9e25a69b038e2f93fb9a8cfd1ed84c9adf
GIT binary patch
literal 1389
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