|
|
|
|
@ -18,15 +18,16 @@ package net.micode.notes.ui;
|
|
|
|
|
|
|
|
|
|
import android.app.AlertDialog;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.text.InputFilter;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
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.ImageButton;
|
|
|
|
|
import android.widget.ImageView;
|
|
|
|
|
import android.widget.LinearLayout;
|
|
|
|
|
import android.widget.PopupMenu;
|
|
|
|
|
@ -41,21 +42,20 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|
|
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
|
|
|
|
|
|
import net.micode.notes.R;
|
|
|
|
|
import net.micode.notes.auth.UserAuthManager;
|
|
|
|
|
import net.micode.notes.data.Notes;
|
|
|
|
|
import net.micode.notes.data.NotesRepository;
|
|
|
|
|
import net.micode.notes.databinding.SidebarLayoutBinding;
|
|
|
|
|
import net.micode.notes.sync.SyncManager;
|
|
|
|
|
import net.micode.notes.viewmodel.FolderListViewModel;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 侧栏Fragment
|
|
|
|
|
* 现代化侧边栏 Fragment - 自定义布局版
|
|
|
|
|
* <p>
|
|
|
|
|
* 显示文件夹树、菜单项和操作按钮
|
|
|
|
|
* 提供文件夹导航、创建、展开/收起等功能
|
|
|
|
|
* 使用自定义 LinearLayout 替代 NavigationView
|
|
|
|
|
* 文件夹树在"文件夹"菜单项位置直接展开
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
public class SidebarFragment extends Fragment {
|
|
|
|
|
@ -63,81 +63,52 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
private static final String TAG = "SidebarFragment";
|
|
|
|
|
private static final int MAX_FOLDER_NAME_LENGTH = 50;
|
|
|
|
|
|
|
|
|
|
// ViewBinding
|
|
|
|
|
private SidebarLayoutBinding binding;
|
|
|
|
|
|
|
|
|
|
// 适配器和数据
|
|
|
|
|
private FolderTreeAdapter adapter;
|
|
|
|
|
// 菜单项
|
|
|
|
|
private LinearLayout menuAllNotes;
|
|
|
|
|
private LinearLayout menuTrash;
|
|
|
|
|
private LinearLayout menuFolders;
|
|
|
|
|
private LinearLayout menuSyncSettings;
|
|
|
|
|
private LinearLayout menuTemplates;
|
|
|
|
|
private LinearLayout menuExport;
|
|
|
|
|
private LinearLayout menuSettings;
|
|
|
|
|
private LinearLayout menuLogin;
|
|
|
|
|
private LinearLayout menuLogout;
|
|
|
|
|
|
|
|
|
|
// 文件夹树
|
|
|
|
|
private LinearLayout folderTreeContainer;
|
|
|
|
|
private RecyclerView rvFolderTree;
|
|
|
|
|
private ImageButton btnCreateFolder;
|
|
|
|
|
private ImageView ivFolderExpand;
|
|
|
|
|
|
|
|
|
|
// 头部
|
|
|
|
|
private LinearLayout headerNotLoggedIn;
|
|
|
|
|
private LinearLayout headerLoggedIn;
|
|
|
|
|
private View btnLoginPrompt;
|
|
|
|
|
private TextView tvUsername;
|
|
|
|
|
private TextView tvDeviceId;
|
|
|
|
|
|
|
|
|
|
// ViewModel
|
|
|
|
|
private FolderListViewModel viewModel;
|
|
|
|
|
|
|
|
|
|
// 单击和双击检测
|
|
|
|
|
private long lastClickTime = 0;
|
|
|
|
|
private View lastClickedView = null;
|
|
|
|
|
private static final long DOUBLE_CLICK_INTERVAL = 300; // 毫秒
|
|
|
|
|
private FolderTreeAdapter adapter;
|
|
|
|
|
|
|
|
|
|
// 回调接口
|
|
|
|
|
private OnSidebarItemSelectedListener listener;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 侧栏项选择回调接口
|
|
|
|
|
*/
|
|
|
|
|
// 状态
|
|
|
|
|
private boolean isFolderTreeExpanded = false;
|
|
|
|
|
|
|
|
|
|
public interface OnSidebarItemSelectedListener {
|
|
|
|
|
/**
|
|
|
|
|
* 跳转到指定文件夹
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
void onFolderSelected(long folderId);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 打开回收站
|
|
|
|
|
*/
|
|
|
|
|
void onTrashSelected();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 同步
|
|
|
|
|
*/
|
|
|
|
|
void onSyncSelected();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 登录
|
|
|
|
|
*/
|
|
|
|
|
void onLoginSelected();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 导出
|
|
|
|
|
*/
|
|
|
|
|
void onLogoutSelected();
|
|
|
|
|
void onExportSelected();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 模板
|
|
|
|
|
*/
|
|
|
|
|
void onTemplateSelected();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置
|
|
|
|
|
*/
|
|
|
|
|
void onSettingsSelected();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建文件夹
|
|
|
|
|
*/
|
|
|
|
|
void onCreateFolder();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 关闭侧栏
|
|
|
|
|
*/
|
|
|
|
|
void onCloseSidebar();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重命名文件夹
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
void onRenameFolder(long folderId);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除文件夹
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
void onDeleteFolder(long folderId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -160,137 +131,146 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
@Nullable
|
|
|
|
|
@Override
|
|
|
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
|
|
|
|
@Nullable Bundle savedInstanceState) {
|
|
|
|
|
binding = SidebarLayoutBinding.inflate(inflater, container, false);
|
|
|
|
|
return binding.getRoot();
|
|
|
|
|
@Nullable Bundle savedInstanceState) {
|
|
|
|
|
return inflater.inflate(R.layout.fragment_sidebar, container, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
|
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
|
initViews();
|
|
|
|
|
setupListeners();
|
|
|
|
|
initViews(view);
|
|
|
|
|
initListeners();
|
|
|
|
|
initFolderTree();
|
|
|
|
|
observeViewModel();
|
|
|
|
|
updateUserState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onDestroyView() {
|
|
|
|
|
super.onDestroyView();
|
|
|
|
|
binding = null;
|
|
|
|
|
public void onResume() {
|
|
|
|
|
super.onResume();
|
|
|
|
|
updateUserState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 刷新文件夹树(供外部调用,如删除笔记后)
|
|
|
|
|
*/
|
|
|
|
|
public void refreshFolderTree() {
|
|
|
|
|
if (viewModel != null) {
|
|
|
|
|
viewModel.loadFolderTree();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private void initViews(View view) {
|
|
|
|
|
// 头部
|
|
|
|
|
headerNotLoggedIn = view.findViewById(R.id.header_not_logged_in);
|
|
|
|
|
headerLoggedIn = view.findViewById(R.id.header_logged_in);
|
|
|
|
|
btnLoginPrompt = view.findViewById(R.id.btn_login_prompt);
|
|
|
|
|
tvUsername = view.findViewById(R.id.tv_username);
|
|
|
|
|
tvDeviceId = view.findViewById(R.id.tv_device_id);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化视图
|
|
|
|
|
*/
|
|
|
|
|
private void initViews() {
|
|
|
|
|
// 设置RecyclerView
|
|
|
|
|
binding.rvFolderTree.setLayoutManager(new LinearLayoutManager(requireContext()));
|
|
|
|
|
adapter = new FolderTreeAdapter(new ArrayList<>(), viewModel);
|
|
|
|
|
adapter.setOnFolderItemClickListener(this::handleFolderItemClick);
|
|
|
|
|
adapter.setOnFolderItemLongClickListener(this::handleFolderItemLongClick);
|
|
|
|
|
binding.rvFolderTree.setAdapter(adapter);
|
|
|
|
|
// 菜单项
|
|
|
|
|
menuAllNotes = view.findViewById(R.id.menu_all_notes);
|
|
|
|
|
menuTrash = view.findViewById(R.id.menu_trash);
|
|
|
|
|
menuFolders = view.findViewById(R.id.menu_folders);
|
|
|
|
|
menuSyncSettings = view.findViewById(R.id.menu_sync_settings);
|
|
|
|
|
menuTemplates = view.findViewById(R.id.menu_templates);
|
|
|
|
|
menuExport = view.findViewById(R.id.menu_export);
|
|
|
|
|
menuSettings = view.findViewById(R.id.menu_settings);
|
|
|
|
|
menuLogin = view.findViewById(R.id.menu_login);
|
|
|
|
|
menuLogout = view.findViewById(R.id.menu_logout);
|
|
|
|
|
|
|
|
|
|
// 文件夹树
|
|
|
|
|
folderTreeContainer = view.findViewById(R.id.folder_tree_container);
|
|
|
|
|
rvFolderTree = view.findViewById(R.id.rv_folder_tree);
|
|
|
|
|
btnCreateFolder = view.findViewById(R.id.btn_create_folder);
|
|
|
|
|
ivFolderExpand = view.findViewById(R.id.iv_folder_expand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置监听器
|
|
|
|
|
*/
|
|
|
|
|
private void setupListeners() {
|
|
|
|
|
// 根文件夹(单击展开/收起,双击跳转)
|
|
|
|
|
setupFolderClickListener(binding.tvRootFolder, Notes.ID_ROOT_FOLDER);
|
|
|
|
|
private void initListeners() {
|
|
|
|
|
if (headerNotLoggedIn != null) {
|
|
|
|
|
headerNotLoggedIn.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) listener.onLoginSelected();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (headerLoggedIn != null) {
|
|
|
|
|
headerLoggedIn.setOnClickListener(v -> {
|
|
|
|
|
Log.d(TAG, "Logged in header clicked");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 关闭侧栏
|
|
|
|
|
binding.btnCloseSidebar.setOnClickListener(v -> {
|
|
|
|
|
// 菜单项点击
|
|
|
|
|
menuAllNotes.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onFolderSelected(Notes.ID_ROOT_FOLDER);
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 创建文件夹
|
|
|
|
|
binding.btnCreateFolder.setOnClickListener(v -> showCreateFolderDialog());
|
|
|
|
|
|
|
|
|
|
// 菜单项
|
|
|
|
|
binding.menuSync.setOnClickListener(v -> {
|
|
|
|
|
menuTrash.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onSyncSelected();
|
|
|
|
|
listener.onTrashSelected();
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
binding.menuLogin.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onLoginSelected();
|
|
|
|
|
}
|
|
|
|
|
menuFolders.setOnClickListener(v -> toggleFolderTree());
|
|
|
|
|
|
|
|
|
|
menuSyncSettings.setOnClickListener(v -> {
|
|
|
|
|
Intent intent = new Intent(requireContext(), SyncActivity.class);
|
|
|
|
|
startActivity(intent);
|
|
|
|
|
if (listener != null) listener.onCloseSidebar();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
binding.menuExport.setOnClickListener(v -> {
|
|
|
|
|
menuTemplates.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onExportSelected();
|
|
|
|
|
listener.onTemplateSelected();
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
binding.menuTemplates.setOnClickListener(v -> {
|
|
|
|
|
menuExport.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onTemplateSelected();
|
|
|
|
|
listener.onExportSelected();
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
binding.menuSettings.setOnClickListener(v -> {
|
|
|
|
|
menuSettings.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onSettingsSelected();
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
binding.menuTrash.setOnClickListener(v -> {
|
|
|
|
|
menuLogin.setOnClickListener(v -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onTrashSelected();
|
|
|
|
|
listener.onLoginSelected();
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
menuLogout.setOnClickListener(v -> showLogoutConfirmDialog());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置文件夹的单击/双击监听器
|
|
|
|
|
*/
|
|
|
|
|
private void setupFolderClickListener(View view, long folderId) {
|
|
|
|
|
view.setOnClickListener(v -> {
|
|
|
|
|
android.util.Log.d(TAG, "setupFolderClickListener: folderId=" + folderId);
|
|
|
|
|
long currentTime = System.currentTimeMillis();
|
|
|
|
|
if (lastClickedView == view && (currentTime - lastClickTime) < DOUBLE_CLICK_INTERVAL) {
|
|
|
|
|
android.util.Log.d(TAG, "Double click on root folder, jumping to: " + folderId);
|
|
|
|
|
// 这是双击,执行跳转
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
// 根文件夹也可以跳转(回到根)
|
|
|
|
|
listener.onFolderSelected(folderId);
|
|
|
|
|
}
|
|
|
|
|
// 重置双击状态
|
|
|
|
|
lastClickTime = 0;
|
|
|
|
|
lastClickedView = null;
|
|
|
|
|
} else {
|
|
|
|
|
android.util.Log.d(TAG, "Single click on root folder, will toggle expand in " + DOUBLE_CLICK_INTERVAL + "ms");
|
|
|
|
|
// 可能是单击,延迟处理
|
|
|
|
|
lastClickTime = currentTime;
|
|
|
|
|
lastClickedView = view;
|
|
|
|
|
view.postDelayed(() -> {
|
|
|
|
|
// 如果在延迟期间没有发生双击,则执行单击操作(展开/收起)
|
|
|
|
|
if (System.currentTimeMillis() - lastClickTime >= DOUBLE_CLICK_INTERVAL) {
|
|
|
|
|
android.util.Log.d(TAG, "Toggling root folder expand");
|
|
|
|
|
toggleFolderExpand(folderId);
|
|
|
|
|
}
|
|
|
|
|
}, DOUBLE_CLICK_INTERVAL);
|
|
|
|
|
private void toggleFolderTree() {
|
|
|
|
|
isFolderTreeExpanded = !isFolderTreeExpanded;
|
|
|
|
|
folderTreeContainer.setVisibility(isFolderTreeExpanded ? View.VISIBLE : View.GONE);
|
|
|
|
|
|
|
|
|
|
// 旋转展开图标
|
|
|
|
|
if (ivFolderExpand != null) {
|
|
|
|
|
ivFolderExpand.setRotation(isFolderTreeExpanded ? 180 : 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initFolderTree() {
|
|
|
|
|
rvFolderTree.setLayoutManager(new LinearLayoutManager(requireContext()));
|
|
|
|
|
adapter = new FolderTreeAdapter(new ArrayList<>(), viewModel);
|
|
|
|
|
adapter.setOnFolderItemClickListener(folderId -> {
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onFolderSelected(folderId);
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
adapter.setOnFolderItemLongClickListener(this::handleFolderItemLongClick);
|
|
|
|
|
rvFolderTree.setAdapter(adapter);
|
|
|
|
|
|
|
|
|
|
if (btnCreateFolder != null) {
|
|
|
|
|
btnCreateFolder.setOnClickListener(v -> showCreateFolderDialog());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 观察ViewModel数据变化
|
|
|
|
|
*/
|
|
|
|
|
private void observeViewModel() {
|
|
|
|
|
viewModel.getFolderTree().observe(getViewLifecycleOwner(), folderItems -> {
|
|
|
|
|
if (folderItems != null) {
|
|
|
|
|
@ -298,67 +278,73 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
adapter.notifyDataSetChanged();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
viewModel.loadFolderTree();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 切换文件夹展开/收起状态
|
|
|
|
|
*/
|
|
|
|
|
private void toggleFolderExpand(long folderId) {
|
|
|
|
|
android.util.Log.d(TAG, "toggleFolderExpand: folderId=" + folderId);
|
|
|
|
|
viewModel.toggleFolderExpand(folderId);
|
|
|
|
|
}
|
|
|
|
|
public void updateUserState() {
|
|
|
|
|
UserAuthManager authManager = UserAuthManager.getInstance(requireContext());
|
|
|
|
|
boolean isLoggedIn = authManager.isLoggedIn();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理文件夹项点击(单击/双击)
|
|
|
|
|
*/
|
|
|
|
|
private void handleFolderItemClick(long folderId) {
|
|
|
|
|
android.util.Log.d(TAG, "handleFolderItemClick: folderId=" + folderId);
|
|
|
|
|
long currentTime = System.currentTimeMillis();
|
|
|
|
|
if (lastClickedFolderId == folderId && (currentTime - lastFolderClickTime) < DOUBLE_CLICK_INTERVAL) {
|
|
|
|
|
android.util.Log.d(TAG, "Double click detected, jumping to folder: " + folderId);
|
|
|
|
|
// 这是双击,执行跳转
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onFolderSelected(folderId);
|
|
|
|
|
}
|
|
|
|
|
// 重置双击状态
|
|
|
|
|
lastFolderClickTime = 0;
|
|
|
|
|
lastClickedFolderId = -1;
|
|
|
|
|
} else {
|
|
|
|
|
android.util.Log.d(TAG, "Single click, will toggle expand in " + DOUBLE_CLICK_INTERVAL + "ms");
|
|
|
|
|
// 可能是单击,延迟处理
|
|
|
|
|
lastFolderClickTime = currentTime;
|
|
|
|
|
lastClickedFolderId = folderId;
|
|
|
|
|
new android.os.Handler().postDelayed(() -> {
|
|
|
|
|
// 如果在延迟期间没有发生双击,则执行单击操作(展开/收起)
|
|
|
|
|
if (System.currentTimeMillis() - lastFolderClickTime >= DOUBLE_CLICK_INTERVAL) {
|
|
|
|
|
android.util.Log.d(TAG, "Toggling folder expand: " + folderId);
|
|
|
|
|
toggleFolderExpand(folderId);
|
|
|
|
|
// 更新头部
|
|
|
|
|
if (headerNotLoggedIn != null && headerLoggedIn != null) {
|
|
|
|
|
if (isLoggedIn) {
|
|
|
|
|
headerNotLoggedIn.setVisibility(View.GONE);
|
|
|
|
|
headerLoggedIn.setVisibility(View.VISIBLE);
|
|
|
|
|
|
|
|
|
|
String username = authManager.getUsername();
|
|
|
|
|
String deviceId = authManager.getDeviceId();
|
|
|
|
|
|
|
|
|
|
if (tvUsername != null) {
|
|
|
|
|
tvUsername.setText(username != null ? username : getString(R.string.drawer_default_username));
|
|
|
|
|
}
|
|
|
|
|
if (tvDeviceId != null) {
|
|
|
|
|
tvDeviceId.setText(deviceId != null ? "Device: " + deviceId.substring(0, Math.min(8, deviceId.length()))
|
|
|
|
|
: getString(R.string.drawer_default_device_id));
|
|
|
|
|
}
|
|
|
|
|
}, DOUBLE_CLICK_INTERVAL);
|
|
|
|
|
} else {
|
|
|
|
|
headerNotLoggedIn.setVisibility(View.VISIBLE);
|
|
|
|
|
headerLoggedIn.setVisibility(View.GONE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新菜单项
|
|
|
|
|
if (menuLogin != null && menuLogout != null) {
|
|
|
|
|
menuLogin.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
|
|
|
|
|
menuLogout.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理文件夹项长按
|
|
|
|
|
*/
|
|
|
|
|
private void handleFolderItemLongClick(long folderId) {
|
|
|
|
|
android.util.Log.d(TAG, "handleFolderItemLongClick: folderId=" + folderId);
|
|
|
|
|
// 检查是否是系统文件夹(根文件夹、回收站等不允许重命名/删除)
|
|
|
|
|
if (folderId <= 0) {
|
|
|
|
|
android.util.Log.d(TAG, "System folder, ignoring long press");
|
|
|
|
|
return;
|
|
|
|
|
private void showLogoutConfirmDialog() {
|
|
|
|
|
new AlertDialog.Builder(requireContext())
|
|
|
|
|
.setTitle(R.string.dialog_logout_title)
|
|
|
|
|
.setMessage(R.string.dialog_logout_message)
|
|
|
|
|
.setPositiveButton(R.string.dialog_logout_confirm, (dialog, which) -> performLogout())
|
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void performLogout() {
|
|
|
|
|
UserAuthManager authManager = UserAuthManager.getInstance(requireContext());
|
|
|
|
|
authManager.logout();
|
|
|
|
|
|
|
|
|
|
// 重置同步状态,清除上次同步时间
|
|
|
|
|
SyncManager.getInstance().resetSyncState();
|
|
|
|
|
|
|
|
|
|
updateUserState();
|
|
|
|
|
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onLogoutSelected();
|
|
|
|
|
listener.onCloseSidebar();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showFolderContextMenu(folderId);
|
|
|
|
|
Toast.makeText(requireContext(), R.string.toast_logout_success, Toast.LENGTH_SHORT).show();
|
|
|
|
|
Log.d(TAG, "User logged out successfully");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 显示文件夹上下文菜单
|
|
|
|
|
*/
|
|
|
|
|
private void showFolderContextMenu(long folderId) {
|
|
|
|
|
PopupMenu popup = new PopupMenu(requireContext(), binding.getRoot());
|
|
|
|
|
private void handleFolderItemLongClick(long folderId) {
|
|
|
|
|
if (folderId <= 0) return;
|
|
|
|
|
|
|
|
|
|
PopupMenu popup = new PopupMenu(requireContext(), rvFolderTree);
|
|
|
|
|
popup.getMenuInflater().inflate(R.menu.folder_context_menu, popup.getMenu());
|
|
|
|
|
|
|
|
|
|
popup.setOnMenuItemClickListener(item -> {
|
|
|
|
|
@ -370,8 +356,7 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
listener.onDeleteFolder(folderId);
|
|
|
|
|
return true;
|
|
|
|
|
} else if (itemId == R.id.action_move) {
|
|
|
|
|
// TODO: 实现移动功能(阶段3)
|
|
|
|
|
Toast.makeText(requireContext(), "移动功能待实现", Toast.LENGTH_SHORT).show();
|
|
|
|
|
Toast.makeText(requireContext(), "移动功能开发中", Toast.LENGTH_SHORT).show();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
@ -380,14 +365,7 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
popup.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 双击检测专用变量(针对文件夹列表项)
|
|
|
|
|
private long lastFolderClickTime = 0;
|
|
|
|
|
private long lastClickedFolderId = -1;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 显示创建文件夹对话框
|
|
|
|
|
*/
|
|
|
|
|
private void showCreateFolderDialog() {
|
|
|
|
|
public void showCreateFolderDialog() {
|
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
|
|
|
|
|
builder.setTitle(R.string.dialog_create_folder_title);
|
|
|
|
|
|
|
|
|
|
@ -403,180 +381,153 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
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<Long>() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onSuccess(Long folderId) {
|
|
|
|
|
if (getActivity() != null) {
|
|
|
|
|
getActivity().runOnUiThread(() -> {
|
|
|
|
|
Toast.makeText(requireContext(), R.string.create_folder_success, Toast.LENGTH_SHORT).show();
|
|
|
|
|
// 刷新文件夹列表
|
|
|
|
|
viewModel.loadFolderTree();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onError(Exception error) {
|
|
|
|
|
if (getActivity() != null) {
|
|
|
|
|
getActivity().runOnUiThread(() -> {
|
|
|
|
|
Toast.makeText(requireContext(),
|
|
|
|
|
getString(R.string.error_folder_name_too_long) + ": " + error.getMessage(),
|
|
|
|
|
Toast.LENGTH_SHORT).show();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
createFolder(folderName);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
builder.setNegativeButton(android.R.string.cancel, null);
|
|
|
|
|
builder.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* FolderTreeAdapter
|
|
|
|
|
* 文件夹树适配器,支持层级显示和展开/收起
|
|
|
|
|
*/
|
|
|
|
|
private static class FolderTreeAdapter extends RecyclerView.Adapter<FolderTreeAdapter.FolderViewHolder> {
|
|
|
|
|
|
|
|
|
|
private List<FolderTreeItem> folderItems;
|
|
|
|
|
private FolderListViewModel viewModel;
|
|
|
|
|
private OnFolderItemClickListener folderItemClickListener;
|
|
|
|
|
private OnFolderItemLongClickListener folderItemLongClickListener;
|
|
|
|
|
|
|
|
|
|
public FolderTreeAdapter(List<FolderTreeItem> folderItems, FolderListViewModel viewModel) {
|
|
|
|
|
this.folderItems = folderItems;
|
|
|
|
|
this.viewModel = viewModel;
|
|
|
|
|
private void createFolder(String folderName) {
|
|
|
|
|
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<Long>() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onSuccess(Long folderId) {
|
|
|
|
|
if (getActivity() != null) {
|
|
|
|
|
getActivity().runOnUiThread(() -> {
|
|
|
|
|
Toast.makeText(requireContext(), R.string.create_folder_success, Toast.LENGTH_SHORT).show();
|
|
|
|
|
viewModel.loadFolderTree();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setData(List<FolderTreeItem> folderItems) {
|
|
|
|
|
this.folderItems = folderItems;
|
|
|
|
|
@Override
|
|
|
|
|
public void onError(Exception error) {
|
|
|
|
|
if (getActivity() != null) {
|
|
|
|
|
getActivity().runOnUiThread(() -> {
|
|
|
|
|
Toast.makeText(requireContext(),
|
|
|
|
|
getString(R.string.error_create_folder) + ": " + error.getMessage(),
|
|
|
|
|
Toast.LENGTH_SHORT).show();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setOnFolderItemClickListener(OnFolderItemClickListener listener) {
|
|
|
|
|
this.folderItemClickListener = listener;
|
|
|
|
|
}
|
|
|
|
|
public void refreshFolderTree() {
|
|
|
|
|
if (viewModel != null) viewModel.loadFolderTree();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setOnFolderItemLongClickListener(OnFolderItemLongClickListener listener) {
|
|
|
|
|
this.folderItemLongClickListener = listener;
|
|
|
|
|
}
|
|
|
|
|
@Override
|
|
|
|
|
public void onDetach() {
|
|
|
|
|
super.onDetach();
|
|
|
|
|
listener = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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, folderItemClickListener, folderItemLongClickListener);
|
|
|
|
|
}
|
|
|
|
|
// ==================== FolderTreeAdapter ====================
|
|
|
|
|
|
|
|
|
|
@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);
|
|
|
|
|
}
|
|
|
|
|
private static class FolderTreeAdapter extends RecyclerView.Adapter<FolderTreeAdapter.FolderViewHolder> {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getItemCount() {
|
|
|
|
|
return folderItems.size();
|
|
|
|
|
}
|
|
|
|
|
private List<FolderTreeItem> folderItems;
|
|
|
|
|
private FolderListViewModel viewModel;
|
|
|
|
|
private OnFolderItemClickListener folderItemClickListener;
|
|
|
|
|
private OnFolderItemLongClickListener folderItemLongClickListener;
|
|
|
|
|
|
|
|
|
|
static class FolderViewHolder extends RecyclerView.ViewHolder {
|
|
|
|
|
private View indentView;
|
|
|
|
|
private ImageView ivExpandIcon;
|
|
|
|
|
private ImageView ivFolderIcon;
|
|
|
|
|
private TextView tvFolderName;
|
|
|
|
|
private TextView tvNoteCount;
|
|
|
|
|
private FolderTreeItem currentItem;
|
|
|
|
|
private final OnFolderItemClickListener folderItemClickListener;
|
|
|
|
|
private final OnFolderItemLongClickListener folderItemLongClickListener;
|
|
|
|
|
|
|
|
|
|
public FolderViewHolder(@NonNull View itemView, OnFolderItemClickListener clickListener,
|
|
|
|
|
OnFolderItemLongClickListener longClickListener) {
|
|
|
|
|
super(itemView);
|
|
|
|
|
this.folderItemClickListener = clickListener;
|
|
|
|
|
this.folderItemLongClickListener = longClickListener;
|
|
|
|
|
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 FolderTreeAdapter(List<FolderTreeItem> folderItems, FolderListViewModel viewModel) {
|
|
|
|
|
this.folderItems = folderItems;
|
|
|
|
|
this.viewModel = viewModel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void bind(FolderTreeItem item, boolean isExpanded) {
|
|
|
|
|
this.currentItem = item;
|
|
|
|
|
public void setData(List<FolderTreeItem> folderItems) {
|
|
|
|
|
this.folderItems = folderItems;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置缩进
|
|
|
|
|
int indent = item.level * 32;
|
|
|
|
|
indentView.setLayoutParams(new LinearLayout.LayoutParams(indent, LinearLayout.LayoutParams.MATCH_PARENT));
|
|
|
|
|
public void setOnFolderItemClickListener(OnFolderItemClickListener listener) {
|
|
|
|
|
this.folderItemClickListener = listener;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置展开/收起图标
|
|
|
|
|
if (item.hasChildren) {
|
|
|
|
|
ivExpandIcon.setVisibility(View.VISIBLE);
|
|
|
|
|
ivExpandIcon.setRotation(isExpanded ? 90 : 0);
|
|
|
|
|
} else {
|
|
|
|
|
ivExpandIcon.setVisibility(View.INVISIBLE);
|
|
|
|
|
public void setOnFolderItemLongClickListener(OnFolderItemLongClickListener listener) {
|
|
|
|
|
this.folderItemLongClickListener = listener;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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, folderItemClickListener, folderItemLongClickListener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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 View ivFolderIcon;
|
|
|
|
|
private TextView tvFolderName;
|
|
|
|
|
private TextView tvNoteCount;
|
|
|
|
|
private FolderTreeItem currentItem;
|
|
|
|
|
|
|
|
|
|
public FolderViewHolder(@NonNull View itemView, OnFolderItemClickListener clickListener,
|
|
|
|
|
OnFolderItemLongClickListener longClickListener) {
|
|
|
|
|
super(itemView);
|
|
|
|
|
indentView = itemView.findViewById(R.id.indent_view);
|
|
|
|
|
ivFolderIcon = itemView.findViewById(R.id.iv_folder_icon);
|
|
|
|
|
tvFolderName = itemView.findViewById(R.id.tv_folder_name);
|
|
|
|
|
tvNoteCount = itemView.findViewById(R.id.tv_note_count);
|
|
|
|
|
|
|
|
|
|
itemView.setOnClickListener(v -> {
|
|
|
|
|
if (clickListener != null && currentItem != null) {
|
|
|
|
|
clickListener.onFolderClick(currentItem.folderId);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 设置文件夹名称
|
|
|
|
|
tvFolderName.setText(item.name);
|
|
|
|
|
itemView.setOnLongClickListener(v -> {
|
|
|
|
|
if (longClickListener != null && currentItem != null) {
|
|
|
|
|
longClickListener.onFolderLongClick(currentItem.folderId);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置便签数量
|
|
|
|
|
tvNoteCount.setText(String.format(itemView.getContext()
|
|
|
|
|
.getString(R.string.folder_note_count), item.noteCount));
|
|
|
|
|
public void bind(FolderTreeItem item, boolean isExpanded) {
|
|
|
|
|
this.currentItem = item;
|
|
|
|
|
|
|
|
|
|
// 设置点击监听器
|
|
|
|
|
itemView.setOnClickListener(v -> {
|
|
|
|
|
if (folderItemClickListener != null) {
|
|
|
|
|
folderItemClickListener.onFolderClick(item.folderId);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
int indent = item.level * 32;
|
|
|
|
|
indentView.setLayoutParams(new LinearLayout.LayoutParams(indent, LinearLayout.LayoutParams.MATCH_PARENT));
|
|
|
|
|
|
|
|
|
|
// 设置长按监听器
|
|
|
|
|
itemView.setOnLongClickListener(v -> {
|
|
|
|
|
if (folderItemLongClickListener != null) {
|
|
|
|
|
folderItemLongClickListener.onFolderLongClick(item.folderId);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
tvFolderName.setText(item.name);
|
|
|
|
|
tvNoteCount.setText(String.format(itemView.getContext()
|
|
|
|
|
.getString(R.string.folder_note_count), item.noteCount));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 文件夹项点击监听器接口
|
|
|
|
|
*/
|
|
|
|
|
public interface OnFolderItemClickListener {
|
|
|
|
|
void onFolderClick(long folderId);
|
|
|
|
|
}
|
|
|
|
|
public interface OnFolderItemClickListener {
|
|
|
|
|
void onFolderClick(long folderId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 文件夹项长按监听器接口
|
|
|
|
|
*/
|
|
|
|
|
public interface OnFolderItemLongClickListener {
|
|
|
|
|
void onFolderLongClick(long folderId);
|
|
|
|
|
}
|
|
|
|
|
public interface OnFolderItemLongClickListener {
|
|
|
|
|
void onFolderLongClick(long folderId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* FolderTreeItem
|
|
|
|
|
* 文件夹树项数据模型
|
|
|
|
|
*/
|
|
|
|
|
public static class FolderTreeItem {
|
|
|
|
|
public long folderId;
|
|
|
|
|
public String name;
|
|
|
|
|
public int level; // 层级,0表示顶级
|
|
|
|
|
public int level;
|
|
|
|
|
public boolean hasChildren;
|
|
|
|
|
public int noteCount;
|
|
|
|
|
|
|
|
|
|
@ -588,10 +539,4 @@ public class SidebarFragment extends Fragment {
|
|
|
|
|
this.noteCount = noteCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onDetach() {
|
|
|
|
|
super.onDetach();
|
|
|
|
|
listener = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|