diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..b2c751a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index 492fcf6..f12e799 100644 --- a/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -80,101 +80,124 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * 笔记列表主界面 + * 负责显示笔记和文件夹列表,处理用户交互和数据操作 + */ public class NotesListActivity extends AppCompatActivity implements OnClickListener, OnItemLongClickListener { + // 背景模式:-1表示默认背景,0表示另一种背景 private int mode=-1; + // 查询令牌:用于标识不同的数据库查询操作 private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - - private static final int FOLDER_LIST_QUERY_TOKEN = 1; - + 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 String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + // 列表编辑状态枚举:表示当前显示的是笔记列表、子文件夹还是通话记录文件夹 private enum ListEditState { NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER }; + // 当前列表编辑状态 private ListEditState mState; - + // 后台查询处理器:用于异步执行数据库查询 private BackgroundQueryHandler mBackgroundQueryHandler; - + // 笔记列表适配器:负责管理列表数据和视图 private NotesListAdapter mNotesListAdapter; - + // 笔记列表视图 private ListView mNotesListView; - + // 新建笔记按钮 private Button mAddNewNote; - + // 触摸事件分发标志 private boolean mDispatch; - + // 触摸事件原始Y坐标 private int mOriginY; - + // 触摸事件分发Y坐标 private int mDispatchY; - + // 标题栏文本视图 private TextView mTitleBar; - + // 当前文件夹ID private long mCurrentFolderId; - + // 内容解析器:用于访问内容提供者 private ContentResolver mContentResolver; - + // 多选模式回调:处理列表多选操作 private ModeCallback mModeCallBack; - + // 日志标签 private static final String TAG = "NotesListActivity"; - + // 列表滚动速率 public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; - + // 当前选中的笔记数据项 private NoteItemData mFocusNoteDataItem; + // 数据库查询条件:普通文件夹查询条件 private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; - + // 根文件夹查询条件:包括普通笔记和通话记录文件夹 private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; + // 活动请求码:打开笔记和新建笔记 private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; + private final static int REQUEST_CODE_NEW_NODE = 103; + /** + * 活动创建时调用 + * 初始化界面和资源,并在首次启动时添加介绍笔记 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_list); + // 设置窗口背景(注意:此处重复设置了背景,可能是代码疏忽) getWindow().setBackgroundDrawableResource(R.drawable.beijing); getWindow().setBackgroundDrawableResource(R.drawable.bb); + + // 初始化界面资源 initResources(); - /** - * Insert an introduction when user firstly use this application - */ + // 首次使用时插入介绍笔记 setAppInfoFromRawRes(); } + /** + * 处理活动返回结果 + * 当打开或新建笔记返回时,刷新列表 + */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + // 数据变更,刷新列表 mNotesListAdapter.changeCursor(null); } else { super.onActivityResult(requestCode, resultCode, data); } } + /** + * 从raw资源读取介绍内容并创建介绍笔记 + * 仅在首次启动应用时执行 + */ 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); + // 读取介绍文本资源 + 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); } @@ -186,21 +209,23 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe e.printStackTrace(); return; } finally { + // 关闭输入流 if(in != null) { try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block 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"); @@ -209,43 +234,70 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 活动启动时调用 + * 开始异步查询笔记列表数据 + */ @Override protected void onStart() { super.onStart(); startAsyncNotesListQuery(); } + /** + * 初始化界面资源和组件 + */ private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); mCurrentFolderId = Notes.ID_ROOT_FOLDER; + + // 初始化列表视图 mNotesListView = (ListView) findViewById(R.id.notes_list); mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); mNotesListView.setOnItemClickListener(new OnListItemClickListener()); mNotesListView.setOnItemLongClickListener(this); + + // 设置适配器 mNotesListAdapter = new NotesListAdapter(this); mNotesListView.setAdapter(mNotesListAdapter); + + // 初始化新建笔记按钮 mAddNewNote = (Button) findViewById(R.id.btn_new_note); mAddNewNote.setOnClickListener(this); mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + + // 初始化触摸事件相关变量 mDispatch = false; mDispatchY = 0; mOriginY = 0; + + // 初始化标题栏和状态 mTitleBar = (TextView) findViewById(R.id.tv_title_bar); mState = ListEditState.NOTE_LIST; mModeCallBack = new ModeCallback(); } + /** + * 多选模式回调类 + * 处理列表项的多选操作和上下文菜单 + */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { private DropdownMenu mDropDownMenu; private ActionMode mActionMode; private MenuItem mMoveMenu; + /** + * 创建多选模式时调用 + * 初始化多选菜单和相关组件 + */ public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.note_list_options, menu); menu.findItem(R.id.delete).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); + + // 根据当前选中项和文件夹数量决定是否显示移动菜单项 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER || DataUtils.getUserFolderCount(mContentResolver) == 0) { mMoveMenu.setVisible(false); @@ -253,33 +305,43 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mMoveMenu.setVisible(true); mMoveMenu.setOnMenuItemClickListener(this); } + + // 设置多选模式 mActionMode = mode; mNotesListAdapter.setChoiceMode(true); mNotesListView.setLongClickable(false); mAddNewNote.setVisibility(View.GONE); + // 加载自定义多选菜单视图 View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); mode.setCustomView(customView); mDropDownMenu = new DropdownMenu(NotesListActivity.this, (Button) customView.findViewById(R.id.selection_menu), R.menu.note_list_dropdown); + + // 设置全选/反选菜单项 mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ public boolean onMenuItemClick(MenuItem item) { mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); updateMenu(); return true; } - }); return true; } + /** + * 更新菜单状态 + * 显示当前选中项数量和全选/反选状态 + */ private void updateMenu() { int selectedCount = mNotesListAdapter.getSelectedCount(); - // Update dropdown menu + // 更新下拉菜单标题 String format = getResources().getString(R.string.menu_select_title, selectedCount); mDropDownMenu.setTitle(format); + + // 更新全选菜单项状态 MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); if (item != null) { if (mNotesListAdapter.isAllSelected()) { @@ -292,32 +354,39 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO Auto-generated method stub - return false; - } - - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // TODO Auto-generated method stub - return false; - } + // 未实现的方法(默认返回false或空实现) + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; } + /** + * 多选模式销毁时调用 + * 恢复界面状态 + */ public void onDestroyActionMode(ActionMode mode) { mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); } + /** + * 结束多选模式 + */ public void finishActionMode() { mActionMode.finish(); } + /** + * 列表项选中状态变更时调用 + */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { mNotesListAdapter.setCheckedItem(position, checked); updateMenu(); } + /** + * 菜单项点击事件处理 + */ public boolean onMenuItemClick(MenuItem item) { if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), @@ -327,22 +396,24 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe 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())); + mNotesListAdapter.getSelectedCount())); builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - batchDelete(); - } - }); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case R.id.move: + // 查询目标文件夹并显示移动菜单 startQueryDestinationFolders(); break; default: @@ -352,47 +423,54 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 新建便签按钮触摸事件处理器 + * 处理按钮透明区域的触摸事件,将事件分发给下方的列表视图 + */ 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 - */ + int start = screenHeight - newNoteViewHeight; // 按钮顶部在屏幕中的Y坐标 + int eventY = start + (int) event.getY(); // 触摸事件在屏幕中的Y坐标 + + // 如果在子文件夹状态下,需要减去标题栏的高度 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. + * HACKME:当点击"新建便签"按钮的透明区域时,将事件分发给按钮下方的列表视图 + * 按钮透明区域由公式 y = -0.12x + 94 (单位:像素) 和按钮顶部边界限定 + * 坐标原点为"新建便签"按钮左上角 + * 注意:如果按钮背景图片改变,此公式需要相应调整 */ 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); + return mNotesListView.dispatchTouchEvent(event); // 将事件分发给列表视图 } } break; } case MotionEvent.ACTION_MOVE: { + // 如果正在分发事件,则更新事件位置并继续分发 if (mDispatch) { mDispatchY += (int) event.getY() - mOriginY; event.setLocation(event.getX(), mDispatchY); @@ -401,6 +479,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe break; } default: { + // 触摸事件结束时,完成事件分发 if (mDispatch) { event.setLocation(event.getX(), mDispatchY); mDispatch = false; @@ -411,18 +490,28 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } return false; } + } - }; - + /** + * 启动异步笔记列表查询 + * 根据当前文件夹ID执行不同的查询条件 + */ 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) + String.valueOf(mCurrentFolderId) }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 后台查询处理器 + * 处理异步数据库查询完成后的回调 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); @@ -432,11 +521,13 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe 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); + showFolderListMenu(cursor); // 显示文件夹选择菜单 } else { Log.e(TAG, "Query folder failed"); } @@ -447,27 +538,42 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 显示文件夹选择菜单 + * 用于移动笔记到指定文件夹 + */ 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(); } + /** + * 创建新笔记 + * 启动笔记编辑界面并传递当前文件夹ID + */ private void createNewNote() { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -475,20 +581,25 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + /** + * 批量删除选中的笔记 + * 根据同步状态决定是直接删除还是移至回收站 + */ private void batchDelete() { new AsyncTask>() { protected HashSet doInBackground(Void... unused) { + // 获取选中的小部件信息 HashSet 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"); } } 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"); @@ -499,6 +610,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe @Override protected void onPostExecute(HashSet widgets) { + // 更新相关小部件 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -507,28 +619,40 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } } + // 退出多选模式 mModeCallBack.finishActionMode(); } }.execute(); } + /** + * 删除文件夹 + * 根据同步状态决定是直接删除还是移至回收站 + */ private void deleteFolder(long folderId) { + // 禁止删除根文件夹 if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); return; } + // 准备要删除的文件夹ID集合 HashSet ids = new HashSet(); ids.add(folderId); + + // 获取文件夹关联的小部件信息 HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId); + if (!isSyncMode()) { - // if not synced, delete folder directly + // 非同步模式:直接删除文件夹 DataUtils.batchDeleteNotes(mContentResolver, ids); } else { - // in sync mode, we'll move the deleted folder into the trash folder + // 同步模式:将文件夹移至回收站 DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); } + + // 更新相关小部件 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -539,154 +663,187 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 打开笔记详情 + * @param data 笔记数据项 + */ private void openNode(NoteItemData data) { + // 启动笔记编辑界面,传入查看模式和笔记ID Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, data.getId()); - this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + intent.setAction(Intent.ACTION_VIEW); // 设置为查看模式 + intent.putExtra(Intent.EXTRA_UID, data.getId()); // 传递笔记ID + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); // 启动活动并等待结果 } + /** + * 打开文件夹 + * @param data 文件夹数据项 + */ private void openFolder(NoteItemData data) { - mCurrentFolderId = data.getId(); - startAsyncNotesListQuery(); + mCurrentFolderId = data.getId(); // 更新当前文件夹ID + startAsyncNotesListQuery(); // 重新查询该文件夹下的内容 + + // 根据文件夹类型更新界面状态 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mState = ListEditState.CALL_RECORD_FOLDER; - mAddNewNote.setVisibility(View.GONE); + mState = ListEditState.CALL_RECORD_FOLDER; // 通话记录文件夹状态 + mAddNewNote.setVisibility(View.GONE); // 隐藏新建按钮(通话记录文件夹不可新建) } else { - mState = ListEditState.SUB_FOLDER; + mState = ListEditState.SUB_FOLDER; // 普通子文件夹状态 } + + // 更新标题栏显示 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mTitleBar.setText(R.string.call_record_folder_name); + mTitleBar.setText(R.string.call_record_folder_name); // 固定标题 } else { - mTitleBar.setText(data.getSnippet()); + mTitleBar.setText(data.getSnippet()); // 显示文件夹名称 } - mTitleBar.setVisibility(View.VISIBLE); + mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏 } + /** + * 视图点击事件处理 + * @param v 被点击的视图 + */ public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: - createNewNote(); + createNewNote(); // 新建笔记 break; default: break; } } + /** + * 显示软键盘 + */ private void showSoftInput() { + // 获取输入法管理器 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { + // 强制显示软键盘 inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } } + /** + * 隐藏软键盘 + * @param view 关联的输入视图 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + if (inputMethodManager != null) { + // 从指定视图的窗口令牌隐藏软键盘 + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } } + /** + * 显示创建/修改文件夹对话框 + * @param create true表示创建文件夹,false表示修改文件夹名称 + */ private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); + // 加载输入对话框布局 View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); - final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); - showSoftInput(); - if (!create) { + 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)); + etName.setText(mFocusNoteDataItem.getSnippet()); // 填充现有文件夹名称 + builder.setTitle(getString(R.string.menu_folder_change_name)); // 设置标题为"重命名" } else { - Log.e(TAG, "The long click data item is null"); + Log.e(TAG, "长按的数据项为空"); return; } - } else { - etName.setText(""); - builder.setTitle(this.getString(R.string.menu_create_folder)); + } else { // 创建模式 + etName.setText(""); // 清空输入框 + builder.setTitle(this.getString(R.string.menu_create_folder)); // 设置标题为"新建文件夹" } - builder.setPositiveButton(android.R.string.ok, null); + // 设置对话框按钮 + 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); + hideSoftInput(etName); // 取消时隐藏软键盘 } }); - final Dialog dialog = builder.setView(view).show(); - final Button positive = (Button)dialog.findViewById(android.R.id.button1); + 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(); + 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()); + etName.setSelection(0, etName.length()); // 选中全部文本以便重新输入 return; } - if (!create) { + + // 根据模式执行更新或插入操作 + 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()) - }); + 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)) { + } else if (!TextUtils.isEmpty(name)) { // 创建新文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.SNIPPET, name); values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); // 插入新文件夹记录 } - dialog.dismiss(); + dialog.dismiss(); // 关闭对话框 } }); + // 输入框内容变化监听:控制"确定"按钮是否可用 if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); } - /** - * When the name edit text is null, disable the positive button - */ etName.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - - } - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (TextUtils.isEmpty(etName.getText())) { - positive.setEnabled(false); - } else { - positive.setEnabled(true); - } - } - - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - + positive.setEnabled(!TextUtils.isEmpty(s)); // 非空时启用按钮 } + // 未使用的方法,保持空实现 + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + public void afterTextChanged(Editable s) {} }); } + /** + * 处理返回键事件 + */ @Override public void onBackPressed() { switch (mState) { - case SUB_FOLDER: + case SUB_FOLDER: // 从子文件夹返回根目录 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; - startAsyncNotesListQuery(); - mTitleBar.setVisibility(View.GONE); + startAsyncNotesListQuery(); // 重新加载根目录内容 + mTitleBar.setVisibility(View.GONE); // 隐藏标题栏 break; - case CALL_RECORD_FOLDER: + case CALL_RECORD_FOLDER: // 从通话记录文件夹返回根目录 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; - mAddNewNote.setVisibility(View.VISIBLE); + mAddNewNote.setVisibility(View.VISIBLE); // 显示新建按钮 mTitleBar.setVisibility(View.GONE); startAsyncNotesListQuery(); break; - case NOTE_LIST: + case NOTE_LIST: // 根目录下按返回键,调用系统默认处理 super.onBackPressed(); break; default: @@ -694,29 +851,38 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + /** + * 更新小部件显示 + * @param appWidgetId 小部件ID + * @param appWidgetType 小部件类型(2x或4x) + */ private void updateWidget(int appWidgetId, int appWidgetType) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + 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"); + Log.e(TAG, "不支持的小部件类型"); return; } - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - appWidgetId - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); + // 传递小部件ID数组 + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId }); + sendBroadcast(intent); // 发送广播更新小部件 + setResult(RESULT_OK, intent); // 设置活动结果 } + /** + * 上下文菜单创建监听器(文件夹操作) + */ private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为文件夹名称 + // 添加查看、删除、重命名菜单项 menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); @@ -724,64 +890,83 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } }; + /** + * 上下文菜单关闭时回调 + * 清除上下文菜单监听器,避免内存泄漏 + */ @Override public void onContextMenuClosed(Menu menu) { if (mNotesListView != null) { - mNotesListView.setOnCreateContextMenuListener(null); + mNotesListView.setOnCreateContextMenuListener(null); // 移除监听器 } super.onContextMenuClosed(menu); } + /** + * 上下文菜单项选中处理 + * @param item 选中的菜单项 + * @return 是否处理成功 + */ @Override public boolean onContextItemSelected(MenuItem item) { if (mFocusNoteDataItem == null) { - Log.e(TAG, "The long click data item is null"); + Log.e(TAG, "长按的数据项为空"); return false; } switch (item.getItemId()) { - case MENU_FOLDER_VIEW: + case MENU_FOLDER_VIEW: // 查看文件夹 openFolder(mFocusNoteDataItem); break; - case MENU_FOLDER_DELETE: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_folder)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { + case MENU_FOLDER_DELETE: // 删除文件夹 + new AlertDialog.Builder(this) + .setTitle(R.string.alert_title_delete) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(R.string.alert_message_delete_folder) // 显示删除确认消息 + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - deleteFolder(mFocusNoteDataItem.getId()); + deleteFolder(mFocusNoteDataItem.getId()); // 执行删除操作 } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); break; - case MENU_FOLDER_CHANGE_NAME: - showCreateOrModifyFolderDialog(false); + case MENU_FOLDER_CHANGE_NAME: // 重命名文件夹 + showCreateOrModifyFolderDialog(false); // 显示修改名称对话框 break; default: break; } - return true; } - +//= + /** + * Callback when preparing the options menu + * Loads different menu layouts based on current UI state and dynamically updates menu items + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { + // Clear existing menu items menu.clear(); + + // Inflate different menu layouts based on current state (mState) if (mState == ListEditState.NOTE_LIST) { + // Load note list menu getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel + // Update sync menu item title based on sync status menu.findItem(R.id.menu_sync).setTitle( GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); } else if (mState == ListEditState.SUB_FOLDER) { + // Load sub-folder menu getMenuInflater().inflate(R.menu.sub_folder, menu); } else if (mState == ListEditState.CALL_RECORD_FOLDER) { + // Load call record folder menu getMenuInflater().inflate(R.menu.call_record_folder, menu); } else { + // Log error for unexpected state Log.e(TAG, "Wrong state:" + mState); } + // Control visibility of background change menu items based on current mode if(mode==-1) menu.findItem(R.id.menu_beijing).setVisible(false); else if(mode==0) @@ -789,48 +974,60 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return true; } + /** + * Handles menu item selection + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_beijing:{ + // Change to Beijing background mode=-1; getWindow().setBackgroundDrawableResource(R.drawable.beijing); break; } case R.id.menu_bb:{ + // Change to bb background mode=0; getWindow().setBackgroundDrawableResource(R.drawable.bb); break; } case R.id.menu_new_folder: { + // Show dialog to create new folder showCreateOrModifyFolderDialog(true); break; } case R.id.menu_export_text: { + // Export notes to text exportNoteToText(); break; } case R.id.menu_sync: { if (isSyncMode()) { + // Toggle between sync and cancel sync based on current state if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { GTaskSyncService.startSync(this); } else { GTaskSyncService.cancelSync(this); } } else { + // Start preference activity if not in sync mode startPreferenceActivity(); } break; } case R.id.menu_setting: { + // Open settings/preferences startPreferenceActivity(); break; } case R.id.menu_new_note: { + // Create new note createNewNote(); break; } case R.id.menu_search: + // Trigger search functionality onSearchRequested(); break; default: @@ -839,24 +1036,35 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return true; } + /** + * Handles search request + */ @Override public boolean onSearchRequested() { + // Start search with default parameters startSearch(null, false, null /* appData */, false); return true; } + /** + * Exports notes to text file + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); + // Perform export in background thread new AsyncTask() { @Override protected Integer doInBackground(Void... unused) { + // Perform actual export operation return backup.exportToText(); } @Override protected void onPostExecute(Integer result) { + // Handle export result and show appropriate dialogs if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { + // Show error if SD card not mounted AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this .getString(R.string.failed_sdcard_export)); @@ -865,6 +1073,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe builder.setPositiveButton(android.R.string.ok, null); builder.show(); } else if (result == BackupUtils.STATE_SUCCESS) { + // Show success message with file location AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this .getString(R.string.success_sdcard_export)); @@ -874,6 +1083,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe builder.setPositiveButton(android.R.string.ok, null); builder.show(); } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + // Show system error message AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this .getString(R.string.failed_sdcard_export)); @@ -887,21 +1097,32 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe }.execute(); } + /** + * Checks if sync mode is enabled + * @return true if sync account is configured + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * Starts preference activity + */ private void startPreferenceActivity() { Activity from = getParent() != null ? getParent() : this; Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } + /** + * Listener for list item clicks + */ 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(); + // Handle click in choice mode (selection mode) if (mNotesListAdapter.isInChoiceMode()) { if (item.getType() == Notes.TYPE_NOTE) { position = position - mNotesListView.getHeaderViewsCount(); @@ -911,12 +1132,15 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return; } + // Handle normal click based on current state switch (mState) { case NOTE_LIST: if (item.getType() == Notes.TYPE_FOLDER || item.getType() == Notes.TYPE_SYSTEM) { + // Open folder if item is folder type openFolder(item); } else if (item.getType() == Notes.TYPE_NOTE) { + // Open note if item is note type openNode(item); } else { Log.e(TAG, "Wrong note type in NOTE_LIST"); @@ -925,6 +1149,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe case SUB_FOLDER: case CALL_RECORD_FOLDER: if (item.getType() == Notes.TYPE_NOTE) { + // Open note in sub-folder or call record folder openNode(item); } else { Log.e(TAG, "Wrong note type in SUB_FOLDER"); @@ -935,13 +1160,12 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } } - } 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 + ")"; + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, null,