You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
XiaoMiNotes/src/ui/NotesListActivity.java

2684 lines
120 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.Gravity;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
/**
* 笔记列表活动
* 笔记应用的主界面,显示笔记和文件夹列表,提供笔记的浏览、管理、搜索和设置等功能
* 支持多选操作、文件夹管理、导入导出等高级功能
*/
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 异步查询标记
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 文件夹笔记列表查询标记
private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 文件夹列表查询标记
// 上下文菜单项ID
private static final int MENU_FOLDER_DELETE = 0; // 删除文件夹
private static final int MENU_FOLDER_VIEW = 1; // 查看文件夹
private static final int MENU_FOLDER_CHANGE_NAME = 2; // 修改文件夹名称
private static final int MENU_FOLDER_PIN = 3; // 置顶文件夹
private static final int MENU_FOLDER_UNPIN = 4; // 取消置顶文件夹
private static final int MENU_NOTE_EDIT_TOPIC = 5; // 编辑便签标题
private static final int MENU_NOTE_PIN = 6; // 置顶便签
private static final int MENU_NOTE_UNPIN = 7; // 取消置顶便签
private static final int MENU_RESTORE = 100; // 恢复
private static final int MENU_PERMANENT_DELETE = 101; // 彻底删除
private static final int MENU_NOTE_DELETE = 102; // 删除便签
// 首选项键名
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
/**
* 列表编辑状态枚举
*/
private enum ListEditState {
NOTE_LIST, // 普通笔记列表
SUB_FOLDER, // 子文件夹视图
CALL_RECORD_FOLDER, // 通话记录文件夹
TRASH_FOLDER // 回收站文件夹
};
private ListEditState mState; // 当前列表状态
private BackgroundQueryHandler mBackgroundQueryHandler; // 后台查询处理器
private NotesListAdapter mNotesListAdapter; // 笔记列表适配器
private ListView mNotesListView; // 笔记列表视图
private Button mAddNewNote; // 新建笔记按钮
private boolean mDispatch; // 是否分发触摸事件标志
private int mOriginY; // 原始Y坐标
private int mDispatchY; // 分发Y坐标
private LinearLayout mTitleBarLayout; // 标题栏布局
private TextView mTitleBar; // 标题栏文本
private Button mStudyTimerButton; // 学习计时器按钮
private long mCurrentFolderId; // 当前文件夹ID
private ContentResolver mContentResolver; // 内容解析器
private ModeCallback mModeCallBack; // 多选模式回调
private static final String TAG = "NotesListActivity"; // 日志标签
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // 列表滚动速率
private NoteItemData mFocusNoteDataItem; // 当前焦点笔记项数据
// 查询条件
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?) OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
private static final String TRASH_SELECTION = "(" + NoteColumns.PARENT_ID + "=?" + " AND "
+ NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + ")";
// 请求码
private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开笔记
private final static int REQUEST_CODE_NEW_NODE = 103; // 新建笔记
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list); // 设置列表布局
// 初始化资源和视图
initResources();
/**
* 首次使用时插入介绍说明
*/
setAppInfoFromRawRes();
// 注册Android 13+的返回键回调
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new android.window.OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
handleBackPress();
}
}
);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
// 笔记操作后刷新列表
mNotesListAdapter.changeCursor(null);
// 接收并处理从 NoteEditActivity 返回的计时器状态
if (data != null) {
boolean isTimerRunning = data.getBooleanExtra("is_timer_running", false);
long timerCurrentTime = data.getLongExtra("timer_current_time", 0);
boolean isCountdownMode = data.getBooleanExtra("is_countdown_mode", false);
long timerDuration = data.getLongExtra("timer_duration", 0);
// 更新计时器状态
mIsTimerRunning = isTimerRunning;
mTimerCurrentTime = timerCurrentTime;
mIsCountdownMode = isCountdownMode;
mTimerDuration = timerDuration;
// 如果计时器已经停止,关闭对话框并刷新列表
if (!mIsTimerRunning) {
// 关闭对话框
if (mTimerDialog != null && mTimerDialog.isShowing()) {
mTimerDialog.dismiss();
mTimerDialog = null;
}
// 刷新文件夹显示,更新专注时长
startAsyncNotesListQuery();
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
/**
* 从资源文件读取应用介绍信息并插入到笔记中
*/
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
// 检查是否已经添加过介绍
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
char [] buf = new char[1024];
int len = 0;
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 创建介绍笔记
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString());
if (note.saveNote()) {
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
return;
}
}
}
@Override
protected void onStart() {
super.onStart();
startAsyncNotesListQuery(); // 启动异步笔记列表查询
}
/**
* 初始化资源和视图
*/
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 默认根文件夹
mNotesListView = (ListView) findViewById(R.id.notes_list);
// 添加列表脚部视图
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置列表项点击监听
mNotesListView.setOnItemLongClickListener(this); // 设置列表项长按监听
mNotesListAdapter = new NotesListAdapter(this); // 创建列表适配器
mNotesListView.setAdapter(mNotesListAdapter);
// 注册上下文菜单,用于文件夹长按操作
registerForContextMenu(mNotesListView);
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
mAddNewNote.setOnClickListener(this); // 设置新建笔记按钮点击监听
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置新建笔记按钮触摸监听
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
mTitleBarLayout = (LinearLayout) findViewById(R.id.title_bar);
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mStudyTimerButton = (Button) findViewById(R.id.btn_study_timer);
mStudyTimerButton.setOnClickListener(this); // 设置时钟按钮点击监听器
mState = ListEditState.NOTE_LIST; // 初始状态为普通笔记列表
mModeCallBack = new ModeCallback(); // 创建多选模式回调
}
/**
* 多选模式回调类
* 实现列表的多选操作功能
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu; // 下拉菜单
private ActionMode mActionMode; // 操作模式
private MenuItem mMoveMenu; // 移动菜单项
private MenuItem mDeleteMenu; // 删除菜单项
private MenuItem mRestoreMenu; // 恢复菜单项
// 定义菜单项ID常量
private static final int MENU_RESTORE = 100;
private static final int MENU_PERMANENT_DELETE = 101;
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
menu.clear(); // 清空菜单
if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
// 回收站中显示恢复和彻底删除菜单
menu.add(0, MENU_RESTORE, 0, R.string.menu_restore)
.setOnMenuItemClickListener(this);
menu.add(0, MENU_PERMANENT_DELETE, 1, R.string.menu_permanent_delete)
.setOnMenuItemClickListener(this);
} else {
// 普通文件夹中显示菜单
getMenuInflater().inflate(R.menu.note_list_options, menu); // 加载多选菜单
menu.findItem(R.id.delete).setOnMenuItemClickListener(this); // 删除菜单项
mMoveMenu = menu.findItem(R.id.move); // 移动菜单项
// 根据条件设置移动菜单项可见性
boolean hideMoveMenu = DataUtils.getUserFolderCount(mContentResolver) == 0;
// 如果有焦点便签且它来自通话记录文件夹,也隐藏移动菜单
if (mFocusNoteDataItem != null) {
hideMoveMenu = hideMoveMenu || (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER);
}
if (hideMoveMenu) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
// 添加Edit topic选项初始状态隐藏
MenuItem editTopicMenu = menu.add(0, MENU_NOTE_EDIT_TOPIC, 0, R.string.menu_edit_topic);
editTopicMenu.setOnMenuItemClickListener(this);
editTopicMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
// 初始状态隐藏在onPrepareActionMode中根据选中数量动态显示
editTopicMenu.setVisible(false);
}
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true); // 启用选择模式
// 注意:不再禁用长按,以便在多选模式下也能通过长按显示上下文菜单
mAddNewNote.setVisibility(View.GONE); // 隐藏新建笔记按钮
// 设置自定义视图(下拉菜单)
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem item) {
// 全选/取消全选
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
updateMenu(); // 更新菜单
return true;
}
});
return true;
}
/**
* 更新菜单状态
*/
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// 更新下拉菜单标题
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item != null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all); // 全选时显示"取消全选"
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all); // 未全选时显示"全选"
}
}
// 如果有编辑标题菜单,根据选中数量动态显示/隐藏
if (mActionMode != null) {
Menu menu = mActionMode.getMenu();
MenuItem editTopicMenu = menu.findItem(MENU_NOTE_EDIT_TOPIC);
if (editTopicMenu != null) {
editTopicMenu.setVisible(selectedCount == 1);
}
}
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// 根据选中的便签数量动态显示/隐藏Edit topic选项
MenuItem editTopicMenu = menu.findItem(MENU_NOTE_EDIT_TOPIC);
if (editTopicMenu != null) {
int selectedCount = mNotesListAdapter.getSelectedCount();
editTopicMenu.setVisible(selectedCount == 1);
}
return true;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
public void onDestroyActionMode(ActionMode mode) {
// 退出多选模式时的清理工作
mNotesListAdapter.setChoiceMode(false); // 禁用选择模式
mNotesListView.setLongClickable(true); // 启用长按
mAddNewNote.setVisibility(View.VISIBLE); // 显示新建笔记按钮
mActionMode = null;
mNotesListView.clearChoices();
}
public void finishActionMode() {
if (mActionMode != null) {
mActionMode.finish(); // 结束操作模式,防止空指针异常
}
}
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked); // 设置选中状态
// 当只选择一个便签时保存为焦点项用于Edit topic
if (checked && mNotesListAdapter.getSelectedCount() == 1) {
Cursor cursor = mNotesListAdapter.getCursor();
if (cursor != null && cursor.moveToPosition(position)) {
mFocusNoteDataItem = new NoteItemData(NotesListActivity.this, cursor);
}
} else if (!checked && mNotesListAdapter.getSelectedCount() == 0) {
mFocusNoteDataItem = null;
}
updateMenu(); // 更新菜单
// 注意不要在这里调用mode.finish()否则会导致系统销毁意外的ActionMode实例
// 选择模式应该由用户自己点击返回键或点击空白区域退出
}
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show(); // 未选择任何项时提示
return true;
}
switch (item.getItemId()) {
case R.id.delete:
// 批量删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete(); // 执行批量删除
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
// 保存当前选择模式的状态,防止在异步查询过程中被意外结束
startQueryDestinationFolders(); // 查询目标文件夹
break;
case MENU_RESTORE:
// 批量恢复确认对话框
AlertDialog.Builder restoreBuilder = new AlertDialog.Builder(NotesListActivity.this);
restoreBuilder.setTitle(getString(R.string.menu_restore_from_trash));
restoreBuilder.setIcon(android.R.drawable.ic_dialog_alert);
restoreBuilder.setMessage(getString(R.string.alert_message_restore_notes,
mNotesListAdapter.getSelectedCount()));
restoreBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchRestoreFromTrash(); // 执行批量恢复
}
});
restoreBuilder.setNegativeButton(android.R.string.cancel, null);
restoreBuilder.show();
break;
case MENU_PERMANENT_DELETE:
// 批量彻底删除确认对话框
AlertDialog.Builder permanentDeleteBuilder = new AlertDialog.Builder(NotesListActivity.this);
permanentDeleteBuilder.setTitle(getString(R.string.menu_permanent_delete));
permanentDeleteBuilder.setIcon(android.R.drawable.ic_dialog_alert);
permanentDeleteBuilder.setMessage(getString(R.string.alert_message_permanent_delete,
mNotesListAdapter.getSelectedCount()));
permanentDeleteBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchPermanentDelete(); // 执行批量彻底删除
}
});
permanentDeleteBuilder.setNegativeButton(android.R.string.cancel, null);
permanentDeleteBuilder.show();
break;
case MENU_NOTE_EDIT_TOPIC:
// 编辑便签标题
if (mNotesListAdapter.getSelectedCount() == 1) {
// 在结束选择模式前保存当前焦点便签防止mFocusNoteDataItem被重置
NoteItemData tempNoteItem = mFocusNoteDataItem;
mActionMode.finish(); // 结束选择模式
// 使用保存的便签项显示编辑对话框
mFocusNoteDataItem = tempNoteItem;
if (mFocusNoteDataItem != null) {
showEditNoteTopicDialog();
}
} else {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_single_note),
Toast.LENGTH_SHORT).show();
}
break;
default:
return false;
}
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();
/**
* 减去标题栏的高度
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME:当点击"新建笔记"按钮的透明部分时,将事件分发给列表视图。
* 透明部分的公式为 y=-0.12x+94单位像素和按钮的顶部线条。
* 坐标基于"新建笔记"按钮的左侧。
* 94表示透明部分的最大高度。
* 注意:如果按钮背景改变,公式也应改变。
* 这只是为了满足UI设计师的强烈要求。
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event); // 分发触摸事件
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
};
/**
* 启动异步笔记列表查询
*/
private void startAsyncNotesListQuery() {
String selection;
String[] selectionArgs;
if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) {
// 根文件夹查询条件
selection = ROOT_FOLDER_SELECTION;
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
} else if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
// 回收站根目录查询条件
// 显示所有直接位于回收站根目录的项目,包括单独删除的便签和文件夹
// 对于文件夹,只显示文件夹本身,不显示文件夹内的便签
// 对于单独删除的便签,直接显示在回收站根目录
// 对于文件夹内的便签,不显示在回收站根目录,只显示在文件夹内
// 显示规则:
// 1. 显示所有类型为文件夹的项目(被删除的文件夹)
// 2. 显示类型为便签的项目,并且它们是单独删除的
// 单独删除的判断该便签的ORIGIN_PARENT_ID对应的文件夹不存在于回收站中
selection = NORMAL_SELECTION + " AND (" +
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " OR " +
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " +
"(" +
// 条件该便签的ORIGIN_PARENT_ID对应的文件夹不存在于回收站中
// 即该便签是单独删除的,而不是某个被删除文件夹的内容
NoteColumns.ORIGIN_PARENT_ID + " NOT IN (" +
"SELECT " + NoteColumns.ID + " FROM " +
net.micode.notes.data.NotesDatabaseHelper.TABLE.NOTE + " WHERE " +
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " +
NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
")" +
")" +
"))";
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
} else if (mState == ListEditState.TRASH_FOLDER) {
// 在回收站中打开文件夹时查询ORIGIN_PARENT_ID等于当前文件夹ID的所有便签和文件夹
// 因为当文件夹被移动到回收站时文件夹内的便签的PARENT_ID会被设置为回收站ID
// 但它们的ORIGIN_PARENT_ID会被保存为原来的文件夹ID
selection = NoteColumns.ORIGIN_PARENT_ID + "=?";
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
} else {
// 普通文件夹查询条件
selection = NORMAL_SELECTION;
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
}
// 排序条件:先按类型排序(文件夹在前,便签在后),再按置顶状态排序,最后按修改时间排序
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
NoteItemData.PROJECTION, selection, selectionArgs,
NoteColumns.TYPE + " DESC," + NoteColumns.PINNED + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
* 后台查询处理器
*/
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@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;
}
}
}
/**
* 显示文件夹列表菜单(用于移动笔记)
*/
private void showFolderListMenu(Cursor cursor) {
// 保存当前的ActionMode引用以便在操作完成后检查它是否仍然有效
final ActionMode currentActionMode = mModeCallBack.mActionMode;
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) {
// 获取目标文件夹ID
long targetFolderId = adapter.getItemId(which);
// 获取要移动的便签数量
int moveCount;
if (mNotesListAdapter.isInChoiceMode()) {
// 多选模式下,移动所有选中的便签
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), targetFolderId);
moveCount = mNotesListAdapter.getSelectedCount();
} else {
// 单选模式下,移动当前焦点便签
if (mFocusNoteDataItem != null) {
DataUtils.batchMoveToFolder(mContentResolver,
new HashSet<Long>() {{ add(mFocusNoteDataItem.getId()); }}, targetFolderId);
moveCount = 1;
} else {
moveCount = 0;
}
}
if (moveCount > 0) {
// 显示移动成功提示
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
moveCount,
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
// 刷新列表,显示更新后的结果
startAsyncNotesListQuery();
}
// 安全地结束选择模式只有当当前ActionMode仍然有效时才结束
if (currentActionMode != null && currentActionMode == mModeCallBack.mActionMode) {
currentActionMode.finish();
}
}
});
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);
// 传递计时器状态
intent.putExtra("is_timer_running", mIsTimerRunning);
intent.putExtra("timer_current_time", mTimerCurrentTime);
intent.putExtra("is_countdown_mode", mIsCountdownMode);
intent.putExtra("timer_duration", mTimerDuration);
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
/**
* 批量删除笔记
*/
private void batchDelete() {
// 保存当前的ActionMode引用以便在异步任务完成后检查它是否仍然有效
final ActionMode currentActionMode = mModeCallBack.mActionMode;
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
// 所有模式下,都将删除的笔记移动到回收站
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
// 刷新列表,显示更新后的结果
startAsyncNotesListQuery();
// 确保 mFocusNoteDataItem 被重置,避免指向已被恢复的数据
mFocusNoteDataItem = null;
// 安全地结束选择模式只有当当前ActionMode仍然有效时才结束
if (currentActionMode != null && currentActionMode == mModeCallBack.mActionMode) {
currentActionMode.finish();
}
}
}.execute();
}
/**
* 从回收站批量恢复笔记
*/
private void batchRestoreFromTrash() {
// 保存当前的ActionMode引用以便在异步任务完成后检查它是否仍然有效
final ActionMode currentActionMode = mModeCallBack.mActionMode;
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
// 将选中的笔记从回收站恢复
HashSet<Long> ids = mNotesListAdapter.getSelectedItemIds();
// 遍历每个选中的笔记
for (long id : ids) {
// 检查当前是否在回收站中的文件夹内
if (mCurrentFolderId != Notes.ID_TRASH_FOLER) {
// 在文件夹内恢复单个便签,恢复到根文件夹
DataUtils.moveNoteToFoler(mContentResolver, id, Notes.ID_TRASH_FOLER, Notes.ID_ROOT_FOLDER);
} else {
// 在回收站根目录恢复
// 查询笔记的原始父文件夹ID
long originParentId = getOriginParentId(id);
// 检查原始父文件夹是否存在且不在回收站中
boolean parentFolderExists;
if (originParentId == Notes.ID_ROOT_FOLDER) {
// 根文件夹永远存在
parentFolderExists = true;
} else {
// 检查普通文件夹是否存在且不在回收站中
parentFolderExists = DataUtils.visibleInNoteDatabase(mContentResolver, originParentId, Notes.TYPE_FOLDER);
}
// 如果父文件夹不存在或在回收站中,恢复到根文件夹
long targetFolderId = parentFolderExists ? originParentId : Notes.ID_ROOT_FOLDER;
// 将笔记移动到目标文件夹
DataUtils.moveNoteToFoler(mContentResolver, id, Notes.ID_TRASH_FOLER, targetFolderId);
}
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
// 刷新列表,显示更新后的结果
startAsyncNotesListQuery();
// 安全地结束选择模式只有当当前ActionMode仍然有效时才结束
if (currentActionMode != null && currentActionMode == mModeCallBack.mActionMode) {
currentActionMode.finish();
}
}
}.execute();
}
/**
* 批量彻底删除笔记
*/
private void batchPermanentDelete() {
// 保存当前的ActionMode引用以便在异步任务完成后检查它是否仍然有效
final ActionMode currentActionMode = mModeCallBack.mActionMode;
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
// 彻底删除选中的笔记
if (!DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter.getSelectedItemIds())) {
Log.e(TAG, "Permanent delete notes error, should not happens");
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
// 刷新列表,显示更新后的结果
startAsyncNotesListQuery();
// 安全地结束选择模式只有当当前ActionMode仍然有效时才结束
if (currentActionMode != null && currentActionMode == mModeCallBack.mActionMode) {
currentActionMode.finish();
}
}
}.execute();
}
/**
* 获取笔记的原始父文件夹ID
*/
private long getOriginParentId(long noteId) {
// 查询笔记的原始父文件夹ID
long originParentId = Notes.ID_ROOT_FOLDER; // 默认根文件夹
String[] projection = {Notes.NoteColumns.ORIGIN_PARENT_ID};
Cursor cursor = mContentResolver.query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
projection,
null,
null,
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
originParentId = cursor.getLong(0);
// 如果原始父文件夹ID无效使用根文件夹
if (originParentId <= 0) {
originParentId = Notes.ID_ROOT_FOLDER;
}
}
cursor.close();
}
return originParentId;
}
/**
* 设置文件夹的置顶状态
* @param folderId 文件夹ID
* @param pinned 是否置顶
*/
private void setFolderPinnedStatus(long folderId, boolean pinned) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PINNED, pinned ? 1 : 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新文件夹的置顶状态
mContentResolver.update(Notes.CONTENT_NOTE_URI, values,
NoteColumns.ID + "=? AND " + NoteColumns.TYPE + "=?",
new String[] {String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)});
// 刷新列表,让置顶状态生效
startAsyncNotesListQuery();
}
/**
* 设置便签的置顶状态
* @param noteId 便签ID
* @param pinned 是否置顶
*/
private void setNotePinnedStatus(long noteId, boolean pinned) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PINNED, pinned ? 1 : 0);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新便签的置顶状态
mContentResolver.update(Notes.CONTENT_NOTE_URI, values,
NoteColumns.ID + "=? AND " + NoteColumns.TYPE + "=?",
new String[] {String.valueOf(noteId), String.valueOf(Notes.TYPE_NOTE)});
// 刷新列表,让置顶状态生效
startAsyncNotesListQuery();
}
/**
* 显示编辑便签标题对话框
*/
private void showEditNoteTopicDialog() {
// 检查mFocusNoteDataItem是否为null
if (mFocusNoteDataItem == null) {
Log.e(TAG, "showEditNoteTopicDialog: mFocusNoteDataItem is null");
return;
}
// 创建对话框视图
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_topic, null);
final EditText etTopic = (EditText) view.findViewById(R.id.et_topic);
// 设置初始标题为便签的标题字段,如果标题字段为空,则使用内容摘要的第一行
String currentTopic = mFocusNoteDataItem.getDisplayTitle();
if (!TextUtils.isEmpty(currentTopic)) {
// 由于NoteItemData中已经处理过HTML标签这里不需要再处理
etTopic.setText(currentTopic);
etTopic.setSelection(currentTopic.length()); // 选中所有文本
}
// 创建对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.menu_edit_topic);
// 使用系统默认编辑图标
builder.setIcon(android.R.drawable.ic_menu_edit);
builder.setView(view);
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 取消编辑,不做任何操作
}
});
final Dialog dialog = builder.show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String topic = etTopic.getText().toString().trim();
if (!TextUtils.isEmpty(topic)) {
// 更新便签标题
updateNoteTopic(topic);
dialog.dismiss();
} else {
// 标题不能为空,显示提示
Toast.makeText(NotesListActivity.this, getString(R.string.topic_cannot_be_empty),
Toast.LENGTH_LONG).show();
}
}
});
}
/**
* 更新便签标题
* @param topic 新标题
*/
private void updateNoteTopic(String topic) {
// 检查mFocusNoteDataItem是否为null
if (mFocusNoteDataItem == null) {
Log.e(TAG, "updateNoteTopic: mFocusNoteDataItem is null");
return;
}
// 更新便签的标题字段,而不是修改内容
ContentValues values = new ContentValues();
values.put(NoteColumns.TITLE, topic);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
mContentResolver.update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + "=? AND " + NoteColumns.TYPE + "=?",
new String[]{String.valueOf(mFocusNoteDataItem.getId()), String.valueOf(Notes.TYPE_NOTE)});
// 刷新列表,显示更新后的结果
startAsyncNotesListQuery();
}
/**
* 恢复文件夹
* @param folderId 文件夹ID
*/
private void restoreFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
// 获取文件夹下的所有子项(包括便签和子文件夹)
// 使用递归方式获取所有层级的子项
HashSet<Long> items = new HashSet<Long>();
// 将文件夹本身也添加到要恢复的列表中
items.add(folderId);
// 递归获取所有子项
getRecycleBinFolderItems(folderId, items);
// 恢复文件夹及其所有子项
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId);
// 遍历每个要恢复的项目
for (long id : items) {
// 查询项目的原始父文件夹ID
long originParentId = getOriginParentId(id);
// 将项目恢复到原始位置
DataUtils.moveNoteToFoler(mContentResolver, id, Notes.ID_TRASH_FOLER, originParentId);
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
// 确保 mFocusNoteDataItem 被重置,避免指向已被恢复的数据
mFocusNoteDataItem = null;
}
}.execute();
// 刷新列表,显示恢复后的结果
startAsyncNotesListQuery();
}
/**
* 恢复单个便签
* @param noteId 便签ID
*/
private void restoreNote(long noteId) {
// 查询便签的原始父文件夹ID
long originParentId = getOriginParentId(noteId);
// 检查原始父文件夹是否存在且不在回收站中
boolean parentFolderExists;
if (originParentId == Notes.ID_ROOT_FOLDER) {
// 根文件夹永远存在
parentFolderExists = true;
} else {
// 检查普通文件夹是否存在且不在回收站中
parentFolderExists = DataUtils.visibleInNoteDatabase(mContentResolver, originParentId, Notes.TYPE_FOLDER);
}
// 如果父文件夹不存在或在回收站中,恢复到根文件夹
long targetFolderId = parentFolderExists ? originParentId : Notes.ID_ROOT_FOLDER;
// 将便签移动到目标文件夹
DataUtils.moveNoteToFoler(mContentResolver, noteId, Notes.ID_TRASH_FOLER, targetFolderId);
// 刷新列表,显示恢复后的结果
startAsyncNotesListQuery();
// 确保 mFocusNoteDataItem 被重置,避免指向已被恢复的数据
mFocusNoteDataItem = null;
}
/**
* 彻底删除文件夹
* @param folderId 文件夹ID
*/
private void permanentDeleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
// 获取文件夹下的所有子项(包括便签和子文件夹)
// 在回收站中需要根据ORIGIN_PARENT_ID来查询文件夹内容
HashSet<Long> items = new HashSet<>();
// 添加文件夹本身
items.add(folderId);
// 递归获取所有子项
getRecycleBinFolderItems(folderId, items);
// 彻底删除文件夹及其所有子项
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取所有相关的小部件
HashSet<AppWidgetAttribute> widgets = new HashSet<>();
for (long itemId : items) {
// 只获取便签的小部件
if (DataUtils.visibleInNoteDatabase(mContentResolver, itemId, Notes.TYPE_NOTE)) {
widgets.addAll(DataUtils.getFolderNoteWidget(mContentResolver, itemId));
}
}
// 彻底删除所有项目
DataUtils.batchDeleteNotes(mContentResolver, items);
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
}
}.execute();
// 刷新列表,显示删除后的结果
startAsyncNotesListQuery();
}
/**
* 递归获取回收站文件夹下的所有子项
* @param folderId 文件夹ID
* @param items 用于存储子项ID的集合
*/
private void getRecycleBinFolderItems(long folderId, HashSet<Long> items) {
// 查询该文件夹下的所有子项根据ORIGIN_PARENT_ID和当前PARENT_ID是回收站ID
Cursor cursor = mContentResolver.query(
Notes.CONTENT_NOTE_URI,
new String[]{Notes.NoteColumns.ID, Notes.NoteColumns.TYPE},
Notes.NoteColumns.ORIGIN_PARENT_ID + "=? AND " + Notes.NoteColumns.PARENT_ID + "=?",
new String[]{String.valueOf(folderId), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
long itemId = cursor.getLong(0);
int itemType = cursor.getInt(1);
// 添加到要恢复的列表中
items.add(itemId);
// 如果是文件夹,递归获取其下的子项
if (itemType == Notes.TYPE_FOLDER) {
getRecycleBinFolderItems(itemId, items);
}
} while (cursor.moveToNext());
}
cursor.close();
}
}
/**
* 删除文件夹
*/
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
// 获取文件夹下的所有子项(包括便签和子文件夹)
HashSet<Long> items = DataUtils.getFolderItems(mContentResolver, folderId);
// 将文件夹本身也添加到要删除的列表中
items.add(folderId);
// 保存原始父文件夹ID并将文件夹及其所有子项移动到回收站
// 对于文件夹本身将其PARENT_ID设置为回收站ID
// 对于文件夹内的便签将其PARENT_ID设置为回收站IDORIGIN_PARENT_ID设置为原始父文件夹ID
// 这样在回收站中打开文件夹时我们可以通过ORIGIN_PARENT_ID来查询该文件夹下的所有便签和文件夹
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId);
// 将文件夹及其所有子项一起移动到回收站
// 这样所有项目的PARENT_ID都会被设置为回收站IDORIGIN_PARENT_ID会被设置为原始父文件夹ID
DataUtils.batchMoveToFolder(mContentResolver, items, Notes.ID_TRASH_FOLER);
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
}
/**
* 打开笔记(进入编辑界面)
*/
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
// 传递计时器状态
intent.putExtra("is_timer_running", mIsTimerRunning);
intent.putExtra("timer_current_time", mTimerCurrentTime);
intent.putExtra("is_countdown_mode", mIsCountdownMode);
intent.putExtra("timer_duration", mTimerDuration);
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
/**
* 打开文件夹(进入文件夹视图)
*/
private void openFolder(NoteItemData data) {
// 检查是否为隐私文件夹
if (data.isPrivate()) {
// 显示密码验证对话框
showPasswordVerifyDialog(data);
} else {
// 普通文件夹,直接打开
openFolderInternal(data);
}
}
/**
* 打开文件夹的内部方法,不包含密码验证
*/
private void openFolderInternal(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery(); // 查询文件夹内容
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mState = ListEditState.CALL_RECORD_FOLDER;
mAddNewNote.setVisibility(View.GONE); // 通话记录文件夹不允许新建笔记
} else if (mCurrentFolderId == Notes.ID_TRASH_FOLER || data.getParentId() == Notes.ID_TRASH_FOLER) {
// 如果是回收站中的文件夹设置状态为TRASH_FOLDER
mState = ListEditState.TRASH_FOLDER;
mAddNewNote.setVisibility(View.GONE); // 回收站中不允许新建笔记
} else {
mState = ListEditState.SUB_FOLDER;
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mTitleBar.setText(R.string.call_record_folder_name);
mStudyTimerButton.setVisibility(View.GONE);
} else if (data.isStudy()) {
// 学习文件夹,显示文件夹名称和专注时长
String folderName = data.getSnippet();
String focusDuration = data.getFormattedFocusDuration();
mTitleBar.setText(folderName + " (专注时间: " + focusDuration + ")");
mStudyTimerButton.setVisibility(View.VISIBLE);
} else {
mTitleBar.setText(data.getSnippet()); // 显示文件夹名称
mStudyTimerButton.setVisibility(View.GONE);
}
mTitleBarLayout.setVisibility(View.VISIBLE); // 显示标题栏布局
}
/**
* 显示密码验证对话框
*/
private void showPasswordVerifyDialog(final NoteItemData folderData) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null);
final EditText etPassword = (EditText) view.findViewById(R.id.et_password);
builder.setTitle(getString(R.string.menu_enter_password));
builder.setMessage(getString(R.string.enter_password_to_open));
showSoftInput(); // 显示软键盘
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etPassword); // 隐藏软键盘
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etPassword);
String password = etPassword.getText().toString();
// 获取存储的密码
SharedPreferences preferences = getSharedPreferences("notes_private_folders", MODE_PRIVATE);
String storedPassword = preferences.getString("password_" + folderData.getId(), null);
// 验证密码
if (storedPassword != null && storedPassword.equals(password)) {
// 密码正确,打开文件夹
dialog.dismiss();
openFolderInternal(folderData);
} else {
// 密码错误
Toast.makeText(NotesListActivity.this, getString(R.string.password_incorrect),
Toast.LENGTH_LONG).show();
etPassword.setSelection(0, etPassword.length());
}
}
});
// 初始时如果密码为空,禁用确定按钮
if (TextUtils.isEmpty(etPassword.getText())) {
positive.setEnabled(false);
}
// 监听密码输入变化
etPassword.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 只有当密码为6位数字时才启用确定按钮
String password = s.toString();
positive.setEnabled(password.length() == 6 && password.matches("\\d{6}"));
}
public void afterTextChanged(Editable s) {}
});
}
/**
* 显示删除隐私文件夹的密码验证对话框
*/
private void showPasswordVerifyDialogForDelete(final NoteItemData folderData) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null);
final EditText etPassword = (EditText) view.findViewById(R.id.et_password);
builder.setTitle(getString(R.string.menu_enter_password));
builder.setMessage(getString(R.string.enter_password_to_delete));
showSoftInput(); // 显示软键盘
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etPassword); // 隐藏软键盘
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etPassword);
String password = etPassword.getText().toString();
// 获取存储的密码
SharedPreferences preferences = getSharedPreferences("notes_private_folders", MODE_PRIVATE);
String storedPassword = preferences.getString("password_" + folderData.getId(), null);
// 验证密码
if (storedPassword != null && storedPassword.equals(password)) {
// 密码正确,显示删除确认对话框
dialog.dismiss();
AlertDialog.Builder deleteBuilder = new AlertDialog.Builder(NotesListActivity.this);
deleteBuilder.setTitle(getString(R.string.alert_title_delete));
deleteBuilder.setIcon(android.R.drawable.ic_dialog_alert);
deleteBuilder.setMessage(getString(R.string.alert_message_delete_folder));
deleteBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface deleteDialog, int which) {
// 彻底删除隐私文件夹及其内容
permanentDeleteFolder(folderData.getId());
// 删除密码存储
SharedPreferences.Editor editor = preferences.edit();
editor.remove("password_" + folderData.getId());
editor.apply();
}
});
deleteBuilder.setNegativeButton(android.R.string.cancel, null);
deleteBuilder.show();
} else {
// 密码错误
Toast.makeText(NotesListActivity.this, getString(R.string.password_incorrect),
Toast.LENGTH_LONG).show();
etPassword.setSelection(0, etPassword.length());
}
}
});
// 初始时如果密码为空,禁用确定按钮
if (TextUtils.isEmpty(etPassword.getText())) {
positive.setEnabled(false);
}
// 监听密码输入变化
etPassword.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 只有当密码为6位数字时才启用确定按钮
String password = s.toString();
positive.setEnabled(password.length() == 6 && password.matches("\\d{6}"));
}
public void afterTextChanged(Editable s) {}
});
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_new_note:
createNewNote(); // 创建新笔记
break;
case R.id.btn_study_timer:
// 处理学习计时器按钮点击
if (mIsTimerRunning) {
// 如果计时器正在运行,切换计时控制界面显示/隐藏
if (mTimerDialog != null && mTimerDialog.isShowing()) {
// 如果对话框已显示,则隐藏
mTimerDialog.dismiss();
} else {
// 如果对话框未显示,则显示
showTimerControlDialog();
}
} else {
// 如果计时器未运行,显示设置对话框
showStudyTimerDialog();
}
break;
default:
break;
}
}
/**
* 显示软键盘
*/
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
/**
* 隐藏软键盘
*/
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
* 显示创建或修改文件夹对话框
*/
private void showCreateOrModifyFolderDialog(final boolean create, final boolean isPrivate, final boolean isStudy) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput(); // 显示软键盘
if (!create) {
// 修改文件夹名称
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet());
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
Log.e(TAG, "The long click data item is null");
return;
}
} else {
// 创建新文件夹
etName.setText("");
if (isStudy) {
builder.setTitle(R.string.menu_create_study_folder);
} else {
builder.setTitle(isPrivate ? getString(R.string.menu_create_private_folder) : getString(R.string.menu_create_folder));
}
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName); // 隐藏软键盘
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button)dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etName);
String name = etName.getText().toString();
// 检查文件夹名称是否已存在
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
etName.setSelection(0, etName.length()); // 选中所有文本
return;
}
if (!create) {
// 修改文件夹名称
if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId())
});
}
} else if (!TextUtils.isEmpty(name)) {
// 创建新文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.PRIVATE, isPrivate ? 1 : 0);
values.put(NoteColumns.IS_STUDY, isStudy ? 1 : 0);
values.put(NoteColumns.PINNED, isPrivate ? 1 : 0); // 隐私文件夹永远置顶
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
if (isPrivate && uri != null) {
// 对于隐私文件夹,需要存储密码
long folderId = ContentUris.parseId(uri);
showPasswordInputDialog(folderId);
}
}
dialog.dismiss();
}
});
// 初始时如果名称为空,禁用确定按钮
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
}
/**
* 当名称编辑框为空时,禁用确定按钮
*/
etName.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 根据输入内容启用/禁用确定按钮
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
} else {
positive.setEnabled(true);
}
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}
/**
* 显示密码输入对话框
* @param folderId 隐私文件夹ID
*/
private void showPasswordInputDialog(final long folderId) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_password_input, null);
final EditText etPassword = (EditText) view.findViewById(R.id.et_password);
builder.setTitle(getString(R.string.menu_enter_password));
builder.setMessage(getString(R.string.password_hint));
showSoftInput(); // 显示软键盘
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etPassword); // 隐藏软键盘
// 如果取消密码设置,删除刚刚创建的隐私文件夹
mContentResolver.delete(Notes.CONTENT_NOTE_URI, NoteColumns.ID + "=?",
new String[] { String.valueOf(folderId) });
startAsyncNotesListQuery();
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etPassword);
String password = etPassword.getText().toString();
// 验证密码是否为6位数字
if (password.length() != 6 || !password.matches("\\d{6}")) {
Toast.makeText(NotesListActivity.this, getString(R.string.password_invalid),
Toast.LENGTH_LONG).show();
etPassword.setSelection(0, etPassword.length());
return;
}
// 存储密码这里简单使用SharedPreferences实际应用中应该加密存储
SharedPreferences preferences = getSharedPreferences("notes_private_folders", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("password_" + folderId, password);
editor.apply();
dialog.dismiss();
startAsyncNotesListQuery();
}
});
// 初始时如果密码为空,禁用确定按钮
if (TextUtils.isEmpty(etPassword.getText())) {
positive.setEnabled(false);
}
// 监听密码输入变化
etPassword.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 只有当密码为6位数字时才启用确定按钮
String password = s.toString();
positive.setEnabled(password.length() == 6 && password.matches("\\d{6}"));
}
public void afterTextChanged(Editable s) {}
});
}
/**
* 处理返回键按下事件
* 统一处理不同状态下的返回键逻辑适用于传统onBackPressed和Android 13+的OnBackInvokedDispatcher
*/
private void handleBackPress() {
switch (mState) {
case SUB_FOLDER:
// 从普通子文件夹返回,回到根目录
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
mAddNewNote.setVisibility(View.VISIBLE); // 显示新建笔记按钮
mTitleBarLayout.setVisibility(View.GONE); // 隐藏标题栏布局
mStudyTimerButton.setVisibility(View.GONE); // 隐藏学习计时器按钮
startAsyncNotesListQuery();
break;
case TRASH_FOLDER:
// 检查当前是否已经在回收站根目录
if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
// 从回收站根目录返回,回到主界面
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
mAddNewNote.setVisibility(View.VISIBLE); // 显示新建笔记按钮
mTitleBarLayout.setVisibility(View.GONE); // 隐藏标题栏布局
mStudyTimerButton.setVisibility(View.GONE); // 隐藏学习计时器按钮
startAsyncNotesListQuery();
} else {
// 从回收站中的文件夹返回,回到回收站根目录
mCurrentFolderId = Notes.ID_TRASH_FOLER;
mState = ListEditState.TRASH_FOLDER;
mTitleBar.setText(R.string.menu_rubbish_bin); // 设置标题栏为回收站
mAddNewNote.setVisibility(View.GONE); // 回收站中不允许新建笔记
mStudyTimerButton.setVisibility(View.GONE); // 隐藏学习计时器按钮
startAsyncNotesListQuery();
}
break;
case CALL_RECORD_FOLDER:
// 返回根文件夹列表
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
mAddNewNote.setVisibility(View.VISIBLE); // 显示新建笔记按钮
mTitleBarLayout.setVisibility(View.GONE); // 隐藏标题栏布局
mStudyTimerButton.setVisibility(View.GONE); // 隐藏学习计时器按钮
startAsyncNotesListQuery();
break;
case NOTE_LIST:
// 在NOTE_LIST状态下调用父类的onBackPressed()退出应用
NotesListActivity.super.onBackPressed();
break;
default:
break;
}
}
@Override
public void onBackPressed() {
handleBackPress();
}
@Override
protected void onResume() {
super.onResume();
}
/**
* 更新小部件
*/
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据小部件类型设置目标类
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
appWidgetId
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
// 上下文菜单项ID - 便签移动
private static final int MENU_NOTE_MOVE = 8;
// 上下文菜单项ID - 便签多选
private static final int MENU_NOTE_MULTI_SELECT = 9;
/**
* 便签上下文菜单创建监听器
*/
private final OnCreateContextMenuListener mNoteOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为便签内容
// 检查当前是否在回收站中
if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
// 在回收站中,显示恢复、彻底删除和多选选项
menu.add(0, MENU_RESTORE, 0, R.string.menu_restore); // 恢复笔记
menu.add(0, MENU_PERMANENT_DELETE, 0, R.string.menu_permanent_delete); // 彻底删除
menu.add(0, MENU_NOTE_MULTI_SELECT, 0, R.string.menu_multi_select); // 多选
} else {
// 不在回收站中,显示普通便签操作选项
menu.add(0, MENU_NOTE_EDIT_TOPIC, 0, R.string.menu_edit_topic); // 编辑标题
menu.add(0, MENU_NOTE_MOVE, 0, R.string.menu_move); // 移动到文件夹
menu.add(0, MENU_NOTE_DELETE, 0, R.string.menu_delete); // 删除便签
menu.add(0, MENU_NOTE_MULTI_SELECT, 0, R.string.menu_multi_select); // 多选
// 根据便签是否已置顶添加相应的菜单选项
if (mFocusNoteDataItem.isPinned()) {
menu.add(0, MENU_NOTE_UNPIN, 0, R.string.menu_unpin_note); // 取消置顶
} else {
menu.add(0, MENU_NOTE_PIN, 0, R.string.menu_pin_note); // 置顶
}
}
}
}
};
/**
* 文件夹上下文菜单创建监听器
*/
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为文件夹名称
// 检查当前是否在回收站中
if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
// 在回收站中,显示恢复和彻底删除选项
if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
menu.add(0, MENU_RESTORE, 0, R.string.menu_restore_folder); // 恢复文件夹
} else {
menu.add(0, MENU_RESTORE, 0, R.string.menu_restore); // 恢复笔记
}
menu.add(0, MENU_PERMANENT_DELETE, 0, R.string.menu_permanent_delete); // 彻底删除
} else {
// 不在回收站中,显示普通文件夹操作选项
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); // 查看文件夹
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); // 删除文件夹
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); // 修改文件夹名称
// 根据文件夹是否已置顶添加相应的菜单选项
if (mFocusNoteDataItem.isPinned()) {
menu.add(0, MENU_FOLDER_UNPIN, 0, R.string.menu_unpin_note); // 取消置顶
} else {
menu.add(0, MENU_FOLDER_PIN, 0, R.string.menu_pin_note); // 置顶
}
}
}
}
};
@Override
public void onContextMenuClosed(Menu menu) {
// 不要移除上下文菜单监听器,否则后续长按将无法显示上下文菜单
super.onContextMenuClosed(menu);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
// 根据当前焦点项目类型设置相应的上下文菜单
if (mFocusNoteDataItem != null) {
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE) {
mNoteOnCreateContextMenuListener.onCreateContextMenu(menu, v, menuInfo);
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mFolderOnCreateContextMenuListener.onCreateContextMenu(menu, v, menuInfo);
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null");
return false;
}
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem); // 打开文件夹
break;
case MENU_FOLDER_DELETE:
// 删除文件夹确认对话框
if (mFocusNoteDataItem.isPrivate()) {
// 隐私文件夹,需要密码验证
showPasswordVerifyDialogForDelete(mFocusNoteDataItem);
} else {
// 普通文件夹,直接显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_folder));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId()); // 执行删除
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
break;
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false, false, false); // 修改文件夹名称
break;
case MENU_FOLDER_PIN:
// 置顶文件夹
setFolderPinnedStatus(mFocusNoteDataItem.getId(), true);
break;
case MENU_NOTE_DELETE:
// 删除便签确认对话框
AlertDialog.Builder deleteNoteBuilder = new AlertDialog.Builder(this);
deleteNoteBuilder.setTitle(getString(R.string.alert_title_delete));
deleteNoteBuilder.setIcon(android.R.drawable.ic_dialog_alert);
deleteNoteBuilder.setMessage(getString(R.string.alert_message_delete_note));
deleteNoteBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 检查便签的父文件夹是否为隐私文件夹
long parentFolderId = mFocusNoteDataItem.getParentId();
boolean isPrivateFolder = false;
// 查询父文件夹信息
Cursor cursor = mContentResolver.query(Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.PRIVATE},
NoteColumns.ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER,
new String[]{String.valueOf(parentFolderId)},
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
isPrivateFolder = (cursor.getInt(0) > 0);
}
cursor.close();
}
// 根据父文件夹类型决定删除方式
if (isPrivateFolder) {
// 隐私文件夹中的便签,直接彻底删除
HashSet<Long> noteIds = new HashSet<Long>();
noteIds.add(mFocusNoteDataItem.getId());
DataUtils.batchDeleteNotes(getContentResolver(), noteIds);
} else {
// 普通文件夹中的便签,移动到回收站
HashSet<Long> noteIds = new HashSet<Long>();
noteIds.add(mFocusNoteDataItem.getId());
DataUtils.batchMoveToFolder(getContentResolver(), noteIds, Notes.ID_TRASH_FOLER);
}
// 刷新列表
startAsyncNotesListQuery();
}
});
deleteNoteBuilder.setNegativeButton(android.R.string.cancel, null);
deleteNoteBuilder.show();
break;
case MENU_FOLDER_UNPIN:
// 取消置顶文件夹
setFolderPinnedStatus(mFocusNoteDataItem.getId(), false);
break;
case MENU_NOTE_EDIT_TOPIC:
// 编辑便签标题
showEditNoteTopicDialog();
break;
case MENU_NOTE_MOVE:
// 移动便签到其他文件夹
// 查询目标文件夹列表
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
"(" + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?) OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")",
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
break;
case MENU_NOTE_MULTI_SELECT:
// 进入多选模式
if (mFocusNoteDataItem != null) {
// 1. 先确保 mModeCallBack 不为 null
if (mModeCallBack == null) {
mModeCallBack = new ModeCallback();
}
// 2. 先设置监听器,再设置选择模式
mNotesListView.setMultiChoiceModeListener(mModeCallBack);
// 3. 进入选择模式
mNotesListAdapter.setChoiceMode(true);
// 4. 查找当前便签在列表中的位置
Cursor cursor = mNotesListAdapter.getCursor();
if (cursor != null) {
long currentNoteId = mFocusNoteDataItem.getId();
int position = 0;
while (cursor.moveToNext()) {
long noteId = cursor.getLong(cursor.getColumnIndex(NoteColumns.ID));
if (noteId == currentNoteId) {
// 5. 设置当前便签为选中状态
mNotesListView.setItemChecked(position, true);
break;
}
position++;
}
}
// 6. 手动启动 ActionMode而不是通过长按触发
mNotesListView.startActionMode(mModeCallBack);
}
break;
case MENU_NOTE_PIN:
// 置顶便签
setNotePinnedStatus(mFocusNoteDataItem.getId(), true);
break;
case MENU_NOTE_UNPIN:
// 取消置顶便签
setNotePinnedStatus(mFocusNoteDataItem.getId(), false);
break;
case MENU_RESTORE:
// 根据类型显示不同的恢复对话框
AlertDialog.Builder restoreBuilder = new AlertDialog.Builder(this);
restoreBuilder.setTitle(getString(R.string.menu_restore_from_trash));
restoreBuilder.setIcon(android.R.drawable.ic_dialog_alert);
// 根据当前选中的是文件夹还是便签,显示不同的对话框消息和执行不同的恢复操作
if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
// 恢复文件夹
restoreBuilder.setMessage(getString(R.string.alert_message_restore_folder));
restoreBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
restoreFolder(mFocusNoteDataItem.getId()); // 执行文件夹恢复
}
});
} else {
// 恢复便签
restoreBuilder.setMessage(getString(R.string.alert_message_restore_notes, 1));
restoreBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
restoreNote(mFocusNoteDataItem.getId()); // 执行便签恢复
}
});
}
restoreBuilder.setNegativeButton(android.R.string.cancel, null);
restoreBuilder.show();
break;
case MENU_PERMANENT_DELETE:
// 彻底删除文件夹确认对话框
AlertDialog.Builder permanentDeleteBuilder = new AlertDialog.Builder(this);
permanentDeleteBuilder.setTitle(getString(R.string.menu_permanent_delete));
permanentDeleteBuilder.setIcon(android.R.drawable.ic_dialog_alert);
permanentDeleteBuilder.setMessage(getString(R.string.alert_message_permanent_delete_folder));
permanentDeleteBuilder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
permanentDeleteFolder(mFocusNoteDataItem.getId()); // 执行彻底删除
}
});
permanentDeleteBuilder.setNegativeButton(android.R.string.cancel, null);
permanentDeleteBuilder.show();
break;
default:
break;
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear(); // 清空菜单
// 根据当前状态加载不同菜单
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu);
// 根据同步状态设置菜单项标题
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER || mState == ListEditState.TRASH_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else {
Log.e(TAG, "Wrong state:" + mState);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_folder: {
showCreateOrModifyFolderDialog(true, false, false); // 创建新文件夹
break;
}
case R.id.menu_new_private_folder: {
showCreateOrModifyFolderDialog(true, true, false); // 创建新隐私文件夹
break;
}
case R.id.menu_new_study_folder: {
showCreateOrModifyFolderDialog(true, false, true); // 创建新学习文件夹
break;
}
case R.id.menu_export_text: {
exportNoteToText(); // 导出笔记为文本
break;
}
case R.id.menu_sync: {
if (isSyncMode()) {
// 同步模式下,启动或取消同步
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this); // 开始同步
} else {
GTaskSyncService.cancelSync(this); // 取消同步
}
} else {
startPreferenceActivity(); // 非同步模式下打开设置页面
}
break;
}
case R.id.menu_setting: {
startPreferenceActivity(); // 打开设置页面
break;
}
case R.id.menu_new_note: {
createNewNote(); // 新建笔记
break;
}
case R.id.menu_search:
onSearchRequested(); // 触发搜索
break;
case R.id.menu_rubbish_bin:
openRubbishBin(); // 打开回收站
break;
default:
break;
}
return true;
}
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false); // 启动搜索
return true;
}
/**
* 将笔记导出为文本文件
*/
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText(); // 执行导出
}
@Override
protected void onPostExecute(Integer result) {
// 根据导出结果显示相应对话框
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_unmounted));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.success_sdcard_export));
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup
.getExportedTextFileName(), backup.getExportedTextFileDir()));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
}.execute();
}
/**
* 检查是否处于同步模式
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 打开回收站
*/
private void openRubbishBin() {
mCurrentFolderId = Notes.ID_TRASH_FOLER; // 设置当前文件夹为回收站
mState = ListEditState.TRASH_FOLDER; // 设置状态为回收站文件夹
mTitleBar.setText(R.string.menu_rubbish_bin); // 设置标题栏为回收站
mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏
mAddNewNote.setVisibility(View.GONE); // 回收站中不允许新建笔记
startAsyncNotesListQuery(); // 启动异步查询,显示回收站内容
}
/**
* 启动设置页面
*/
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
}
/**
* 列表项点击监听器
*/
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) {
// 多选模式下,处理笔记选择
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
return;
}
// 根据当前状态处理点击事件
switch (mState) {
case NOTE_LIST:
if (item.getType() == Notes.TYPE_FOLDER
|| item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item); // 打开文件夹
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST");
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
case TRASH_FOLDER:
// 回收站中,处理便签和文件夹点击
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else if (item.getType() == Notes.TYPE_FOLDER) {
openFolder(item); // 打开文件夹
} else {
Log.e(TAG, "Wrong note type in TRASH_FOLDER");
}
break;
default:
break;
}
}
}
}
/**
* 查询目标文件夹(用于移动笔记)
*/
private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
// 根据当前状态调整查询条件
selection = (mState == ListEditState.NOTE_LIST) ? selection:
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData itemData = ((NotesListItem) view).getItemData();
if (itemData != null) {
// 无论是文件夹还是便签,长按都显示上下文菜单
mFocusNoteDataItem = itemData;
// 确保退出多选模式
if (mModeCallBack != null && mModeCallBack.mActionMode != null) {
mModeCallBack.mActionMode.finish();
}
// 重置列表的选择模式,确保长按总是触发上下文菜单
mNotesListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
mNotesListAdapter.setChoiceMode(false);
mNotesListView.clearChoices();
mNotesListView.setMultiChoiceModeListener(null);
mNotesListAdapter.notifyDataSetChanged();
return false;
}
}
return false;
}
// 学习计时器相关变量
private boolean mIsTimerRunning = false; // 计时器是否正在运行
private boolean mIsCountdownMode = false; // 是否为逆向计时模式
private long mTimerStartTime = 0; // 计时器开始时间
private long mTimerCurrentTime = 0; // 当前计时时间(毫秒)
private long mTimerDuration = 0; // 逆向计时时长(毫秒)
private android.os.Handler mTimerHandler = new android.os.Handler();
private Runnable mTimerRunnable = new Runnable() {
@Override
public void run() {
if (mIsTimerRunning) {
if (mIsCountdownMode) {
// 逆向计时
long elapsed = System.currentTimeMillis() - mTimerStartTime;
mTimerCurrentTime = mTimerDuration - elapsed;
if (mTimerCurrentTime <= 0) {
// 计时结束
mTimerCurrentTime = mTimerDuration; // 设置为完整时长,确保计入专注时间
mIsTimerRunning = false;
updateTimerDisplay();
Toast.makeText(NotesListActivity.this, "学习时间结束!", Toast.LENGTH_LONG).show();
// 停止计时器并处理
stopStudyTimerWithDisplay();
return;
}
} else {
// 正向计时
mTimerCurrentTime = System.currentTimeMillis() - mTimerStartTime;
}
updateTimerDisplay();
mTimerHandler.postDelayed(this, 1000); // 每秒更新一次
}
}
};
// 计时器对话框相关变量
private Dialog mTimerDialog = null;
private TextView mTimerDisplayView = null;
private Button mPauseResumeButton = null;
private android.os.Handler mDisplayHandler = new android.os.Handler();
// 悬浮窗相关变量
private Dialog mFloatingTimerDialog = null;
private TextView mFloatingTimerDisplayView = null;
private boolean mIsFloatingMode = false;
/**
* 显示学习计时器对话框
*/
private void showStudyTimerDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_study_timer, null);
final RadioGroup timerModeGroup = (RadioGroup) view.findViewById(R.id.timer_mode_group);
final EditText hoursEdit = (EditText) view.findViewById(R.id.edit_hours);
final EditText minutesEdit = (EditText) view.findViewById(R.id.edit_minutes);
final EditText secondsEdit = (EditText) view.findViewById(R.id.edit_seconds);
builder.setTitle("设置学习计时器");
builder.setView(view);
builder.setPositiveButton("开始", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 获取计时模式
int selectedModeId = timerModeGroup.getCheckedRadioButtonId();
mIsCountdownMode = (selectedModeId == R.id.radio_countdown);
// 获取时间设置
int hours = Integer.parseInt(!TextUtils.isEmpty(hoursEdit.getText()) ? hoursEdit.getText().toString() : "0");
int minutes = Integer.parseInt(!TextUtils.isEmpty(minutesEdit.getText()) ? minutesEdit.getText().toString() : "0");
int seconds = Integer.parseInt(!TextUtils.isEmpty(secondsEdit.getText()) ? secondsEdit.getText().toString() : "0");
// 计算总时长(毫秒)
mTimerDuration = (hours * 3600 + minutes * 60 + seconds) * 1000;
// 验证时间设置
if (mIsCountdownMode && mTimerDuration < 60000) {
// 逆向计时时长小于1分钟
Toast.makeText(NotesListActivity.this, "逆向计时时长必须大于等于1分钟", Toast.LENGTH_SHORT).show();
return;
}
// 开始计时
startStudyTimer();
dialog.dismiss();
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
/**
* 开始学习计时器
*/
private void startStudyTimer() {
mTimerStartTime = System.currentTimeMillis();
mIsTimerRunning = true;
mTimerHandler.post(mTimerRunnable);
// 显示计时控制对话框
showTimerControlDialog();
}
/**
* 暂停学习计时器
*/
private void pauseStudyTimer() {
mIsTimerRunning = false;
mTimerHandler.removeCallbacks(mTimerRunnable);
// 停止显示更新
mDisplayHandler.removeCallbacksAndMessages(null);
}
/**
* 恢复学习计时器
*/
private void resumeStudyTimer() {
if (!mIsTimerRunning) {
if (mIsCountdownMode) {
// 倒计时模式:计算已经过去的时间
long elapsedTime = mTimerDuration - mTimerCurrentTime;
mTimerStartTime = System.currentTimeMillis() - elapsedTime;
} else {
// 正向计时模式:使用当前时间减去已计时时间
mTimerStartTime = System.currentTimeMillis() - mTimerCurrentTime;
}
mIsTimerRunning = true;
mTimerHandler.post(mTimerRunnable);
// 重启显示更新
startTimerDisplayUpdate();
}
}
/**
* 停止学习计时器
*/
// 旧的停止方法已被stopStudyTimerWithDisplay替代
// private void stopStudyTimer() {
// mIsTimerRunning = false;
// mTimerHandler.removeCallbacks(mTimerRunnable);
//
// // 验证学习有效性
// if (mTimerCurrentTime >= 60000) {
// // 学习时间超过1分钟视为有效学习
// updateFocusDuration(mTimerCurrentTime);
// }
//
// // 重置计时器变量
// mTimerCurrentTime = 0;
// mTimerDuration = 0;
// }
/**
* 更新计时器显示
*/
private void updateTimerDisplay() {
long totalSeconds = mTimerCurrentTime / 1000;
int hours = (int) (totalSeconds / 3600);
int minutes = (int) ((totalSeconds % 3600) / 60);
int seconds = (int) (totalSeconds % 60);
String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds);
if (mTimerDisplayView != null) {
mTimerDisplayView.setText(timeStr);
}
if (mFloatingTimerDisplayView != null) {
mFloatingTimerDisplayView.setText(timeStr);
}
}
/**
* 启动计时器显示更新
*/
private void startTimerDisplayUpdate() {
mDisplayHandler.removeCallbacksAndMessages(null);
final Runnable updateRunnable = new Runnable() {
@Override
public void run() {
if (mIsTimerRunning) {
updateTimerDisplay();
mDisplayHandler.postDelayed(this, 1000);
}
}
};
mDisplayHandler.postDelayed(updateRunnable, 1000);
}
/**
* 显示计时器控制对话框
*/
private void showTimerControlDialog() {
// 创建一个悬浮的非模态对话框
final Dialog timerDialog = new Dialog(this, android.R.style.Theme_Dialog);
timerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
timerDialog.setCancelable(true);
timerDialog.setCanceledOnTouchOutside(true);
// 设置对话框大小和位置
Window window = timerDialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.BOTTOM;
params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
window.setAttributes(params);
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
View view = LayoutInflater.from(this).inflate(R.layout.dialog_timer_control, null);
mTimerDisplayView = (TextView) view.findViewById(R.id.timer_display);
mPauseResumeButton = (Button) view.findViewById(R.id.btn_pause_resume);
final Button btnStop = (Button) view.findViewById(R.id.btn_stop);
// 更新计时器显示
updateTimerDisplay();
// 设置按钮文本
mPauseResumeButton.setText(mIsTimerRunning ? "暂停" : "继续");
mPauseResumeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mIsTimerRunning) {
pauseStudyTimer();
mPauseResumeButton.setText("继续");
} else {
resumeStudyTimer();
mPauseResumeButton.setText("暂停");
}
}
});
btnStop.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
stopStudyTimerWithDisplay();
timerDialog.dismiss();
}
});
timerDialog.setContentView(view);
timerDialog.show();
mTimerDialog = timerDialog;
// 启动计时器显示更新
startTimerDisplayUpdate();
}
/**
* 显示悬浮窗计时器(固定按钮形式)
*/
private void showFloatingTimer() {
// 关闭之前的对话框
if (mTimerDialog != null && mTimerDialog.isShowing()) {
mTimerDialog.dismiss();
mTimerDialog = null;
}
// 如果悬浮窗已经显示,直接返回
if (mFloatingTimerDialog != null && mFloatingTimerDialog.isShowing()) {
return;
}
// 创建固定的悬浮按钮对话框
mFloatingTimerDialog = new Dialog(this, android.R.style.Theme_Dialog);
mFloatingTimerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
mFloatingTimerDialog.setCancelable(false); // 设置为不可取消,确保按钮一直存在
// 设置对话框大小和位置
Window window = mFloatingTimerDialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.TOP | Gravity.RIGHT;
params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
params.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
window.setAttributes(params);
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
// 使用闹钟图标布局
View view = LayoutInflater.from(this).inflate(R.layout.floating_timer_icon, null);
// 点击按钮时显示完整的计时器对话框
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mFloatingTimerDialog != null && mFloatingTimerDialog.isShowing()) {
mFloatingTimerDialog.dismiss();
mFloatingTimerDialog = null;
showTimerControlDialog();
}
}
});
mFloatingTimerDialog.setContentView(view);
mFloatingTimerDialog.show();
mIsFloatingMode = true;
}
/**
* 停止学习计时器(带显示处理)
*/
private void stopStudyTimerWithDisplay() {
mIsTimerRunning = false;
mTimerHandler.removeCallbacks(mTimerRunnable);
mDisplayHandler.removeCallbacksAndMessages(null);
// 验证学习有效性
if (mTimerCurrentTime >= 60000) {
// 学习时间超过1分钟视为有效学习
updateFocusDuration(mTimerCurrentTime);
}
// 重置计时器变量
mTimerCurrentTime = 0;
mTimerDuration = 0;
// 关闭计时器对话框
if (mTimerDialog != null && mTimerDialog.isShowing()) {
mTimerDialog.dismiss();
mTimerDialog = null;
mTimerDisplayView = null;
mPauseResumeButton = null;
}
}
/**
* 更新计时器显示视图
*/
private void updateTimerDisplayView(TextView display) {
long totalSeconds = mTimerCurrentTime / 1000;
int hours = (int) (totalSeconds / 3600);
int minutes = (int) ((totalSeconds % 3600) / 60);
int seconds = (int) (totalSeconds % 60);
String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds);
display.setText(timeStr);
}
/**
* 更新专注时长
*/
private void updateFocusDuration(long duration) {
// 查询当前文件夹的专注时长
Cursor cursor = getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{NoteColumns.FOCUS_DURATION},
NoteColumns.ID + "=? AND " + NoteColumns.TYPE + "=?",
new String[]{String.valueOf(mCurrentFolderId), String.valueOf(Notes.TYPE_FOLDER)},
null
);
long currentDuration = 0;
if (cursor != null && cursor.moveToFirst()) {
currentDuration = cursor.getLong(0);
cursor.close();
}
// 更新专注时长
long newDuration = currentDuration + duration;
ContentValues values = new ContentValues();
values.put(NoteColumns.FOCUS_DURATION, newDuration);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + "=? AND " + NoteColumns.TYPE + "=?",
new String[]{String.valueOf(mCurrentFolderId), String.valueOf(Notes.TYPE_FOLDER)}
);
// 刷新文件夹显示
startAsyncNotesListQuery();
}
}