|
|
|
@ -450,150 +450,224 @@ public boolean onMenuItemClick(MenuItem item) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private class NewNoteOnTouchListener implements OnTouchListener {
|
|
|
|
|
|
|
|
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
|
|
|
switch (event.getAction()) {
|
|
|
|
|
case MotionEvent.ACTION_DOWN: {
|
|
|
|
|
Display display = getWindowManager().getDefaultDisplay();
|
|
|
|
|
int screenHeight = display.getHeight();
|
|
|
|
|
int newNoteViewHeight = mAddNewNote.getHeight();
|
|
|
|
|
int start = screenHeight - newNoteViewHeight;
|
|
|
|
|
int eventY = start + (int) event.getY();
|
|
|
|
|
/**
|
|
|
|
|
* Minus TitleBar's height
|
|
|
|
|
*/
|
|
|
|
|
if (mState == ListEditState.SUB_FOLDER) {
|
|
|
|
|
eventY -= mTitleBar.getHeight();
|
|
|
|
|
start -= mTitleBar.getHeight();
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* HACKME:When click the transparent part of "New Note" button, dispatch
|
|
|
|
|
* the event to the list view behind this button. The transparent part of
|
|
|
|
|
* "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel)
|
|
|
|
|
* and the line top of the button. The coordinate based on left of the "New
|
|
|
|
|
* Note" button. The 94 represents maximum height of the transparent part.
|
|
|
|
|
* Notice that, if the background of the button changes, the formula should
|
|
|
|
|
* also change. This is very bad, just for the UI designer's strong requirement.
|
|
|
|
|
*/
|
|
|
|
|
if (event.getY() < (event.getX() * (-0.12) + 94)) {
|
|
|
|
|
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
|
|
|
|
|
- mNotesListView.getFooterViewsCount());
|
|
|
|
|
if (view != null && view.getBottom() > start
|
|
|
|
|
&& (view.getTop() < (start + 94))) {
|
|
|
|
|
mOriginY = (int) event.getY();
|
|
|
|
|
mDispatchY = eventY;
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
mDispatch = true;
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// 定义一个私有内部类NewNoteOnTouchListener,实现OnTouchListener接口
|
|
|
|
|
private class NewNoteOnTouchListener implements OnTouchListener {
|
|
|
|
|
|
|
|
|
|
// 实现OnTouchListener接口的onTouch方法
|
|
|
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
|
|
|
// 根据MotionEvent的动作类型进行不同的处理
|
|
|
|
|
switch (event.getAction()) {
|
|
|
|
|
// 当用户按下屏幕时
|
|
|
|
|
case MotionEvent.ACTION_DOWN: {
|
|
|
|
|
// 获取当前窗口的显示信息
|
|
|
|
|
Display display = getWindowManager().getDefaultDisplay();
|
|
|
|
|
// 获取屏幕的高度
|
|
|
|
|
int screenHeight = display.getHeight();
|
|
|
|
|
// 获取“新建笔记”视图的高度
|
|
|
|
|
int newNoteViewHeight = mAddNewNote.getHeight();
|
|
|
|
|
// 计算起始位置
|
|
|
|
|
int start = screenHeight - newNoteViewHeight;
|
|
|
|
|
// 计算触摸事件的Y坐标
|
|
|
|
|
int eventY = start + (int) event.getY();
|
|
|
|
|
|
|
|
|
|
// 如果当前状态是子文件夹,需要减去标题栏的高度
|
|
|
|
|
if (mState == ListEditState.SUB_FOLDER) {
|
|
|
|
|
eventY -= mTitleBar.getHeight();
|
|
|
|
|
start -= mTitleBar.getHeight();
|
|
|
|
|
}
|
|
|
|
|
case MotionEvent.ACTION_MOVE: {
|
|
|
|
|
if (mDispatch) {
|
|
|
|
|
mDispatchY += (int) event.getY() - mOriginY;
|
|
|
|
|
|
|
|
|
|
// 处理“新建笔记”按钮透明部分的事件分发
|
|
|
|
|
// 当点击按钮的透明部分时,将事件分发给背后的列表视图
|
|
|
|
|
if (event.getY() < (event.getX() * (-0.12) + 94)) {
|
|
|
|
|
// 获取列表视图中的最后一个子视图
|
|
|
|
|
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
|
|
|
|
|
- mNotesListView.getFooterViewsCount());
|
|
|
|
|
// 如果子视图存在,并且其底部在“新建笔记”按钮的起始位置之上
|
|
|
|
|
// 且顶部在“新建笔记”按钮的起始位置加上94像素之下
|
|
|
|
|
if (view != null && view.getBottom() > start
|
|
|
|
|
&& (view.getTop() < (start + 94))) {
|
|
|
|
|
// 记录原始Y坐标和需要分发的Y坐标
|
|
|
|
|
mOriginY = (int) event.getY();
|
|
|
|
|
mDispatchY = eventY;
|
|
|
|
|
// 更新事件的坐标
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
// 设置分发标志
|
|
|
|
|
mDispatch = true;
|
|
|
|
|
// 分发触摸事件给列表视图
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
if (mDispatch) {
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
mDispatch = false;
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// 当用户在屏幕上移动手指时
|
|
|
|
|
case MotionEvent.ACTION_MOVE: {
|
|
|
|
|
// 如果需要分发事件
|
|
|
|
|
if (mDispatch) {
|
|
|
|
|
// 更新需要分发的Y坐标
|
|
|
|
|
mDispatchY += (int) event.getY() - mOriginY;
|
|
|
|
|
// 更新事件的坐标
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
// 分发触摸事件给列表视图
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// 默认情况,处理其他动作类型
|
|
|
|
|
default: {
|
|
|
|
|
// 如果需要分发事件
|
|
|
|
|
if (mDispatch) {
|
|
|
|
|
// 更新事件的坐标
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
// 重置分发标志
|
|
|
|
|
mDispatch = false;
|
|
|
|
|
// 分发触摸事件给列表视图
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 如果不需要处理事件,返回false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void startAsyncNotesListQuery() {
|
|
|
|
|
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
|
|
|
|
|
: NORMAL_SELECTION;
|
|
|
|
|
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
|
|
|
|
|
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
|
|
|
|
|
String.valueOf(mCurrentFolderId)
|
|
|
|
|
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final class BackgroundQueryHandler extends AsyncQueryHandler {
|
|
|
|
|
public BackgroundQueryHandler(ContentResolver contentResolver) {
|
|
|
|
|
super(contentResolver);
|
|
|
|
|
}
|
|
|
|
|
// 定义一个私有方法startAsyncNotesListQuery,用于启动异步查询笔记列表
|
|
|
|
|
private void startAsyncNotesListQuery() {
|
|
|
|
|
// 根据当前文件夹ID设置查询条件,如果是根文件夹,使用根文件夹的查询条件,否则使用普通查询条件
|
|
|
|
|
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
|
|
|
|
|
: NORMAL_SELECTION;
|
|
|
|
|
// 使用BackgroundQueryHandler启动查询,传入查询令牌、Cookie(这里为null)、内容URI、查询列、查询条件、查询参数、排序规则
|
|
|
|
|
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
|
|
|
|
|
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
|
|
|
|
|
String.valueOf(mCurrentFolderId)
|
|
|
|
|
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
|
|
|
|
|
switch (token) {
|
|
|
|
|
case FOLDER_NOTE_LIST_QUERY_TOKEN:
|
|
|
|
|
mNotesListAdapter.changeCursor(cursor);
|
|
|
|
|
break;
|
|
|
|
|
case FOLDER_LIST_QUERY_TOKEN:
|
|
|
|
|
if (cursor != null && cursor.getCount() > 0) {
|
|
|
|
|
showFolderListMenu(cursor);
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Query folder failed");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 定义一个私有内部类BackgroundQueryHandler,继承自AsyncQueryHandler
|
|
|
|
|
private final class BackgroundQueryHandler extends AsyncQueryHandler {
|
|
|
|
|
// 构造方法,接收ContentResolver作为参数,并调用父类构造方法
|
|
|
|
|
public BackgroundQueryHandler(ContentResolver contentResolver) {
|
|
|
|
|
super(contentResolver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重写onQueryComplete方法,当查询完成时被调用
|
|
|
|
|
@Override
|
|
|
|
|
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
|
|
|
|
|
// 根据查询令牌进行不同的处理
|
|
|
|
|
switch (token) {
|
|
|
|
|
// 如果是笔记列表查询令牌
|
|
|
|
|
case FOLDER_NOTE_LIST_QUERY_TOKEN:
|
|
|
|
|
// 更新适配器的Cursor
|
|
|
|
|
mNotesListAdapter.changeCursor(cursor);
|
|
|
|
|
break;
|
|
|
|
|
// 如果是文件夹列表查询令牌
|
|
|
|
|
case FOLDER_LIST_QUERY_TOKEN:
|
|
|
|
|
// 如果Cursor不为空且包含数据
|
|
|
|
|
if (cursor != null && cursor.getCount() > 0) {
|
|
|
|
|
// 显示文件夹列表菜单
|
|
|
|
|
showFolderListMenu(cursor);
|
|
|
|
|
} else {
|
|
|
|
|
// 如果查询失败,记录错误日志
|
|
|
|
|
Log.e(TAG, "Query folder failed");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// 默认情况,不做任何处理
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void showFolderListMenu(Cursor cursor) {
|
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
|
|
|
|
|
builder.setTitle(R.string.menu_title_select_folder);
|
|
|
|
|
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
|
|
|
|
|
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
|
|
|
|
|
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
DataUtils.batchMoveToFolder(mContentResolver,
|
|
|
|
|
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
|
|
|
|
|
Toast.makeText(
|
|
|
|
|
NotesListActivity.this,
|
|
|
|
|
getString(R.string.format_move_notes_to_folder,
|
|
|
|
|
mNotesListAdapter.getSelectedCount(),
|
|
|
|
|
adapter.getFolderName(NotesListActivity.this, which)),
|
|
|
|
|
Toast.LENGTH_SHORT).show();
|
|
|
|
|
mModeCallBack.finishActionMode();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
builder.show();
|
|
|
|
|
}
|
|
|
|
|
// 定义一个私有方法showFolderListMenu,用于显示文件夹列表菜单
|
|
|
|
|
private void showFolderListMenu(Cursor cursor) {
|
|
|
|
|
// 创建一个AlertDialog.Builder对象,用于构建对话框
|
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
|
|
|
|
|
// 设置对话框的标题,使用资源文件中的字符串
|
|
|
|
|
builder.setTitle(R.string.menu_title_select_folder);
|
|
|
|
|
// 创建一个FoldersListAdapter对象,用于适配数据到列表视图
|
|
|
|
|
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
|
|
|
|
|
// 设置对话框的适配器,并定义一个点击事件监听器
|
|
|
|
|
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
|
|
|
|
|
|
|
|
|
|
// 当对话框中的项目被点击时,会调用此方法
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
// 使用DataUtils工具类批量移动笔记到指定的文件夹
|
|
|
|
|
DataUtils.batchMoveToFolder(mContentResolver,
|
|
|
|
|
// 获取当前选中的笔记项的ID列表
|
|
|
|
|
mNotesListAdapter.getSelectedItemIds(),
|
|
|
|
|
// 获取被点击的文件夹的ID
|
|
|
|
|
adapter.getItemId(which));
|
|
|
|
|
// 显示一个Toast消息,通知用户笔记已移动到指定文件夹
|
|
|
|
|
Toast.makeText(
|
|
|
|
|
NotesListActivity.this,
|
|
|
|
|
// 使用资源文件中的格式化字符串,并传递参数
|
|
|
|
|
getString(R.string.format_move_notes_to_folder,
|
|
|
|
|
// 获取选中的笔记数量
|
|
|
|
|
mNotesListAdapter.getSelectedCount(),
|
|
|
|
|
// 获取被点击的文件夹名称
|
|
|
|
|
adapter.getFolderName(NotesListActivity.this, which)),
|
|
|
|
|
// 设置Toast显示时长为短时间
|
|
|
|
|
Toast.LENGTH_SHORT).show();
|
|
|
|
|
// 调用mModeCallBack的finishActionMode方法,结束当前的操作模式
|
|
|
|
|
mModeCallBack.finishActionMode();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// 显示构建好的对话框
|
|
|
|
|
builder.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void createNewNote() {
|
|
|
|
|
Intent intent = new Intent(this, NoteEditActivity.class);
|
|
|
|
|
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
|
|
|
|
|
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
|
|
|
|
|
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
|
|
|
|
|
}
|
|
|
|
|
// 定义一个私有方法createNewNote,用于创建一个新的笔记
|
|
|
|
|
private void createNewNote() {
|
|
|
|
|
// 创建一个新的Intent对象,指定从当前活动跳转到NoteEditActivity
|
|
|
|
|
Intent intent = new Intent(this, NoteEditActivity.class);
|
|
|
|
|
// 设置Intent的动作,这里使用ACTION_INSERT_OR_EDIT,表示插入或编辑操作
|
|
|
|
|
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
|
|
|
|
|
// 将当前文件夹的ID作为额外数据放入Intent中,以便NoteEditActivity可以使用
|
|
|
|
|
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
|
|
|
|
|
// 启动NoteEditActivity,并期望它返回结果
|
|
|
|
|
// REQUEST_CODE_NEW_NODE是请求代码,用于在onActivityResult中区分请求来源
|
|
|
|
|
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void batchDelete() {
|
|
|
|
|
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
|
|
|
|
|
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
|
|
|
|
|
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
|
|
|
|
|
if (!isSyncMode()) {
|
|
|
|
|
// if not synced, delete notes directly
|
|
|
|
|
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
|
|
|
|
|
.getSelectedItemIds())) {
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Delete notes error, should not happens");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 定义一个私有方法batchDelete,用于批量删除笔记
|
|
|
|
|
private void batchDelete() {
|
|
|
|
|
// 创建一个新的AsyncTask匿名内部类,用于在后台线程执行删除操作
|
|
|
|
|
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
|
|
|
|
|
// doInBackground方法在后台线程执行,参数为Void...表示没有输入参数
|
|
|
|
|
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
|
|
|
|
|
// 获取选中的笔记对应的桌面小部件属性集合
|
|
|
|
|
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
|
|
|
|
|
// 判断当前是否处于同步模式
|
|
|
|
|
if (!isSyncMode()) {
|
|
|
|
|
// 如果不是同步模式,直接删除笔记
|
|
|
|
|
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
|
|
|
|
|
.getSelectedItemIds())) {
|
|
|
|
|
// 如果删除成功,这里没有做额外处理
|
|
|
|
|
} else {
|
|
|
|
|
// in sync mode, we'll move the deleted note into the trash
|
|
|
|
|
// folder
|
|
|
|
|
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
|
|
|
|
|
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
|
|
|
|
|
Log.e(TAG, "Move notes to trash folder error, should not happens");
|
|
|
|
|
}
|
|
|
|
|
// 如果删除失败,记录错误日志
|
|
|
|
|
Log.e(TAG, "Delete notes error, should not happens");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果是同步模式,将删除的笔记移动到垃圾箱文件夹
|
|
|
|
|
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
|
|
|
|
|
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
|
|
|
|
|
// 如果移动到垃圾箱文件夹失败,记录错误日志
|
|
|
|
|
Log.e(TAG, "Move notes to trash folder error, should not happens");
|
|
|
|
|
}
|
|
|
|
|
return widgets;
|
|
|
|
|
}
|
|
|
|
|
// 返回桌面小部件属性集合,可能会用于更新桌面小部件
|
|
|
|
|
return widgets;
|
|
|
|
|
}
|
|
|
|
|
// onPostExecute方法在主线程执行,用于处理doInBackground返回的结果,这里没有实现
|
|
|
|
|
// onCancelled方法在任务被取消时调用,这里没有实现
|
|
|
|
|
};
|
|
|
|
|
// 注意:这里没有调用AsyncTask的execute方法来启动任务,所以实际上这段代码不会执行
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
|
|
|
|
|