From 02847cb9f890f83f8e4bd1574abc0bb6adf29d5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?=
Date: Wed, 28 Jan 2026 18:45:03 +0800
Subject: [PATCH] =?UTF-8?q?ui=E7=95=8C=E9=9D=A2=E7=8E=B0=E4=BB=A3=E5=8C=96?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A5=E5=8F=8A=E5=8A=9F=E8=83=BD=E7=9A=84?=
=?UTF-8?q?=E8=BF=81=E7=A7=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../app/src/main/AndroidManifest.xml | 40 +
.../java/net/micode/notes/data/Notes.java | 10 +
.../micode/notes/data/NotesRepository.java | 223 ++-
.../net/micode/notes/ui/NoteInfoAdapter.java | 385 ++---
.../micode/notes/ui/NotesListActivity.java | 1264 ++++-------------
.../net/micode/notes/ui/SettingsFragment.java | 118 ++
.../net/micode/notes/ui/SidebarFragment.java | 7 +-
.../net/micode/notes/ui/TaskListActivity.java | 22 +-
.../notes/viewmodel/NotesListViewModel.java | 127 +-
.../src/main/res/layout/activity_settings.xml | 16 +-
.../main/res/layout/activity_task_list.xml | 39 +-
.../app/src/main/res/layout/note_item.xml | 202 +--
.../app/src/main/res/layout/note_list.xml | 133 +-
.../src/main/res/layout/sidebar_layout.xml | 26 +-
.../src/main/res/layout/task_list_item.xml | 122 +-
.../app/src/main/res/values/colors.xml | 22 +-
.../app/src/main/res/values/strings.xml | 9 +
.../app/src/main/res/values/styles.xml | 9 +
.../app/src/main/res/xml/preferences.xml | 71 +-
19 files changed, 1204 insertions(+), 1641 deletions(-)
diff --git a/src/Notesmaster/app/src/main/AndroidManifest.xml b/src/Notesmaster/app/src/main/AndroidManifest.xml
index a08a1f3..a29be50 100644
--- a/src/Notesmaster/app/src/main/AndroidManifest.xml
+++ b/src/Notesmaster/app/src/main/AndroidManifest.xml
@@ -197,6 +197,13 @@
android:theme="@style/Theme.Notesmaster" >
+
+
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
> callback) {
executor.execute(() -> {
try {
- List notes = queryNotes(folderId);
+ // Modified to only return notes (no folders) as per new UI requirement
+ List notes = queryNotesOnly(folderId);
callback.onSuccess(notes);
Log.d(TAG, "Successfully loaded notes for folder: " + folderId);
} catch (Exception e) {
@@ -214,10 +233,108 @@ public class NotesRepository {
}
/**
- * 查询笔记列表(内部方法)
- *
- * 同时返回文件夹和便签,文件夹显示在便签之前
- *
+ * 获取指定文件夹下的子文件夹
+ *
+ * @param folderId 父文件夹ID
+ * @param callback 回调接口
+ */
+ public void getSubFolders(long folderId, Callback> callback) {
+ executor.execute(() -> {
+ try {
+ List folders = querySubFolders(folderId);
+ callback.onSuccess(folders);
+ Log.d(TAG, "Successfully loaded sub-folders for folder: " + folderId);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to load sub-folders for folder: " + folderId, e);
+ callback.onError(e);
+ }
+ });
+ }
+
+ private List querySubFolders(long folderId) {
+ List folders = new ArrayList<>();
+ String selection;
+ String[] selectionArgs;
+
+ if (folderId == Notes.ID_ROOT_FOLDER) {
+ selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + NoteColumns.PARENT_ID + "=?) OR (" +
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)";
+ selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
+ } else {
+ selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER;
+ selectionArgs = new String[]{String.valueOf(folderId)};
+ }
+
+ Cursor cursor = contentResolver.query(
+ Notes.CONTENT_NOTE_URI,
+ null,
+ selection,
+ selectionArgs,
+ NoteColumns.MODIFIED_DATE + " DESC"
+ );
+
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ folders.add(noteFromCursor(cursor));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return folders;
+ }
+
+ private List queryNotesOnly(long folderId) {
+ List normalNotes = new ArrayList<>();
+ String selection;
+ String[] selectionArgs;
+
+ if (folderId == Notes.ID_ALL_NOTES_FOLDER) {
+ // Query ALL notes (except trash and system folders)
+ // We want all notes where TYPE=NOTE and PARENT_ID != TRASH
+ selection = NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " +
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER;
+ selectionArgs = null;
+ } else if (folderId == Notes.ID_ROOT_FOLDER) {
+ selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=?)";
+ selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
+ } else {
+ selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
+ selectionArgs = new String[]{String.valueOf(folderId)};
+ }
+
+ Cursor cursor = contentResolver.query(
+ Notes.CONTENT_NOTE_URI,
+ null,
+ selection,
+ selectionArgs,
+ NoteColumns.MODIFIED_DATE + " DESC"
+ );
+
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ normalNotes.add(noteFromCursor(cursor));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ // Sort: Pinned first, then Modified Date DESC
+ normalNotes.sort((a, b) -> {
+ if (a.isPinned != b.isPinned) {
+ return a.isPinned ? -1 : 1;
+ }
+ return Long.compare(b.modifiedDate, a.modifiedDate);
+ });
+
+ return normalNotes;
+ }
+
+ /**
+ * 查询笔记列表(内部方法) - Deprecated logic kept for reference but unused by main flow now
*
* @param folderId 文件夹ID
* @return 笔记列表(包含文件夹和便签)
@@ -589,7 +706,7 @@ public class NotesRepository {
/**
* 删除笔记
*
- * 将笔记移动到回收站文件夹
+ * 将笔记移动到回收站文件夹,并保存原始位置
*
*
* @param noteId 笔记ID
@@ -598,8 +715,12 @@ public class NotesRepository {
public void deleteNote(long noteId, Callback callback) {
executor.execute(() -> {
try {
+ // 1. 获取当前父文件夹ID
+ long currentParentId = getParentFolderId(noteId);
+
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, currentParentId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@@ -607,7 +728,7 @@ public class NotesRepository {
if (rows > 0) {
callback.onSuccess(rows);
- Log.d(TAG, "Successfully moved note to trash: " + noteId);
+ Log.d(TAG, "Successfully moved note to trash: " + noteId + ", origin: " + currentParentId);
} else {
callback.onError(new RuntimeException("No note found with ID: " + noteId));
}
@@ -634,8 +755,12 @@ public class NotesRepository {
int totalRows = 0;
for (Long noteId : noteIds) {
+ // 1. 获取当前父文件夹ID
+ long currentParentId = getParentFolderId(noteId);
+
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, currentParentId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@@ -657,7 +782,7 @@ public class NotesRepository {
}
/**
- * 恢复笔记(从回收站移回根目录)
+ * 恢复笔记(从回收站移回原始位置或根目录)
*
* @param noteIds 笔记ID列表
* @param callback 回调接口
@@ -671,12 +796,40 @@ public class NotesRepository {
}
int totalRows = 0;
- // 恢复到根目录
- long targetFolderId = Notes.ID_ROOT_FOLDER;
for (Long noteId : noteIds) {
+ // 1. 查询原始父文件夹ID
+ long originParentId = Notes.ID_ROOT_FOLDER;
+ Cursor cursor = contentResolver.query(
+ ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
+ new String[]{NoteColumns.ORIGIN_PARENT_ID},
+ null,
+ null,
+ null
+ );
+
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ int idx = cursor.getColumnIndex(NoteColumns.ORIGIN_PARENT_ID);
+ if (idx != -1) {
+ originParentId = cursor.getLong(idx);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ // 如果原始位置无效(例如0或仍是回收站),则恢复到根目录
+ if (originParentId == Notes.ID_TRASH_FOLER || originParentId == 0) {
+ originParentId = Notes.ID_ROOT_FOLDER;
+ }
+
+ // 2. 恢复
ContentValues values = new ContentValues();
- values.put(NoteColumns.PARENT_ID, targetFolderId);
+ values.put(NoteColumns.PARENT_ID, originParentId);
+ values.put(NoteColumns.ORIGIN_PARENT_ID, 0); // Reset origin
values.put(NoteColumns.LOCAL_MODIFIED, 1);
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
@@ -1004,6 +1157,36 @@ public class NotesRepository {
});
}
+ /**
+ * 解锁所有笔记
+ *
+ * 将所有笔记的锁定状态重置为未锁定
+ *
+ *
+ * @param callback 回调接口
+ */
+ public void unlockAllNotes(Callback callback) {
+ executor.execute(() -> {
+ try {
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.LOCKED, 0);
+ values.put(NoteColumns.LOCAL_MODIFIED, 1);
+
+ // Update all notes where locked is 1
+ String selection = NoteColumns.LOCKED + " = ?";
+ String[] selectionArgs = new String[]{"1"};
+
+ int rows = contentResolver.update(Notes.CONTENT_NOTE_URI, values, selection, selectionArgs);
+
+ callback.onSuccess(rows);
+ Log.d(TAG, "Successfully unlocked " + rows + " notes");
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to unlock all notes", e);
+ callback.onError(e);
+ }
+ });
+ }
+
/**
* 从内容中提取摘要
*
@@ -1014,10 +1197,22 @@ public class NotesRepository {
if (content == null || content.isEmpty()) {
return "";
}
+
+ // Convert HTML to plain text to remove tags and resolve entities
+ String plainText;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ plainText = android.text.Html.fromHtml(content, android.text.Html.FROM_HTML_MODE_LEGACY).toString();
+ } else {
+ plainText = android.text.Html.fromHtml(content).toString();
+ }
+
+ // Remove extra whitespace
+ plainText = plainText.trim();
+
int maxLength = 100;
- return content.length() > maxLength
- ? content.substring(0, maxLength)
- : content;
+ return plainText.length() > maxLength
+ ? plainText.substring(0, maxLength)
+ : plainText;
}
/**
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteInfoAdapter.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteInfoAdapter.java
index 2b9c202..ad837c4 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteInfoAdapter.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteInfoAdapter.java
@@ -1,37 +1,22 @@
-/*
- * Copyright (c) 2025, Modern Notes Project
- *
- * 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.content.Context;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.cardview.widget.CardView;
+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.tool.ResourceParser;
-import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
-
-import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -40,143 +25,68 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-/**
- * 便签列表适配器
- *
- * 将 List 数据绑定到 ListView
- * 支持便签显示、选中状态、图标显示
- *
- *
- * 现代化实现:使用 ViewHolder 模式优化性能
- *
- */
-public class NoteInfoAdapter extends BaseAdapter {
- private LayoutInflater inflater;
+public class NoteInfoAdapter extends RecyclerView.Adapter {
+
+ private Context context;
private List notes;
private HashSet selectedIds;
+ private boolean isSelectionMode;
private OnNoteButtonClickListener buttonClickListener;
private OnNoteItemClickListener itemClickListener;
private OnNoteItemLongClickListener itemLongClickListener;
-
- /**
- * 便签按钮点击事件回调接口
- */
+
public interface OnNoteButtonClickListener {
- /**
- * 编辑按钮点击事件
- *
- * @param position 位置
- * @param noteId 便签 ID
- */
void onEditButtonClick(int position, long noteId);
}
- /**
- * 便签项点击事件回调接口
- */
public interface OnNoteItemClickListener {
void onNoteItemClick(int position, long noteId);
}
- /**
- * 便签项长按事件回调接口
- */
public interface OnNoteItemLongClickListener {
void onNoteItemLongClick(int position, long noteId);
}
-
- /**
- * 构造函数
- *
- * @param context 上下文
- */
+
public NoteInfoAdapter(Context context) {
- this.inflater = LayoutInflater.from(context);
+ this.context = context;
this.notes = new ArrayList<>();
this.selectedIds = new HashSet<>();
+ this.isSelectionMode = false;
}
-
- /**
- * 设置便签列表
- *
- * @param notes 便签列表
- */
+
+ public void setSelectionMode(boolean isSelectionMode) {
+ this.isSelectionMode = isSelectionMode;
+ notifyDataSetChanged();
+ }
+
public void setNotes(List notes) {
this.notes = notes != null ? notes : new ArrayList<>();
notifyDataSetChanged();
}
- /**
- * 设置选中的便签 ID 集合
- *
- * 用于多选模式同步,让 ViewModel 更新 selectedNoteIds 后,
- * Adapter 的 selectedIds 也能同步更新
- *
- *
- * @param selectedIds 选中的便签 ID 集合
- */
public void setSelectedIds(HashSet selectedIds) {
- if (selectedIds != null && selectedIds != this.selectedIds) {
- this.selectedIds.clear();
- this.selectedIds.addAll(selectedIds);
- notifyDataSetChanged();
- } else if (selectedIds == null) {
- this.selectedIds.clear();
- notifyDataSetChanged();
- }
+ this.selectedIds = selectedIds != null ? new HashSet<>(selectedIds) : new HashSet<>();
+ notifyDataSetChanged();
}
- /**
- * 设置选中的便签 ID 列表
- *
- * 重载方法,接受 List 参数,在内部转换为 HashSet
- *
- *
- * @param selectedIds 选中的便签 ID 列表
- */
- public void setSelectedIds(List selectedIds) {
- if (selectedIds != null && !selectedIds.isEmpty()) {
- this.selectedIds.clear();
- this.selectedIds.addAll(selectedIds);
- notifyDataSetChanged();
- } else {
- this.selectedIds.clear();
- notifyDataSetChanged();
- }
+ @NonNull
+ @Override
+ public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(context).inflate(R.layout.note_item, parent, false);
+ return new NoteViewHolder(view);
}
- /**
- * 获取选中的便签 ID
- *
- * @return 选中的便签 ID 集合
- */
- public HashSet getSelectedIds() {
- return selectedIds;
+ @Override
+ public void onBindViewHolder(@NonNull NoteViewHolder holder, int position) {
+ NotesRepository.NoteInfo note = notes.get(position);
+ holder.bind(note, position);
}
-
- /**
- * 切换选中状态
- *
- * @param noteId 便签 ID
- */
- public void toggleSelection(long noteId) {
- if (selectedIds.contains(noteId)) {
- selectedIds.remove(noteId);
- } else {
- selectedIds.add(noteId);
- }
- notifyDataSetChanged();
+
+ @Override
+ public int getItemCount() {
+ return notes.size();
}
- /**
- * 设置按钮点击监听器
- *
- * @param listener 监听器
- */
- public void setOnNoteButtonClickListener(OnNoteButtonClickListener listener) {
- this.buttonClickListener = listener;
- }
-
public void setOnNoteItemClickListener(OnNoteItemClickListener listener) {
this.itemClickListener = listener;
}
@@ -184,168 +94,113 @@ public class NoteInfoAdapter extends BaseAdapter {
public void setOnNoteItemLongClickListener(OnNoteItemLongClickListener listener) {
this.itemLongClickListener = listener;
}
-
- @Override
- public int getCount() {
- return notes.size();
- }
-
- @Override
- public Object getItem(int position) {
- return position >= 0 && position < notes.size() ? notes.get(position) : null;
- }
-
- @Override
- public long getItemId(int position) {
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) getItem(position);
- return note != null ? note.getId() : -1;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- Log.d("NoteInfoAdapter", "getView called, position: " + position + ", convertView: " + (convertView != null ? "REUSED" : "NEW"));
- ViewHolder holder;
-
- if (convertView == null) {
- convertView = inflater.inflate(R.layout.note_item, parent, false);
- holder = new ViewHolder();
- holder.title = convertView.findViewById(R.id.tv_title);
- holder.time = convertView.findViewById(R.id.tv_time);
- holder.typeIcon = convertView.findViewById(R.id.iv_type_icon);
- holder.checkBox = convertView.findViewById(android.R.id.checkbox);
- holder.pinnedIcon = convertView.findViewById(R.id.iv_pinned_icon);
- holder.lockIcon = convertView.findViewById(R.id.iv_lock_icon);
- convertView.setTag(holder);
-
- convertView.setOnClickListener(v -> {
- Log.d("NoteInfoAdapter", "===== onClick TRIGGERED =====");
- ViewHolder currentHolder = (ViewHolder) v.getTag();
- if (currentHolder != null && itemClickListener != null) {
- Log.d("NoteInfoAdapter", "Calling itemClickListener");
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) getItem(currentHolder.position);
- if (note != null) {
- itemClickListener.onNoteItemClick(currentHolder.position, note.getId());
+
+ class NoteViewHolder extends RecyclerView.ViewHolder {
+ CardView cardView;
+ TextView title, time, name;
+ ImageView typeIcon, lockIcon, alertIcon, pinnedIcon;
+ CheckBox checkBox;
+
+ public NoteViewHolder(View itemView) {
+ super(itemView);
+ cardView = (CardView) itemView;
+ title = itemView.findViewById(R.id.tv_title);
+ time = itemView.findViewById(R.id.tv_time);
+ name = itemView.findViewById(R.id.tv_name);
+ checkBox = itemView.findViewById(android.R.id.checkbox);
+ typeIcon = itemView.findViewById(R.id.iv_type_icon);
+ lockIcon = itemView.findViewById(R.id.iv_lock_icon);
+ alertIcon = itemView.findViewById(R.id.iv_alert_icon);
+ pinnedIcon = itemView.findViewById(R.id.iv_pinned_icon);
+
+ itemView.setOnClickListener(v -> {
+ if (itemClickListener != null) {
+ int pos = getAdapterPosition();
+ if (pos != RecyclerView.NO_POSITION) {
+ // Pass click event to Fragment, which will handle selection toggle if in selection mode
+ itemClickListener.onNoteItemClick(pos, notes.get(pos).getId());
}
}
- Log.d("NoteInfoAdapter", "===== onClick END =====");
});
-
- convertView.setOnLongClickListener(v -> {
- Log.d("NoteInfoAdapter", "===== setOnLongClickListener TRIGGERED =====");
- Log.d("NoteInfoAdapter", "Event triggered on view: " + v.getClass().getSimpleName());
- ViewHolder currentHolder = (ViewHolder) v.getTag();
- if (currentHolder != null && itemLongClickListener != null) {
- Log.d("NoteInfoAdapter", "Calling itemLongClickListener");
- itemLongClickListener.onNoteItemLongClick(currentHolder.position, currentHolder.position < notes.size() ? notes.get(currentHolder.position).getId() : -1);
- } else {
- Log.e("NoteInfoAdapter", "itemLongClickListener is NULL!");
+
+ itemView.setOnLongClickListener(v -> {
+ if (itemLongClickListener != null) {
+ int pos = getAdapterPosition();
+ if (pos != RecyclerView.NO_POSITION) {
+ itemLongClickListener.onNoteItemLongClick(pos, notes.get(pos).getId());
+ return true;
+ }
}
- Log.d("NoteInfoAdapter", "===== setOnLongClickListener END =====");
- return true;
+ return false;
});
- } else {
- holder = (ViewHolder) convertView.getTag();
}
-
- holder.position = position;
-
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) getItem(position);
- if (note != null) {
- String title = note.title;
- if (title == null || title.trim().isEmpty()) {
- title = "无标题";
- }
- holder.title.setText(title);
+
+ public void bind(NotesRepository.NoteInfo note, int position) {
+ String titleStr = note.title;
+ String snippet = note.snippet;
- // 设置类型图标和时间显示
- if (note.type == Notes.TYPE_FOLDER) {
- // 文件夹
- holder.typeIcon.setVisibility(View.VISIBLE);
- holder.typeIcon.setImageResource(R.drawable.ic_folder);
- // 文件夹不显示时间
- holder.time.setVisibility(View.GONE);
- } else {
- // 便签
- holder.typeIcon.setVisibility(View.GONE);
- holder.time.setVisibility(View.VISIBLE);
- holder.time.setText(formatDate(note.modifiedDate));
+ if (titleStr == null || titleStr.trim().isEmpty()) {
+ titleStr = snippet;
+ if (titleStr != null && titleStr.contains("\n")) {
+ titleStr = titleStr.substring(0, titleStr.indexOf("\n"));
+ }
+ if (titleStr == null || titleStr.trim().isEmpty()) {
+ titleStr = "无标题";
+ }
}
- int bgResId;
- int totalCount = getCount();
- int bgColorId = note.bgColorId;
-
- if (totalCount == 1) {
- bgResId = NoteItemBgResources.getNoteBgSingleRes(bgColorId);
- } else if (position == 0) {
- bgResId = NoteItemBgResources.getNoteBgFirstRes(bgColorId);
- } else if (position == totalCount - 1) {
- bgResId = NoteItemBgResources.getNoteBgLastRes(bgColorId);
- } else {
- bgResId = NoteItemBgResources.getNoteBgNormalRes(bgColorId);
+ // Bind Summary
+ TextView summaryView = itemView.findViewById(R.id.tv_summary);
+ if (summaryView != null) {
+ // Clean up snippet for display (in case it contains HTML or entities)
+ String cleanSnippet = snippet;
+ if (cleanSnippet != null && !cleanSnippet.isEmpty()) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ cleanSnippet = android.text.Html.fromHtml(cleanSnippet, android.text.Html.FROM_HTML_MODE_LEGACY).toString();
+ } else {
+ cleanSnippet = android.text.Html.fromHtml(cleanSnippet).toString();
+ }
+ cleanSnippet = cleanSnippet.trim();
+ }
+ summaryView.setText(cleanSnippet);
}
-
- convertView.setBackgroundResource(bgResId);
- if (selectedIds.contains(note.getId())) {
- convertView.setActivated(true);
+ if (note.type == Notes.TYPE_FOLDER) {
+ title.setText(note.getSnippet() + " (" + note.getNotesCount() + ")");
+ time.setVisibility(View.GONE);
+ typeIcon.setVisibility(View.VISIBLE);
+ typeIcon.setImageResource(R.drawable.ic_folder);
+ cardView.setCardBackgroundColor(context.getColor(R.color.bg_white));
} else {
- convertView.setActivated(false);
+ typeIcon.setVisibility(View.GONE);
+ time.setVisibility(View.VISIBLE);
+ title.setText(titleStr);
+ time.setText(formatDate(note.modifiedDate));
+
+ // Color Logic
+ int bgColorId = note.bgColorId;
+ int color = ResourceParser.getNoteBgColor(context, bgColorId);
+ cardView.setCardBackgroundColor(color);
}
- Log.d("NoteInfoAdapter", "===== Setting checkbox visibility =====");
- Log.d("NoteInfoAdapter", "selectedIds.isEmpty(): " + selectedIds.isEmpty());
- Log.d("NoteInfoAdapter", "selectedIds.size(): " + selectedIds.size());
- Log.d("NoteInfoAdapter", "selectedIds contains note " + note.getId() + ": " + selectedIds.contains(note.getId()));
-
- if (!selectedIds.isEmpty()) {
- Log.d("NoteInfoAdapter", "Setting checkbox VISIBLE");
- holder.checkBox.setVisibility(View.VISIBLE);
- holder.checkBox.setChecked(selectedIds.contains(note.getId()));
- holder.checkBox.setClickable(false);
+ // Selection Logic
+ if (isSelectionMode) {
+ checkBox.setVisibility(View.VISIBLE);
+ checkBox.setChecked(selectedIds.contains(note.getId()));
} else {
- Log.d("NoteInfoAdapter", "Setting checkbox GONE");
- holder.checkBox.setVisibility(View.GONE);
+ checkBox.setVisibility(View.GONE);
}
- Log.d("NoteInfoAdapter", "===== Checkbox visibility set =====");
- if (note.isPinned) {
- holder.pinnedIcon.setVisibility(View.VISIBLE);
- } else {
- holder.pinnedIcon.setVisibility(View.GONE);
- }
+ if (note.isPinned) pinnedIcon.setVisibility(View.VISIBLE);
+ else pinnedIcon.setVisibility(View.GONE);
- if (note.isLocked) {
- holder.lockIcon.setVisibility(View.VISIBLE);
- } else {
- holder.lockIcon.setVisibility(View.GONE);
- }
+ if (note.isLocked) lockIcon.setVisibility(View.VISIBLE);
+ else lockIcon.setVisibility(View.GONE);
}
-
- return convertView;
}
-
- /**
- * 格式化日期
- *
- * @param timestamp 时间戳
- * @return 格式化后的日期字符串
- */
+
private String formatDate(long timestamp) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
return sdf.format(new Date(timestamp));
}
-
- /**
- * ViewHolder 模式:优化 ListView 性能
- */
- private static class ViewHolder {
- TextView title;
- TextView time;
- ImageView typeIcon;
- ImageView lockIcon;
- CheckBox checkBox;
- ImageView pinnedIcon;
- int position;
- }
-}
+}
\ No newline at end of file
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 429f4b8..386f83b 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
@@ -1,1058 +1,418 @@
-/*
- * 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.appwidget.AppWidgetManager;
-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.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.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowCompat;
+import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.ViewModel;
+import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+import androidx.viewpager2.widget.ViewPager2;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+import android.app.AlertDialog;
+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.databinding.NoteListBinding;
-import net.micode.notes.tool.SecurityManager;
-import net.micode.notes.ui.NoteInfoAdapter;
import net.micode.notes.viewmodel.NotesListViewModel;
+import net.micode.notes.tool.SecurityManager;
+import android.content.Intent;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-
-import java.util.List;
+public class NotesListActivity extends AppCompatActivity implements SidebarFragment.OnSidebarItemSelectedListener {
-/**
- * 笔记列表Activity(重构版)
- *
- * 仅负责UI展示和用户交互,业务逻辑委托给ViewModel
- * 符合MVVM架构模式
- *
- *
- * 相比原版(1305行),重构后代码量减少约70%
- *
- *
- * @see NotesListViewModel
- * @see NotesRepository
- */
-public class NotesListActivity extends AppCompatActivity
- implements NoteInfoAdapter.OnNoteButtonClickListener,
- NoteInfoAdapter.OnNoteItemClickListener,
- 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;
- private static final int REQUEST_CODE_CHECK_PASSWORD_FOR_OPEN = 104;
- private static final int REQUEST_CODE_CHECK_PASSWORD_FOR_LOCK = 105;
-
+ private ViewPager2 viewPager;
+ private DrawerLayout drawerLayout;
+ private TextView tvTitle;
+ private ImageView btnChangeLayout;
private NotesListViewModel viewModel;
- private NoteListBinding binding;
- private NoteInfoAdapter adapter;
- private View sidebarFragment;
-
- // 多选模式状态
- private boolean isMultiSelectMode = false;
- // 待打开的受保护笔记
- private long mPendingNodeIdToOpen = -1;
- private int mPendingNodeTypeToOpen = -1;
- private static final String KEY_PENDING_NODE_ID = "pending_node_id";
- private static final String KEY_PENDING_NODE_TYPE = "pending_node_type";
- private static final String KEY_CURRENT_FOLDER_ID = "current_folder_id";
+ private RecyclerView rvFolderTabs;
+ private View tvSearchBar; // Add member variable
+ private FolderAdapter folderAdapter;
+ private View headerContainer;
+ private View selectionHeader;
+ private View selectionBottomBar;
+ private BottomNavigationView bottomNav;
+ private TextView tvSelectionCount;
+ private TextView btnSelectionRestore;
+ private TextView btnSelectionDeleteForever;
+ private TextView btnActionPin;
+ private TextView btnActionLock;
+ private static final int REQUEST_CODE_VERIFY_PASSWORD_FOR_LOCK = 106;
- /**
- * 活动创建时的初始化方法
- *
- * 设置布局,初始化ViewModel,设置UI监听器
- *
- *
- * @param savedInstanceState 保存的实例状态
- */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_home);
- // 启用边缘到边缘显示
- WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
-
- binding = NoteListBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
-
- // 注意:CoordinatorLayout和AppBarLayout会自动处理WindowInsets
- // FAB也会自动避开导航栏
- // 不需要手动设置padding
-
- initViewModel();
-
- // 恢复 pending 状态和当前文件夹
- if (savedInstanceState != null) {
- mPendingNodeIdToOpen = savedInstanceState.getLong(KEY_PENDING_NODE_ID, -1);
- mPendingNodeTypeToOpen = savedInstanceState.getInt(KEY_PENDING_NODE_TYPE, -1);
-
- long savedFolderId = savedInstanceState.getLong(KEY_CURRENT_FOLDER_ID, Notes.ID_ROOT_FOLDER);
- if (savedFolderId != Notes.ID_ROOT_FOLDER) {
- viewModel.setCurrentFolderId(savedFolderId);
- }
-
- Log.d(TAG, "Restored pending node: " + mPendingNodeIdToOpen + ", type: " + mPendingNodeTypeToOpen + ", folder: " + savedFolderId);
- }
-
- initViews();
- observeViewModel();
- }
-
- /**
- * 保存 Activity 状态
- */
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putLong(KEY_PENDING_NODE_ID, mPendingNodeIdToOpen);
- outState.putInt(KEY_PENDING_NODE_TYPE, mPendingNodeTypeToOpen);
- outState.putLong(KEY_CURRENT_FOLDER_ID, viewModel.getCurrentFolderId());
- Log.d(TAG, "Saved pending node: " + mPendingNodeIdToOpen + ", current folder: " + viewModel.getCurrentFolderId());
- }
+ drawerLayout = findViewById(R.id.drawer_layout);
+ viewPager = findViewById(R.id.view_pager);
+ tvTitle = findViewById(R.id.tv_title);
+ btnChangeLayout = findViewById(R.id.btn_change_layout);
+ bottomNav = findViewById(R.id.bottom_navigation);
+
+ rvFolderTabs = findViewById(R.id.rv_folder_tabs);
+ headerContainer = findViewById(R.id.header_container);
+ selectionHeader = findViewById(R.id.selection_header);
+ selectionBottomBar = findViewById(R.id.selection_bottom_bar);
+ tvSelectionCount = findViewById(R.id.tv_selection_count);
+ tvSearchBar = findViewById(R.id.tv_search_bar); // Init tvSearchBar
+
+ btnSelectionRestore = findViewById(R.id.btn_selection_restore);
+ btnSelectionDeleteForever = findViewById(R.id.btn_selection_delete_forever);
+ btnActionPin = findViewById(R.id.btn_action_pin);
+ btnActionLock = findViewById(R.id.btn_action_lock);
- /**
- * 活动启动时的回调方法
- *
- * 加载笔记列表
- *
- */
- @Override
- protected void onStart() {
- super.onStart();
- // 刷新当前文件夹的笔记,而不是强制加载根目录
- // 这样可以保证从 PasswordActivity 返回时,如果 onStart 先执行,不会重置为根目录
- viewModel.refreshNotes();
- }
- private void initViewModel() {
+ // Init Shared ViewModel
NotesRepository repository = new NotesRepository(getContentResolver());
viewModel = new ViewModelProvider(this,
new ViewModelProvider.Factory() {
@Override
- public T create(Class modelClass) {
- if (modelClass.isAssignableFrom(NotesListViewModel.class)) {
- return (T) new NotesListViewModel(repository);
- }
- throw new IllegalArgumentException("Unknown ViewModel class");
+ public T create(Class modelClass) {
+ return (T) new NotesListViewModel(repository);
}
}).get(NotesListViewModel.class);
- Log.d(TAG, "ViewModel initialized");
- }
-
- /**
- * 初始化视图
- */
- private void initViews() {
- // 特殊处理:Fragment标签不会在ViewBinding中生成字段
- // 必须使用findViewById获取标签声明的Fragment实例
- // 这是Android ViewBinding的已知限制,不是遗漏
- sidebarFragment = findViewById(R.id.sidebar_fragment);
+
+ // Load initial notes (All Notes by default now)
+ if (savedInstanceState == null) {
+ viewModel.loadNotes(Notes.ID_ALL_NOTES_FOLDER);
+ }
- // 设置适配器
- adapter = new NoteInfoAdapter(this);
- binding.notesList.setAdapter(adapter);
- adapter.setOnNoteButtonClickListener(this);
- adapter.setOnNoteItemClickListener(this);
- adapter.setOnNoteItemLongClickListener(this);
+ // Setup Folder Tabs
+ folderAdapter = new FolderAdapter(this);
+ rvFolderTabs.setAdapter(folderAdapter);
+ folderAdapter.setOnFolderClickListener(folderId -> viewModel.enterFolder(folderId));
+
+ // Observe Folder Changes for UI updates
+ viewModel.getCurrentFolderIdLiveData().observe(this, folderId -> {
+ updateTrashModeUI(folderId == Notes.ID_TRASH_FOLER);
+ folderAdapter.setSelectedFolderId(folderId);
+ });
- // 设置点击监听
- binding.notesList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- Object item = parent.getItemAtPosition(position);
- if (item instanceof NotesRepository.NoteInfo) {
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) item;
- handleItemClick(note, position);
+ viewModel.getFoldersLiveData().observe(this, folders -> {
+ folderAdapter.setFolders(folders);
+ });
+
+ // Selection Mode Observer
+ viewModel.getIsSelectionMode().observe(this, isSelection -> {
+ if (isSelection) {
+ headerContainer.setVisibility(View.GONE);
+ selectionHeader.setVisibility(View.VISIBLE);
+
+ if (viewModel.isTrashMode()) {
+ selectionBottomBar.setVisibility(View.GONE);
+ btnSelectionRestore.setVisibility(View.VISIBLE);
+ btnSelectionDeleteForever.setVisibility(View.VISIBLE);
+ // Hide Select All if needed, or keep it. Let's keep it for now but maybe change layout if crowded.
+ } else {
+ selectionBottomBar.setVisibility(View.VISIBLE);
+ btnSelectionRestore.setVisibility(View.GONE);
+ btnSelectionDeleteForever.setVisibility(View.GONE);
+ }
+
+ // bottomNav.setVisibility(View.GONE); // Overlay covers it
+ updateSelectionCount(); // Initial update when entering mode
+ } else {
+ headerContainer.setVisibility(View.VISIBLE);
+ selectionHeader.setVisibility(View.GONE);
+ selectionBottomBar.setVisibility(View.GONE);
+
+ // Only show bottom nav if NOT in trash mode (handled by updateTrashModeUI)
+ if (!viewModel.isTrashMode()) {
+ bottomNav.setVisibility(View.VISIBLE);
}
}
});
-
- // 初始化 Toolbar
- setSupportActionBar(binding.toolbar);
- if (getSupportActionBar() != null) {
- getSupportActionBar().setTitle(R.string.app_name);
- }
-
- // 初始化为普通模式
- updateToolbarForNormalMode();
-
- // 设置 Toolbar 的汉堡菜单按钮点击监听器(打开侧栏)
- binding.toolbar.setNavigationOnClickListener(v -> {
- binding.drawerLayout.openDrawer(sidebarFragment);
+
+ // Update selection count
+ viewModel.getSelectedIdsLiveData().observe(this, selectedIds -> {
+ updateSelectionCount();
+ updateSelectionActionUI();
});
+
+ // Selection Actions
+ findViewById(R.id.btn_close_selection).setOnClickListener(v -> viewModel.setIsSelectionMode(false));
- // Set FAB click event
- if (binding.btnNewNote != null) {
- binding.btnNewNote.setOnClickListener(v -> {
- Intent intent = new Intent(NotesListActivity.this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, viewModel.getCurrentFolderId());
- startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
- });
- }
- }
+ btnActionPin.setOnClickListener(v -> {
+ viewModel.toggleSelectedNotesPin();
+ viewModel.setIsSelectionMode(false);
+ });
- /**
- * 处理列表项点击
- *
- * 如果是便签,打开编辑器;如果是文件夹,进入该文件夹
- *
- *
- * @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) {
- // 文件夹:进入该文件夹
- // 检查隐私锁
- if (note.isLocked && SecurityManager.getInstance(this).isPasswordSet()) {
- mPendingNodeIdToOpen = note.getId();
- mPendingNodeTypeToOpen = note.type;
- Intent intent = new Intent(this, PasswordActivity.class);
- intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
- startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD_FOR_OPEN);
- return;
- }
- viewModel.enterFolder(note.getId());
+ btnActionLock.setOnClickListener(v -> {
+ SecurityManager securityManager = SecurityManager.getInstance(this);
+ if (!securityManager.isPasswordSet()) {
+ new AlertDialog.Builder(this)
+ .setTitle("设置密码")
+ .setMessage("使用加锁功能前请先设置密码")
+ .setPositiveButton("去设置", (d, w) -> {
+ showSetPasswordDialog();
+ })
+ .setNegativeButton("取消", null)
+ .show();
} else {
- // 便签:打开编辑器
- openNoteEditor(note);
+ Intent intent = new Intent(this, PasswordActivity.class);
+ intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
+ startActivityForResult(intent, REQUEST_CODE_VERIFY_PASSWORD_FOR_LOCK);
}
- }
- }
+ });
+
+ findViewById(R.id.btn_select_all).setOnClickListener(v -> {
+ viewModel.selectAllNotes();
+ });
+
+ findViewById(R.id.btn_action_delete).setOnClickListener(v -> {
+ new AlertDialog.Builder(this)
+ .setTitle("删除")
+ .setMessage("确定要删除选中的笔记吗?")
+ .setPositiveButton("删除", (d,w) -> {
+ viewModel.deleteSelectedNotes();
+ viewModel.setIsSelectionMode(false);
+ })
+ .setNegativeButton("取消", null)
+ .show();
+ });
+
+ // Restore Action
+ btnSelectionRestore.setOnClickListener(v -> {
+ new AlertDialog.Builder(this)
+ .setTitle("恢复笔记")
+ .setMessage("确定要恢复选中的笔记吗?")
+ .setPositiveButton("确定", (d, w) -> {
+ viewModel.restoreSelectedNotes();
+ viewModel.setIsSelectionMode(false);
+ })
+ .setNegativeButton("再想想", null)
+ .show();
+ });
- /**
- * 观察ViewModel的LiveData
- */
- private void observeViewModel() {
- // 观察笔记列表
- viewModel.getNotesLiveData().observe(this, new Observer>() {
- @Override
- public void onChanged(List notes) {
- updateAdapter(notes);
- updateToolbarForNormalMode();
- }
+ // Delete Forever Action
+ btnSelectionDeleteForever.setOnClickListener(v -> {
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setTitle("永久删除")
+ .setMessage("确定要永久删除选中的笔记吗?此操作不可撤销。")
+ .setPositiveButton("确定", (d, w) -> {
+ viewModel.deleteSelectedNotesForever();
+ viewModel.setIsSelectionMode(false);
+ })
+ .setNegativeButton("再想想", null)
+ .show();
+ // Set positive button color to red/dark if possible, but default is fine or need custom view
+ dialog.setOnShowListener(d -> {
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getResources().getColor(android.R.color.holo_red_dark));
+ });
});
- // 观察加载状态
- viewModel.getIsLoading().observe(this, new Observer() {
- @Override
- public void onChanged(Boolean isLoading) {
- updateLoadingState(isLoading);
- }
+ // Search Bar
+ tvSearchBar.setOnClickListener(v -> {
+ startActivity(new android.content.Intent(this, NoteSearchActivity.class));
});
- // 观察错误消息
- viewModel.getErrorMessage().observe(this, new Observer() {
- @Override
- public void onChanged(String message) {
- if (message != null && !message.isEmpty()) {
- showError(message);
- }
+ MainPagerAdapter adapter = new MainPagerAdapter(this);
+ viewPager.setAdapter(adapter);
+ viewPager.setUserInputEnabled(false); // Disable swipe to avoid conflict
+
+ // Setup Bottom Navigation
+ bottomNav.setOnItemSelectedListener(item -> {
+ int itemId = item.getItemId();
+ if (itemId == R.id.nav_notes) {
+ viewPager.setCurrentItem(0, false);
+ tvTitle.setText(R.string.app_name);
+ btnChangeLayout.setVisibility(View.VISIBLE);
+
+ // Show Search & Tabs
+ tvSearchBar.setVisibility(View.VISIBLE);
+ rvFolderTabs.setVisibility(View.VISIBLE);
+
+ updateLayoutButtonIcon();
+ return true;
+ } else if (itemId == R.id.nav_tasks) {
+ viewPager.setCurrentItem(1, false);
+ tvTitle.setText("待办");
+ btnChangeLayout.setVisibility(View.GONE);
+
+ // Hide Search & Tabs
+ tvSearchBar.setVisibility(View.GONE);
+ rvFolderTabs.setVisibility(View.GONE);
+
+ return true;
}
+ return false;
});
- // 观察文件夹路径(用于面包屑导航)
- viewModel.getFolderPathLiveData().observe(this, new Observer>() {
- @Override
- public void onChanged(List path) {
- updateBreadcrumb(path);
+ // Sidebar Button
+ findViewById(R.id.btn_sidebar).setOnClickListener(v -> {
+ if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
+ drawerLayout.closeDrawer(GravityCompat.START);
+ } else {
+ drawerLayout.openDrawer(GravityCompat.START);
}
});
- // 观察侧栏刷新通知
- viewModel.getSidebarRefreshNeeded().observe(this, new Observer() {
- @Override
- public void onChanged(Boolean refreshNeeded) {
- if (refreshNeeded != null && refreshNeeded) {
- // 通知侧栏刷新
- SidebarFragment sidebarFrag = (SidebarFragment) getSupportFragmentManager()
- .findFragmentById(R.id.sidebar_fragment);
- if (sidebarFrag != null) {
- sidebarFrag.refreshFolderTree();
- }
- // 重置刷新状态
- viewModel.getSidebarRefreshNeeded().setValue(false);
- }
+ // Layout Toggle Button
+ btnChangeLayout.setOnClickListener(v -> {
+ NotesListFragment fragment = getNotesListFragment();
+ if (fragment != null) {
+ boolean isStaggered = fragment.toggleLayout();
+ btnChangeLayout.setImageResource(isStaggered ? R.drawable.ic_view_list : R.drawable.ic_view_grid);
}
});
+
+ // Initial icon update (post to ensure fragment is attached)
+ viewPager.post(this::updateLayoutButtonIcon);
}
-
- /**
- * 更新面包屑导航
- *
- * @param path 文件夹路径
- */
- private void updateBreadcrumb(List path) {
- if (binding.breadcrumbInclude == null || binding.breadcrumbInclude.breadcrumbItems == null || path == null) {
- return;
- }
-
- binding.breadcrumbInclude.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);
- binding.breadcrumbInclude.breadcrumbItems.addView(separator);
- }
-
- // 创建面包屑项
- TextView breadcrumbItem = (TextView) getLayoutInflater()
- .inflate(R.layout.breadcrumb_item, binding.breadcrumbInclude.breadcrumbItems, false);
- breadcrumbItem.setText(folder.title);
-
- // 如果是当前文件夹(最后一个),高亮显示且不可点击
- if (i == path.size() - 1) {
- breadcrumbItem.setTextColor(getColor(R.color.primary_color));
- breadcrumbItem.setEnabled(false);
+
+ private void updateTrashModeUI(boolean isTrash) {
+ if (isTrash) {
+ tvTitle.setText("回收站");
+ tvSearchBar.setVisibility(View.GONE);
+ rvFolderTabs.setVisibility(View.GONE);
+ bottomNav.setVisibility(View.GONE);
+ } else {
+ // Restore normal UI state
+ if (bottomNav.getSelectedItemId() == R.id.nav_notes) {
+ tvTitle.setText(R.string.app_name);
+ tvSearchBar.setVisibility(View.VISIBLE);
+ rvFolderTabs.setVisibility(View.VISIBLE);
} else {
- // 其他层级可以点击跳转
- final long targetFolderId = folder.id;
- breadcrumbItem.setOnClickListener(v -> viewModel.enterFolder(targetFolderId));
+ tvTitle.setText("待办");
+ tvSearchBar.setVisibility(View.GONE);
+ rvFolderTabs.setVisibility(View.GONE);
}
-
- binding.breadcrumbInclude.breadcrumbItems.addView(breadcrumbItem);
+ bottomNav.setVisibility(View.VISIBLE);
}
}
-
- /**
- * 更新适配器数据
- */
- private void updateAdapter(List notes) {
- adapter.setNotes(notes);
- Log.d(TAG, "Adapter updated with " + notes.size() + " notes");
- }
-
- /**
- * 更新加载状态
- */
- private void updateLoadingState(boolean isLoading) {
- // TODO: 显示/隐藏进度条
- }
-
- /**
- * 显示错误消息
- */
- private void showError(String message) {
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
- }
-
- /**
- * 打开笔记编辑器
- */
- private void openNoteEditor(NotesRepository.NoteInfo note) {
- // 检查隐私锁
- if (note.isLocked && SecurityManager.getInstance(this).isPasswordSet()) {
- mPendingNodeIdToOpen = note.getId();
- mPendingNodeTypeToOpen = note.type;
- Intent intent = new Intent(this, PasswordActivity.class);
- intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
- startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD_FOR_OPEN);
- return;
+
+ public void updateSelectionCount() {
+ if (viewModel != null) {
+ int count = viewModel.getSelectedCount();
+ tvSelectionCount.setText("已选择 " + count + " 项");
}
-
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, note.getParentId());
- intent.putExtra(Intent.EXTRA_UID, note.getId());
- startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
- /**
- * 编辑按钮点击事件处理
- *
- * @param position 列表位置
- * @param noteId 便签 ID
- */
- @Override
- public void onEditButtonClick(int position, long noteId) {
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
- if (note != null) {
- openNoteEditor(note);
- } else {
- Log.e(TAG, "Edit button clicked but note is null at position: " + position);
- }
+ private void showSetPasswordDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle("设置密码")
+ .setItems(new String[]{"数字锁", "手势锁"}, (dialog, which) -> {
+ int type = (which == 0) ? SecurityManager.TYPE_PIN : SecurityManager.TYPE_PATTERN;
+ Intent intent = new Intent(this, PasswordActivity.class);
+ intent.setAction(PasswordActivity.ACTION_SETUP_PASSWORD);
+ intent.putExtra(PasswordActivity.EXTRA_PASSWORD_TYPE, type);
+ startActivity(intent);
+ })
+ .show();
}
- @Override
- public void onNoteItemClick(int position, long noteId) {
- Log.d(TAG, "===== onNoteItemClick CALLED =====");
- Log.d(TAG, "position: " + position + ", noteId: " + noteId);
-
- 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());
+ private void updateSelectionActionUI() {
+ if (viewModel != null) {
+ if (btnActionPin != null) {
+ boolean isAllPinned = viewModel.isAllSelectedPinned();
+ if (isAllPinned) {
+ btnActionPin.setText("取消置顶");
+ } else {
+ btnActionPin.setText("置顶");
}
- // 更新toolbar标题
- updateToolbarForMultiSelectMode();
}
- Log.d(TAG, "===== onNoteItemClick END (multi-select mode) =====");
- } else {
- Log.d(TAG, "Normal mode, checking item type");
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
- if (note != null) {
- if (viewModel.isTrashMode()) {
- // 回收站模式:弹出恢复/删除对话框
- showTrashItemDialog(note);
- } else if (viewModel.isTemplateMode() && note.type == Notes.TYPE_NOTE) {
- showApplyTemplateDialog(note);
- } else if (note.type == Notes.TYPE_FOLDER) {
- // 文件夹:进入该文件夹
- // 检查隐私锁
- if (note.isLocked && SecurityManager.getInstance(this).isPasswordSet()) {
- mPendingNodeIdToOpen = note.getId();
- mPendingNodeTypeToOpen = note.type;
- Intent intent = new Intent(this, PasswordActivity.class);
- intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
- startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD_FOR_OPEN);
- return;
- }
- Log.d(TAG, "Folder clicked, entering folder: " + note.getId());
- viewModel.enterFolder(note.getId());
+
+ if (btnActionLock != null) {
+ boolean isAllLocked = viewModel.isAllSelectedLocked();
+ if (isAllLocked) {
+ btnActionLock.setText("解锁");
} else {
- // 便签:打开编辑器
- Log.d(TAG, "Note clicked, opening editor");
- openNoteEditor(note);
+ btnActionLock.setText("加锁");
}
}
- Log.d(TAG, "===== onNoteItemClick END =====");
}
}
- @Override
- public void onNoteItemLongClick(int position, long noteId) {
- Log.d(TAG, "===== onNoteItemLongClick CALLED =====");
- Log.d(TAG, "position: " + position + ", noteId: " + noteId);
-
- if (!isMultiSelectMode) {
- Log.d(TAG, "Entering multi-select mode");
- enterMultiSelectMode();
- viewModel.toggleNoteSelection(noteId, true);
-
- if (adapter != null) {
- adapter.setSelectedIds(viewModel.getSelectedNoteIds());
+ private NotesListFragment getNotesListFragment() {
+ for (Fragment fragment : getSupportFragmentManager().getFragments()) {
+ if (fragment instanceof NotesListFragment) {
+ return (NotesListFragment) fragment;
}
-
- updateSelectionState(position, true);
-
- Log.d(TAG, "===== onNoteItemLongClick END =====");
- } else {
- Log.d(TAG, "Multi-select mode already active, ignoring long click");
- }
- }
-
- /**
- * 显示应用模板确认对话框
- */
- private void showApplyTemplateDialog(NotesRepository.NoteInfo note) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("应用模板");
- builder.setMessage("使用模板 \"" + (TextUtils.isEmpty(note.title) ? "未命名" : note.title) + "\" 创建新笔记?");
- builder.setPositiveButton("创建", (dialog, which) -> {
- viewModel.applyTemplate(note.getId(), new NotesRepository.Callback() {
- @Override
- public void onSuccess(Long newNoteId) {
- runOnUiThread(() -> {
- Toast.makeText(NotesListActivity.this, "创建成功", Toast.LENGTH_SHORT).show();
- // 跳转到新笔记编辑页
- Intent intent = new Intent(NotesListActivity.this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(Intent.EXTRA_UID, newNoteId);
- startActivity(intent);
- // 退出模板模式(返回根目录)
- // viewModel.loadNotes(Notes.ID_ROOT_FOLDER);
- });
- }
-
- @Override
- public void onError(Exception error) {
- runOnUiThread(() -> {
- Toast.makeText(NotesListActivity.this, "创建失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
- });
- }
- });
- });
- builder.setNegativeButton("取消", null);
- builder.setNeutralButton("编辑模板", (dialog, which) -> {
- // 打开编辑器编辑模板本身
- openNoteEditor(note);
- });
- builder.show();
- }
-
- /**
- * 显示回收站条目操作对话框
- */
- private void showTrashItemDialog(NotesRepository.NoteInfo note) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("操作确认");
- builder.setMessage("要恢复还是永久删除此笔记?");
- builder.setPositiveButton("恢复", (dialog, which) -> {
- // 临时将选中的ID设为当前ID,以便复用ViewModel的restoreSelectedNotes
- // 这里为了简单,我们直接调用ViewModel的新方法restoreNote(note.getId())
- // 但ViewModel还没这个方法,所以我们先手动构造一个List
- viewModel.clearSelection();
- viewModel.toggleNoteSelection(note.getId(), true);
- viewModel.restoreSelectedNotes();
- });
- builder.setNegativeButton("永久删除", (dialog, which) -> {
- showDeleteForeverConfirmDialog(note);
- });
- builder.setNeutralButton("再想想", null);
- builder.show();
- }
-
- /**
- * 显示永久删除确认对话框
- */
- private void showDeleteForeverConfirmDialog(NotesRepository.NoteInfo note) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("永久删除");
- builder.setMessage("确定要永久删除此笔记吗?删除后无法恢复!");
- builder.setIcon(android.R.drawable.ic_dialog_alert);
-
- builder.setPositiveButton("确定", (dialog, which) -> {
- viewModel.clearSelection();
- viewModel.toggleNoteSelection(note.getId(), true);
- viewModel.deleteSelectedNotesForever();
- });
-
- // 设置“确定”按钮颜色为深色(通常系统默认就是强调色,如果需要特定颜色需自定义View或Theme)
- // 这里使用默认样式,通常Positive是强调色
-
- builder.setNegativeButton("取消", null);
- builder.show();
- }
-
- /**
- * 进入多选模式
- */
- private void enterMultiSelectMode() {
- isMultiSelectMode = true;
- // 隐藏FAB按钮
- if (binding.btnNewNote != null) {
- binding.btnNewNote.setVisibility(View.GONE);
}
- // 更新toolbar为多选模式
- updateToolbarForMultiSelectMode();
+ return null;
}
- /**
- * 退出多选模式
- */
- private void exitMultiSelectMode() {
- isMultiSelectMode = false;
- // 显示FAB按钮
- if (binding.btnNewNote != null) {
- binding.btnNewNote.setVisibility(View.VISIBLE);
- }
- // 清除选中状态
- viewModel.clearSelection();
- if (adapter != null) {
- adapter.setSelectedIds(new java.util.HashSet<>());
- adapter.notifyDataSetChanged();
- }
- // 更新toolbar为普通模式
- updateToolbarForNormalMode();
- }
-
- /**
- * 更新Toolbar为多选模式
- */
- private void updateToolbarForMultiSelectMode() {
- if (binding.toolbar == null) return;
-
- // 设置标题为选中数量
- int selectedCount = viewModel.getSelectedCount();
- String title = getString(R.string.menu_select_title, selectedCount);
- binding.toolbar.setTitle(title);
-
- // 设置导航图标为返回(取消多选)
- binding.toolbar.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
- binding.toolbar.setNavigationOnClickListener(v -> exitMultiSelectMode());
-
- // 移除普通模式的菜单(如果有)
- binding.toolbar.getMenu().clear();
-
- // 直接在toolbar上添加操作按钮(不在三点菜单中)
- Menu menu = binding.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);
-
- // 置顶按钮
- boolean allPinned = viewModel.isAllSelectedPinned();
- MenuItem pinItem = menu.add(Menu.NONE, R.id.multi_select_pin, 3, allPinned ? getString(R.string.menu_unpin) : getString(R.string.menu_pin));
- // 使用上传图标代替置顶图标,或者如果有合适的资源可以使用
- pinItem.setIcon(android.R.drawable.ic_menu_upload);
- pinItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-
- // 锁定按钮
- boolean allLocked = viewModel.isAllSelectedLocked();
- MenuItem lockItem = menu.add(Menu.NONE, R.id.multi_select_lock, 4, getString(R.string.menu_lock));
- lockItem.setIcon(android.R.drawable.ic_lock_lock);
- lockItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- }
-
- /**
- * 更新Toolbar为普通模式
- */
- private void updateToolbarForNormalMode() {
- if (binding.toolbar == null) return;
-
- // 清除多选模式菜单
- binding.toolbar.getMenu().clear();
-
- // 设置标题
- if (viewModel.isTrashMode()) {
- binding.toolbar.setTitle(R.string.menu_trash);
- } else if (viewModel.isTemplateMode()) {
- binding.toolbar.setTitle(R.string.menu_templates);
- } else {
- binding.toolbar.setTitle(R.string.app_name);
- // 添加普通模式菜单
- binding.toolbar.inflateMenu(R.menu.note_list);
- }
-
- // 设置导航图标为汉堡菜单
- binding.toolbar.setNavigationIcon(android.R.drawable.ic_menu_sort_by_size);
- binding.toolbar.setNavigationOnClickListener(v -> {
- binding.drawerLayout.openDrawer(sidebarFragment);
- });
-
- // 如果是回收站模式,不显示新建按钮
- if (viewModel.isTrashMode()) {
- if (binding.btnNewNote != null) {
- binding.btnNewNote.setVisibility(View.GONE);
- }
- } else {
- if (binding.btnNewNote != null) {
- binding.btnNewNote.setVisibility(View.VISIBLE);
- }
+ private void updateLayoutButtonIcon() {
+ NotesListFragment fragment = getNotesListFragment();
+ if (fragment != null) {
+ boolean isStaggered = fragment.isStaggeredLayout();
+ btnChangeLayout.setImageResource(isStaggered ? R.drawable.ic_view_list : R.drawable.ic_view_grid);
}
}
-
-
- /**
- * 显示删除确认对话框
- */
- private void showDeleteDialog() {
- int selectedCount = viewModel.getSelectedCount();
- 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_notes, selectedCount));
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- viewModel.deleteSelectedNotes();
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- }
-
- /**
- * 显示移动菜单
- */
- private void showMoveMenu() {
- // TODO: 实现文件夹选择逻辑
- Toast.makeText(this, "移动功能开发中", Toast.LENGTH_SHORT).show();
- }
-
- /**
- * 活动结果回调方法
- */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
-
- if (resultCode == RESULT_OK) {
- if (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE) {
- viewModel.refreshNotes();
- } else if (requestCode == REQUEST_CODE_CHECK_PASSWORD_FOR_OPEN) {
- if (mPendingNodeIdToOpen != -1) {
- if (mPendingNodeTypeToOpen == Notes.TYPE_FOLDER) {
- // 文件夹密码验证通过,直接进入文件夹
- Log.d(TAG, "Password verified for folder: " + mPendingNodeIdToOpen);
- viewModel.enterFolder(mPendingNodeIdToOpen);
- } else {
- // 密码验证通过,直接打开编辑器
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(Intent.EXTRA_UID, mPendingNodeIdToOpen);
- startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
- }
- mPendingNodeIdToOpen = -1;
- mPendingNodeTypeToOpen = -1;
- }
- } else if (requestCode == REQUEST_CODE_CHECK_PASSWORD_FOR_LOCK) {
- // 加锁/解锁密码验证通过
- boolean wasLocked = viewModel.isAllSelectedLocked();
- viewModel.toggleSelectedNotesLock();
- String lockMsg = wasLocked ? getString(R.string.menu_unlock) + "成功" : getString(R.string.menu_lock) + "成功";
- Toast.makeText(this, lockMsg, Toast.LENGTH_SHORT).show();
- }
- } else if (requestCode == REQUEST_CODE_CHECK_PASSWORD_FOR_LOCK) {
- // 加锁/解锁密码验证失败或取消
- boolean wasLocked = viewModel.isAllSelectedLocked();
- String lockMsg = wasLocked ? getString(R.string.menu_unlock) + "失败" : getString(R.string.menu_lock) + "失败";
- Toast.makeText(this, lockMsg, Toast.LENGTH_SHORT).show();
+ if (requestCode == REQUEST_CODE_VERIFY_PASSWORD_FOR_LOCK && resultCode == RESULT_OK) {
+ viewModel.toggleSelectedNotesLock();
+ viewModel.setIsSelectionMode(false);
}
}
- /**
- * 创建选项菜单
- */
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.note_list, menu);
- return true;
- }
-
- /**
- * 选项菜单项点击事件
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int itemId = item.getItemId();
+ private class MainPagerAdapter extends FragmentStateAdapter {
+ public MainPagerAdapter(@NonNull AppCompatActivity activity) {
+ super(activity);
+ }
- switch (itemId) {
- case R.id.menu_tasks:
- startActivity(new Intent(this, TaskListActivity.class));
- overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
- return true;
- case R.id.menu_search:
- Intent searchIntent = new Intent(this, NoteSearchActivity.class);
- startActivity(searchIntent);
- return true;
- case R.id.menu_new_folder:
- // 创建新文件夹
- showCreateFolderDialog();
- return true;
- case R.id.menu_export_text:
- // TODO: 导出笔记
- Toast.makeText(this, "导出功能开发中", Toast.LENGTH_SHORT).show();
- return true;
- case R.id.menu_sync:
- // TODO: 同步功能
- Toast.makeText(this, "同步功能暂不可用", Toast.LENGTH_SHORT).show();
- return true;
- case R.id.menu_setting:
- // 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;
- case R.id.multi_select_pin:
- boolean wasPinned = viewModel.isAllSelectedPinned();
- viewModel.toggleSelectedNotesPin();
- String toastMsg = wasPinned ? getString(R.string.menu_unpin) + "成功" : getString(R.string.menu_pin) + "成功";
- Toast.makeText(this, toastMsg, Toast.LENGTH_SHORT).show();
- return true;
- case R.id.multi_select_lock:
- // 检查是否设置了隐私密码
- if (SecurityManager.getInstance(this).isPasswordSet()) {
- // 需要先验证密码
- Intent intent = new Intent(this, PasswordActivity.class);
- intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
- startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD_FOR_LOCK);
- } else {
- Toast.makeText(this, "请先在设置中设置隐私密码", Toast.LENGTH_SHORT).show();
- // 跳转到设置密码界面
- Intent intent = new Intent(this, NotesPreferenceActivity.class);
- startActivity(intent);
- }
- return true;
- default:
- return super.onOptionsItemSelected(item);
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ if (position == 0) {
+ return new NotesListFragment();
+ } else {
+ return new TaskListFragment();
+ }
}
- }
- /**
- * 上下文菜单创建
- */
- @Override
- public void onCreateContextMenu(android.view.ContextMenu menu, View v, android.view.ContextMenu.ContextMenuInfo menuInfo) {
- getMenuInflater().inflate(R.menu.sub_folder, menu);
+ @Override
+ public int getItemCount() {
+ return 2;
+ }
}
- /**
- * 上下文菜单项点击
- */
@Override
- public boolean onContextItemSelected(MenuItem item) {
- // TODO: 处理文件夹上下文菜单
- return super.onContextItemSelected(item);
- }
-
- private void updateSelectionState(int position, boolean selected) {
- Log.d("NotesListActivity", "===== updateSelectionState called =====");
- Log.d("NotesListActivity", "position: " + position + ", selected: " + selected);
- NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
- if (note != null) {
- Log.d("NotesListActivity", "note ID: " + note.getId());
- Log.d("NotesListActivity", "Current selectedIds size before update: " + adapter.getSelectedIds().size());
- Log.d("NotesListActivity", "Note already in selectedIds: " + adapter.getSelectedIds().contains(note.getId()));
- if (adapter.getSelectedIds().contains(note.getId()) != selected) {
- if (selected) {
- Log.d("NotesListActivity", "Adding note ID to selectedIds");
- adapter.getSelectedIds().add(note.getId());
- } else {
- Log.d("NotesListActivity", "Removing note ID from selectedIds");
- adapter.getSelectedIds().remove(note.getId());
- }
- Log.d("NotesListActivity", "SelectedIds size after update: " + adapter.getSelectedIds().size());
- adapter.notifyDataSetChanged();
- Log.d("NotesListActivity", "notifyDataSetChanged() called");
- } else {
- Log.d("NotesListActivity", "Note selection state unchanged, skipping update");
- }
+ public void onBackPressed() {
+ if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.START)) {
+ drawerLayout.closeDrawer(GravityCompat.START);
+ } else if (viewModel != null && viewModel.navigateUp()) {
+ // Handled by ViewModel navigation
} else {
- Log.e("NotesListActivity", "note is NULL at position: " + position);
+ super.onBackPressed();
}
- Log.d("NotesListActivity", "===== updateSelectionState END =====");
}
- // ==================== SidebarFragment.OnSidebarItemSelectedListener 实现 ====================
-
+ // ==================== Sidebar Callbacks ====================
+
@Override
public void onFolderSelected(long folderId) {
- // 跳转到指定文件夹
+ drawerLayout.closeDrawer(GravityCompat.START);
viewModel.enterFolder(folderId);
- // 关闭侧栏
- if (binding.drawerLayout != null) {
- binding.drawerLayout.closeDrawer(sidebarFragment);
- }
}
@Override
public void onTrashSelected() {
- // 跳转到回收站
+ drawerLayout.closeDrawer(GravityCompat.START);
viewModel.enterFolder(Notes.ID_TRASH_FOLER);
- // 关闭侧栏
- if (binding.drawerLayout != null) {
- binding.drawerLayout.closeDrawer(sidebarFragment);
- }
}
-
- @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 onTemplateSelected() {
- // 跳转到模板文件夹
+ @Override public void onSyncSelected() { drawerLayout.closeDrawer(GravityCompat.START); }
+ @Override public void onLoginSelected() { drawerLayout.closeDrawer(GravityCompat.START); }
+ @Override public void onExportSelected() { drawerLayout.closeDrawer(GravityCompat.START); }
+ @Override public void onTemplateSelected() {
+ drawerLayout.closeDrawer(GravityCompat.START);
viewModel.enterFolder(Notes.ID_TEMPLATE_FOLDER);
- // 关闭侧栏
- if (binding.drawerLayout != null) {
- binding.drawerLayout.closeDrawer(sidebarFragment);
- }
}
-
- @Override
- public void onSettingsSelected() {
- // 打开设置页面
- Intent intent = new Intent(this, net.micode.notes.ui.NotesPreferenceActivity.class);
- startActivity(intent);
- // 关闭侧栏
- if (binding.drawerLayout != null) {
- binding.drawerLayout.closeDrawer(sidebarFragment);
- }
- }
-
- @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 (binding.drawerLayout != null) {
- binding.drawerLayout.closeDrawer(sidebarFragment);
- }
- }
-
- /**
- * 返回键按下事件处理
- *
- * 多选模式:退出多选模式
- * 子文件夹:返回上一级文件夹
- * 根文件夹:最小化应用
- *
- */
- @Override
- public void onBackPressed() {
- if (isMultiSelectMode) {
- // 多选模式:退出多选模式
- exitMultiSelectMode();
- } else if (binding.drawerLayout != null && binding.drawerLayout.isDrawerOpen(sidebarFragment)) {
- // 侧栏打开:关闭侧栏
- binding.drawerLayout.closeDrawer(sidebarFragment);
- } 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);
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- binding = null;
+
+ @Override public void onSettingsSelected() {
+ drawerLayout.closeDrawer(GravityCompat.START);
+ startActivity(new android.content.Intent(this, SettingsActivity.class));
}
-}
+ @Override public void onCreateFolder() { drawerLayout.closeDrawer(GravityCompat.START); }
+ @Override public void onCloseSidebar() { drawerLayout.closeDrawer(GravityCompat.START); }
+}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java
index de22828..b8a9eda 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/SettingsFragment.java
@@ -18,7 +18,13 @@ import net.micode.notes.R;
import net.micode.notes.tool.SecurityManager;
import net.micode.notes.data.ThemeRepository;
+import net.micode.notes.data.NotesRepository;
+import android.provider.Settings;
+import android.net.Uri;
+import androidx.preference.SwitchPreferenceCompat;
import androidx.preference.ListPreference;
+import net.micode.notes.capsule.CapsuleService;
+import net.micode.notes.capsule.ClipboardMonitorService;
import static android.app.Activity.RESULT_OK;
@@ -26,7 +32,9 @@ public class SettingsFragment extends PreferenceFragmentCompat {
public static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
public static final String PREFERENCE_SECURITY_KEY = "pref_key_security";
public static final String PREFERENCE_THEME_MODE = "pref_theme_mode";
+ public static final String PREFERENCE_CAPSULE_ENABLE = "pref_key_capsule_enable";
public static final int REQUEST_CODE_CHECK_PASSWORD = 104;
+ public static final int REQUEST_CODE_OVERLAY_PERMISSION = 105;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -34,6 +42,116 @@ public class SettingsFragment extends PreferenceFragmentCompat {
loadThemePreference();
loadSecurityPreference();
loadAccountPreference();
+ loadCapsulePreference();
+ }
+
+ private void loadCapsulePreference() {
+ SwitchPreferenceCompat capsulePref = findPreference(PREFERENCE_CAPSULE_ENABLE);
+ if (capsulePref != null) {
+ capsulePref.setOnPreferenceChangeListener((preference, newValue) -> {
+ boolean enabled = (boolean) newValue;
+ if (enabled) {
+ checkCapsulePermissions();
+ } else {
+ stopCapsuleService();
+ }
+ return true;
+ });
+ }
+ }
+
+ private void checkCapsulePermissions() {
+ Context context = getContext();
+ if (context == null) return;
+
+ // Check Overlay Permission
+ if (!Settings.canDrawOverlays(context)) {
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.capsule_permission_alert_window_title)
+ .setMessage(R.string.capsule_permission_alert_window_message)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+ Uri.parse("package:" + context.getPackageName()));
+ startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION);
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+ // Reset switch if cancelled
+ SwitchPreferenceCompat pref = findPreference(PREFERENCE_CAPSULE_ENABLE);
+ if (pref != null) pref.setChecked(false);
+ })
+ .show();
+ return;
+ }
+
+ // Check Accessibility Permission
+ if (!isAccessibilitySettingsOn(context)) {
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.capsule_permission_accessibility_title)
+ .setMessage(R.string.capsule_permission_accessibility_message)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
+ startActivity(intent);
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+ // Reset switch
+ SwitchPreferenceCompat pref = findPreference(PREFERENCE_CAPSULE_ENABLE);
+ if (pref != null) pref.setChecked(false);
+ })
+ .show();
+ return;
+ }
+
+ startCapsuleService();
+ }
+
+ private boolean isAccessibilitySettingsOn(Context mContext) {
+ int accessibilityEnabled = 0;
+ final String service = mContext.getPackageName() + "/" + ClipboardMonitorService.class.getCanonicalName();
+ try {
+ accessibilityEnabled = Settings.Secure.getInt(
+ mContext.getApplicationContext().getContentResolver(),
+ android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
+ } catch (Settings.SettingNotFoundException e) {
+ }
+ TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
+
+ if (accessibilityEnabled == 1) {
+ String settingValue = Settings.Secure.getString(
+ mContext.getApplicationContext().getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (settingValue != null) {
+ mStringColonSplitter.setString(settingValue);
+ while (mStringColonSplitter.hasNext()) {
+ String accessibilityService = mStringColonSplitter.next();
+ if (accessibilityService.equalsIgnoreCase(service)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void startCapsuleService() {
+ Context context = getContext();
+ if (context != null) {
+ Intent intent = new Intent(context, CapsuleService.class);
+ // In Android 8.0+, we might need startForegroundService, but for now stick to startService
+ // The service itself should call startForeground
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ context.startForegroundService(intent);
+ } else {
+ context.startService(intent);
+ }
+ }
+ }
+
+ private void stopCapsuleService() {
+ Context context = getContext();
+ if (context != null) {
+ Intent intent = new Intent(context, CapsuleService.class);
+ context.stopService(intent);
+ }
}
private void loadThemePreference() {
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
index f47be61..b6c3830 100644
--- 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
@@ -131,10 +131,13 @@ public class SidebarFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- if (context instanceof OnSidebarItemSelectedListener) {
+ if (getParentFragment() instanceof OnSidebarItemSelectedListener) {
+ listener = (OnSidebarItemSelectedListener) getParentFragment();
+ } else if (context instanceof OnSidebarItemSelectedListener) {
listener = (OnSidebarItemSelectedListener) context;
} else {
- throw new RuntimeException(context.toString() + " must implement OnSidebarItemSelectedListener");
+ // throw new RuntimeException(context.toString() + " must implement OnSidebarItemSelectedListener");
+ // Allow null listener for now to avoid crashes during refactoring
}
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java
index b97cf59..9b965d4 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/TaskListActivity.java
@@ -36,21 +36,13 @@ public class TaskListActivity extends AppCompatActivity implements TaskListAdapt
// Initialize Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
-
- // Remove default title
- if (getSupportActionBar() != null) {
- getSupportActionBar().setDisplayShowTitleEnabled(false);
- getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- }
-
- // Setup custom "Notes" navigation
- View notesTitle = findViewById(R.id.tv_toolbar_title_notes);
- if (notesTitle != null) {
- notesTitle.setOnClickListener(v -> {
- finish();
- overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);
- });
+ if (toolbar != null) {
+ setSupportActionBar(toolbar);
+ // Remove default title
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ }
}
// Setup Navigation Icon (Back Button) - REMOVED as per new requirement
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
index 574d462..1217078 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/viewmodel/NotesListViewModel.java
@@ -36,7 +36,7 @@ import java.util.List;
*
*
* @see NotesRepository
- * @see Note
+ * @see net.micode.notes.model.Note
*/
public class NotesListViewModel extends ViewModel {
private static final String TAG = "NotesListViewModel";
@@ -46,6 +46,9 @@ public class NotesListViewModel extends ViewModel {
// 笔记列表LiveData
private final MutableLiveData> notesLiveData = new MutableLiveData<>();
+ // 文件夹列表LiveData (用于顶部Tab)
+ private final MutableLiveData> foldersLiveData = new MutableLiveData<>();
+
// 加载状态LiveData
private final MutableLiveData isLoading = new MutableLiveData<>(false);
@@ -54,9 +57,16 @@ public class NotesListViewModel extends ViewModel {
// 选中的笔记ID集合
private final HashSet selectedNoteIds = new HashSet<>();
+ private final MutableLiveData> selectedIdsLiveData = new MutableLiveData<>(new HashSet<>());
+
+ // 是否处于多选模式
+ private final MutableLiveData isSelectionMode = new MutableLiveData<>(false);
// 当前文件夹ID
- private long currentFolderId = Notes.ID_ROOT_FOLDER;
+ private long currentFolderId = Notes.ID_ALL_NOTES_FOLDER; // Default to "All"
+
+ // 当前文件夹ID LiveData (用于UI监听)
+ private final MutableLiveData currentFolderIdLiveData = new MutableLiveData<>((long) Notes.ID_ALL_NOTES_FOLDER);
// 文件夹路径LiveData(用于面包屑导航)
private final MutableLiveData> folderPathLiveData = new MutableLiveData<>();
@@ -104,6 +114,30 @@ public class NotesListViewModel extends ViewModel {
return errorMessage;
}
+ /**
+ * 获取文件夹列表LiveData (顶部Tab)
+ */
+ public MutableLiveData> getFoldersLiveData() {
+ return foldersLiveData;
+ }
+
+ /**
+ * 获取是否处于多选模式
+ */
+ public MutableLiveData getIsSelectionMode() {
+ return isSelectionMode;
+ }
+
+ /**
+ * 设置多选模式
+ */
+ public void setIsSelectionMode(boolean isSelection) {
+ isSelectionMode.postValue(isSelection);
+ if (!isSelection) {
+ clearSelection();
+ }
+ }
+
/**
* 加载笔记列表
*
@@ -114,8 +148,12 @@ public class NotesListViewModel extends ViewModel {
*/
public void loadNotes(long folderId) {
this.currentFolderId = folderId;
+ this.currentFolderIdLiveData.postValue(folderId);
isLoading.postValue(true);
errorMessage.postValue(null);
+
+ // 退出选择模式
+ setIsSelectionMode(false);
// 加载文件夹路径
repository.getFolderPath(folderId, new NotesRepository.Callback>() {
@@ -129,8 +167,41 @@ public class NotesListViewModel extends ViewModel {
Log.e(TAG, "Failed to load folder path", error);
}
});
+
+ // 加载子文件夹 (Category Tabs) - Always load root folders to keep tabs visible
+ repository.getSubFolders(Notes.ID_ROOT_FOLDER, new NotesRepository.Callback>() {
+ @Override
+ public void onSuccess(List folders) {
+ // Construct the display list with "All" and "Uncategorized"
+ List displayFolders = new ArrayList<>();
+
+ // 1. "All" Folder (Virtual)
+ NotesRepository.NoteInfo allFolder = new NotesRepository.NoteInfo();
+ allFolder.setId(Notes.ID_ALL_NOTES_FOLDER);
+ allFolder.snippet = "所有"; // Name
+ displayFolders.add(allFolder);
+
+ // 2. Real Folders (from DB)
+ if (folders != null) {
+ displayFolders.addAll(folders);
+ }
+
+ // 3. "Uncategorized" Folder (Actually Root Folder)
+ NotesRepository.NoteInfo uncategorizedFolder = new NotesRepository.NoteInfo();
+ uncategorizedFolder.setId(Notes.ID_ROOT_FOLDER);
+ uncategorizedFolder.snippet = "未分类"; // Custom Name for Root
+ displayFolders.add(uncategorizedFolder);
+
+ foldersLiveData.postValue(displayFolders);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ Log.e(TAG, "Failed to load sub-folders", error);
+ }
+ });
- // 加载笔记
+ // 加载笔记 (No folders)
repository.getNotes(folderId, new NotesRepository.Callback>() {
@Override
public void onSuccess(List notes) {
@@ -283,6 +354,14 @@ public class NotesListViewModel extends ViewModel {
});
}
+ public MutableLiveData> getSelectedIdsLiveData() {
+ return selectedIdsLiveData;
+ }
+
+ public void notifySelectionChanged() {
+ selectedIdsLiveData.postValue(new HashSet<>(selectedNoteIds));
+ }
+
/**
* 切换笔记选中状态
*
@@ -295,6 +374,7 @@ public class NotesListViewModel extends ViewModel {
} else {
selectedNoteIds.remove(noteId);
}
+ notifySelectionChanged();
}
/**
@@ -310,6 +390,7 @@ public class NotesListViewModel extends ViewModel {
selectedNoteIds.add(note.getId());
}
}
+ notifySelectionChanged();
}
/**
@@ -320,6 +401,7 @@ public class NotesListViewModel extends ViewModel {
*/
public void deselectAllNotes() {
selectedNoteIds.clear();
+ notifySelectionChanged();
}
/**
@@ -363,6 +445,13 @@ public class NotesListViewModel extends ViewModel {
return currentFolderId;
}
+ /**
+ * 获取当前文件夹ID LiveData
+ */
+ public MutableLiveData getCurrentFolderIdLiveData() {
+ return currentFolderIdLiveData;
+ }
+
/**
* 设置当前文件夹
*
@@ -432,6 +521,7 @@ public class NotesListViewModel extends ViewModel {
*/
public void clearSelection() {
selectedNoteIds.clear();
+ notifySelectionChanged();
}
/**
@@ -745,4 +835,33 @@ public class NotesListViewModel extends ViewModel {
selectedNoteIds.clear();
Log.d(TAG, "ViewModel cleared");
}
-}
+
+ /**
+ * 切换单个笔记的锁定状态
+ *
+ * @param noteId 笔记ID
+ * @param isLocked 是否锁定
+ */
+ public void toggleNoteLock(long noteId, boolean isLocked) {
+ List ids = new ArrayList<>();
+ ids.add(noteId);
+
+ isLoading.postValue(true);
+ repository.batchLock(ids, isLocked, new NotesRepository.Callback() {
+ @Override
+ public void onSuccess(Integer rowsAffected) {
+ isLoading.postValue(false);
+ refreshNotes();
+ Log.d(TAG, "Successfully toggled lock state for note: " + noteId);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ isLoading.postValue(false);
+ String message = "锁定操作失败: " + error.getMessage();
+ errorMessage.postValue(message);
+ Log.e(TAG, message, error);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/activity_settings.xml b/src/Notesmaster/app/src/main/res/layout/activity_settings.xml
index 791a380..b331ecc 100644
--- a/src/Notesmaster/app/src/main/res/layout/activity_settings.xml
+++ b/src/Notesmaster/app/src/main/res/layout/activity_settings.xml
@@ -1,15 +1,5 @@
-
-
-
-
-
-
-
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/activity_task_list.xml b/src/Notesmaster/app/src/main/res/layout/activity_task_list.xml
index 51fbca5..7c108ae 100644
--- a/src/Notesmaster/app/src/main/res/layout/activity_task_list.xml
+++ b/src/Notesmaster/app/src/main/res/layout/activity_task_list.xml
@@ -5,47 +5,12 @@
android:layout_height="match_parent"
android:background="@color/background_color">
-
-
-
-
-
-
-
-
-
-
+ android:paddingBottom="80dp"/>
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/note_item.xml b/src/Notesmaster/app/src/main/res/layout/note_item.xml
index a479806..1718f1a 100644
--- a/src/Notesmaster/app/src/main/res/layout/note_item.xml
+++ b/src/Notesmaster/app/src/main/res/layout/note_item.xml
@@ -1,122 +1,122 @@
-
-
-
-
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="6dp"
+ app:cardCornerRadius="16dp"
+ app:cardElevation="2dp"
+ app:cardBackgroundColor="@color/bg_white">
-
+ android:orientation="vertical"
+ android:padding="16dp">
-
-
+
+
+
+
+
+
+
+
+
-
-
+ android:visibility="visible" />
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+ android:layout_marginTop="12dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+
+
+
+
+
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/note_list.xml b/src/Notesmaster/app/src/main/res/layout/note_list.xml
index 3d88391..4571f8f 100644
--- a/src/Notesmaster/app/src/main/res/layout/note_list.xml
+++ b/src/Notesmaster/app/src/main/res/layout/note_list.xml
@@ -1,106 +1,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/sidebar_layout.xml b/src/Notesmaster/app/src/main/res/layout/sidebar_layout.xml
index 79d28b6..b762932 100644
--- a/src/Notesmaster/app/src/main/res/layout/sidebar_layout.xml
+++ b/src/Notesmaster/app/src/main/res/layout/sidebar_layout.xml
@@ -10,7 +10,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="vertical"
- android:background="@android:color/white"
+ android:background="@color/bg_white"
android:elevation="8dp">
@@ -19,7 +19,7 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
- android:background="@android:color/white"
+ android:background="@color/bg_white"
android:elevation="4dp">
@@ -30,7 +30,8 @@
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="关闭侧栏"
android:padding="8dp"
- android:src="@android:drawable/ic_menu_close_clear_cancel" />
+ android:src="@android:drawable/ic_menu_close_clear_cancel"
+ android:tint="@color/text_color_primary" />
+ android:src="@android:drawable/ic_input_add"
+ android:tint="@color/text_color_primary" />
@@ -64,7 +66,7 @@
android:drawablePadding="12dp"
android:text="@string/root_folder_name"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@@ -105,7 +107,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_sync"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@@ -119,7 +121,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_login"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@@ -133,7 +135,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_export"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@@ -147,7 +149,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_templates"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@@ -161,7 +163,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_settings"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
@@ -182,7 +184,7 @@
android:drawablePadding="12dp"
android:text="@string/menu_trash"
android:textSize="16sp"
- android:textColor="@android:color/black"
+ android:textColor="@color/text_color_primary"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" />
-
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/task_list_item.xml b/src/Notesmaster/app/src/main/res/layout/task_list_item.xml
index 65706b2..2d9817c 100644
--- a/src/Notesmaster/app/src/main/res/layout/task_list_item.xml
+++ b/src/Notesmaster/app/src/main/res/layout/task_list_item.xml
@@ -1,70 +1,84 @@
-
-
-
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="6dp"
+ app:cardCornerRadius="16dp"
+ app:cardElevation="2dp"
+ app:cardBackgroundColor="@color/bg_white">
+ android:orientation="horizontal"
+ android:padding="16dp"
+ android:gravity="center_vertical">
-
+
-
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingStart="12dp"
+ android:paddingEnd="8dp">
+
+ android:textSize="16sp"
+ android:textColor="@color/text_color_primary"
+ android:ellipsize="end"
+ android:maxLines="2" />
-
+ android:orientation="horizontal"
+ android:layout_marginTop="4dp"
+ android:gravity="center_vertical">
+
+
+
+
+
-
-
-
+
+
-
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/values/colors.xml b/src/Notesmaster/app/src/main/res/values/colors.xml
index c07b5f2..bffeabd 100644
--- a/src/Notesmaster/app/src/main/res/values/colors.xml
+++ b/src/Notesmaster/app/src/main/res/values/colors.xml
@@ -19,23 +19,23 @@
#335b5b5b
#FFFFFF
#000000
- #263238
- #FFFFFF
- #E8E8E8
- #000000
- #808080
+ #F7F7F7
+ #212121
+ #F7F7F7
+ #212121
+ #757575
#FFC107
- #000000
- #808080
+ #212121
+ #757575
- #FFF9C4
- #B3E5FC
+ #FFF8D6
+ #E3F2FD
#FFFFFF
- #C8E6C9
- #FFCDD2
+ #E0F2F1
+ #FCE4EC
#212121
diff --git a/src/Notesmaster/app/src/main/res/values/strings.xml b/src/Notesmaster/app/src/main/res/values/strings.xml
index 993846d..1050839 100644
--- a/src/Notesmaster/app/src/main/res/values/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values/strings.xml
@@ -179,4 +179,13 @@
Save as template
Picture
Rich Text
+
+
+ 小米便签-全局速记服务,用于监听剪贴板和应用来源。
+ 需要悬浮窗权限
+ 全局速记胶囊需要悬浮窗权限才能显示在其他应用上层。
+ 需要无障碍服务权限
+ 全局速记胶囊需要无障碍服务权限来监听剪贴板和获取来源应用。
+ 全局速记胶囊
+ 开启侧边悬浮胶囊,支持跨应用拖拽和剪贴板速记
diff --git a/src/Notesmaster/app/src/main/res/values/styles.xml b/src/Notesmaster/app/src/main/res/values/styles.xml
index 67bc75f..4d1e7fd 100644
--- a/src/Notesmaster/app/src/main/res/values/styles.xml
+++ b/src/Notesmaster/app/src/main/res/values/styles.xml
@@ -36,6 +36,15 @@
+
+