葛兴海 报告提交

pull/12/head
gexinghai 3 years ago
parent f20562350c
commit 649aa6a305

Binary file not shown.

@ -0,0 +1,81 @@
// Apache许可证协议
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* 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;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
public class Contact {
private static HashMap<String, String> 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 "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 查找HashMap中是否已有phoneNumber信息
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 查找数据库中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;
} finally {
cursor.close();
}
// 未找到相关信息
} else {
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -0,0 +1,288 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.net.Uri;
// Notes 类中定义了很多常量这些常量大多是int型和string型
public class Notes {
public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
//以下三个常量对NoteColumns.TYPE的值进行设置时会用到
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_TEMPORARY_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");
//定义查找数据的指针。
// 定义NoteColumns的常量,用于后面创建数据库的表头
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
//为什么会有parent_id
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
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
* <P> Type: INTEGER </P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
}//这些常量主要是定义便签的属性的。
// 定义DataColumns的常量,用于后面创建数据库的表头
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
public static final String CONTENT = "content";
/*
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA1 = "data1";
/*
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA2 = "data2";
/*
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA3 = "data3";
/*
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA4 = "data4";
/*
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA5 = "data5";
//主要是定义存储便签内容数据的
}
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
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
* <P> Type: INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
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");
}//电话内容的数据结构
}

@ -0,0 +1,364 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* 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 { //接口分成note和data在后面的程序里分别使用过
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";//在文件夹中移入一个Note之后需要更改的数据的表格。
/**
* 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";//在文件夹中移出一个Note之后需要更改的数据的表格。
/**
* 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";//在文件夹中插入一个Note之后需要更改的数据的表格。
/**
* 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";//在文件夹中删除一个Note之后需要更改的数据的表格。
/**
* 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";//在文件夹中对一个Note导入新的数据之后需要更改的数据的表格。
/**
* 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";//Note数据被修改后需要更改的数据的表格。
/**
* 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";//Note数据被删除后需要更改的数据的表格。
/**
* 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);
}//同上面的execSQL
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);
}//更新到V2版本
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);
}//更新到V3版本
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}//更新到V4版本,但是不知道V2、V3、V4是什么意思
}

@ -0,0 +1,328 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 {
// UriMatcher用于匹配Uri
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 {
// 创建UriMatcher时调用UriMatcher(UriMatcher.NO_MATCH)表示不匹配任何路径的返回码
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);// 把需要匹配Uri路径全部给注册上
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.
*/
// 声明 NOTES_SEARCH_PROJECTION
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;
// 声明NOTES_SNIPPET_SEARCH_QUERY
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
// Context只有在onCreate()中才被初始化
// 对mHelper进行实例化
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
@Override
// 查询uri在数据库中对应的位置
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
// 获取可读数据库
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 匹配查找uri
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) {
// getPathSegments()方法得到一个String的List
// 在uri.getPathSegments().get(1)为第2个元素
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
// 插入一个uri
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;
// 如果存在查找NOTE_ID
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
// notifyChange获得一个ContextResolver对象并且更新里面的内容
// 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);
}
// 返回插入的uri的路径
return ContentUris.withAppendedId(uri, insertedId);
}
@Override
// 删除一个uri
public int delete(Uri uri, String selection, String[] selectionArgs) {
//Uri代表要操作的数据Android上可用的每种资源 -包括 图像、视频片段、音频资源等都可以用Uri来表示。
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
// 更新一个uri
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 + ')' : "");
}
//增加一个noteVersion
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);
}
// execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句
mHelper.getWritableDatabase().execSQL(sql.toString());
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
}

@ -0,0 +1,253 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* 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<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
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;
}
}
}

@ -0,0 +1,368 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* 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);
}
}

@ -0,0 +1,330 @@
// Apache许可证协议
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
//如果当前备份不存在,则新声明一个
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*
*/
// Currently, the sdcard is not mounted SD卡没有被装入手机
public static final int STATE_SD_CARD_UNMOUNTED = 0;
// The backup file not exist 备份文件不存在
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formatted, may be changed by other programs 数据已被破坏,可能被修改
public static final int STATE_DATA_DESTROYED = 2;
// Some run-time exception which causes restore or backup fails 超时异常
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success 备份或还原成功
public static final int STATE_SUCCESS = 4;
private final TextExport mTextExport;
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
//判断外部存储功能是否可用
public int exportToText() {
return mTextExport.exportToText();
}
//输出至文本
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
//获取输出的文本文件名
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
//获取输出文本文件目录
private static class TextExport {//内部类文本输出定义笔记ID、修改日期等笔记、内容、日期、电话号码的数据和格式等常量
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
//下面几行标识数组里属性值
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = {//字符串储存便签的基本信息
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
//标识设定数据内容标识为0媒体类型标识为1访问日期标识为2电话号码标识为4
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
//文档格式标识名称标识为0;日期标识为1;内容标识为2
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
//定义文件名、上下文、文件夹路径三个类
private Context mContext;
private String mFileName;
private String mFileDirectory;
public TextExport(Context context) {//从context获取便签的信息并给其中一些成员赋予初值
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {
return TEXT_FORMAT[id];
}//通过ID返回文件的格式信息
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {//
// 通过文件ID找到该文件下的便签并且打印出该便签最后修改的日期并且将该便签的内容输出并显示出来。
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.printf((getFormat(FORMAT_NOTE_DATE)) + "%n", DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
//将便签的内容以文本的形式显示在屏幕上。比如电话号码、打电话的日期等。
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.printf((getFormat(FORMAT_NOTE_CONTENT)) + "%n",
phoneNumber);
}
// Print call date
ps.printf((getFormat(FORMAT_NOTE_CONTENT)) + "%n", DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.printf((getFormat(FORMAT_NOTE_CONTENT)) + "%n",
location);
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.printf((getFormat(FORMAT_NOTE_CONTENT)) + "%n",
content);
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// print a line separator between note
try {//在note下面输出一条线
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {//将note里面的内容输出到用户的屏幕
if (!externalStorageAvailable()) {//检查外部设备安装情况
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUNTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");//先导出对应的目录,然后导出对应的便签
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(//定位需要导出的文件夹
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {//导出文件夹,把里面的便签导出来
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName;
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.printf((getFormat(FORMAT_FOLDER_NAME)) + "%n", folderName);
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(//输出根目录下的便签
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {//输出修改的时间
if (noteCursor.moveToFirst()) {
do {
ps.printf((getFormat(FORMAT_NOTE_DATE)) + "%n", DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {//获取指向文件的打印流
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}//初始化储存在SD卡中的文件如果为空创造失败
mFileName = file.getName();//得到文件名
mFileDirectory = mContext.getString(R.string.file_path);//得到文件路径
PrintStream ps;
try {//将ps输出流输出到特定的文件目的就是导出到文件而不是直接输出
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
//生成存储文件安装在SD卡上
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {//如果这些文件不存在,则新建
if (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException | IOException e) {
e.printStackTrace();
}
return null;
}
}

@ -0,0 +1,240 @@
// Apache许可证协议
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.OperationApplicationException;
import android.database.Cursor;
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.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
public class DataUtils {//定义便签的处理工具类
public static final String TAG = "DataUtils";
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {//一个处理批量删除便签的方法
if (ids == null) {//判断笔记是否为空
Log.d(TAG, "the ids is null");
return true;
}
if (ids.isEmpty()) {
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
for (long id : ids) {//遍历便签id
if (id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");//.如果是根目录的文件夹输出Don't delete system folder root
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation//批量的删除对应的ID
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {//返回被删除的数据如果返回为空则删除失败返回false打印异常信息删除成功返回true
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids);
return false;
}
return true;
} catch (RemoteException | OperationApplicationException e) {//错误处理
Log.e(TAG, String.format("%s: %s", e, e.getMessage()));
}
return false;
}
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,//批量移动
long folderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
for (long id : ids) {//遍历便签ID
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
}
try {//错误检测
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids);
return false;
}
return true;
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e, e.getMessage()));
}
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {//得到文件夹数目
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String[]{"COUNT(*)"},
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[]{String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLDER)},
null);
int count = 0;
if (cursor != null && (cursor.moveToFirst())) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e);
} finally {
cursor.close();
}
}
return count;
}
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {//在数据库中是否可见
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER,
new String[]{String.valueOf(type)},
null);
boolean exist = false;//判断note是否在数据库中并返回相应的布尔值
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {//检查文件名以获取文件是否存在
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[]{name}, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {//获得文件夹窗口小部件
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE},
NoteColumns.PARENT_ID + "=?",
new String[]{String.valueOf(folderId)},
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<>();
do {
try {//把每一个条目对应的窗口id和type记录下来放到set里面。
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {//当下标超过边界,那么返回错误
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
c.close();
}
return set;
}
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {//通过笔记ID获取号码
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String[]{CallNote.PHONE_NUMBER},
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String[]{String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE},
null);
if (cursor != null && cursor.moveToFirst()) {//获取电话号码,并处理异常
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e);
} finally {
cursor.close();
}
}
return "";
}
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {//通过号码和日期获取ID
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String[]{CallNote.NOTE_ID},
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String[]{String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber},
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e);
}
}
cursor.close();
}
return 0;
}
public static String getSnippetById(ContentResolver resolver, long noteId) {//通过Note的Id获取Note的片段
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.SNIPPET},
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)},
null);
if (cursor != null) {
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
cursor.close();
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
public static String getFormattedSnippet(String snippet) {//格式化,对字符串进行格式处理,将字符串两头的空格去掉,同时将换行符去掉
if (snippet != null) {
snippet = snippet.trim();
int index = snippet.indexOf('\n');
if (index != -1) {
snippet = snippet.substring(0, index);
}
}
return snippet;
}
}

@ -0,0 +1,181 @@
// Apache许可证协议
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
public class ResourceParser {
//ResourceParser类资源分析实际上就是获取资源并且在程序中使用比如图片资源、布局资源、字体大小等
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
public static final int BG_DEFAULT_COLOR = YELLOW;
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {//背景资源的获取
private static final int[] BG_EDIT_RESOURCES = new int[]{
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
private static final int[] BG_EDIT_TITLE_RESOURCES = new int[]{
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
//获取Note的背景颜色
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
//ID可以查找到编辑框的颜色的地址
public static int getDefaultBgId(Context context) {//直接获取默认的背景颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
"pref_key_bg_random_appear", false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;
}
}
public static class NoteItemBgResources {//便签背景资源类
private static final int[] BG_FIRST_RESOURCES = new int[]{
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
private static final int[] BG_NORMAL_RESOURCES = new int[]{
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
private static final int[] BG_LAST_RESOURCES = new int[]{
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
private static final int[] BG_SINGLE_RESOURCES = new int[]{
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
public static class WidgetBgResources {//获取图片资源类
private static final int[] BG_2X_RESOURCES = new int[]{//通过id获得2x的小窗口背景资源
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
//通过ID获取较小的图片
private static final int[] BG_3X_RESOURCES = new int[]{//通过id获得3x的小窗口背景资源
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
private static final int[] BG_4X_RESOURCES = new int[]{
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
//根据ID加载BG_4X_RESOURCES数组里的颜色资源序号。
public static class TextAppearanceResources {
private static final int[] TEXT_APPEARANCE_RESOURCES = new int[]{
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
//文本外观资源,包括默认字体,以及获取资源大小
public static int getTexAppearanceResource(int id) {
/**
* @HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXT_APPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXT_APPEARANCE_RESOURCES[id];
}
//防止输入的id大于资源总量若如此则自动返回默认的设置结果
public static int getResourcesSize() {
return TEXT_APPEARANCE_RESOURCES.length;
}
}
}
//直接返回为资源的长度

@ -0,0 +1,139 @@
// Apache许可证协议
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);
}
// 在Activity创建时被调用它首先设置Activity的特性然后获取传递过来的Intent从中获取提醒的ID和摘录信息并根据提醒的ID判断提醒是否可以在Note数据库中找到并显示对应的提醒对话框和播放提醒声音。
Intent intent = getIntent();
try {
mNoteId = Long.parseLong(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();
}
}
// 以一个try-catch块开始尝试从意图中提取一个笔记ID使用笔记ID从内容提供程序中检索与该笔记相关的文本片段并在文本片段太长时截断它。如果在此过程中抛出IllegalArgumentException异常则捕获该异常并返回方法。
//接下来的代码块创建一个新的MediaPlayer对象并检查与笔记ID关联的笔记是否在笔记数据库中可见。如果可见则调用名为showActionDialog()的方法该方法可能显示一个对话框给用户。还调用了playAlarmSound()方法,该方法可能会播放警报声。如果笔记在数据库中不可见,则方法仅完成而不显示对话框或播放声音。
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 | SecurityException | IllegalStateException |
IOException e) {
e.printStackTrace();
}
}
// 用于播放提醒声音首先获取系统默认的闹铃铃声然后判断当前是否为静音模式如果是则设置MediaPlayer的音频流类型为系统当前可影响铃声的流否则设置为闹钟流类型。最后设置铃声数据源、循环播放并开始播放。
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) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
}
}
// 用于处理对话框中按钮的点击事件如果是进入按钮则启动NoteEditActivity并携带提醒的ID将它作为Extra参数传递给NoteEditActivity以便打开对应的Note。如果是确认按钮则什么都不做。
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
// 用于处理提醒对话框关闭事件停止播放提醒声音并结束Activity。
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}
// 用于停止播放提醒声音。

@ -0,0 +1,54 @@
// Apache许可证协议
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;
//这里定义了一个包名为net.micode.notes.ui的Java类AlarmInitReceiver它继承了BroadcastReceiver类。引入了Android系统提供的AlarmManager、PendingIntent、BroadcastReceiver、ContentUris等类。还引入了笔记相关的数据模型Notes和NoteColumns。
//PROJECTION是笔记查询时的投影指定了需要查询的列。COLUMN_ID和COLUMN_ALERTED_DATE则是笔记查询结果中对应的列索引。
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
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
}
}
// onReceive方法是接收广播后执行的方法。在这里首先获取当前时间戳currentDate然后通过getContentResolver().query方法查询所有需要提醒的笔记查询条件是提醒时间大于当前时间戳并且类型是笔记类型。
}

@ -0,0 +1,18 @@
// Apache许可证协议
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);
}
// onReceive方法中首先获取传入的Intent对象并通过setClass方法将Intent的目标Activity设置为AlarmAlertActivity。然后通过addFlags方法设置Intent的标志位FLAG_ACTIVITY_NEW_TASK表示启动一个新的Task来显示Activity。最后通过context.startActivity方法启动Activity。
}
//这段代码的作用是在系统闹钟触发时启动AlarmAlertActivity来显示提醒内容。

@ -0,0 +1,504 @@
// Apache许可证协议
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);// 使用Calendar类的add方法将mDate对象的日期字段即Calendar.DAY_OF_YEAR增加newVal - oldVal天以更新日期。
updateDateControl();//调用updateDateControl方法该方法用来更新日期控件的显示。
onDateTimeChanged();//调用onDateTimeChanged方法该方法用来通知其他组件该日期时间已经发生了变化。
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 根据24小时制或12小时制以及上下午状态等情况计算新的时间并更新到mDate字段中。
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
// 调用onDateTimeChanged方法该方法用来通知其他组件该日期时间已经发生了变化。
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
// 如果日期发生了变化则调用setCurrentYear和setCurrentMonth方法更新年份和月份控件的显示。
}
};
// 这段代码定义了一个监听器对象用于监听小时NumberPicker控件的值变化事件。
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
// 当监听器被触发时,它首先根据 NumberPicker 的旧值和新值计算出一个 offset。如果旧值是最大值且新值是最小值则将 offset 增加1。如果旧值是最小值且新值是最大值则将 offset 减少1。
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
// 如果 offset 不为0则监听器会更新 mDate 变量,将 offset 添加到小时数中,将小时选择器的值设置为当前小时,调用 updateDateControl() 更新日期控件,并根据新的小时数更新 AM/PM 控件。
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
// 更新 mDate 变量后,监听器将分钟值设置为新值,并调用 onDateTimeChanged() 通知任何监听器日期和时间已更新。
}
};
// 定义了一个私有字段 mOnMinuteChangedListener它是 NumberPicker.OnValueChangeListener 的一个实例。当表示分钟的 NumberPicker 的值发生变化时,此监听器将被触发。
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
// 当监听器被触发时,它将 mIsAm 变量取反,表示用户选择了 AM 还是 PM。如果 mIsAm 是 true则说明用户选择了 AM此时监听器将 mDate 变量减去半天的小时数。如果 mIsAm 是 false则说明用户选择了 PM此时监听器将 mDate 变量加上半天的小时数。然后,监听器会调用 updateAmPmControl() 更新 AM/PM 控件,并调用 onDateTimeChanged() 通知任何监听器日期和时间已更新。
};
// 定义了一个私有字段 mOnAmPmChangedListener它是 NumberPicker.OnValueChangeListener 的一个实例。当表示 AM/PM 的 NumberPicker 的值发生变化时,此监听器将被触发。
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
// 该接口有一个抽象方法 onDateTimeChanged(),它在日期或时间发生变化时被调用。该方法接收 6 个参数view 表示当前 DateTimePicker 实例year 表示年份month 表示月份(从 0 开始dayOfMonth 表示月中的某一天hourOfDay 表示小时数24 小时制minute 表示分钟数。
}
//定义了一个接口 OnDateTimeChangedListener它用于监听日期和时间的变化。当日期或时间发生变化时可以调用该接口的 onDateTimeChanged() 方法通知任何实现该接口的监听器。通过实现该接口并在需要的地方注册监听器,可以在日期或时间发生变化时执行自定义操作,例如更新 UI 或执行某些计算。
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
// 首先调用了父类 ViewGroup 的构造函数 super(context) 来初始化 DateTimePicker 实例。然后,代码使用 Calendar.getInstance() 获取一个 Calendar 对象,该对象表示当前日期和时间。变量 mIsAm 被初始化为当前小时数是否大于等于 12以确定当前用户选择的是 AM 还是 PM。
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 获取了布局文件中 ID 为 date 的 NumberPicker 控件,并将其赋值给 mDateSpinner 变量。然后,使用 setMinValue() 和 setMaxValue() 方法将 mDateSpinner 的最小值和最大值分别设置为 DATE_SPINNER_MIN_VAL 和 DATE_SPINNER_MAX_VAL。使用 setOnValueChangedListener() 方法将 mDateSpinner 的值更改监听器设置为 mOnDateChangedListener。这意味着当 mDateSpinner 的值更改时,将会调用 mOnDateChangedListener 中的方法来处理这个事件。
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 获取了布局文件中 ID 为 hour 和 minute 的 NumberPicker 控件,并将它们分别赋值给 mHourSpinner 和 mMinuteSpinner 变量。然后,使用 setOnValueChangedListener() 方法将 mHourSpinner 和 mMinuteSpinner 控件的值更改监听器分别设置为 mOnHourChangedListener 和 mOnMinuteChangedListener。
// 接下来,使用 setMinValue() 和 setMaxValue() 方法将 mMinuteSpinner 的最小值和最大值分别设置为 MINUT_SPINNER_MIN_VAL 和 MINUT_SPINNER_MAX_VAL以限制分钟选择器的范围。
// 最后,使用 setOnLongPressUpdateInterval() 方法将 mMinuteSpinner 的长按更新间隔设置为 100 毫秒。这意味着当用户长按 mMinuteSpinner 中的增加或减少按钮时,它将以每 100 毫秒的速度连续增加或减少 mMinuteSpinner 的值。
// 通过设置 NumberPicker 的值更改监听器和其他属性,代码实现了一个可以选择小时和分钟的控件,并将它们分别绑定到 mHourSpinner 和 mMinuteSpinner 变量上,以便在之后的操作中使用。
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();//获取了当前设备的日期格式符号,并使用 getAmPmStrings() 方法从中获取 AM/PM 格式符号的字符串数组,并将其赋值给 stringsForAmPm 变量。
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
// 获取了布局文件中 ID 为 amPm 的 NumberPicker 控件,并将其赋值给 mAmPmSpinner 变量。使用 setMinValue() 和 setMaxValue() 方法将 mAmPmSpinner 的最小值和最大值分别设置为 AMPM_SPINNER_MIN_VAL 和 AMPM_SPINNER_MAX_VAL以限制 AM/PM 选择器的范围。
mAmPmSpinner.setDisplayedValues(stringsForAmPm);//使用 setDisplayedValues() 方法将 stringsForAmPm 数组设置为 mAmPmSpinner 的显示值。这意味着 AM/PM 选择器将显示 stringsForAmPm 数组中的字符串,而不是默认的数字值。
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);//使用 setOnValueChangedListener() 方法将 mAmPmSpinner 的值更改监听器设置为 mOnAmPmChangedListener。这意味着当用户更改 AM/PM 选择器的值时,将调用 mOnAmPmChangedListener 中的方法来处理这个事件。
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// 代码调用了 updateDateControl()、updateHourControl() 和 updateAmPmControl() 方法,以便将控件显示为当前日期和时间的值。然后,代码调用了 set24HourView() 方法将选择器的时间格式设置为 24 小时制或 12 小时制。
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
// 调用了 setCurrentDate() 方法将选择器的日期和时间设置为指定的日期和时间,调用 setEnabled() 方法将选择器的可用状态设置为指定的值,并将 mInitialising 标志设置为 false以便通知选择器已完成初始化。
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
// 当调用 setEnabled() 方法时,如果传入的参数 enabled 与当前的 mIsEnabled 变量的值相同,说明选择器的可用状态没有发生变化,直接返回即可。
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
// 代码首先调用父类的 setEnabled() 方法,将整个时间选择器的可用状态设置为传入的参数 enabled。然后分别调用 mDateSpinner、mMinuteSpinner、mHourSpinner 和 mAmPmSpinner 的 setEnabled() 方法,将它们的可用状态也设置为传入的参数 enabled。
//最后,将 mIsEnabled 变量的值设置为传入的参数 enabled以便在下一次调用 setEnabled() 方法时使用。
}
// 重写 setEnabled() 方法,代码实现了一个可以同时设置整个时间选择器的可用状态的功能。
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 当传入的参数 is24HourView 与当前的 mIs24HourView 变量的值相同时,方法直接返回。否则,将 mIs24HourView 变量的值设置为传入的参数 is24HourView表示时间选择器的显示格式已经更改。
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
// 获取当前的小时数并调用 updateHourControl() 方法更新小时选择器,以保持小时选择器的一致性。接着,使用 setCurrentHour() 方法将当前的小时数设置回时间选择器,并调用 updateAmPmControl() 方法更新 AM/PM 选择器。
}
//通过设置 AM/PM 选择器的可见性和更新时间选择器的小时控件和 AM/PM 控件,代码实现了一个可以切换时间选择器显示格式的功能。
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}
// 用于更新日期选择器的显示值。它首先获取当前日期并将其设置为日历实例 cal 的时间。然后cal 被设置为一周的第一天,以确保日期选择器显示一周的日期。
//接下来,使用 setDisplayedValues() 方法将日期选择器的显示值设置为 null以便重新生成新的日期显示值。然后使用循环从 cal 中获取每一天的日期,并将其格式化为 MM.dd EEEE 的格式,并将其存储在一个字符串数组中。最后,使用 setDisplayedValues() 方法将新的日期显示值设置为 mDateSpinner并将当前选择的日期值设置为一周的中间值以保持日期选择器的一致性。
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
// 用于更新 AM/PM 选择器的显示值。如果时间选择器为 24 小时格式,则将 AM/PM 选择器隐藏;否则,根据当前是否为 AM将 AM/PM 选择器的值设置为 Calendar.AM 或 Calendar.PM并将其可见性设置为 View.VISIBLE。
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
// 用于更新小时选择器的显示值。如果时间选择器为 24 小时格式,则将小时选择器的最小值和最大值分别设置为 HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW 和 HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW否则将小时选择器的最小值和最大值分别设置为 HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW 和 HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW。
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,79 @@
// Apache许可证协议
package net.micode.notes.ui;
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;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import java.util.Calendar;
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);
}
// 实现了一个 OnDateTimeSetListener 接口,该接口定义了一个 OnDateTimeSet() 方法,用于在用户选择日期时间后通知调用者。
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());
}
});
// 包含了一个 DateTimePicker 控件,用于让用户选择日期和时间。通过 mDateTimePicker.setOnDateTimeChangedListener() 方法,当用户选择日期和时间时,将更新 mDate 的值,并使用 updateTitle() 方法更新对话框的标题。
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;
}
//set24HourView() 方法,用于设置日期时间选择器是否为 24 小时格式。默认情况下,该值将由系统的设置决定。
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());
}
}
// 重写了 onClick() 方法,以便在用户点击“确定”按钮后,将用户选择的日期时间作为参数传递到 OnDateTimeSet() 方法中,从而通知调用者。
}

@ -0,0 +1,44 @@
// Apache许可证协议
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
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;
// 一个 Button 控件,用于显示菜单的标题,以及一个 PopupMenu 对象,用于显示菜单项。在构造函数中,该类接受一个菜单资源 ID并使用 mPopupMenu.getMenuInflater().inflate() 方法从 XML 资源中加载菜单项,然后将菜单项添加到 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(v -> mPopupMenu.show());
// 重写了 setOnClickListener() 方法,以便在用户点击 Button 控件时显示下拉菜单。
}
// 在 Button 控件上注册一个单击事件监听器,并在该监听器中实现下拉菜单的显示逻辑。
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
// setOnDropdownMenuItemClickListener() 方法,用于设置菜单项的点击监听器。通过这个方法,应用程序可以在用户选择菜单项时执行相应的操作。
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}//findItem() 方法用于查找指定 ID 的菜单项
public void setTitle(CharSequence title) {
mButton.setText(title);
}//用于设置菜单的标题。
}

@ -0,0 +1,70 @@
// Apache许可证协议
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);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
// 用于创建新的视图,该方法返回一个新的 FolderListItem 对象,它是一个自定义的 LinearLayout用于显示文件夹列表项的布局。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
// 用于绑定视图和数据,该方法会将数据从 Cursor 对象中读取出来,并将其绑定到 FolderListItem 视图中。
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 类是一个自定义的 LinearLayout用于显示文件夹列表项的布局。它包含一个 TextView 控件,用于显示文件夹名称。
}
//这段代码是一个自定义的 CursorAdapter 类 FoldersListAdapter用于在 Android 应用程序中显示一个文件夹列表。

@ -0,0 +1,891 @@
// Apache许可证协议
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;
}
// HeadViewHolder类定义了一组视图用于在列表或网格中显示信息。这些视图包括一个TextView用于显示修改信息一个ImageView用于显示警报图标另一个TextView用于显示警报日期还有一个ImageView用于选择背景颜色。
private static final Map<Integer, Integer> 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<Integer, Integer> 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);
}
// sBgSelectorBtnsMap是一个HashMap将用于选择背景颜色的ImageView视图的资源ID映射到整数值这些整数值在ResourceParser类中定义。同样sBgSelectorSelectionMap将整数值映射到所选ImageView视图的资源ID。
private static final Map<Integer, Integer> 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<Integer, Integer> 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);
}
// sFontSizeBtnsMap将用于选择字体大小的LinearLayout视图的资源ID映射到整数值而sFontSelectorSelectionMap将整数值映射到所选ImageView视图的资源ID。
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
*/
// 果用户指定了 Intent.ACTION_VIEW 动作但没有提供ID则跳转到 NotesListActivity
mWorkingNote = null;//在方法中首先将mWorkingNote设置为null。然后根据传入的Intent的动作(action)进行选择。
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
// Intent中获取笔记ID
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
// 将用户查询字符串设置为空
mUserQuery = "";
// 如果Intent包含了搜索结果的额外数据则从额外数据中获取笔记ID并将用户查询字符串设置为搜索管理器中的用户查询字符串
/**
* 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);
}
// 如果笔记在笔记数据库中不存在,则跳转到 NotesListActivity 并显示错误信息的Toast最后结束 Activity 并返回 false
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;
}
}
// 如果该Intent的动作(action)为Intent.ACTION_VIEW则从该Intent中获取笔记的ID(noteId)。如果该Intent还包含了一个搜索结果(Search)的额外数据(extra data key)则将noteId从额外数据中获取并将用户查询字符串(mUserQuery)设置为搜索管理器中的用户查询字符串(SearchManager.USER_QUERY)。如果noteId在笔记数据库中不存在则将Activity转到NotesListActivity并显示一个错误信息的Toast最后结束Activity并返回false。如果noteId在笔记数据库中存在则加载该笔记的工作副本(mWorkingNote)如果加载失败则结束Activity并返回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 如果用户指定了 Intent.ACTION_INSERT_OR_EDIT 动作则获取笔记ID如果笔记ID存在则加载笔记否则创建一个新的笔记
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_INVALID);
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() {
// 使用mFontSizeId设置笔记编辑器的文本外观。
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
// 如果mWorkingNote的CheckListMode为TextNote.MODE_CHECK_LIST则将编辑器转换为列表模式。
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
// 如果mWorkingNote的CheckListMode不是TextNote.MODE_CHECK_LIST则在编辑器中显示笔记内容并使用mUserQuery高亮显示查询结果。
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
// 隐藏背景选择器中所有不在sBgSelectorSelectionMap中的ID的视图。
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
// 设置mHeadViewPanel和mNoteEditorPanel的背景颜色为mWorkingNote的标题背景ID和背景颜色ID。
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() {
// 如果mWorkingNote具有闹钟提醒则检查当前时间是否超过提醒时间如果超过则在标题栏中显示“note_alert_expired”文本否则在标题栏中显示相对时间。
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 {
// 如果mWorkingNote没有闹钟提醒则将标题栏中的提醒日期文本和提醒图标设置为不可见。
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) {
// 如果笔记背景颜色选择器可见并且触摸事件不在笔记背景颜色选择器范围内则隐藏笔记背景颜色选择器并返回true。
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
// 如果字体大小选择器可见并且触摸事件不在字体大小选择器范围内则隐藏字体大小选择器并返回true。
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
// 返回父类的dispatchTouchEvent方法。
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];
// 如果触摸事件的x坐标小于视图的x坐标、大于视图的宽度和x坐标之和、y坐标小于视图的y坐标、或大于视图的高度和y坐标之和则返回false否则返回true。
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);
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);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/*
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
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();
}//onPause()方法在活动即将暂停时被调用。如果有任何更改,它会保存当前笔记,并清除任何设置状态。
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) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unsupported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}//updateWidget()方法更新与当前笔记相关联的小部件。它创建一个意图并根据笔记的小部件类型设置相应的小部件提供程序类。然后它发送一个广播带有小部件ID以更新小部件。
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}//onClick()方法处理UI中各种按钮的点击事件。当单击相应的按钮时它会显示颜色选择器或字体大小选择器。它还根据所选选项更新笔记的字体大小或背景颜色。
@Override
public void onBackPressed() {
if (clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}//onBackPressed()方法在按下返回按钮时被调用。它检查当前是否有任何设置状态处于活动状态,并清除它。然后它保存当前笔记并调用超类实现。
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}//clearSettingState()方法检查当前是否有任何设置状态处于活动状态,并清除它。
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}//onBackgroundColorChanged()方法在选择新的背景颜色时被调用。它更新笔记的背景颜色,并更新颜色选择器的颜色。
@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 {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}//onFontSizeChanged()方法在选择新的字体大小时被调用。它更新笔记的字体大小,并更新字体大小选择器的值。
@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);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
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 ?
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:
break;
}
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);
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}//sendTo(Context context, String info)方法创建一个新的意图并使用Intent.EXTRA_TEXT将信息作为文本传递。它的目的是启动共享对话框让用户分享笔记的内容。
private void createNewNote() {
// 首先,保存当前正在编辑的笔记
saveNote();
// 为了安全起见启动一个新的NoteEditActivity
finish();
// 创建一个新的笔记
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置Intent.ACTION_INSERT_OR_EDIT和Notes.INTENT_EXTRA_FOLDER_ID指示NoteEditActivity启动以插入或编辑笔记并指定所选文件夹的ID。
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
// 设置Intent.EXTRA_TITLE指示NoteEditActivity启动以编辑笔记。
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
// 启动NoteEditActivity
startActivity(intent);
}//createNewNote()方法保存当前正在编辑的笔记然后启动一个新的NoteEditActivity以创建一个新的笔记。通过设置Intent.ACTION_INSERT_OR_EDIT和Notes.INTENT_EXTRA_FOLDER_ID该方法指示NoteEditActivity启动以插入或编辑笔记并指定所选文件夹的ID。
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
}
mWorkingNote.markDeleted(true);
}//deleteCurrentNote()方法删除当前笔记。如果该笔记已存储在数据库中,则将其从数据库中删除。如果应用程序处于同步模式下,则将其移动到垃圾文件夹中,而不是永久删除。无论是删除还是移动,该方法都会将当前笔记标记为已删除。
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
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 {
/**
* 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, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
// onWidgetChanged()方法会在小部件更改时被调用。它会调用updateWidget()方法来更新小部件。
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);
NoteEditText edit;
if (index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();
edit.setSelection(length);
}
// onEditTextDelete(int index, String text)方法会在删除NoteEditText视图时被调用。该方法首先检查是否只有一个NoteEditText视图存在如果是则直接返回。然后该方法会将所有后续视图的索引递减1。接下来该方法会删除指定索引处的NoteEditText视图并将其文本追加到前一个视图的文本中以便将文本合并到一个视图中。最后该方法将焦点设置在前一个视图中并将光标移动到文本末尾。
public void onEditTextEnter(int index, String text) {
// 不应该发生,检查调试
if (index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {
mEditTextList.removeAllViews();
String[] items = text.split("\n");
int index = 0;
for (String item : items) {
if (!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
}
// switchToListMode(String text)方法用于切换到清单模式。该方法首先清除所有视图然后将文本根据换行符分隔为多个条目并将每个条目添加到mEditTextList中。同时该方法会添加一个新的空条目以便用户可以在列表末尾添加新的条目。最后该方法会将焦点设置在最后一个条目上并将编辑器视图隐藏将列表视图显示。
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
// getHighlightQueryResult(String fullText, String userQuery)方法用于标记在搜索查询中匹配的文本。该方法将返回一个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();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
// getListItem(String item, int index)方法用于获取一个清单视图。该方法将从R.layout.note_edit_list_item文件中充气视图。在充气视图之后该方法会将文本添加到NoteEditText视图中并将复选框设置为选中或未选中状态。如果条目已选中则文本将具有删除线。最后该方法将返回该视图。
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);
}
}
// onTextChange(int index, boolean hasText)方法用于在清单视图中标记是否有文本。如果hasText为true则将显示复选框否则将隐藏复选框。
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);
}
}
// onCheckListModeChanged(int oldMode, int newMode)方法用于在清单模式和文本编辑模式之间切换。如果新模式为清单模式,则将文本转换为清单视图;否则,将清单视图转换为文本编辑视图。
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());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
// getWorkingText()方法用于获取当前工作文本。如果当前模式为清单模式,则将所有条目合并为一个字符串,并添加检查框状态。否则,将返回当前编辑器文本。
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();
}
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 void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
}

@ -0,0 +1,200 @@
// Apache许可证协议
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<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
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);
}
@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);
}
// onKeyDown和onKeyUp方法实现了按下和松开键盘按键时的响应。
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
mOnTextViewChangeListener.onTextChange(mIndex, focused || !TextUtils.isEmpty(getText()));
}
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);
}
// 创建上下文菜单,当用户长按控件中的链接时,根据链接的协议类型创建不同的菜单项,点击菜单项后跳转到相应的链接。
}

@ -0,0 +1,210 @@
// Apache许可证协议
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;
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 = "";
}
checkPosition(cursor);
}
private void checkPosition(Cursor cursor) {
mIsLastItem = cursor.isLast();
mIsFirstItem = cursor.isFirst();
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);
}
}

@ -0,0 +1,953 @@
// Apache许可证协议
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
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.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
}
private ListEditState mState;
private BackgroundQueryHandler mBackgroundQueryHandler;
private NotesListAdapter mNotesListAdapter;
private ListView mNotesListView;
private Button mAddNewNote;
private boolean mDispatch;
private int mOriginY;
private int mDispatchY;
private TextView mTitleBar;
private long mCurrentFolderId;
private ContentResolver mContentResolver;
private ModeCallback mModeCallBack;
private static final String TAG = "NotesListActivity";
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
private NoteItemData mFocusNoteDataItem;
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
private static final int REQUEST_CODE_OPEN_NODE = 102;
private static final int REQUEST_CODE_NEW_NODE = 103;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
// 设置背景图片
getWindow().setBackgroundDrawableResource(R.drawable.ice);
initResources();
/**
* Insert an introduction when user firstly use this application
*/
setAppInfoFromRawRes();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
// 这个方法重写了Activity类的onActivityResult()方法。当startActivityForResult()启动的活动结束时它会被调用。它检查结果是否为RESULT_OK并且请求了REQUEST_CODE_OPEN_NODE或REQUEST_CODE_NEW_NODE。如果是则通过调用changeCursor(null)更新笔记列表适配器。否则它调用超类实现的onActivityResult()方法。
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
// 从raw资源中读取介绍文件
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
char[] buf = new char[1024];
int len = 0;
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALID,
ResourceParser.RED);
note.setWorkingText(sb.toString());
if (note.saveNote()) {
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
}
}
}
// 这个方法从一个原始的资源文件中读取介绍文件并将其内容添加到应用程序的SharedPreferences中。如果介绍文件尚未被添加到SharedPreferences中它将被添加并设置一个标志表示文件已被添加。在此之后每次启动应用程序时它将不再读取介绍文件。
@Override
protected void onStart() {
super.onStart();
startAsyncNotesListQuery();
}
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mNotesListView = (ListView) findViewById(R.id.notes_list);
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
mNotesListView.setOnItemLongClickListener(this);
mNotesListAdapter = new NotesListAdapter(this);
mNotesListView.setAdapter(mNotesListAdapter);
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
mAddNewNote.setOnClickListener(this);
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mState = ListEditState.NOTE_LIST;
mModeCallBack = new ModeCallback();
}
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
mNotesListView.setLongClickable(false);
mAddNewNote.setVisibility(View.GONE);
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
updateMenu();
return true;
}
});
return true;
}
// 这是Activity的onCreate()方法它在创建活动时被调用。它设置了视图并调用了setAppInfoFromRawRes()和initNoteListView()方法以初始化应用程序。
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// Update dropdown menu
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item != null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all);
}
}
}//更新下拉菜单中的选项。该方法首先获取当前选中的笔记数量,然后格式化菜单标题,更新下拉菜单的标题,并更新菜单项的状态。
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}//准备操作模式。该方法在操作模式即将被启动时调用,并返回一个布尔值,指示是否应该启动操作模式。
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}//处理操作模式中的菜单项点击事件。该方法在用户点击操作模式中的菜单项时调用,并返回一个布尔值,指示是否已经处理了该事件。
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}//销毁操作模式。该方法在操作模式被销毁时调用并将列表适配器的选择模式设置为false将列表项的长按功能重新启用并显示添加新笔记的按钮。
public void finishActionMode() {
mActionMode.finish();
}//结束操作模式。该方法结束操作模式。
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}//当用户选中或取消选中一个列表项时调用。该方法更新适配器中对应列表项的选中状态并调用updateMenu()方法更新下拉菜单中的选项。
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show();
return true;
}
switch (item.getItemId()) {
case R.id.delete:
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
startQueryDestinationFolders();
break;
default:
return false;
}
return true;
}
// 处理下拉菜单中的菜单项点击事件。该方法在用户点击下拉菜单中的菜单项时调用并根据菜单项的ID执行相应的操作。如果成功处理了菜单项则返回true否则返回false。如果没有选中任何笔记则显示一个Toast提示用户需要先选择笔记。如果选择了“删除”菜单项则弹出一个对话框询问用户是否确定要删除所选笔记。如果选择了“移动”菜单项则启动查询目标文件夹的操作。
}
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
/**
* Minus TitleBar's height
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* @HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
* Note" button. The 94 represents maximum height of the transparent part.
* Notice that, if the background of the button changes, the formula should
* also change. This is very bad, just for the UI designer's strong requirement.
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
}
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[]{
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
// 后台查询处理程序继承自AsyncQueryHandler
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
// 构造函数调用父类AsyncQueryHandler的构造函数
// 查询完成后的回调函数根据token值进行不同的处理
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
// 更新列表Adapter的Cursor
mNotesListAdapter.changeCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
// 显示文件夹列表菜单
showFolderListMenu(cursor);
} else {
// 查询文件夹失败,输出错误信息
Log.e(TAG, "Query folder failed");
}
break;
default:
return;
}
}
}
// 显示文件夹列表菜单
private void showFolderListMenu(Cursor cursor) {
// 创建AlertDialog.Builder
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
// 创建文件夹列表的Adapter
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
// 设置Adapter和点击事件
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 批量移动选中的笔记到指定文件夹
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
// 显示移动笔记的提示信息
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
// 结束ActionMode
mModeCallBack.finishActionMode();
}
});
// 显示AlertDialog
builder.show();
}
// 创建新笔记
private void createNewNote() {
// 创建Intent跳转到NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
// 启动ActivityForResult
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
// 批量删除选中的笔记
private void batchDelete() {
// 创建异步任务
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (true) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// in sync mode, we'll move the deleted note into the trash
// folder
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALID) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
mModeCallBack.finishActionMode();
}
}.execute();
}
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
HashSet<Long> ids = new HashSet<>();
ids.add(folderId);
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALID) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
}
// 打开笔记详情页
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());// 将笔记的id作为参数传递给NoteEditActivity
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);// 启动NoteEditActivity并等待返回结果
}
// 打开文件夹
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId();// 将当前文件夹的id保存到成员变量mCurrentFolderId
startAsyncNotesListQuery();// 异步查询当前文件夹下的笔记列表
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {// 如果打开的是CallRecord文件夹
mState = ListEditState.CALL_RECORD_FOLDER;// 设置状态为CALL_RECORD_FOLDER
mAddNewNote.setVisibility(View.GONE);// 隐藏新建笔记按钮
} else {
mState = ListEditState.SUB_FOLDER;// 设置状态为SUB_FOLDER
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {// 如果打开的是CallRecord文件夹
mTitleBar.setText(R.string.call_record_folder_name);// 将标题栏文本设置为"通话录音"
} else {
mTitleBar.setText(data.getSnippet());// 将标题栏文本设置为当前文件夹的名字
}
mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏
}
// 按钮点击事件
public void onClick(View v) {
if (v.getId() == R.id.btn_new_note) {
createNewNote();// 创建新笔记
}
}
// 显示软键盘
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); // 显示软键盘
}
}
// 隐藏软键盘
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showCreateOrModifyFolderDialog(final boolean create) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput();
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet());
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
Log.e(TAG, "The long click data item is null");
return;
}
} else {
etName.setText("");
builder.setTitle(this.getString(R.string.menu_create_folder));
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName);
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etName);
String name = etName.getText().toString();
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
etName.setSelection(0, etName.length());
return;
}
if (!create) {
if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[]{
String.valueOf(mFocusNoteDataItem.getId())
});
}
} else if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
}
dialog.dismiss();
}
});
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
}
/**
* When the name edit text is null, disable the positive button
*/
etName.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
} else {
positive.setEnabled(true);
}
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}//用于显示一个对话框,让用户输入或修改一个文件夹的名称。
@Override
public void onBackPressed() {
switch (mState) {
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
startAsyncNotesListQuery();
mTitleBar.setVisibility(View.GONE);
break;
case CALL_RECORD_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
mAddNewNote.setVisibility(View.VISIBLE);
mTitleBar.setVisibility(View.GONE);
startAsyncNotesListQuery();
break;
case NOTE_LIST:
super.onBackPressed();
break;
default:
break;
}
}
// 重写返回键的行为,根据当前的状态执行不同的操作,如返回到上一级文件夹、返回到笔记列表、或者直接退出应用程序。
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unsupported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{
appWidgetId
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
// 更新小部件的方法,根据小部件的类型选择不同的小部件提供者,并发送广播更新小部件。
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view);
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete);
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
}
}
};
// 长按文件夹时创建上下文菜单的监听器,菜单包括查看文件夹、删除文件夹和修改文件夹名称等选项。
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null);
}
super.onContextMenuClosed(menu);
}
// 上下文菜单关闭时的操作将监听器设为null。
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null");
return false;
}
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem);
break;
case MENU_FOLDER_DELETE:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_folder));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId());
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false);
break;
default:
break;
}
return true;
}
// 选择上下文菜单选项时的操作,根据所选选项执行不同的操作,如打开文件夹、删除文件夹或修改文件夹名称。
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu);
} else if (mState == ListEditState.SUB_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else {
Log.e(TAG, "Wrong state:" + mState);
}
return true;
}
// 准备选项菜单时的操作,根据当前的状态显示不同的菜单选项,如笔记列表、子文件夹或通话记录文件夹。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// 新增2个背景按键的响应。
case R.id.menu_ice:{
getWindow().setBackgroundDrawableResource(R.drawable.ice);
break;
}
case R.id.menu_star:{
getWindow().setBackgroundDrawableResource(R.drawable.star);
break;
}
case R.id.menu_new_folder: {
showCreateOrModifyFolderDialog(true);
break;
}
case R.id.menu_export_text: {
exportNoteToText();
break;
}
case R.id.menu_new_note: {
createNewNote();
break;
}
case R.id.menu_search:
onSearchRequested();
break;
default:
break;
}
return true;
}
// 选择选项菜单选项时的操作,根据所选选项执行不同的操作,如打开笔记列表、打开子文件夹或打开通话记录文件夹。
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
return true;
}
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText();
}
@Override
protected void onPostExecute(Integer result) {
if (result == BackupUtils.STATE_SD_CARD_UNMOUNTED) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_unmounted));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.success_sdcard_export));
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup
.getExportedTextFileName(), backup.getExportedTextFileDir()));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
}.execute();
}
// ,表示当前是否处于同步模式。它通过调用 NotesPreferenceActivity.getSyncAccountName(this) 获取同步账户名,并检查它的长度是否大于 0 来确定是否启用同步模式。
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) {
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
return;
}
switch (mState) {
case NOTE_LIST:
if (item.getType() == Notes.TYPE_FOLDER
|| item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item);
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST");
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
default:
break;
}
}
}
}
// OnListItemClickListener 是一个内部类,实现了 OnItemClickListener 接口。它覆盖了 onItemClick() 方法,在列表项被单击时执行一系列操作。首先,它检查当前是否处于选择模式。如果是,则根据列表项的类型执行不同的操作。如果不是,则根据当前状态执行不同的操作,如打开文件夹或笔记。
private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection :
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[]{
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLDER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
// 启动后台查询,以获取目标文件夹列表。它构建一个查询选择器,通过调用 mBackgroundQueryHandler.startQuery() 方法执行查询操作。查询的结果将被传递到 FoldersListAdapter 中进行处理。
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
return false;
}
// 在列表项被长按时执行。它首先获取当前长按的列表项,并将其类型存储在 mFocusNoteDataItem 中。如果类型为笔记且不在选择模式下,则启动上下文操作模式,并将当前列表项标记为已选中。如果类型为文件夹,则设置上下文菜单的创建监听器。
}

@ -0,0 +1,171 @@
// Apache许可证协议
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<Integer, Boolean> 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);
}//创建一个新的笔记列表项视图,即 NotesListItem。
@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;
}//设置选择笔记的模式,并清空 mSelectedIndex。
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<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
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;
}//返回选定的笔记的 ID。
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<>();
for (Integer position : mSelectedIndex.keySet()) {
if (Boolean.TRUE.equals(mSelectedIndex.get(position))) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return new HashSet<>();
}
}
}
return itemSet;
}//返回选定的小部件信息。
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (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;
}
}
}//计算笔记数量。
}

@ -0,0 +1,107 @@
// Apache许可证协议
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);
}
// 用于将数据绑定到视图的UI组件上它接受一个NoteItemData对象其中包含有关笔记项的信息以及两个布尔标志choiceMode和checked。
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;
}
}

@ -0,0 +1,35 @@
package net.micode.notes.ui;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import net.micode.notes.R;
/**
*
*/
public class WelcomeActivity extends Activity {
/**
* : Handler
*/
Handler mHandler = new Handler();
/**
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //加载启动界面
setContentView(R.layout.activity_welcome); //加载启动图片
// 当计时结束时跳转至NotesListActivity
mHandler.postDelayed(() -> {
Intent intent = new Intent();
intent.setClass(WelcomeActivity.this, NotesListActivity.class);
startActivity(intent);
finish(); //销毁欢迎页面
}, 2000); // 2 秒后跳转
}
}

@ -1,80 +0,0 @@
// Generated by view binder compiler. Do not edit!
package net.micode.notes.databinding;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.viewbinding.ViewBinding;
import android.viewbinding.ViewBindings;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;
import net.micode.notes.R;
public final class Widget3xBinding implements ViewBinding {
@NonNull
private final FrameLayout rootView;
@NonNull
public final ImageView widgetBgImage;
@NonNull
public final TextView widgetText;
private Widget3xBinding(@NonNull FrameLayout rootView, @NonNull ImageView widgetBgImage,
@NonNull TextView widgetText) {
this.rootView = rootView;
this.widgetBgImage = widgetBgImage;
this.widgetText = widgetText;
}
@Override
@NonNull
public FrameLayout getRoot() {
return rootView;
}
@NonNull
public static Widget3xBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static Widget3xBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.widget_3x, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static Widget3xBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.widget_bg_image;
ImageView widgetBgImage = ViewBindings.findChildViewById(rootView, id);
if (widgetBgImage == null) {
break missingId;
}
id = R.id.widget_text;
TextView widgetText = ViewBindings.findChildViewById(rootView, id);
if (widgetText == null) {
break missingId;
}
return new Widget3xBinding((FrameLayout) rootView, widgetBgImage, widgetText);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}

@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="widget_3x" modulePackage="net.micode.notes" filePath="app\src\main\res\layout\widget_3x.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.FrameLayout"><Targets><Target tag="layout/widget_3x_0" view="FrameLayout"><Expressions/><location startLine="2" startOffset="0" endLine="23" endOffset="13"/></Target><Target id="@+id/widget_bg_image" view="ImageView"><Expressions/><location startLine="7" startOffset="4" endLine="10" endOffset="45"/></Target><Target id="@+id/widget_text" view="TextView"><Expressions/><location startLine="12" startOffset="4" endLine="22" endOffset="33"/></Target></Targets></Layout>

@ -1,4 +1,4 @@
#Thu Jun 08 22:15:52 CST 2023
#Thu Jun 08 22:29:32 CST 2023
net.micode.notes.app-main-9\:/color/primary_text_dark.xml=C\:\\Users\\Admin\\Desktop\\gitProject1\\gitProject1\\src\\Notes\\app\\build\\intermediates\\merged_res\\debug\\color_primary_text_dark.xml.flat
net.micode.notes.app-main-9\:/color/secondary_text_dark.xml=C\:\\Users\\Admin\\Desktop\\gitProject1\\gitProject1\\src\\Notes\\app\\build\\intermediates\\merged_res\\debug\\color_secondary_text_dark.xml.flat
net.micode.notes.app-main-9\:/drawable-hdpi/bg_btn_set_color.png=C\:\\Users\\Admin\\Desktop\\gitProject1\\gitProject1\\src\\Notes\\app\\build\\intermediates\\merged_res\\debug\\drawable-hdpi_bg_btn_set_color.png.flat

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
#Thu Jun 08 22:15:54 CST 2023
#Thu Jun 08 22:29:35 CST 2023
base.0=C\:\\Users\\Admin\\Desktop\\gitProject1\\gitProject1\\src\\Notes\\app\\build\\intermediates\\dex\\debug\\mergeDexDebug\\classes.dex
path.0=classes.dex
renamed.0=classes.dex

@ -40,7 +40,7 @@
19-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:19:22-78
20
21 <application
21-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:21:5-151:19
21-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:21:5-137:19
22 android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
22-->[com.android.support:support-compat:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\bc75f1cc23ddb61cebc1fd590824ad73\transformed\support-compat-28.0.0\AndroidManifest.xml:22:18-91
23 android:debuggable="true"
@ -195,14 +195,14 @@
100-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:101:17-58
101 android:resource="@xml/widget_2x_info" />
101-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:102:17-55
102 </receiver> <!-- 新增3x3大小窗口小部件 -->
102 </receiver>
103 <receiver
103-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:104:9-117:20
104 android:name="net.micode.notes.widget.NoteWidgetProvider_3x"
104 android:name="net.micode.notes.widget.NoteWidgetProvider_4x"
104-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:105:13-57
105 android:exported="true"
105-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:106:13-36
106 android:label="@string/app_widget3x3" >
106 android:label="@string/app_widget4x4" >
106-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:107:13-50
107 <intent-filter>
107-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:94:13-98:29
@ -221,72 +221,44 @@
113-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:100:13-102:58
114 android:name="android.appwidget.provider"
114-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:101:17-58
115 android:resource="@xml/widget_3x_info" />
115 android:resource="@xml/widget_4x_info" />
115-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:102:17-55
116 </receiver>
117 <receiver
117-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:118:9-131:20
118 android:name="net.micode.notes.widget.NoteWidgetProvider_4x"
118-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:119:13-57
119 android:exported="true"
117-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:118:9-124:20
118 android:name="net.micode.notes.ui.AlarmInitReceiver"
118-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:119:13-49
119 android:exported="true" >
119-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:120:13-36
120 android:label="@string/app_widget4x4" >
120-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:121:13-50
121 <intent-filter>
121-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:94:13-98:29
122 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
122-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:95:17-84
122-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:95:25-81
123 <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
123-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:96:17-85
123-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:96:25-82
124 <action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
124-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:97:17-85
124-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:97:25-82
125 </intent-filter>
126
127 <meta-data
127-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:100:13-102:58
128 android:name="android.appwidget.provider"
128-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:101:17-58
129 android:resource="@xml/widget_4x_info" />
129-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:102:17-55
130 </receiver>
131 <receiver
131-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:132:9-138:20
132 android:name="net.micode.notes.ui.AlarmInitReceiver"
132-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:133:13-49
133 android:exported="true" >
133-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:134:13-36
134 <intent-filter>
134-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:135:13-137:29
135 <action android:name="android.intent.action.BOOT_COMPLETED" />
135-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:136:17-79
135-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:136:25-76
136 </intent-filter>
137 </receiver>
138 <receiver
138-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:139:9-141:41
139 android:name="net.micode.notes.ui.AlarmReceiver"
139-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:140:13-45
140 android:process=":remote" />
140-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:141:13-38
141
142 <activity
142-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:143:9-146:78
143 android:name="net.micode.notes.ui.AlarmAlertActivity"
143-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:144:13-50
144 android:launchMode="singleInstance"
144-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:145:13-48
145 android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" />
145-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:146:13-75
146
147 <meta-data
147-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:148:9-150:52
148 android:name="android.app.default_searchable"
148-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:149:13-58
149 android:value=".ui.NoteEditActivity" />
149-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:150:13-49
150 </application>
151
152</manifest>
120 <intent-filter>
120-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:121:13-123:29
121 <action android:name="android.intent.action.BOOT_COMPLETED" />
121-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:122:17-79
121-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:122:25-76
122 </intent-filter>
123 </receiver>
124 <receiver
124-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:125:9-127:41
125 android:name="net.micode.notes.ui.AlarmReceiver"
125-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:126:13-45
126 android:process=":remote" />
126-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:127:13-38
127
128 <activity
128-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:129:9-132:78
129 android:name="net.micode.notes.ui.AlarmAlertActivity"
129-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:130:13-50
130 android:launchMode="singleInstance"
130-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:131:13-48
131 android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" />
131-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:132:13-75
132
133 <meta-data
133-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:134:9-136:52
134 android:name="android.app.default_searchable"
134-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:135:13-58
135 android:value=".ui.NoteEditActivity" />
135-->C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:136:13-49
136 </application>
137
138</manifest>

@ -99,20 +99,6 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver> <!-- 新增3x3大小窗口小部件 -->
<receiver
android:name="net.micode.notes.widget.NoteWidgetProvider_3x"
android:exported="true"
android:label="@string/app_widget3x3" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_3x_info" />
</receiver>
<receiver
android:name="net.micode.notes.widget.NoteWidgetProvider_4x"

@ -99,20 +99,6 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver> <!-- 新增3x3大小窗口小部件 -->
<receiver
android:name="net.micode.notes.widget.NoteWidgetProvider_3x"
android:exported="true"
android:label="@string/app_widget3x3" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_3x_info" />
</receiver>
<receiver
android:name="net.micode.notes.widget.NoteWidgetProvider_4x"

@ -386,10 +386,20 @@
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-it_values-it.arsc.flat",
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-es_values-es.arsc.flat",
"map": [
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-it\\values-it.xml",
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-es\\values-es.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,222,335,443,528,629,757,842,924,1016,1110,1208,1302,1403,1497,1593,1689,1781,1873,1955,2062,2162,2261,2369,2476,2583,2742,2842",
"endColumns": "116,112,107,84,100,127,84,81,91,93,97,93,100,93,95,95,91,91,81,106,99,98,107,106,106,158,99,81",
"endOffsets": "217,330,438,523,624,752,837,919,1011,1105,1203,1297,1398,1492,1588,1684,1776,1868,1950,2057,2157,2256,2364,2471,2578,2737,2837,2919"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-es\\values-es.xml",
"from": {
"startLines": "2",
"startColumns": "4",
@ -400,38 +410,18 @@
"to": {
"startLines": "30",
"startColumns": "4",
"startOffsets": "2906",
"startOffsets": "2924",
"endColumns": "100",
"endOffsets": "3002"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-it\\values-it.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,214,314,423,507,616,741,818,894,986,1080,1174,1268,1370,1464,1561,1667,1759,1851,1932,2038,2146,2244,2348,2453,2560,2723,2823",
"endColumns": "108,99,108,83,108,124,76,75,91,93,93,93,101,93,96,105,91,91,80,105,107,97,103,104,106,162,99,82",
"endOffsets": "209,309,418,502,611,736,813,889,981,1075,1169,1263,1365,1459,1556,1662,1754,1846,1927,2033,2141,2239,2343,2448,2555,2718,2818,2901"
"endOffsets": "3020"
}
}
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-es_values-es.arsc.flat",
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-it_values-it.arsc.flat",
"map": [
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-es\\values-es.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,222,335,443,528,629,757,842,924,1016,1110,1208,1302,1403,1497,1593,1689,1781,1873,1955,2062,2162,2261,2369,2476,2583,2742,2842",
"endColumns": "116,112,107,84,100,127,84,81,91,93,97,93,100,93,95,95,91,91,81,106,99,98,107,106,106,158,99,81",
"endOffsets": "217,330,438,523,624,752,837,919,1011,1105,1203,1297,1398,1492,1588,1684,1776,1868,1950,2057,2157,2256,2364,2471,2578,2737,2837,2919"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-es\\values-es.xml",
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-it\\values-it.xml",
"from": {
"startLines": "2",
"startColumns": "4",
@ -442,9 +432,19 @@
"to": {
"startLines": "30",
"startColumns": "4",
"startOffsets": "2924",
"startOffsets": "2906",
"endColumns": "100",
"endOffsets": "3020"
"endOffsets": "3002"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-it\\values-it.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,214,314,423,507,616,741,818,894,986,1080,1174,1268,1370,1464,1561,1667,1759,1851,1932,2038,2146,2244,2348,2453,2560,2723,2823",
"endColumns": "108,99,108,83,108,124,76,75,91,93,93,93,101,93,96,105,91,91,80,105,107,97,103,104,106,162,99,82",
"endOffsets": "209,309,418,502,611,736,813,889,981,1075,1169,1263,1365,1459,1556,1662,1754,1846,1927,2033,2141,2239,2343,2448,2555,2718,2818,2901"
}
}
]
@ -706,33 +706,44 @@
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-te_values-te.arsc.flat",
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-v21_values-v21.arsc.flat",
"map": [
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-te\\values-te.xml",
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-v21\\values-v21.xml",
"from": {
"startLines": "2",
"startColumns": "4",
"startOffsets": "55",
"endColumns": "100",
"endOffsets": "151"
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,19,20,21,22,24,26,27,28,29,30,32,34,36,38,40,42,43,48,50,52,53,54,56,58,59,60,61,62,63,107,110,154,157,160,162,164,166,169,171,174,175,176,179,180,181,182,183,184,187,188,190,192,194,196,200,202,203,204,205,207,211,213,215,216,217,218,219,221,222,223,233,234,235,247",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "55,146,249,352,457,564,673,782,891,1000,1109,1216,1319,1438,1593,1748,1853,1974,2075,2222,2363,2466,2585,2692,2795,2950,3121,3270,3435,3592,3743,3862,4234,4383,4532,4644,4791,4944,5091,5166,5255,5342,5443,5546,8614,8799,11879,12076,12275,12398,12521,12634,12817,12948,13149,13238,13349,13582,13683,13778,13901,14030,14147,14324,14423,14558,14701,14836,14955,15156,15275,15368,15479,15535,15642,15837,15948,16081,16176,16267,16358,16475,16614,16685,16768,17448,17505,17563,18257",
"endLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,16,18,19,20,21,23,25,26,27,28,29,31,33,35,37,39,41,42,47,49,51,52,53,55,57,58,59,60,61,62,106,109,153,156,159,161,163,165,168,170,173,174,175,178,179,180,181,182,183,186,187,189,191,193,195,199,201,202,203,204,206,210,212,214,215,216,217,218,220,221,222,232,233,234,246,258",
"endColumns": "90,102,102,104,106,108,108,108,108,108,106,102,118,12,12,104,120,100,12,12,102,118,106,102,12,12,12,12,12,12,118,12,12,12,111,146,12,12,74,88,86,100,102,12,12,12,12,12,12,12,12,12,12,12,88,110,12,100,94,122,128,116,12,98,12,12,12,12,12,12,92,110,55,12,12,12,12,94,90,90,116,12,70,82,12,56,57,12,12",
"endOffsets": "141,244,347,452,559,668,777,886,995,1104,1211,1314,1433,1588,1743,1848,1969,2070,2217,2358,2461,2580,2687,2790,2945,3116,3265,3430,3587,3738,3857,4229,4378,4527,4639,4786,4939,5086,5161,5250,5337,5438,5541,8609,8794,11874,12071,12270,12393,12516,12629,12812,12943,13144,13233,13344,13577,13678,13773,13896,14025,14142,14319,14418,14553,14696,14831,14950,15151,15270,15363,15474,15530,15637,15832,15943,16076,16171,16262,16353,16470,16609,16680,16763,17443,17500,17558,18252,18958"
},
"to": {
"startLines": "30",
"startColumns": "4",
"startOffsets": "2946",
"endColumns": "100",
"endOffsets": "3042"
"startLines": "6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,23,24,25,26,28,30,31,32,33,34,36,38,40,42,44,46,47,52,54,56,57,58,60,62,63,64,65,66,67,111,114,158,161,164,166,168,170,173,175,178,179,180,183,184,185,186,187,188,191,192,194,196,198,200,204,206,207,208,209,211,215,217,219,220,221,222,223,225,226,227,237,238,239,251",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "354,445,548,651,756,863,972,1081,1190,1299,1408,1515,1618,1737,1892,2047,2152,2273,2374,2521,2662,2765,2884,2991,3094,3249,3420,3569,3734,3891,4042,4161,4533,4682,4831,4943,5090,5243,5390,5465,5554,5641,5742,5845,8697,8882,11746,11943,12142,12265,12388,12501,12684,12815,13016,13105,13216,13449,13550,13645,13768,13897,14014,14191,14290,14425,14568,14703,14822,15023,15142,15235,15346,15402,15509,15704,15815,15948,16043,16134,16225,16342,16481,16552,16635,17258,17315,17373,17997",
"endLines": "6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,27,29,30,31,32,33,35,37,39,41,43,45,46,51,53,55,56,57,59,61,62,63,64,65,66,110,113,157,160,163,165,167,169,172,174,177,178,179,182,183,184,185,186,187,190,191,193,195,197,199,203,205,206,207,208,210,214,216,218,219,220,221,222,224,225,226,236,237,238,250,262",
"endColumns": "90,102,102,104,106,108,108,108,108,108,106,102,118,12,12,104,120,100,12,12,102,118,106,102,12,12,12,12,12,12,118,12,12,12,111,146,12,12,74,88,86,100,102,12,12,12,12,12,12,12,12,12,12,12,88,110,12,100,94,122,128,116,12,98,12,12,12,12,12,12,92,110,55,12,12,12,12,94,90,90,116,12,70,82,12,56,57,12,12",
"endOffsets": "440,543,646,751,858,967,1076,1185,1294,1403,1510,1613,1732,1887,2042,2147,2268,2369,2516,2657,2760,2879,2986,3089,3244,3415,3564,3729,3886,4037,4156,4528,4677,4826,4938,5085,5238,5385,5460,5549,5636,5737,5840,8692,8877,11741,11938,12137,12260,12383,12496,12679,12810,13011,13100,13211,13444,13545,13640,13763,13892,14009,14186,14285,14420,14563,14698,14817,15018,15137,15230,15341,15397,15504,15699,15810,15943,16038,16129,16220,16337,16476,16547,16630,17253,17310,17368,17992,18628"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-te\\values-te.xml",
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-v21\\values-v21.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,219,328,439,529,634,759,841,923,1014,1107,1203,1297,1398,1491,1586,1681,1772,1863,1947,2060,2168,2267,2378,2480,2597,2763,2864",
"endColumns": "113,108,110,89,104,124,81,81,90,92,95,93,100,92,94,94,90,90,83,112,107,98,110,101,116,165,100,81",
"endOffsets": "214,323,434,524,629,754,836,918,1009,1102,1198,1292,1393,1486,1581,1676,1767,1858,1942,2055,2163,2262,2373,2475,2592,2758,2859,2941"
"startLines": "2,3,4,5,6,7,8,9,10,13",
"startColumns": "4,4,4,4,4,4,4,4,4,4",
"startOffsets": "55,159,223,290,354,470,596,722,850,1022",
"endLines": "2,3,4,5,6,7,8,9,12,17",
"endColumns": "103,63,66,63,115,125,125,127,12,12",
"endOffsets": "154,218,285,349,465,591,717,845,1017,1355"
},
"to": {
"startLines": "2,3,4,5,263,264,265,266,267,270",
"startColumns": "4,4,4,4,4,4,4,4,4,4",
"startOffsets": "55,159,223,290,18633,18749,18875,19001,19129,19301",
"endLines": "2,3,4,5,263,264,265,266,269,274",
"endColumns": "103,63,66,63,115,125,125,127,12,12",
"endOffsets": "154,218,285,349,18744,18870,18996,19124,19296,19634"
}
}
]
@ -770,44 +781,33 @@
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-v21_values-v21.arsc.flat",
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-te_values-te.arsc.flat",
"map": [
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-v21\\values-v21.xml",
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-te\\values-te.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,19,20,21,22,24,26,27,28,29,30,32,34,36,38,40,42,43,48,50,52,53,54,56,58,59,60,61,62,63,107,110,154,157,160,162,164,166,169,171,174,175,176,179,180,181,182,183,184,187,188,190,192,194,196,200,202,203,204,205,207,211,213,215,216,217,218,219,221,222,223,233,234,235,247",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "55,146,249,352,457,564,673,782,891,1000,1109,1216,1319,1438,1593,1748,1853,1974,2075,2222,2363,2466,2585,2692,2795,2950,3121,3270,3435,3592,3743,3862,4234,4383,4532,4644,4791,4944,5091,5166,5255,5342,5443,5546,8614,8799,11879,12076,12275,12398,12521,12634,12817,12948,13149,13238,13349,13582,13683,13778,13901,14030,14147,14324,14423,14558,14701,14836,14955,15156,15275,15368,15479,15535,15642,15837,15948,16081,16176,16267,16358,16475,16614,16685,16768,17448,17505,17563,18257",
"endLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,16,18,19,20,21,23,25,26,27,28,29,31,33,35,37,39,41,42,47,49,51,52,53,55,57,58,59,60,61,62,106,109,153,156,159,161,163,165,168,170,173,174,175,178,179,180,181,182,183,186,187,189,191,193,195,199,201,202,203,204,206,210,212,214,215,216,217,218,220,221,222,232,233,234,246,258",
"endColumns": "90,102,102,104,106,108,108,108,108,108,106,102,118,12,12,104,120,100,12,12,102,118,106,102,12,12,12,12,12,12,118,12,12,12,111,146,12,12,74,88,86,100,102,12,12,12,12,12,12,12,12,12,12,12,88,110,12,100,94,122,128,116,12,98,12,12,12,12,12,12,92,110,55,12,12,12,12,94,90,90,116,12,70,82,12,56,57,12,12",
"endOffsets": "141,244,347,452,559,668,777,886,995,1104,1211,1314,1433,1588,1743,1848,1969,2070,2217,2358,2461,2580,2687,2790,2945,3116,3265,3430,3587,3738,3857,4229,4378,4527,4639,4786,4939,5086,5161,5250,5337,5438,5541,8609,8794,11874,12071,12270,12393,12516,12629,12812,12943,13144,13233,13344,13577,13678,13773,13896,14025,14142,14319,14418,14553,14696,14831,14950,15151,15270,15363,15474,15530,15637,15832,15943,16076,16171,16262,16353,16470,16609,16680,16763,17443,17500,17558,18252,18958"
"startLines": "2",
"startColumns": "4",
"startOffsets": "55",
"endColumns": "100",
"endOffsets": "151"
},
"to": {
"startLines": "6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,23,24,25,26,28,30,31,32,33,34,36,38,40,42,44,46,47,52,54,56,57,58,60,62,63,64,65,66,67,111,114,158,161,164,166,168,170,173,175,178,179,180,183,184,185,186,187,188,191,192,194,196,198,200,204,206,207,208,209,211,215,217,219,220,221,222,223,225,226,227,237,238,239,251",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "354,445,548,651,756,863,972,1081,1190,1299,1408,1515,1618,1737,1892,2047,2152,2273,2374,2521,2662,2765,2884,2991,3094,3249,3420,3569,3734,3891,4042,4161,4533,4682,4831,4943,5090,5243,5390,5465,5554,5641,5742,5845,8697,8882,11746,11943,12142,12265,12388,12501,12684,12815,13016,13105,13216,13449,13550,13645,13768,13897,14014,14191,14290,14425,14568,14703,14822,15023,15142,15235,15346,15402,15509,15704,15815,15948,16043,16134,16225,16342,16481,16552,16635,17258,17315,17373,17997",
"endLines": "6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,27,29,30,31,32,33,35,37,39,41,43,45,46,51,53,55,56,57,59,61,62,63,64,65,66,110,113,157,160,163,165,167,169,172,174,177,178,179,182,183,184,185,186,187,190,191,193,195,197,199,203,205,206,207,208,210,214,216,218,219,220,221,222,224,225,226,236,237,238,250,262",
"endColumns": "90,102,102,104,106,108,108,108,108,108,106,102,118,12,12,104,120,100,12,12,102,118,106,102,12,12,12,12,12,12,118,12,12,12,111,146,12,12,74,88,86,100,102,12,12,12,12,12,12,12,12,12,12,12,88,110,12,100,94,122,128,116,12,98,12,12,12,12,12,12,92,110,55,12,12,12,12,94,90,90,116,12,70,82,12,56,57,12,12",
"endOffsets": "440,543,646,751,858,967,1076,1185,1294,1403,1510,1613,1732,1887,2042,2147,2268,2369,2516,2657,2760,2879,2986,3089,3244,3415,3564,3729,3886,4037,4156,4528,4677,4826,4938,5085,5238,5385,5460,5549,5636,5737,5840,8692,8877,11741,11938,12137,12260,12383,12496,12679,12810,13011,13100,13211,13444,13545,13640,13763,13892,14009,14186,14285,14420,14563,14698,14817,15018,15137,15230,15341,15397,15504,15699,15810,15943,16038,16129,16220,16337,16476,16547,16630,17253,17310,17368,17992,18628"
"startLines": "30",
"startColumns": "4",
"startOffsets": "2946",
"endColumns": "100",
"endOffsets": "3042"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-v21\\values-v21.xml",
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-te\\values-te.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,13",
"startColumns": "4,4,4,4,4,4,4,4,4,4",
"startOffsets": "55,159,223,290,354,470,596,722,850,1022",
"endLines": "2,3,4,5,6,7,8,9,12,17",
"endColumns": "103,63,66,63,115,125,125,127,12,12",
"endOffsets": "154,218,285,349,465,591,717,845,1017,1355"
},
"to": {
"startLines": "2,3,4,5,263,264,265,266,267,270",
"startColumns": "4,4,4,4,4,4,4,4,4,4",
"startOffsets": "55,159,223,290,18633,18749,18875,19001,19129,19301",
"endLines": "2,3,4,5,263,264,265,266,269,274",
"endColumns": "103,63,66,63,115,125,125,127,12,12",
"endOffsets": "154,218,285,349,18744,18870,18996,19124,19296,19634"
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,219,328,439,529,634,759,841,923,1014,1107,1203,1297,1398,1491,1586,1681,1772,1863,1947,2060,2168,2267,2378,2480,2597,2763,2864",
"endColumns": "113,108,110,89,104,124,81,81,90,92,95,93,100,92,94,94,90,90,83,112,107,98,110,101,116,165,100,81",
"endOffsets": "214,323,434,524,629,754,836,918,1009,1102,1198,1292,1393,1486,1581,1676,1767,1858,1942,2055,2163,2262,2373,2475,2592,2758,2859,2941"
}
}
]
@ -868,6 +868,38 @@
}
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-mk_values-mk.arsc.flat",
"map": [
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-mk\\values-mk.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,213,317,425,511,616,735,818,900,997,1096,1193,1293,1400,1499,1600,1696,1793,1884,1971,2077,2184,2285,2392,2503,2607,2763,2861",
"endColumns": "107,103,107,85,104,118,82,81,96,98,96,99,106,98,100,95,96,90,86,105,106,100,106,110,103,155,97,83",
"endOffsets": "208,312,420,506,611,730,813,895,992,1091,1188,1288,1395,1494,1595,1691,1788,1879,1966,2072,2179,2280,2387,2498,2602,2758,2856,2940"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-mk\\values-mk.xml",
"from": {
"startLines": "2",
"startColumns": "4",
"startOffsets": "55",
"endColumns": "100",
"endOffsets": "151"
},
"to": {
"startLines": "30",
"startColumns": "4",
"startOffsets": "2945",
"endColumns": "100",
"endOffsets": "3041"
}
}
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-zh-rTW_values-zh-rTW.arsc.flat",
"map": [
@ -945,38 +977,6 @@
}
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-mk_values-mk.arsc.flat",
"map": [
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\176e2cb87ca60634b7f06bd4bd6aa64f\\transformed\\appcompat-v7-28.0.0\\res\\values-mk\\values-mk.xml",
"from": {
"startLines": "2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29",
"startColumns": "4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4",
"startOffsets": "105,213,317,425,511,616,735,818,900,997,1096,1193,1293,1400,1499,1600,1696,1793,1884,1971,2077,2184,2285,2392,2503,2607,2763,2861",
"endColumns": "107,103,107,85,104,118,82,81,96,98,96,99,106,98,100,95,96,90,86,105,106,100,106,110,103,155,97,83",
"endOffsets": "208,312,420,506,611,730,813,895,992,1091,1188,1288,1395,1494,1595,1691,1788,1879,1966,2072,2179,2280,2387,2498,2602,2758,2856,2940"
}
},
{
"source": "C:\\Users\\Admin\\.gradle\\caches\\transforms-3\\bc75f1cc23ddb61cebc1fd590824ad73\\transformed\\support-compat-28.0.0\\res\\values-mk\\values-mk.xml",
"from": {
"startLines": "2",
"startColumns": "4",
"startOffsets": "55",
"endColumns": "100",
"endOffsets": "151"
},
"to": {
"startLines": "30",
"startColumns": "4",
"startOffsets": "2945",
"endColumns": "100",
"endOffsets": "3041"
}
}
]
},
{
"outputFile": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\values-ky_values-ky.arsc.flat",
"map": [

@ -47,14 +47,14 @@
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\drawable-hdpi_title_alert.png.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\drawable-hdpi\\title_alert.png"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\drawable-hdpi_edit_title_red.9.png.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\drawable-hdpi\\edit_title_red.9.png"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\drawable-hdpi_list_green_middle.9.png.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\drawable-hdpi\\list_green_middle.9.png"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\drawable-hdpi_edit_title_red.9.png.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\drawable-hdpi\\edit_title_red.9.png"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\layout_add_account_text.xml.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\layout\\add_account_text.xml"
@ -159,14 +159,14 @@
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\menu_sub_folder.xml.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\menu\\sub_folder.xml"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\layout_account_dialog_title.xml.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\layout\\account_dialog_title.xml"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\raw-zh-rCN_introduction.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\raw-zh-rCN\\introduction"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\layout_account_dialog_title.xml.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\layout\\account_dialog_title.xml"
},
{
"merged": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-merged_res-7:\\drawable-hdpi_widget_2x_green.png.flat",
"source": "C:\\Users\\Admin\\.gradle\\daemon\\8.0\\net.micode.notes.app-main-9:\\drawable-hdpi\\widget_2x_green.png"

@ -99,20 +99,6 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver> <!-- 新增3x3大小窗口小部件 -->
<receiver
android:name="net.micode.notes.widget.NoteWidgetProvider_3x"
android:exported="true"
android:label="@string/app_widget3x3" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_3x_info" />
</receiver>
<receiver
android:name="net.micode.notes.widget.NoteWidgetProvider_4x"

@ -928,7 +928,6 @@ int layout select_dialog_singlechoice_material 0x7f0a0030
int layout settings_header 0x7f0a0031
int layout support_simple_spinner_dropdown_item 0x7f0a0032
int layout widget_2x 0x7f0a0033
int layout widget_3x 0x7f0a0034
int layout widget_4x 0x7f0a0035
int menu call_note_edit 0x7f0b0000
int menu call_record_folder 0x7f0b0001
@ -1915,5 +1914,4 @@ int styleable ViewStubCompat_android_layout 1
int styleable ViewStubCompat_android_inflatedId 2
int xml searchable 0x7f110001
int xml widget_2x_info 0x7f110002
int xml widget_3x_info 0x7f110003
int xml widget_4x_info 0x7f110004

@ -1,149 +1,138 @@
net.micode.notes:xml/widget_4x_info = 0x7f110004
net.micode.notes:styleable/AppCompatTextHelper = 0x7f10000c
net.micode.notes:styleable/ButtonBarLayout = 0x7f10000f
net.micode.notes:styleable/AnimatedStateListDrawableCompat = 0x7f100007
net.micode.notes:string/success_sync_account = 0x7f0e008b
net.micode.notes:id/useLogo = 0x7f0800c9
net.micode.notes:style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse = 0x7f0f00e2
net.micode.notes:styleable/ActionBarLayout = 0x7f100001
net.micode.notes:style/Base.V28.Theme.AppCompat.Light = 0x7f0f005e
net.micode.notes:string/search_hint = 0x7f0e0084
net.micode.notes:layout/select_dialog_singlechoice_material = 0x7f0a0030
net.micode.notes:styleable/TextAppearance = 0x7f100028
net.micode.notes:style/Widget.AppCompat.ButtonBar.AlertDialog = 0x7f0f012f
net.micode.notes:style/Base.TextAppearance.AppCompat.SearchResult.Subtitle = 0x7f0f001f
net.micode.notes:style/Widget.AppCompat.ListView = 0x7f0f014f
net.micode.notes:style/Widget.AppCompat.SeekBar = 0x7f0f015c
net.micode.notes:styleable/ListPopupWindow = 0x7f10001c
net.micode.notes:style/ThemeOverlay.AppCompat.Dialog = 0x7f0f0119
net.micode.notes:id/right_side = 0x7f080094
net.micode.notes:string/search_hint = 0x7f0e0084
net.micode.notes:style/Widget.AppCompat.ListView.DropDown = 0x7f0f0150
net.micode.notes:style/Widget.Theme.Notes.ButtonBar.Fullscreen = 0x7f0f0169
net.micode.notes:string/menu_select_none = 0x7f0e005f
net.micode.notes:style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small = 0x7f0f00cf
net.micode.notes:id/right_side = 0x7f080094
net.micode.notes:style/Widget.AppCompat.ListView = 0x7f0f014f
net.micode.notes:style/Widget.AppCompat.Button.Small = 0x7f0f012d
net.micode.notes:style/Base.TextAppearance.AppCompat.SearchResult.Subtitle = 0x7f0f001f
net.micode.notes:style/Widget.AppCompat.ButtonBar.AlertDialog = 0x7f0f012f
net.micode.notes:styleable/TextAppearance = 0x7f100028
net.micode.notes:layout/select_dialog_singlechoice_material = 0x7f0a0030
net.micode.notes:style/TextAppearance.AppCompat.SearchResult.Title = 0x7f0f00d4
net.micode.notes:style/Widget.AppCompat.Light.DropDownItem.Spinner = 0x7f0f0146
net.micode.notes:style/Widget.Theme.Notes.ButtonBar.Fullscreen = 0x7f0f0169
net.micode.notes:style/Widget.AppCompat.ListView.DropDown = 0x7f0f0150
net.micode.notes:style/Widget.AppCompat.ProgressBar = 0x7f0f0155
net.micode.notes:style/Theme.AppCompat.Light.Dialog.MinWidth = 0x7f0f010f
net.micode.notes:styleable/PopupWindowBackgroundState = 0x7f100021
net.micode.notes:style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small = 0x7f0f00cf
net.micode.notes:style/Base.V28.Theme.AppCompat.Light = 0x7f0f005e
net.micode.notes:styleable/ActionBarLayout = 0x7f100001
net.micode.notes:xml/widget_4x_info = 0x7f110004
net.micode.notes:style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse = 0x7f0f00e2
net.micode.notes:style/RtlUnderlay.Widget.AppCompat.ActionButton.Overflow = 0x7f0f00be
net.micode.notes:string/preferences_bg_random_appear_title = 0x7f0e0074
net.micode.notes:style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle = 0x7f0f00e1
net.micode.notes:styleable/PopupWindowBackgroundState = 0x7f100021
net.micode.notes:style/ThemeOverlay.AppCompat.Dialog = 0x7f0f0119
net.micode.notes:style/Theme.AppCompat.Light.Dialog.MinWidth = 0x7f0f010f
net.micode.notes:style/Widget.AppCompat.ProgressBar = 0x7f0f0155
net.micode.notes:styleable/AnimatedStateListDrawableCompat = 0x7f100007
net.micode.notes:string/abc_toolbar_collapse_description = 0x7f0e0026
net.micode.notes:style/Widget.AppCompat.SeekBar = 0x7f0f015c
net.micode.notes:style/Widget.AppCompat.Light.SearchView = 0x7f0f014b
net.micode.notes:style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle = 0x7f0f00e1
net.micode.notes:styleable/ButtonBarLayout = 0x7f10000f
net.micode.notes:style/Widget.AppCompat.Spinner = 0x7f0f015e
net.micode.notes:style/Widget.AppCompat.Light.SearchView = 0x7f0f014b
net.micode.notes:style/TextAppearance.AppCompat.Light.SearchResult.Title = 0x7f0f00cd
net.micode.notes:id/line1 = 0x7f080063
net.micode.notes:style/Widget.AppCompat.EditText = 0x7f0f0135
net.micode.notes:style/Base.V7.Theme.AppCompat.Light = 0x7f0f0061
net.micode.notes:id/ll_font_super = 0x7f08006a
net.micode.notes:string/abc_prepend_shortcut_label = 0x7f0e001d
net.micode.notes:style/Widget.AppCompat.Light.ActionMode.Inverse = 0x7f0f0143
net.micode.notes:style/Base.TextAppearance.AppCompat = 0x7f0f000c
net.micode.notes:string/success_sdcard_export = 0x7f0e008a
net.micode.notes:id/menu_export_text = 0x7f08006e
net.micode.notes:style/Widget.AppCompat.Button.Small = 0x7f0f012d
net.micode.notes:style/Widget.AppCompat.PopupMenu = 0x7f0f0152
net.micode.notes:style/Base.TextAppearance.AppCompat = 0x7f0f000c
net.micode.notes:style/TextAppearance.AppCompat.Subhead.Inverse = 0x7f0f00d8
net.micode.notes:style/Widget.AppCompat.CompoundButton.CheckBox = 0x7f0f0130
net.micode.notes:styleable/FontFamily = 0x7f100015
net.micode.notes:style/Widget.AppCompat.ActionBar.Solid = 0x7f0f011e
net.micode.notes:id/scrollIndicatorDown = 0x7f080096
net.micode.notes:style/Base.Widget.AppCompat.CompoundButton.CheckBox = 0x7f0f007a
net.micode.notes:string/preferences_account_title = 0x7f0e0072
net.micode.notes:style/RtlOverlay.DialogWindowTitle.AppCompat = 0x7f0f00ae
net.micode.notes:style/Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large = 0x7f0f0019
net.micode.notes:styleable/View = 0x7f10002a
net.micode.notes:style/Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large = 0x7f0f0019
net.micode.notes:style/Widget.AppCompat.EditText = 0x7f0f0135
net.micode.notes:id/useLogo = 0x7f0800c9
net.micode.notes:string/success_sync_account = 0x7f0e008b
net.micode.notes:id/ll_font_super = 0x7f08006a
net.micode.notes:style/Base.V7.Theme.AppCompat.Light = 0x7f0f0061
net.micode.notes:string/preferences_dialog_select_account_tips = 0x7f0e0079
net.micode.notes:style/RtlOverlay.DialogWindowTitle.AppCompat = 0x7f0f00ae
net.micode.notes:id/scrollIndicatorDown = 0x7f080096
net.micode.notes:style/Base.TextAppearance.AppCompat.Body2 = 0x7f0f000e
net.micode.notes:drawable/font_super = 0x7f070068
net.micode.notes:string/preferences_account_title = 0x7f0e0072
net.micode.notes:style/Base.Widget.AppCompat.CompoundButton.CheckBox = 0x7f0f007a
net.micode.notes:style/Widget.AppCompat.ActionBar.Solid = 0x7f0f011e
net.micode.notes:layout/abc_action_mode_bar = 0x7f0a0004
net.micode.notes:style/Widget.AppCompat.ActionBar.TabView = 0x7f0f0121
net.micode.notes:style/ThemeOverlay.AppCompat.Light = 0x7f0f011b
net.micode.notes:string/menu_delete = 0x7f0e004d
net.micode.notes:style/Base.V7.ThemeOverlay.AppCompat.Dialog = 0x7f0f0063
net.micode.notes:style/Base.Widget.AppCompat.ActionMode = 0x7f0f006f
net.micode.notes:style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar = 0x7f0f014c
net.micode.notes:drawable/notification_action_background = 0x7f070089
net.micode.notes:dimen/tooltip_y_offset_non_touch = 0x7f060075
net.micode.notes:drawable/abc_ic_star_black_16dp = 0x7f07001f
net.micode.notes:style/Widget.Support.CoordinatorLayout = 0x7f0f0167
net.micode.notes:layout/abc_action_mode_bar = 0x7f0a0004
net.micode.notes:style/ThemeOverlay.AppCompat.ActionBar = 0x7f0f0116
net.micode.notes:string/app_widget2x2 = 0x7f0e002c
net.micode.notes:style/ThemeOverlay.AppCompat = 0x7f0f0115
net.micode.notes:style/RtlOverlay.Widget.AppCompat.Search.DropDown.Icon2 = 0x7f0f00b9
net.micode.notes:string/menu_select_title = 0x7f0e0060
net.micode.notes:style/Widget.AppCompat.ActionMode = 0x7f0f0125
net.micode.notes:styleable/GradientColorItem = 0x7f100019
net.micode.notes:string/abc_font_family_display_2_material = 0x7f0e000d
net.micode.notes:drawable/widget_2x_yellow = 0x7f0700a1
net.micode.notes:style/Base.Widget.AppCompat.ListView.DropDown = 0x7f0f008d
net.micode.notes:style/Widget.AppCompat.RatingBar = 0x7f0f0157
net.micode.notes:dimen/notification_big_circle_margin = 0x7f06005d
net.micode.notes:style/Base.Widget.AppCompat.ActionButton = 0x7f0f006c
net.micode.notes:style/Widget.AppCompat.ActivityChooserView = 0x7f0f0126
net.micode.notes:style/Base.ThemeOverlay.AppCompat.Dialog = 0x7f0f004e
net.micode.notes:style/Widget.AppCompat.PopupMenu = 0x7f0f0152
net.micode.notes:style/Theme.AppCompat.DialogWhenLarge = 0x7f0f010a
net.micode.notes:style/TextAppearance.AppCompat.Widget.Switch = 0x7f0f00ed
net.micode.notes:style/Widget.AppCompat.Light.ListView.DropDown = 0x7f0f0148
net.micode.notes:id/menu_export_text = 0x7f08006e
net.micode.notes:styleable/FontFamily = 0x7f100015
net.micode.notes:style/Widget.AppCompat.CompoundButton.CheckBox = 0x7f0f0130
net.micode.notes:style/TextAppearance.AppCompat.Medium.Inverse = 0x7f0f00d1
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.Button.Colored = 0x7f0f0031
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabText = 0x7f0f013c
net.micode.notes:styleable/AnimatedStateListDrawableTransition = 0x7f100009
net.micode.notes:menu/note_list_dropdown = 0x7f0b0004
net.micode.notes:layout/abc_activity_chooser_view = 0x7f0a0006
net.micode.notes:id/search_bar = 0x7f08009a
net.micode.notes:style/Widget.AppCompat.Light.ListView.DropDown = 0x7f0f0148
net.micode.notes:style/TextAppearance.AppCompat.Widget.Switch = 0x7f0f00ed
net.micode.notes:style/Theme.AppCompat.DialogWhenLarge = 0x7f0f010a
net.micode.notes:drawable/font_super = 0x7f070068
net.micode.notes:styleable/GradientColorItem = 0x7f100019
net.micode.notes:style/Widget.AppCompat.ActionMode = 0x7f0f0125
net.micode.notes:style/Widget.AppCompat.Light.AutoCompleteTextView = 0x7f0f0145
net.micode.notes:style/Base.TextAppearance.AppCompat.Body2 = 0x7f0f000e
net.micode.notes:style/TextAppearance.AppCompat.Widget.Button.Borderless.Colored = 0x7f0f00e6
net.micode.notes:style/Platform.ThemeOverlay.AppCompat.Dark = 0x7f0f00a7
net.micode.notes:style/Theme.Notes.Fullscreen = 0x7f0f0114
net.micode.notes:style/TextAppearance.AppCompat.Medium.Inverse = 0x7f0f00d1
net.micode.notes:style/TextAppearance.AppCompat.Widget.TextView.SpinnerItem = 0x7f0f00ee
net.micode.notes:id/textSpacerNoTitle = 0x7f0800b9
net.micode.notes:style/Base.TextAppearance.AppCompat.Title = 0x7f0f0025
net.micode.notes:style/Theme.AppCompat.DayNight.Dialog.MinWidth = 0x7f0f0104
net.micode.notes:style/Widget.AppCompat.AutoCompleteTextView = 0x7f0f0127
net.micode.notes:style/Widget.AppCompat.Button.Borderless = 0x7f0f0129
net.micode.notes:style/Base.TextAppearance.Widget.AppCompat.ExpandedMenu.Item = 0x7f0f0039
net.micode.notes:string/preferences_menu_change_account = 0x7f0e007e
net.micode.notes:id/textSpacerNoButtons = 0x7f0800b8
net.micode.notes:string/abc_font_family_display_2_material = 0x7f0e000d
net.micode.notes:style/Widget.AppCompat.Toolbar = 0x7f0f0163
net.micode.notes:style/Theme.AppCompat.Light.DarkActionBar = 0x7f0f010c
net.micode.notes:style/TextAppearance.AppCompat.Small = 0x7f0f00d5
net.micode.notes:id/collapseActionView = 0x7f080031
net.micode.notes:drawable/bg_btn_set_color = 0x7f070054
net.micode.notes:style/TextAppearance.AppCompat.Small = 0x7f0f00d5
net.micode.notes:id/menu_new_folder = 0x7f080072
net.micode.notes:string/abc_font_family_display_1_material = 0x7f0e000c
net.micode.notes:style/Base.Widget.AppCompat.ListMenuView = 0x7f0f008a
net.micode.notes:string/title_activity_welcome = 0x7f0e0093
net.micode.notes:style/Base.Theme.AppCompat.Light.Dialog.MinWidth = 0x7f0f0048
net.micode.notes:style/TextAppearance.AppCompat.Button = 0x7f0f00c2
net.micode.notes:dimen/tooltip_precise_anchor_extra_offset = 0x7f060072
net.micode.notes:style/Base.Widget.AppCompat.ListMenuView = 0x7f0f008a
net.micode.notes:style/TextAppearance.AppCompat.Menu = 0x7f0f00d2
net.micode.notes:dimen/tooltip_precise_anchor_extra_offset = 0x7f060072
net.micode.notes:string/note_link_tel = 0x7f0e006b
net.micode.notes:style/Base.TextAppearance.Widget.AppCompat.Toolbar.Subtitle = 0x7f0f003a
net.micode.notes:style/TextAppearance.Compat.Notification.Time = 0x7f0f00f2
net.micode.notes:id/add = 0x7f08001c
net.micode.notes:string/format_exported_file_location = 0x7f0e0046
net.micode.notes:style/TextAppearance.AppCompat.Small.Inverse = 0x7f0f00d6
net.micode.notes:id/add = 0x7f08001c
net.micode.notes:raw/introduction = 0x7f0d0000
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabView.Inverse = 0x7f0f013f
net.micode.notes:id/search_bar = 0x7f08009a
net.micode.notes:layout/abc_activity_chooser_view = 0x7f0a0006
net.micode.notes:style/Widget.AppCompat.ActivityChooserView = 0x7f0f0126
net.micode.notes:style/Widget.AppCompat.ActionButton.Overflow = 0x7f0f0124
net.micode.notes:styleable/ViewStubCompat = 0x7f10002c
net.micode.notes:style/Widget.AppCompat.Light.ListPopupWindow = 0x7f0f0147
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabView.Inverse = 0x7f0f013f
net.micode.notes:style/ThemeOverlay.Notes.FullscreenContainer = 0x7f0f011c
net.micode.notes:drawable/abc_ratingbar_indicator_material = 0x7f070036
net.micode.notes:id/iv_bg_red = 0x7f080058
net.micode.notes:raw/introduction = 0x7f0d0000
net.micode.notes:style/TextAppearance.AppCompat.Caption = 0x7f0f00c3
net.micode.notes:style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse = 0x7f0f00e0
net.micode.notes:drawable/abc_ratingbar_indicator_material = 0x7f070036
net.micode.notes:id/iv_bg_red = 0x7f080058
net.micode.notes:style/ThemeOverlay.Notes.FullscreenContainer = 0x7f0f011c
net.micode.notes:string/menu_delete = 0x7f0e004d
net.micode.notes:style/Base.V7.ThemeOverlay.AppCompat.Dialog = 0x7f0f0063
net.micode.notes:style/Widget.Support.CoordinatorLayout = 0x7f0f0167
net.micode.notes:drawable/abc_ic_star_black_16dp = 0x7f07001f
net.micode.notes:dimen/tooltip_y_offset_non_touch = 0x7f060075
net.micode.notes:style/Platform.ThemeOverlay.AppCompat.Dark = 0x7f0f00a7
net.micode.notes:style/TextAppearance.AppCompat.Widget.Button.Borderless.Colored = 0x7f0f00e6
net.micode.notes:style/Theme.Notes.Fullscreen = 0x7f0f0114
net.micode.notes:style/RtlOverlay.Widget.AppCompat.SearchView.MagIcon = 0x7f0f00bc
net.micode.notes:style/Theme.AppCompat.DayNight.DarkActionBar = 0x7f0f0101
net.micode.notes:style/Base.ThemeOverlay.AppCompat.Dialog = 0x7f0f004e
net.micode.notes:menu/note_list_dropdown = 0x7f0b0004
net.micode.notes:styleable/AnimatedStateListDrawableTransition = 0x7f100009
net.micode.notes:style/TextAppearance.AppCompat.Display1 = 0x7f0f00c4
net.micode.notes:string/dummy_content = 0x7f0e0037
net.micode.notes:drawable/edit_title_blue = 0x7f07005d
net.micode.notes:style/RtlUnderlay.Widget.AppCompat.ActionButton = 0x7f0f00bd
net.micode.notes:styleable/CoordinatorLayout = 0x7f100012
net.micode.notes:id/widget_bg_image = 0x7f0800ca
net.micode.notes:layout/abc_select_dialog_material = 0x7f0a001a
net.micode.notes:id/widget_bg_image = 0x7f0800ca
net.micode.notes:style/TextAppearance.AppCompat.Widget.DropDownItem = 0x7f0f00e9
net.micode.notes:style/RtlOverlay.Widget.AppCompat.Search.DropDown.Query = 0x7f0f00ba
net.micode.notes:string/abc_font_family_subhead_material = 0x7f0e0012
@ -158,14 +147,25 @@ net.micode.notes:style/RtlOverlay.Widget.AppCompat.PopupMenuItem = 0x7f0f00b1
net.micode.notes:drawable/edit_blue = 0x7f07005a
net.micode.notes:style/Base.Widget.AppCompat.SearchView = 0x7f0f0097
net.micode.notes:style/RtlOverlay.Widget.AppCompat.DialogTitle.Icon = 0x7f0f00b0
net.micode.notes:styleable/ColorStateListItem = 0x7f100010
net.micode.notes:id/iv_bg_yellow = 0x7f08005c
net.micode.notes:styleable/FullscreenAttrs = 0x7f100017
net.micode.notes:style/Base.TextAppearance.Widget.AppCompat.ExpandedMenu.Item = 0x7f0f0039
net.micode.notes:string/preferences_menu_change_account = 0x7f0e007e
net.micode.notes:id/textSpacerNoButtons = 0x7f0800b8
net.micode.notes:style/Theme.AppCompat.DayNight.NoActionBar = 0x7f0f0106
net.micode.notes:style/RtlOverlay.Widget.AppCompat.ActionBar.TitleItem = 0x7f0f00af
net.micode.notes:drawable/abc_ic_search_api_material = 0x7f07001e
net.micode.notes:styleable/ActionMode = 0x7f100004
net.micode.notes:style/Widget.AppCompat.RatingBar.Indicator = 0x7f0f0158
net.micode.notes:style/ThemeOverlay.AppCompat.Light = 0x7f0f011b
net.micode.notes:string/preferences_bg_random_appear_title = 0x7f0e0074
net.micode.notes:styleable/ColorStateListItem = 0x7f100010
net.micode.notes:styleable/FullscreenAttrs = 0x7f100017
net.micode.notes:id/iv_bg_yellow = 0x7f08005c
net.micode.notes:style/Theme.AppCompat.DayNight.Dialog.MinWidth = 0x7f0f0104
net.micode.notes:style/Widget.AppCompat.AutoCompleteTextView = 0x7f0f0127
net.micode.notes:style/Widget.AppCompat.Button.Borderless = 0x7f0f0129
net.micode.notes:id/textSpacerNoTitle = 0x7f0800b9
net.micode.notes:style/Base.TextAppearance.AppCompat.Title = 0x7f0f0025
net.micode.notes:style/TextAppearance.AppCompat.Widget.TextView.SpinnerItem = 0x7f0f00ee
net.micode.notes:drawable/notification_bg_low_normal = 0x7f07008c
net.micode.notes:drawable/note_edit_color_selector_panel = 0x7f070087
net.micode.notes:style/RtlOverlay.Widget.AppCompat.PopupMenuItem.Text = 0x7f0f00b5
@ -205,8 +205,8 @@ net.micode.notes:style/TextAppearance.AppCompat.Display4 = 0x7f0f00c7
net.micode.notes:style/Base.TextAppearance.AppCompat.Caption = 0x7f0f0010
net.micode.notes:layout/abc_action_menu_item_layout = 0x7f0a0002
net.micode.notes:layout/notification_template_part_time = 0x7f0a002d
net.micode.notes:style/Platform.V25.AppCompat.Light = 0x7f0f00ac
net.micode.notes:style/Widget.AppCompat.RatingBar.Small = 0x7f0f0159
net.micode.notes:style/Platform.V25.AppCompat.Light = 0x7f0f00ac
net.micode.notes:style/Base.Widget.AppCompat.RatingBar.Indicator = 0x7f0f0095
net.micode.notes:style/Base.Widget.AppCompat.Button.Borderless = 0x7f0f0073
net.micode.notes:string/menu_sync_cancel = 0x7f0e0065
@ -232,14 +232,14 @@ net.micode.notes:style/AlertDialog.AppCompat.Light = 0x7f0f0001
net.micode.notes:string/search_menu_title = 0x7f0e0086
net.micode.notes:string/abc_menu_shift_shortcut_label = 0x7f0e001a
net.micode.notes:style/Base.Widget.AppCompat.ActionButton.CloseMode = 0x7f0f006d
net.micode.notes:style/Base.Theme.AppCompat.Dialog = 0x7f0f003e
net.micode.notes:string/format_move_notes_to_folder = 0x7f0e0048
net.micode.notes:id/action_context_bar = 0x7f080010
net.micode.notes:style/TextAppearance.AppCompat.SearchResult.Subtitle = 0x7f0f00d3
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabText.Inverse = 0x7f0f013d
net.micode.notes:style/Base.V26.Theme.AppCompat.Light = 0x7f0f005b
net.micode.notes:id/iv_super_select = 0x7f080061
net.micode.notes:color/abc_primary_text_disable_only_material_dark = 0x7f050008
net.micode.notes:string/format_move_notes_to_folder = 0x7f0e0048
net.micode.notes:style/Base.Theme.AppCompat.Dialog = 0x7f0f003e
net.micode.notes:style/Animation.AppCompat.DropDownUp = 0x7f0f0003
net.micode.notes:style/TextAppearance.AppCompat.Headline = 0x7f0f00c8
net.micode.notes:id/showCustom = 0x7f0800a6
@ -265,13 +265,13 @@ net.micode.notes:id/actions = 0x7f08001a
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.PopupMenu.Large = 0x7f0f0035
net.micode.notes:style/Theme.AppCompat = 0x7f0f00fe
net.micode.notes:drawable/abc_vector_test = 0x7f070053
net.micode.notes:style/Base.Widget.AppCompat.Light.ActionBar.TabView = 0x7f0f0087
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse = 0x7f0f002c
net.micode.notes:menu/note_list = 0x7f0b0003
net.micode.notes:style/Base.Widget.AppCompat.CompoundButton.RadioButton = 0x7f0f007b
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.PopupMenu.Header = 0x7f0f0034
net.micode.notes:style/Base.Widget.AppCompat.Light.ActionBar.TabView = 0x7f0f0087
net.micode.notes:style/TextAppearance.Compat.Notification.Info = 0x7f0f00f0
net.micode.notes:style/Platform.V21.AppCompat.Light = 0x7f0f00aa
net.micode.notes:style/Base.Widget.AppCompat.CompoundButton.RadioButton = 0x7f0f007b
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.PopupMenu.Header = 0x7f0f0034
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.Button.Inverse = 0x7f0f0032
net.micode.notes:string/alert_message_delete_notes = 0x7f0e0029
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse = 0x7f0f002a
@ -292,16 +292,16 @@ net.micode.notes:styleable/SearchView = 0x7f100023
net.micode.notes:style/Base.AlertDialog.AppCompat = 0x7f0f0005
net.micode.notes:id/async = 0x7f080022
net.micode.notes:style/Animation.AppCompat.Tooltip = 0x7f0f0004
net.micode.notes:style/Base.TextAppearance.AppCompat.SearchResult = 0x7f0f001e
net.micode.notes:style/Base.Widget.AppCompat.SeekBar = 0x7f0f0099
net.micode.notes:string/menu_font_size = 0x7f0e0055
net.micode.notes:style/Base.TextAppearance.AppCompat.SearchResult = 0x7f0f001e
net.micode.notes:style/AlertDialog.AppCompat = 0x7f0f0000
net.micode.notes:style/Base.Widget.AppCompat.ActionBar.Solid = 0x7f0f0068
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabBar.Inverse = 0x7f0f013b
net.micode.notes:string/ticker_fail = 0x7f0e0090
net.micode.notes:style/Theme.Notes = 0x7f0f0113
net.micode.notes:style/Base.Widget.AppCompat.Button.Colored = 0x7f0f0076
net.micode.notes:drawable/list_blue_middle = 0x7f07006d
net.micode.notes:string/ticker_fail = 0x7f0e0090
net.micode.notes:style/Theme.Notes = 0x7f0f0113
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabBar.Inverse = 0x7f0f013b
net.micode.notes:string/ticker_cancel = 0x7f0e008f
net.micode.notes:style/Base.V7.Theme.AppCompat.Dialog = 0x7f0f0060
net.micode.notes:string/status_bar_notification_info_overflow = 0x7f0e0089
@ -350,10 +350,10 @@ net.micode.notes:string/set_remind_time_message = 0x7f0e0088
net.micode.notes:style/Widget.AppCompat.Spinner.DropDown = 0x7f0f015f
net.micode.notes:style/Widget.AppCompat.SeekBar.Discrete = 0x7f0f015d
net.micode.notes:style/Widget.AppCompat.Light.ActionButton.Overflow = 0x7f0f0142
net.micode.notes:id/submenuarrow = 0x7f0800af
net.micode.notes:style/Theme.AppCompat.Dialog.MinWidth = 0x7f0f0109
net.micode.notes:style/Widget.AppCompat.PopupWindow = 0x7f0f0154
net.micode.notes:style/TextAppearance.AppCompat.Widget.Button = 0x7f0f00e5
net.micode.notes:id/submenuarrow = 0x7f0800af
net.micode.notes:style/Theme.AppCompat.Dialog.MinWidth = 0x7f0f0109
net.micode.notes:string/abc_search_hint = 0x7f0e001e
net.micode.notes:style/TextAppearance.AppCompat.Widget.Button.Inverse = 0x7f0f00e8
net.micode.notes:id/search_mag_icon = 0x7f08009f
@ -361,8 +361,8 @@ net.micode.notes:drawable/abc_scrubber_control_off_mtrl_alpha = 0x7f070039
net.micode.notes:string/abc_menu_space_shortcut_label = 0x7f0e001b
net.micode.notes:menu/call_note_edit = 0x7f0b0000
net.micode.notes:string/menu_share = 0x7f0e0063
net.micode.notes:string/menu_deselect_all = 0x7f0e004e
net.micode.notes:drawable/edit_white = 0x7f070062
net.micode.notes:string/menu_deselect_all = 0x7f0e004e
net.micode.notes:string/abc_menu_ctrl_shortcut_label = 0x7f0e0015
net.micode.notes:layout/abc_popup_menu_item_layout = 0x7f0a0013
net.micode.notes:style/Widget.AppCompat.Button.ButtonBar.AlertDialog = 0x7f0f012b
@ -424,8 +424,8 @@ net.micode.notes:style/Base.DialogWindowTitle.AppCompat = 0x7f0f000a
net.micode.notes:layout/abc_list_menu_item_icon = 0x7f0a000f
net.micode.notes:id/clip_vertical = 0x7f080030
net.micode.notes:drawable/delete = 0x7f070058
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.DropDownItem = 0x7f0f0033
net.micode.notes:style/Base.Widget.AppCompat.Button = 0x7f0f0072
net.micode.notes:style/Base.TextAppearance.AppCompat.Widget.DropDownItem = 0x7f0f0033
net.micode.notes:layout/abc_expanded_menu_layout = 0x7f0a000d
net.micode.notes:plurals/search_results_title = 0x7f0c0000
net.micode.notes:style/Widget.AppCompat.Light.ActionBar.TabBar = 0x7f0f013a
@ -447,9 +447,9 @@ net.micode.notes:style/Base.Widget.AppCompat.Spinner = 0x7f0f009b
net.micode.notes:id/tv_time = 0x7f0800c4
net.micode.notes:style/Widget.AppCompat.Light.ActionButton.CloseMode = 0x7f0f0141
net.micode.notes:style/ThemeOverlay.AppCompat.Dark = 0x7f0f0117
net.micode.notes:id/tv_name = 0x7f0800c3
net.micode.notes:xml/searchable = 0x7f110001
net.micode.notes:style/Base.Theme.AppCompat.Light.DarkActionBar = 0x7f0f0044
net.micode.notes:id/tv_name = 0x7f0800c3
net.micode.notes:style/Base.Widget.AppCompat.Toolbar.Button.Navigation = 0x7f0f009f
net.micode.notes:id/tv_folder_name = 0x7f0800c1
net.micode.notes:integer/config_tooltipAnimTime = 0x7f090003
@ -485,8 +485,8 @@ net.micode.notes:id/right = 0x7f080092
net.micode.notes:id/progress_horizontal = 0x7f080090
net.micode.notes:dimen/notification_right_icon_size = 0x7f060063
net.micode.notes:id/menu_star = 0x7f080078
net.micode.notes:style/Base.Widget.AppCompat.Light.PopupMenu = 0x7f0f0088
net.micode.notes:id/progress_circular = 0x7f08008f
net.micode.notes:style/Base.Widget.AppCompat.Light.PopupMenu = 0x7f0f0088
net.micode.notes:string/abc_font_family_button_material = 0x7f0e000a
net.micode.notes:styleable/CompoundButton = 0x7f100011
net.micode.notes:id/prefenerece_sync_status_textview = 0x7f08008d
@ -692,8 +692,8 @@ net.micode.notes:drawable/abc_seekbar_track_material = 0x7f070040
net.micode.notes:attr/alertDialogCenterButtons = 0x7f030023
net.micode.notes:drawable/abc_seekbar_tick_mark_material = 0x7f07003f
net.micode.notes:drawable/abc_scrubber_track_mtrl_alpha = 0x7f07003d
net.micode.notes:drawable/abc_item_background_holo_dark = 0x7f070026
net.micode.notes:attr/navigationMode = 0x7f0300ab
net.micode.notes:drawable/abc_item_background_holo_dark = 0x7f070026
net.micode.notes:menu/sub_folder = 0x7f0b0006
net.micode.notes:id/fill_horizontal = 0x7f080043
net.micode.notes:drawable/abc_ratingbar_small_material = 0x7f070038

@ -929,7 +929,6 @@ layout select_dialog_singlechoice_material
layout settings_header
layout support_simple_spinner_dropdown_item
layout widget_2x
layout widget_3x
layout widget_4x
menu call_note_edit
menu call_record_folder
@ -1499,5 +1498,4 @@ styleable ViewBackgroundHelper android_background backgroundTint backgroundTintM
styleable ViewStubCompat android_id android_layout android_inflatedId
xml searchable
xml widget_2x_info
xml widget_3x_info
xml widget_4x_info

@ -1,9 +1,9 @@
-- Merging decision tree log ---
manifest
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
MERGED from [com.android.databinding:viewbinding:8.0.2] C:\Users\Admin\.gradle\caches\transforms-3\5e01bab49cfea4f8ead461781eb78488\transformed\viewbinding-8.0.2\AndroidManifest.xml:2:1-7:12
MERGED from [com.android.support:appcompat-v7:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\176e2cb87ca60634b7f06bd4bd6aa64f\transformed\appcompat-v7-28.0.0\AndroidManifest.xml:17:1-22:12
MERGED from [com.android.support:support-fragment:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\53f9a967fbe4d8886ac406b372ab1e09\transformed\support-fragment-28.0.0\AndroidManifest.xml:17:1-22:12
@ -31,12 +31,12 @@ MERGED from [android.arch.lifecycle:livedata:1.1.1] C:\Users\Admin\.gradle\cache
MERGED from [android.arch.lifecycle:livedata-core:1.1.1] C:\Users\Admin\.gradle\caches\transforms-3\fa6bfb40545b18e04732844c26c91597\transformed\livedata-core-1.1.1\AndroidManifest.xml:17:1-22:12
MERGED from [android.arch.core:runtime:1.1.1] C:\Users\Admin\.gradle\caches\transforms-3\29394f5304979126d1242735f2487979\transformed\runtime-1.1.1\AndroidManifest.xml:17:1-22:12
MERGED from [com.android.support:interpolator:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\dce00f9d154866288b53bdb3705ea6aa\transformed\interpolator-28.0.0\AndroidManifest.xml:17:1-22:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
package
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-153:12
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:2:1-139:12
INJECTED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml
android:versionName
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:5:5-30
@ -157,7 +157,7 @@ ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:19:22-78
application
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:21:5-151:19
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:21:5-137:19
MERGED from [com.android.support:support-compat:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\bc75f1cc23ddb61cebc1fd590824ad73\transformed\support-compat-28.0.0\AndroidManifest.xml:22:5-94
MERGED from [com.android.support:support-compat:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\bc75f1cc23ddb61cebc1fd590824ad73\transformed\support-compat-28.0.0\AndroidManifest.xml:22:5-94
MERGED from [com.android.support:versionedparcelable:28.0.0] C:\Users\Admin\.gradle\caches\transforms-3\d8e7999a1d02387939b86944b25cfbe1\transformed\versionedparcelable-28.0.0\AndroidManifest.xml:22:5-23:19
@ -292,7 +292,7 @@ ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:102:17-55
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:101:17-58
receiver#net.micode.notes.widget.NoteWidgetProvider_3x
receiver#net.micode.notes.widget.NoteWidgetProvider_4x
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:104:9-117:20
android:label
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:107:13-50
@ -300,43 +300,35 @@ ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:106:13-36
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:105:13-57
receiver#net.micode.notes.widget.NoteWidgetProvider_4x
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:118:9-131:20
android:label
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:121:13-50
android:exported
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:120:13-36
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:119:13-57
receiver#net.micode.notes.ui.AlarmInitReceiver
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:132:9-138:20
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:118:9-124:20
android:exported
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:134:13-36
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:120:13-36
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:133:13-49
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:119:13-49
intent-filter#action:name:android.intent.action.BOOT_COMPLETED
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:135:13-137:29
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:121:13-123:29
action#android.intent.action.BOOT_COMPLETED
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:136:17-79
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:122:17-79
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:136:25-76
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:122:25-76
receiver#net.micode.notes.ui.AlarmReceiver
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:139:9-141:41
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:125:9-127:41
android:process
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:141:13-38
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:127:13-38
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:140:13-45
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:126:13-45
activity#net.micode.notes.ui.AlarmAlertActivity
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:143:9-146:78
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:129:9-132:78
android:launchMode
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:145:13-48
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:131:13-48
android:theme
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:146:13-75
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:132:13-75
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:144:13-50
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:130:13-50
meta-data#android.app.default_searchable
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:148:9-150:52
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:134:9-136:52
android:value
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:150:13-49
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:136:13-49
android:name
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:149:13-58
ADDED from C:\Users\Admin\Desktop\gitProject1\gitProject1\src\Notes\app\src\main\AndroidManifest.xml:135:13-58

@ -100,20 +100,6 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver> <!-- 新增3x3大小窗口小部件 -->
<receiver
android:name=".widget.NoteWidgetProvider_3x"
android:exported="true"
android:label="@string/app_widget3x3">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_3x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"

@ -43,7 +43,6 @@ public class Notes {
public static final int TYPE_WIDGET_INVALID = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
public static final int TYPE_WIDGET_3X = 2; // 3x3窗口小部件
/**
*

@ -1,23 +1,13 @@
// Apache许可证协议
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;
/**
*

@ -137,12 +137,6 @@ public class ResourceParser {
R.drawable.widget_2x_red,
};
public static int getWidget3xBgResource(int id) {
return BG_3X_RESOURCES[id];
}
//通过ID获取较小的图片
private static final int[] BG_4X_RESOURCES = new int[]{
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,

@ -49,7 +49,6 @@ 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_3x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
@ -426,8 +425,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
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_3X) {
intent.setClass(this, NoteWidgetProvider_3x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {

@ -55,7 +55,6 @@ import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_3x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
@ -118,8 +117,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
private static final int REQUEST_CODE_OPEN_NODE = 102;
private static final int REQUEST_CODE_NEW_NODE = 103;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -174,7 +173,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@ -188,7 +186,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
return;
}
}
}
@ -357,7 +354,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
start -= mTitleBar.getHeight();
}
/**
* HACKME:When click the transparent part of "New Note" button, dispatch
* @HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
@ -528,13 +525,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
ids.add(folderId);
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
if (true) {
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLDER);
}
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -706,8 +698,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_3X) {
intent.setClass(this, NoteWidgetProvider_3x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {

@ -1,32 +0,0 @@
// Apache许可证协议
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
public class NoteWidgetProvider_3x extends NoteWidgetProvider{ // 3x3大小的窗口小部件
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
@Override // 重写窗口小部件中的获取布局ID函数
protected int getLayoutId() {
return R.layout.widget_3x;
}
@Override // 重写窗口小部件中的获取资源ID函数
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget3xBgResource(bgId);
}
@Override // 重写窗口小部件中的获取宽度类型函数
protected int getWidgetType() {
return Notes.TYPE_WIDGET_3X;
}
}

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="220dip"
android:layout_height="220dip">
<ImageView
android:id="@+id/widget_bg_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<TextView
android:id="@+id/widget_text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:lineSpacingMultiplier="1.2"
android:maxLines="9"
android:paddingLeft="15dip"
android:paddingTop="30dip"
android:paddingRight="15dip"
android:textColor="#FF663300"
android:textSize="14sp" />
</FrameLayout>

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_3x"
android:minWidth="220dip"
android:minHeight="220dip">
</appwidget-provider>
Loading…
Cancel
Save