From ea369604c0ae65d003cd7369b1dbfd60f2bf5b67 Mon Sep 17 00:00:00 2001 From: xxy <18873782407@163.com> Date: Sun, 22 Sep 2024 21:45:15 +0800 Subject: [PATCH] commit --- ...€æ ‡æ³¨å’Œç»´æŠ¤æŠ¥å‘Šæ–‡æ¡£2024 (1).docx | Bin 0 -> 67346 bytes ...€æºè½¯ä»¶çš„è´¨é‡åˆ†æžæŠ¥å‘Šæ–‡æ¡£.docx | Bin 0 -> 28056 bytes src/.idea/gradle.xml | 4 +- src/.idea/misc.xml | 1 - src/.idea/modules.xml | 8 + src/.idea/src.iml | 9 + src/.idea/vcs.xml | 6 + .../java/net/micode/notes/data/Contact.java | 18 +- .../java/net/micode/notes/data/Notes.java | 537 ++++----- .../notes/data/NotesDatabaseHelper.java | 701 +++++------ .../net/micode/notes/data/NotesProvider.java | 578 ++++----- .../java/net/micode/notes/model/Note.java | 481 ++++---- .../net/micode/notes/model/WorkingNote.java | 705 +++++------ .../micode/notes/ui/AlarmAlertActivity.java | 310 ++--- .../micode/notes/ui/AlarmInitReceiver.java | 105 +- .../net/micode/notes/ui/AlarmReceiver.java | 31 +- .../micode/notes/ui/DateTimePickerDialog.java | 170 +-- .../net/micode/notes/ui/DropdownMenu.java | 100 +- .../micode/notes/ui/FoldersListAdapter.java | 134 ++- .../net/micode/notes/ui/NoteEditActivity.java | 1040 +++++++++-------- .../net/micode/notes/ui/NoteEditText.java | 416 +++---- .../net/micode/notes/ui/NoteItemData.java | 452 +++---- .../net/micode/notes/ui/NotesListAdapter.java | 363 +++--- .../net/micode/notes/ui/NotesListItem.java | 245 ++-- .../notes/ui/NotesPreferenceActivity.java | 739 ++++++------ src/local.properties | 7 +- 26 files changed, 3735 insertions(+), 3425 deletions(-) create mode 100644 doc/实践模æ¿-å¼€æºè½¯ä»¶æ³›è¯»ã€æ ‡æ³¨å’Œç»´æŠ¤æŠ¥å‘Šæ–‡æ¡£2024 (1).docx create mode 100644 doc/å¼€æºè½¯ä»¶çš„è´¨é‡åˆ†æžæŠ¥å‘Šæ–‡æ¡£.docx create mode 100644 src/.idea/modules.xml create mode 100644 src/.idea/src.iml create mode 100644 src/.idea/vcs.xml diff --git a/doc/实践模æ¿-å¼€æºè½¯ä»¶æ³›è¯»ã€æ ‡æ³¨å’Œç»´æŠ¤æŠ¥å‘Šæ–‡æ¡£2024 (1).docx b/doc/实践模æ¿-å¼€æºè½¯ä»¶æ³›è¯»ã€æ ‡æ³¨å’Œç»´æŠ¤æŠ¥å‘Šæ–‡æ¡£2024 (1).docx new file mode 100644 index 0000000000000000000000000000000000000000..5d7a035f0f236d56323dfef7b1308569c6bc7b67 GIT binary patch literal 67346 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

XK+-v7xc7KIlME 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 XF237viXEi`tF#W#v=e*4OoRRp)$gcB literal 0 HcmV?d00001 diff --git a/doc/å¼€æºè½¯ä»¶çš„è´¨é‡åˆ†æžæŠ¥å‘Šæ–‡æ¡£.docx b/doc/å¼€æºè½¯ä»¶çš„è´¨é‡åˆ†æžæŠ¥å‘Šæ–‡æ¡£.docx new file mode 100644 index 0000000000000000000000000000000000000000..0bba912ffe701bbe80cae87c01d7f1e8d595c30f GIT binary patch literal 28056 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 D*QEn; literal 0 HcmV?d00001 diff --git a/src/.idea/gradle.xml b/src/.idea/gradle.xml index de8896e..c680525 100644 --- a/src/.idea/gradle.xml +++ b/src/.idea/gradle.xml @@ -1,12 +1,12 @@ + diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml index 7c65e80..bfb7499 100644 --- a/src/.idea/misc.xml +++ b/src/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml new file mode 100644 index 0000000..f669a0e --- /dev/null +++ b/src/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/src.iml b/src/.idea/src.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/src/.idea/src.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/src/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/data/Contact.java b/src/app/src/main/java/net/micode/notes/data/Contact.java index d97ac5d..b4011b0 100644 --- a/src/app/src/main/java/net/micode/notes/data/Contact.java +++ b/src/app/src/main/java/net/micode/notes/data/Contact.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package net.micode.notes.data; - import android.content.Context; import android.database.Cursor; import android.provider.ContactsContract.CommonDataKinds.Phone; @@ -24,11 +22,11 @@ import android.telephony.PhoneNumberUtils; import android.util.Log; import java.util.HashMap; - public class Contact { private static HashMap sContactCache; + //ç”¨äºŽç¼“å­˜å·²ç»æŸ¥è¯¢è¿‡çš„电è¯å·ç å’Œå¯¹åº”çš„è”系人姓å,目的是é¿å…é‡å¤æŸ¥è¯¢ï¼Œæé«˜æ€§èƒ½ã€‚ private static final String TAG = "Contact"; - + //ç”¨äºŽæ—¥å¿—è¾“å‡ºçš„æ ‡ç­¾ï¼Œæ–¹ä¾¿åœ¨æ—¥å¿—ä¸­è¯†åˆ«è¯¥ç±»ç›¸å…³çš„æ—¥å¿—ä¿¡æ¯ private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.RAW_CONTACT_ID + " IN " @@ -40,34 +38,38 @@ public class Contact { if(sContactCache == null) { sContactCache = new HashMap(); } - + //检查缓存sContactCache是å¦å·²ç»åˆå§‹åŒ–,如果没有则进行åˆå§‹åŒ– if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } - + //检查传入的电è¯å·ç æ˜¯å¦å·²ç»åœ¨ç¼“存中,如果在缓存中则直接返回对应的è”系人姓å String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + //如果ä¸åœ¨ç¼“存中,则构建查询æ¡ä»¶ã€‚通过CALLER_ID_SELECTION字符串构建查询语å¥ï¼Œå…¶ä¸­å°†+替æ¢ä¸ºé€šè¿‡PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)生æˆçš„匹é…字符串 Cursor cursor = context.getContentResolver().query( Data.CONTENT_URI, new String [] { Phone.DISPLAY_NAME }, selection, new String[] { phoneNumber }, null); + //执行查询æ“作,查询的结果集包å«è”系人姓å if (cursor != null && cursor.moveToFirst()) { try { String name = cursor.getString(0); sContactCache.put(phoneNumber, name); return name; + //如果查询结果ä¸ä¸ºç©ºä¸”游标å¯ä»¥ç§»åŠ¨åˆ°ç¬¬ä¸€æ¡è®°å½•(表示找到了匹é…çš„è”系人),则从游标中获å–è”系人姓å,并将其放入缓存中,然åŽè¿”回该姓å } catch (IndexOutOfBoundsException e) { Log.e(TAG, " Cursor get string error " + e.toString()); return null; + //如果在获å–å§“å过程中å‘生索引越界异常,则在日志中记录错误信æ¯å¹¶è¿”回null } finally { cursor.close(); - } + }//无论是å¦å‘生异常,都需è¦åœ¨finallyå—ä¸­å…³é—­æ¸¸æ ‡ä»¥é‡Šæ”¾èµ„æº } else { Log.d(TAG, "No contact matched with number:" + phoneNumber); return null; - } + }//如果查询结果为空,表示没有找到匹é…çš„è”系人 } } diff --git a/src/app/src/main/java/net/micode/notes/data/Notes.java b/src/app/src/main/java/net/micode/notes/data/Notes.java index f240604..c470baa 100644 --- a/src/app/src/main/java/net/micode/notes/data/Notes.java +++ b/src/app/src/main/java/net/micode/notes/data/Notes.java @@ -14,266 +14,277 @@ * limitations under the License. */ -package net.micode.notes.data; - -import android.net.Uri; -public class Notes { - public static final String AUTHORITY = "micode_notes"; - public static final String TAG = "Notes"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; - - /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records - */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; - - public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; - } - - /** - * Uri to query all notes and folders - */ - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); - - /** - * Uri to query data - */ - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); - - public interface NoteColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: TEXT

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER

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

Type: INTEGER (long)

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

Type: INTEGER

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

Type: INTEGER (long)

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

Type: INTEGER

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

Type : INTEGER

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

Type : TEXT

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

Type : INTEGER (long)

- */ - public static final String VERSION = "version"; - } - - public interface DataColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

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

Type: Text

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: TEXT

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

Type: INTEGER

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

Type: INTEGER

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

Type: TEXT

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

Type: TEXT

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

Type: TEXT

- */ - public static final String DATA5 = "data5"; - } - - public static final class TextNote implements DataColumns { - /** - * Mode to indicate the text in check list mode or not - *

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

- */ - public static final String MODE = DATA1; - - public static final int MODE_CHECK_LIST = 1; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); - } - - public static final class CallNote implements DataColumns { - /** - * Call date for this record - *

Type: INTEGER (long)

- */ - public static final String CALL_DATE = DATA1; - - /** - * Phone number for this record - *

Type: TEXT

- */ - public static final String PHONE_NUMBER = DATA3; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); - } -} + package net.micode.notes.data; + + import android.net.Uri; + public class Notes { + public static final String AUTHORITY = "micode_notes"; + public static final String TAG = "Notes"; + public static final int TYPE_NOTE = 0; + public static final int TYPE_FOLDER = 1; + public static final int TYPE_SYSTEM = 2; + + /** + * Following IDs are system folders' identifiers + * {@link Notes#ID_ROOT_FOLDER } is default folder + * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder + * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + */ + public static final int ID_ROOT_FOLDER = 0; + public static final int ID_TEMPARAY_FOLDER = -1; + public static final int ID_CALL_RECORD_FOLDER = -2; + public static final int ID_TRASH_FOLER = -3; + + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; + + public static final int TYPE_WIDGET_INVALIDE = -1; + public static final int TYPE_WIDGET_2X = 0; + public static final int TYPE_WIDGET_4X = 1; + + public static class DataConstants { + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + } + + /** + * Uri to query all notes and folders + */ + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); + + /** + * Uri to query data + */ + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + + public interface NoteColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; + //行的唯一标识 + /** + * The parent's id for note or folder + *

Type: INTEGER (long)

+ */ + public static final String PARENT_ID = "parent_id"; + //笔记或文件夹的父级 ID + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + //创建日期 + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + //最åŽä¿®æ”¹æ—¥æœŸ + + /** + * Alert date + *

Type: INTEGER (long)

+ */ + public static final String ALERTED_DATE = "alert_date"; + //æé†’日期 + /** + * Folder's name or text content of note + *

Type: TEXT

+ */ + public static final String SNIPPET = "snippet"; + //文件夹å称或者笔记的文本内容片段 + /** + * Note's widget id + *

Type: INTEGER (long)

+ */ + public static final String WIDGET_ID = "widget_id"; + //与笔记关è”çš„å°éƒ¨ä»¶ ID + /** + * Note's widget type + *

Type: INTEGER (long)

+ */ + public static final String WIDGET_TYPE = "widget_type"; + //与笔记关è”çš„å°éƒ¨ä»¶ç±»åž‹ + /** + * Note's background color's id + *

Type: INTEGER (long)

+ */ + public static final String BG_COLOR_ID = "bg_color_id"; + //笔记背景颜色 ID + /** + * For text note, it doesn't has attachment, for multi-media + * note, it has at least one attachment + *

Type: INTEGER

+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + //è¡¨ç¤ºç¬”è®°æ˜¯å¦æœ‰é™„ä»¶ + /** + * Folder's count of notes + *

Type: INTEGER (long)

+ */ + public static final String NOTES_COUNT = "notes_count"; + //æ–‡ä»¶å¤¹ä¸­ç¬”è®°çš„æ•°é‡ + /** + * The file type: folder or note + *

Type: INTEGER

+ */ + public static final String TYPE = "type"; + //文件类型 + /** + * The last sync id + *

Type: INTEGER (long)

+ */ + public static final String SYNC_ID = "sync_id"; + //最åŽåŒæ­¥ ID + /** + * Sign to indicate local modified or not + *

Type: INTEGER

+ */ + public static final String LOCAL_MODIFIED = "local_modified"; + //æ˜¯å¦æœ¬åœ°ä¿®æ”¹ + /** + * Original parent id before moving into temporary folder + *

Type : INTEGER

+ */ + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + //在移动到临时文件夹之å‰çš„原始父 ID + /** + * The gtask id + *

Type : TEXT

+ */ + public static final String GTASK_ID = "gtask_id"; + //与GTASK相关的 ID + /** + * The version code + *

Type : INTEGER (long)

+ */ + public static final String VERSION = "version"; + //ç‰ˆæœ¬å· + }//这个接å£å®šä¹‰äº†ä¸Žç¬”记相关的列å和对应的数æ®ç±»åž‹ï¼Œè¿™äº›åˆ—用于数æ®åº“表或者内容æä¾›è€…ä¸­å­˜å‚¨ç¬”è®°çš„ç›¸å…³ä¿¡æ¯ + + public interface DataColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

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

Type: Text

+ */ + public static final String MIME_TYPE = "mime_type"; + //æ•°æ®é¡¹çš„ MIME 类型 + /** + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

+ */ + public static final String NOTE_ID = "note_id"; + //æ•°æ®æ‰€å±žç¬”记的引用 ID + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + //创建日期 + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + //修改日期 + + /** + * Data's content + *

Type: TEXT

+ */ + public static final String CONTENT = "content"; + //æ•°æ®çš„内容 + + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

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

Type: INTEGER

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

Type: TEXT

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

Type: TEXT

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

Type: TEXT

+ */ + public static final String DATA5 = "data5"; + // + }//这个接å£å®šä¹‰äº†ä¸Žæ•°æ®ç›¸å…³çš„列å和对应的数æ®ç±»åž‹ï¼Œç”¨äºŽå­˜å‚¨ä¸Žç¬”记相关的数æ®ä¿¡æ¯ + + public static final class TextNote implements DataColumns { + /** + * Mode to indicate the text in check list mode or not + *

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

+ */ + public static final String MODE = DATA1; + + public static final int MODE_CHECK_LIST = 1; + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); + }//实现了DataColumns接å£ï¼Œç”¨äºŽè¡¨ç¤ºæ–‡æœ¬ç¬”è®°ç›¸å…³çš„æ•°æ® + + public static final class CallNote implements DataColumns { + /** + * Call date for this record + *

Type: INTEGER (long)

+ */ + public static final String CALL_DATE = DATA1; + + /** + * Phone number for this record + *

Type: TEXT

+ */ + public static final String PHONE_NUMBER = DATA3; + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); + }//实现了DataColumns接å£ï¼Œç”¨äºŽè¡¨ç¤ºé€šè¯è®°å½•ç¬”è®°ç›¸å…³çš„æ•°æ® + } + \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..125c46e 100644 --- a/src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -14,349 +14,358 @@ * limitations under the License. */ -package net.micode.notes.data; - -import android.content.ContentValues; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; - - -public class NotesDatabaseHelper extends SQLiteOpenHelper { - private static final String DB_NAME = "note.db"; - - private static final int DB_VERSION = 4; - - public interface TABLE { - public static final String NOTE = "note"; - - public static final String DATA = "data"; - } - - private static final String TAG = "NotesDatabaseHelper"; - - private static NotesDatabaseHelper mInstance; - - private static final String CREATE_NOTE_TABLE_SQL = - "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + - ")"; - - private static final String CREATE_DATA_TABLE_SQL = - "CREATE TABLE " + TABLE.DATA + "(" + - DataColumns.ID + " INTEGER PRIMARY KEY," + - DataColumns.MIME_TYPE + " TEXT NOT NULL," + - DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA1 + " INTEGER," + - DataColumns.DATA2 + " INTEGER," + - DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + - ")"; - - private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = - "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; - - /** - * Increase folder's note count when move note to the folder - */ - private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_update "+ - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; - - /** - * Decrease folder's note count when move note from folder - */ - private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_update " + - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + - " END"; - - /** - * Increase folder's note count when insert new note to the folder - */ - private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_insert " + - " AFTER INSERT ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; - - /** - * Decrease folder's note count when delete note from the folder - */ - private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0;" + - " END"; - - /** - * Update note's content when insert data with type {@link DataConstants#NOTE} - */ - private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = - "CREATE TRIGGER update_note_content_on_insert " + - " AFTER INSERT ON " + TABLE.DATA + - " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed - */ - private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER update_note_content_on_update " + - " AFTER UPDATE ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted - */ - private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = - "CREATE TRIGGER update_note_content_on_delete " + - " AFTER delete ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + - " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Delete datas belong to note which has been deleted - */ - private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = - "CREATE TRIGGER delete_data_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.DATA + - " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - /** - * Delete notes belong to folder which has been deleted - */ - private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = - "CREATE TRIGGER folder_delete_notes_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.NOTE + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - /** - * Move notes belong to folder which has been moved to trash folder - */ - private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = - "CREATE TRIGGER folder_move_notes_on_trash " + - " AFTER UPDATE ON " + TABLE.NOTE + - " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - public NotesDatabaseHelper(Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - public void createNoteTable(SQLiteDatabase db) { - db.execSQL(CREATE_NOTE_TABLE_SQL); - reCreateNoteTableTriggers(db); - createSystemFolder(db); - Log.d(TAG, "note table has been created"); - } - - private void reCreateNoteTableTriggers(SQLiteDatabase db) { - db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); - db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); - db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); - - db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); - db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); - db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); - db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); - db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); - db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); - db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); - } - - private void createSystemFolder(SQLiteDatabase db) { - ContentValues values = new ContentValues(); - - /** - * call record foler for call notes - */ - values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - - /** - * root folder which is default folder - */ - values.clear(); - values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - - /** - * temporary folder which is used for moving note - */ - values.clear(); - values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - - /** - * create trash folder - */ - values.clear(); - values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - } - - public void createDataTable(SQLiteDatabase db) { - db.execSQL(CREATE_DATA_TABLE_SQL); - reCreateDataTableTriggers(db); - db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); - Log.d(TAG, "data table has been created"); - } - - private void reCreateDataTableTriggers(SQLiteDatabase db) { - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); - - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); - } - - static synchronized NotesDatabaseHelper getInstance(Context context) { - if (mInstance == null) { - mInstance = new NotesDatabaseHelper(context); - } - return mInstance; - } - - @Override - public void onCreate(SQLiteDatabase db) { - createNoteTable(db); - createDataTable(db); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean reCreateTriggers = false; - boolean skipV2 = false; - - if (oldVersion == 1) { - upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 - oldVersion++; - } - - if (oldVersion == 2 && !skipV2) { - upgradeToV3(db); - reCreateTriggers = true; - oldVersion++; - } - - if (oldVersion == 3) { - upgradeToV4(db); - oldVersion++; - } - - if (reCreateTriggers) { - reCreateNoteTableTriggers(db); - reCreateDataTableTriggers(db); - } - - if (oldVersion != newVersion) { - throw new IllegalStateException("Upgrade notes database to version " + newVersion - + "fails"); - } - } - - private void upgradeToV2(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); - db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); - createNoteTable(db); - createDataTable(db); - } - - private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id - db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID - + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder - ContentValues values = new ContentValues(); - values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - } - - private void upgradeToV4(SQLiteDatabase db) { - db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION - + " INTEGER NOT NULL DEFAULT 0"); - } -} + package net.micode.notes.data; + + import android.content.ContentValues; + import android.content.Context; + import android.database.sqlite.SQLiteDatabase; + import android.database.sqlite.SQLiteOpenHelper; + import android.util.Log; + + import net.micode.notes.data.Notes.DataColumns; + import net.micode.notes.data.Notes.DataConstants; + import net.micode.notes.data.Notes.NoteColumns; + + + public class NotesDatabaseHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "note.db"; + //æ•°æ®åº“çš„åç§° + + private static final int DB_VERSION = 4; + //æ•°æ®åº“çš„ç‰ˆæœ¬å· + + public interface TABLE { + public static final String NOTE = "note"; + + public static final String DATA = "data"; + }//存储笔记信æ¯å’Œç›¸å…³æ•°æ®çš„表 + + private static final String TAG = "NotesDatabaseHelper"; + + private static NotesDatabaseHelper mInstance; + + private static final String CREATE_NOTE_TABLE_SQL = + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ")"; + + private static final String CREATE_DATA_TABLE_SQL = + "CREATE TABLE " + TABLE.DATA + "(" + + DataColumns.ID + " INTEGER PRIMARY KEY," + + DataColumns.MIME_TYPE + " TEXT NOT NULL," + + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA1 + " INTEGER," + + DataColumns.DATA2 + " INTEGER," + + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + + ")"; + + private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + + /** + * Increase folder's note count when move note to the folder + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_update "+ + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; + + /** + * Decrease folder's note count when move note from folder + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END"; + + /** + * Increase folder's note count when insert new note to the folder + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; + + /** + * Decrease folder's note count when delete note from the folder + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END"; + + /** + * Update note's content when insert data with type {@link DataConstants#NOTE} + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has changed + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has deleted + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; + + /** + * Delete datas belong to note which has been deleted + */ + private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + /** + * Delete notes belong to folder which has been deleted + */ + private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + /** + * Move notes belong to folder which has been moved to trash folder + */ + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + public NotesDatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + public void createNoteTable(SQLiteDatabase db) { + db.execSQL(CREATE_NOTE_TABLE_SQL); + //执行CREATE_NOTE_TABLE_SQL语å¥åˆ›å»ºè¡¨ + reCreateNoteTableTriggers(db); + //釿–°åˆ›å»ºä¸Žnote表相关的触å‘器 + createSystemFolder(db); + //创建系统文件夹相关的记录 + Log.d(TAG, "note table has been created"); + } + + private void reCreateNoteTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); + //首先删除å¯èƒ½å­˜åœ¨çš„æ—§è§¦å‘器 + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); + //æŒ‰ç…§å®šä¹‰çš„é¡ºåºæ‰§è¡Œåˆ›å»ºè§¦å‘器的 SQL 语å¥ï¼Œè¿™äº›è§¦å‘å™¨ç”¨äºŽå¤„ç†æ–‡ä»¶å¤¹ä¸­ç¬”è®°æ•°é‡çš„增å‡ã€ç¬”记内容的更新以åŠåˆ é™¤ç›¸å…³æ“作 + } + + private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + + /** + * call record foler for call notes + */ + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * root folder which is default folder + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * temporary folder which is used for moving note + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * create trash folder + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + } + + public void createDataTable(SQLiteDatabase db) { + db.execSQL(CREATE_DATA_TABLE_SQL); + reCreateDataTableTriggers(db); + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); + Log.d(TAG, "data table has been created"); + } + + private void reCreateDataTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); + + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); + } + + static synchronized NotesDatabaseHelper getInstance(Context context) { + if (mInstance == null) { + mInstance = new NotesDatabaseHelper(context); + } + return mInstance; + } + //è¿™æ˜¯ä¸€ä¸ªé™æ€æ–¹æ³•,实现了å•例模å¼ã€‚它确ä¿åœ¨æ•´ä¸ªåº”用程åºä¸­åªæœ‰ä¸€ä¸ªNotesDatabaseHelper实例被创建和使用,é¿å…了多次创建数æ®åº“连接对象 + + @Override + public void onCreate(SQLiteDatabase db) { + createNoteTable(db); + createDataTable(db); + } + //在数æ®åº“首次创建时被调用,它调用createNoteTableå’ŒcreateDataTable方法分别创建note表和data表 + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + boolean reCreateTriggers = false; + boolean skipV2 = false; + + if (oldVersion == 1) { + upgradeToV2(db); + skipV2 = true; // this upgrade including the upgrade from v2 to v3 + oldVersion++; + } + + if (oldVersion == 2 && !skipV2) { + upgradeToV3(db); + reCreateTriggers = true; + oldVersion++; + } + + if (oldVersion == 3) { + upgradeToV4(db); + oldVersion++; + } + + if (reCreateTriggers) { + reCreateNoteTableTriggers(db); + reCreateDataTableTriggers(db); + } + + if (oldVersion != newVersion) { + throw new IllegalStateException("Upgrade notes database to version " + newVersion + + "fails"); + } + } + //ç”¨äºŽå¤„ç†æ•°æ®åº“å‡çº§æ“作 + private void upgradeToV2(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); + db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); + createNoteTable(db); + createDataTable(db); + } + + private void upgradeToV3(SQLiteDatabase db) { + // drop unused triggers + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); + // add a column for gtask id + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + + " TEXT NOT NULL DEFAULT ''"); + // add a trash system folder + ContentValues values = new ContentValues(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + } + + private void upgradeToV4(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + + " INTEGER NOT NULL DEFAULT 0"); + } + }//NotesDatabaseHelper类是一个SQLiteOpenHelperçš„å­ç±»ï¼Œç”¨äºŽç®¡ç†ä¸Žç¬”记相关的 SQLite æ•°æ®åº“。它负责数æ®åº“的创建ã€å‡çº§ä»¥åŠä¸€äº›ä¸Žæ•°æ®åº“表和触å‘器相关的æ“作 + \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/data/NotesProvider.java b/src/app/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..358ca46 100644 --- a/src/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/src/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -14,292 +14,296 @@ * limitations under the License. */ -package net.micode.notes.data; + package net.micode.notes.data; -import android.app.SearchManager; -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Intent; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.R; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.NotesDatabaseHelper.TABLE; - - -public class NotesProvider extends ContentProvider { - private static final UriMatcher mMatcher; - - private NotesDatabaseHelper mHelper; - - private static final String TAG = "NotesProvider"; - - private static final int URI_NOTE = 1; - private static final int URI_NOTE_ITEM = 2; - private static final int URI_DATA = 3; - private static final int URI_DATA_ITEM = 4; - - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; - - static { - mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); - mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); - } - - /** - * x'0A' represents the '\n' character in sqlite. For title and content in the search result, - * we will trim '\n' and white space in order to show more information. - */ - private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," - + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," - + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," - + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," - + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - - private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION - + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; - - @Override - public boolean onCreate() { - mHelper = NotesDatabaseHelper.getInstance(getContext()); - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - Cursor c = null; - SQLiteDatabase db = mHelper.getReadableDatabase(); - String id = null; - switch (mMatcher.match(uri)) { - case URI_NOTE: - c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, - sortOrder); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); - break; - case URI_DATA: - c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, - sortOrder); - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); - break; - case URI_SEARCH: - case URI_SEARCH_SUGGEST: - if (sortOrder != null || projection != null) { - throw new IllegalArgumentException( - "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); - } - - String searchString = null; - if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { - if (uri.getPathSegments().size() > 1) { - searchString = uri.getPathSegments().get(1); - } - } else { - searchString = uri.getQueryParameter("pattern"); - } - - if (TextUtils.isEmpty(searchString)) { - return null; - } - - try { - searchString = String.format("%%%s%%", searchString); - c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, - new String[] { searchString }); - } catch (IllegalStateException ex) { - Log.e(TAG, "got exception: " + ex.toString()); - } - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), uri); - } - return c; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mHelper.getWritableDatabase(); - long dataId = 0, noteId = 0, insertedId = 0; - switch (mMatcher.match(uri)) { - case URI_NOTE: - insertedId = noteId = db.insert(TABLE.NOTE, null, values); - break; - case URI_DATA: - if (values.containsKey(DataColumns.NOTE_ID)) { - noteId = values.getAsLong(DataColumns.NOTE_ID); - } else { - Log.d(TAG, "Wrong data format without note id:" + values.toString()); - } - insertedId = dataId = db.insert(TABLE.DATA, null, values); - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - // Notify the note uri - if (noteId > 0) { - getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); - } - - // Notify the data uri - if (dataId > 0) { - getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); - } - - return ContentUris.withAppendedId(uri, insertedId); - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - int count = 0; - String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean deleteData = false; - switch (mMatcher.match(uri)) { - case URI_NOTE: - selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; - count = db.delete(TABLE.NOTE, selection, selectionArgs); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - /** - * ID that smaller than 0 is system folder which is not allowed to - * trash - */ - long noteId = Long.valueOf(id); - if (noteId <= 0) { - break; - } - count = db.delete(TABLE.NOTE, - NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); - break; - case URI_DATA: - count = db.delete(TABLE.DATA, selection, selectionArgs); - deleteData = true; - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - count = db.delete(TABLE.DATA, - DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); - deleteData = true; - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - if (count > 0) { - if (deleteData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } - return count; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - int count = 0; - String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean updateData = false; - switch (mMatcher.match(uri)) { - case URI_NOTE: - increaseNoteVersion(-1, selection, selectionArgs); - count = db.update(TABLE.NOTE, values, selection, selectionArgs); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); - count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); - break; - case URI_DATA: - count = db.update(TABLE.DATA, values, selection, selectionArgs); - updateData = true; - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); - updateData = true; - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - - if (count > 0) { - if (updateData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } - return count; - } - - private String parseSelection(String selection) { - return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); - } - - private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(TABLE.NOTE); - sql.append(" SET "); - sql.append(NoteColumns.VERSION); - sql.append("=" + NoteColumns.VERSION + "+1 "); - - if (id > 0 || !TextUtils.isEmpty(selection)) { - sql.append(" WHERE "); - } - if (id > 0) { - sql.append(NoteColumns.ID + "=" + String.valueOf(id)); - } - if (!TextUtils.isEmpty(selection)) { - String selectString = id > 0 ? parseSelection(selection) : selection; - for (String args : selectionArgs) { - selectString = selectString.replaceFirst("\\?", args); - } - sql.append(selectString); - } - - mHelper.getWritableDatabase().execSQL(sql.toString()); - } - - @Override - public String getType(Uri uri) { - // TODO Auto-generated method stub - return null; - } - -} + import android.app.SearchManager; + import android.content.ContentProvider; + import android.content.ContentUris; + import android.content.ContentValues; + import android.content.Intent; + import android.content.UriMatcher; + import android.database.Cursor; + import android.database.sqlite.SQLiteDatabase; + import android.net.Uri; + import android.text.TextUtils; + import android.util.Log; + + import net.micode.notes.R; + import net.micode.notes.data.Notes.DataColumns; + import net.micode.notes.data.Notes.NoteColumns; + import net.micode.notes.data.NotesDatabaseHelper.TABLE; + + + public class NotesProvider extends ContentProvider { + private static final UriMatcher mMatcher; + //1ä¸ªé™æ€çš„UriMatcher对象,用于匹é…传入的Uri,以便根æ®ä¸åŒçš„Uri执行ä¸åŒçš„æ“ä½œ + private NotesDatabaseHelper mHelper; + //NotesDatabaseHelper类型的对象,用于辅助数æ®åº“æ“作 + private static final String TAG = "NotesProvider"; + //用于日志输出的标签 + private static final int URI_NOTE = 1; + private static final int URI_NOTE_ITEM = 2; + private static final int URI_DATA = 3; + private static final int URI_DATA_ITEM = 4; + + private static final int URI_SEARCH = 5; + private static final int URI_SEARCH_SUGGEST = 6; + + static { + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + }//åœ¨é™æ€ä»£ç å—中åˆå§‹åŒ–mMatcher,将ä¸åŒçš„Uri模å¼ä¸Žå¯¹åº”的整数值进行匹é…,这些模å¼åŒ…括对笔记(noteï¼‰ã€æ•°æ®ï¼ˆdataï¼‰ã€æœç´¢ï¼ˆsearch)和æœç´¢å»ºè®®ï¼ˆsearch_suggest)相关的Uri + + /** + * x'0A' represents the '\n' character in sqlite. For title and content in the search result, + * we will trim '\n' and white space in order to show more information. + */ + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + + private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + @Override + public boolean onCreate() { + mHelper = NotesDatabaseHelper.getInstance(getContext()); + return true; + }//在ContentProvider被创建时调用,用于åˆå§‹åŒ–mHelper,通过NotesDatabaseHelper.getInstance(getContext())获å–NotesDatabaseHelper的实例 + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + Cursor c = null; + SQLiteDatabase db = mHelper.getReadableDatabase(); + String id = null; + switch (mMatcher.match(uri)) { + //æ ¹æ®mMatcher.match(uri)的结果æ¥åˆ¤æ–­Uri的类型,然åŽé’ˆå¯¹ä¸åŒç±»åž‹çš„Uri在相应的表上执行查询æ“作 + case URI_NOTE: + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_DATA: + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_SEARCH: + case URI_SEARCH_SUGGEST: + if (sortOrder != null || projection != null) { + throw new IllegalArgumentException( + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); + } + + String searchString = null; + if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + if (uri.getPathSegments().size() > 1) { + searchString = uri.getPathSegments().get(1); + } + } else { + searchString = uri.getQueryParameter("pattern"); + } + + if (TextUtils.isEmpty(searchString)) { + return null; + } + + try { + searchString = String.format("%%%s%%", searchString); + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + new String[] { searchString }); + } catch (IllegalStateException ex) { + Log.e(TAG, "got exception: " + ex.toString()); + } + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); + }//如果查询结果游标ä¸ä¸ºç©ºï¼Œåˆ™è®¾ç½®é€šçŸ¥Uri,以便在数æ®å‘生å˜åŒ–时通知相关的监å¬å™¨ + return c; + }//æ ¹æ®ä¼ å…¥çš„Uri执行查询æ“作 + + @Override + public Uri insert(Uri uri, ContentValues values) { + SQLiteDatabase db = mHelper.getWritableDatabase(); + long dataId = 0, noteId = 0, insertedId = 0; + switch (mMatcher.match(uri)) { + case URI_NOTE: + insertedId = noteId = db.insert(TABLE.NOTE, null, values); + break; + case URI_DATA: + if (values.containsKey(DataColumns.NOTE_ID)) { + noteId = values.getAsLong(DataColumns.NOTE_ID); + } else { + Log.d(TAG, "Wrong data format without note id:" + values.toString()); + } + insertedId = dataId = db.insert(TABLE.DATA, null, values); + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + }//æ ¹æ®mMatcher.match(uri)的结果判断Uri的类型,如果是URI_NOTE,则å‘note表æ’入数æ®ï¼›å¦‚果是URI_DATA,则å‘data表æ’å…¥æ•°æ® + // Notify the note uri + if (noteId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); + } + + // Notify the data uri + if (dataId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + } + //在æ’入数æ®åŽï¼Œæ ¹æ®æ’入的记录 ID(noteId或dataId)通知相关的Uri(Notes.CONTENT_NOTE_URI或Notes.CONTENT_DATA_URI)数æ®å‘生了å˜åŒ– + return ContentUris.withAppendedId(uri, insertedId); + }//æ ¹æ®ä¼ å…¥çš„Uri执行æ’å…¥æ“作 + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int count = 0; + String id = null; + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean deleteData = false; + switch (mMatcher.match(uri)) { + case URI_NOTE: + selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; + count = db.delete(TABLE.NOTE, selection, selectionArgs); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + /** + * ID that smaller than 0 is system folder which is not allowed to + * trash + */ + long noteId = Long.valueOf(id); + if (noteId <= 0) { + break; + } + count = db.delete(TABLE.NOTE, + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + break; + case URI_DATA: + count = db.delete(TABLE.DATA, selection, selectionArgs); + deleteData = true; + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + count = db.delete(TABLE.DATA, + DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + deleteData = true; + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + }//æ ¹æ®mMatcher.match(uri)的结果判断Uri的类型,然åŽåœ¨ç›¸åº”的表(TABLE.NOTE或TABLE.DATA)上执行删除æ“作 + if (count > 0) { + if (deleteData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + getContext().getContentResolver().notifyChange(uri, null); + }//对于删除æ“作,如果删除的行数大于 0ï¼Œåˆ™æ ¹æ®æƒ…况通知相关的Uri(Notes.CONTENT_NOTE_URI或uri本身)数æ®å‘生了å˜åŒ– + return count; + }//æ ¹æ®ä¼ å…¥çš„Uri执行删除æ“作 + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int count = 0; + String id = null; + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean updateData = false; + switch (mMatcher.match(uri)) { + case URI_NOTE: + increaseNoteVersion(-1, selection, selectionArgs); + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + break; + case URI_DATA: + count = db.update(TABLE.DATA, values, selection, selectionArgs); + updateData = true; + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + updateData = true; + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + }//æ ¹æ®mMatcher.match(uri)的结果判断Uri的类型,然åŽåœ¨ç›¸åº”的表(TABLE.NOTE或TABLE.DATA)上执行更新æ“作 + + if (count > 0) { + if (updateData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + getContext().getContentResolver().notifyChange(uri, null); + }//如果更新的行数大于 0ï¼Œåˆ™æ ¹æ®æƒ…况通知相关的Uri(Notes.CONTENT_NOTE_URI或uri本身)数æ®å‘生了å˜åŒ– + return count; + }//æ ¹æ®ä¼ å…¥çš„Uri执行更新æ“作 + + private String parseSelection(String selection) { + return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + }//ç”¨äºŽè§£æžæŸ¥è¯¢æ¡ä»¶å­—符串,如果原始的查询æ¡ä»¶å­—符串ä¸ä¸ºç©ºï¼Œåˆ™åœ¨å‰é¢æ·»åŠ AND并加上括å·ï¼Œä»¥ä¾¿åœ¨æŸ¥è¯¢è¯­å¥ä¸­æ­£ç¡®ä½¿ç”¨ + + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(TABLE.NOTE); + sql.append(" SET "); + sql.append(NoteColumns.VERSION); + sql.append("=" + NoteColumns.VERSION + "+1 "); + //构建一个SQL语å¥ï¼Œæ ¹æ®ä¼ å…¥çš„id和查询æ¡ä»¶ï¼ˆselectionï¼‰æ¥æ›´æ–°note表中的version列。 + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + } + if (id > 0) { + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + + if (!TextUtils.isEmpty(selection)) { + String selectString = id > 0 ? parseSelection(selection) : selection; + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); + } + sql.append(selectString); + } + + mHelper.getWritableDatabase().execSQL(sql.toString()); + //通过mHelper.getWritableDatabase().execSQL执行构建的SQLè¯­å¥ + }//ç”¨äºŽå¢žåŠ ç¬”è®°çš„ç‰ˆæœ¬å· + + @Override + public String getType(Uri uri) { + // TODO Auto-generated method stub + return null; + } + + }//负责处ç†ä¸Žç¬”记相关的数æ®çš„æŸ¥è¯¢ã€æ’å…¥ã€åˆ é™¤å’Œæ›´æ–°æ“作,并通过UriMatcheræ¥åŒ¹é…ä¸åŒçš„Uri以执行相应的æ“作 + \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/model/Note.java b/src/app/src/main/java/net/micode/notes/model/Note.java index 6706cf6..170eb42 100644 --- a/src/app/src/main/java/net/micode/notes/model/Note.java +++ b/src/app/src/main/java/net/micode/notes/model/Note.java @@ -14,240 +14,247 @@ * limitations under the License. */ -package net.micode.notes.model; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.net.Uri; -import android.os.RemoteException; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.CallNote; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.Notes.TextNote; - -import java.util.ArrayList; - - -public class Note { - private ContentValues mNoteDiffValues; - private NoteData mNoteData; - private static final String TAG = "Note"; - /** - * Create a new note id for adding a new note to databases - */ - public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database - ContentValues values = new ContentValues(); - long createdTime = System.currentTimeMillis(); - values.put(NoteColumns.CREATED_DATE, createdTime); - values.put(NoteColumns.MODIFIED_DATE, createdTime); - values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - values.put(NoteColumns.PARENT_ID, folderId); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); - - long noteId = 0; - try { - noteId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - noteId = 0; - } - if (noteId == -1) { - throw new IllegalStateException("Wrong note id:" + noteId); - } - return noteId; - } - - public Note() { - mNoteDiffValues = new ContentValues(); - mNoteData = new NoteData(); - } - - public void setNoteValue(String key, String value) { - mNoteDiffValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - public void setTextData(String key, String value) { - mNoteData.setTextData(key, value); - } - - public void setTextDataId(long id) { - mNoteData.setTextDataId(id); - } - - public long getTextDataId() { - return mNoteData.mTextDataId; - } - - public void setCallDataId(long id) { - mNoteData.setCallDataId(id); - } - - public void setCallData(String key, String value) { - mNoteData.setCallData(key, value); - } - - public boolean isLocalModified() { - return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); - } - - public boolean syncNote(Context context, long noteId) { - if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); - } - - if (!isLocalModified()) { - return true; - } - - /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info - */ - if (context.getContentResolver().update( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, - null) == 0) { - Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through - } - mNoteDiffValues.clear(); - - if (mNoteData.isLocalModified() - && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { - return false; - } - - return true; - } - - private class NoteData { - private long mTextDataId; - - private ContentValues mTextDataValues; - - private long mCallDataId; - - private ContentValues mCallDataValues; - - private static final String TAG = "NoteData"; - - public NoteData() { - mTextDataValues = new ContentValues(); - mCallDataValues = new ContentValues(); - mTextDataId = 0; - mCallDataId = 0; - } - - boolean isLocalModified() { - return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; - } - - void setTextDataId(long id) { - if(id <= 0) { - throw new IllegalArgumentException("Text data id should larger than 0"); - } - mTextDataId = id; - } - - void setCallDataId(long id) { - if (id <= 0) { - throw new IllegalArgumentException("Call data id should larger than 0"); - } - mCallDataId = id; - } - - void setCallData(String key, String value) { - mCallDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - void setTextData(String key, String value) { - mTextDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - Uri pushIntoContentResolver(Context context, long noteId) { - /** - * Check for safety - */ - if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); - } - - ArrayList operationList = new ArrayList(); - ContentProviderOperation.Builder builder = null; - - 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); - try { - setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); - } catch (NumberFormatException e) { - Log.e(TAG, "Insert new text data fail with noteId" + noteId); - mTextDataValues.clear(); - return null; - } - } else { - builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mTextDataId)); - builder.withValues(mTextDataValues); - operationList.add(builder.build()); - } - mTextDataValues.clear(); - } - - 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); - try { - setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); - } catch (NumberFormatException e) { - Log.e(TAG, "Insert new call data fail with noteId" + noteId); - mCallDataValues.clear(); - return null; - } - } else { - builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mCallDataId)); - builder.withValues(mCallDataValues); - operationList.add(builder.build()); - } - mCallDataValues.clear(); - } - - if (operationList.size() > 0) { - try { - ContentProviderResult[] results = context.getContentResolver().applyBatch( - Notes.AUTHORITY, operationList); - return (results == null || results.length == 0 || results[0] == null) ? null - : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } - } - return null; - } - } -} + package net.micode.notes.model; + import android.content.ContentProviderOperation; + import android.content.ContentProviderResult; + import android.content.ContentUris; + import android.content.ContentValues; + import android.content.Context; + import android.content.OperationApplicationException; + import android.net.Uri; + import android.os.RemoteException; + import android.util.Log; + + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.CallNote; + import net.micode.notes.data.Notes.DataColumns; + import net.micode.notes.data.Notes.NoteColumns; + import net.micode.notes.data.Notes.TextNote; + + import java.util.ArrayList; + + + public class Note { + private ContentValues mNoteDiffValues; + //一个ContentValues对象,用于存储笔记的基本信æ¯ï¼ˆå¦‚创建日期ã€ä¿®æ”¹æ—¥æœŸã€ç±»åž‹ç­‰ï¼‰çš„å˜åŒ– + private NoteData mNoteData; + //NoteData类型的对象,用于处ç†ç¬”记中的数æ®ç›¸å…³ä¿¡æ¯ + private static final String TAG = "Note"; + //用于日志输出的标签 + /** + * Create a new note id for adding a new note to databases + */ + public static synchronized long getNewNoteId(Context context, long folderId) { + // Create a new note in the database + ContentValues values = new ContentValues(); + long createdTime = System.currentTimeMillis(); + values.put(NoteColumns.CREATED_DATE, createdTime); + values.put(NoteColumns.MODIFIED_DATE, createdTime); + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.PARENT_ID, folderId); + //创建一个ContentValues对象,设置一些默认的笔记信æ¯ï¼ˆå¦‚创建时间ã€ä¿®æ”¹æ—¶é—´ã€ç±»åž‹ã€æ˜¯å¦æœ¬åœ°ä¿®æ”¹ä»¥åŠçˆ¶æ–‡ä»¶å¤¹ ID 等) + Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + //通过context.getContentResolver().insertæ–¹æ³•å°†è¿™äº›ä¿¡æ¯æ’入到数æ®åº“ä¸­ï¼Œå¹¶èŽ·å–æ’å…¥åŽçš„Uri + long noteId = 0; + try { + noteId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + noteId = 0; + } + if (noteId == -1) { + throw new IllegalStateException("Wrong note id:" + noteId); + }//从Uri中æå–出笔记 ID,如果æå–过程中å‘生NumberFormatException或者笔记 ID 为 -1ï¼Œåˆ™æŠ›å‡ºå¼‚å¸¸æˆ–è€…è®°å½•é”™è¯¯ä¿¡æ¯ + return noteId; + }//用于创建一个新的笔记 ID + + public Note() { + mNoteDiffValues = new ContentValues(); + mNoteData = new NoteData(); + } + + public void setNoteValue(String key, String value) { + mNoteDiffValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + //将传入的键值对放入mNoteDiffValuesä¸­ï¼Œå¹¶åŒæ—¶è®¾ç½®LOCAL_MODIFIEDå’ŒMODIFIED_DATE,表示笔记已被本地修改以åŠä¿®æ”¹çš„æ—¶é—´ + }//ç”¨äºŽè®¾ç½®ç¬”è®°çš„åŸºæœ¬ä¿¡æ¯ + + public void setTextData(String key, String value) { + mNoteData.setTextData(key, value); + }//ç”¨äºŽè®¾ç½®ç¬”è®°ä¸­çš„æ–‡æœ¬æ•°æ® + + public void setTextDataId(long id) { + mNoteData.setTextDataId(id); + } + + public long getTextDataId() { + return mNoteData.mTextDataId; + } + + public void setCallDataId(long id) { + mNoteData.setCallDataId(id); + } + + public void setCallData(String key, String value) { + mNoteData.setCallData(key, value); + } + //ç”¨äºŽè®¾ç½®ç¬”è®°ä¸­çš„é€šè¯æ•°æ® + + public boolean isLocalModified() { + return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); + }//用于判断笔记是å¦è¢«æœ¬åœ°ä¿®æ”¹ + + public boolean syncNote(Context context, long noteId) { + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + + if (!isLocalModified()) { + return true; + } + + /** + * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and + * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the + * note data info + */ + if (context.getContentResolver().update( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, + null) == 0) { + Log.e(TAG, "Update note error, should not happen"); + // Do not return, fall through + } + mNoteDiffValues.clear(); + + if (mNoteData.isLocalModified() + && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { + return false; + } + + return true; + }//ç”¨äºŽåŒæ­¥ç¬”è®° + + private class NoteData { + private long mTextDataId;//å­˜å‚¨æ–‡æœ¬æ•°æ® ID + + private ContentValues mTextDataValues; + + private long mCallDataId; + + private ContentValues mCallDataValues; + + private static final String TAG = "NoteData"; + + public NoteData() { + mTextDataValues = new ContentValues(); + mCallDataValues = new ContentValues(); + mTextDataId = 0; + mCallDataId = 0; + } + + boolean isLocalModified() { + return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; + } + + void setTextDataId(long id) { + if(id <= 0) { + throw new IllegalArgumentException("Text data id should larger than 0"); + } + mTextDataId = id; + } + + void setCallDataId(long id) { + if (id <= 0) { + throw new IllegalArgumentException("Call data id should larger than 0"); + } + mCallDataId = id; + } + + void setCallData(String key, String value) { + mCallDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + + void setTextData(String key, String value) { + mTextDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + + Uri pushIntoContentResolver(Context context, long noteId) { + /** + * Check for safety + */ + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + + ArrayList operationList = new ArrayList(); + ContentProviderOperation.Builder builder = null; + + 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); + try { + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new text data fail with noteId" + noteId); + mTextDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mTextDataId)); + builder.withValues(mTextDataValues); + operationList.add(builder.build()); + } + mTextDataValues.clear(); + } + + 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); + try { + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new call data fail with noteId" + noteId); + mCallDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mCallDataId)); + builder.withValues(mCallDataValues); + operationList.add(builder.build()); + } + mCallDataValues.clear(); + } + + if (operationList.size() > 0) { + try { + ContentProviderResult[] results = context.getContentResolver().applyBatch( + Notes.AUTHORITY, operationList); + return (results == null || results.length == 0 || results[0] == null) ? null + : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } + } + return null; + } + }//用于处ç†ç¬”记中的数æ®ç›¸å…³æ“作 + } + \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/model/WorkingNote.java b/src/app/src/main/java/net/micode/notes/model/WorkingNote.java index be081e4..9679b29 100644 --- a/src/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/src/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -14,355 +14,356 @@ * limitations under the License. */ -package net.micode.notes.model; - -import android.appwidget.AppWidgetManager; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.CallNote; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.Notes.TextNote; -import net.micode.notes.tool.ResourceParser.NoteBgResources; - - -public class WorkingNote { - // Note for the working note - private Note mNote; - // Note Id - private long mNoteId; - // Note content - private String mContent; - // Note mode - private int mMode; - - private long mAlertDate; - - private long mModifiedDate; - - private int mBgColorId; - - private int mWidgetId; - - private int mWidgetType; - - private long mFolderId; - - private Context mContext; - - private static final String TAG = "WorkingNote"; - - private boolean mIsDeleted; - - private NoteSettingChangedListener mNoteSettingStatusListener; - - public static final String[] DATA_PROJECTION = new String[] { - DataColumns.ID, - DataColumns.CONTENT, - DataColumns.MIME_TYPE, - DataColumns.DATA1, - DataColumns.DATA2, - DataColumns.DATA3, - DataColumns.DATA4, - }; - - public static final String[] NOTE_PROJECTION = new String[] { - NoteColumns.PARENT_ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - NoteColumns.MODIFIED_DATE - }; - - private static final int DATA_ID_COLUMN = 0; - - private static final int DATA_CONTENT_COLUMN = 1; - - private static final int DATA_MIME_TYPE_COLUMN = 2; - - private static final int DATA_MODE_COLUMN = 3; - - private static final int NOTE_PARENT_ID_COLUMN = 0; - - private static final int NOTE_ALERTED_DATE_COLUMN = 1; - - private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - - private static final int NOTE_WIDGET_ID_COLUMN = 3; - - private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - - private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - - // New note construct - private WorkingNote(Context context, long folderId) { - mContext = context; - mAlertDate = 0; - mModifiedDate = System.currentTimeMillis(); - mFolderId = folderId; - mNote = new Note(); - mNoteId = 0; - mIsDeleted = false; - mMode = 0; - mWidgetType = Notes.TYPE_WIDGET_INVALIDE; - } - - // Existing note construct - private WorkingNote(Context context, long noteId, long folderId) { - mContext = context; - mNoteId = noteId; - mFolderId = folderId; - mIsDeleted = false; - mNote = new Note(); - loadNote(); - } - - private void loadNote() { - Cursor cursor = mContext.getContentResolver().query( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, - null, null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); - mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); - mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); - mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); - mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); - mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); - } - cursor.close(); - } else { - Log.e(TAG, "No note with id:" + mNoteId); - throw new IllegalArgumentException("Unable to find note with id " + mNoteId); - } - loadNoteData(); - } - - private void loadNoteData() { - Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, - DataColumns.NOTE_ID + "=?", new String[] { - String.valueOf(mNoteId) - }, null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - do { - String type = cursor.getString(DATA_MIME_TYPE_COLUMN); - if (DataConstants.NOTE.equals(type)) { - mContent = cursor.getString(DATA_CONTENT_COLUMN); - mMode = cursor.getInt(DATA_MODE_COLUMN); - mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); - } else if (DataConstants.CALL_NOTE.equals(type)) { - mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); - } else { - Log.d(TAG, "Wrong note type with type:" + type); - } - } while (cursor.moveToNext()); - } - cursor.close(); - } else { - Log.e(TAG, "No data with id:" + mNoteId); - throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); - } - } - - public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, - int widgetType, int defaultBgColorId) { - WorkingNote note = new WorkingNote(context, folderId); - note.setBgColorId(defaultBgColorId); - note.setWidgetId(widgetId); - note.setWidgetType(widgetType); - return note; - } - - public static WorkingNote load(Context context, long id) { - return new WorkingNote(context, id, 0); - } - - public synchronized boolean saveNote() { - if (isWorthSaving()) { - if (!existInDatabase()) { - if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { - Log.e(TAG, "Create new note fail with id:" + mNoteId); - return false; - } - } - - mNote.syncNote(mContext, mNoteId); - - /** - * Update widget content if there exist any widget of this note - */ - if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && mWidgetType != Notes.TYPE_WIDGET_INVALIDE - && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); - } - return true; - } else { - return false; - } - } - - public boolean existInDatabase() { - return mNoteId > 0; - } - - private boolean isWorthSaving() { - if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) - || (existInDatabase() && !mNote.isLocalModified())) { - return false; - } else { - return true; - } - } - - public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { - mNoteSettingStatusListener = l; - } - - public void setAlertDate(long date, boolean set) { - if (date != mAlertDate) { - mAlertDate = date; - mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); - } - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onClockAlertChanged(date, set); - } - } - - public void markDeleted(boolean mark) { - mIsDeleted = mark; - if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); - } - } - - public void setBgColorId(int id) { - if (id != mBgColorId) { - mBgColorId = id; - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onBackgroundColorChanged(); - } - mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); - } - } - - public void setCheckListMode(int mode) { - if (mMode != mode) { - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); - } - mMode = mode; - mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); - } - } - - public void setWidgetType(int type) { - if (type != mWidgetType) { - mWidgetType = type; - mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); - } - } - - public void setWidgetId(int id) { - if (id != mWidgetId) { - mWidgetId = id; - mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); - } - } - - public void setWorkingText(String text) { - if (!TextUtils.equals(mContent, text)) { - mContent = text; - mNote.setTextData(DataColumns.CONTENT, mContent); - } - } - - public void convertToCallNote(String phoneNumber, long callDate) { - mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); - mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); - mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); - } - - public boolean hasClockAlert() { - return (mAlertDate > 0 ? true : false); - } - - public String getContent() { - return mContent; - } - - public long getAlertDate() { - return mAlertDate; - } - - public long getModifiedDate() { - return mModifiedDate; - } - - public int getBgColorResId() { - return NoteBgResources.getNoteBgResource(mBgColorId); - } - - public int getBgColorId() { - return mBgColorId; - } - - public int getTitleBgResId() { - return NoteBgResources.getNoteTitleBgResource(mBgColorId); - } - - public int getCheckListMode() { - return mMode; - } - - public long getNoteId() { - return mNoteId; - } - - public long getFolderId() { - return mFolderId; - } - - public int getWidgetId() { - return mWidgetId; - } - - public int getWidgetType() { - return mWidgetType; - } - - public interface NoteSettingChangedListener { - /** - * Called when the background color of current note has just changed - */ - void onBackgroundColorChanged(); - - /** - * Called when user set clock - */ - void onClockAlertChanged(long date, boolean set); - - /** - * Call when user create note from widget - */ - void onWidgetChanged(); - - /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode - */ - void onCheckListModeChanged(int oldMode, int newMode); - } -} + package net.micode.notes.model; + + import android.appwidget.AppWidgetManager; + import android.content.ContentUris; + import android.content.Context; + import android.database.Cursor; + import android.text.TextUtils; + import android.util.Log; + + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.CallNote; + import net.micode.notes.data.Notes.DataColumns; + import net.micode.notes.data.Notes.DataConstants; + import net.micode.notes.data.Notes.NoteColumns; + import net.micode.notes.data.Notes.TextNote; + import net.micode.notes.tool.ResourceParser.NoteBgResources; + + + public class WorkingNote { + // Note for the working note + private Note mNote; + // Note Id + private long mNoteId; + // Note content + private String mContent; + // Note mode + private int mMode; + + private long mAlertDate; + + private long mModifiedDate; + + private int mBgColorId; + + private int mWidgetId; + + private int mWidgetType; + + private long mFolderId; + + private Context mContext; + + private static final String TAG = "WorkingNote"; + + private boolean mIsDeleted; + + private NoteSettingChangedListener mNoteSettingStatusListener; + + public static final String[] DATA_PROJECTION = new String[] { + DataColumns.ID, + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + public static final String[] NOTE_PROJECTION = new String[] { + NoteColumns.PARENT_ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + NoteColumns.MODIFIED_DATE + }; + + private static final int DATA_ID_COLUMN = 0; + + private static final int DATA_CONTENT_COLUMN = 1; + + private static final int DATA_MIME_TYPE_COLUMN = 2; + + private static final int DATA_MODE_COLUMN = 3; + + private static final int NOTE_PARENT_ID_COLUMN = 0; + + private static final int NOTE_ALERTED_DATE_COLUMN = 1; + + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + + private static final int NOTE_WIDGET_ID_COLUMN = 3; + + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; + + // New note construct + private WorkingNote(Context context, long folderId) { + mContext = context; + mAlertDate = 0; + mModifiedDate = System.currentTimeMillis(); + mFolderId = folderId; + mNote = new Note(); + mNoteId = 0; + mIsDeleted = false; + mMode = 0; + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + } + //用于新建笔记,åˆå§‹åŒ–了一些默认值,并创建了一个新的Note对象。 + // Existing note construct + private WorkingNote(Context context, long noteId, long folderId) { + mContext = context; + mNoteId = noteId; + mFolderId = folderId; + mIsDeleted = false; + mNote = new Note(); + loadNote(); + }//用于加载已存在的笔记,通过查询数æ®åº“加载笔记的属性和数æ®ã€‚ + + private void loadNote() { + Cursor cursor = mContext.getContentResolver().query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, + null, null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); + mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); + mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); + mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); + mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); + } + cursor.close(); + } else { + Log.e(TAG, "No note with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note with id " + mNoteId); + } + loadNoteData(); + }//用于加载笔记的基本属性,如父文件夹 IDã€æé†’æ—¥æœŸã€èƒŒæ™¯é¢œè‰²ç­‰ã€‚ + + private void loadNoteData() { + Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, + DataColumns.NOTE_ID + "=?", new String[] { + String.valueOf(mNoteId) + }, null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + String type = cursor.getString(DATA_MIME_TYPE_COLUMN); + if (DataConstants.NOTE.equals(type)) { + mContent = cursor.getString(DATA_CONTENT_COLUMN); + mMode = cursor.getInt(DATA_MODE_COLUMN); + mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); + } else if (DataConstants.CALL_NOTE.equals(type)) { + mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); + } else { + Log.d(TAG, "Wrong note type with type:" + type); + } + } while (cursor.moveToNext()); + } + cursor.close(); + } else { + Log.e(TAG, "No data with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); + } + }//用于加载笔记的数æ®ï¼ŒåŒ…括笔记内容和模å¼ç­‰ + + public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, + int widgetType, int defaultBgColorId) { + WorkingNote note = new WorkingNote(context, folderId); + note.setBgColorId(defaultBgColorId); + note.setWidgetId(widgetId); + note.setWidgetType(widgetType); + return note; + } + + public static WorkingNote load(Context context, long id) { + return new WorkingNote(context, id, 0); + } + + public synchronized boolean saveNote() { + if (isWorthSaving()) { + if (!existInDatabase()) { + if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { + Log.e(TAG, "Create new note fail with id:" + mNoteId); + return false; + } + } + + mNote.syncNote(mContext, mNoteId); + + /** + * Update widget content if there exist any widget of this note + */ + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE + && mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onWidgetChanged(); + } + return true; + } else { + return false; + } + }//判断笔记是å¦å€¼å¾—ä¿å­˜ï¼Œå¦‚果值得ä¿å­˜åˆ™å°†ç¬”è®°ä¿å­˜åˆ°æ•°æ®åº“。如果笔记是新创建的,会获å–一个新的笔记 ID。ä¿å­˜æˆåŠŸåŽï¼Œå¦‚果有相关的å°éƒ¨ä»¶ï¼Œä¼šé€šçŸ¥ç›‘å¬å™¨æ›´æ–°å°éƒ¨ä»¶å†…容 + + public boolean existInDatabase() { + return mNoteId > 0; + } + + private boolean isWorthSaving() { + if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) + || (existInDatabase() && !mNote.isLocalModified())) { + return false; + } else { + return true; + } + } + + public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { + mNoteSettingStatusListener = l; + } + + public void setAlertDate(long date, boolean set) { + if (date != mAlertDate) { + mAlertDate = date; + mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); + } + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onClockAlertChanged(date, set); + } + } + + public void markDeleted(boolean mark) { + mIsDeleted = mark; + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onWidgetChanged(); + } + } + + public void setBgColorId(int id) { + if (id != mBgColorId) { + mBgColorId = id; + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onBackgroundColorChanged(); + } + mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); + } + } + + public void setCheckListMode(int mode) { + if (mMode != mode) { + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); + } + mMode = mode; + mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); + } + } + + public void setWidgetType(int type) { + if (type != mWidgetType) { + mWidgetType = type; + mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); + } + } + + public void setWidgetId(int id) { + if (id != mWidgetId) { + mWidgetId = id; + mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); + } + } + + public void setWorkingText(String text) { + if (!TextUtils.equals(mContent, text)) { + mContent = text; + mNote.setTextData(DataColumns.CONTENT, mContent); + } + } + + public void convertToCallNote(String phoneNumber, long callDate) { + mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); + mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); + mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); + } + + public boolean hasClockAlert() { + return (mAlertDate > 0 ? true : false); + } + + public String getContent() { + return mContent; + } + + public long getAlertDate() { + return mAlertDate; + } + + public long getModifiedDate() { + return mModifiedDate; + } + + public int getBgColorResId() { + return NoteBgResources.getNoteBgResource(mBgColorId); + } + + public int getBgColorId() { + return mBgColorId; + } + + public int getTitleBgResId() { + return NoteBgResources.getNoteTitleBgResource(mBgColorId); + } + + public int getCheckListMode() { + return mMode; + } + + public long getNoteId() { + return mNoteId; + } + + public long getFolderId() { + return mFolderId; + } + + public int getWidgetId() { + return mWidgetId; + } + + public int getWidgetType() { + return mWidgetType; + } + + public interface NoteSettingChangedListener { + /** + * Called when the background color of current note has just changed + */ + void onBackgroundColorChanged(); + + /** + * Called when user set clock + */ + void onClockAlertChanged(long date, boolean set); + + /** + * Call when user create note from widget + */ + void onWidgetChanged(); + + /** + * Call when switch between check list mode and normal mode + * @param oldMode is previous mode before change + * @param newMode is new mode + */ + void onCheckListModeChanged(int oldMode, int newMode); + } + } + \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/src/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..4866e64 100644 --- a/src/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -14,145 +14,171 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.PowerManager; -import android.provider.Settings; -import android.view.Window; -import android.view.WindowManager; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; - -import java.io.IOException; - - -public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - final Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - - if (!isScreenOn()) { - win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - } - - Intent intent = getIntent(); - - try { - mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); - mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); - mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, - SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) - : mSnippet; - } catch (IllegalArgumentException e) { - e.printStackTrace(); - return; - } - - mPlayer = new MediaPlayer(); - if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); - } else { - finish(); - } - } - - private boolean isScreenOn() { - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - return pm.isScreenOn(); - } - - private void playAlarmSound() { - Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); - - int silentModeStreams = Settings.System.getInt(getContentResolver(), - Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - - if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { - mPlayer.setAudioStreamType(silentModeStreams); - } else { - mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - } - try { - mPlayer.setDataSource(this, url); - mPlayer.prepare(); - mPlayer.setLooping(true); - mPlayer.start(); - } 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); - dialog.setMessage(mSnippet); - dialog.setPositiveButton(R.string.notealert_ok, this); - if (isScreenOn()) { - dialog.setNegativeButton(R.string.notealert_enter, this); - } - dialog.show().setOnDismissListener(this); - } - - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_NEGATIVE: - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, mNoteId); - startActivity(intent); - break; - default: - break; - } - } - - public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); - } - - private void stopAlarmSound() { - if (mPlayer != null) { - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; - } - } -} + package net.micode.notes.ui; + + import android.app.Activity; + import android.app.AlertDialog; + import android.content.Context; + import android.content.DialogInterface; + import android.content.DialogInterface.OnClickListener; + import android.content.DialogInterface.OnDismissListener; + import android.content.Intent; + import android.media.AudioManager; + import android.media.MediaPlayer; + import android.media.RingtoneManager; + import android.net.Uri; + import android.os.Bundle; + import android.os.PowerManager; + import android.provider.Settings; + import android.view.Window; + import android.view.WindowManager; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.tool.DataUtils; + + import java.io.IOException; + + + // 定义一个å为 AlarmAlertActivity 的类,继承自 Activity,并实现了 OnClickListener å’Œ OnDismissListener æŽ¥å£ + 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 + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 请求无标题æ çš„窗å£ç‰¹å¾ + requestWindowFeature(Window.FEATURE_NO_TITLE); + + final Window win = getWindow(); + // è®¾ç½®çª—å£æ ‡å¿—,使其在é”定时显示 + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + + if (!isScreenOn()) { + // 如果å±å¹•æœªç‚¹äº®ï¼Œè®¾ç½®ä¸€ç³»åˆ—çª—å£æ ‡å¿—ä»¥ä¿æŒå±å¹•常亮ã€ç‚¹äº®å±å¹•ç­‰ + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + } + + Intent intent = getIntent(); + + try { + // 从 Intent 的数æ®è·¯å¾„中获å–笔记 ID,并转æ¢ä¸ºé•¿æ•´åž‹ + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // æ ¹æ®ç¬”è®° ID 获å–片段内容 + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + // 如果片段内容长度大于最大长度,截å–并添加æç¤ºä¿¡æ¯ï¼Œå¦åˆ™ä¿æŒä¸å˜ + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + // 打å°å¼‚å¸¸ä¿¡æ¯ + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); + // 如果笔记在数æ®åº“中å¯è§ä¸”类型为 Notes.TYPE_NOTE + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + // 显示æ“ä½œå¯¹è¯æ¡† + showActionDialog(); + // 播放闹钟声音 + playAlarmSound(); + } else { + // å¦‚æžœä¸æ»¡è¶³æ¡ä»¶ï¼Œç»“æŸæ´»åЍ + finish(); + } + } + + // 判断å±å¹•是å¦ç‚¹äº®çš„æ–¹æ³• + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } + + // 播放闹钟声音的方法 + private void playAlarmSound() { + // 获å–系统默认的闹钟铃声 Uri + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM))!= 0) { + // æ ¹æ®ç³»ç»Ÿè®¾ç½®è®¾ç½®åª’体播放器的音频æµç±»åž‹ + mPlayer.setAudioStreamType(silentModeStreams); + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + try { + // è®¾ç½®æ•°æ®æºã€å‡†å¤‡æ’­æ”¾ã€è®¾ç½®å¾ªçŽ¯æ’­æ”¾å¹¶å¼€å§‹æ’­æ”¾ + mPlayer.setDataSource(this, url); + mPlayer.prepare(); + mPlayer.setLooping(true); + mPlayer.start(); + } catch (IllegalArgumentException e) { + // 打å°éžæ³•傿•°å¼‚å¸¸ä¿¡æ¯ + e.printStackTrace(); + } catch (SecurityException e) { + // 打å°å®‰å…¨å¼‚å¸¸ä¿¡æ¯ + e.printStackTrace(); + } catch (IllegalStateException e) { + // 打å°éžæ³•状æ€å¼‚å¸¸ä¿¡æ¯ + e.printStackTrace(); + } catch (IOException e) { + // 打å°è¾“å…¥è¾“å‡ºå¼‚å¸¸ä¿¡æ¯ + e.printStackTrace(); + } + } + + // 显示æ“ä½œå¯¹è¯æ¡†çš„æ–¹æ³• + private void showActionDialog() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(R.string.app_name); + dialog.setMessage(mSnippet); + dialog.setPositiveButton(R.string.notealert_ok, this); + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); + } + // æ˜¾ç¤ºå¯¹è¯æ¡†å¹¶è®¾ç½®å…¶æ¶ˆå¤±ç›‘å¬å™¨ + dialog.show().setOnDismissListener(this); + } + + // 实现 OnClickListener 接å£çš„æ–¹æ³•,处ç†å¯¹è¯æ¡†æŒ‰é’®ç‚¹å‡»äº‹ä»¶ + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + startActivity(intent); + break; + default: + break; + } + } + + // 实现 OnDismissListener 接å£çš„æ–¹æ³•,处ç†å¯¹è¯æ¡†æ¶ˆå¤±äº‹ä»¶ + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); + finish(); + } + + // åœæ­¢é—¹é’Ÿå£°éŸ³çš„æ–¹æ³• + private void stopAlarmSound() { + if (mPlayer!= null) { + mPlayer.stop(); + mPlayer.release(); + mPlayer = null; + } + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/src/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..cb681d6 100644 --- a/src/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -14,52 +14,63 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; - -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 - }; - - private static final int COLUMN_ID = 0; - private static final int COLUMN_ALERTED_DATE = 1; - - @Override - public void onReceive(Context context, Intent intent) { - long currentDate = System.currentTimeMillis(); - Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, - PROJECTION, - NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, - new String[] { String.valueOf(currentDate) }, - null); - - 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))); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); - AlarmManager alermManager = (AlarmManager) context + import android.app.AlarmManager; + import android.app.PendingIntent; + import android.content.BroadcastReceiver; + import android.content.ContentUris; + import android.content.Context; + import android.content.Intent; + import android.database.Cursor; + + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + + // 定义一个å为 AlarmInitReceiver 的类,继承自 BroadcastReceiver + public class AlarmInitReceiver extends BroadcastReceiver { + + // 定义查询数æ®åº“时的投影数组,包å«ç¬”è®° ID å’Œæé†’日期两个字段 + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + + // 定义常é‡ï¼Œè¡¨ç¤ºæŠ•影数组中 ID å’Œæé†’日期字段的索引 + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + @Override + public void onReceive(Context context, Intent intent) { + // 获å–当剿—¶é—´ + long currentDate = System.currentTimeMillis(); + // 查询数æ®åº“ï¼ŒèŽ·å–æé†’æ—¥æœŸå¤§äºŽå½“å‰æ—¶é—´ä¸”类型为笔记的记录 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[]{String.valueOf(currentDate)}, + null); + + if (c!= null) { + if (c.moveToFirst()) { + do { + // èŽ·å–æ¯æ¡è®°å½•çš„æé†’日期 + long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 创建 Intent,用于å‘é€ç»™ AlarmReceiver + Intent sender = new Intent(context, AlarmReceiver.class); + // 设置 Intent 的数æ®ä¸ºå¸¦æœ‰ç¬”è®° ID çš„ Uri + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建 PendingIntent,用于触å‘广播 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获å–系统的 AlarmManager æœåŠ¡ + AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); - alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); - } while (c.moveToNext()); - } - c.close(); - } - } -} + // è®¾ç½®é—¹é’Ÿï¼Œåœ¨æŒ‡å®šæ—¶é—´è§¦å‘ PendingIntent + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); + } + // 关闭游标 + c.close(); + } + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java b/src/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java index 54e503b..c592b37 100644 --- a/src/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/src/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -14,17 +14,22 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class AlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - intent.setClass(context, AlarmAlertActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } -} + import android.content.BroadcastReceiver; + import android.content.Context; + import android.content.Intent; + + // 定义一个å为 AlarmReceiver 的类,它继承自 BroadcastReceiver + public class AlarmReceiver extends BroadcastReceiver { + // é‡å†™ onReceive 方法,当接收到广播时会调用这个方法 + @Override + public void onReceive(Context context, Intent intent) { + // 设置 Intent 的目标类为 AlarmAlertActivity + intent.setClass(context, AlarmAlertActivity.class); + // 为 Intent 添加标志,表示å¯åŠ¨ä¸€ä¸ªæ–°çš„ä»»åŠ¡æ ˆ + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 以给定的 Intent å¯åŠ¨ä¸€ä¸ªæ–°çš„ Activity + context.startActivity(intent); + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/src/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..3b2f9a0 100644 --- a/src/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -14,77 +14,101 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import java.util.Calendar; - -import net.micode.notes.R; -import net.micode.notes.ui.DateTimePicker; -import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.text.format.DateFormat; -import android.text.format.DateUtils; - -public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - - private Calendar mDate = Calendar.getInstance(); - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; - - public interface OnDateTimeSetListener { - void OnDateTimeSet(AlertDialog dialog, long 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()); - } - - public void set24HourView(boolean is24HourView) { - mIs24HourView = is24HourView; - } - - public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { - mOnDateTimeSetListener = callBack; - } - - private void updateTitle(long date) { - int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; - flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; - setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); - } - - public void onClick(DialogInterface arg0, int arg1) { - if (mOnDateTimeSetListener != null) { - mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); - } - } - -} \ No newline at end of file + import java.util.Calendar; + + import net.micode.notes.R; + import net.micode.notes.ui.DateTimePicker; + import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; + + import android.app.AlertDialog; + import android.content.Context; + import android.content.DialogInterface; + import android.content.DialogInterface.OnClickListener; + import android.text.format.DateFormat; + import android.text.format.DateUtils; + + // 定义一个å为 DateTimePickerDialog 的类,它继承自 AlertDialog 并实现了 OnClickListener æŽ¥å£ + public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + // 存储日期的 Calendar 对象 + private Calendar mDate = Calendar.getInstance(); + // 标记是å¦ä¸º 24 å°æ—¶åˆ¶æ˜¾ç¤º + private boolean mIs24HourView; + // 日期时间设置监å¬å™¨ + private OnDateTimeSetListener mOnDateTimeSetListener; + // 日期时间选择器 + private DateTimePicker mDateTimePicker; + + // 定义日期时间设置监å¬å™¨æŽ¥å£ + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + + // 构造函数,接å—上下文和一个日期时间的长整型值 + public DateTimePickerDialog(Context context, long date) { + super(context); + // 创建一个 DateTimePicker 对象 + mDateTimePicker = new DateTimePicker(context); + // è®¾ç½®å¯¹è¯æ¡†çš„视图为 DateTimePicker + setView(mDateTimePicker); + // 设置日期时间改å˜ç›‘å¬å™¨ + mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { + public void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + // 设置 Calendar å¯¹è±¡çš„å¹´ã€æœˆã€æ—¥ã€æ—¶ã€åˆ† + mDate.set(Calendar.YEAR, year); + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + // æ›´æ–°å¯¹è¯æ¡†æ ‡é¢˜ + updateTitle(mDate.getTimeInMillis()); + } + }); + // 设置 Calendar 对象的时间为传入的日期时间 + mDate.setTimeInMillis(date); + // 将秒设置为 0 + mDate.set(Calendar.SECOND, 0); + // 设置 DateTimePicker çš„å½“å‰æ—¥æœŸæ—¶é—´ + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + // 设置确定按钮的文本和点击监å¬å™¨ + setButton(context.getString(R.string.datetime_dialog_ok), this); + // è®¾ç½®å–æ¶ˆæŒ‰é’®çš„æ–‡æœ¬å’Œç‚¹å‡»ç›‘å¬å™¨ä¸º null + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + // æ ¹æ®ä¸Šä¸‹æ–‡è®¾ç½®æ˜¯å¦ä¸º 24 å°æ—¶åˆ¶æ˜¾ç¤º + set24HourView(DateFormat.is24HourFormat(this.getContext())); + // æ›´æ–°å¯¹è¯æ¡†æ ‡é¢˜ + updateTitle(mDate.getTimeInMillis()); + } + + // 设置是å¦ä¸º 24 å°æ—¶åˆ¶æ˜¾ç¤ºçš„æ–¹æ³• + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + + // 设置日期时间设置监å¬å™¨çš„æ–¹æ³• + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; + } + + // æ›´æ–°å¯¹è¯æ¡†æ ‡é¢˜çš„ç§æœ‰æ–¹æ³• + private void updateTitle(long date) { + int flag = + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; + flag |= mIs24HourView? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); + } + + // 实现 OnClickListener 接å£çš„æ–¹æ³•,当点击确定按钮时调用 + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener!= null) { + // 调用日期时间设置监å¬å™¨çš„æ–¹æ³• + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } + + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/DropdownMenu.java b/src/app/src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..8b5430b 100644 --- a/src/app/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/src/app/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -14,48 +14,60 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnMenuItemClickListener; - -import net.micode.notes.R; - -public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; - - 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(); - } - }); - } - - public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { - if (mPopupMenu != null) { - mPopupMenu.setOnMenuItemClickListener(listener); - } - } - - public MenuItem findItem(int id) { - return mMenu.findItem(id); - } - - public void setTitle(CharSequence title) { - mButton.setText(title); - } -} + import android.content.Context; + import android.view.Menu; + import android.view.MenuItem; + import android.view.View; + import android.view.View.OnClickListener; + import android.widget.Button; + import android.widget.PopupMenu; + import android.widget.PopupMenu.OnMenuItemClickListener; + + import net.micode.notes.R; + + // 定义一个å为 DropdownMenu 的类 + public class DropdownMenu { + // 下拉èœå•的按钮 + private Button mButton; + // 弹出èœå•对象 + private PopupMenu mPopupMenu; + // èœå•对象 + private Menu mMenu; + + // 构造函数,接å—ä¸Šä¸‹æ–‡ã€æŒ‰é’®å’Œèœå•èµ„æº 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(); + // 从给定的èœå•èµ„æº ID 中填充èœå• + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 为按钮设置点击监å¬å™¨ï¼Œç‚¹å‡»æ—¶æ˜¾ç¤ºå¼¹å‡ºèœå• + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); + } + }); + } + + // 设置弹出èœå•项点击监å¬å™¨çš„æ–¹æ³• + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu!= null) { + mPopupMenu.setOnMenuItemClickListener(listener); + } + } + + // æ ¹æ® ID 查找èœå•项的方法 + public MenuItem findItem(int id) { + return mMenu.findItem(id); + } + + // 设置按钮标题的方法 + public void setTitle(CharSequence title) { + mButton.setText(title); + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/src/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..89935fa 100644 --- a/src/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -14,67 +14,79 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.database.Cursor; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; - - -public class FoldersListAdapter extends CursorAdapter { - public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET - }; - - public static final int ID_COLUMN = 0; - public static final int NAME_COLUMN = 1; - - public FoldersListAdapter(Context context, Cursor c) { - super(context, c); - // TODO Auto-generated constructor stub - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new FolderListItem(context); - } - - @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 + import android.content.Context; + import android.database.Cursor; + import android.view.View; + import android.view.ViewGroup; + import android.widget.CursorAdapter; + import android.widget.LinearLayout; + import android.widget.TextView; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + + // 定义一个å为 FoldersListAdapter 的类,继承自 CursorAdapter + public class FoldersListAdapter extends CursorAdapter { + // å®šä¹‰æŸ¥è¯¢æŠ•å½±ï¼ŒåŒ…å« ID 和片段(SNIPPET)列 + public static final String[] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + }; + // ID 列的索引 + public static final int ID_COLUMN = 0; + // å称列的索引(实际上是片段列在这个上下文中用作å称) + public static final int NAME_COLUMN = 1; + + // 构造函数,接å—上下文和游标 + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub + } + + // 创建新视图的方法 + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new FolderListItem(context); + } + + // 绑定数æ®åˆ°è§†å›¾çš„æ–¹æ³• + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof FolderListItem) { + // æ ¹æ® ID 判断是å¦ä¸ºæ ¹æ–‡ä»¶å¤¹ï¼Œè®¾ç½®ä¸åŒçš„æ–‡ä»¶å¤¹åç§° + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - ((FolderListItem) view).bind(folderName); - } - } - - public String getFolderName(Context context, int position) { - Cursor cursor = (Cursor) getItem(position); - return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + ((FolderListItem) view).bind(folderName); + } + } + + // èŽ·å–æŒ‡å®šä½ç½®çš„æ–‡ä»¶å¤¹å称的方法 + public String getFolderName(Context context, int position) { + Cursor cursor = (Cursor) getItem(position); + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - } - - private class FolderListItem extends LinearLayout { - private TextView mName; - - public FolderListItem(Context context) { - super(context); - inflate(context, R.layout.folder_list_item, this); - mName = (TextView) findViewById(R.id.tv_folder_name); - } - - public void bind(String name) { - mName.setText(name); - } - } - -} + } + + // 内部类 FolderListItem,代表列表中的å•个文件夹项视图 + private class FolderListItem extends LinearLayout { + private TextView mName; + + // 构造函数,接å—上下文 + public FolderListItem(Context context) { + super(context); + // 从布局文件中填充视图 + inflate(context, R.layout.folder_list_item, this); + // èŽ·å–æ˜¾ç¤ºæ–‡ä»¶å¤¹åç§°çš„ TextView + mName = (TextView) findViewById(R.id.tv_folder_name); + } + + // 绑定文件夹å称到视图的方法 + public void bind(String name) { + mName.setText(name); + } + } + + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..908e652 100644 --- a/src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -14,377 +14,334 @@ * limitations under the License. */ -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; - - -public class NoteEditActivity extends Activity implements OnClickListener, - NoteSettingChangedListener, OnTextViewChangeListener { - private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvAlertDate; - - public ImageView ibSetBgColor; - } - - private static final Map sBgSelectorBtnsMap = new HashMap(); - static { - sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); - sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); - sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); - sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); - sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); - } - - private static final Map sBgSelectorSelectionMap = new HashMap(); - static { - sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); - sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); - sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); - sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); - sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); - } - - private static final Map sFontSizeBtnsMap = new HashMap(); - static { - sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); - sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); - sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); - sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); - } - - private static final Map sFontSelectorSelectionMap = new HashMap(); - static { - sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); - sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); - sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); - sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); - } - - private static final String TAG = "NoteEditActivity"; - - private 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; - - private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; - - private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; - - public static final String TAG_CHECKED = String.valueOf('\u221A'); - public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); - - private LinearLayout mEditTextList; - - private String mUserQuery; - private Pattern mPattern; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.setContentView(R.layout.note_edit); - - if (savedInstanceState == null && !initActivityState(getIntent())) { - finish(); - return; - } - initResources(); - } - - /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state - */ - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); - if (!initActivityState(intent)) { - finish(); - return; - } - Log.d(TAG, "Restoring from killed activity"); - } - } - - private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ - mWorkingNote = null; - if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { - long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); - mUserQuery = ""; - - /** - * Starting from the searched result - */ - if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { - noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); - mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); - } - - if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { - Intent jump = new Intent(this, NotesListActivity.class); - startActivity(jump); - showToast(R.string.error_note_not_exist); - finish(); - return false; - } else { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load note failed with note id" + noteId); - finish(); - return false; - } - } - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // New note - long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); - int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, - Notes.TYPE_WIDGET_INVALIDE); - int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, - ResourceParser.getDefaultBgId(this)); - - // Parse call-record note - String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); - if (callDate != 0 && phoneNumber != null) { - if (TextUtils.isEmpty(phoneNumber)) { - Log.w(TAG, "The call record number is null"); - } - long noteId = 0; - if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), - phoneNumber, callDate)) > 0) { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load call note failed with note id" + noteId); - finish(); - return false; - } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, - widgetType, bgResId); - mWorkingNote.convertToCallNote(phoneNumber, callDate); - } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, - bgResId); - } - - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } else { - Log.e(TAG, "Intent not specified action, should not support"); - finish(); - return false; - } - mWorkingNote.setOnSettingStatusChangedListener(this); - return true; - } - - @Override - protected void onResume() { - super.onResume(); - initNoteScreen(); - } - - private void initNoteScreen() { - mNoteEditor.setTextAppearance(this, TextAppearanceResources + 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; + + // 定义 NoteEditActivity 类,用于编辑笔记的活动 + public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener { + // 内部类,用于ä¿å­˜ç¬”è®°å¤´éƒ¨è§†å›¾çš„æŒæœ‰è€… + private class HeadViewHolder { + public TextView tvModified; + public ImageView ivAlertIcon; + public TextView tvAlertDate; + public ImageView ibSetBgColor; + } + + // èƒŒæ™¯é¢œè‰²é€‰æ‹©æŒ‰é’®å’Œå¯¹åº”çš„èµ„æº ID 的映射 + private static final Map sBgSelectorBtnsMap = new HashMap(); + static { + sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); + sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); + sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); + } + + // 背景颜色选择åŽçš„æ˜¾ç¤º ID 的映射 + private static final Map sBgSelectorSelectionMap = new HashMap(); + static { + sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); + sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); + sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); + sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); + sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); + } + + // 字体大å°é€‰æ‹©æŒ‰é’®å’Œå¯¹åº”çš„èµ„æº ID 的映射 + private static final Map sFontSizeBtnsMap = new HashMap(); + static { + sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); + sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); + sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); + sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); + } + + // 字体大å°é€‰æ‹©åŽçš„æ˜¾ç¤º ID 的映射 + private static final Map sFontSelectorSelectionMap = new HashMap(); + static { + sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); + } + + private static final String TAG = "NoteEditActivity"; + + // ç¬”è®°å¤´éƒ¨è§†å›¾çš„æŒæœ‰è€…实例 + private HeadViewHolder mNoteHeaderHolder; + + // ç¬”è®°å¤´éƒ¨è§†å›¾é¢æ¿ + private View mHeadViewPanel; + + // 笔记背景颜色选择器视图 + private View mNoteBgColorSelector; + + // 字体大å°é€‰æ‹©å™¨è§†å›¾ + private View mFontSizeSelector; + + // 笔记编辑框 + private EditText mNoteEditor; + + // ç¬”è®°ç¼–è¾‘é¢æ¿è§†å›¾ + private View mNoteEditorPanel; + + // 正在编辑的笔记对象 + private WorkingNote mWorkingNote; + + // 共享å好设置对象 + private SharedPreferences mSharedPrefs; + + // 当å‰å­—ä½“å¤§å° ID + private int mFontSizeId; + + // 共享å好设置中的字体大å°é”®å + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + + // å¿«æ·æ–¹å¼å›¾æ ‡æ ‡é¢˜çš„æœ€å¤§é•¿åº¦ + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + + // 笔记中已勾选项的标记 + public static final String TAG_CHECKED = String.valueOf('\u221A'); + // 笔记中未勾选项的标记 + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + + // 线性布局,用于容纳多个编辑框(列表模å¼ä¸‹ï¼‰ + private LinearLayout mEditTextList; + + // 用户查询字符串 + private String mUserQuery; + + // æ­£åˆ™è¡¨è¾¾å¼æ¨¡å¼å¯¹è±¡ï¼Œç”¨äºŽæœç´¢ç”¨æˆ·æŸ¥è¯¢å­—符串 + private Pattern mPattern; + + // 活动创建时调用的方法 + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 设置布局文件 + this.setContentView(R.layout.note_edit); + + // 如果ä¿å­˜çš„状æ€ä¸ºç©ºä¸”åˆå§‹åŒ–活动状æ€å¤±è´¥ï¼Œåˆ™ç»“æŸæ´»åЍ + if (savedInstanceState == null &&!initActivityState(getIntent())) { + finish(); + return; + } + + // åˆå§‹åŒ–èµ„æº + initResources(); + } + + // å½“æ´»åŠ¨ä»Žè¢«æ€æ­»çš„çŠ¶æ€æ¢å¤æ—¶è°ƒç”¨çš„æ–¹æ³• + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + // 如果ä¿å­˜çš„状æ€ä¸ä¸ºç©ºä¸”包å«ç¬”è®° ID,则å°è¯•æ¢å¤æ´»åŠ¨çŠ¶æ€ + if (savedInstanceState!= null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + if (!initActivityState(intent)) { + finish(); + return; + } + Log.d(TAG, "Restoring from killed activity"); + } + } + + // åˆå§‹åŒ–活动状æ€çš„æ–¹æ³•ï¼Œæ ¹æ®æ„图确定è¦ç¼–辑的笔记 + private boolean initActivityState(Intent intent) { + mWorkingNote = null; + // 如果æ„图的动作是查看笔记 + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); + mUserQuery = ""; + + // 如果是从æœç´¢ç»“æžœå¯åŠ¨çš„ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + } + + // 如果笔记在数æ®åº“中ä¸å¯è§ï¼Œåˆ™è·³è½¬åˆ° NotesListActivity å¹¶æ˜¾ç¤ºé”™è¯¯ä¿¡æ¯ + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { + Intent jump = new Intent(this, NotesListActivity.class); + startActivity(jump); + showToast(R.string.error_note_not_exist); + finish(); + return false; + } else { + // 加载è¦ç¼–辑的笔记 + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load note failed with note id" + noteId); + finish(); + return false; + } + } + // 设置软键盘状æ€ä¸ºéšè—且调整布局 + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // 创建新笔记的情况 + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE); + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this)); + + // 处ç†é€šè¯è®°å½•笔记 + String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); + long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); + if (callDate!= 0 && phoneNumber!= null) { + if (TextUtils.isEmpty(phoneNumber)) { + Log.w(TAG, "The call record number is null"); + } + long noteId = 0; + if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), + phoneNumber, callDate)) > 0) { + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load call note failed with note id" + noteId); + finish(); + return false; + } + } else { + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, + widgetType, bgResId); + mWorkingNote.convertToCallNote(phoneNumber, callDate); + } + } else { + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, + bgResId); + } + + // 设置软键盘状æ€ä¸ºå¯è§ä¸”调整布局 + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else { + Log.e(TAG, "Intent not specified action, should not support"); + finish(); + return false; + } + // è®¾ç½®ç¬”è®°è®¾ç½®çŠ¶æ€æ”¹å˜ç›‘å¬å™¨ + mWorkingNote.setOnSettingStatusChangedListener(this); + return true; + } + + // 活动æ¢å¤æ—¶è°ƒç”¨çš„æ–¹æ³• + @Override + protected void onResume() { + super.onResume(); + // åˆå§‹åŒ–笔记å±å¹•显示 + initNoteScreen(); + } + + // åˆå§‹åŒ–笔记å±å¹•显示的方法 + private void initNoteScreen() { + // 设置笔记编辑框的文本外观 + mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); - } - for (Integer id : sBgSelectorSelectionMap.keySet()) { - findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); - } - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - - mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, - mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR)); - - /** - * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready - */ - showAlertHeader(); - } - - private void showAlertHeader() { - if (mWorkingNote.hasClockAlert()) { - long time = System.currentTimeMillis(); - if (time > mWorkingNote.getAlertDate()) { - mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); - } else { - mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( - mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); - } - mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); - } else { - mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - }; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - initActivityState(intent); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mNoteBgColorSelector, ev)) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } - - if (mFontSizeSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mFontSizeSelector, ev)) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return super.dispatchTouchEvent(ev); - } - - private boolean inRangeOfView(View view, MotionEvent ev) { - int []location = new int[2]; - view.getLocationOnScreen(location); - int x = location[0]; - int y = location[1]; - if (ev.getX() < x - || ev.getX() > (x + view.getWidth()) - || ev.getY() < y - || ev.getY() > (y + view.getHeight())) { - return false; - } - return true; - } - - private void initResources() { - mHeadViewPanel = findViewById(R.id.note_title); - mNoteHeaderHolder = new HeadViewHolder(); - mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); - mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); - mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + // å¦‚æžœç¬”è®°å¤„äºŽæ¸…å•æ¨¡å¼ + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 将笔记内容转æ¢ä¸ºåˆ—è¡¨æ¨¡å¼æ˜¾ç¤º + switchToListMode(mWorkingNote.getContent()); + } else { + // çªå‡ºæ˜¾ç¤ºæŸ¥è¯¢ç»“果并设置编辑框文本和光标ä½ç½® + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mNoteEditor.setSelection(mNoteEditor.getText().length()); + } + // éšè—所有背景颜色选择åŽçš„æ˜¾ç¤º + for (Integer id : sBgSelectorSelectionMap.keySet()) { + findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); + } + // è®¾ç½®å¤´éƒ¨é¢æ¿å’Œç¼–è¾‘é¢æ¿çš„èƒŒæ™¯èµ„æº + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + + // 设置修改日期显示文本 mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + // 设置背景颜色设置按钮的点击监å¬å™¨ mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + // 获å–笔记编辑框实例 mNoteEditor = (EditText) findViewById(R.id.note_edit_view); + // 获å–ç¬”è®°ç¼–è¾‘é¢æ¿è§†å›¾å®žä¾‹ mNoteEditorPanel = findViewById(R.id.sv_note_edit); + // 获å–笔记背景颜色选择器视图实例 mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + // 为所有背景颜色选择按钮设置点击监å¬å™¨ for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } + // 获å–字体大å°é€‰æ‹©å™¨è§†å›¾å®žä¾‹ mFontSizeSelector = findViewById(R.id.font_size_selector); + // 为所有字体大å°é€‰æ‹©æŒ‰é’®è®¾ç½®ç‚¹å‡»ç›‘å¬å™¨ for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); }; + // 获å–共享å好设置实例 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + // 获å–当å‰å­—ä½“å¤§å° ID 从共享å好设置中 mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); /** * HACKME: Fix bug of store the resource id in shared preference. @@ -394,20 +351,26 @@ public class NoteEditActivity extends Activity implements OnClickListener, if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } + // 获å–线性布局实例,用于容纳多个编辑框(列表模å¼ä¸‹ï¼‰ mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } + // æš‚åœæ´»åŠ¨æ—¶è°ƒç”¨çš„æ–¹æ³• @Override protected void onPause() { super.onPause(); + // ä¿å­˜ç¬”记,如果ä¿å­˜æˆåŠŸåˆ™è®°å½•æ—¥å¿— if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } + // æ¸…é™¤è®¾ç½®çŠ¶æ€ clearSettingState(); } + // æ›´æ–°å°éƒ¨ä»¶çš„æ–¹æ³• private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // æ ¹æ®å°éƒ¨ä»¶ç±»åž‹è®¾ç½®æ„图的类 if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { intent.setClass(this, NoteWidgetProvider_2x.class); } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { @@ -421,82 +384,115 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.getWidgetId() }); + // å‘é€å¹¿æ’­æ›´æ–°å°éƒ¨ä»¶ sendBroadcast(intent); + // 设置结果ç ä¸º RESULT_OK,并将æ„图作为结果返回 setResult(RESULT_OK, intent); } + // ç‚¹å‡»äº‹ä»¶å¤„ç†æ–¹æ³• public void onClick(View v) { int id = v.getId(); + // 如果点击了背景颜色设置按钮 if (id == R.id.btn_set_bg_color) { + // 显示背景颜色选择器 mNoteBgColorSelector.setVisibility(View.VISIBLE); + // 显示当å‰é€‰æ‹©çš„èƒŒæ™¯é¢œè‰²çš„é€‰ä¸­çŠ¶æ€ findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); + View.VISIBLE); } else if (sBgSelectorBtnsMap.containsKey(id)) { + // éšè—之å‰é€‰æ‹©çš„èƒŒæ™¯é¢œè‰²çš„é€‰ä¸­çŠ¶æ€ findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); + // 设置新的背景颜色 ID mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); + // éšè—背景颜色选择器 mNoteBgColorSelector.setVisibility(View.GONE); } else if (sFontSizeBtnsMap.containsKey(id)) { + // éšè—之å‰é€‰æ‹©çš„字体大å°çš„é€‰ä¸­çŠ¶æ€ findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + // è®¾ç½®æ–°çš„å­—ä½“å¤§å° ID mFontSizeId = sFontSizeBtnsMap.get(id); + // å°†æ–°çš„å­—ä½“å¤§å° ID ä¿å­˜åˆ°å…±äº«å好设置中 mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); + // 显示新选择的字体大å°çš„é€‰ä¸­çŠ¶æ€ findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 获å–工作文本(在列表模å¼ä¸‹ï¼‰ getWorkingText(); + // 将笔记内容转æ¢ä¸ºåˆ—è¡¨æ¨¡å¼æ˜¾ç¤º switchToListMode(mWorkingNote.getContent()); } else { + // è®¾ç½®ç¬”è®°ç¼–è¾‘æ¡†çš„æ–‡æœ¬å¤–è§‚ä¸ºæ–°çš„å­—ä½“å¤§å° mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } + // éšè—字体大å°é€‰æ‹©å™¨ mFontSizeSelector.setVisibility(View.GONE); } } + // 按下返回键时调用的方法 @Override public void onBackPressed() { + // å¦‚æžœæ¸…é™¤è®¾ç½®çŠ¶æ€æˆåŠŸï¼Œåˆ™ç›´æŽ¥è¿”å›žï¼Œä¸æ‰§è¡Œé»˜è®¤çš„返回æ“作 if(clearSettingState()) { return; } + // ä¿å­˜ç¬”è®° saveNote(); + // 执行默认的返回æ“作 super.onBackPressed(); } + // 清除设置状æ€çš„æ–¹æ³• private boolean clearSettingState() { + // 如果背景颜色选择器å¯è§ï¼Œåˆ™éšè—它并返回 true if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + // 如果字体大å°é€‰æ‹©å™¨å¯è§ï¼Œåˆ™éšè—它并返回 true mFontSizeSelector.setVisibility(View.GONE); return true; } return false; } + // å½“èƒŒæ™¯é¢œè‰²æ”¹å˜æ—¶è°ƒç”¨çš„æ–¹æ³• public void onBackgroundColorChanged() { + // 显示当å‰é€‰æ‹©çš„èƒŒæ™¯é¢œè‰²çš„é€‰ä¸­çŠ¶æ€ findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); + // è®¾ç½®ç¼–è¾‘é¢æ¿å’Œå¤´éƒ¨é¢æ¿çš„背景资æºä¸ºæ–°çš„背景颜色 mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + // 准备选项èœå•时调用的方法 @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { return true; } + // æ¸…é™¤è®¾ç½®çŠ¶æ€ clearSettingState(); menu.clear(); + // æ ¹æ®ç¬”记类型设置ä¸åŒçš„èœå•èµ„æº if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); } + // å¦‚æžœç¬”è®°å¤„äºŽæ¸…å•æ¨¡å¼ï¼Œåˆ™è®¾ç½®èœå•项标题为切æ¢åˆ°æ™®é€šæ¨¡å¼ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { + // 如果笔记处于普通模å¼ï¼Œåˆ™è®¾ç½®èœå•项标题为切æ¢åˆ°æ¸…啿¨¡å¼ menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } + // æ ¹æ®æ˜¯å¦æœ‰æé†’设置ä¸åŒçš„èœå•项å¯è§æ€§ if (mWorkingNote.hasClockAlert()) { menu.findItem(R.id.menu_alert).setVisible(false); } else { @@ -505,13 +501,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + // 选项èœå•项被选择时调用的方法 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_note: + // 创建新笔记 createNewNote(); break; case R.id.menu_delete: + // æ˜¾ç¤ºåˆ é™¤ç¡®è®¤å¯¹è¯æ¡† AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -519,6 +518,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { + // 删除当å‰ç¬”è®°å¹¶ç»“æŸæ´»åЍ deleteCurrentNote(); finish(); } @@ -527,24 +527,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, builder.show(); break; case R.id.menu_font_size: + // 显示字体大å°é€‰æ‹©å™¨ mFontSizeSelector.setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: - mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? + // 切æ¢ç¬”è®°çš„æ¸…å•æ¨¡å¼çŠ¶æ€ + mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: + // 获å–工作文本并分享笔记内容 getWorkingText(); sendTo(this, mWorkingNote.getContent()); break; case R.id.menu_send_to_desktop: + // 将笔记å‘é€åˆ°æ¡Œé¢ sendToDesktop(); break; case R.id.menu_alert: + // 设置æé†’ setReminder(); break; case R.id.menu_delete_remind: + // 删除æé†’ mWorkingNote.setAlertDate(0, false); break; default: @@ -553,11 +559,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + // 设置æé†’的方法 private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { public void OnDateTimeSet(AlertDialog dialog, long date) { - mWorkingNote.setAlertDate(date , true); + // 设置笔记的æé†’日期 + mWorkingNote.setAlertDate(date, true); } }); d.show(); @@ -574,11 +582,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + // 创建新笔记的方法 private void createNewNote() { - // Firstly, save current editing notes + // 首先ä¿å­˜å½“剿­£åœ¨ç¼–辑的笔记 saveNote(); - // For safety, start a new NoteEditActivity + // 为了安全起è§ï¼Œå¯åŠ¨ä¸€ä¸ªæ–°çš„ NoteEditActivity finish(); Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -586,32 +595,38 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + // 删除当å‰ç¬”记的方法 private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) { + if (id!= Notes.ID_ROOT_FOLDER) { ids.add(id); } else { Log.d(TAG, "Wrong note id, should not happen"); } if (!isSyncMode()) { + // å¦‚æžœä¸æ˜¯åŒæ­¥æ¨¡å¼ï¼Œåˆ™ç›´æŽ¥åˆ é™¤ç¬”è®° if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { Log.e(TAG, "Delete Note error"); } } else { + // å¦‚æžœæ˜¯åŒæ­¥æ¨¡å¼ï¼Œåˆ™å°†ç¬”记移动到回收站文件夹 if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); } } } + // 标记笔记为已删除 mWorkingNote.markDeleted(true); } + // 判断是å¦ä¸ºåŒæ­¥æ¨¡å¼çš„æ–¹æ³• private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + // 当时钟æé†’æ”¹å˜æ—¶è°ƒç”¨çš„æ–¹æ³• public void onClockAlertChanged(long date, boolean set) { /** * User could set clock to an unsaved note, so before setting the @@ -625,10 +640,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + // 显示æé†’å¤´éƒ¨ä¿¡æ¯ showAlertHeader(); if(!set) { + // å¦‚æžœå–æ¶ˆæé†’ï¼Œåˆ™å–æ¶ˆé—¹é’Ÿ alarmManager.cancel(pendingIntent); } else { + // 如果设置æé†’,则设置闹钟 alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { @@ -638,14 +656,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, * should input something */ Log.e(TAG, "Clock alert setting error"); + // 显示æç¤ºä¿¡æ¯ï¼Œè¡¨ç¤ºç¬”记为空无法设置闹钟 showToast(R.string.error_note_empty_for_clock); } } + // 当å°éƒ¨ä»¶æ”¹å˜æ—¶è°ƒç”¨çš„æ–¹æ³• public void onWidgetChanged() { updateWidget(); } + // 当编辑框中的文本被删除时调用的方法 public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { @@ -654,7 +675,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i - 1); + .setIndex(i - 1); } mEditTextList.removeViewAt(index); @@ -672,6 +693,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.setSelection(length); } + // 当编辑框中按下回车键时调用的方法 public void onEditTextEnter(int index, String text) { /** * Should not happen, check for debug @@ -687,10 +709,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.setSelection(0); for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i); + .setIndex(i); } } + // 将笔记内容转æ¢ä¸ºåˆ—è¡¨æ¨¡å¼æ˜¾ç¤ºçš„æ–¹æ³• private void switchToListMode(String text) { mEditTextList.removeAllViews(); String[] items = text.split("\n"); @@ -708,166 +731,173 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList.setVisibility(View.VISIBLE); } + // 获å–çªå‡ºæ˜¾ç¤ºæŸ¥è¯¢ç»“果的 Spannable 对象的方法 private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + SpannableString spannable = new SpannableString(fullText == null? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { mPattern = Pattern.compile(userQuery); Matcher m = mPattern.matcher(fullText); int start = 0; - while (m.find(start)) { - spannable.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - start = m.end(); - } - } - return spannable; - } - - private View getListItem(String item, int index) { - View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); - final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); - cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - } - } - }); - - if (item.startsWith(TAG_CHECKED)) { - cb.setChecked(true); - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - item = item.substring(TAG_CHECKED.length(), item.length()).trim(); - } else if (item.startsWith(TAG_UNCHECKED)) { - cb.setChecked(false); - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); - } + while (m.find(start m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end(); +} +} +return spannable; +} - edit.setOnTextViewChangeListener(this); - edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); - return view; +// 获å–列表项视图的方法 +private View getListItem(String item, int index) { +View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); +final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); +edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); +CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); +cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { +public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); } +} +}); + +if (item.startsWith(TAG_CHECKED)) { +cb.setChecked(true); +edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); +item = item.substring(TAG_CHECKED.length(), item.length()).trim(); +} else if (item.startsWith(TAG_UNCHECKED)) { +cb.setChecked(false); +edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); +item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); +} - public void onTextChange(int index, boolean hasText) { - if (index >= mEditTextList.getChildCount()) { - Log.e(TAG, "Wrong index, should not happen"); - return; - } - if(hasText) { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); - } else { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); - } - } +edit.setOnTextViewChangeListener(this); +edit.setIndex(index); +edit.setText(getHighlightQueryResult(item, mUserQuery)); +return view; +} - public void onCheckListModeChanged(int oldMode, int newMode) { - if (newMode == TextNote.MODE_CHECK_LIST) { - switchToListMode(mNoteEditor.getText().toString()); - } else { - if (!getWorkingText()) { - mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", - "")); - } - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mEditTextList.setVisibility(View.GONE); - mNoteEditor.setVisibility(View.VISIBLE); - } - } +// å½“ç¼–è¾‘æ¡†ä¸­çš„æ–‡æœ¬æ”¹å˜æ—¶è°ƒç”¨çš„æ–¹æ³• +public void onTextChange(int index, boolean hasText) { +if (index >= mEditTextList.getChildCount()) { +Log.e(TAG, "Wrong index, should not happen"); +return; +} +if(hasText) { +mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); +} else { +mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); +} +} - private boolean getWorkingText() { - boolean hasChecked = false; - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mEditTextList.getChildCount(); i++) { - View view = mEditTextList.getChildAt(i); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - if (!TextUtils.isEmpty(edit.getText())) { - if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { - sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); - hasChecked = true; - } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); - } - } - } - mWorkingNote.setWorkingText(sb.toString()); +// å½“æ¸…å•æ¨¡å¼æ”¹å˜æ—¶è°ƒç”¨çš„æ–¹æ³• +public void onCheckListModeChanged(int oldMode, int newMode) { +if (newMode == TextNote.MODE_CHECK_LIST) { +switchToListMode(mNoteEditor.getText().toString()); +} else { +if (!getWorkingText()) { + mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", + "")); +} +mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); +mEditTextList.setVisibility(View.GONE); +mNoteEditor.setVisibility(View.VISIBLE); +} +} + +// 获å–工作文本的方法 +private boolean getWorkingText() { +boolean hasChecked = false; +if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { +StringBuilder sb = new StringBuilder(); +for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + if (!TextUtils.isEmpty(edit.getText())) { + if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + hasChecked = true; } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); } - return hasChecked; } +} +mWorkingNote.setWorkingText(sb.toString()); +} else { +mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); +} +return hasChecked; +} - private boolean saveNote() { - getWorkingText(); - boolean saved = mWorkingNote.saveNote(); - if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ - setResult(RESULT_OK); - } - return saved; - } +// ä¿å­˜ç¬”记的方法 +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(); - } +// 将笔记å‘é€åˆ°æ¡Œé¢çš„æ–¹æ³• +private void sendToDesktop() { +/** +* Before send message to home, we should make sure that current +* editing note is exists in databases. So, for new note, firstly +* save it +*/ +if (!mWorkingNote.existInDatabase()) { +saveNote(); +} - if (mWorkingNote.getNoteId() > 0) { - Intent sender = new Intent(); - Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent())); - sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); - sender.putExtra("duplicate", true); - sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - showToast(R.string.info_note_enter_desktop); - sendBroadcast(sender); - } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Send to desktop error"); - showToast(R.string.error_note_empty_for_send_to_desktop); - } - } +if (mWorkingNote.getNoteId() > 0) { +Intent sender = new Intent(); +Intent shortcutIntent = new Intent(this, NoteEditActivity.class); +shortcutIntent.setAction(Intent.ACTION_VIEW); +shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); +sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); +sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent())); +sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); +sender.putExtra("duplicate", true); +sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); +showToast(R.string.info_note_enter_desktop); +sendBroadcast(sender); +} else { +/** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ +Log.e(TAG, "Send to desktop error"); +showToast(R.string.error_note_empty_for_send_to_desktop); +} +} - private String makeShortcutIconTitle(String content) { - content = content.replace(TAG_CHECKED, ""); - content = content.replace(TAG_UNCHECKED, ""); - return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, - SHORTCUT_ICON_TITLE_MAX_LEN) : content; - } +// 生æˆå¿«æ·æ–¹å¼å›¾æ ‡æ ‡é¢˜çš„æ–¹æ³• +private String makeShortcutIconTitle(String content) { +content = content.replace(TAG_CHECKED, ""); +content = content.replace(TAG_UNCHECKED, ""); +return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN? content.substring(0, + SHORTCUT_ICON_TITLE_MAX_LEN) : content; +} - private void showToast(int resId) { - showToast(resId, Toast.LENGTH_SHORT); - } +// 显示å叿¶ˆæ¯çš„æ–¹æ³• +private void showToast(int resId) { +showToast(resId, Toast.LENGTH_SHORT); +} - private void showToast(int resId, int duration) { - Toast.makeText(this, resId, duration).show(); - } +// 显示å叿¶ˆæ¯çš„é‡è½½æ–¹æ³•ï¼Œå¯æŒ‡å®šæ—¶é•¿ +private void showToast(int resId, int duration) { +Toast.makeText(this, resId, duration).show(); } +} \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 2afe2a8..0158fe6 100644 --- a/src/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -14,204 +14,218 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.content.Context; -import android.graphics.Rect; -import android.text.Layout; -import android.text.Selection; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.URLSpan; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; -import android.view.MotionEvent; -import android.widget.EditText; - -import net.micode.notes.R; - -import java.util.HashMap; -import java.util.Map; - -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:" ; - - private static final Map sSchemaActionResMap = new HashMap(); - static { - sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); - sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); - sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); - } - - /** - * Call by the {@link NoteEditActivity} to delete or add edit text - */ - public interface OnTextViewChangeListener { - /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null - */ - void onEditTextDelete(int index, String text); - - /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen - */ - void onEditTextEnter(int index, String text); - - /** - * Hide or show item option when text change - */ - void onTextChange(int index, boolean hasText); - } - - private OnTextViewChangeListener mOnTextViewChangeListener; - - public NoteEditText(Context context) { - super(context, null); - mIndex = 0; - } - - public void setIndex(int index) { - mIndex = index; - } - - public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { - mOnTextViewChangeListener = listener; - } - - public NoteEditText(Context context, AttributeSet attrs) { - super(context, attrs, android.R.attr.editTextStyle); - } - - public NoteEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - - int x = (int) event.getX(); - int y = (int) event.getY(); - x -= getTotalPaddingLeft(); - y -= getTotalPaddingTop(); - x += getScrollX(); - y += getScrollY(); - - Layout layout = getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - Selection.setSelection(getText(), off); - break; - } - - return super.onTouchEvent(event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { - return false; - } - break; - case KeyEvent.KEYCODE_DEL: - mSelectionStartBeforeDelete = getSelectionStart(); - break; - default: - break; - } - return super.onKeyDown(keyCode, event); - } - - @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()); - return true; - } - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - } - 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"); - } - break; - default: - break; - } - return super.onKeyUp(keyCode, event); - } - - @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); - } - - @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); - - final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); - if (urls.length == 1) { - int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - if(urls[0].getURL().indexOf(schema) >= 0) { - defaultResId = sSchemaActionResMap.get(schema); - break; - } - } - - 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; - } - }); - } - } - super.onCreateContextMenu(menu); - } -} + package net.micode.notes.ui; + + import android.content.Context; + import android.graphics.Rect; + import android.text.Layout; + import android.text.Selection; + import android.text.Spanned; + import android.text.TextUtils; + import android.text.style.URLSpan; + import android.util.AttributeSet; + import android.util.Log; + import android.view.ContextMenu; + import android.view.KeyEvent; + import android.view.MenuItem; + import android.view.MenuItem.OnMenuItemClickListener; + import android.view.MotionEvent; + import android.widget.EditText; + + import net.micode.notes.R; + + import java.util.HashMap; + import java.util.Map; + + // 定义一个å为 NoteEditText 的自定义 EditText ç±» + public class NoteEditText extends EditText { + private static final String TAG = "NoteEditText"; + // å½“å‰ EditText 的索引 + 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(); + static { + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + // 定义接å£ï¼Œç”¨äºŽåœ¨ NoteEditActivity ä¸­ç›‘å¬ EditText çš„å˜åŒ– + public interface OnTextViewChangeListener { + // å½“æŒ‰ä¸‹åˆ é™¤é”®ä¸”æ–‡æœ¬ä¸ºç©ºæ—¶ï¼Œåˆ é™¤å½“å‰ EditText + void onEditTextDelete(int index, String text); + + // å½“æŒ‰ä¸‹å›žè½¦é”®æ—¶ï¼Œåœ¨å½“å‰ EditText åŽæ·»åŠ ä¸€ä¸ªæ–°çš„ EditText + void onEditTextEnter(int index, String text); + + // æ ¹æ®æ–‡æœ¬æ˜¯å¦å­˜åœ¨æ˜¾ç¤ºæˆ–éšè—选项 + void onTextChange(int index, boolean hasText); + } + + // ç”¨äºŽç›‘å¬æ–‡æœ¬å˜åŒ–的接å£å®žä¾‹ + private OnTextViewChangeListener mOnTextViewChangeListener; + + // 构造函数,接å—上下文 + public NoteEditText(Context context) { + super(context, null); + mIndex = 0; + } + + // è®¾ç½®å½“å‰ EditText 的索引 + public void setIndex(int index) { + mIndex = index; + } + + // 设置文本å˜åŒ–监å¬å™¨ + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + + // 构造函数,接å—上下文和属性集 + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + // 构造函数,接å—上下文ã€å±žæ€§é›†å’Œæ ·å¼ + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + // 处ç†è§¦æ‘¸äº‹ä»¶ + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // 获å–è§¦æ‘¸ç‚¹åæ ‡å¹¶è¿›è¡Œè°ƒæ•´ + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= getTotalPaddingLeft(); + y -= getTotalPaddingTop(); + x += getScrollX(); + y += getScrollY(); + + Layout layout = getLayout(); + // 获å–触摸点所在的行 + int line = layout.getLineForVertical(y); + // 获å–触摸点在行中的åç§»é‡ + int off = layout.getOffsetForHorizontal(line, x); + // 设置选中文本 + Selection.setSelection(getText(), off); + break; + } + return super.onTouchEvent(event); + } + + // å¤„ç†æŒ‰é”®æŒ‰ä¸‹äº‹ä»¶ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener!= null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + // 记录删除æ“作å‰çš„选中文本起始ä½ç½® + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + } + return super.onKeyDown(keyCode, event); + } + + // å¤„ç†æŒ‰é”®æŠ¬èµ·äº‹ä»¶ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DEL: + if (mOnTextViewChangeListener!= null) { + // 如果起始ä½ç½®ä¸º 0 䏔䏿˜¯ç¬¬ä¸€ä¸ª EditText,则调用删除方法 + if (0 == mSelectionStartBeforeDelete && mIndex!= 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener!= null) { + int selectionStart = getSelectionStart(); + // 获å–选中文本åŽçš„剩余文本 + String text = getText().subSequence(selectionStart, length()).toString(); + // 设置 EditText 的文本为选中å‰çš„部分 + setText(getText().subSequence(0, selectionStart)); + // 调用添加新 EditText 的方法 + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + return super.onKeyUp(keyCode, event); + } + + // 处ç†ç„¦ç‚¹å˜åŒ–事件 + @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); + } + + // 创建上下文èœå• + @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); + + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + if (urls.length == 1) { + int defaultResId = 0; + for (String schema : sSchemaActionResMap.keySet()) { + if (urls[0].getURL().indexOf(schema) >= 0) { + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { + defaultResId = R.string.note_link_other; + } + + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // 点击èœå•项时触å‘链接的点击事件 + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/app/src/main/java/net/micode/notes/ui/NoteItemData.java index 0f5a878..a962107 100644 --- a/src/app/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/src/app/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -14,211 +14,247 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; - -import net.micode.notes.data.Contact; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.tool.DataUtils; - - -public class NoteItemData { - static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - }; - - private static final int ID_COLUMN = 0; - private static final int ALERTED_DATE_COLUMN = 1; - private static final int BG_COLOR_ID_COLUMN = 2; - private static final int CREATED_DATE_COLUMN = 3; - private static final int HAS_ATTACHMENT_COLUMN = 4; - private static final int MODIFIED_DATE_COLUMN = 5; - private static final int NOTES_COUNT_COLUMN = 6; - private static final int PARENT_ID_COLUMN = 7; - private static final int SNIPPET_COLUMN = 8; - private static final int TYPE_COLUMN = 9; - private static final int WIDGET_ID_COLUMN = 10; - private static final int WIDGET_TYPE_COLUMN = 11; - - private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private boolean mHasAttachment; - private long mModifiedDate; - private int mNotesCount; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private String mName; - private String mPhoneNumber; - - private boolean mIsLastItem; - private boolean mIsFirstItem; - private boolean mIsOnlyOneItem; - private boolean mIsOneNoteFollowingFolder; - private boolean mIsMultiNotesFollowingFolder; - - public NoteItemData(Context context, Cursor cursor) { - mId = cursor.getLong(ID_COLUMN); - mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); - mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); - mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); - mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; - mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); - mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); - mParentId = cursor.getLong(PARENT_ID_COLUMN); - mSnippet = cursor.getString(SNIPPET_COLUMN); - mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( - NoteEditActivity.TAG_UNCHECKED, ""); - mType = cursor.getInt(TYPE_COLUMN); - mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); - mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - - mPhoneNumber = ""; - if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { - mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); - if (!TextUtils.isEmpty(mPhoneNumber)) { - mName = Contact.getContact(context, mPhoneNumber); - if (mName == null) { - mName = mPhoneNumber; - } - } - } - - if (mName == null) { - mName = ""; - } - checkPostion(cursor); - } - - private void checkPostion(Cursor cursor) { - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; - mIsOnlyOneItem = (cursor.getCount() == 1); - mIsMultiNotesFollowingFolder = false; - mIsOneNoteFollowingFolder = false; - - if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { - int position = cursor.getPosition(); - if (cursor.moveToPrevious()) { - if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER - || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { - if (cursor.getCount() > (position + 1)) { - mIsMultiNotesFollowingFolder = true; - } else { - mIsOneNoteFollowingFolder = true; - } - } - if (!cursor.moveToNext()) { - throw new IllegalStateException("cursor move to previous but can't move back"); - } - } - } - } - - public boolean isOneFollowingFolder() { - return mIsOneNoteFollowingFolder; - } - - public boolean isMultiFollowingFolder() { - return mIsMultiNotesFollowingFolder; - } - - public boolean isLast() { - return mIsLastItem; - } - - public String getCallName() { - return mName; - } - - public boolean isFirst() { - return mIsFirstItem; - } - - public boolean isSingle() { - return mIsOnlyOneItem; - } - - public long getId() { - return mId; - } - - public long getAlertDate() { - return mAlertDate; - } - - public long getCreatedDate() { - return mCreatedDate; - } - - public boolean hasAttachment() { - return mHasAttachment; - } - - public long getModifiedDate() { - return mModifiedDate; - } - - public int getBgColorId() { - return mBgColorId; - } - - public long getParentId() { - return mParentId; - } - - public int getNotesCount() { - return mNotesCount; - } - - public long getFolderId () { - return mParentId; - } - - public int getType() { - return mType; - } - - public int getWidgetType() { - return mWidgetType; - } - - public int getWidgetId() { - return mWidgetId; - } - - public String getSnippet() { - return mSnippet; - } - - public boolean hasAlert() { - return (mAlertDate > 0); - } - - public boolean isCallRecord() { - return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); - } - - public static int getNoteType(Cursor cursor) { - return cursor.getInt(TYPE_COLUMN); - } -} + package net.micode.notes.ui; + + import android.content.Context; + import android.database.Cursor; + import android.text.TextUtils; + + import net.micode.notes.data.Contact; + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + import net.micode.notes.tool.DataUtils; + + // 定义一个å为 NoteItemData çš„ç±»ï¼Œç”¨äºŽå­˜å‚¨ç¬”è®°é¡¹çš„æ•°æ® + public class NoteItemData { + // 定义查询投影,包å«å¤šä¸ªåˆ— + static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + }; + + // å„ä¸ªåˆ—çš„ç´¢å¼•å¸¸é‡ + private static final int ID_COLUMN = 0; + private static final int ALERTED_DATE_COLUMN = 1; + private static final int BG_COLOR_ID_COLUMN = 2; + private static final int CREATED_DATE_COLUMN = 3; + private static final int HAS_ATTACHMENT_COLUMN = 4; + private static final int MODIFIED_DATE_COLUMN = 5; + private static final int NOTES_COUNT_COLUMN = 6; + private static final int PARENT_ID_COLUMN = 7; + private static final int SNIPPET_COLUMN = 8; + private static final int TYPE_COLUMN = 9; + private static final int WIDGET_ID_COLUMN = 10; + private static final int WIDGET_TYPE_COLUMN = 11; + + // 笔记项的å„个属性 + private long mId; + private long mAlertDate; + private int mBgColorId; + private long mCreatedDate; + private boolean mHasAttachment; + private long mModifiedDate; + private int mNotesCount; + private long mParentId; + private String mSnippet; + private int mType; + private int mWidgetId; + private int mWidgetType; + private String mName; + private String mPhoneNumber; + + // 用于标记笔记项在列表中的ä½ç½®ç›¸å…³å±žæ€§ + private boolean mIsLastItem; + private boolean mIsFirstItem; + private boolean mIsOnlyOneItem; + private boolean mIsOneNoteFollowingFolder; + private boolean mIsMultiNotesFollowingFolder; + + // 构造函数,接å—上下文和游标,用于从游标中æå–æ•°æ®åˆå§‹åŒ–笔记项 + public NoteItemData(Context context, Cursor cursor) { + // 从游标中获å–å„个列的值并åˆå§‹åŒ–相应的属性 + mId = cursor.getLong(ID_COLUMN); + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0)? true : false; + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + mParentId = cursor.getLong(PARENT_ID_COLUMN); + mSnippet = cursor.getString(SNIPPET_COLUMN); + // 处ç†ç‰‡æ®µå†…容,去除特定标记 + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + mType = cursor.getInt(TYPE_COLUMN); + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + + mPhoneNumber = ""; + // 如果父 ID 是通è¯è®°å½•文件夹的 ID,则获å–通è¯å·ç å¹¶è®¾ç½®åç§° + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + if (!TextUtils.isEmpty(mPhoneNumber)) { + mName = Contact.getContact(context, mPhoneNumber); + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + if (mName == null) { + mName = ""; + } + // 检查笔记项在游标中的ä½ç½® + checkPostion(cursor); + } + + // 检查笔记项在游标中的ä½ç½®çš„ç§æœ‰æ–¹æ³• + private void checkPostion(Cursor cursor) { + // 设置是å¦ä¸ºæœ€åŽä¸€é¡¹ã€ç¬¬ä¸€é¡¹ã€å”¯ä¸€é¡¹ç­‰æ ‡å¿— + mIsLastItem = cursor.isLast()? true : false; + mIsFirstItem = cursor.isFirst()? true : false; + mIsOnlyOneItem = (cursor.getCount() == 1); + mIsMultiNotesFollowingFolder = false; + mIsOneNoteFollowingFolder = false; + + // å¦‚æžœç¬”è®°ç±»åž‹ä¸ºæ™®é€šç¬”è®°ä¸”ä¸æ˜¯ç¬¬ä¸€é¡¹ + if (mType == Notes.TYPE_NOTE &&!mIsFirstItem) { + int position = cursor.getPosition(); + // 移动游标到å‰ä¸€é¡¹å¹¶æ£€æŸ¥å…¶ç±»åž‹ + if (cursor.moveToPrevious()) { + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true; + } else { + mIsOneNoteFollowingFolder = true; + } + } + // å°è¯•将游标移回原ä½ï¼Œå¦‚果失败则抛出异常 + if (!cursor.moveToNext()) { + throw new IllegalStateException("cursor move to previous but can't move back"); + } + } + } + } + + // åˆ¤æ–­æ˜¯å¦æœ‰ä¸€ä¸ªç¬”记项跟éšåœ¨æ–‡ä»¶å¤¹åŽé¢ + public boolean isOneFollowingFolder() { + return mIsOneNoteFollowingFolder; + } + + // åˆ¤æ–­æ˜¯å¦æœ‰å¤šä¸ªç¬”记项跟éšåœ¨æ–‡ä»¶å¤¹åŽé¢ + public boolean isMultiFollowingFolder() { + return mIsMultiNotesFollowingFolder; + } + + // 判断是å¦ä¸ºæœ€åŽä¸€é¡¹ + public boolean isLast() { + return mIsLastItem; + } + + // 获å–通è¯è®°å½•çš„åç§° + public String getCallName() { + return mName; + } + + // 判断是å¦ä¸ºç¬¬ä¸€é¡¹ + public boolean isFirst() { + return mIsFirstItem; + } + + // 判断是å¦ä¸ºå”¯ä¸€é¡¹ + public boolean isSingle() { + return mIsOnlyOneItem; + } + + // 获å–笔记项的 ID + public long getId() { + return mId; + } + + // èŽ·å–æé†’æ—¥æœŸ + public long getAlertDate() { + return mAlertDate; + } + + // 获å–创建日期 + public long getCreatedDate() { + return mCreatedDate; + } + + // åˆ¤æ–­æ˜¯å¦æœ‰é™„ä»¶ + public boolean hasAttachment() { + return mHasAttachment; + } + + // 获å–修改日期 + public long getModifiedDate() { + return mModifiedDate; + } + + // 获å–背景颜色 ID + public int getBgColorId() { + return mBgColorId; + } + + // 获å–父 ID(文件夹 ID) + public long getParentId() { + return mParentId; + } + + // 获å–ç¬”è®°æ•°é‡ + public int getNotesCount() { + return mNotesCount; + } + + // èŽ·å–æ–‡ä»¶å¤¹ ID + public long getFolderId() { + return mParentId; + } + + // 获å–笔记类型 + public int getType() { + return mType; + } + + // 获å–å°éƒ¨ä»¶ç±»åž‹ + public int getWidgetType() { + return mWidgetType; + } + + // 获å–å°éƒ¨ä»¶ ID + public int getWidgetId() { + return mWidgetId; + } + + // 获å–片段内容 + public String getSnippet() { + return mSnippet; + } + + // åˆ¤æ–­æ˜¯å¦æœ‰æé†’ + public boolean hasAlert() { + return (mAlertDate > 0); + } + + // 判断是å¦ä¸ºé€šè¯è®°å½• + public boolean isCallRecord() { + return (mParentId == Notes.ID_CALL_RECORD_FOLDER &&!TextUtils.isEmpty(mPhoneNumber)); + } + + // 陿€æ–¹æ³•,从游标中获å–笔记类型 + public static int getNoteType(Cursor cursor) { + return cursor.getInt(TYPE_COLUMN); + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/src/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..b13f28f 100644 --- a/src/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/src/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -14,171 +14,198 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; - -import net.micode.notes.data.Notes; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; - - -public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; - - public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; - }; - - public NotesListAdapter(Context context) { - super(context, null); - mSelectedIndex = new HashMap(); - mContext = context; - mNotesCount = 0; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new NotesListItem(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - if (view instanceof NotesListItem) { - NoteItemData itemData = new NoteItemData(context, cursor); - ((NotesListItem) view).bind(context, itemData, mChoiceMode, - isSelectedItem(cursor.getPosition())); - } - } - - public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - notifyDataSetChanged(); - } - - public boolean isInChoiceMode() { - return mChoiceMode; - } - - public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); - mChoiceMode = mode; - } - - public void selectAll(boolean checked) { - Cursor cursor = getCursor(); - for (int i = 0; i < getCount(); i++) { - if (cursor.moveToPosition(i)) { - if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { - setCheckedItem(i, checked); - } - } - } - } - - public HashSet getSelectedItemIds() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Long id = getItemId(position); - if (id == Notes.ID_ROOT_FOLDER) { - Log.d(TAG, "Wrong item id, should not happen"); - } else { - itemSet.add(id); - } - } - } - - return itemSet; - } - - public HashSet getSelectedWidget() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Cursor c = (Cursor) getItem(position); - if (c != null) { - AppWidgetAttribute widget = new AppWidgetAttribute(); - NoteItemData item = new NoteItemData(mContext, c); - widget.widgetId = item.getWidgetId(); - widget.widgetType = item.getWidgetType(); - itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ - } else { - Log.e(TAG, "Invalid cursor"); - return null; - } - } - } - return itemSet; - } - - public int getSelectedCount() { - Collection values = mSelectedIndex.values(); - if (null == values) { - return 0; - } - Iterator iter = values.iterator(); - int count = 0; - while (iter.hasNext()) { - if (true == iter.next()) { - count++; - } - } - return count; - } - - public boolean isAllSelected() { - int checkedCount = getSelectedCount(); - return (checkedCount != 0 && checkedCount == mNotesCount); - } - - public boolean isSelectedItem(final int position) { - if (null == mSelectedIndex.get(position)) { - return false; - } - return mSelectedIndex.get(position); - } - - @Override - protected void onContentChanged() { - super.onContentChanged(); - calcNotesCount(); - } - - @Override - public void changeCursor(Cursor cursor) { - super.changeCursor(cursor); - calcNotesCount(); - } - - private void calcNotesCount() { - mNotesCount = 0; - for (int i = 0; i < getCount(); i++) { - Cursor c = (Cursor) getItem(i); - if (c != null) { - if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { - mNotesCount++; - } - } else { - Log.e(TAG, "Invalid cursor"); - return; - } - } - } -} + package net.micode.notes.ui; + + import android.content.Context; + import android.database.Cursor; + import android.util.Log; + import android.view.View; + import android.view.ViewGroup; + import android.widget.CursorAdapter; + + import net.micode.notes.data.Notes; + + import java.util.Collection; + import java.util.HashMap; + import java.util.HashSet; + import java.util.Iterator; + + // 定义一个å为 NotesListAdapter 的类,继承自 CursorAdapter + public class NotesListAdapter extends CursorAdapter { + private static final String TAG = "NotesListAdapter"; + private Context mContext; + // 用于存储选中项ä½ç½®å’Œé€‰ä¸­çжæ€çš„哈希表 + private HashMap mSelectedIndex; + // ç¬”è®°æ•°é‡ + private int mNotesCount; + // 是å¦å¤„äºŽé€‰æ‹©æ¨¡å¼ + private boolean mChoiceMode; + + // 内部类,用于存储å°éƒ¨ä»¶çš„属性 + public static class AppWidgetAttribute { + public int widgetId; + public int widgetType; + }; + + // 构造函数,接å—上下文 + public NotesListAdapter(Context context) { + super(context, null); + // åˆå§‹åŒ–选中项的哈希表 + mSelectedIndex = new HashMap(); + mContext = context; + mNotesCount = 0; + } + + // 创建新视图的方法 + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new NotesListItem(context); + } + + // 绑定数æ®åˆ°è§†å›¾çš„æ–¹æ³• + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) { + // 创建 NoteItemData å¯¹è±¡ï¼Œç”¨äºŽå­˜å‚¨ç¬”è®°é¡¹çš„æ•°æ® + NoteItemData itemData = new NoteItemData(context, cursor); + // 绑定数æ®åˆ° NotesListItem 视图 + ((NotesListItem) view).bind(context, itemData, mChoiceMode, + isSelectedItem(cursor.getPosition())); + } + } + + // 设置指定ä½ç½®çš„项为选中或未选中状æ€çš„æ–¹æ³• + public void setCheckedItem(final int position, final boolean checked) { + mSelectedIndex.put(position, checked); + // é€šçŸ¥æ•°æ®æ”¹å˜ï¼Œåˆ·æ–°è§†å›¾ + notifyDataSetChanged(); + } + + // 判断是å¦å¤„äºŽé€‰æ‹©æ¨¡å¼ + public boolean isInChoiceMode() { + return mChoiceMode; + } + + // 设置选择模å¼çš„æ–¹æ³• + public void setChoiceMode(boolean mode) { + // 清空选中项的哈希表 + mSelectedIndex.clear(); + mChoiceMode = mode; + } + + // 全选或全ä¸é€‰çš„æ–¹æ³• + public void selectAll(boolean checked) { + Cursor cursor = getCursor(); + for (int i = 0; i < getCount(); i++) { + if (cursor.moveToPosition(i)) { + // å¦‚æžœæ˜¯ç¬”è®°ç±»åž‹ï¼Œåˆ™è®¾ç½®é€‰ä¸­çŠ¶æ€ + if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { + setCheckedItem(i, checked); + } + } + } + } + + // 获å–选中项的 ID 集åˆçš„æ–¹æ³• + public HashSet getSelectedItemIds() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Long id = getItemId(position); + if (id == Notes.ID_ROOT_FOLDER) { + Log.d(TAG, "Wrong item id, should not happen"); + } else { + itemSet.add(id); + } + } + } + + return itemSet; + } + + // 获å–选中的å°éƒ¨ä»¶å±žæ€§é›†åˆçš„æ–¹æ³• + public HashSet getSelectedWidget() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Cursor c = (Cursor) getItem(position); + if (c!= null) { + AppWidgetAttribute widget = new AppWidgetAttribute(); + NoteItemData item = new NoteItemData(mContext, c); + widget.widgetId = item.getWidgetId(); + widget.widgetType = item.getWidgetType(); + itemSet.add(widget); + /** + * Don't close cursor here, only the adapter could close it + */ + } else { + Log.e(TAG, "Invalid cursor"); + return null; + } + } + } + return itemSet; + } + + // 获å–选中项的数é‡çš„æ–¹æ³• + public int getSelectedCount() { + Collection values = mSelectedIndex.values(); + if (null == values) { + return 0; + } + Iterator iter = values.iterator(); + int count = 0; + while (iter.hasNext()) { + if (true == iter.next()) { + count++; + } + } + return count; + } + + // 判断是å¦å…¨é€‰çš„æ–¹æ³• + public boolean isAllSelected() { + int checkedCount = getSelectedCount(); + return (checkedCount!= 0 && checkedCount == mNotesCount); + } + + // 判断指定ä½ç½®çš„项是å¦è¢«é€‰ä¸­çš„æ–¹æ³• + public boolean isSelectedItem(final int position) { + if (null == mSelectedIndex.get(position)) { + return false; + } + return mSelectedIndex.get(position); + } + + // 当数æ®å†…å®¹æ”¹å˜æ—¶è°ƒç”¨çš„æ–¹æ³• + @Override + protected void onContentChanged() { + super.onContentChanged(); + // è®¡ç®—ç¬”è®°æ•°é‡ + calcNotesCount(); + } + + // æ›´æ¢æ¸¸æ ‡æ—¶è°ƒç”¨çš„æ–¹æ³• + @Override + public void changeCursor(Cursor cursor) { + super.changeCursor(cursor); + // è®¡ç®—ç¬”è®°æ•°é‡ + calcNotesCount(); + } + + // 计算笔记数é‡çš„ç§æœ‰æ–¹æ³• + private void calcNotesCount() { + mNotesCount = 0; + for (int i = 0; i < getCount(); i++) { + Cursor c = (Cursor) getItem(i); + if (c!= null) { + if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { + mNotesCount++; + } + } else { + Log.e(TAG, "Invalid cursor"); + return; + } + } + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/app/src/main/java/net/micode/notes/ui/NotesListItem.java index 1221e80..1bb8427 100644 --- a/src/app/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/src/app/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -14,109 +14,144 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - - -public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; - - public NotesListItem(Context context) { - super(context); - inflate(context, R.layout.note_item, this); - mAlert = (ImageView) findViewById(R.id.iv_alert_icon); - mTitle = (TextView) findViewById(R.id.tv_title); - mTime = (TextView) findViewById(R.id.tv_time); - mCallName = (TextView) findViewById(R.id.tv_name); - mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); - } - - 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); - } else { - mCheckBox.setVisibility(View.GONE); - } - - mItemData = data; - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.GONE); - mAlert.setVisibility(View.VISIBLE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - mTitle.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) { - mCallName.setVisibility(View.VISIBLE); - mCallName.setText(data.getCallName()); - mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); - mAlert.setVisibility(View.VISIBLE); - } else { - mAlert.setVisibility(View.GONE); - } - } else { - mCallName.setVisibility(View.GONE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - - if (data.getType() == Notes.TYPE_FOLDER) { - mTitle.setText(data.getSnippet() - + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); - mAlert.setVisibility(View.GONE); - } else { - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); - mAlert.setVisibility(View.VISIBLE); - } else { - mAlert.setVisibility(View.GONE); - } - } - } - mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - - setBackground(data); - } - - private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); - if (data.getType() == Notes.TYPE_NOTE) { - if (data.isSingle() || data.isOneFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); - } else if (data.isLast()) { - setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); - } else if (data.isFirst() || data.isMultiFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); - } else { - setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); - } - } else { - setBackgroundResource(NoteItemBgResources.getFolderBgRes()); - } - } - - public NoteItemData getItemData() { - return mItemData; - } -} + import android.content.Context; + import android.text.format.DateUtils; + import android.view.View; + import android.widget.CheckBox; + import android.widget.ImageView; + import android.widget.LinearLayout; + import android.widget.TextView; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.tool.DataUtils; + import net.micode.notes.tool.ResourceParser.NoteItemBgResources; + + // 定义一个å为 NotesListItem 的类,继承自 LinearLayout + public class NotesListItem extends LinearLayout { + // æé†’图标 + private ImageView mAlert; + // 标题文本视图 + private TextView mTitle; + // 时间文本视图 + private TextView mTime; + // 通è¯è®°å½•å称文本视图 + private TextView mCallName; + // å¤é€‰æ¡† + private NoteItemData mItemData; + private CheckBox mCheckBox; + + // 构造函数,接å—上下文 + public NotesListItem(Context context) { + super(context); + // 从布局文件中填充视图 + inflate(context, R.layout.note_item, this); + mAlert = (ImageView) findViewById(R.id.iv_alert_icon); + mTitle = (TextView) findViewById(R.id.tv_title); + mTime = (TextView) findViewById(R.id.tv_time); + mCallName = (TextView) findViewById(R.id.tv_name); + mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); + } + + // 绑定数æ®åˆ°è§†å›¾çš„æ–¹æ³• + 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); + } else { + // éšè—å¤é€‰æ¡† + mCheckBox.setVisibility(View.GONE); + } + + mItemData = data; + // 如果是通è¯è®°å½•文件夹 + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // éšè—通è¯è®°å½•å称视图 + mCallName.setVisibility(View.GONE); + // 显示æé†’图标 + mAlert.setVisibility(View.VISIBLE); + // 设置标题文本外观和内容 + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + mTitle.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) { + // 显示通è¯è®°å½•å称视图 + mCallName.setVisibility(View.VISIBLE); + // 设置通è¯è®°å½•å称文本 + mCallName.setText(data.getCallName()); + // 设置标题文本外观 + mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem); + // 设置标题文本为格å¼åŒ–åŽçš„片段内容 + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 如果有æé†’,则显示æé†’图标 + if (data.hasAlert()) { + mAlert.setImageResource(R.drawable.clock); + mAlert.setVisibility(View.VISIBLE); + } else { + // 没有æé†’则éšè—æé†’图标 + mAlert.setVisibility(View.GONE); + } + } else { + // éšè—通è¯è®°å½•å称视图 + mCallName.setVisibility(View.GONE); + // 设置标题文本外观 + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + + if (data.getType() == Notes.TYPE_FOLDER) { + // å¦‚æžœæ˜¯æ–‡ä»¶å¤¹ç±»åž‹ï¼Œè®¾ç½®æ ‡é¢˜æ–‡æœ¬ä¸ºç‰‡æ®µå†…å®¹åŠ ä¸Šæ–‡ä»¶æ•°é‡ + mTitle.setText(data.getSnippet() + + context.getString(R.string.format_folder_files_count, + data.getNotesCount())); + // éšè—æé†’图标 + mAlert.setVisibility(View.GONE); + } else { + // 如果是笔记类型,设置标题文本为格å¼åŒ–åŽçš„片段内容 + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 如果有æé†’,则显示æé†’图标 + if (data.hasAlert()) { + mAlert.setImageResource(R.drawable.clock); + mAlert.setVisibility(View.VISIBLE); + } else { + // 没有æé†’则éšè—æé†’图标 + mAlert.setVisibility(View.GONE); + } + } + } + // 设置时间文本为相对时间 + mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + + // 设置背景 + setBackground(data); + } + + // è®¾ç½®èƒŒæ™¯çš„ç§æœ‰æ–¹æ³• + private void setBackground(NoteItemData data) { + int id = data.getBgColorId(); + if (data.getType() == Notes.TYPE_NOTE) { + // æ ¹æ®ä¸åŒçš„ä½ç½®çжæ€è®¾ç½®ä¸åŒçš„ç¬”è®°èƒŒæ™¯èµ„æº + if (data.isSingle() || data.isOneFollowingFolder()) { + setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); + } else if (data.isLast()) { + setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); + } else if (data.isFirst() || data.isMultiFollowingFolder()) { + setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); + } else { + setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); + } + } else { + // è®¾ç½®æ–‡ä»¶å¤¹çš„èƒŒæ™¯èµ„æº + setBackgroundResource(NoteItemBgResources.getFolderBgRes()); + } + } + + // 获å–当å‰é¡¹çš„æ•°æ®å¯¹è±¡ + public NoteItemData getItemData() { + return mItemData; + } + } \ No newline at end of file diff --git a/src/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/src/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..b3e3286 100644 --- a/src/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -14,367 +14,388 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.ActionBar; -import android.app.AlertDialog; -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceActivity; -import android.preference.PreferenceCategory; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -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.NoteColumns; -import net.micode.notes.gtask.remote.GTaskSyncService; - - -public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; - - public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - - public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - - public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - - private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - /* using the app icon for navigation */ - getActionBar().setDisplayHomeAsUpEnabled(true); - - addPreferencesFromResource(R.xml.preferences); - mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); - mReceiver = new GTaskReceiver(); - IntentFilter filter = new IntentFilter(); - filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); - - mOriAccounts = null; - View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); - getListView().addHeaderView(header, null, true); - } - - @Override - protected void onResume() { - super.onResume(); - - // need to set sync account automatically if user has added a new - // account - if (mHasAddedAccount) { - Account[] accounts = getGoogleAccounts(); - if (mOriAccounts != null && accounts.length > mOriAccounts.length) { - for (Account accountNew : accounts) { - boolean found = false; - for (Account accountOld : mOriAccounts) { - if (TextUtils.equals(accountOld.name, accountNew.name)) { - found = true; - break; - } - } - if (!found) { - setSyncAccount(accountNew.name); - break; - } - } - } - } - - refreshUI(); - } - - @Override - protected void onDestroy() { - if (mReceiver != null) { - unregisterReceiver(mReceiver); - } - super.onDestroy(); - } - - private void loadAccountPreference() { - mAccountCategory.removeAll(); - - Preference accountPref = new Preference(this); - final String defaultAccount = getSyncAccountName(this); - accountPref.setTitle(getString(R.string.preferences_account_title)); - accountPref.setSummary(getString(R.string.preferences_account_summary)); - accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - 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) + package net.micode.notes.ui; + + import android.accounts.Account; + import android.accounts.AccountManager; + import android.app.ActionBar; + import android.app.AlertDialog; + import android.content.BroadcastReceiver; + import android.content.ContentValues; + import android.content.Context; + import android.content.DialogInterface; + import android.content.Intent; + import android.content.IntentFilter; + import android.content.SharedPreferences; + import android.os.Bundle; + import android.preference.Preference; + import android.preference.Preference.OnPreferenceClickListener; + import android.preference.PreferenceActivity; + import android.preference.PreferenceCategory; + import android.text.TextUtils; + import android.text.format.DateFormat; + import android.view.LayoutInflater; + import android.view.Menu; + import android.view.MenuItem; + import android.view.View; + import android.widget.Button; + 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.NoteColumns; + import net.micode.notes.gtask.remote.GTaskSyncService; + + // 定义一个å为 NotesPreferenceActivity 的类,继承自 PreferenceActivity + 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; + + // 活动创建时调用的方法 + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + /* using the app icon for navigation */ + getActionBar().setDisplayHomeAsUpEnabled(true); + + // ä»Žèµ„æºæ–‡ä»¶ä¸­åŠ è½½å好设置 + addPreferencesFromResource(R.xml.preferences); + mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + mReceiver = new GTaskReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); + // 注册广播接收器 + registerReceiver(mReceiver, filter); + + mOriAccounts = null; + // 加载头部视图 + View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); + getListView().addHeaderView(header, null, true); + } + + // 活动æ¢å¤æ—¶è°ƒç”¨çš„æ–¹æ³• + @Override + protected void onResume() { + super.onResume(); + + // 如果添加了账户,则需è¦è‡ªåŠ¨è®¾ç½®åŒæ­¥è´¦æˆ· + if (mHasAddedAccount) { + Account[] accounts = getGoogleAccounts(); + if (mOriAccounts!= null && accounts.length > mOriAccounts.length) { + for (Account accountNew : accounts) { + boolean found = false; + for (Account accountOld : mOriAccounts) { + if (TextUtils.equals(accountOld.name, accountNew.name)) { + found = true; + break; + } + } + if (!found) { + setSyncAccount(accountNew.name); + break; + } + } + } + } + + // åˆ·æ–°ç”¨æˆ·ç•Œé¢ + refreshUI(); + } + + // æ´»åŠ¨é”€æ¯æ—¶è°ƒç”¨çš„æ–¹æ³• + @Override + protected void onDestroy() { + if (mReceiver!= null) { + // 注销广播接收器 + unregisterReceiver(mReceiver); + } + super.onDestroy(); + } + + // 加载账户å好设置的方法 + private void loadAccountPreference() { + mAccountCategory.removeAll(); + + Preference accountPref = new Preference(this); + final String defaultAccount = getSyncAccountName(this); + accountPref.setTitle(getString(R.string.preferences_account_title)); + accountPref.setSummary(getString(R.string.preferences_account_summary)); + accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + if (!GTaskSyncService.isSyncing()) { + if (TextUtils.isEmpty(defaultAccount)) { + // å¦‚æžœæ˜¯ç¬¬ä¸€æ¬¡è®¾ç½®è´¦æˆ·ï¼Œåˆ™æ˜¾ç¤ºé€‰æ‹©è´¦æˆ·çš„è­¦å‘Šå¯¹è¯æ¡† + showSelectAccountAlertDialog(); + } else { + // 如果账户已ç»è®¾ç½®ï¼Œåˆ™æ˜¾ç¤ºæ›´æ”¹è´¦æˆ·ç¡®è®¤çš„è­¦å‘Šå¯¹è¯æ¡† + showChangeAccountConfirmAlertDialog(); + } + } else { + // å¦‚æžœæ­£åœ¨åŒæ­¥ï¼Œåˆ™æ˜¾ç¤ºä¸èƒ½æ›´æ”¹è´¦æˆ·çš„å叿¶ˆæ¯ + Toast.makeText(NotesPreferenceActivity.this, + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); - } - return true; - } - }); - - 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) { - GTaskSyncService.cancelSync(NotesPreferenceActivity.this); - } - }); - } else { - syncButton.setText(getString(R.string.preferences_button_sync_immediately)); - syncButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - GTaskSyncService.startSync(NotesPreferenceActivity.this); - } - }); - } - syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - - // set last sync time - if (GTaskSyncService.isSyncing()) { - lastSyncTimeView.setText(GTaskSyncService.getProgressString()); - lastSyncTimeView.setVisibility(View.VISIBLE); - } else { - long lastSyncTime = getLastSyncTime(this); - if (lastSyncTime != 0) { - lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, - DateFormat.format(getString(R.string.preferences_last_sync_time_format), - lastSyncTime))); - lastSyncTimeView.setVisibility(View.VISIBLE); - } else { - lastSyncTimeView.setVisibility(View.GONE); - } - } - } - - private void refreshUI() { - loadAccountPreference(); - loadSyncButton(); - } - - private void showSelectAccountAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); - - dialogBuilder.setCustomTitle(titleView); - dialogBuilder.setPositiveButton(null, null); - - 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; - int checkedItem = -1; - int index = 0; - for (Account account : accounts) { - if (TextUtils.equals(account.name, defAccount)) { - checkedItem = index; - } - items[index++] = account.name; - } - dialogBuilder.setSingleChoiceItems(items, checkedItem, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - setSyncAccount(itemMapping[which].toString()); - dialog.dismiss(); - refreshUI(); - } - }); - } - - View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); - dialogBuilder.setView(addAccountView); - - final AlertDialog dialog = dialogBuilder.show(); - addAccountView.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - mHasAddedAccount = true; - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" - }); - startActivityForResult(intent, -1); - dialog.dismiss(); - } - }); - } - - private void showChangeAccountConfirmAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, - getSyncAccountName(this))); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); - dialogBuilder.setCustomTitle(titleView); - - CharSequence[] menuItemArray = new CharSequence[] { - getString(R.string.preferences_menu_change_account), - getString(R.string.preferences_menu_remove_account), - getString(R.string.preferences_menu_cancel) - }; - dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - showSelectAccountAlertDialog(); - } else if (which == 1) { - removeSyncAccount(); - refreshUI(); - } - } - }); - dialogBuilder.show(); - } - - private Account[] getGoogleAccounts() { - AccountManager accountManager = AccountManager.get(this); - return accountManager.getAccountsByType("com.google"); - } - - private void setSyncAccount(String account) { - if (!getSyncAccountName(this).equals(account)) { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - if (account != null) { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); - } else { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); - } - editor.commit(); - - // clean up last sync time - setLastSyncTime(this, 0); - - // clean up local gtask related info - new Thread(new Runnable() { - public void run() { - ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); - } - }).start(); - - Toast.makeText(NotesPreferenceActivity.this, - getString(R.string.preferences_toast_success_set_accout, account), - Toast.LENGTH_SHORT).show(); - } - } - - private void removeSyncAccount() { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { - editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); - } - if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { - editor.remove(PREFERENCE_LAST_SYNC_TIME); - } - editor.commit(); - - // clean up local gtask related info - new Thread(new Runnable() { - public void run() { - ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); - } - }).start(); - } - - public static String getSyncAccountName(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); - } - - public static void setLastSyncTime(Context context, long time) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); - editor.commit(); - } - - public static long getLastSyncTime(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); - } - - private class GTaskReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - refreshUI(); - if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { - TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - syncStatus.setText(intent + } + return true; + } + }); + + 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); + + // è®¾ç½®æŒ‰é’®çŠ¶æ€ + if (GTaskSyncService.isSyncing()) { + syncButton.setText(getString(R.string.preferences_button_sync_cancel)); + syncButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + GTaskSyncService.cancelSync(NotesPreferenceActivity.this); + } + }); + } else { + syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + syncButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + GTaskSyncService.startSync(NotesPreferenceActivity.this); + } + }); + } + syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + + // 设置最åŽåŒæ­¥æ—¶é—´æ–‡æœ¬ + if (GTaskSyncService.isSyncing()) { + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); + lastSyncTimeView.setVisibility(View.VISIBLE); + } else { + long lastSyncTime = getLastSyncTime(this); + if (lastSyncTime!= 0) { + lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, + DateFormat.format(getString(R.string.preferences_last_sync_time_format), + lastSyncTime))); + lastSyncTimeView.setVisibility(View.VISIBLE); + } else { + lastSyncTimeView.setVisibility(View.GONE); + } + } + } + + // 刷新用户界é¢çš„æ–¹æ³• + private void refreshUI() { + loadAccountPreference(); + loadSyncButton(); + } + + // æ˜¾ç¤ºé€‰æ‹©è´¦æˆ·è­¦å‘Šå¯¹è¯æ¡†çš„æ–¹æ³• + private void showSelectAccountAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); + + dialogBuilder.setCustomTitle(titleView); + dialogBuilder.setPositiveButton(null, null); + + 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; + int checkedItem = -1; + int index = 0; + for (Account account : accounts) { + if (TextUtils.equals(account.name, defAccount)) { + checkedItem = index; + } + items[index++] = account.name; + } + dialogBuilder.setSingleChoiceItems(items, checkedItem, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setSyncAccount(itemMapping[which].toString()); + dialog.dismiss(); + refreshUI(); + } + }); + } + + View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); + dialogBuilder.setView(addAccountView); + + final AlertDialog dialog = dialogBuilder.show(); + addAccountView.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mHasAddedAccount = true; + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { + "gmail-ls" + }); + startActivityForResult(intent, -1); + dialog.dismiss(); + } + }); + } + + // æ˜¾ç¤ºæ›´æ”¹è´¦æˆ·ç¡®è®¤è­¦å‘Šå¯¹è¯æ¡†çš„æ–¹æ³• + private void showChangeAccountConfirmAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, + getSyncAccountName(this))); + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); + dialogBuilder.setCustomTitle(titleView); + + CharSequence[] menuItemArray = new CharSequence[] { + getString(R.string.preferences_menu_change_account), + getString(R.string.preferences_menu_remove_account), + getString(R.string.preferences_menu_cancel) + }; + dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == 0) { + showSelectAccountAlertDialog(); + } else if (which == 1) { + removeSyncAccount(); + refreshUI(); + } + } + }); + dialogBuilder.show(); + } + + // 获å–谷歌账户数组的方法 + private Account[] getGoogleAccounts() { + AccountManager accountManager = AccountManager.get(this); + return accountManager.getAccountsByType("com.google"); + } + + // è®¾ç½®åŒæ­¥è´¦æˆ·çš„æ–¹æ³• + private void setSyncAccount(String account) { + if (!getSyncAccountName(this).equals(account)) { + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + if (account!= null) { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); + } else { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + editor.commit(); + + // æ¸…ç†æœ€åŽåŒæ­¥æ—¶é—´ + setLastSyncTime(this, 0); + + // æ¸…ç†æœ¬åœ°ä¸Žè°·æ­Œä»»åŠ¡ç›¸å…³çš„ä¿¡æ¯ + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + + Toast.makeText(NotesPreferenceActivity.this, + getString(R.string.preferences_toast_success_set_accout, account), + Toast.LENGTH_SHORT).show(); + } + } + + // ç§»é™¤åŒæ­¥è´¦æˆ·çš„æ–¹æ³• + private void removeSyncAccount() { + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { + editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); + } + if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { + editor.remove(PREFERENCE_LAST_SYNC_TIME); + } + editor.commit(); + + // æ¸…ç†æœ¬åœ°ä¸Žè°·æ­Œä»»åŠ¡ç›¸å…³çš„ä¿¡æ¯ + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + } + + // 获å–åŒæ­¥è´¦æˆ·åç§°çš„é™æ€æ–¹æ³• + public static String getSyncAccountName(Context context) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + + // 设置最åŽåŒæ­¥æ—¶é—´çš„陿€æ–¹æ³• + public static void setLastSyncTime(Context context, long time) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); + editor.commit(); + } + + // èŽ·å–æœ€åŽåŒæ­¥æ—¶é—´çš„陿€æ–¹æ³• + public static long getLastSyncTime(Context context) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); + } + + // 广播接收器内部类 + private class GTaskReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + refreshUI(); + if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { + TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + syncStatus.setText(intent .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); - } - - } - } - - public boolean onOptionsItemSelected(MenuItem item) { + } + + } + } + + // 选项èœå•项被选择时调用的方法 + public boolean onOptionsItemSelected( switch (item.getItemId()) { case android.R.id.home: Intent intent = new Intent(this, NotesListActivity.class); diff --git a/src/local.properties b/src/local.properties index 9d722e3..32a7e36 100644 --- a/src/local.properties +++ b/src/local.properties @@ -2,6 +2,7 @@ # as it contains information specific to your local configuration. # # Location of the SDK. This is only used by Gradle. -# -#Sun Sep 08 13:35:35 CST 2024 -sdk.dir=D\:\\Android\\Sdk +# For customization when using a Version Control System, please read the +# header note. +#Sun Sep 22 17:32:55 CST 2024 +sdk.dir=D\:\\sdk