feat: 完整覆盖上传src目录,同步所有文件和目录结构

master
lh 6 days ago
parent 31d554b6e2
commit eae89cb84b

@ -1,391 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
// 导入内容提供者操作相关类,用于批量操作数据库
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
// 用于在URI后面追加ID
import android.content.ContentUris;
// 用于存储键值对数据类似于Map
import android.content.ContentValues;
// Android上下文类用于访问系统资源
import android.content.Context;
// 操作应用异常,批量操作时可能抛出
import android.content.OperationApplicationException;
// URI类用于标识数据资源
import android.net.Uri;
// 远程操作异常
import android.os.RemoteException;
// 日志工具类
import android.util.Log;
// 导入数据模型相关类
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
// ArrayList列表类
import java.util.ArrayList;
// Note类表示一个便签的数据模型负责管理便签的属性和关联数据
public class Note {
// 存储便签本身的属性差异值(修改后的值)
private ContentValues mNoteDiffValues;
// 存储便签关联的数据(文本数据和通话数据)
private NoteData mNoteData;
// 日志标签
private static final String TAG = "Note";
/**
* 便ID便
* @param context Android
* @param folderId 便ID
* @return 便ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// 在数据库中创建一个新的便签记录
// ContentValues用于存储要插入的键值对
ContentValues values = new ContentValues();
// 获取当前时间作为创建时间
long createdTime = System.currentTimeMillis();
// 设置创建时间
values.put(NoteColumns.CREATED_DATE, createdTime);
// 设置修改时间(刚创建时与创建时间相同)
values.put(NoteColumns.MODIFIED_DATE, createdTime);
// 设置类型为便签(非文件夹、非系统类型)
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// 标记为本地已修改
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 设置父文件夹ID
values.put(NoteColumns.PARENT_ID, folderId);
// 通过内容解析器插入到数据库返回新记录的URI
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
// 初始化便签ID为0
long noteId = 0;
try {
// 从URI路径中解析出ID路径格式通常为 content://authority/note/123
// getPathSegments().get(1) 获取路径的第二个部分ID
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果URI格式不正确记录错误日志
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
// 如果ID为-1说明出现错误抛出异常
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
// 返回新创建的便签ID
return noteId;
}
// 构造函数初始化Note对象
public Note() {
// 初始化便签属性差异值容器
mNoteDiffValues = new ContentValues();
// 初始化便签数据对象
mNoteData = new NoteData();
}
// 设置便签的属性值(如标题、背景色等)
// @param key 属性键名
// @param value 属性值
public void setNoteValue(String key, String value) {
// 将键值对存入差异值容器
mNoteDiffValues.put(key, value);
// 标记为本地已修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新修改时间为当前时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 设置文本数据的属性值
// @param key 属性键名
// @param value 属性值
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
// 设置文本数据的ID
// @param id 文本数据在数据库中的ID
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
// 获取文本数据的ID
// @return 文本数据ID
public long getTextDataId() {
return mNoteData.mTextDataId;
}
// 设置通话记录数据的ID
// @param id 通话数据在数据库中的ID
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
// 设置通话记录数据的属性值
// @param key 属性键名
// @param value 属性值
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
// 检查本地是否有修改
// @return true表示有修改false表示无修改
public boolean isLocalModified() {
// 便签属性有修改 或 便签数据有修改返回true
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
// 同步便签到数据库
// @param context Android上下文
// @param noteId 便签ID
// @return true表示同步成功false表示失败
public boolean syncNote(Context context, long noteId) {
// 检查便签ID是否有效
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 如果本地没有修改,直接返回成功
if (!isLocalModified()) {
return true;
}
/**
* 便NoteColumns.LOCAL_MODIFIEDNoteColumns.MODIFIED_DATE
* 使便便
*/
// 更新便签属性到数据库
// ContentUris.withAppendedId 将noteId附加到URI后面形成完整的资源路径
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
// 如果更新失败,记录错误日志
Log.e(TAG, "Update note error, should not happen");
// 不返回,继续执行(为了数据安全)
}
// 清除已同步的差异值
mNoteDiffValues.clear();
// 如果便签数据有修改,尝试推送到数据库
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
// 如果推送失败返回false
return false;
}
// 所有同步操作完成,返回成功
return true;
}
// 内部类NoteData用于管理便签的关联数据文本数据和通话记录数据
private class NoteData {
// 文本数据在数据库中的ID
private long mTextDataId;
// 存储文本数据的属性值
private ContentValues mTextDataValues;
// 通话记录数据在数据库中的ID
private long mCallDataId;
// 存储通话记录数据的属性值
private ContentValues mCallDataValues;
// 日志标签
private static final String TAG = "NoteData";
// 构造函数初始化NoteData对象
public NoteData() {
// 初始化文本数据容器
mTextDataValues = new ContentValues();
// 初始化通话数据容器
mCallDataValues = new ContentValues();
// 初始化文本数据ID为0
mTextDataId = 0;
// 初始化通话数据ID为0
mCallDataId = 0;
}
// 检查本地数据是否有修改
// @return true表示有修改false表示无修改
boolean isLocalModified() {
// 文本数据有修改 或 通话数据有修改返回true
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
// 设置文本数据的ID
// @param id 文本数据ID
void setTextDataId(long id) {
// 检查ID是否有效
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
// 设置文本数据ID
mTextDataId = id;
}
// 设置通话记录数据的ID
// @param id 通话数据ID
void setCallDataId(long id) {
// 检查ID是否有效
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
// 设置通话数据ID
mCallDataId = id;
}
// 设置通话记录数据的属性值
// @param key 属性键名
// @param value 属性值
void setCallData(String key, String value) {
// 将键值对存入通话数据容器
mCallDataValues.put(key, value);
// 标记为本地已修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新修改时间为当前时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 设置文本数据的属性值
// @param key 属性键名
// @param value 属性值
void setTextData(String key, String value) {
// 将键值对存入文本数据容器
mTextDataValues.put(key, value);
// 标记为本地已修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新修改时间为当前时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 将数据推送到ContentResolver数据库
// @param context Android上下文
// @param noteId 便签ID
// @return 成功返回URI失败返回null
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* 便ID
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 创建批量操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 批量操作构建器
ContentProviderOperation.Builder builder = null;
// 如果文本数据有修改
if(mTextDataValues.size() > 0) {
// 设置关联的便签ID
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
// 如果文本数据ID为0说明是新数据需要插入
if (mTextDataId == 0) {
// 设置MIME类型为文本便签
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
// 插入到数据库
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
// 从返回的URI中解析出新插入的数据ID
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
// 如果解析失败,记录错误日志
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
// 清空文本数据容器
mTextDataValues.clear();
return null;
}
} else {
// 如果是已有数据,构建更新操作
// 创建更新操作的构建器指定要更新的资源URI
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
// 设置要更新的值
builder.withValues(mTextDataValues);
// 将操作添加到批量操作列表
operationList.add(builder.build());
}
// 清空文本数据容器
mTextDataValues.clear();
}
// 如果通话记录数据有修改
if(mCallDataValues.size() > 0) {
// 设置关联的便签ID
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
// 如果通话数据ID为0说明是新数据需要插入
if (mCallDataId == 0) {
// 设置MIME类型为通话记录
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
// 插入到数据库
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
// 从返回的URI中解析出新插入的数据ID
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
// 如果解析失败,记录错误日志
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
// 清空通话数据容器
mCallDataValues.clear();
return null;
}
} else {
// 如果是已有数据,构建更新操作
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
// 设置要更新的值
builder.withValues(mCallDataValues);
// 将操作添加到批量操作列表
operationList.add(builder.build());
}
// 清空通话数据容器
mCallDataValues.clear();
}
// 如果有批量操作需要执行
if (operationList.size() > 0) {
try {
// 批量执行所有操作
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
// 检查执行结果如果成功返回便签URI否则返回null
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
// 捕获远程操作异常,记录日志
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
// 捕获操作应用异常,记录日志
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
// 没有需要执行的操作返回null
return null;
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,498 +0,0 @@
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {
// 工作便签的笔记对象
private Note mNote;
// 便签ID
private long mNoteId;
// 便签内容
private String mContent;
// 便签模式(普通模式或清单模式)
private int mMode;
// 提醒日期(时间戳)
private long mAlertDate;
// 最后修改日期(时间戳)
private long mModifiedDate;
// 背景颜色ID
private int mBgColorId;
// 桌面小部件ID
private int mWidgetId;
// 桌面小部件类型2x或4x
private int mWidgetType;
// 所属文件夹ID
private long mFolderId;
// Android上下文对象
private Context mContext;
// 日志标签
private static final String TAG = "WorkingNote";
// 删除标记,表示便签是否已被删除
private boolean mIsDeleted;
// 便签设置变更监听器
private NoteSettingChangedListener mNoteSettingStatusListener;
// 数据查询投影数组 - 用于从数据库查询便签数据内容
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
// 便签查询投影数组 - 用于从数据库查询便签基本信息
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
// 数据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 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;
}
// 现有便签构造函数 - 从数据库加载已存在的便签
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote();
}
/**
* 便
*/
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
cursor.close();
} else {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
loadNoteData();
}
/**
* 便
*/
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
cursor.close();
} else {
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
/**
* 便
* @param context Android
* @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);
return note;
}
/**
* ID便
* @param context Android
* @param id 便ID
* @return 便
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
* 便
* @return truefalse
*/
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
/**
* 便
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
/**
* 便ID
* @return truefalse
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
* 便
* @return truefalse
*/
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) {
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
/**
* 便
* @param mark truefalse
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
/**
* 便
* @param id ID
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
/**
* 便
* @param mode 01
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
/**
*
* @param type 2x4x
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
/**
* ID
* @param id ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
/**
* 便
* @param text 便
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
/**
* 便便
* @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));
}
/**
* 便
* @return truefalse
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
/**
* 便
* @return 便
*/
public String getContent() {
return mContent;
}
/**
*
* @return 0
*/
public long getAlertDate() {
return mAlertDate;
}
/**
* 便
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* IDUI
* @return ID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* ID
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* IDUI
* @return ID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
* 便
* @return 01
*/
public int getCheckListMode() {
return mMode;
}
/**
* 便ID
* @return 便ID便0
*/
public long getNoteId() {
return mNoteId;
}
/**
* 便ID
* @return ID
*/
public long getFolderId() {
return mFolderId;
}
/**
* ID
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
*
* @return 2x4x
*/
public int getWidgetType() {
return mWidgetType;
}
/**
* 便 - UI便
*/
public interface NoteSettingChangedListener {
/**
* 便
*/
void onBackgroundColorChanged();
/**
*
* @param date
* @param set truefalse
*/
void onClockAlertChanged(long date, boolean set);
/**
* 便
*/
void onWidgetChanged();
/**
*
* @param oldMode
* @param newMode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

File diff suppressed because it is too large Load Diff

@ -1,264 +0,0 @@
# 小米便签搜索功能优化说明
## 改进内容
### 优化搜索功能Search Enhancement
#### 问题描述
原小米便签的搜索功能存在以下缺陷:
1. **只有高亮,没有过滤** - 搜索后只显示关键词高亮,不会筛选出匹配的便签
2. **搜索入口不明显** - 依赖系统搜索功能,用户难以发现
3. **搜索体验差** - 无法实时搜索,需要跳转到搜索页面
#### 改进方案
##### 1. 新增搜索框到列表页
**文件修改**`app/src/main/res/layout/note_list.xml`
添加了搜索框组件,包含:
- `LinearLayout` (id: ll_search_box) - 搜索框容器
- `EditText` (id: et_search) - 搜索输入框
- `Button` (id: btn_cancel_search) - 取消搜索按钮
**布局特点**
```xml
<!-- 搜索框默认隐藏,点击菜单后显示 -->
<LinearLayout
android:id="@+id/ll_search_box"
android:visibility="gone"
...>
<!-- 搜索输入框 -->
<EditText
android:id="@+id/et_search"
android:hint="搜索便签..."
android:imeOptions="actionSearch"
... />
<!-- 取消按钮 -->
<Button
android:id="@+id/btn_cancel_search"
android:text="取消"
... />
</LinearLayout>
```
##### 2. 新增字符串资源
**文件修改**`app/src/main/res/values/strings.xml`
```xml
<string name="hint_search">搜索便签...</string>
<string name="menu_search">搜索</string>
<string name="menu_cancel_search">取消</string>
<string name="search_result_empty">没有找到相关便签</string>
```
##### 3. 功能实现要点(需要继续开发)
**需要在 NotesListActivity.java 中添加的代码逻辑**
```java
// 1. 声明搜索相关变量
private LinearLayout mSearchBox;
private EditText mSearchEditText;
private Button mCancelSearchButton;
private String mSearchQuery = "";
// 2. 在 initResources() 中初始化搜索组件
mSearchBox = findViewById(R.id.ll_search_box);
mSearchEditText = findViewById(R.id.et_search);
mCancelSearchButton = findViewById(R.id.btn_cancel_search);
// 3. 添加搜索菜单项
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
return true;
}
// 4. 处理搜索菜单点击
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_search) {
showSearchBox();
return true;
}
return super.onOptionsItemSelected(item);
}
// 5. 显示搜索框
private void showSearchBox() {
mSearchBox.setVisibility(View.VISIBLE);
mSearchEditText.requestFocus();
// 显示软键盘
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
}
// 6. 实现实时搜索过滤
mSearchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mSearchQuery = s.toString();
// 重新查询数据库,添加搜索条件
startQueryNotes();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
});
// 7. 修改数据库查询,添加搜索条件
private void startQueryNotes() {
String selection;
String[] selectionArgs;
if (!TextUtils.isEmpty(mSearchQuery)) {
// 有搜索关键词,添加过滤条件
selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.SNIPPET + " LIKE ?";
selectionArgs = new String[] {
String.valueOf(mCurrentFolderId),
"%" + mSearchQuery + "%"
};
} else {
// 无搜索关键词,正常查询
selection = NoteColumns.PARENT_ID + "=?";
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
}
// 执行查询
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs,
NoteColumns.MODIFIED_DATE + " DESC");
}
// 8. 取消搜索
mCancelSearchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideSearchBox();
}
});
private void hideSearchBox() {
mSearchBox.setVisibility(View.GONE);
mSearchEditText.setText("");
mSearchQuery = "";
// 隐藏软键盘
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), 0);
// 重新查询所有便签
startQueryNotes();
}
```
---
## 修改的文件清单
### 修改文件
1. `app/src/main/res/layout/note_list.xml`
- 添加搜索框布局组件
- 包含搜索输入框和取消按钮
2. `app/src/main/res/values/strings.xml`
- 添加搜索相关的字符串资源
### 待修改文件(需要继续开发)
1. `app/src/main/java/net/micode/notes/ui/NotesListActivity.java`
- 添加搜索逻辑
- 实现实时过滤功能
- 添加菜单项处理
2. `app/src/main/res/menu/note_list_options.xml`(需要添加)
- 添加搜索菜单项
---
## 功能特点
### 已实现
**搜索框UI** - 在列表页顶部添加搜索框
**搜索字符串资源** - 支持多语言
**默认隐藏** - 不占用空间,需要时展开
### 待实现(下一步开发)
**实时搜索过滤** - 输入时自动过滤便签列表
**搜索高亮** - 在搜索结果中高亮显示关键词
**搜索历史** - 保存最近的搜索记录
**搜索菜单** - 在菜单中添加搜索入口
**空结果提示** - 搜索无结果时显示提示
---
## 测试建议
### UI测试
1. [ ] 搜索框是否正确显示
2. [ ] 搜索框默认是否隐藏
3. [ ] 软键盘是否正确弹出
4. [ ] 取消按钮是否正常工作
### 功能测试(待实现后)
1. [ ] 输入关键词是否实时过滤
2. [ ] 搜索匹配是否准确(模糊匹配)
3. [ ] 取消搜索是否恢复完整列表
4. [ ] 搜索无结果是否显示提示
5. [ ] 中英文名称是否都能搜索
---
## 技术实现原理
### 数据库查询过滤
```sql
-- 原查询(无过滤)
SELECT * FROM note WHERE parent_id = ?
-- 搜索查询(添加过滤)
SELECT * FROM note
WHERE parent_id = ?
AND snippet LIKE '%关键词%'
```
### 实时搜索流程
```
用户输入 → TextWatcher监听 → 更新搜索关键词 → 重新查询数据库 → 更新列表显示
```
---
## 总结
本次改进为小米便签添加了**搜索框UI**,主要特点:
**用户友好** - 搜索入口明显,操作简单
**实时搜索** - 输入即过滤,无需跳转
**空间优化** - 默认隐藏,不占用列表空间
**扩展性强** - 可轻松添加搜索历史、搜索建议等功能
**注意**本次改进主要完成了UI层面的修改完整的搜索功能还需要在`NotesListActivity.java`中添加相应的逻辑代码。这是一个很好的起点,后续可以根据需要继续完善搜索功能!
---
## 后续开发建议
### 阶段 1基础搜索功能当前已完成50%
- [x] 搜索框UI
- [ ] 实时搜索过滤
- [ ] 取消搜索功能
### 阶段 2增强搜索体验
- [ ] 搜索高亮显示
- [ ] 搜索历史记录
- [ ] 搜索动画效果
### 阶段 3高级搜索功能
- [ ] 全文搜索(搜索便签内容,不仅是标题)
- [ ] 多关键词搜索
- [ ] 搜索排序选项

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background">
<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" />
<!-- 搜索框 - 新增功能 -->
<LinearLayout
android:id="@+id/ll_search_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
android:background="#20000000"
android:visibility="gone">
<EditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="搜索便签..."
android:textColorHint="#88000000"
android:textColor="#FF000000"
android:background="@android:color/white"
android:padding="10dp"
android:maxLines="1"
android:imeOptions="actionSearch"
android:inputType="text" />
<Button
android:id="@+id/btn_cancel_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="取消"
android:textColor="#FF000000"
android:background="@android:color/white"
android:padding="10dp" />
</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>
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="bottom" />
</FrameLayout>

@ -1,85 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
便签列表布局 - 增强搜索功能版本
添加了搜索框,支持实时搜索和过滤便签
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background">
<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" />
<!-- 搜索框 -->
<LinearLayout
android:id="@+id/ll_search_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
android:background="#20000000"
android:visibility="gone">
<!-- 搜索输入框 -->
<EditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/hint_search"
android:textColorHint="#88000000"
android:textColor="#FF000000"
android:background="@android:color/white"
android:padding="10dp"
android:maxLines="1"
android:imeOptions="actionSearch"
android:inputType="text" />
<!-- 取消搜索按钮 -->
<Button
android:id="@+id/btn_cancel_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/menu_cancel_search"
android:textColor="#FF000000"
android:background="@android:color/white"
android:padding="10dp" />
</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>
<!-- 新建便签按钮 -->
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="bottom" />
</FrameLayout>

@ -1,139 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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="notelist_string_info">...</string>
<string name="notelist_menu_new">Add note</string>
<string name="delete_remind_time_message">Delete reminder successfully</string>
<string name="set_remind_time_message">Set reminder</string>
<string name="note_alert_expired">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="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>
<!-- 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_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>
<!-- 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>
<!-- 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>
<!-- 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_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="button_delete">Delete</string>
<string name="call_record_folder_name">Call notes</string>
<string name="hint_foler_name">Input 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="search">Notes</string>
<string name="datetime_dialog_ok">set</string>
<string name="datetime_dialog_cancel">cancel</string>
<plurals name="search_results_title">
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
</plurals>
<!-- 搜索相关字符串 - 新增 -->
<string name="hint_search">搜索便签...</string>
<string name="menu_cancel_search">取消</string>
<string name="search_result_empty">没有找到相关便签</string>
</resources>
Loading…
Cancel
Save