新增功能代码合并 #32

Merged
pjhil9q6x merged 12 commits from gaochanglei_branch into master 2 months ago

@ -2,9 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"

@ -29,11 +29,13 @@ public class Notes {
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
* {@link Notes#ID_TODO_FOLDER} is to store todo items
*/
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_FOLDER = -3;
public static final int ID_TODO_FOLDER = -4;
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";

@ -273,6 +273,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* create todo folder which is used for todo items
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TODO_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
public void createDataTable(SQLiteDatabase db) {
@ -389,5 +397,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
+ " INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.DELETED_DATE
+ " INTEGER NOT NULL DEFAULT 0");
// Add todo folder
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TODO_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
}

@ -52,6 +52,7 @@ public class NotesProvider extends ContentProvider {
private static final int URI_SEARCH_SUGGEST = 6;
private static final int URI_LIST_SEARCH = 7;
private static final int URI_TIME_SEARCH = 8;
private static final int URI_DELETED_SEARCH = 9;
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@ -62,6 +63,7 @@ public class NotesProvider extends ContentProvider {
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, "note/list_search", URI_LIST_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, "note/time_search", URI_TIME_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, "note/deleted_search", URI_DELETED_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);
}
@ -126,7 +128,27 @@ public class NotesProvider extends ContentProvider {
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.CREATED_DATE + " BETWEEN ? AND ?"
+ " AND " + NoteColumns.PARENT_ID + "=?"
+ " AND " + NoteColumns.DELETED + "=0"
+ " AND " + NoteColumns.DELETED + "=0";
// 用于 NotesListActivity 的回收站搜索查询
private static String NOTES_DELETED_SEARCH_QUERY = "SELECT " + TABLE.NOTE + "." + NoteColumns.ID + ","
+ TABLE.NOTE + "." + NoteColumns.ALERTED_DATE + ","
+ TABLE.NOTE + "." + NoteColumns.BG_COLOR_ID + ","
+ TABLE.NOTE + "." + NoteColumns.CREATED_DATE + ","
+ TABLE.NOTE + "." + NoteColumns.HAS_ATTACHMENT + ","
+ TABLE.NOTE + "." + NoteColumns.MODIFIED_DATE + ","
+ TABLE.NOTE + "." + NoteColumns.NOTES_COUNT + ","
+ TABLE.NOTE + "." + NoteColumns.PARENT_ID + ","
+ TABLE.NOTE + "." + NoteColumns.SNIPPET + ","
+ TABLE.NOTE + "." + NoteColumns.TYPE + ","
+ TABLE.NOTE + "." + NoteColumns.WIDGET_ID + ","
+ TABLE.NOTE + "." + NoteColumns.WIDGET_TYPE + ","
+ TABLE.NOTE + "." + NoteColumns.PINNED + ","
+ TABLE.NOTE + "." + NoteColumns.ENCRYPTED
+ " FROM " + TABLE.NOTE
+ " LEFT JOIN " + TABLE.DATA + " ON " + TABLE.NOTE + "." + NoteColumns.ID + "=" + TABLE.DATA + "." + DataColumns.NOTE_ID + " AND " + TABLE.DATA + "." + DataColumns.MIME_TYPE + "='" + Notes.DataConstants.NOTE + "'"
+ " WHERE (" + NoteColumns.SNIPPET + " LIKE ? OR data." + DataColumns.DATA3 + " LIKE ?)"
+ " AND " + NoteColumns.DELETED + "=1"
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
@Override
@ -221,6 +243,26 @@ public class NotesProvider extends ContentProvider {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
case URI_DELETED_SEARCH:
// 处理 NotesListActivity 的回收站搜索请求
if (projection == null) {
projection = NoteItemData.PROJECTION;
}
String deletedSearchString = uri.getQueryParameter("pattern");
if (TextUtils.isEmpty(deletedSearchString)) {
return null;
}
try {
deletedSearchString = String.format("%%%s%%", deletedSearchString);
c = db.rawQuery(NOTES_DELETED_SEARCH_QUERY,
new String[] { deletedSearchString, deletedSearchString });
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
case URI_TIME_SEARCH:
// 处理 NotesListActivity 的时间搜索请求
if (projection == null) {

@ -29,45 +29,56 @@ 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.EncryptionUtils;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
* WorkingNote 便
* 便
*/
public class WorkingNote {
// Note for the working note
/** 便签对象,用于存储便签的详细数据 */
private Note mNote;
// Note Id
/** 便签 ID */
private long mNoteId;
// Note content
/** 便签内容 */
private String mContent;
// Note title
/** 便签标题 */
private String mTitle;
// Note mode
/** 便签模式(普通模式或 checklist 模式) */
private int mMode;
/** 提醒日期 */
private long mAlertDate;
/** 修改日期 */
private long mModifiedDate;
/** 背景颜色 ID */
private int mBgColorId;
/** 小组件 ID */
private int mWidgetId;
/** 小组件类型 */
private int mWidgetType;
/** 是否置顶 */
private boolean mPinned;
/** 是否加密 */
private boolean mEncrypted;
/** 密码哈希值 */
private String mPasswordHash;
/** 文件夹 ID */
private long mFolderId;
/** 上下文对象 */
private Context mContext;
/** 日志标签 */
private static final String TAG = "WorkingNote";
/** 是否已删除 */
private boolean mIsDeleted;
/** 便签设置变更监听器 */
private NoteSettingChangedListener mNoteSettingStatusListener;
/**
* 便
* IDMIME DATA1-DATA4
*/
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
@ -78,6 +89,10 @@ public class WorkingNote {
DataColumns.DATA4,
};
/**
* 便便
* ID
*/
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
@ -90,66 +105,87 @@ public class WorkingNote {
NoteColumns.PASSWORD_HASH
};
/** 数据列索引ID */
private static final int DATA_ID_COLUMN = 0;
/** 数据列索引:内容 */
private static final int DATA_CONTENT_COLUMN = 1;
/** 数据列索引MIME 类型 */
private static final int DATA_MIME_TYPE_COLUMN = 2;
/** 数据列索引:模式 */
private static final int DATA_MODE_COLUMN = 3;
/** 便签列索引:父文件夹 ID */
private static final int NOTE_PARENT_ID_COLUMN = 0;
/** 便签列索引:提醒日期 */
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
/** 便签列索引:背景颜色 ID */
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
/** 便签列索引:小组件 ID */
private static final int NOTE_WIDGET_ID_COLUMN = 3;
/** 便签列索引:小组件类型 */
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
/** 便签列索引:修改日期 */
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
/** 便签列索引:是否置顶 */
private static final int NOTE_PINNED_COLUMN = 6;
/** 便签列索引:是否加密 */
private static final int NOTE_ENCRYPTED_COLUMN = 7;
/** 便签列索引:密码哈希值 */
private static final int NOTE_PASSWORD_HASH_COLUMN = 8;
// New note construct
/**
* 便
* @param context
* @param folderId ID
*/
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;
mPinned = false;
mEncrypted = false;
mPasswordHash = "";
mTitle = "";
}
// Existing note construct
mContext = context; // 初始化上下文对象
mAlertDate = 0; // 初始化提醒日期为 0
mModifiedDate = System.currentTimeMillis(); // 设置修改日期为当前时间
mFolderId = folderId; // 设置文件夹 ID
mNote = new Note(); // 创建新的 Note 对象
mNoteId = 0; // 新便签 ID 为 0
mIsDeleted = false; // 初始状态为未删除
mMode = 0; // 初始模式为普通模式
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 初始小组件类型为无效
mPinned = false; // 初始状态为未置顶
mEncrypted = false; // 初始状态为未加密
mPasswordHash = ""; // 初始密码哈希值为空
mTitle = ""; // 初始标题为空
}
/**
* 便
* @param context
* @param noteId 便 ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote();
mContext = context; // 初始化上下文对象
mNoteId = noteId; // 设置便签 ID
mFolderId = folderId; // 设置文件夹 ID
mIsDeleted = false; // 初始状态为未删除
mNote = new Note(); // 创建新的 Note 对象
boolean loaded = loadNote(); // 加载便签数据
if (!loaded) {
Log.e(TAG, "Failed to load note with id:" + noteId); // 加载失败时记录错误日志
}
}
private void loadNote() {
/**
* 便
* 便 ID
* @return
*/
private boolean loadNote() {
// 查询便签基本信息
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
boolean success = false; // 初始化加载成功标志
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);
@ -159,75 +195,110 @@ public class WorkingNote {
mPinned = (cursor.getInt(NOTE_PINNED_COLUMN) > 0) ? true : false;
mEncrypted = (cursor.getInt(NOTE_ENCRYPTED_COLUMN) > 0) ? true : false;
mPasswordHash = cursor.getString(NOTE_PASSWORD_HASH_COLUMN);
success = true; // 加载成功
}
cursor.close();
cursor.close(); // 关闭游标
} else {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
Log.e(TAG, "No note with id:" + mNoteId); // 记录错误日志
}
loadNoteData();
if (success) {
return loadNoteData(); // 加载成功后加载便签详细数据
}
return false; // 加载失败
}
private void loadNoteData() {
/**
* 便
* 便
* @return
*/
private boolean loadNoteData() {
// 查询便签详细数据
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
boolean success = false; // 初始化加载成功标志
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);
mTitle = cursor.getString(5); // DATA3 column for title
mTitle = cursor.getString(5); // DATA3 列存储标题(索引 5
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
success = true; // 加载成功
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 处理通话便签数据
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
success = true; // 加载成功
} else {
Log.d(TAG, "Wrong note type with type:" + type);
Log.d(TAG, "Wrong note type with type:" + type); // 记录错误类型日志
}
} while (cursor.moveToNext());
}
cursor.close();
cursor.close(); // 关闭游标
} else {
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
Log.e(TAG, "No data with id:" + mNoteId); // 记录错误日志
}
}
return success; // 返回加载结果
}
/**
* 便
* @param context
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId ID
* @return 便
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
note.setBgColorId(defaultBgColorId); // 设置默认背景颜色
note.setWidgetId(widgetId); // 设置小组件 ID
note.setWidgetType(widgetType); // 设置小组件类型
return note;
}
/**
* ID 便
* @param context
* @param id 便 ID
* @return 便
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
* 便
* @return
*/
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
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);
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();
mNoteSettingStatusListener.onWidgetChanged(); // 通知小组件变更
}
return true;
} else {
@ -235,197 +306,341 @@ public class WorkingNote {
}
}
/**
* 便
* @return
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
* 便
* @return
*/
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
// 已删除、新建且内容为空、已存在且未修改的便签不值得保存
return false;
} else {
return true;
}
}
/**
* 便
* @param l
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
/**
*
* @param date
* @param set
*/
public void setAlertDate(long date, boolean set) {
Log.d(TAG, "setAlertDate: date=" + date + ", set=" + set);
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
mAlertDate = date; // 设置提醒日期
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); // 同步到 Note 对象
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
mNoteSettingStatusListener.onClockAlertChanged(date, set); // 通知监听器
}
}
/**
* 便
* @param mark
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
mIsDeleted = mark; // 设置删除状态
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
mNoteSettingStatusListener.onWidgetChanged(); // 通知监听器
}
}
/**
* 便
* @return
*/
public boolean isDeleted() {
return mIsDeleted;
}
/**
* ID
* @param id ID
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
if (id != mBgColorId) { // 检查颜色是否变更
mBgColorId = id; // 设置背景颜色 ID
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
mNoteSettingStatusListener.onBackgroundColorChanged(); // 通知监听器
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); // 同步到 Note 对象
}
}
/**
* 便
* @param mode checklist
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mMode != mode) { // 检查模式是否变更
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); // 通知监听器
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
mMode = mode; // 设置模式
mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); // 同步到 Note 对象
}
}
/**
*
* @param type
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
if (type != mWidgetType) { // 检查类型是否变更
mWidgetType = type; // 设置小组件类型
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); // 同步到 Note 对象
}
}
/**
* ID
* @param id ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
if (id != mWidgetId) { // 检查 ID 是否变更
mWidgetId = id; // 设置小组件 ID
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); // 同步到 Note 对象
}
}
/**
* 便
* @param text 便
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
if (!TextUtils.equals(mContent, text)) { // 检查内容是否变更
mContent = text; // 设置便签内容
mNote.setTextData(DataColumns.CONTENT, mContent); // 同步到 Note 对象
mModifiedDate = System.currentTimeMillis(); // 更新修改日期
}
}
/**
* 便
* @param title 便
*/
public void setTitle(String title) {
if (!TextUtils.equals(mTitle, title)) {
mTitle = title;
mNote.setTextData(DataColumns.DATA3, mTitle);
if (!TextUtils.equals(mTitle, title)) { // 检查标题是否变更
mTitle = title; // 设置便签标题
mNote.setTextData(DataColumns.DATA3, mTitle); // 同步到 Note 对象
mModifiedDate = System.currentTimeMillis(); // 更新修改日期
}
}
/**
* 便
* @return 便
*/
public String getTitle() {
return mTitle;
}
/**
* 便便
* @param phoneNumber
* @param callDate
*/
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); // 设置通话日期
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); // 设置电话号码
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); // 设置父文件夹为通话记录文件夹
}
/**
* 便
* @return
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
/**
* 便
* @return 便
*/
public String getContent() {
return mContent;
}
/**
*
* @return
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
* @return ID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* ID
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
* @return ID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
* 便
* @return 便
*/
public int getCheckListMode() {
return mMode;
}
/**
* 便 ID
* @return 便 ID
*/
public long getNoteId() {
return mNoteId;
}
/**
* ID
* @return ID
*/
public long getFolderId() {
return mFolderId;
}
/**
* ID
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
*
* @return
*/
public int getWidgetType() {
return mWidgetType;
}
/**
* 便
* @return
*/
public boolean isPinned() {
return mPinned;
}
/**
* 便
* @param pinned
*/
public void setPinned(boolean pinned) {
if (mPinned != pinned) {
mPinned = pinned;
mNote.setNoteValue(NoteColumns.PINNED, String.valueOf(mPinned ? 1 : 0));
if (mPinned != pinned) { // 检查置顶状态是否变更
mPinned = pinned; // 设置置顶状态
mNote.setNoteValue(NoteColumns.PINNED, String.valueOf(mPinned ? 1 : 0)); // 同步到 Note 对象
}
}
/**
* 便
* @return
*/
public boolean isEncrypted() {
return mEncrypted;
}
/**
* 便
* @param encrypted
*/
public void setEncrypted(boolean encrypted) {
if (mEncrypted != encrypted) {
mEncrypted = encrypted;
mNote.setNoteValue(NoteColumns.ENCRYPTED, String.valueOf(mEncrypted ? 1 : 0));
if (mEncrypted != encrypted) { // 检查加密状态是否变更
mEncrypted = encrypted; // 设置加密状态
mNote.setNoteValue(NoteColumns.ENCRYPTED, String.valueOf(mEncrypted ? 1 : 0)); // 同步到 Note 对象
}
}
/**
*
* @param passwordHash
*/
public void setPasswordHash(String passwordHash) {
mPasswordHash = passwordHash;
mNote.setNoteValue(NoteColumns.PASSWORD_HASH, mPasswordHash);
mPasswordHash = passwordHash; // 设置密码哈希值
mNote.setNoteValue(NoteColumns.PASSWORD_HASH, mPasswordHash); // 同步到 Note 对象
}
/**
*
* @param password
* @return
*/
public boolean verifyPassword(String password) {
return net.micode.notes.tool.EncryptionUtils.generatePasswordHash(password).equals(mPasswordHash);
}
/**
* 便
* 便
*/
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
* 便
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*
* @param date
* @param set
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
* 便
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
* checklist
* @param oldMode
* @param newMode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}

@ -47,32 +47,39 @@ public class DataUtils {
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
Log.d(TAG, "Batch deleting notes, ids: " + ids.toString());
long currentTime = System.currentTimeMillis();
boolean allSuccess = true;
// 为每个ID单独执行删除操作这样即使某个ID删除失败也不会影响其他ID的删除
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.DELETED, 1);
builder.withValue(NoteColumns.DELETED_DATE, currentTime);
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.toString());
return false;
// 创建ContentValues对象设置要更新的值
ContentValues values = new ContentValues();
values.put(NoteColumns.DELETED, 1);
values.put(NoteColumns.DELETED_DATE, currentTime);
// 直接使用ContentResolver的update方法执行更新操作
int rowsUpdated = resolver.update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id),
values,
null,
null);
// 检查更新是否成功
if (rowsUpdated == 0) {
Log.e(TAG, "Failed to delete note with id: " + id);
allSuccess = false;
} else {
Log.d(TAG, "Successfully deleted note with id: " + id);
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
return allSuccess;
}
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {

@ -19,17 +19,26 @@ package net.micode.notes.tool;
import android.util.Base64;
import android.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
public class EncryptionUtils {
private static final String TAG = "EncryptionUtils";
private static final String ENCRYPTION_ALGORITHM = "AES";
private static final String CIPHER_TRANSFORMATION = "AES/ECB/PKCS5Padding";
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA256";
private static final int KEY_SIZE = 256;
private static final int IV_SIZE = 16;
private static final int SALT_SIZE = 16;
private static final int ITERATION_COUNT = 10000;
/**
* SHA-256
@ -39,9 +48,9 @@ public class EncryptionUtils {
public static String generatePasswordHash(String password) {
try {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] hash = digest.digest(password.getBytes());
byte[] hash = digest.digest(password.getBytes("UTF-8"));
return Base64.encodeToString(hash, Base64.NO_WRAP);
} catch (NoSuchAlgorithmException e) {
} catch (Exception e) {
Log.e(TAG, "Error generating password hash", e);
return null;
}
@ -51,15 +60,26 @@ public class EncryptionUtils {
*
* @param input
* @param password
* @return
* @return salt:iv:encrypted
*/
public static String encrypt(String input, String password) {
try {
SecretKeySpec keySpec = new SecretKeySpec(generateKey(password), ENCRYPTION_ALGORITHM);
// 生成随机盐值
byte[] salt = generateSalt();
// 生成随机IV
byte[] iv = generateIV();
// 从密码生成密钥
SecretKey secretKey = generateKey(password, salt);
// 初始化加密器
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(input.getBytes());
return Base64.encodeToString(encrypted, Base64.NO_WRAP);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
// 加密数据
byte[] encrypted = cipher.doFinal(input.getBytes("UTF-8"));
// 组合结果salt:iv:encrypted
String saltBase64 = Base64.encodeToString(salt, Base64.NO_WRAP);
String ivBase64 = Base64.encodeToString(iv, Base64.NO_WRAP);
String encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP);
return saltBase64 + ":" + ivBase64 + ":" + encryptedBase64;
} catch (Exception e) {
Log.e(TAG, "Error encrypting data", e);
return null;
@ -68,36 +88,67 @@ public class EncryptionUtils {
/**
*
* @param encrypted
* @param encrypted salt:iv:encrypted
* @param password
* @return
*/
public static String decrypt(String encrypted, String password) {
try {
SecretKeySpec keySpec = new SecretKeySpec(generateKey(password), ENCRYPTION_ALGORITHM);
// 解析加密字符串
String[] parts = encrypted.split(":");
if (parts.length != 3) {
Log.e(TAG, "Invalid encrypted string format");
return null;
}
// 解码各部分
byte[] salt = Base64.decode(parts[0], Base64.NO_WRAP);
byte[] iv = Base64.decode(parts[1], Base64.NO_WRAP);
byte[] encryptedData = Base64.decode(parts[2], Base64.NO_WRAP);
// 从密码生成密钥
SecretKey secretKey = generateKey(password, salt);
// 初始化解密器
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.decode(encrypted, Base64.NO_WRAP);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// 解密数据
byte[] decrypted = cipher.doFinal(encryptedData);
return new String(decrypted, "UTF-8");
} catch (Exception e) {
Log.e(TAG, "Error decrypting data", e);
Log.e(TAG, "Error decrypting data: " + e.getMessage(), e);
return null;
}
}
/**
* AES
*
* @return
*/
private static byte[] generateSalt() {
byte[] salt = new byte[SALT_SIZE];
new SecureRandom().nextBytes(salt);
return salt;
}
/**
* IV
* @return IV
*/
private static byte[] generateIV() {
byte[] iv = new byte[IV_SIZE];
new SecureRandom().nextBytes(iv);
return iv;
}
/**
*
* @param password
* @return
* @throws NoSuchAlgorithmException
* @param salt
* @return
* @throws Exception
*/
private static byte[] generateKey(String password) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] hash = digest.digest(password.getBytes());
// AES密钥长度必须为16字节128位
byte[] key = new byte[16];
System.arraycopy(hash, 0, key, 0, 16);
return key;
private static SecretKey generateKey(String password, byte[] salt) throws Exception {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_SIZE);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
return new SecretKeySpec(keyBytes, ENCRYPTION_ALGORITHM);
}
}
}

@ -0,0 +1,359 @@
/*
* 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.tool;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class ImageUtils {
private static final String TAG = "ImageUtils";
private static final String IMAGE_DIR = "Notes/Images";
private static final String IMAGE_FORMAT = ".jpg";
private static final int MAX_IMAGE_WIDTH = 1024;
private static final int MAX_IMAGE_HEIGHT = 1024;
private static final int MAX_IMAGE_SIZE = 1024 * 1024; // 1MB
/**
*
* @param context
* @return
*/
public static Bitmap getImageFromClipboard(Context context) {
try {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard == null || !clipboard.hasPrimaryClip()) {
Log.d(TAG, "剪贴板为空");
return null;
}
android.content.ClipData clip = clipboard.getPrimaryClip();
if (clip == null || clip.getItemCount() == 0) {
Log.d(TAG, "剪贴板内容为空");
return null;
}
Log.d(TAG, "剪贴板项目数量: " + clip.getItemCount());
// 检查剪贴板描述
android.content.ClipDescription description = clipboard.getPrimaryClipDescription();
if (description != null) {
Log.d(TAG, "剪贴板描述: " + description.toString());
for (int i = 0; i < description.getMimeTypeCount(); i++) {
Log.d(TAG, "MIME类型 " + i + ": " + description.getMimeType(i));
}
}
for (int i = 0; i < clip.getItemCount(); i++) {
android.content.ClipData.Item item = clip.getItemAt(i);
Log.d(TAG, "处理剪贴板项目 " + i);
// 尝试从URI加载图片标准方式
if (item.getUri() != null) {
Log.d(TAG, "尝试从URI加载图片: " + item.getUri().toString());
try {
Bitmap bitmap = BitmapFactory.decodeStream(
context.getContentResolver().openInputStream(item.getUri()));
if (bitmap != null) {
Log.d(TAG, "成功从URI加载图片: " + bitmap.getWidth() + "x" + bitmap.getHeight());
return bitmap;
} else {
Log.e(TAG, "从URI加载图片失败");
}
} catch (Exception e) {
Log.e(TAG, "从URI加载图片时出错: " + e.getMessage());
e.printStackTrace();
}
} else {
Log.d(TAG, "未找到URI");
}
// 尝试获取Intent可能包含微信截屏的图片信息
if (item.getIntent() != null) {
Log.d(TAG, "找到Intent: " + item.getIntent().toString());
try {
android.content.Intent intent = item.getIntent();
// 检查Intent是否包含图片信息
if (intent.hasExtra(android.content.Intent.EXTRA_STREAM)) {
android.net.Uri uri = (android.net.Uri) intent.getParcelableExtra(android.content.Intent.EXTRA_STREAM);
if (uri != null) {
Log.d(TAG, "从Intent中找到图片URI: " + uri.toString());
Bitmap bitmap = BitmapFactory.decodeStream(
context.getContentResolver().openInputStream(uri));
if (bitmap != null) {
Log.d(TAG, "成功从Intent加载图片: " + bitmap.getWidth() + "x" + bitmap.getHeight());
return bitmap;
}
}
}
} catch (Exception e) {
Log.e(TAG, "从Intent加载图片时出错: " + e.getMessage());
e.printStackTrace();
}
} else {
Log.d(TAG, "未找到Intent");
}
// 尝试从剪贴板直接获取Bitmap针对某些应用的特殊处理
try {
// 注意这是一个尝试因为ClipData.Item没有直接的getBitmap方法
// 但某些应用可能会以特殊方式存储Bitmap
Log.d(TAG, "尝试其他方式从剪贴板获取图片");
// 这里可以添加针对特定应用的特殊处理
} catch (Exception e) {
Log.e(TAG, "尝试其他方式获取图片时出错: " + e.getMessage());
e.printStackTrace();
}
}
Log.d(TAG, "无法从剪贴板获取图片");
return null;
} catch (Exception e) {
Log.e(TAG, "从剪贴板获取图片时出错: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
*
* @param context
* @param bitmap
* @return
*/
public static String saveImage(Context context, Bitmap bitmap) {
if (bitmap == null) {
Log.e(TAG, "Bitmap is null");
return null;
}
Log.d(TAG, "开始保存图片,尺寸: " + bitmap.getWidth() + "x" + bitmap.getHeight());
// 创建图片存储目录
File imageDir;
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
// Android 10+ 使用应用私有存储
imageDir = new File(context.getExternalFilesDir(null), IMAGE_DIR);
Log.d(TAG, "使用应用私有存储: " + imageDir.getAbsolutePath());
} else {
// Android 9 及以下使用外部存储
imageDir = new File(Environment.getExternalStorageDirectory(), IMAGE_DIR);
Log.d(TAG, "使用外部存储: " + imageDir.getAbsolutePath());
}
if (!imageDir.exists()) {
Log.d(TAG, "创建图片存储目录: " + imageDir.getAbsolutePath());
if (!imageDir.mkdirs()) {
Log.e(TAG, "Failed to create image directory");
return null;
}
}
// 生成唯一的文件名
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String fileName = "IMG_" + timeStamp + IMAGE_FORMAT;
File imageFile = new File(imageDir, fileName);
Log.d(TAG, "图片保存路径: " + imageFile.getAbsolutePath());
// 压缩图片
Log.d(TAG, "开始压缩图片");
Bitmap compressedBitmap = compressBitmap(bitmap);
Log.d(TAG, "压缩后图片尺寸: " + compressedBitmap.getWidth() + "x" + compressedBitmap.getHeight());
// 保存图片
Log.d(TAG, "开始写入图片文件");
FileOutputStream fos = new FileOutputStream(imageFile);
boolean compressed = compressedBitmap.compress(Bitmap.CompressFormat.JPEG, 85, fos);
Log.d(TAG, "图片压缩结果: " + compressed);
fos.flush();
fos.close();
Log.d(TAG, "图片保存成功");
// 释放资源
if (compressedBitmap != bitmap) {
compressedBitmap.recycle();
}
Log.d(TAG, "图片保存路径: " + imageFile.getAbsolutePath());
return imageFile.getAbsolutePath();
} catch (Exception e) {
Log.e(TAG, "Error saving image: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
* URI
* @param context
* @param uri URI
* @return
*/
public static String saveImage(Context context, android.net.Uri uri) {
if (uri == null) {
Log.e(TAG, "URI is null");
return null;
}
Log.d(TAG, "开始从URI保存图片: " + uri.toString());
try {
// 从URI加载图片
Bitmap bitmap = BitmapFactory.decodeStream(
context.getContentResolver().openInputStream(uri));
if (bitmap == null) {
Log.e(TAG, "Failed to decode bitmap from URI");
return null;
}
// 使用现有的saveImage方法保存图片
return saveImage(context, bitmap);
} catch (Exception e) {
Log.e(TAG, "Error saving image from URI: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
*
* @param bitmap
* @return
*/
private static Bitmap compressBitmap(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
// 计算缩放比例
float scaleWidth = ((float) MAX_IMAGE_WIDTH) / width;
float scaleHeight = ((float) MAX_IMAGE_HEIGHT) / height;
float scale = Math.min(scaleWidth, scaleHeight);
// 如果图片不需要压缩,直接返回
if (scale >= 1.0f) {
return bitmap;
}
// 缩放图片
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
}
/**
*
* @param path
* @return
*/
public static Bitmap loadImage(String path) {
if (path == null || path.isEmpty()) {
Log.e(TAG, "Image path is null or empty");
return null;
}
Log.d(TAG, "开始加载图片: " + path);
File imageFile = new File(path);
if (!imageFile.exists()) {
Log.e(TAG, "Image file does not exist: " + path);
return null;
}
Log.d(TAG, "图片文件存在,大小: " + imageFile.length() + " bytes");
try {
// 解码图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
Log.d(TAG, "图片原始尺寸: " + options.outWidth + "x" + options.outHeight);
// 计算缩放比例
int scale = 1;
while (options.outWidth / scale > MAX_IMAGE_WIDTH || options.outHeight / scale > MAX_IMAGE_HEIGHT) {
scale *= 2;
}
Log.d(TAG, "图片缩放比例: " + scale);
// 加载缩放后的图片
options.inJustDecodeBounds = false;
options.inSampleSize = scale;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
if (bitmap != null) {
Log.d(TAG, "成功加载图片,尺寸: " + bitmap.getWidth() + "x" + bitmap.getHeight());
} else {
Log.e(TAG, "Failed to decode bitmap from file: " + path);
}
return bitmap;
} catch (Exception e) {
Log.e(TAG, "Error loading image: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
*
* @param path
* @return
*/
public static boolean deleteImage(String path) {
if (path == null || path.isEmpty()) {
return false;
}
File imageFile = new File(path);
if (!imageFile.exists()) {
return true;
}
return imageFile.delete();
}
/**
*
* @param path
* @return
*/
public static int getImageOrientation(String path) {
try {
ExifInterface exif = new ExifInterface(path);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
return orientation;
} catch (Exception e) {
Log.e(TAG, "Error getting image orientation: " + e.getMessage());
return ExifInterface.ORIENTATION_NORMAL;
}
}
}

File diff suppressed because it is too large Load Diff

@ -16,7 +16,12 @@
package net.micode.notes.ui;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
@ -31,11 +36,16 @@ import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.tool.ImageUtils;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
@ -52,6 +62,37 @@ public class NoteEditText extends EditText {
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
*
* @param text
* @return
*/
private boolean isImagePath(String text) {
if (text == null || text.isEmpty()) {
return false;
}
// 检查文本是否是有效的文件路径
File file = new File(text);
if (!file.exists()) {
return false;
}
// 检查文件是否是图片类型
String fileName = file.getName();
String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
// 常见的图片文件扩展名
String[] imageExtensions = {"jpg", "jpeg", "png", "gif", "bmp", "webp"};
for (String ext : imageExtensions) {
if (extension.equals(ext)) {
return true;
}
}
return false;
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
@ -75,7 +116,18 @@ public class NoteEditText extends EditText {
void onTextChange(int index, boolean hasText);
}
/**
* Call by the {@link NoteEditActivity} to update format buttons state
*/
public interface OnSelectionChangedListener {
/**
* Called when the selection in the edit text changes
*/
void onSelectionChanged(int start, int end);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
private OnSelectionChangedListener mOnSelectionChangedListener;
public NoteEditText(Context context) {
super(context, null);
@ -90,6 +142,18 @@ public class NoteEditText extends EditText {
mOnTextViewChangeListener = listener;
}
public void setOnSelectionChangedListener(OnSelectionChangedListener listener) {
mOnSelectionChangedListener = listener;
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
if (mOnSelectionChangedListener != null) {
mOnSelectionChangedListener.onSelectionChanged(selStart, selEnd);
}
}
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
@ -214,4 +278,529 @@ public class NoteEditText extends EditText {
}
super.onCreateContextMenu(menu);
}
@Override
public boolean onTextContextMenuItem(int id) {
if (id == android.R.id.paste) {
// 处理粘贴操作,检查剪贴板是否有图片
if (handlePasteImage()) {
return true;
}
}
return super.onTextContextMenuItem(id);
}
/**
*
* @param imagePath
*/
public void insertImage(final String imagePath) {
Log.d(TAG, "开始插入图片,路径: " + imagePath);
// 确保在主线程中操作
post(new Runnable() {
@Override
public void run() {
doInsertImage(imagePath);
}
});
}
/**
*
* @param imagePath
*/
private void doInsertImage(String imagePath) {
if (TextUtils.isEmpty(imagePath)) {
Log.e(TAG, "图片路径为空");
Toast.makeText(getContext(), "图片路径为空", Toast.LENGTH_SHORT).show();
return;
}
// 检查文件是否存在
java.io.File imageFile = new java.io.File(imagePath);
if (!imageFile.exists()) {
Log.e(TAG, "图片文件不存在: " + imagePath);
Toast.makeText(getContext(), "图片文件不存在: " + imagePath, Toast.LENGTH_SHORT).show();
return;
}
Log.d(TAG, "图片文件存在,大小: " + imageFile.length() + " bytes");
// 获取当前文本
android.text.Editable editable = getText();
int start = getSelectionStart();
Log.d(TAG, "插入位置: " + start);
// 添加图片标记,以便保存时能够正确保存图片路径
String imageTag = "[IMAGE]" + imagePath + "[/IMAGE]";
editable.insert(start, imageTag);
// 移动光标到图片标记后面
setSelection(start + imageTag.length());
// 处理图片标记,将其转换为 ImageSpan
processImageTags();
// 强制刷新界面
invalidate();
requestLayout();
Log.d(TAG, "强制刷新界面和布局");
// 显示成功提示
Toast.makeText(getContext(), "图片插入成功", Toast.LENGTH_SHORT).show();
Log.d(TAG, "图片插入成功,路径: " + imagePath);
Log.d(TAG, "插入后文本长度: " + getText().length());
}
/**
* ImageSpan
*/
public void processImageTags() {
android.text.Editable editable = getText();
String text = editable.toString();
Pattern pattern = Pattern.compile("\\[IMAGE\\](.*?)\\[/IMAGE\\]");
Matcher matcher = pattern.matcher(text);
// 从后往前处理,避免索引变化
while (matcher.find()) {
String imagePath = matcher.group(1);
int start = matcher.start();
int end = matcher.end();
// 加载图片
try {
android.graphics.BitmapFactory.Options options = new android.graphics.BitmapFactory.Options();
options.inSampleSize = 4;
options.inPreferredConfig = android.graphics.Bitmap.Config.RGB_565;
android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeFile(imagePath, options);
if (bitmap != null) {
// 保留原始标记,直接在标记上设置 ImageSpan
// 创建 ImageSpan
android.text.style.ImageSpan imageSpan = new android.text.style.ImageSpan(getContext(), bitmap);
// 设置 ImageSpan
editable.setSpan(imageSpan, start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Log.d(TAG, "成功处理图片标记: " + imagePath);
}
} catch (Exception e) {
Log.e(TAG, "处理图片标记时出错: " + e.getMessage(), e);
}
}
// 强制刷新界面
invalidate();
requestLayout();
}
/**
*
* @return
*/
private boolean handlePasteImage() {
Log.d(TAG, "开始处理粘贴图片");
// 尝试使用专门的方法从剪贴板获取图片(针对虚拟机环境)
Log.d(TAG, "尝试使用专门的方法从剪贴板获取图片");
Bitmap bitmap = ImageUtils.getImageFromClipboard(getContext());
if (bitmap != null) {
Log.d(TAG, "成功从剪贴板获取图片: " + bitmap.getWidth() + "x" + bitmap.getHeight());
// 保存图片到本地存储
Log.d(TAG, "尝试保存图片");
String imagePath = ImageUtils.saveImage(getContext(), bitmap);
if (imagePath != null) {
Log.d(TAG, "成功保存图片到: " + imagePath);
// 生成图片标记
String imageTag = "[IMAGE]" + imagePath + "[/IMAGE]";
Log.d(TAG, "生成图片标记: " + imageTag);
// 将图片标记插入到文本中
int start = getSelectionStart();
Log.d(TAG, "插入位置: " + start);
getText().insert(start, imageTag);
Log.d(TAG, "成功插入图片标记");
Log.d(TAG, "插入后文本长度: " + getText().length());
Log.d(TAG, "插入后文本内容: " + getText().toString());
// 移动光标到图片标记后面
setSelection(start + imageTag.length());
Log.d(TAG, "成功移动光标到位置: " + (start + imageTag.length()));
// 强制刷新界面
invalidate();
Log.d(TAG, "强制刷新界面");
Toast.makeText(getContext(), "图片粘贴成功,路径: " + imagePath, Toast.LENGTH_LONG).show();
Log.d(TAG, "图片粘贴成功,路径: " + imagePath);
return true;
} else {
Log.e(TAG, "保存图片失败");
Toast.makeText(getContext(), "图片保存失败", Toast.LENGTH_SHORT).show();
return true;
}
} else {
Log.d(TAG, "使用专门方法从剪贴板获取图片失败,尝试使用传统方法");
}
// 获取剪贴板管理器
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
// 检查剪贴板是否有内容
if (clipboard == null) {
Log.e(TAG, "剪贴板管理器为空");
Toast.makeText(getContext(), "剪贴板管理器不可用", Toast.LENGTH_SHORT).show();
return false;
}
if (!clipboard.hasPrimaryClip()) {
Log.e(TAG, "剪贴板为空");
Toast.makeText(getContext(), "剪贴板为空", Toast.LENGTH_SHORT).show();
return false;
}
// 获取剪贴板内容
ClipData clip = clipboard.getPrimaryClip();
if (clip == null) {
Log.e(TAG, "剪贴板内容为空");
Toast.makeText(getContext(), "剪贴板内容为空", Toast.LENGTH_SHORT).show();
return false;
}
Log.d(TAG, "剪贴板项目数量: " + clip.getItemCount());
// 检查剪贴板描述
ClipDescription description = clipboard.getPrimaryClipDescription();
if (description != null) {
Log.d(TAG, "剪贴板描述: " + description.toString());
for (int i = 0; i < description.getMimeTypeCount(); i++) {
Log.d(TAG, "MIME类型 " + i + ": " + description.getMimeType(i));
}
} else {
Log.e(TAG, "剪贴板描述为空");
}
// 检查是否有图片
boolean foundContent = false;
for (int i = 0; i < clip.getItemCount(); i++) {
Log.d(TAG, "处理剪贴板项目 " + i);
ClipData.Item item = clip.getItemAt(i);
// 检查是否有URI可能是图片文件
if (item.getUri() != null) {
Log.d(TAG, "找到URI: " + item.getUri().toString());
foundContent = true;
try {
// 尝试从URI加载图片
Log.d(TAG, "尝试从URI加载图片: " + item.getUri().toString());
Bitmap bitmapFromUri = BitmapFactory.decodeStream(
getContext().getContentResolver().openInputStream(item.getUri()));
if (bitmapFromUri != null) {
Log.d(TAG, "成功加载图片: " + bitmapFromUri.getWidth() + "x" + bitmapFromUri.getHeight());
// 保存图片到本地存储
Log.d(TAG, "尝试保存图片");
String imagePath = ImageUtils.saveImage(getContext(), bitmapFromUri);
if (imagePath != null) {
Log.d(TAG, "成功保存图片到: " + imagePath);
// 生成图片标记
String imageTag = "[IMAGE]" + imagePath + "[/IMAGE]";
Log.d(TAG, "生成图片标记: " + imageTag);
// 将图片标记插入到文本中
int start = getSelectionStart();
Log.d(TAG, "插入位置: " + start);
getText().insert(start, imageTag);
Log.d(TAG, "成功插入图片标记");
Log.d(TAG, "插入后文本长度: " + getText().length());
Log.d(TAG, "插入后文本内容: " + getText().toString());
// 移动光标到图片标记后面
setSelection(start + imageTag.length());
Log.d(TAG, "成功移动光标到位置: " + (start + imageTag.length()));
// 强制刷新界面
invalidate();
Log.d(TAG, "强制刷新界面");
Toast.makeText(getContext(), "图片粘贴成功,路径: " + imagePath, Toast.LENGTH_LONG).show();
Log.d(TAG, "图片粘贴成功,路径: " + imagePath);
return true;
} else {
Log.e(TAG, "保存图片失败");
Toast.makeText(getContext(), "图片保存失败", Toast.LENGTH_SHORT).show();
return true;
}
} else {
Log.e(TAG, "从URI加载图片失败");
Toast.makeText(getContext(), "无法加载图片", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Log.e(TAG, "加载图片时出错: " + e.getMessage());
e.printStackTrace();
Toast.makeText(getContext(), "加载图片时出错: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "未找到URI");
}
// 尝试获取文本(可能包含图片路径或其他信息)
if (item.getText() != null) {
String text = item.getText().toString();
Log.d(TAG, "找到文本: " + text);
foundContent = true;
// 检查文本是否是图片路径
if (isImagePath(text)) {
Log.d(TAG, "文本是图片路径: " + text);
// 尝试从路径加载图片
try {
Bitmap bitmapFromPath = BitmapFactory.decodeFile(text);
if (bitmapFromPath != null) {
Log.d(TAG, "成功从路径加载图片: " + bitmapFromPath.getWidth() + "x" + bitmapFromPath.getHeight());
// 保存图片到本地存储
String imagePath = ImageUtils.saveImage(getContext(), bitmapFromPath);
if (imagePath != null) {
Log.d(TAG, "成功保存图片到: " + imagePath);
// 生成图片标记
String imageTag = "[IMAGE]" + imagePath + "[/IMAGE]";
Log.d(TAG, "生成图片标记: " + imageTag);
// 将图片标记插入到文本中
int start = getSelectionStart();
Log.d(TAG, "插入位置: " + start);
getText().insert(start, imageTag);
Log.d(TAG, "成功插入图片标记");
Log.d(TAG, "插入后文本长度: " + getText().length());
Log.d(TAG, "插入后文本内容: " + getText().toString());
// 移动光标到图片标记后面
setSelection(start + imageTag.length());
Log.d(TAG, "成功移动光标到位置: " + (start + imageTag.length()));
// 强制刷新界面
invalidate();
Log.d(TAG, "强制刷新界面");
Toast.makeText(getContext(), "图片粘贴成功,路径: " + imagePath, Toast.LENGTH_LONG).show();
Log.d(TAG, "图片粘贴成功,路径: " + imagePath);
return true;
} else {
Log.e(TAG, "保存图片失败");
Toast.makeText(getContext(), "图片保存失败", Toast.LENGTH_SHORT).show();
return true;
}
} else {
Log.e(TAG, "从路径加载图片失败: " + text);
// 图片加载失败,直接粘贴文本内容
int start = getSelectionStart();
getText().insert(start, text);
setSelection(start + text.length());
Toast.makeText(getContext(), "图片加载失败,已粘贴路径", Toast.LENGTH_SHORT).show();
Log.d(TAG, "图片加载失败,已粘贴路径");
return true;
}
} catch (Exception e) {
Log.e(TAG, "从路径加载图片时出错: " + e.getMessage());
e.printStackTrace();
// 加载失败,直接粘贴文本内容
int start = getSelectionStart();
getText().insert(start, text);
setSelection(start + text.length());
Toast.makeText(getContext(), "图片加载失败,已粘贴路径", Toast.LENGTH_SHORT).show();
Log.d(TAG, "图片加载失败,已粘贴路径");
return true;
}
} else {
// 不是图片路径,直接粘贴文本内容
int start = getSelectionStart();
getText().insert(start, text);
setSelection(start + text.length());
Toast.makeText(getContext(), "文本粘贴成功", Toast.LENGTH_SHORT).show();
Log.d(TAG, "文本粘贴成功");
return true;
}
} else {
Log.d(TAG, "未找到文本");
}
// 尝试获取Intent可能包含图片信息
if (item.getIntent() != null) {
Log.d(TAG, "找到Intent: " + item.getIntent().toString());
foundContent = true;
// 尝试从Intent中提取图片
try {
android.content.Intent intent = item.getIntent();
if (intent.hasExtra(android.content.Intent.EXTRA_STREAM)) {
android.net.Uri uri = (android.net.Uri) intent.getParcelableExtra(android.content.Intent.EXTRA_STREAM);
if (uri != null) {
Log.d(TAG, "从Intent中找到图片URI: " + uri.toString());
// 尝试从URI加载图片
Bitmap bitmapFromIntent = BitmapFactory.decodeStream(
getContext().getContentResolver().openInputStream(uri));
if (bitmapFromIntent != null) {
Log.d(TAG, "成功从Intent加载图片: " + bitmapFromIntent.getWidth() + "x" + bitmapFromIntent.getHeight());
// 保存图片到本地存储
String imagePath = ImageUtils.saveImage(getContext(), bitmapFromIntent);
if (imagePath != null) {
Log.d(TAG, "成功保存图片到: " + imagePath);
// 生成图片标记
String imageTag = "[IMAGE]" + imagePath + "[/IMAGE]";
Log.d(TAG, "生成图片标记: " + imageTag);
// 将图片标记插入到文本中
int start = getSelectionStart();
Log.d(TAG, "插入位置: " + start);
getText().insert(start, imageTag);
Log.d(TAG, "成功插入图片标记");
Log.d(TAG, "插入后文本长度: " + getText().length());
Log.d(TAG, "插入后文本内容: " + getText().toString());
// 移动光标到图片标记后面
setSelection(start + imageTag.length());
Log.d(TAG, "成功移动光标到位置: " + (start + imageTag.length()));
// 强制刷新界面
invalidate();
Log.d(TAG, "强制刷新界面");
Toast.makeText(getContext(), "图片粘贴成功,路径: " + imagePath, Toast.LENGTH_LONG).show();
Log.d(TAG, "图片粘贴成功,路径: " + imagePath);
return true;
} else {
Log.e(TAG, "保存图片失败");
Toast.makeText(getContext(), "图片保存失败", Toast.LENGTH_SHORT).show();
return true;
}
} else {
Log.e(TAG, "从Intent URI加载图片失败");
}
}
}
} catch (Exception e) {
Log.e(TAG, "从Intent加载图片时出错: " + e.getMessage());
e.printStackTrace();
}
} else {
Log.d(TAG, "未找到Intent");
}
// 尝试获取HTML文本可能包含图片信息
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
if (item.getHtmlText() != null) {
String htmlText = item.getHtmlText().toString();
Log.d(TAG, "找到HTML文本: " + htmlText);
foundContent = true;
// 尝试从HTML中提取图片信息
if (htmlText.contains("<img")) {
Log.d(TAG, "HTML文本中包含图片标签");
Toast.makeText(getContext(), "HTML文本中包含图片标签但暂不支持", Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "未找到HTML文本");
}
}
// 尝试使用ContentResolver获取剪贴板中的图片
try {
Log.d(TAG, "尝试使用ContentResolver获取剪贴板中的图片");
android.content.ContentResolver resolver = getContext().getContentResolver();
// 尝试获取剪贴板中的图片
android.net.Uri clipboardUri = null;
if (clip.getDescription().hasMimeType("image/*")) {
clipboardUri = item.getUri();
} else if (clip.getDescription().hasMimeType("text/html")) {
// 尝试从HTML中提取图片URI
Log.d(TAG, "尝试从HTML中提取图片URI");
}
if (clipboardUri != null) {
Log.d(TAG, "使用ContentResolver找到图片URI: " + clipboardUri.toString());
Bitmap bitmapFromContentResolver = BitmapFactory.decodeStream(
resolver.openInputStream(clipboardUri));
if (bitmapFromContentResolver != null) {
Log.d(TAG, "成功从ContentResolver加载图片: " + bitmapFromContentResolver.getWidth() + "x" + bitmapFromContentResolver.getHeight());
// 保存图片到本地存储
String imagePath = ImageUtils.saveImage(getContext(), bitmapFromContentResolver);
if (imagePath != null) {
Log.d(TAG, "成功保存图片到: " + imagePath);
// 生成图片标记
String imageTag = "[IMAGE]" + imagePath + "[/IMAGE]";
Log.d(TAG, "生成图片标记: " + imageTag);
// 将图片标记插入到文本中
int start = getSelectionStart();
Log.d(TAG, "插入位置: " + start);
getText().insert(start, imageTag);
Log.d(TAG, "成功插入图片标记");
Log.d(TAG, "插入后文本长度: " + getText().length());
Log.d(TAG, "插入后文本内容: " + getText().toString());
// 移动光标到图片标记后面
setSelection(start + imageTag.length());
Log.d(TAG, "成功移动光标到位置: " + (start + imageTag.length()));
// 强制刷新界面
invalidate();
Log.d(TAG, "强制刷新界面");
Toast.makeText(getContext(), "图片粘贴成功,路径: " + imagePath, Toast.LENGTH_LONG).show();
Log.d(TAG, "图片粘贴成功,路径: " + imagePath);
return true;
} else {
Log.e(TAG, "保存图片失败");
Toast.makeText(getContext(), "图片保存失败", Toast.LENGTH_SHORT).show();
return true;
}
} else {
Log.e(TAG, "从ContentResolver加载图片失败");
}
} else {
Log.d(TAG, "未找到ContentResolver图片URI");
}
} catch (Exception e) {
Log.e(TAG, "使用ContentResolver加载图片时出错: " + e.getMessage());
e.printStackTrace();
}
}
// 如果没有找到图片,显示提示
if (foundContent) {
Log.e(TAG, "找到剪贴板内容,但无法识别为图片或文本");
Toast.makeText(getContext(), "无法识别剪贴板内容", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "剪贴板中没有可识别的内容");
Toast.makeText(getContext(), "剪贴板中没有可识别的内容", Toast.LENGTH_SHORT).show();
}
return false;
}
}

File diff suppressed because it is too large Load Diff

@ -75,13 +75,23 @@ public class NotesListAdapter extends CursorAdapter {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public void toggleItemChecked(final int position) {
Boolean currentState = mSelectedIndex.get(position);
boolean newState = (currentState == null) ? true : !currentState;
mSelectedIndex.put(position, newState);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
// 只有在从选择模式切换到非选择模式时才清除mSelectedIndex集合
if (!mode) {
mSelectedIndex.clear();
}
mChoiceMode = mode;
}
@ -98,17 +108,32 @@ public class NotesListAdapter extends CursorAdapter {
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);
Log.d(TAG, "getSelectedItemIds called, mSelectedIndex size: " + mSelectedIndex.size());
// 获取整个Cursor对象
Cursor cursor = getCursor();
Log.d(TAG, "Cursor obtained: " + (cursor != null));
if (cursor != null) {
for (Integer position : mSelectedIndex.keySet()) {
Log.d(TAG, "Checking position: " + position + ", selected: " + mSelectedIndex.get(position));
if (mSelectedIndex.get(position) == true) {
// 将Cursor移动到指定位置
if (cursor.moveToPosition(position)) {
// 从Cursor中获取笔记的实际ID使用ID_COLUMN索引0
long id = cursor.getLong(0);
Log.d(TAG, "Found note with id: " + id);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
Log.d(TAG, "Added note id to set: " + id);
}
} else {
Log.e(TAG, "Failed to move cursor to position: " + position);
}
}
}
}
Log.d(TAG, "Returning itemSet with size: " + itemSet.size() + ", ids: " + itemSet);
return itemSet;
}

@ -17,7 +17,7 @@
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.content.Intent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
@ -27,7 +27,6 @@ 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 {
@ -47,7 +46,10 @@ public class NotesListItem extends LinearLayout {
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);
@ -57,6 +59,120 @@ public class NotesListItem extends LinearLayout {
}
mItemData = data;
// 移除所有监听器确保不会与Activity的监听器冲突
setOnTouchListener(null);
setOnLongClickListener(null);
// 添加点击监听器
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
// 在选择模式下,切换复选框状态
boolean newCheckedState = !mCheckBox.isChecked();
mCheckBox.setChecked(newCheckedState);
// 通知适配器更新选择状态
if (context instanceof NotesListActivity) {
NotesListActivity activity = (NotesListActivity) context;
// 通过反射获取适配器并更新选择状态
try {
// 获取GridView
android.widget.GridView gridView = (android.widget.GridView) activity.findViewById(net.micode.notes.R.id.notes_grid);
if (gridView != null) {
// 获取适配器
android.widget.ListAdapter adapter = gridView.getAdapter();
if (adapter instanceof net.micode.notes.ui.NotesListAdapter) {
net.micode.notes.ui.NotesListAdapter notesAdapter = (net.micode.notes.ui.NotesListAdapter) adapter;
// 查找当前项在适配器中的位置
android.database.Cursor cursor = (android.database.Cursor) notesAdapter.getCursor();
if (cursor != null) {
int position = -1;
for (int i = 0; i < cursor.getCount(); i++) {
if (cursor.moveToPosition(i)) {
long noteId = cursor.getLong(0); // 假设ID是第一列
if (noteId == data.getId()) {
position = i;
break;
}
}
}
if (position != -1) {
// 切换选择状态
notesAdapter.toggleItemChecked(position);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} else if (context instanceof NotesListActivity) {
// 非选择模式下,正常打开便签或文件夹
NotesListActivity activity = (NotesListActivity) context;
if (data.getType() == Notes.TYPE_FOLDER) {
activity.openFolder(data);
} else if (data.getType() == Notes.TYPE_NOTE) {
// 检查便签是否加密
boolean isEncrypted = data.isEncrypted();
// 加载便签并再次检查加密状态,确保准确性
net.micode.notes.model.WorkingNote note = net.micode.notes.model.WorkingNote.load(activity, data.getId());
if (note != null) {
isEncrypted = note.isEncrypted();
}
if (isEncrypted) {
// 弹出密码输入对话框
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(activity);
builder.setTitle("输入密码");
builder.setIcon(android.R.drawable.ic_dialog_alert);
final android.widget.EditText input = new android.widget.EditText(activity);
input.setHint("请输入密码");
input.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
builder.setView(input);
builder.setPositiveButton("确定",
new android.content.DialogInterface.OnClickListener() {
public void onClick(android.content.DialogInterface dialog, int which) {
String password = input.getText().toString();
if (!android.text.TextUtils.isEmpty(password)) {
// 验证密码
net.micode.notes.model.WorkingNote verifyNote = net.micode.notes.model.WorkingNote.load(activity, data.getId());
if (verifyNote != null && verifyNote.verifyPassword(password)) {
// 密码正确,启动 NoteEditActivity 并传递密码
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
intent.putExtra("password", password);
activity.startActivityForResult(intent, 102);
} else {
// 密码错误,显示错误提示
android.widget.Toast.makeText(activity, "密码错误", android.widget.Toast.LENGTH_SHORT).show();
}
} else {
android.widget.Toast.makeText(activity, "密码不能为空", android.widget.Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton("取消", null);
// 设置对话框不可取消,确保用户必须输入密码或点击取消
builder.setCancelable(false);
builder.show();
} else {
// 非加密便签,直接启动 NoteEditActivity
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
activity.startActivityForResult(intent, 102);
}
}
}
}
});
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
@ -82,47 +198,86 @@ public class NotesListItem extends LinearLayout {
}
mTitle.setText(title);
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
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 {
// 优先显示标题,如果没有标题则显示内容摘要
String title = data.getTitle();
if (title == null || title.isEmpty()) {
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 {
// 优先显示标题,如果没有标题则显示内容摘要
String title = data.getTitle();
if (title == null || title.isEmpty()) {
if (data.isEncrypted()) {
title = "[已加密]";
} else {
title = DataUtils.getFormattedSnippet(data.getSnippet());
}
// 如果有提醒,添加剩余提醒时间
if (data.hasAlert()) {
String reminderTime = getRemainingReminderTime(context, data.getAlertDate());
if (!reminderTime.isEmpty()) {
title += " " + reminderTime;
}
}
// 如果有提醒,添加剩余提醒时间
if (data.hasAlert()) {
String reminderTime = getRemainingReminderTime(context, data.getAlertDate());
if (!reminderTime.isEmpty()) {
title += " " + reminderTime;
}
mTitle.setText(title);
}
mTitle.setText(title);
if (data.isEncrypted()) {
// 加密状态显示加密图标
mAlert.setImageResource(R.drawable.menu_delete);
mAlert.setVisibility(View.VISIBLE);
} else if (data.isPinned()) {
// 置顶状态显示置顶图标
mAlert.setImageResource(R.drawable.selected);
mAlert.setVisibility(View.VISIBLE);
} else if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
// 加密状态显示锁图标
mAlert.setImageResource(android.R.drawable.ic_lock_lock);
mAlert.setVisibility(View.VISIBLE);
} else {
// 未加密的笔记右下角不显示任何图标
mAlert.setVisibility(View.GONE);
}
}
}
// 格式化创建时间为年月日格式显示
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy年MM月dd日", java.util.Locale.getDefault());
mTime.setText(sdf.format(new java.util.Date(data.getCreatedDate())));
// 格式化创建时间为年月日 上午/下午 HH:mm 格式显示
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.getDefault());
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.setTimeInMillis(data.getCreatedDate());
int hour = calendar.get(java.util.Calendar.HOUR_OF_DAY);
String timeStr;
if (hour < 12) {
timeStr = "上午 " + sdf.format(new java.util.Date(data.getCreatedDate()));
} else {
timeStr = "下午 " + sdf.format(new java.util.Date(data.getCreatedDate()));
}
mTime.setText(timeStr);
// 更新副标题
TextView tvSubtitle = (TextView) findViewById(R.id.tv_subtitle);
if (tvSubtitle != null) {
if (data.getType() == Notes.TYPE_NOTE) {
// 对于普通笔记,显示内容摘要
if (!data.isEncrypted()) {
String snippet = data.getSnippet();
if (snippet != null && !snippet.isEmpty()) {
String formattedSnippet = DataUtils.getFormattedSnippet(snippet);
// 如果有标题,且摘要与标题不同,则显示摘要
String title = data.getTitle();
if (title != null && !title.isEmpty() && !formattedSnippet.equals(title)) {
tvSubtitle.setText(formattedSnippet);
tvSubtitle.setVisibility(View.VISIBLE);
} else {
// 如果没有标题或摘要与标题相同,则隐藏副标题
tvSubtitle.setVisibility(View.GONE);
}
} else {
// 如果没有摘要,则隐藏副标题
tvSubtitle.setVisibility(View.GONE);
}
} else {
// 对于加密笔记,隐藏副标题
tvSubtitle.setVisibility(View.GONE);
}
} else {
// 对于文件夹,隐藏副标题
tvSubtitle.setVisibility(View.GONE);
}
}
setBackground(data);
}
@ -159,23 +314,11 @@ public class NotesListItem extends LinearLayout {
}
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());
}
// 使用白色背景,忽略笔记的背景颜色设置
setBackgroundResource(R.drawable.note_card_bg);
}
public NoteItemData getItemData() {
return mItemData;
}
}
}

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#F0F0F0" />
<corners android:radius="4dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="4dp" />
</shape>
</item>
</selector>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="#FFF0E6" />
<corners android:radius="4dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="4dp" />
</shape>
</item>
</selector>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF666666" />
</shape>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="2dp" />
<solid android:color="#FFC107" />
</shape>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"/>
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"/>
</vector>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="8dp" />
</shape>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<stroke android:width="1dp" android:color="#000000" />
<corners android:radius="4dp" />
</shape>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<stroke android:width="1dp" android:color="#CCCCCC" />
<corners android:radius="20dp" />
</shape>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<stroke
android:width="2dp"
android:color="#333333"
android:dashWidth="0dp"
android:dashGap="0dp" />
<size
android:width="16dp"
android:height="2dp" />
</shape>

@ -0,0 +1,62 @@
<?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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@drawable/note_card_bg"
android:orientation="vertical"
android:padding="16dp"
android:layout_margin="8dp"
android:elevation="0dp">
<!-- 标题 -->
<TextView
android:id="@+id/tv_card_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"
android:maxLines="2"
android:ellipsize="end"
android:text="笔记标题" />
<!-- 无附加文案 -->
<TextView
android:id="@+id/tv_card_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#999999"
android:textSize="12sp"
android:layout_marginBottom="16dp"
android:text="无附加文案" />
<!-- 时间戳 -->
<TextView
android:id="@+id/tv_card_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#666666"
android:textSize="12sp"
android:layout_gravity="left|bottom"
android:layout_weight="1"
android:gravity="left|bottom"
android:text="上午 9:55" />
</LinearLayout>

@ -1,68 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
@ -83,7 +18,7 @@
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background"
android:background="#FFFFFF"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
@ -91,410 +26,61 @@
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- 顶部导航栏 -->
<LinearLayout
android:id="@+id/note_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="left|center_vertical"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/title_alert" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="2dip"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
android:layout_height="56dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<!-- 左侧返回按钮 -->
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
</LinearLayout>
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:id="@+id/btn_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_revert"
android:tint="#000000" /> <!-- 设置为黑色 -->
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
<!-- 中间占位 -->
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dip">
<!-- 右侧操作按钮 -->
<ImageButton
android:id="@+id/btn_share"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_share"
android:tint="#000000" /> <!-- 设置为黑色 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:background="@null"
android:autoLink="all"
android:linksClickable="false"
android:minLines="12"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dip"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ImageButton
android:id="@+id/btn_theme"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_manage"
android:tint="#000000" /> <!-- 设置为黑色 -->
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
<ImageButton
android:id="@+id/btn_more"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_more"
android:tint="#000000" /> <!-- 设置为黑色 -->
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
<LinearLayout
android:id="@+id/note_bg_color_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/note_edit_color_selector_panel"
android:layout_marginTop="30dip"
android:layout_marginRight="8dip"
android:layout_gravity="top|right"
android:visibility="gone">
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_yellow"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_yellow_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_blue"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_blue_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="3dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_white"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_white_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="2dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_green"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_green_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_red"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_red_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/font_size_selector"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/font_size_selector_bg"
android:layout_gravity="bottom"
android:visibility="gone">
<FrameLayout
android:id="@+id/ll_font_small"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_small"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_small"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_small_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_normal"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_normal"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_normal"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_medium_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_large"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_large"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_large"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_large_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_super"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_super"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_super"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_super_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
</FrameLayout>
<?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.
-->
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- 标题栏(隐藏,仅用于代码兼容) -->
<LinearLayout
android:id="@+id/note_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:visibility="gone">
<TextView
android:id="@+id/tv_modified_date"
@ -521,130 +107,167 @@
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageButton
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
</LinearLayout>
<!-- 富文本编辑工具栏 -->
<LinearLayout
android:id="@+id/rich_text_toolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:orientation="horizontal"
android:padding="8dp"
android:visibility="gone">
<!-- 加粗按钮 -->
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_compass"
android:contentDescription="加粗" />
<!-- 斜体按钮 -->
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_camera"
android:contentDescription="斜体" />
<!-- 下划线按钮 -->
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_zoom"
android:contentDescription="下划线" />
<!-- 分割线 -->
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#30FFFFFF"
android:layout_marginHorizontal="8dp" />
<!-- 列表按钮 -->
<ImageButton
android:id="@+id/btn_bullet_list"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_view"
android:contentDescription="项目符号列表" />
<!-- 编号列表按钮 -->
<ImageButton
android:id="@+id/btn_number_list"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_sort_by_size"
android:contentDescription="编号列表" />
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
<!-- 内容区 -->
<ScrollView
android:id="@+id/sv_note_edit"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none"
android:padding="0dp"
android:layout_margin="0dp"
android:overScrollMode="never">
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
<ScrollView
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dip">
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="0dp"
android:paddingBottom="32dp"
android:layout_margin="0dp"
android:gravity="top"
android:baselineAligned="false">
<!-- 标题输入框 -->
<EditText
android:id="@+id/note_title_view"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:background="@drawable/note_title_border"
android:hint="标题"
android:textColorHint="#666666"
android:singleLine="true"
android:textSize="18sp"
android:textColor="#000000"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="4dp"
android:includeFontPadding="false"
android:maxLines="1" />
<!-- 正文内容 -->
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:textColor="#000000"
android:textSize="16sp"
android:lineSpacingMultiplier="1.5"
android:padding="0dp"
android:layout_margin="0dp"
android:includeFontPadding="false"
android:minHeight="100dp" />
<!-- 信息行 -->
<TextView
android:id="@+id/tv_info_line"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="1 月 31 日 上午 9:55 | 0 字"
android:textColor="#999999"
android:textSize="12sp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_margin="0dp"
android:includeFontPadding="false" />
<!-- 笔记编辑列表(隐藏) -->
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:background="@null"
android:autoLink="all"
android:linksClickable="false"
android:minLines="12"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dip"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
</LinearLayout>
<!-- 底部系统手势条占位 -->
<View
android:layout_width="fill_parent"
android:layout_height="24dp"
android:background="#FFFFFF" />
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
<!-- 富文本编辑工具栏(隐藏) -->
<LinearLayout
android:id="@+id/rich_text_toolbar"
android:layout_width="fill_parent"
android:layout_height="56dp"
android:background="#FFFFFF"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:visibility="gone">
<!-- 加粗按钮 -->
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_format_bold"
android:tint="#666666"
android:contentDescription="加粗"
android:scaleType="center"
android:padding="12dp" />
<!-- 斜体按钮 -->
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_format_italic"
android:tint="#666666"
android:contentDescription="斜体"
android:scaleType="center"
android:padding="12dp" />
<!-- 下划线按钮 -->
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_format_underlined"
android:tint="#666666"
android:contentDescription="下划线"
android:scaleType="center"
android:padding="12dp" />
<!-- 有序列表按钮 -->
<ImageButton
android:id="@+id/btn_number_list"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_format_list_numbered"
android:tint="#666666"
android:contentDescription="有序列表"
android:scaleType="center"
android:padding="12dp" />
<!-- 无序列表按钮 -->
<ImageButton
android:id="@+id/btn_bullet_list"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_format_list_bulleted"
android:tint="#666666"
android:contentDescription="无序列表"
android:scaleType="center"
android:padding="12dp" />
</LinearLayout>
<!-- 背景颜色选择器(隐藏) -->
<LinearLayout
android:id="@+id/note_bg_color_selector"
android:layout_width="wrap_content"
@ -691,9 +314,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="3dip"
android:src="@drawable/selected" />
</FrameLayout>
@ -712,9 +335,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="2dip"
android:src="@drawable/selected" />
</FrameLayout>
@ -733,6 +356,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
@ -753,12 +377,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<!-- 字体大小选择器(隐藏) -->
<LinearLayout
android:id="@+id/font_size_selector"
android:layout_width="fill_parent"
@ -838,8 +464,6 @@
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
@ -876,8 +500,6 @@
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
@ -914,9 +536,7 @@
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
</FrameLayout>
</FrameLayout>

@ -23,43 +23,49 @@
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical">
android:layout_height="180dp"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="0dip"
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone" />
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<!-- 标题 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"
android:maxLines="2"
android:ellipsize="end" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true" />
<!-- 无附加文案 -->
<TextView
android:id="@+id/tv_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#999999"
android:textSize="12sp"
android:layout_marginBottom="16dp"
android:text="无附加文案" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
</LinearLayout>
</LinearLayout>
<!-- 时间戳 -->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#666666"
android:textSize="12sp"
android:layout_gravity="left|bottom"
android:layout_weight="1"
android:gravity="left|bottom" />
<CheckBox
android:id="@android:id/checkbox"
@ -68,11 +74,11 @@
android:focusable="false"
android:clickable="false"
android:visibility="gone" />
</LinearLayout>
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"/>
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"/>
</LinearLayout>
</FrameLayout>

@ -15,108 +15,239 @@
limitations under the License.
-->
<FrameLayout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:visibility="gone"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium" />
<!-- 应用名称 -->
<TextView
android:id="@+id/tv_app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:gravity="center"
android:text="笔记"
android:textColor="#000000"
android:textSize="24sp"
android:textStyle="bold" />
<!-- 搜索栏和时间搜索按钮 -->
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_below="@id/tv_app_name"
android:layout_marginTop="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<!-- 搜索栏 -->
<LinearLayout
android:id="@+id/search_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="40dp"
android:background="@drawable/search_bar_bg"
android:orientation="horizontal"
android:padding="10dp"
android:gravity="center_vertical">
<!-- 搜索容器 -->
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="44dp"
android:background="@drawable/search_bg"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="12dp">
<!-- 搜索图标 -->
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@android:drawable/ic_menu_search"
android:tint="#80FFFFFF"
android:layout_marginRight="8dp" />
<!-- 搜索输入框 -->
<EditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:hint="搜索便签..."
android:textColorHint="#80FFFFFF"
android:textColor="#FFFFFF"
android:background="@android:color/transparent"
android:singleLine="true"
android:imeOptions="actionSearch"
android:paddingHorizontal="4dp"
android:textSize="16sp"
android:selectAllOnFocus="true" />
<!-- 清除按钮 -->
<ImageView
android:id="@+id/btn_clear_search"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:tint="#80FFFFFF"
android:layout_marginLeft="8dp"
android:visibility="gone"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
<!-- 时间搜索按钮 -->
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<ImageView
android:id="@+id/btn_search_by_time"
android:layout_width="44dp"
android:layout_height="44dp"
android:src="@android:drawable/ic_menu_today"
android:tint="#FFFFFF"
android:layout_marginLeft="10dp"
android:clickable="true"
android:focusable="true" />
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/ic_menu_search"
android:layout_marginRight="8dp"
android:tint="#999999" />
<EditText
android:id="@+id/et_search"
android:layout_width="match_parent"
android:layout_height="36dp"
android:hint="搜索笔记"
android:textColor="#000000"
android:textSize="14sp"
android:background="@null"
android:singleLine="true"
android:imeOptions="actionSearch"
android:cursorVisible="true"
android:textCursorDrawable="@null"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true" />
</LinearLayout>
<!-- 按时间查找按钮 -->
<ImageView
android:id="@+id/btn_time_search"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="12dp"
android:src="@android:drawable/ic_menu_today"
android:tint="#000000"
android:clickable="true"
android:focusable="true" />
<!-- 排序按钮 -->
<ImageView
android:id="@+id/btn_sort"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="12dp"
android:src="@android:drawable/ic_menu_sort_by_size"
android:tint="#000000"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
<ListView
android:id="@+id/notes_list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null" />
<!-- 删除按钮容器 -->
<LinearLayout
android:id="@+id/delete_button_container"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="#FFFFFF"
android:orientation="horizontal"
android:gravity="center"
android:layout_alignParentBottom="true"
android:visibility="gone">
<!-- 删除按钮 -->
<Button
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:background="#FF4444"
android:textColor="#FFFFFF"
android:text="删除"
android:textSize="14sp"
android:paddingLeft="32dp"
android:paddingRight="32dp"
android:visibility="gone" />
</LinearLayout>
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
<!-- 底部导航栏 -->
<LinearLayout
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="bottom" />
</FrameLayout>
android:layout_height="56dp"
android:background="#FFFFFF"
android:orientation="horizontal"
android:gravity="center"
android:layout_alignParentBottom="true"
android:layout_above="@+id/delete_button_container">
<!-- 笔记标签 -->
<LinearLayout
android:id="@+id/nav_notes"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_gallery"
android:layout_marginBottom="4dp"
android:tint="#000000" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="笔记"
android:textColor="#000000"
android:textSize="12sp" />
</LinearLayout>
<!-- 待办标签 -->
<LinearLayout
android:id="@+id/nav_todo"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_myplaces"
android:layout_marginBottom="4dp"
android:tint="#CCCCCC" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="待办"
android:textColor="#CCCCCC"
android:textSize="12sp" />
</LinearLayout>
<!-- 回收站标签 -->
<LinearLayout
android:id="@+id/nav_recycle"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_delete"
android:layout_marginBottom="4dp"
android:tint="#CCCCCC" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="回收站"
android:textColor="#CCCCCC"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<!-- 笔记卡片网格布局 -->
<GridView
android:id="@+id/notes_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/search_container"
android:layout_above="@+id/bottom_nav"
android:numColumns="2"
android:columnWidth="0dp"
android:stretchMode="columnWidth"
android:horizontalSpacing="16dp"
android:verticalSpacing="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="80dp" />
<!-- 悬浮新建按钮 -->
<LinearLayout
android:id="@+id/fab_new_note"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="#FFC107"
android:gravity="center"
android:layout_marginRight="24dp"
android:layout_marginBottom="80dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:clickable="true"
android:focusable="true"
android:elevation="4dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>

@ -0,0 +1,199 @@
<?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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5">
<!-- 页面标题 -->
<TextView
android:id="@+id/tv_app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:gravity="center"
android:text="待办"
android:textColor="#000000"
android:textSize="24sp"
android:textStyle="bold" />
<!-- 待办事项列表 -->
<LinearLayout
android:id="@+id/todo_list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tv_app_name"
android:layout_above="@+id/bottom_nav"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<!-- 已完成列表折叠区域 -->
<LinearLayout
android:id="@+id/completed_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:layout_marginBottom="12dp"
android:padding="16dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:clickable="true"
android:focusable="true">
<!-- 向上的小箭头图标 -->
<ImageView
android:id="@+id/completed_arrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/ic_menu_more"
android:layout_marginRight="8dp"
android:tint="#000000" />
<!-- 已完成标题 -->
<TextView
android:id="@+id/completed_title"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="已完成 0"
android:textColor="#000000"
android:textSize="14sp" />
</LinearLayout>
<!-- 已完成列表 -->
<LinearLayout
android:id="@+id/completed_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
</LinearLayout>
</LinearLayout>
<!-- 黄色圆形悬浮按钮 -->
<LinearLayout
android:id="@+id/fab_new_note"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="#FFC107"
android:gravity="center"
android:layout_marginRight="24dp"
android:layout_marginBottom="80dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:clickable="true"
android:focusable="true"
android:elevation="4dp"
android:radius="28dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 底部导航栏 -->
<LinearLayout
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#FFFFFF"
android:orientation="horizontal"
android:gravity="center"
android:layout_alignParentBottom="true">
<!-- 笔记标签 -->
<LinearLayout
android:id="@+id/nav_notes"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_gallery"
android:layout_marginBottom="4dp"
android:tint="#000000" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="笔记"
android:textColor="#000000"
android:textSize="12sp" />
</LinearLayout>
<!-- 待办标签 -->
<LinearLayout
android:id="@+id/nav_todo"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_myplaces"
android:layout_marginBottom="4dp"
android:tint="#CCCCCC" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="待办"
android:textColor="#CCCCCC"
android:textSize="12sp" />
</LinearLayout>
<!-- 回收站标签 -->
<LinearLayout
android:id="@+id/nav_recycle"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_delete"
android:layout_marginBottom="4dp"
android:tint="#CCCCCC" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="回收站"
android:textColor="#CCCCCC"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>

@ -15,58 +15,6 @@
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
<item
android:id="@+id/menu_delete"
android:title="@string/menu_delete"/>
<item
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"/>
<item
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode" />
<item
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
<item
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"/>
<item
android:id="@+id/menu_alert"
android:title="@string/menu_alert" />
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
</menu>
<?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.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
@ -117,4 +65,10 @@
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
<item
android:id="@+id/menu_insert_image"
android:title="插入图片" />
</menu>

@ -21,9 +21,7 @@
android:id="@+id/menu_new_folder"
android:title="@string/menu_create_folder"/>
<item
android:id="@+id/menu_recently_deleted"
android:title="@string/menu_recently_deleted"/>
<item
android:id="@+id/menu_export_text"

@ -17,148 +17,155 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Notes</string>
<string name="app_widget2x2">Notes 2x2</string>
<string name="app_widget4x4">Notes 4x4</string>
<string name="widget_havenot_content">No associated note found, click to create associated note.</string>
<string name="widget_under_visit_mode">Privacy modecan not see note content</string>
<string name="app_name">便签</string>
<string name="app_widget2x2">便签 2x2</string>
<string name="app_widget4x4">便签 4x4</string>
<string name="widget_havenot_content">未找到关联便签,点击创建关联便签。</string>
<string name="widget_under_visit_mode">隐私模式,无法查看便签内容</string>
<string name="notelist_string_info">...</string>
<string name="notelist_menu_new">Add note</string>
<string name="note_alert_expired">Expired</string>
<string name="notelist_menu_new">添加便签</string>
<string name="note_alert_expired">已过期</string>
<string name="format_date_ymd">yyyyMMdd</string>
<string name="format_datetime_mdhm">MMMd kk:mm</string>
<string name="notealert_ok">Got it</string>
<string name="notealert_enter">Take a look</string>
<string name="notealert_no_content">No content</string>
<string name="note_link_tel">Call</string>
<string name="note_link_email">Send email</string>
<string name="note_link_web">Browse web</string>
<string name="note_link_other">Open map</string>
<string name="notealert_ok">知道了</string>
<string name="notealert_enter">查看</string>
<string name="notealert_no_content">无内容</string>
<string name="note_link_tel">拨打电话</string>
<string name="note_link_email">发送邮件</string>
<string name="note_link_web">浏览网页</string>
<string name="note_link_other">打开地图</string>
<!-- Text export file information -->
<string name="file_path">/MIUI/notes/</string>
<string name="file_name_txt_format">notes_%s.txt</string>
<!-- notes list string -->
<string name="format_folder_files_count">(%d)</string>
<string name="menu_create_folder">New Folder</string>
<string name="menu_export_text">Export text</string>
<string name="menu_sync">Sync</string>
<string name="menu_sync_cancel">Cancel syncing</string>
<string name="menu_setting">Settings</string>
<string name="menu_search">Search</string>
<string name="menu_delete">Delete</string>
<string name="menu_move">Move to folder</string>
<string name="menu_select_title">%d selected</string>
<string name="menu_select_none">Nothing selected, the operation is invalid</string>
<string name="menu_select_all">Select all</string>
<string name="menu_deselect_all">Deselect all</string>
<string name="menu_font_size">Font size</string>
<string name="menu_font_small">Small</string>
<string name="menu_font_normal">Medium</string>
<string name="menu_font_large">Large</string>
<string name="menu_font_super">Super</string>
<string name="menu_rich_text">Rich text editor</string>
<string name="menu_rich_text_hide">Hide rich text editor</string>
<string name="menu_list_mode">Enter check list</string>
<string name="menu_normal_mode">Leave check list</string>
<string name="menu_folder_view">View folder</string>
<string name="menu_folder_delete">Delete folder</string>
<string name="menu_folder_change_name">Change folder name</string>
<string name="folder_exist">The folder %1$s exist, please rename</string>
<string name="menu_share">Share</string>
<string name="menu_send_to_desktop">Send to home</string>
<string name="menu_alert">Remind me</string>
<string name="menu_remove_remind">Delete reminder</string>
<string name="menu_title_select_folder">Select folder</string>
<string name="menu_move_parent_folder">Parent folder</string>
<string name="info_note_enter_desktop">Note added to home</string>
<string name="alert_message_delete_folder">Confirm to delete folder and its notes?</string>
<string name="alert_title_delete">Delete selected notes</string>
<string name="alert_message_delete_notes">Confirm to delete the selected %d notes?</string>
<string name="alert_message_delete_note">Confirm to delete this note?</string>
<string name="format_move_notes_to_folder">Have moved selected %1$d notes to %2$s folder</string>
<string name="menu_create_folder">新建文件夹</string>
<string name="menu_export_text">导出文本</string>
<string name="menu_sync">同步</string>
<string name="menu_sync_cancel">取消同步</string>
<string name="menu_setting">设置</string>
<string name="menu_search">搜索</string>
<string name="menu_delete">删除</string>
<string name="menu_move">移动到文件夹</string>
<string name="menu_select_title">已选择 %d 项</string>
<string name="menu_select_none">未选择任何项,操作无效</string>
<string name="menu_select_all">全选</string>
<string name="menu_deselect_all">取消全选</string>
<string name="menu_font_size">字体大小</string>
<string name="menu_font_small"></string>
<string name="menu_font_normal"></string>
<string name="menu_font_large"></string>
<string name="menu_font_super">超大</string>
<string name="menu_rich_text">富文本编辑器</string>
<string name="menu_rich_text_hide">隐藏富文本编辑器</string>
<string name="menu_list_mode">进入检查列表</string>
<string name="menu_normal_mode">退出检查列表</string>
<string name="menu_folder_view">查看文件夹</string>
<string name="menu_folder_delete">删除文件夹</string>
<string name="menu_folder_change_name">修改文件夹名称</string>
<string name="folder_exist">文件夹 %1$s 已存在,请重命名</string>
<string name="menu_share">分享</string>
<string name="menu_send_to_desktop">发送到桌面</string>
<string name="menu_alert">提醒我</string>
<string name="menu_remove_remind">删除提醒</string>
<string name="menu_title_select_folder">选择文件夹</string>
<string name="menu_move_parent_folder">父文件夹</string>
<string name="info_note_enter_desktop">便签已添加到桌面</string>
<string name="alert_message_delete_folder">确认删除文件夹及其便签?</string>
<string name="alert_title_delete">删除选中的便签</string>
<string name="alert_message_delete_notes">确认删除选中的 %d 个便签?</string>
<string name="alert_message_delete_note">确认删除此便签?</string>
<string name="format_move_notes_to_folder">已将选中的 %1$d 个便签移动到 %2$s 文件夹</string>
<!-- Error information -->
<string name="error_sdcard_unmounted">SD card busy, not available now</string>
<string name="error_sdcard_export">Export failed, please check SD card</string>
<string name="error_note_not_exist">The note is not exist</string>
<string name="error_note_empty_for_clock">Sorry, can not set clock on empty note</string>
<string name="error_note_empty_for_send_to_desktop">Sorry, can not send and empty note to home</string>
<string name="success_sdcard_export">Export successful</string>
<string name="failed_sdcard_export">Export fail</string>
<string name="format_exported_file_location">Export text file (%1$s) to SD (%2$s) directory</string>
<string name="error_sdcard_unmounted">SD卡忙,暂时不可用</string>
<string name="error_sdcard_export">导出失败请检查SD卡</string>
<string name="error_note_not_exist">便签不存在</string>
<string name="error_note_empty_for_clock">抱歉,无法为空白便签设置提醒</string>
<string name="error_note_empty_for_send_to_desktop">抱歉,无法将空白便签发送到桌面</string>
<string name="success_sdcard_export">导出成功</string>
<string name="failed_sdcard_export">导出失败</string>
<string name="format_exported_file_location">已将文本文件 (%1$s) 导出到SD卡 (%2$s) 目录</string>
<!-- Sync -->
<string name="ticker_syncing">Syncing notes...</string>
<string name="ticker_success">Sync is successful</string>
<string name="ticker_fail">Sync is failed</string>
<string name="ticker_cancel">Sync is canceled</string>
<string name="success_sync_account">Sync is successful with account %1$s</string>
<string name="error_sync_network">Sync failed, please check network and account settings</string>
<string name="error_sync_internal">Sync failed, internal error occurs</string>
<string name="error_sync_cancelled">Sync is canceled</string>
<string name="sync_progress_login">Logging into %1$s...</string>
<string name="sync_progress_init_list">Getting remote note list...</string>
<string name="sync_progress_syncing">Synchronize local notes with Google Task...</string>
<string name="ticker_syncing">正在同步便签...</string>
<string name="ticker_success">同步成功</string>
<string name="ticker_fail">同步失败</string>
<string name="ticker_cancel">同步已取消</string>
<string name="success_sync_account">与账户 %1$s 同步成功</string>
<string name="error_sync_network">同步失败,请检查网络和账户设置</string>
<string name="error_sync_internal">同步失败,发生内部错误</string>
<string name="error_sync_cancelled">同步已取消</string>
<string name="sync_progress_login">正在登录 %1$s...</string>
<string name="sync_progress_init_list">正在获取远程便签列表...</string>
<string name="sync_progress_syncing">正在将本地便签与Google任务同步...</string>
<!-- Preferences -->
<string name="preferences_title">Settings</string>
<string name="preferences_account_title">Sync account</string>
<string name="preferences_account_summary">Sync notes with google task</string>
<string name="preferences_last_sync_time">Last sync time %1$s</string>
<string name="preferences_title">设置</string>
<string name="preferences_account_title">同步账户</string>
<string name="preferences_account_summary">将便签与Google任务同步</string>
<string name="preferences_last_sync_time">上次同步时间 %1$s</string>
<string name="preferences_last_sync_time_format">yyyy-MM-dd hh:mm:ss</string>
<string name="preferences_add_account">Add account</string>
<string name="preferences_menu_change_account">Change sync account</string>
<string name="preferences_menu_remove_account">Remove sync account</string>
<string name="preferences_menu_cancel">Cancel</string>
<string name="preferences_button_sync_immediately">Sync immediately</string>
<string name="preferences_button_sync_cancel">Cancel syncing</string>
<string name="preferences_dialog_change_account_title">Current account %1$s</string>
<string name="preferences_dialog_change_account_warn_msg">All sync related information will be deleted, which may result in duplicated items sometime</string>
<string name="preferences_dialog_select_account_title">Sync notes</string>
<string name="preferences_dialog_select_account_tips">Please select a google account. Local notes will be synced with google task.</string>
<string name="preferences_toast_cannot_change_account">Cannot change the account because sync is in progress</string>
<string name="preferences_toast_success_set_accout">%1$s has been set as the sync account</string>
<string name="preferences_bg_random_appear_title">New note background color random</string>
<string name="preferences_add_account">添加账户</string>
<string name="preferences_menu_change_account">更改同步账户</string>
<string name="preferences_menu_remove_account">移除同步账户</string>
<string name="preferences_menu_cancel">取消</string>
<string name="preferences_button_sync_immediately">立即同步</string>
<string name="preferences_button_sync_cancel">取消同步</string>
<string name="preferences_dialog_change_account_title">当前账户 %1$s</string>
<string name="preferences_dialog_change_account_warn_msg">所有同步相关信息将被删除,可能会导致有时出现重复项目</string>
<string name="preferences_dialog_select_account_title">同步便签</string>
<string name="preferences_dialog_select_account_tips">请选择一个Google账户。本地便签将与Google任务同步。</string>
<string name="preferences_toast_cannot_change_account">无法更改账户,因为同步正在进行中</string>
<string name="preferences_toast_success_set_accout">%1$s 已设置为同步账户</string>
<string name="preferences_bg_random_appear_title">新便签背景颜色随机</string>
<string name="call_record_folder_name">Call notes</string>
<string name="hint_foler_name">Input name</string>
<string name="call_record_folder_name">通话记录</string>
<string name="hint_foler_name">输入名称</string>
<string name="search_label">Searching Notes</string>
<string name="search_hint">Search notes</string>
<string name="search_setting_description">Text in your notes</string>
<string name="datetime_dialog_ok">set</string>
<string name="datetime_dialog_cancel">cancel</string>
<string name="search_label">搜索便签</string>
<string name="search_hint">搜索便签</string>
<string name="search_setting_description">便签中的文本</string>
<string name="dialog_search">搜索</string>
<string name="dialog_enter_search_query">输入搜索内容</string>
<string name="dialog_clear_search">清除搜索</string>
<string name="datetime_dialog_ok">设置</string>
<string name="datetime_dialog_cancel">取消</string>
<!-- Pin note -->
<string name="menu_pin">Pin to top</string>
<string name="menu_unpin">Unpin</string>
<string name="menu_pin">置顶</string>
<string name="menu_unpin">取消置顶</string>
<!-- Encrypt note -->
<string name="menu_encrypt">Encrypt</string>
<string name="menu_decrypt">Decrypt</string>
<string name="dialog_enter_password">Enter password</string>
<string name="dialog_set_password">Set password</string>
<string name="dialog_confirm_password">Confirm password</string>
<string name="dialog_password_mismatch">Passwords do not match</string>
<string name="dialog_password_empty">Password cannot be empty</string>
<string name="toast_encrypt_success">Note encrypted successfully</string>
<string name="toast_decrypt_success">Note decrypted successfully</string>
<string name="toast_password_incorrect">Incorrect password</string>
<string name="menu_encrypt">加密</string>
<string name="menu_decrypt">解密</string>
<string name="dialog_enter_password">输入密码</string>
<string name="dialog_set_password">设置密码</string>
<string name="dialog_confirm_password">确认密码</string>
<string name="dialog_password_mismatch">密码不匹配</string>
<string name="dialog_password_empty">密码不能为空</string>
<string name="toast_encrypt_success">便签加密成功</string>
<string name="toast_decrypt_success">便签解密成功</string>
<string name="toast_password_incorrect">密码错误</string>
<!-- Recently deleted -->
<string name="menu_recently_deleted">Recently deleted</string>
<string name="title_recently_deleted">Recently deleted</string>
<string name="empty_recently_deleted">No recently deleted notes</string>
<string name="menu_restore">Restore</string>
<string name="menu_delete_permanently">Delete permanently</string>
<string name="alert_message_delete_permanently">Confirm to permanently delete the selected %d notes?</string>
<string name="toast_restore_success">Note restored successfully</string>
<string name="menu_recently_deleted">最近删除</string>
<string name="title_recently_deleted">最近删除</string>
<string name="empty_recently_deleted">无最近删除的便签</string>
<string name="menu_restore">恢复</string>
<string name="menu_delete_permanently">永久删除</string>
<string name="alert_message_delete_permanently">确认永久删除选中的 %d 个便签?</string>
<string name="toast_restore_success">便签恢复成功</string>
<string name="toast_delete_permanently_success">便签永久删除成功</string>
<!-- Reminder strings -->
<string name="error_alert_time_invalid">Reminder time must be in the future</string>
<string name="toast_alert_set_success">Reminder set successfully</string>
<string name="toast_alert_cancel_success">Reminder cancelled successfully</string>
<string name="error_note_save_failed">Failed to save note</string>
<string name="format_reminder_days">%d days later</string>
<string name="format_reminder_hours">%d hours later</string>
<string name="format_reminder_minutes">%d minutes later</string>
<string name="format_reminder_soon">Reminder soon</string>
<string name="error_alert_time_invalid">提醒时间必须在未来</string>
<string name="toast_alert_set_success">提醒设置成功</string>
<string name="toast_alert_cancel_success">提醒取消成功</string>
<string name="toast_delete_success">便签删除成功</string>
<string name="error_note_save_failed">保存便签失败</string>
<string name="format_reminder_days">%d天后</string>
<string name="format_reminder_hours">%d小时后</string>
<string name="format_reminder_minutes">%d分钟后</string>
<string name="format_reminder_soon">即将提醒</string>
</resources>

Loading…
Cancel
Save