callback) {
+ executor.execute(() -> {
+ try {
+ ContentValues values = new ContentValues();
+ long currentTime = System.currentTimeMillis();
+
+ values.put(NoteColumns.PARENT_ID, parentId);
+ values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
+ values.put(NoteColumns.SNIPPET, name);
+ values.put(NoteColumns.CREATED_DATE, currentTime);
+ values.put(NoteColumns.MODIFIED_DATE, currentTime);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+ values.put(NoteColumns.NOTES_COUNT, 0);
+
+ Uri uri = contentResolver.insert(Notes.CONTENT_NOTE_URI, values);
+
+ Long folderId = 0L;
+ if (uri != null) {
+ try {
+ folderId = ContentUris.parseId(uri);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to parse folder ID from URI", e);
+ }
+ }
+
+ callback.onSuccess(folderId);
+ Log.d(TAG, "Successfully created folder: " + name + " with ID: " + folderId);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to create folder: " + name, e);
+ callback.onError(e);
+ }
+ });
+ }
+
/**
* 创建新笔记
*
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
index 9544f6c..d4cecfe 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
@@ -71,8 +71,11 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.material.appbar.MaterialToolbar;
-public class NoteEditActivity extends Activity implements OnClickListener,
+
+public class NoteEditActivity extends AppCompatActivity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
* 笔记头部视图持有者
@@ -143,6 +146,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
+ private MaterialToolbar toolbar;
+
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
@@ -160,6 +165,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
+ // 初始化Toolbar(使用MaterialToolbar,与列表页面一致)
+ MaterialToolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ }
+ toolbar.setNavigationOnClickListener(v -> finish());
+
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
@@ -284,6 +298,49 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
+ /**
+ * 初始化资源
+ *
+ * 初始化笔记编辑界面的所有UI组件引用和点击监听器
+ *
+ */
+ private void initResources() {
+ mHeadViewPanel = findViewById(R.id.note_title);
+ mNoteHeaderHolder = new HeadViewHolder();
+ mNoteHeaderHolder.tvModified = findViewById(R.id.tv_modified_date);
+ mNoteHeaderHolder.ivAlertIcon = findViewById(R.id.iv_alert_icon);
+ mNoteHeaderHolder.tvAlertDate = findViewById(R.id.tv_alert_date);
+ mNoteHeaderHolder.ibSetBgColor = findViewById(R.id.btn_set_bg_color);
+ mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
+ mNoteEditor = findViewById(R.id.note_edit_view);
+ mNoteEditorPanel = findViewById(R.id.sv_note_edit);
+ mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
+
+ // 设置背景颜色选择器的点击事件
+ for (int id : sBgSelectorBtnsMap.keySet()) {
+ ImageView iv = findViewById(id);
+ iv.setOnClickListener(this);
+ }
+
+ mFontSizeSelector = findViewById(R.id.font_size_selector);
+ for (int id : sFontSizeBtnsMap.keySet()) {
+ View view = findViewById(id);
+ view.setOnClickListener(this);
+ }
+
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
+ /**
+ * HACKME: Fix bug of store the resource id in shared preference.
+ * The id may larger than the length of resources, in this case,
+ * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
+ */
+ if (mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
+ mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
+ }
+ mEditTextList = findViewById(R.id.note_edit_list);
+ }
+
@Override
protected void onResume() {
super.onResume();
@@ -430,47 +487,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
- /**
- * 初始化资源
- *
- * 初始化所有UI组件的引用,设置点击监听器,
- * 并从SharedPreferences中读取字体大小设置。
- *
- */
- private void initResources() {
- mHeadViewPanel = findViewById(R.id.note_title);
- mNoteHeaderHolder = new HeadViewHolder();
- mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
- mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
- mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
- mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
- mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
- mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
- mNoteEditorPanel = findViewById(R.id.sv_note_edit);
- mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
- for (int id : sBgSelectorBtnsMap.keySet()) {
- ImageView iv = (ImageView) findViewById(id);
- iv.setOnClickListener(this);
- }
-
- mFontSizeSelector = findViewById(R.id.font_size_selector);
- for (int id : sFontSizeBtnsMap.keySet()) {
- View view = findViewById(id);
- view.setOnClickListener(this);
- };
- mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
- /**
- * HACKME: Fix bug of store the resource id in shared preference.
- * The id may larger than the length of resources, in this case,
- * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
- */
- if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
- mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
- }
- mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
- }
-
/**
* 活动暂停时保存笔记
*
@@ -528,7 +544,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- - View.VISIBLE);
+ View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
index d4d961f..c237a90 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java
@@ -22,18 +22,31 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.TextUtils;
import android.util.Log;
import androidx.appcompat.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
+import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@@ -64,7 +77,8 @@ import java.util.List;
public class NotesListActivity extends AppCompatActivity
implements NoteInfoAdapter.OnNoteButtonClickListener,
NoteInfoAdapter.OnNoteItemClickListener,
- NoteInfoAdapter.OnNoteItemLongClickListener {
+ NoteInfoAdapter.OnNoteItemLongClickListener,
+ SidebarFragment.OnSidebarItemSelectedListener {
private static final String TAG = "NotesListActivity";
private static final int REQUEST_CODE_OPEN_NODE = 102;
private static final int REQUEST_CODE_NEW_NODE = 103;
@@ -73,7 +87,13 @@ public class NotesListActivity extends AppCompatActivity
private ListView notesListView;
private androidx.appcompat.widget.Toolbar toolbar;
private NoteInfoAdapter adapter;
- private androidx.appcompat.view.ActionMode actionMode;
+ private DrawerLayout drawerLayout;
+ private FloatingActionButton fabNewNote;
+ private LinearLayout breadcrumbContainer;
+ private LinearLayout breadcrumbItems;
+
+ // 多选模式状态
+ private boolean isMultiSelectMode = false;
/**
* 活动创建时的初始化方法
@@ -86,8 +106,21 @@ public class NotesListActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ // 启用边缘到边缘显示
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+
setContentView(R.layout.note_list);
+ // 处理窗口insets(状态栏和导航栏)
+ View mainView = findViewById(android.R.id.content);
+ ViewCompat.setOnApplyWindowInsetsListener(mainView, (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
+ // 设置内容区域的padding以避免被状态栏遮挡
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsetsCompat.CONSUMED;
+ });
+
initViewModel();
initViews();
observeViewModel();
@@ -129,6 +162,11 @@ public class NotesListActivity extends AppCompatActivity
private void initViews() {
notesListView = findViewById(R.id.notes_list);
toolbar = findViewById(R.id.toolbar);
+ drawerLayout = findViewById(R.id.drawer_layout);
+
+ // 初始化面包屑导航
+ breadcrumbContainer = findViewById(R.id.breadcrumb_container);
+ breadcrumbItems = findViewById(R.id.breadcrumb_items);
// 设置适配器
adapter = new NoteInfoAdapter(this);
@@ -144,7 +182,7 @@ public class NotesListActivity extends AppCompatActivity
Object item = parent.getItemAtPosition(position);
if (item instanceof NotesRepository.NoteInfo) {
NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) item;
- openNoteEditor(note);
+ handleItemClick(note, position);
}
}
});
@@ -156,18 +194,58 @@ public class NotesListActivity extends AppCompatActivity
getSupportActionBar().setTitle(R.string.app_name);
}
+ // 初始化为普通模式
+ updateToolbarForNormalMode();
+
+ // 设置 Toolbar 的汉堡菜单按钮点击监听器(打开侧栏)
+ toolbar.setNavigationOnClickListener(v -> {
+ if (drawerLayout != null) {
+ drawerLayout.openDrawer(findViewById(R.id.sidebar_fragment));
+ }
+ });
+
// Set FAB click event
- FloatingActionButton fabNewNote = findViewById(R.id.btn_new_note);
+ fabNewNote = findViewById(R.id.btn_new_note);
if (fabNewNote != null) {
fabNewNote.setOnClickListener(v -> {
Intent intent = new Intent(NotesListActivity.this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, Notes.ID_ROOT_FOLDER);
+ intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, viewModel.getCurrentFolderId());
startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
});
}
}
+ /**
+ * 处理列表项点击
+ *
+ * 如果是便签,打开编辑器;如果是文件夹,进入该文件夹
+ *
+ *
+ * @param note 项
+ * @param position 位置
+ */
+ private void handleItemClick(NotesRepository.NoteInfo note, int position) {
+ if (isMultiSelectMode) {
+ // 多选模式:切换选中状态
+ boolean isSelected = viewModel.getSelectedNoteIds().contains(note.getId());
+ viewModel.toggleNoteSelection(note.getId(), !isSelected);
+ if (adapter != null) {
+ adapter.setSelectedIds(viewModel.getSelectedNoteIds());
+ }
+ updateToolbarForMultiSelectMode();
+ } else {
+ // 普通模式
+ if (note.type == Notes.TYPE_FOLDER) {
+ // 文件夹:进入该文件夹
+ viewModel.enterFolder(note.getId());
+ } else {
+ // 便签:打开编辑器
+ openNoteEditor(note);
+ }
+ }
+ }
+
/**
* 观察ViewModel的LiveData
*/
@@ -197,6 +275,74 @@ public class NotesListActivity extends AppCompatActivity
}
}
});
+
+ // 观察文件夹路径(用于面包屑导航)
+ viewModel.getFolderPathLiveData().observe(this, new Observer>() {
+ @Override
+ public void onChanged(List path) {
+ updateBreadcrumb(path);
+ }
+ });
+
+ // 观察侧栏刷新通知
+ viewModel.getSidebarRefreshNeeded().observe(this, new Observer() {
+ @Override
+ public void onChanged(Boolean refreshNeeded) {
+ if (refreshNeeded != null && refreshNeeded) {
+ // 通知侧栏刷新
+ SidebarFragment sidebarFragment = (SidebarFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.sidebar_fragment);
+ if (sidebarFragment != null) {
+ sidebarFragment.refreshFolderTree();
+ }
+ // 重置刷新状态
+ viewModel.getSidebarRefreshNeeded().setValue(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * 更新面包屑导航
+ *
+ * @param path 文件夹路径
+ */
+ private void updateBreadcrumb(List path) {
+ if (breadcrumbItems == null || path == null) {
+ return;
+ }
+
+ breadcrumbItems.removeAllViews();
+
+ for (int i = 0; i < path.size(); i++) {
+ NotesRepository.NoteInfo folder = path.get(i);
+
+ // 如果不是第一个,添加分隔符 " > "
+ if (i > 0) {
+ TextView separator = new TextView(this);
+ separator.setText(" > ");
+ separator.setTextSize(14);
+ separator.setTextColor(android.R.color.darker_gray);
+ breadcrumbItems.addView(separator);
+ }
+
+ // 创建面包屑项
+ TextView breadcrumbItem = (TextView) getLayoutInflater()
+ .inflate(R.layout.breadcrumb_item, breadcrumbItems, false);
+ breadcrumbItem.setText(folder.title);
+
+ // 如果是当前文件夹(最后一个),高亮显示且不可点击
+ if (i == path.size() - 1) {
+ breadcrumbItem.setTextColor(getColor(R.color.primary_color));
+ breadcrumbItem.setEnabled(false);
+ } else {
+ // 其他层级可以点击跳转
+ final long targetFolderId = folder.id;
+ breadcrumbItem.setOnClickListener(v -> viewModel.enterFolder(targetFolderId));
+ }
+
+ breadcrumbItems.addView(breadcrumbItem);
+ }
}
/**
@@ -252,26 +398,36 @@ public class NotesListActivity extends AppCompatActivity
public void onNoteItemClick(int position, long noteId) {
Log.d(TAG, "===== onNoteItemClick CALLED =====");
Log.d(TAG, "position: " + position + ", noteId: " + noteId);
-
- if (actionMode != null) {
- Log.d(TAG, "ActionMode is active, toggling selection");
+
+ if (isMultiSelectMode) {
+ Log.d(TAG, "Multi-select mode active, toggling selection");
NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
if (note != null) {
boolean isSelected = viewModel.getSelectedNoteIds().contains(note.getId());
viewModel.toggleNoteSelection(note.getId(), !isSelected);
-
+
if (adapter != null) {
adapter.setSelectedIds(viewModel.getSelectedNoteIds());
}
+ // 更新toolbar标题
+ updateToolbarForMultiSelectMode();
}
Log.d(TAG, "===== onNoteItemClick END (multi-select mode) =====");
} else {
- Log.d(TAG, "ActionMode is not active, opening editor");
+ Log.d(TAG, "Normal mode, checking item type");
NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
if (note != null) {
- openNoteEditor(note);
+ if (note.type == Notes.TYPE_FOLDER) {
+ // 文件夹:进入该文件夹
+ Log.d(TAG, "Folder clicked, entering folder: " + note.getId());
+ viewModel.enterFolder(note.getId());
+ } else {
+ // 便签:打开编辑器
+ Log.d(TAG, "Note clicked, opening editor");
+ openNoteEditor(note);
+ }
}
- Log.d(TAG, "===== onNoteItemClick END (editor mode) =====");
+ Log.d(TAG, "===== onNoteItemClick END =====");
}
}
@@ -279,91 +435,114 @@ public class NotesListActivity extends AppCompatActivity
public void onNoteItemLongClick(int position, long noteId) {
Log.d(TAG, "===== onNoteItemLongClick CALLED =====");
Log.d(TAG, "position: " + position + ", noteId: " + noteId);
-
- if (actionMode == null) {
- Log.d(TAG, "Starting ActionMode manually");
- actionMode = startSupportActionMode(new androidx.appcompat.view.ActionMode.Callback() {
- @Override
- public boolean onCreateActionMode(androidx.appcompat.view.ActionMode mode, Menu menu) {
- Log.d(TAG, "onCreateActionMode called");
- mode.getMenuInflater().inflate(R.menu.note_list_options, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(androidx.appcompat.view.ActionMode mode, MenuItem item) {
- Log.d(TAG, "onActionItemClicked: " + item.getTitle());
- int itemId = item.getItemId();
-
- if (itemId == R.id.delete) {
- showDeleteDialog();
- } else if (itemId == R.id.move) {
- showMoveMenu();
- }
-
- return true;
- }
- @Override
- public void onDestroyActionMode(androidx.appcompat.view.ActionMode mode) {
- Log.d(TAG, "onDestroyActionMode called");
- actionMode = null;
- viewModel.clearSelection();
-
- if (adapter != null) {
- adapter.setSelectedIds(new java.util.HashSet<>());
- }
- adapter.notifyDataSetChanged();
- }
- });
-
+ if (!isMultiSelectMode) {
+ Log.d(TAG, "Entering multi-select mode");
+ enterMultiSelectMode();
viewModel.toggleNoteSelection(noteId, true);
-
+
if (adapter != null) {
adapter.setSelectedIds(viewModel.getSelectedNoteIds());
}
-
+
updateSelectionState(position, true);
-
+
Log.d(TAG, "===== onNoteItemLongClick END =====");
} else {
- Log.d(TAG, "ActionMode already active, ignoring long click");
+ Log.d(TAG, "Multi-select mode already active, ignoring long click");
}
}
/**
- * 更新ActionMode标题
+ * 进入多选模式
*/
- private void updateActionModeTitle(androidx.appcompat.view.ActionMode mode) {
- int selectedCount = viewModel.getSelectedCount();
- String title = getString(R.string.menu_select_title, selectedCount);
- mode.setTitle(title);
+ private void enterMultiSelectMode() {
+ isMultiSelectMode = true;
+ // 隐藏FAB按钮
+ if (fabNewNote != null) {
+ fabNewNote.setVisibility(View.GONE);
+ }
+ // 更新toolbar为多选模式
+ updateToolbarForMultiSelectMode();
}
/**
- * 选中状态变化回调
- *
- * @param mode ActionMode 实例
- * @param position 位置
- * @param id 便签 ID
- * @param checked 是否选中
+ * 退出多选模式
*/
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
- Log.d(TAG, "onItemCheckedStateChanged: id=" + id + ", checked=" + checked);
- viewModel.toggleNoteSelection(id, checked);
-
+ private void exitMultiSelectMode() {
+ isMultiSelectMode = false;
+ // 显示FAB按钮
+ if (fabNewNote != null) {
+ fabNewNote.setVisibility(View.VISIBLE);
+ }
+ // 清除选中状态
+ viewModel.clearSelection();
if (adapter != null) {
- adapter.setSelectedIds(viewModel.getSelectedNoteIds());
+ adapter.setSelectedIds(new java.util.HashSet<>());
+ adapter.notifyDataSetChanged();
}
-
- updateActionModeTitle(mode);
+ // 更新toolbar为普通模式
+ updateToolbarForNormalMode();
}
+ /**
+ * 更新Toolbar为多选模式
+ */
+ private void updateToolbarForMultiSelectMode() {
+ if (toolbar == null) return;
+
+ // 设置标题为选中数量
+ int selectedCount = viewModel.getSelectedCount();
+ String title = getString(R.string.menu_select_title, selectedCount);
+ toolbar.setTitle(title);
+
+ // 设置导航图标为返回(取消多选)
+ toolbar.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
+ toolbar.setNavigationOnClickListener(v -> exitMultiSelectMode());
+
+ // 移除普通模式的菜单(如果有)
+ toolbar.getMenu().clear();
+
+ // 直接在toolbar上添加操作按钮(不在三点菜单中)
+ Menu menu = toolbar.getMenu();
+
+ // 删除按钮
+ MenuItem deleteItem = menu.add(Menu.NONE, R.id.multi_select_delete, 1, getString(R.string.menu_delete));
+ deleteItem.setIcon(android.R.drawable.ic_menu_delete);
+ deleteItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+
+ // 移动按钮
+ MenuItem moveItem = menu.add(Menu.NONE, R.id.multi_select_move, 2, getString(R.string.menu_move));
+ moveItem.setIcon(android.R.drawable.ic_menu_sort_by_size);
+ moveItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
+
+ /**
+ * 更新Toolbar为普通模式
+ */
+ private void updateToolbarForNormalMode() {
+ if (toolbar == null) return;
+
+ // 设置标题为应用名称
+ toolbar.setTitle(R.string.app_name);
+
+ // 设置导航图标为汉堡菜单
+ toolbar.setNavigationIcon(android.R.drawable.ic_menu_sort_by_size);
+ toolbar.setNavigationOnClickListener(v -> {
+ if (drawerLayout != null) {
+ drawerLayout.openDrawer(findViewById(R.id.sidebar_fragment));
+ }
+ });
+
+ // 清除多选模式菜单
+ toolbar.getMenu().clear();
+
+ // 添加普通模式菜单(如果需要)
+ // getMenuInflater().inflate(R.menu.note_list_options, menu);
+ }
+
+
+
/**
* 显示删除确认对话框
*/
@@ -427,8 +606,8 @@ public class NotesListActivity extends AppCompatActivity
Toast.makeText(this, "搜索功能开发中", Toast.LENGTH_SHORT).show();
return true;
case R.id.menu_new_folder:
- // TODO: 创建新文件夹
- Toast.makeText(this, "创建文件夹功能开发中", Toast.LENGTH_SHORT).show();
+ // 创建新文件夹
+ showCreateFolderDialog();
return true;
case R.id.menu_export_text:
// TODO: 导出笔记
@@ -442,6 +621,13 @@ public class NotesListActivity extends AppCompatActivity
// TODO: 设置功能
Toast.makeText(this, "设置功能开发中", Toast.LENGTH_SHORT).show();
return true;
+ // 多选模式菜单项
+ case R.id.multi_select_delete:
+ showDeleteDialog();
+ return true;
+ case R.id.multi_select_move:
+ showMoveMenu();
+ return true;
default:
return super.onOptionsItemSelected(item);
}
@@ -500,4 +686,152 @@ public class NotesListActivity extends AppCompatActivity
}
Log.d("NotesListActivity", "===== updateSelectionState END =====");
}
+
+ // ==================== SidebarFragment.OnSidebarItemSelectedListener 实现 ====================
+
+ @Override
+ public void onFolderSelected(long folderId) {
+ // 跳转到指定文件夹
+ viewModel.enterFolder(folderId);
+ // 关闭侧栏
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ }
+ }
+
+ @Override
+ public void onTrashSelected() {
+ // TODO: 实现跳转到回收站
+ Log.d(TAG, "Trash selected");
+ // 关闭侧栏
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ }
+ }
+
+ @Override
+ public void onSyncSelected() {
+ // TODO: 实现同步功能
+ Log.d(TAG, "Sync selected");
+ Toast.makeText(this, "同步功能待实现", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onLoginSelected() {
+ // TODO: 实现登录功能
+ Log.d(TAG, "Login selected");
+ Toast.makeText(this, "登录功能待实现", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onExportSelected() {
+ // TODO: 实现导出功能
+ Log.d(TAG, "Export selected");
+ Toast.makeText(this, "导出功能待实现", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onSettingsSelected() {
+ // TODO: 实现设置功能
+ Log.d(TAG, "Settings selected");
+ Toast.makeText(this, "设置功能待实现", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onCreateFolder() {
+ // 显示创建文件夹对话框
+ showCreateFolderDialog();
+ }
+
+ /**
+ * 显示创建文件夹对话框
+ */
+ private void showCreateFolderDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.dialog_create_folder_title);
+
+ final EditText input = new EditText(this);
+ input.setHint(R.string.dialog_create_folder_hint);
+ input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(50)});
+
+ builder.setView(input);
+
+ builder.setPositiveButton(R.string.menu_create_folder, (dialog, which) -> {
+ String folderName = input.getText().toString().trim();
+ if (TextUtils.isEmpty(folderName)) {
+ Toast.makeText(this, R.string.error_folder_name_empty, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (folderName.length() > 50) {
+ Toast.makeText(this, R.string.error_folder_name_too_long, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 创建文件夹
+ NotesRepository repository = new NotesRepository(getContentResolver());
+ long parentId = viewModel.getCurrentFolderId();
+ if (parentId == 0) {
+ parentId = Notes.ID_ROOT_FOLDER;
+ }
+ repository.createFolder(parentId, folderName,
+ new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(Long folderId) {
+ runOnUiThread(() -> {
+ Toast.makeText(NotesListActivity.this, R.string.create_folder_success, Toast.LENGTH_SHORT).show();
+ // 刷新笔记列表
+ viewModel.loadNotes(viewModel.getCurrentFolderId());
+ });
+ }
+
+ @Override
+ public void onError(Exception error) {
+ runOnUiThread(() -> {
+ Toast.makeText(NotesListActivity.this, "创建文件夹失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
+ });
+ }
+ });
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.show();
+ }
+
+ @Override
+ public void onCloseSidebar() {
+ // 关闭侧栏
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ }
+ }
+
+ /**
+ * 返回键按下事件处理
+ *
+ *
+ * 多选模式:退出多选模式
+ * 子文件夹:返回上一级文件夹
+ * 根文件夹:最小化应用
+ *
+ */
+ @Override
+ public void onBackPressed() {
+ if (isMultiSelectMode) {
+ // 多选模式:退出多选模式
+ exitMultiSelectMode();
+ } else if (drawerLayout != null && drawerLayout.isDrawerOpen(findViewById(R.id.sidebar_fragment))) {
+ // 侧栏打开:关闭侧栏
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ } else if (viewModel.getCurrentFolderId() != Notes.ID_ROOT_FOLDER &&
+ viewModel.getCurrentFolderId() != Notes.ID_CALL_RECORD_FOLDER) {
+ // 子文件夹:返回上一级
+ if (!viewModel.navigateUp()) {
+ // 如果没有导航历史,返回根文件夹
+ viewModel.loadNotes(Notes.ID_ROOT_FOLDER);
+ }
+ } else {
+ // 根文件夹:最小化应用
+ moveTaskToBack(true);
+ }
+ }
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java
new file mode 100644
index 0000000..2eec39b
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SidebarFragment.java
@@ -0,0 +1,450 @@
+/*
+ * 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.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.NotesRepository;
+import net.micode.notes.viewmodel.FolderListViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 侧栏Fragment
+ *
+ * 显示文件夹树、菜单项和操作按钮
+ * 提供文件夹导航、创建、展开/收起等功能
+ *
+ */
+public class SidebarFragment extends Fragment {
+
+ private static final String TAG = "SidebarFragment";
+ private static final int MAX_FOLDER_NAME_LENGTH = 50;
+
+ // 视图组件
+ private RecyclerView rvFolderTree;
+ private TextView tvRootFolder;
+ private TextView menuSync;
+ private TextView menuLogin;
+ private TextView menuExport;
+ private TextView menuSettings;
+ private TextView menuTrash;
+
+ // 适配器和数据
+ private FolderTreeAdapter adapter;
+ private FolderListViewModel viewModel;
+
+ // 单击和双击检测
+ private long lastClickTime = 0;
+ private View lastClickedView = null;
+ private static final long DOUBLE_CLICK_INTERVAL = 300; // 毫秒
+
+ // 回调接口
+ private OnSidebarItemSelectedListener listener;
+
+ /**
+ * 侧栏项选择回调接口
+ */
+ public interface OnSidebarItemSelectedListener {
+ /**
+ * 跳转到指定文件夹
+ * @param folderId 文件夹ID
+ */
+ void onFolderSelected(long folderId);
+
+ /**
+ * 打开回收站
+ */
+ void onTrashSelected();
+
+ /**
+ * 同步
+ */
+ void onSyncSelected();
+
+ /**
+ * 登录
+ */
+ void onLoginSelected();
+
+ /**
+ * 导出
+ */
+ void onExportSelected();
+
+ /**
+ * 设置
+ */
+ void onSettingsSelected();
+
+ /**
+ * 创建文件夹
+ */
+ void onCreateFolder();
+
+ /**
+ * 关闭侧栏
+ */
+ void onCloseSidebar();
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (context instanceof OnSidebarItemSelectedListener) {
+ listener = (OnSidebarItemSelectedListener) context;
+ } else {
+ throw new RuntimeException(context.toString() + " must implement OnSidebarItemSelectedListener");
+ }
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ viewModel = new ViewModelProvider(this).get(FolderListViewModel.class);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.sidebar_layout, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ initViews(view);
+ setupListeners();
+ observeViewModel();
+ }
+
+ /**
+ * 刷新文件夹树(供外部调用,如删除笔记后)
+ */
+ public void refreshFolderTree() {
+ if (viewModel != null) {
+ viewModel.loadFolderTree();
+ }
+ }
+
+ /**
+ * 初始化视图
+ */
+ private void initViews(View view) {
+ rvFolderTree = view.findViewById(R.id.rv_folder_tree);
+ tvRootFolder = view.findViewById(R.id.tv_root_folder);
+ menuSync = view.findViewById(R.id.menu_sync);
+ menuLogin = view.findViewById(R.id.menu_login);
+ menuExport = view.findViewById(R.id.menu_export);
+ menuSettings = view.findViewById(R.id.menu_settings);
+ menuTrash = view.findViewById(R.id.menu_trash);
+
+ // 设置RecyclerView
+ rvFolderTree.setLayoutManager(new LinearLayoutManager(requireContext()));
+ adapter = new FolderTreeAdapter(new ArrayList<>(), viewModel);
+ rvFolderTree.setAdapter(adapter);
+ }
+
+ /**
+ * 设置监听器
+ */
+ private void setupListeners() {
+ View view = getView();
+ if (view == null) return;
+
+ // 根文件夹(单击展开/收起,双击跳转)
+ setupFolderClickListener(tvRootFolder, Notes.ID_ROOT_FOLDER);
+
+ // 关闭侧栏
+ view.findViewById(R.id.btn_close_sidebar).setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onCloseSidebar();
+ }
+ });
+
+ // 创建文件夹
+ view.findViewById(R.id.btn_create_folder).setOnClickListener(v -> showCreateFolderDialog());
+
+ // 菜单项
+ menuSync.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onSyncSelected();
+ }
+ });
+
+ menuLogin.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onLoginSelected();
+ }
+ });
+
+ menuExport.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onExportSelected();
+ }
+ });
+
+ menuSettings.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onSettingsSelected();
+ }
+ });
+
+ menuTrash.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onTrashSelected();
+ }
+ });
+ }
+
+ /**
+ * 设置文件夹的单击/双击监听器
+ */
+ private void setupFolderClickListener(View view, long folderId) {
+ view.setOnClickListener(v -> {
+ long currentTime = System.currentTimeMillis();
+ if (lastClickedView == view && (currentTime - lastClickTime) < DOUBLE_CLICK_INTERVAL) {
+ // 这是双击,执行跳转
+ if (listener != null && folderId != Notes.ID_ROOT_FOLDER) {
+ listener.onFolderSelected(folderId);
+ }
+ // 重置双击状态
+ lastClickTime = 0;
+ lastClickedView = null;
+ } else {
+ // 可能是单击,延迟处理
+ lastClickTime = currentTime;
+ lastClickedView = view;
+ view.postDelayed(() -> {
+ // 如果在延迟期间没有发生双击,则执行单击操作(展开/收起)
+ if (System.currentTimeMillis() - lastClickTime >= DOUBLE_CLICK_INTERVAL) {
+ toggleFolderExpand(folderId);
+ }
+ }, DOUBLE_CLICK_INTERVAL);
+ }
+ });
+ }
+
+ /**
+ * 观察ViewModel数据变化
+ */
+ private void observeViewModel() {
+ viewModel.getFolderTree().observe(getViewLifecycleOwner(), folderItems -> {
+ if (folderItems != null) {
+ adapter.setData(folderItems);
+ adapter.notifyDataSetChanged();
+ }
+ });
+
+ viewModel.loadFolderTree();
+ }
+
+ /**
+ * 切换文件夹展开/收起状态
+ */
+ private void toggleFolderExpand(long folderId) {
+ viewModel.toggleFolderExpand(folderId);
+ }
+
+ /**
+ * 显示创建文件夹对话框
+ */
+ private void showCreateFolderDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
+ builder.setTitle(R.string.dialog_create_folder_title);
+
+ final EditText input = new EditText(requireContext());
+ input.setHint(R.string.dialog_create_folder_hint);
+ input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(MAX_FOLDER_NAME_LENGTH)});
+
+ builder.setView(input);
+
+ builder.setPositiveButton(R.string.menu_create_folder, (dialog, which) -> {
+ String folderName = input.getText().toString().trim();
+ if (TextUtils.isEmpty(folderName)) {
+ Toast.makeText(requireContext(), R.string.error_folder_name_empty, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (folderName.length() > MAX_FOLDER_NAME_LENGTH) {
+ Toast.makeText(requireContext(), R.string.error_folder_name_too_long, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 创建文件夹
+ NotesRepository repository = new NotesRepository(requireContext().getContentResolver());
+ long parentId = viewModel.getCurrentFolderId();
+ if (parentId == 0) {
+ parentId = Notes.ID_ROOT_FOLDER;
+ }
+ repository.createFolder(parentId, folderName,
+ new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(Long folderId) {
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> {
+ Toast.makeText(requireContext(), "创建文件夹成功", Toast.LENGTH_SHORT).show();
+ // 刷新文件夹列表
+ viewModel.loadFolderTree();
+ });
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> {
+ Toast.makeText(requireContext(), "创建文件夹失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
+ });
+ }
+ }
+ });
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.show();
+ }
+
+ /**
+ * FolderTreeAdapter
+ * 文件夹树适配器,支持层级显示和展开/收起
+ */
+ private static class FolderTreeAdapter extends RecyclerView.Adapter {
+
+ private List folderItems;
+ private FolderListViewModel viewModel;
+
+ public FolderTreeAdapter(List folderItems, FolderListViewModel viewModel) {
+ this.folderItems = folderItems;
+ this.viewModel = viewModel;
+ }
+
+ public void setData(List folderItems) {
+ this.folderItems = folderItems;
+ }
+
+ @NonNull
+ @Override
+ public FolderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.sidebar_folder_item, parent, false);
+ return new FolderViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull FolderViewHolder holder, int position) {
+ FolderTreeItem item = folderItems.get(position);
+ boolean isExpanded = viewModel != null && viewModel.isFolderExpanded(item.folderId);
+ holder.bind(item, isExpanded);
+ }
+
+ @Override
+ public int getItemCount() {
+ return folderItems.size();
+ }
+
+ static class FolderViewHolder extends RecyclerView.ViewHolder {
+ private View indentView;
+ private ImageView ivExpandIcon;
+ private ImageView ivFolderIcon;
+ private TextView tvFolderName;
+ private TextView tvNoteCount;
+
+ public FolderViewHolder(@NonNull View itemView) {
+ super(itemView);
+ indentView = itemView.findViewById(R.id.indent_view);
+ ivExpandIcon = itemView.findViewById(R.id.iv_expand_icon);
+ ivFolderIcon = itemView.findViewById(R.id.iv_folder_icon);
+ tvFolderName = itemView.findViewById(R.id.tv_folder_name);
+ tvNoteCount = itemView.findViewById(R.id.tv_note_count);
+ }
+
+ public void bind(FolderTreeItem item, boolean isExpanded) {
+ // 设置缩进
+ int indent = item.level * 32;
+ indentView.setLayoutParams(new LinearLayout.LayoutParams(indent, LinearLayout.LayoutParams.MATCH_PARENT));
+
+ // 设置展开/收起图标
+ if (item.hasChildren) {
+ ivExpandIcon.setVisibility(View.VISIBLE);
+ ivExpandIcon.setRotation(isExpanded ? 90 : 0);
+ } else {
+ ivExpandIcon.setVisibility(View.INVISIBLE);
+ }
+
+ // 设置文件夹名称
+ tvFolderName.setText(item.name);
+
+ // 设置便签数量
+ tvNoteCount.setText(String.format(itemView.getContext()
+ .getString(R.string.folder_note_count), item.noteCount));
+ }
+ }
+ }
+
+ /**
+ * FolderTreeItem
+ * 文件夹树项数据模型
+ */
+ public static class FolderTreeItem {
+ public long folderId;
+ public String name;
+ public int level; // 层级,0表示顶级
+ public boolean hasChildren;
+ public int noteCount;
+
+ public FolderTreeItem(long folderId, String name, int level, boolean hasChildren, int noteCount) {
+ this.folderId = folderId;
+ this.name = name;
+ this.level = level;
+ this.hasChildren = hasChildren;
+ this.noteCount = noteCount;
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ listener = null;
+ }
+}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/FolderListViewModel.java b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/FolderListViewModel.java
new file mode 100644
index 0000000..683a128
--- /dev/null
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/FolderListViewModel.java
@@ -0,0 +1,269 @@
+/*
+ * 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.viewmodel;
+
+import android.app.Application;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.NotesDatabaseHelper;
+import net.micode.notes.data.NotesDatabaseHelper.TABLE;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.data.NotesRepository;
+import net.micode.notes.ui.SidebarFragment.FolderTreeItem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 文件夹列表ViewModel
+ *
+ * 管理文件夹树的数据和业务逻辑
+ * 提供文件夹树的查询和构建功能
+ *
+ */
+public class FolderListViewModel extends AndroidViewModel {
+
+ private MutableLiveData> folderTreeLiveData;
+ private NotesDatabaseHelper dbHelper;
+ private NotesRepository repository;
+ private long currentFolderId = Notes.ID_ROOT_FOLDER; // 当前文件夹ID
+ private Set expandedFolderIds = new HashSet<>(); // 已展开的文件夹ID集合
+
+ public FolderListViewModel(@NonNull Application application) {
+ super(application);
+ dbHelper = NotesDatabaseHelper.getInstance(application);
+ repository = new NotesRepository(application.getContentResolver());
+ folderTreeLiveData = new MutableLiveData<>();
+ }
+
+ /**
+ * 获取当前文件夹ID
+ */
+ public long getCurrentFolderId() {
+ return currentFolderId;
+ }
+
+ /**
+ * 设置当前文件夹ID
+ */
+ public void setCurrentFolderId(long folderId) {
+ this.currentFolderId = folderId;
+ }
+
+ /**
+ * 切换文件夹展开/收起状态
+ * @param folderId 文件夹ID
+ */
+ public void toggleFolderExpand(long folderId) {
+ if (expandedFolderIds.contains(folderId)) {
+ expandedFolderIds.remove(folderId);
+ } else {
+ expandedFolderIds.add(folderId);
+ }
+ // 重新加载文件夹树
+ loadFolderTree();
+ }
+
+ /**
+ * 检查文件夹是否已展开
+ * @param folderId 文件夹ID
+ * @return 是否已展开
+ */
+ public boolean isFolderExpanded(long folderId) {
+ return expandedFolderIds.contains(folderId);
+ }
+
+ /**
+ * 获取文件夹树LiveData
+ */
+ public LiveData> getFolderTree() {
+ return folderTreeLiveData;
+ }
+
+ /**
+ * 加载文件夹树数据
+ */
+ public void loadFolderTree() {
+ new Thread(() -> {
+ List folderTree = buildFolderTree();
+ folderTreeLiveData.postValue(folderTree);
+ }).start();
+ }
+
+ /**
+ * 构建文件夹树
+ *
+ * 从数据库中查询所有文件夹,并构建层级结构
+ *
+ * @return 文件夹树列表
+ */
+ private List buildFolderTree() {
+ // 查询所有文件夹(不包括系统文件夹)
+ List