/* * 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); adapter.setOnFolderItemClickListener(this::handleFolderItemClick); 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 -> { 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); } }); } /** * 观察ViewModel数据变化 */ private void observeViewModel() { viewModel.getFolderTree().observe(getViewLifecycleOwner(), folderItems -> { if (folderItems != null) { adapter.setData(folderItems); adapter.notifyDataSetChanged(); } }); viewModel.loadFolderTree(); } /** * 切换文件夹展开/收起状态 */ private void toggleFolderExpand(long folderId) { android.util.Log.d(TAG, "toggleFolderExpand: folderId=" + folderId); viewModel.toggleFolderExpand(folderId); } /** * 处理文件夹项点击(单击/双击) */ 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); } }, DOUBLE_CLICK_INTERVAL); } } // 双击检测专用变量(针对文件夹列表项) private long lastFolderClickTime = 0; private long lastClickedFolderId = -1; /** * 显示创建文件夹对话框 */ 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