From 75cb7ec7fde5d0e600fa0be478e45204189005a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?=
Date: Tue, 20 Jan 2026 18:10:41 +0800
Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=87=8D=E5=A4=8D=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../app/src/main/AndroidManifest.xml | 4 +-
.../java/net/micode/notes/MainActivity.java | 124 +-
.../java/net/micode/notes/data/Notes.java | 6 +
.../notes/data/NotesDatabaseHelper.java | 19 +-
.../java/net/micode/notes/tool/DataUtils.java | 2 +-
.../notes/ui/GridSpacingItemDecoration.java | 68 -
.../notes/ui/LayoutManagerController.java | 294 ---
.../micode/notes/ui/LayoutSettingsDialog.java | 199 --
.../java/net/micode/notes/ui/LayoutType.java | 61 -
.../net/micode/notes/ui/NoteEditActivity.java | 102 +-
.../net/micode/notes/ui/NoteItemData.java | 18 +
.../micode/notes/ui/NoteItemDecoration.java | 73 -
.../net/micode/notes/ui/NoteViewHolder.java | 54 -
.../micode/notes/ui/NotesListActivity.java | 1761 ++++++-----------
.../notes/ui/NotesRecyclerViewAdapter.java | 465 -----
.../StaggeredGridSpacingItemDecoration.java | 70 -
.../src/main/res/drawable/list_divider.xml | 20 -
.../app/src/main/res/layout/activity_main.xml | 40 +-
.../res/layout/layout_settings_dialog.xml | 103 -
.../app/src/main/res/layout/note_edit.xml | 572 +++---
.../app/src/main/res/layout/note_item.xml | 107 +-
.../app/src/main/res/layout/note_list.xml | 156 +-
.../app/src/main/res/menu/note_list.xml | 4 -
.../src/main/res/values-zh-rCN/strings.xml | 12 +-
.../app/src/main/res/values/colors.xml | 3 +
.../app/src/main/res/values/strings.xml | 31 +-
.../app/src/main/res/values/themes.xml | 12 +-
27 files changed, 1380 insertions(+), 3000 deletions(-)
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/GridSpacingItemDecoration.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutManagerController.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutSettingsDialog.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutType.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemDecoration.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteViewHolder.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerViewAdapter.java
delete mode 100644 src/Notesmaster/app/src/main/java/net/micode/notes/ui/StaggeredGridSpacingItemDecoration.java
delete mode 100644 src/Notesmaster/app/src/main/res/drawable/list_divider.xml
delete mode 100644 src/Notesmaster/app/src/main/res/layout/layout_settings_dialog.xml
diff --git a/src/Notesmaster/app/src/main/AndroidManifest.xml b/src/Notesmaster/app/src/main/AndroidManifest.xml
index b93c62f..773066d 100644
--- a/src/Notesmaster/app/src/main/AndroidManifest.xml
+++ b/src/Notesmaster/app/src/main/AndroidManifest.xml
@@ -42,7 +42,7 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
- android:theme="@android:style/Theme.Holo.Light"
+ android:theme="@style/Theme.Notesmaster"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan"
android:exported="true">
@@ -60,7 +60,7 @@
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
- android:theme="@style/NoteTheme"
+ android:theme="@style/Theme.Notesmaster.Edit"
android:exported="true">
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/MainActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/MainActivity.java
index 3716891..930f6fe 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/MainActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/MainActivity.java
@@ -1,28 +1,39 @@
package net.micode.notes;
+import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.ui.SidebarFragment;
/**
* 主活动类
*
- * 应用的主入口,负责初始化主界面并处理窗口边距。
+ * 应用的主入口,负责启动笔记列表界面
* 支持边到边显示模式,自动适配系统栏的边距。
*
*/
-public class MainActivity extends AppCompatActivity {
+public class MainActivity extends AppCompatActivity implements SidebarFragment.OnSidebarItemSelectedListener {
+
+ private static final String TAG = "MainActivity";
+ private DrawerLayout drawerLayout;
/**
* 创建活动
*
* 初始化活动界面,启用边到边显示模式,并设置窗口边距监听器。
*
- *
+ *
* @param savedInstanceState 保存的实例状态,用于恢复活动状态
*/
@Override
@@ -31,13 +42,118 @@ public class MainActivity extends AppCompatActivity {
// 启用边到边显示模式
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
+
+ // 初始化DrawerLayout
+ drawerLayout = findViewById(R.id.drawer_layout);
+ if (drawerLayout != null) {
+ // 设置侧栏在左侧
+ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
+
+ // 设置监听器:侧栏关闭时更新状态
+ drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ // 侧栏滑动时
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ // 侧栏打开时
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ // 侧栏关闭时
+ }
+
+ @Override
+ public void onDrawerStateChanged(int newState) {
+ // 侧栏状态改变时
+ }
+ });
+ }
+
// 设置窗口边距监听器,自动适配系统栏
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main_content), (v, insets) -> {
// 获取系统栏边距
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
// 设置视图内边距以适配系统栏
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
+
+ // 启动NotesListActivity作为主界面
+ Intent intent = new Intent(this, net.micode.notes.ui.NotesListActivity.class);
+ startActivity(intent);
+ }
+
+ // ==================== SidebarFragment.OnSidebarItemSelectedListener 实现 ====================
+
+ @Override
+ public void onFolderSelected(long folderId) {
+ Log.d(TAG, "Folder selected: " + folderId);
+ // 打开侧栏中的文件夹:不关闭侧栏,直接切换视图
+ // 这个回调通常用于侧栏中的文件夹项双击
+ // 实际跳转逻辑应该在NotesListActivity中处理
+ closeSidebar();
+ }
+
+ @Override
+ public void onTrashSelected() {
+ Log.d(TAG, "Trash selected");
+ // TODO: 实现跳转到回收站
+ // 关闭侧栏
+ closeSidebar();
+ }
+
+ @Override
+ public void onSyncSelected() {
+ Log.d(TAG, "Sync selected");
+ // TODO: 实现同步功能
+ }
+
+ @Override
+ public void onLoginSelected() {
+ Log.d(TAG, "Login selected");
+ // TODO: 实现登录功能
+ }
+
+ @Override
+ public void onExportSelected() {
+ Log.d(TAG, "Export selected");
+ // TODO: 实现导出功能
+ }
+
+ @Override
+ public void onSettingsSelected() {
+ Log.d(TAG, "Settings selected");
+ // 打开设置界面
+ Intent intent = new Intent(this, net.micode.notes.ui.NotesPreferenceActivity.class);
+ startActivity(intent);
+ // 关闭侧栏
+ closeSidebar();
+ }
+
+ @Override
+ public void onCreateFolder() {
+ Log.d(TAG, "Create folder");
+ // 创建文件夹功能由SidebarFragment内部处理
+ // 这里不需要做任何事情
+ }
+
+ @Override
+ public void onCloseSidebar() {
+ closeSidebar();
+ }
+
+ // ==================== 私有方法 ====================
+
+ /**
+ * 关闭侧栏
+ */
+ private void closeSidebar() {
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(Gravity.LEFT);
+ }
}
}
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java
index 8b1f853..71f11fa 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java
@@ -235,6 +235,12 @@ public class Notes {
* Type : INTEGER (long)
*/
public static final String VERSION = "version";
+
+ /**
+ * Sign to indicate the note is pinned to top or not
+ * Type : INTEGER
+ */
+ public static final String TOP = "top";
}
public interface DataColumns {
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java
index 558caf7..c862ead 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java
@@ -66,11 +66,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* 数据库版本号
*
- * 当前数据库版本为4,用于跟踪数据库结构变更。
+ * 当前数据库版本为5,用于跟踪数据库结构变更。
* 当数据库版本变更时,onUpgrade方法会被调用以执行升级逻辑。
*
*/
- private static final int DB_VERSION = 4;
+ private static final int DB_VERSION = 5;
/**
* 数据库表名常量接口
@@ -471,7 +471,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
* @param context 应用上下文
* @return NotesDatabaseHelper单例实例
*/
- static synchronized NotesDatabaseHelper getInstance(Context context) {
+ public static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
@@ -595,4 +595,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
+
+ /**
+ * 升级数据库到V5版本
+ *
+ * 添加TOP列到note表,用于标记笔记是否置顶。
+ *
+ *
+ * @param db SQLiteDatabase实例
+ */
+ private void upgradeToV5(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TOP
+ + " INTEGER NOT NULL DEFAULT 0");
+ }
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java
index 0872c4c..d982351 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java
@@ -29,7 +29,7 @@ import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
-import net.micode.notes.ui.NotesRecyclerViewAdapter.AppWidgetAttribute;
+import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/GridSpacingItemDecoration.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/GridSpacingItemDecoration.java
deleted file mode 100644
index 1a7d15d..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/GridSpacingItemDecoration.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.graphics.Rect;
-import android.view.View;
-import androidx.recyclerview.widget.RecyclerView;
-
-/**
- * 网格布局间距装饰类
- *
- * 为网格布局的RecyclerView添加统一的间距,确保每个网格项之间有合适的间隔。
- * 支持是否包含边缘间距的配置。
- *
- */
-public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
- private int spanCount;
- private int spacing;
- private boolean includeEdge;
-
- /**
- * 构造函数
- * @param spanCount 网格列数
- * @param spacing 间距大小(像素)
- * @param includeEdge 是否包含边缘间距
- */
- public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
- this.spanCount = spanCount;
- this.spacing = spacing;
- this.includeEdge = includeEdge;
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- int position = parent.getChildAdapterPosition(view);
- int column = position % spanCount;
-
- if (includeEdge) {
- outRect.left = spacing - column * spacing / spanCount;
- outRect.right = (column + 1) * spacing / spanCount;
-
- if (position < spanCount) {
- outRect.top = spacing;
- }
- outRect.bottom = spacing;
- } else {
- outRect.left = column * spacing / spanCount;
- outRect.right = spacing - (column + 1) * spacing / spanCount;
- if (position >= spanCount) {
- outRect.top = spacing;
- }
- }
- }
-}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutManagerController.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutManagerController.java
deleted file mode 100644
index 0024c82..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutManagerController.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * 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.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.StaggeredGridLayoutManager;
-
-/**
- * 布局管理器控制类
- *
- * 负责管理RecyclerView的布局切换,提供平滑的布局过渡效果。
- * 支持线性布局、网格布局和瀑布流布局的无缝切换。
- * 保存用户的布局偏好设置,确保应用重启后保持布局状态。
- *
- */
-public class LayoutManagerController {
- private static final String TAG = "LayoutManagerController";
- private static final String PREF_LAYOUT_TYPE = "layout_type";
- private static final String PREF_GRID_SPAN_COUNT = "grid_span_count";
- private static final String PREF_ITEM_SPACING = "item_spacing";
-
- private Context mContext;
- private RecyclerView mRecyclerView;
- private RecyclerView.LayoutManager mCurrentLayoutManager;
- private LayoutType mCurrentLayoutType;
- private SharedPreferences mPreferences;
- private LayoutChangeListener mListener;
-
- /**
- * 布局变化监听器接口
- */
- public interface LayoutChangeListener {
- void onLayoutChanged(LayoutType newLayoutType);
- void onLayoutChangeFailed(Exception e);
- }
-
- /**
- * 构造函数
- * @param context 上下文
- * @param recyclerView RecyclerView实例
- * @param listener 布局变化监听器
- */
- public LayoutManagerController(Context context, RecyclerView recyclerView, LayoutChangeListener listener) {
- mContext = context;
- mRecyclerView = recyclerView;
- mListener = listener;
- mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
- // 加载保存的布局类型
- String savedLayoutKey = mPreferences.getString(PREF_LAYOUT_TYPE, LayoutType.LINEAR.getKey());
- mCurrentLayoutType = LayoutType.fromKey(savedLayoutKey);
-
- Log.d(TAG, "LayoutManagerController initialized with layout: " + mCurrentLayoutType.getDisplayName());
- }
-
- /**
- * 初始化布局
- */
- public void initializeLayout() {
- switchLayout(mCurrentLayoutType, false);
- }
-
- /**
- * 切换布局
- * @param layoutType 目标布局类型
- * @param animate 是否播放动画
- * @return 切换是否成功
- */
- public boolean switchLayout(LayoutType layoutType, boolean animate) {
- if (layoutType == mCurrentLayoutType) {
- Log.d(TAG, "Already in " + layoutType.getDisplayName() + " mode");
- return true;
- }
-
- long startTime = System.currentTimeMillis();
-
- try {
- // 保存滚动位置
- int scrollPosition = 0;
- if (mRecyclerView.getLayoutManager() != null) {
- if (mRecyclerView.getLayoutManager() instanceof LinearLayoutManager) {
- LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
- scrollPosition = layoutManager.findFirstVisibleItemPosition();
- } else if (mRecyclerView.getLayoutManager() instanceof GridLayoutManager) {
- GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
- scrollPosition = layoutManager.findFirstVisibleItemPosition();
- } else if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
- StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) mRecyclerView.getLayoutManager();
- int[] positions = layoutManager.findFirstVisibleItemPositions(null);
- scrollPosition = positions.length > 0 ? positions[0] : 0;
- }
- }
-
- // 移除所有装饰
- int decorationCount = mRecyclerView.getItemDecorationCount();
- for (int i = decorationCount - 1; i >= 0; i--) {
- mRecyclerView.removeItemDecorationAt(i);
- }
-
- // 创建新的布局管理器
- RecyclerView.LayoutManager newLayoutManager = createLayoutManager(layoutType);
-
- // 应用新布局
- if (animate) {
- mRecyclerView.setLayoutManager(newLayoutManager);
- } else {
- mRecyclerView.setLayoutManager(newLayoutManager);
- }
-
- // 添加间距装饰
- addItemDecoration(layoutType);
-
- // 保存布局偏好
- saveLayoutPreference(layoutType);
-
- // 恢复滚动位置
- restoreScrollPosition(scrollPosition);
-
- mCurrentLayoutManager = newLayoutManager;
- mCurrentLayoutType = layoutType;
-
- long duration = System.currentTimeMillis() - startTime;
- Log.d(TAG, "Layout switched to " + layoutType.getDisplayName() + " in " + duration + "ms");
-
- // 通知监听器
- if (mListener != null) {
- mListener.onLayoutChanged(layoutType);
- }
-
- return true;
-
- } catch (Exception e) {
- Log.e(TAG, "Failed to switch layout: " + e.getMessage(), e);
-
- // 通知监听器失败
- if (mListener != null) {
- mListener.onLayoutChangeFailed(e);
- }
-
- return false;
- }
- }
-
- /**
- * 创建布局管理器
- * @param layoutType 布局类型
- * @return 布局管理器实例
- */
- private RecyclerView.LayoutManager createLayoutManager(LayoutType layoutType) {
- int spanCount = getGridSpanCount();
- switch (layoutType) {
- case LINEAR:
- return new LinearLayoutManager(mContext);
- case GRID:
- return new GridLayoutManager(mContext, spanCount);
- case STAGGERED:
- return new StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.VERTICAL);
- default:
- return new LinearLayoutManager(mContext);
- }
- }
-
- /**
- * 添加间距装饰
- * @param layoutType 布局类型
- */
- private void addItemDecoration(LayoutType layoutType) {
- int spacing = getItemSpacing();
-
- switch (layoutType) {
- case LINEAR:
- mRecyclerView.addItemDecoration(new NoteItemDecoration(mContext));
- break;
- case GRID:
- mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(getGridSpanCount(), spacing, true));
- break;
- case STAGGERED:
- mRecyclerView.addItemDecoration(new StaggeredGridSpacingItemDecoration(getGridSpanCount(), spacing, true));
- break;
- }
- }
-
- /**
- * 恢复滚动位置
- * @param position 滚动位置
- */
- private void restoreScrollPosition(int position) {
- if (mRecyclerView.getAdapter() != null && position >= 0 && position < mRecyclerView.getAdapter().getItemCount()) {
- mRecyclerView.scrollToPosition(position);
- }
- }
-
- /**
- * 保存布局偏好
- * @param layoutType 布局类型
- */
- private void saveLayoutPreference(LayoutType layoutType) {
- mPreferences.edit()
- .putString(PREF_LAYOUT_TYPE, layoutType.getKey())
- .apply();
- }
-
- /**
- * 获取网格列数
- * @return 网格列数
- */
- public int getGridSpanCount() {
- return mPreferences.getInt(PREF_GRID_SPAN_COUNT, 2);
- }
-
- /**
- * 设置网格列数
- * @param spanCount 网格列数
- */
- public void setGridSpanCount(int spanCount) {
- if (spanCount < 1) spanCount = 1;
- if (spanCount > 4) spanCount = 4;
-
- mPreferences.edit()
- .putInt(PREF_GRID_SPAN_COUNT, spanCount)
- .apply();
-
- // 重新应用布局
- switchLayout(mCurrentLayoutType, true);
- }
-
- /**
- * 获取项目间距
- * @return 项目间距(像素)
- */
- public int getItemSpacing() {
- return mPreferences.getInt(PREF_ITEM_SPACING, 16);
- }
-
- /**
- * 设置项目间距
- * @param spacing 项目间距(像素)
- */
- public void setItemSpacing(int spacing) {
- if (spacing < 0) spacing = 0;
- if (spacing > 48) spacing = 48;
-
- mPreferences.edit()
- .putInt(PREF_ITEM_SPACING, spacing)
- .apply();
-
- // 重新应用布局
- switchLayout(mCurrentLayoutType, true);
- }
-
- /**
- * 获取当前布局类型
- * @return 当前布局类型
- */
- public LayoutType getCurrentLayoutType() {
- return mCurrentLayoutType;
- }
-
- /**
- * 获取下一个布局类型(循环切换)
- * @return 下一个布局类型
- */
- public LayoutType getNextLayoutType() {
- LayoutType[] types = LayoutType.values();
- int currentIndex = 0;
- for (int i = 0; i < types.length; i++) {
- if (types[i] == mCurrentLayoutType) {
- currentIndex = i;
- break;
- }
- }
- return types[(currentIndex + 1) % types.length];
- }
-}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutSettingsDialog.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutSettingsDialog.java
deleted file mode 100644
index e443472..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutSettingsDialog.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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.content.DialogInterface;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ArrayAdapter;
-import android.widget.SeekBar;
-import android.widget.Spinner;
-import android.widget.TextView;
-import net.micode.notes.R;
-
-/**
- * 布局设置对话框
- *
- * 提供布局类型选择、网格列数和项目间距的配置界面。
- * 支持实时预览布局效果。
- *
- */
-public class LayoutSettingsDialog {
- private Context mContext;
- private LayoutManagerController mLayoutManagerController;
- private AlertDialog mDialog;
- private Spinner mLayoutTypeSpinner;
- private SeekBar mGridColumnsSeekBar;
- private SeekBar mItemSpacingSeekBar;
- private TextView mGridColumnsValue;
- private TextView mItemSpacingValue;
-
- /**
- * 构造函数
- * @param context 上下文
- * @param layoutManagerController 布局管理器
- */
- public LayoutSettingsDialog(Context context, LayoutManagerController layoutManagerController) {
- mContext = context;
- mLayoutManagerController = layoutManagerController;
- }
-
- /**
- * 显示布局设置对话框
- */
- public void show() {
- AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- builder.setTitle(R.string.layout_settings_title);
-
- View dialogView = LayoutInflater.from(mContext).inflate(R.layout.layout_settings_dialog, null);
- builder.setView(dialogView);
-
- initViews(dialogView);
- setupListeners();
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- saveSettings();
- }
- });
-
- builder.setNegativeButton(android.R.string.cancel, null);
-
- mDialog = builder.create();
- mDialog.show();
- }
-
- /**
- * 初始化视图
- * @param dialogView 对话框视图
- */
- private void initViews(View dialogView) {
- mLayoutTypeSpinner = (Spinner) dialogView.findViewById(R.id.layout_type_spinner);
- mGridColumnsSeekBar = (SeekBar) dialogView.findViewById(R.id.grid_columns_seekbar);
- mItemSpacingSeekBar = (SeekBar) dialogView.findViewById(R.id.item_spacing_seekbar);
- mGridColumnsValue = (TextView) dialogView.findViewById(R.id.grid_columns_value);
- mItemSpacingValue = (TextView) dialogView.findViewById(R.id.item_spacing_value);
-
- // 设置布局类型选项
- ArrayAdapter layoutAdapter = new ArrayAdapter<>(
- mContext, android.R.layout.simple_spinner_item, LayoutType.values());
- mLayoutTypeSpinner.setAdapter(layoutAdapter);
-
- // 设置当前值
- LayoutType currentLayout = mLayoutManagerController.getCurrentLayoutType();
- mLayoutTypeSpinner.setSelection(currentLayout.ordinal());
-
- int gridColumns = mLayoutManagerController.getGridSpanCount();
- mGridColumnsSeekBar.setProgress(gridColumns - 1);
- mGridColumnsValue.setText(String.valueOf(gridColumns));
-
- int itemSpacing = mLayoutManagerController.getItemSpacing();
- mItemSpacingSeekBar.setProgress(itemSpacing / 2);
- mItemSpacingValue.setText(String.valueOf(itemSpacing));
-
- // 根据布局类型启用/禁用控件
- updateControlStates(currentLayout);
- }
-
- /**
- * 设置监听器
- */
- private void setupListeners() {
- mLayoutTypeSpinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(android.widget.AdapterView> parent, View view, int position, long id) {
- LayoutType selectedLayout = (LayoutType) parent.getItemAtPosition(position);
- updateControlStates(selectedLayout);
- }
-
- @Override
- public void onNothingSelected(android.widget.AdapterView> parent) {
- }
- });
-
- mGridColumnsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- int columns = progress + 1;
- mGridColumnsValue.setText(String.valueOf(columns));
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- }
- });
-
- mItemSpacingSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- int spacing = progress * 2;
- mItemSpacingValue.setText(String.valueOf(spacing));
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- }
- });
- }
-
- /**
- * 更新控件状态
- * @param layoutType 布局类型
- */
- private void updateControlStates(LayoutType layoutType) {
- switch (layoutType) {
- case LINEAR:
- mGridColumnsSeekBar.setEnabled(false);
- mItemSpacingSeekBar.setEnabled(false);
- break;
- case GRID:
- case STAGGERED:
- mGridColumnsSeekBar.setEnabled(true);
- mItemSpacingSeekBar.setEnabled(true);
- break;
- }
- }
-
- /**
- * 保存设置
- */
- private void saveSettings() {
- LayoutType selectedLayout = (LayoutType) mLayoutTypeSpinner.getSelectedItem();
- int gridColumns = mGridColumnsSeekBar.getProgress() + 1;
- int itemSpacing = mItemSpacingSeekBar.getProgress() * 2;
-
- mLayoutManagerController.setGridSpanCount(gridColumns);
- mLayoutManagerController.setItemSpacing(itemSpacing);
-
- if (selectedLayout != mLayoutManagerController.getCurrentLayoutType()) {
- mLayoutManagerController.switchLayout(selectedLayout, true);
- }
-
- mDialog.dismiss();
- }
-}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutType.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutType.java
deleted file mode 100644
index a94eec4..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/LayoutType.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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;
-
-/**
- * 布局类型枚举
- *
- * 定义笔记列表支持的所有布局类型,包括线性布局、网格布局和瀑布流布局。
- * 每种布局类型都有对应的显示名称和描述。
- *
- */
-public enum LayoutType {
- LINEAR("linear", "列表布局", "传统的垂直列表布局,适合大量笔记浏览"),
- GRID("grid", "网格布局", "网格排列布局,适合快速浏览和预览"),
- STAGGERED("staggered", "瀑布流布局", "错落有致的布局,适合不同长度的笔记展示");
-
- private final String key;
- private final String displayName;
- private final String description;
-
- LayoutType(String key, String displayName, String description) {
- this.key = key;
- this.displayName = displayName;
- this.description = description;
- }
-
- public String getKey() {
- return key;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public String getDescription() {
- return description;
- }
-
- public static LayoutType fromKey(String key) {
- for (LayoutType type : values()) {
- if (type.key.equals(key)) {
- return type;
- }
- }
- return LINEAR;
- }
-}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
index 9544f6c..d4cecfe 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java
@@ -71,8 +71,11 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.material.appbar.MaterialToolbar;
-public class NoteEditActivity extends Activity implements OnClickListener,
+
+public class NoteEditActivity extends AppCompatActivity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
* 笔记头部视图持有者
@@ -143,6 +146,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
+ private MaterialToolbar toolbar;
+
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
@@ -160,6 +165,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
+ // 初始化Toolbar(使用MaterialToolbar,与列表页面一致)
+ MaterialToolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ }
+ toolbar.setNavigationOnClickListener(v -> finish());
+
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
@@ -284,6 +298,49 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
+ /**
+ * 初始化资源
+ *
+ * 初始化笔记编辑界面的所有UI组件引用和点击监听器
+ *
+ */
+ private void initResources() {
+ mHeadViewPanel = findViewById(R.id.note_title);
+ mNoteHeaderHolder = new HeadViewHolder();
+ mNoteHeaderHolder.tvModified = findViewById(R.id.tv_modified_date);
+ mNoteHeaderHolder.ivAlertIcon = findViewById(R.id.iv_alert_icon);
+ mNoteHeaderHolder.tvAlertDate = findViewById(R.id.tv_alert_date);
+ mNoteHeaderHolder.ibSetBgColor = findViewById(R.id.btn_set_bg_color);
+ mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
+ mNoteEditor = findViewById(R.id.note_edit_view);
+ mNoteEditorPanel = findViewById(R.id.sv_note_edit);
+ mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
+
+ // 设置背景颜色选择器的点击事件
+ for (int id : sBgSelectorBtnsMap.keySet()) {
+ ImageView iv = findViewById(id);
+ iv.setOnClickListener(this);
+ }
+
+ mFontSizeSelector = findViewById(R.id.font_size_selector);
+ for (int id : sFontSizeBtnsMap.keySet()) {
+ View view = findViewById(id);
+ view.setOnClickListener(this);
+ }
+
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
+ /**
+ * HACKME: Fix bug of store the resource id in shared preference.
+ * The id may larger than the length of resources, in this case,
+ * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
+ */
+ if (mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
+ mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
+ }
+ mEditTextList = findViewById(R.id.note_edit_list);
+ }
+
@Override
protected void onResume() {
super.onResume();
@@ -430,47 +487,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
- /**
- * 初始化资源
- *
- * 初始化所有UI组件的引用,设置点击监听器,
- * 并从SharedPreferences中读取字体大小设置。
- *
- */
- private void initResources() {
- mHeadViewPanel = findViewById(R.id.note_title);
- mNoteHeaderHolder = new HeadViewHolder();
- mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
- mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
- mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
- mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
- mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
- mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
- mNoteEditorPanel = findViewById(R.id.sv_note_edit);
- mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
- for (int id : sBgSelectorBtnsMap.keySet()) {
- ImageView iv = (ImageView) findViewById(id);
- iv.setOnClickListener(this);
- }
-
- mFontSizeSelector = findViewById(R.id.font_size_selector);
- for (int id : sFontSizeBtnsMap.keySet()) {
- View view = findViewById(id);
- view.setOnClickListener(this);
- };
- mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
- /**
- * HACKME: Fix bug of store the resource id in shared preference.
- * The id may larger than the length of resources, in this case,
- * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
- */
- if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
- mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
- }
- mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
- }
-
/**
* 活动暂停时保存笔记
*
@@ -528,7 +544,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- - View.VISIBLE);
+ View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java
index 024cb5d..46638e9 100644
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java
+++ b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java
@@ -59,6 +59,7 @@ public class NoteItemData {
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
+ NoteColumns.TOP, // 新增TOP字段
};
// 列索引常量,用于从查询结果中获取对应列的数据
@@ -74,6 +75,7 @@ public class NoteItemData {
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
+ private static final int TOP_COLUMN = 12;
// 笔记ID
private long mId;
@@ -99,6 +101,8 @@ public class NoteItemData {
private int mWidgetId;
// 桌面小部件类型
private int mWidgetType;
+ // 是否置顶
+ private boolean mIsPinned;
// 联系人名称(用于通话记录)
private String mName;
// 电话号码(用于通话记录)
@@ -140,6 +144,12 @@ public class NoteItemData {
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
+ // 读取置顶状态
+ if (cursor.getColumnCount() > TOP_COLUMN) {
+ mIsPinned = cursor.getInt(TOP_COLUMN) > 0;
+ } else {
+ mIsPinned = false;
+ }
mPhoneNumber = "";
// 如果是通话记录笔记,获取电话号码和联系人名称
@@ -377,6 +387,14 @@ public class NoteItemData {
return (mAlertDate > 0);
}
+ /**
+ * 判断是否置顶
+ * @return 如果置顶返回true
+ */
+ public boolean isPinned() {
+ return mIsPinned;
+ }
+
/**
* 判断是否为通话记录笔记
*
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemDecoration.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemDecoration.java
deleted file mode 100644
index 8011a60..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemDecoration.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
-import net.micode.notes.R;
-
-/**
- * 笔记列表项装饰
- *
- * 为RecyclerView添加分隔线效果,替代ListView的divider属性。
- *
- */
-public class NoteItemDecoration extends ItemDecoration {
- private Drawable mDivider;
- private int mDividerHeight;
-
- /**
- * 构造函数
- * @param context 上下文
- */
- public NoteItemDecoration(Context context) {
- mDivider = context.getResources().getDrawable(R.drawable.list_divider);
- if (mDivider != null) {
- mDividerHeight = 1;
- } else {
- mDividerHeight = mDivider.getIntrinsicHeight();
- }
- }
-
- @Override
- public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
- int left = parent.getPaddingLeft();
- int right = parent.getWidth() - parent.getPaddingRight();
-
- int childCount = parent.getChildCount();
- for (int i = 0; i < childCount - 1; i++) {
- View child = parent.getChildAt(i);
- RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
-
- int top = child.getBottom() + params.bottomMargin;
- int bottom = top + mDividerHeight;
-
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(c);
- }
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- outRect.bottom = mDividerHeight;
- }
-}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteViewHolder.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteViewHolder.java
deleted file mode 100644
index 29bb5ad..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteViewHolder.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.view.View;
-import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.TextView;
-import androidx.recyclerview.widget.RecyclerView;
-
-import net.micode.notes.R;
-
-/**
- * 笔记ViewHolder
- *
- * RecyclerView的ViewHolder实现,持有笔记列表项的所有视图引用。
- * 避免重复findViewById,提升列表滚动性能。
- *
- */
-public class NoteViewHolder extends RecyclerView.ViewHolder {
- public ImageView mAlert;
- public TextView mTitle;
- public TextView mTime;
- public TextView mCallName;
- public CheckBox mCheckBox;
-
- /**
- * 构造函数
- * @param itemView 列表项的根视图
- */
- public NoteViewHolder(View itemView) {
- super(itemView);
- // 查找所有子视图(只执行一次)
- mAlert = (ImageView) itemView.findViewById(R.id.iv_alert_icon);
- mTitle = (TextView) itemView.findViewById(R.id.tv_title);
- mTime = (TextView) itemView.findViewById(R.id.tv_time);
- mCallName = (TextView) itemView.findViewById(R.id.tv_name);
- mCheckBox = (CheckBox) itemView.findViewById(android.R.id.checkbox);
- }
-}
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 ff680a4..067fc53 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
@@ -16,1347 +16,834 @@
package net.micode.notes.ui;
-import android.app.Activity;
import android.app.AlertDialog;
-import android.app.Dialog;
import android.appwidget.AppWidgetManager;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Editable;
+import android.text.InputFilter;
import android.text.TextUtils;
-import android.text.TextWatcher;
import android.util.Log;
-import android.view.ActionMode;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.Display;
-import android.view.HapticFeedbackConstants;
-import android.view.LayoutInflater;
+import androidx.appcompat.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnCreateContextMenuListener;
-import android.view.View.OnTouchListener;
-import android.view.inputmethod.InputMethodManager;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
-import net.micode.notes.data.Notes.NoteColumns;
-import net.micode.notes.gtask.remote.GTaskSyncService;
-import net.micode.notes.model.WorkingNote;
-import net.micode.notes.tool.BackupUtils;
-import net.micode.notes.tool.DataUtils;
-import net.micode.notes.tool.ResourceParser;
-import net.micode.notes.ui.NotesRecyclerViewAdapter.AppWidgetAttribute;
-import net.micode.notes.widget.NoteWidgetProvider_2x;
-import net.micode.notes.widget.NoteWidgetProvider_4x;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.HashSet;
+import net.micode.notes.data.NotesRepository;
+import net.micode.notes.ui.NoteInfoAdapter;
+import net.micode.notes.viewmodel.NotesListViewModel;
-/**
- * 笔记列表活动
- *
- * 这个类是应用的主界面,用于显示笔记列表并提供笔记管理功能。
- * 支持创建、编辑、删除笔记,文件夹管理,笔记同步,以及桌面小部件集成。
- *
- * 主要功能:
- * 1. 显示笔记列表,支持按文件夹分类查看
- * 2. 创建新笔记和文件夹
- * 3. 批量选择和操作笔记(删除、移动)
- * 4. 笔记同步到 Google Tasks
- * 5. 导出笔记为文本文件
- * 6. 与桌面小部件集成
- *
- * @see NoteEditActivity
- * @see NotesListAdapter
- * @see GTaskSyncService
- */
-public class NotesListActivity extends Activity implements OnClickListener, NotesRecyclerViewAdapter.OnItemLongClickListener {
- // 笔记列表查询令牌
- private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
-
- // 文件夹列表查询令牌
- private static final int FOLDER_LIST_QUERY_TOKEN = 1;
-
- // 文件夹删除菜单ID
- private static final int MENU_FOLDER_DELETE = 0;
-
- // 文件夹查看菜单ID
- private static final int MENU_FOLDER_VIEW = 1;
-
- // 文件夹重命名菜单ID
- private static final int MENU_FOLDER_CHANGE_NAME = 2;
-
- // 首次使用应用时添加介绍笔记的偏好设置键
- private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
-
- /**
- * 列表编辑状态枚举
- *
- * 定义笔记列表的三种显示状态
- */
- private enum ListEditState {
- NOTE_LIST, // 笔记列表状态
- SUB_FOLDER, // 子文件夹状态
- CALL_RECORD_FOLDER // 通话记录文件夹状态
- };
-
- // 当前列表编辑状态
- private ListEditState mState;
-
- // 后台查询处理器
- private BackgroundQueryHandler mBackgroundQueryHandler;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
- // 笔记列表适配器
- private NotesRecyclerViewAdapter mNotesListAdapter;
-
- // 笔记列表视图
- private RecyclerView mNotesRecyclerView;
-
- // 布局管理器
- private LinearLayoutManager mLayoutManager;
-
- // 布局切换控制器
- private LayoutManagerController mLayoutManagerController;
-
- // 新建笔记按钮
- private Button mAddNewNote;
-
- // 是否正在分发触摸事件
- private boolean mDispatch;
-
- // 触摸事件的原始Y坐标
- private int mOriginY;
-
- // 分发触摸事件的Y坐标
- private int mDispatchY;
-
- // 标题栏文本视图
- private TextView mTitleBar;
-
- // 当前文件夹ID
- private long mCurrentFolderId;
-
- // 内容解析器
- private ContentResolver mContentResolver;
-
- // 多选模式回调
- private ModeCallback mModeCallBack;
+import java.util.List;
+/**
+ * 笔记列表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;
- // 笔记列表滚动速率
- public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
-
- // 当前聚焦的笔记数据项
- private NoteItemData mFocusNoteDataItem;
-
- // 普通选择条件:指定父文件夹ID
- private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
+ private NotesListViewModel viewModel;
+ private ListView notesListView;
+ private androidx.appcompat.widget.Toolbar toolbar;
+ private NoteInfoAdapter adapter;
+ private DrawerLayout drawerLayout;
+ private FloatingActionButton fabNewNote;
+ private LinearLayout breadcrumbContainer;
+ private LinearLayout breadcrumbItems;
- // 根文件夹选择条件:显示所有非系统笔记和有内容的通话记录文件夹
- private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
- + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
- + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
- + NoteColumns.NOTES_COUNT + ">0)";
-
- // 打开笔记的请求码
- private final static int REQUEST_CODE_OPEN_NODE = 102;
- // 新建笔记的请求码
- private final static int REQUEST_CODE_NEW_NODE = 103;
+ // 多选模式状态
+ private boolean isMultiSelectMode = false;
/**
* 活动创建时的初始化方法
- *
- * 设置布局,初始化资源,首次使用时添加介绍笔记
- *
+ *
+ * 设置布局,初始化ViewModel,设置UI监听器
+ *
+ *
* @param savedInstanceState 保存的实例状态
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.note_list);
- initResources();
- /**
- * Insert an introduction when user firstly use this application
- */
- setAppInfoFromRawRes();
- }
+ // 启用边缘到边缘显示
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
- /**
- * 活动结果回调方法
- *
- * 当从笔记编辑活动返回时,刷新笔记列表
- *
- * @param requestCode 请求码,标识是哪个活动返回
- * @param resultCode 结果码,RESULT_OK表示操作成功
- * @param data 返回的Intent数据
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == RESULT_OK
- && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
- mNotesListAdapter.swapCursor(null);
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
+ setContentView(R.layout.note_list);
- /**
- * 从原始资源文件加载并创建介绍笔记
- *
- * 首次使用应用时,从res/raw/introduction文件读取内容并创建一条介绍笔记
- */
- private void setAppInfoFromRawRes() {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
- StringBuilder sb = new StringBuilder();
- InputStream in = null;
- try {
- in = getResources().openRawResource(R.raw.introduction);
- if (in != null) {
- InputStreamReader isr = new InputStreamReader(in);
- BufferedReader br = new BufferedReader(isr);
- char [] buf = new char[1024];
- int len = 0;
- while ((len = br.read(buf)) > 0) {
- sb.append(buf, 0, len);
- }
- } else {
- Log.e(TAG, "Read introduction file error");
- return;
- }
- } catch (IOException e) {
- e.printStackTrace();
- return;
- } finally {
- if(in != null) {
- try {
- in.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
+ // 处理窗口insets(状态栏和导航栏)
+ View mainView = findViewById(android.R.id.content);
+ ViewCompat.setOnApplyWindowInsetsListener(mainView, (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
+ // 设置内容区域的padding以避免被状态栏遮挡
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsetsCompat.CONSUMED;
+ });
- WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
- AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
- ResourceParser.RED);
- note.setWorkingText(sb.toString());
- if (note.saveNote()) {
- sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
- } else {
- Log.e(TAG, "Save introduction note error");
- return;
- }
- }
+ initViewModel();
+ initViews();
+ observeViewModel();
}
/**
* 活动启动时的回调方法
- *
- * 启动异步查询笔记列表
+ *
+ * 加载笔记列表
+ *
*/
@Override
protected void onStart() {
super.onStart();
- startAsyncNotesListQuery();
+ viewModel.loadNotes(Notes.ID_ROOT_FOLDER);
}
/**
- * 初始化资源
- *
- * 初始化所有UI组件、适配器和监听器
+ * 初始化ViewModel
*/
- private void initResources() {
- mContentResolver = this.getContentResolver();
- mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
- mCurrentFolderId = Notes.ID_ROOT_FOLDER;
- mNotesRecyclerView = (RecyclerView) findViewById(R.id.notes_list);
- mLayoutManager = new LinearLayoutManager(this);
- mNotesRecyclerView.setLayoutManager(mLayoutManager);
-
- // 创建适配器
- mNotesListAdapter = new NotesRecyclerViewAdapter(this);
- mNotesRecyclerView.setAdapter(mNotesListAdapter);
-
- // 设置动画
- DefaultItemAnimator animator = new DefaultItemAnimator();
- animator.setAddDuration(300);
- animator.setRemoveDuration(300);
- animator.setMoveDuration(300);
- animator.setChangeDuration(300);
- mNotesRecyclerView.setItemAnimator(animator);
-
- // 初始化布局切换控制器
- mLayoutManagerController = new LayoutManagerController(this, mNotesRecyclerView,
- new LayoutManagerController.LayoutChangeListener() {
- @Override
- public void onLayoutChanged(LayoutType newLayoutType) {
- showToast("已切换到" + newLayoutType.getDisplayName());
+ private void initViewModel() {
+ 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);
}
-
- @Override
- public void onLayoutChangeFailed(Exception e) {
- showToast("布局切换失败: " + e.getMessage());
- }
- });
-
- // 应用保存的布局
- mLayoutManagerController.initializeLayout();
-
- // 设置点击和长按监听
- mNotesListAdapter.setOnItemClickListener(new OnListItemClickListener());
- mNotesListAdapter.setOnItemLongClickListener(this);
-
- mAddNewNote = (Button) findViewById(R.id.btn_new_note);
- mAddNewNote.setOnClickListener(this);
- mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
- mDispatch = false;
- mDispatchY = 0;
- mOriginY = 0;
- mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
- mState = ListEditState.NOTE_LIST;
- mModeCallBack = new ModeCallback();
+ throw new IllegalArgumentException("Unknown ViewModel class");
+ }
+ }).get(NotesListViewModel.class);
+ Log.d(TAG, "ViewModel initialized");
}
/**
- * 多选模式回调类
- *
- * 实现ActionMode.Callback接口,处理多选模式的创建、销毁和项选中状态变化
+ * 初始化视图
*/
- private class ModeCallback implements ActionMode.Callback, OnMenuItemClickListener {
- private DropdownMenu mDropDownMenu;
- private ActionMode mActionMode;
- private MenuItem mMoveMenu;
-
- /**
- * 创建多选模式的操作栏
- *
- * @param mode ActionMode对象
- * @param menu 菜单对象
- * @return true表示成功创建
- */
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- getMenuInflater().inflate(R.menu.note_list_options, menu);
- menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
- mMoveMenu = menu.findItem(R.id.move);
- if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
- || DataUtils.getUserFolderCount(mContentResolver) == 0) {
- mMoveMenu.setVisible(false);
- } else {
- mMoveMenu.setVisible(true);
- mMoveMenu.setOnMenuItemClickListener(this);
- }
- mActionMode = mode;
- mNotesListAdapter.setChoiceMode(true);
- mAddNewNote.setVisibility(View.GONE);
-
- View customView = LayoutInflater.from(NotesListActivity.this).inflate(
- R.layout.note_list_dropdown_menu, null);
- mode.setCustomView(customView);
- mDropDownMenu = new DropdownMenu(NotesListActivity.this,
- (Button) customView.findViewById(R.id.selection_menu),
- R.menu.note_list_dropdown);
- mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
- /**
- * 下拉菜单项点击事件处理
- *
- * @param item 被点击的菜单项
- * @return true表示事件已处理
- */
- public boolean onMenuItemClick(MenuItem item) {
- mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
- updateMenu();
- return true;
- }
+ private void initViews() {
+ notesListView = findViewById(R.id.notes_list);
+ toolbar = findViewById(R.id.toolbar);
+ drawerLayout = findViewById(R.id.drawer_layout);
- });
- return true;
- }
+ // 初始化面包屑导航
+ breadcrumbContainer = findViewById(R.id.breadcrumb_container);
+ breadcrumbItems = findViewById(R.id.breadcrumb_items);
- /**
- * 更新菜单显示
- *
- * 根据选中数量更新下拉菜单标题和全选按钮状态
- */
- private void updateMenu() {
- int selectedCount = mNotesListAdapter.getSelectedCount();
- // Update dropdown menu
- String format = getResources().getString(R.string.menu_select_title, selectedCount);
- mDropDownMenu.setTitle(format);
- MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
- if (item != null) {
- if (mNotesListAdapter.isAllSelected()) {
- item.setChecked(true);
- item.setTitle(R.string.menu_deselect_all);
- } else {
- item.setChecked(false);
- item.setTitle(R.string.menu_select_all);
+ // 设置适配器
+ adapter = new NoteInfoAdapter(this);
+ notesListView.setAdapter(adapter);
+ adapter.setOnNoteButtonClickListener(this);
+ adapter.setOnNoteItemClickListener(this);
+ adapter.setOnNoteItemLongClickListener(this);
+
+ // 设置点击监听
+ notesListView.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);
}
}
- }
+ });
- /**
- * 准备多选模式的操作栏
- *
- * @param mode ActionMode对象
- * @param menu 菜单对象
- * @return false
- */
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- // TODO Auto-generated method stub
- return false;
+ // 初始化 Toolbar
+ toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setTitle(R.string.app_name);
}
- /**
- * 操作栏菜单项点击事件处理
- *
- * @param mode ActionMode对象
- * @param item 被点击的菜单项
- * @return false
- */
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- // TODO Auto-generated method stub
- return false;
- }
+ // 初始化为普通模式
+ updateToolbarForNormalMode();
- /**
- * 销毁多选模式的操作栏
- *
- * 退出选择模式,恢复列表视图的常规状态
- *
- * @param mode ActionMode对象
- */
- public void onDestroyActionMode(ActionMode mode) {
- mNotesListAdapter.setChoiceMode(false);
- mAddNewNote.setVisibility(View.VISIBLE);
- }
+ // 设置 Toolbar 的汉堡菜单按钮点击监听器(打开侧栏)
+ toolbar.setNavigationOnClickListener(v -> {
+ if (drawerLayout != null) {
+ drawerLayout.openDrawer(findViewById(R.id.sidebar_fragment));
+ }
+ });
- /**
- * 完成多选模式
- *
- * 手动结束ActionMode
- */
- public void finishActionMode() {
- mActionMode.finish();
+ // Set FAB click event
+ fabNewNote = findViewById(R.id.btn_new_note);
+ if (fabNewNote != null) {
+ fabNewNote.setOnClickListener(v -> {
+ Intent intent = new Intent(NotesListActivity.this, NoteEditActivity.class);
+ intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
+ intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, viewModel.getCurrentFolderId());
+ startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
+ });
}
+ }
- /**
- * 列表项选中状态变化事件处理
- *
- * @param mode ActionMode对象
- * @param position 列表项位置
- * @param id 列表项ID
- * @param checked 是否选中
- */
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
- boolean checked) {
- mNotesListAdapter.setCheckedItem(position, checked);
- updateMenu();
+ /**
+ * 处理列表项点击
+ *
+ * 如果是便签,打开编辑器;如果是文件夹,进入该文件夹
+ *
+ *
+ * @param note 项
+ * @param position 位置
+ */
+ private void handleItemClick(NotesRepository.NoteInfo note, int position) {
+ if (isMultiSelectMode) {
+ // 多选模式:切换选中状态
+ boolean isSelected = viewModel.getSelectedNoteIds().contains(note.getId());
+ viewModel.toggleNoteSelection(note.getId(), !isSelected);
+ if (adapter != null) {
+ adapter.setSelectedIds(viewModel.getSelectedNoteIds());
+ }
+ updateToolbarForMultiSelectMode();
+ } else {
+ // 普通模式
+ if (note.type == Notes.TYPE_FOLDER) {
+ // 文件夹:进入该文件夹
+ viewModel.enterFolder(note.getId());
+ } else {
+ // 便签:打开编辑器
+ openNoteEditor(note);
+ }
}
+ }
- public boolean onMenuItemClick(MenuItem item) {
- if (mNotesListAdapter.getSelectedCount() == 0) {
- Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
- Toast.LENGTH_SHORT).show();
- return true;
+ /**
+ * 观察ViewModel的LiveData
+ */
+ private void observeViewModel() {
+ // 观察笔记列表
+ viewModel.getNotesLiveData().observe(this, new Observer>() {
+ @Override
+ public void onChanged(List notes) {
+ updateAdapter(notes);
}
+ });
- switch (item.getItemId()) {
- case R.id.delete:
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(getString(R.string.alert_title_delete));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(getString(R.string.alert_message_delete_notes,
- mNotesListAdapter.getSelectedCount()));
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int which) {
- batchDelete();
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- break;
- case R.id.move:
- startQueryDestinationFolders();
- break;
- default:
- return false;
+ // 观察加载状态
+ viewModel.getIsLoading().observe(this, new Observer() {
+ @Override
+ public void onChanged(Boolean isLoading) {
+ updateLoadingState(isLoading);
}
- return true;
- }
- }
+ });
- private class NewNoteOnTouchListener implements OnTouchListener {
-
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- Display display = getWindowManager().getDefaultDisplay();
- int screenHeight = display.getHeight();
- int newNoteViewHeight = mAddNewNote.getHeight();
- int start = screenHeight - newNoteViewHeight;
- int eventY = start + (int) event.getY();
- /**
- * Minus TitleBar's height
- */
- if (mState == ListEditState.SUB_FOLDER) {
- eventY -= mTitleBar.getHeight();
- start -= mTitleBar.getHeight();
- }
- /**
- * HACKME:When click the transparent part of "New Note" button, dispatch
- * the event to the list view behind this button. The transparent part of
- * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel)
- * and the line top of the button. The coordinate based on left of the "New
- * Note" button. The 94 represents maximum height of the transparent part.
- * Notice that, if the background of the button changes, the formula should
- * also change. This is very bad, just for the UI designer's strong requirement.
- */
- if (event.getY() < (event.getX() * (-0.12) + 94)) {
- View view = mNotesRecyclerView.getChildAt(mNotesRecyclerView.getChildCount() - 1);
- if (view != null && view.getBottom() > start
- && (view.getTop() < (start + 94))) {
- mOriginY = (int) event.getY();
- mDispatchY = eventY;
- event.setLocation(event.getX(), mDispatchY);
- mDispatch = true;
- return mNotesRecyclerView.dispatchTouchEvent(event);
- }
- }
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mDispatch) {
- mDispatchY += (int) event.getY() - mOriginY;
- event.setLocation(event.getX(), mDispatchY);
- return mNotesRecyclerView.dispatchTouchEvent(event);
- }
- break;
- }
- default: {
- if (mDispatch) {
- event.setLocation(event.getX(), mDispatchY);
- mDispatch = false;
- return mNotesRecyclerView.dispatchTouchEvent(event);
- }
- break;
+ // 观察错误消息
+ viewModel.getErrorMessage().observe(this, new Observer() {
+ @Override
+ public void onChanged(String message) {
+ if (message != null && !message.isEmpty()) {
+ showError(message);
}
}
- return false;
- }
+ });
- };
+ // 观察文件夹路径(用于面包屑导航)
+ viewModel.getFolderPathLiveData().observe(this, new Observer>() {
+ @Override
+ public void onChanged(List path) {
+ updateBreadcrumb(path);
+ }
+ });
- /**
- * 启动异步笔记列表查询
- *
- * 根据当前文件夹ID构建查询条件,启动后台查询获取笔记列表数据。
- * 根文件夹使用特殊的查询条件,子文件夹使用普通查询条件。
- *
- */
- private void startAsyncNotesListQuery() {
- String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
- : NORMAL_SELECTION;
- mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
- Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
- String.valueOf(mCurrentFolderId)
- }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
+ // 观察侧栏刷新通知
+ viewModel.getSidebarRefreshNeeded().observe(this, new Observer() {
+ @Override
+ public void onChanged(Boolean refreshNeeded) {
+ if (refreshNeeded != null && refreshNeeded) {
+ // 通知侧栏刷新
+ SidebarFragment sidebarFragment = (SidebarFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.sidebar_fragment);
+ if (sidebarFragment != null) {
+ sidebarFragment.refreshFolderTree();
+ }
+ // 重置刷新状态
+ viewModel.getSidebarRefreshNeeded().setValue(false);
+ }
+ }
+ });
}
/**
- * 后台查询处理器
- *
- * 继承自AsyncQueryHandler,用于在后台线程执行数据库查询,
- * 避免阻塞UI线程。
- *
+ * 更新面包屑导航
+ *
+ * @param path 文件夹路径
*/
- private final class BackgroundQueryHandler extends AsyncQueryHandler {
- /**
- * 构造函数
- * @param contentResolver 内容解析器
- */
- public BackgroundQueryHandler(ContentResolver contentResolver) {
- super(contentResolver);
+ private void updateBreadcrumb(List path) {
+ if (breadcrumbItems == null || path == null) {
+ return;
}
- /**
- * 查询完成回调
- *
- * 根据查询令牌处理不同的查询结果:
- *
- * FOLDER_NOTE_LIST_QUERY_TOKEN: 更新笔记列表适配器
- * FOLDER_LIST_QUERY_TOKEN: 显示文件夹选择菜单
- *
- *
- * @param token 查询令牌
- * @param cookie Cookie对象
- * @param cursor 查询结果游标
- */
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- switch (token) {
- case FOLDER_NOTE_LIST_QUERY_TOKEN:
- mNotesListAdapter.swapCursor(cursor);
- break;
- case FOLDER_LIST_QUERY_TOKEN:
- if (cursor != null && cursor.getCount() > 0) {
- showFolderListMenu(cursor);
- } else {
- Log.e(TAG, "Query folder failed");
- }
- break;
- default:
- return;
+ breadcrumbItems.removeAllViews();
+
+ for (int i = 0; i < path.size(); i++) {
+ NotesRepository.NoteInfo folder = path.get(i);
+
+ // 如果不是第一个,添加分隔符 " > "
+ if (i > 0) {
+ TextView separator = new TextView(this);
+ separator.setText(" > ");
+ separator.setTextSize(14);
+ separator.setTextColor(android.R.color.darker_gray);
+ breadcrumbItems.addView(separator);
}
- }
- }
- /**
- * 显示文件夹选择菜单
- *
- * 显示一个对话框,列出所有可用的目标文件夹供用户选择,
- * 用于移动选中的笔记到指定文件夹。
- *
- * @param cursor 包含文件夹列表的游标
- */
- private void showFolderListMenu(Cursor cursor) {
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(R.string.menu_title_select_folder);
- final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
- builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+ // 创建面包屑项
+ TextView breadcrumbItem = (TextView) getLayoutInflater()
+ .inflate(R.layout.breadcrumb_item, breadcrumbItems, false);
+ breadcrumbItem.setText(folder.title);
- public void onClick(DialogInterface dialog, int which) {
- DataUtils.batchMoveToFolder(mContentResolver,
- mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
- Toast.makeText(
- NotesListActivity.this,
- getString(R.string.format_move_notes_to_folder,
- mNotesListAdapter.getSelectedCount(),
- adapter.getFolderName(NotesListActivity.this, which)),
- Toast.LENGTH_SHORT).show();
- mModeCallBack.finishActionMode();
+ // 如果是当前文件夹(最后一个),高亮显示且不可点击
+ if (i == path.size() - 1) {
+ breadcrumbItem.setTextColor(getColor(R.color.primary_color));
+ breadcrumbItem.setEnabled(false);
+ } else {
+ // 其他层级可以点击跳转
+ final long targetFolderId = folder.id;
+ breadcrumbItem.setOnClickListener(v -> viewModel.enterFolder(targetFolderId));
}
- });
- builder.show();
+
+ breadcrumbItems.addView(breadcrumbItem);
+ }
}
/**
- * 创建新笔记
- *
- * 启动NoteEditActivity创建新笔记,传递当前文件夹ID。
- *
+ * 更新适配器数据
*/
- private void createNewNote() {
- Intent intent = new Intent(this, NoteEditActivity.class);
- intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
- this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
+ private void updateAdapter(List notes) {
+ adapter.setNotes(notes);
+ Log.d(TAG, "Adapter updated with " + notes.size() + " notes");
}
/**
- * 批量删除笔记
- *
- * 在后台线程中删除选中的笔记。
- * 如果处于同步模式,将笔记移动到垃圾箱文件夹;
- * 否则直接删除。同时更新相关的小部件。
- *
+ * 更新加载状态
*/
- private void batchDelete() {
- new AsyncTask>() {
- protected HashSet doInBackground(Void... unused) {
- HashSet widgets = mNotesListAdapter.getSelectedWidget();
- if (!isSyncMode()) {
- // if not synced, delete notes directly
- if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
- .getSelectedItemIds())) {
- } else {
- Log.e(TAG, "Delete notes error, should not happens");
- }
- } else {
- // in sync mode, we'll move the deleted note into the trash
- // folder
- if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
- .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
- Log.e(TAG, "Move notes to trash folder error, should not happens");
- }
- }
- return widgets;
- }
-
- @Override
- protected void onPostExecute(HashSet widgets) {
- if (widgets != null) {
- for (AppWidgetAttribute widget : widgets) {
- if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
- && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
- updateWidget(widget.widgetId, widget.widgetType);
- }
- }
- }
- mModeCallBack.finishActionMode();
- }
- }.execute();
+ private void updateLoadingState(boolean isLoading) {
+ // TODO: 显示/隐藏进度条
}
/**
- * 删除文件夹
- *
- * 删除指定的文件夹及其包含的所有笔记。
- * 如果处于同步模式,将文件夹移动到垃圾箱;
- * 否则直接删除。同时更新相关的小部件。
- *
- * @param folderId 要删除的文件夹ID
+ * 显示错误消息
*/
- private void deleteFolder(long folderId) {
- if (folderId == Notes.ID_ROOT_FOLDER) {
- Log.e(TAG, "Wrong folder id, should not happen " + folderId);
- return;
- }
-
- HashSet ids = new HashSet();
- ids.add(folderId);
- HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver,
- folderId);
- if (!isSyncMode()) {
- // if not synced, delete folder directly
- DataUtils.batchDeleteNotes(mContentResolver, ids);
- } else {
- // in sync mode, we'll move the deleted folder into the trash folder
- DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
- }
- if (widgets != null) {
- for (AppWidgetAttribute widget : widgets) {
- if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
- && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
- updateWidget(widget.widgetId, widget.widgetType);
- }
- }
- }
+ private void showError(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
/**
- * 打开笔记
- *
- * 启动NoteEditActivity查看和编辑指定的笔记。
- *
- * @param data 笔记数据项
+ * 打开笔记编辑器
*/
- private void openNode(NoteItemData data) {
+ private void openNoteEditor(NotesRepository.NoteInfo note) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(Intent.EXTRA_UID, data.getId());
- this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
+ intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, note.getParentId());
+ intent.putExtra(Intent.EXTRA_UID, note.getId());
+ startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
/**
- * 打开文件夹
- *
- * 进入指定的文件夹,显示该文件夹中的笔记列表。
- * 更新标题栏显示文件夹名称,并隐藏新建笔记按钮(如果是通话记录文件夹)。
- *
- * @param data 文件夹数据项
+ * 编辑按钮点击事件处理
+ *
+ * @param position 列表位置
+ * @param noteId 便签 ID
*/
- private void openFolder(NoteItemData data) {
- mCurrentFolderId = data.getId();
- startAsyncNotesListQuery();
- if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
- mState = ListEditState.CALL_RECORD_FOLDER;
- mAddNewNote.setVisibility(View.GONE);
+ @Override
+ public void onEditButtonClick(int position, long noteId) {
+ NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
+ if (note != null) {
+ openNoteEditor(note);
} else {
- mState = ListEditState.SUB_FOLDER;
+ Log.e(TAG, "Edit button clicked but note is null at position: " + position);
}
- if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
- mTitleBar.setText(R.string.call_record_folder_name);
+ }
+
+ @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());
+ }
+ // 更新toolbar标题
+ updateToolbarForMultiSelectMode();
+ }
+ Log.d(TAG, "===== onNoteItemClick END (multi-select mode) =====");
} else {
- mTitleBar.setText(data.getSnippet());
+ Log.d(TAG, "Normal mode, checking item type");
+ NotesRepository.NoteInfo note = (NotesRepository.NoteInfo) adapter.getItem(position);
+ if (note != null) {
+ if (note.type == Notes.TYPE_FOLDER) {
+ // 文件夹:进入该文件夹
+ Log.d(TAG, "Folder clicked, entering folder: " + note.getId());
+ viewModel.enterFolder(note.getId());
+ } else {
+ // 便签:打开编辑器
+ Log.d(TAG, "Note clicked, opening editor");
+ openNoteEditor(note);
+ }
+ }
+ Log.d(TAG, "===== onNoteItemClick END =====");
}
- mTitleBar.setVisibility(View.VISIBLE);
}
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.btn_new_note:
- createNewNote();
- break;
- default:
- break;
+ @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());
+ }
+
+ updateSelectionState(position, true);
+
+ Log.d(TAG, "===== onNoteItemLongClick END =====");
+ } else {
+ Log.d(TAG, "Multi-select mode already active, ignoring long click");
}
}
/**
- * 显示软键盘
- *
- * 强制显示系统软键盘,用于输入文件夹名称。
- *
+ * 进入多选模式
*/
- private void showSoftInput() {
- InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- if (inputMethodManager != null) {
- inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+ private void enterMultiSelectMode() {
+ isMultiSelectMode = true;
+ // 隐藏FAB按钮
+ if (fabNewNote != null) {
+ fabNewNote.setVisibility(View.GONE);
}
+ // 更新toolbar为多选模式
+ updateToolbarForMultiSelectMode();
}
/**
- * 隐藏软键盘
- *
- * 隐藏指定视图的软键盘。
- *
- * @param view 要隐藏键盘的视图
+ * 退出多选模式
*/
- private void hideSoftInput(View view) {
- InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ private void exitMultiSelectMode() {
+ isMultiSelectMode = false;
+ // 显示FAB按钮
+ if (fabNewNote != null) {
+ fabNewNote.setVisibility(View.VISIBLE);
+ }
+ // 清除选中状态
+ viewModel.clearSelection();
+ if (adapter != null) {
+ adapter.setSelectedIds(new java.util.HashSet<>());
+ adapter.notifyDataSetChanged();
+ }
+ // 更新toolbar为普通模式
+ updateToolbarForNormalMode();
}
/**
- * 显示创建或修改文件夹对话框
- *
- * 显示一个对话框,允许用户输入文件夹名称。
- * 根据create参数决定是创建新文件夹还是修改现有文件夹名称。
- *
- * @param create true表示创建新文件夹,false表示修改文件夹名称
+ * 更新Toolbar为多选模式
*/
- private void showCreateOrModifyFolderDialog(final boolean create) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
- final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
- showSoftInput();
- if (!create) {
- if (mFocusNoteDataItem != null) {
- etName.setText(mFocusNoteDataItem.getSnippet());
- builder.setTitle(getString(R.string.menu_folder_change_name));
- } else {
- Log.e(TAG, "The long click data item is null");
- return;
- }
- } else {
- etName.setText("");
- builder.setTitle(this.getString(R.string.menu_create_folder));
- }
+ private void updateToolbarForMultiSelectMode() {
+ if (toolbar == null) return;
- builder.setPositiveButton(android.R.string.ok, null);
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- hideSoftInput(etName);
- }
- });
+ // 设置标题为选中数量
+ int selectedCount = viewModel.getSelectedCount();
+ String title = getString(R.string.menu_select_title, selectedCount);
+ toolbar.setTitle(title);
- final Dialog dialog = builder.setView(view).show();
- final Button positive = (Button)dialog.findViewById(android.R.id.button1);
- positive.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- hideSoftInput(etName);
- String name = etName.getText().toString();
- if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
- Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
- Toast.LENGTH_LONG).show();
- etName.setSelection(0, etName.length());
- return;
- }
- if (!create) {
- if (!TextUtils.isEmpty(name)) {
- ContentValues values = new ContentValues();
- values.put(NoteColumns.SNIPPET, name);
- values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
- values.put(NoteColumns.LOCAL_MODIFIED, 1);
- mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
- + "=?", new String[] {
- String.valueOf(mFocusNoteDataItem.getId())
- });
- }
- } else if (!TextUtils.isEmpty(name)) {
- ContentValues values = new ContentValues();
- values.put(NoteColumns.SNIPPET, name);
- values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
- mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
- }
- dialog.dismiss();
- }
- });
+ // 设置导航图标为返回(取消多选)
+ toolbar.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
+ toolbar.setNavigationOnClickListener(v -> exitMultiSelectMode());
- if (TextUtils.isEmpty(etName.getText())) {
- positive.setEnabled(false);
- }
- /**
- * When the name edit text is null, disable the positive button
- */
- etName.addTextChangedListener(new TextWatcher() {
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // TODO Auto-generated method stub
+ // 移除普通模式的菜单(如果有)
+ toolbar.getMenu().clear();
- }
+ // 直接在toolbar上添加操作按钮(不在三点菜单中)
+ Menu menu = toolbar.getMenu();
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- if (TextUtils.isEmpty(etName.getText())) {
- positive.setEnabled(false);
- } else {
- positive.setEnabled(true);
- }
- }
+ // 删除按钮
+ 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);
- public void afterTextChanged(Editable s) {
- // TODO Auto-generated method stub
+ // 移动按钮
+ 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);
}
/**
- * 返回键按下处理
- *
- * 根据当前列表状态处理返回键事件:
- *
- * 子文件夹或通话记录文件夹:返回根文件夹列表
- * 笔记列表:调用父类方法退出Activity
- *
- *
+ * 更新Toolbar为普通模式
*/
- @Override
- public void onBackPressed() {
- switch (mState) {
- case SUB_FOLDER:
- mCurrentFolderId = Notes.ID_ROOT_FOLDER;
- mState = ListEditState.NOTE_LIST;
- startAsyncNotesListQuery();
- mTitleBar.setVisibility(View.GONE);
- break;
- case CALL_RECORD_FOLDER:
- mCurrentFolderId = Notes.ID_ROOT_FOLDER;
- mState = ListEditState.NOTE_LIST;
- mAddNewNote.setVisibility(View.VISIBLE);
- mTitleBar.setVisibility(View.GONE);
- startAsyncNotesListQuery();
- break;
- case NOTE_LIST:
- super.onBackPressed();
- break;
- default:
- break;
- }
- }
+ private void updateToolbarForNormalMode() {
+ if (toolbar == null) return;
- /**
- * 更新小部件
- *
- * 发送广播更新指定的小部件,使其显示最新的笔记内容。
- *
- * @param appWidgetId 小部件ID
- * @param appWidgetType 小部件类型(2x或4x)
- */
- private void updateWidget(int appWidgetId, int appWidgetType) {
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- if (appWidgetType == Notes.TYPE_WIDGET_2X) {
- intent.setClass(this, NoteWidgetProvider_2x.class);
- } else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
- intent.setClass(this, NoteWidgetProvider_4x.class);
- } else {
- Log.e(TAG, "Unspported widget type");
- return;
- }
+ // 设置标题为应用名称
+ toolbar.setTitle(R.string.app_name);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
- appWidgetId
+ // 设置导航图标为汉堡菜单
+ toolbar.setNavigationIcon(android.R.drawable.ic_menu_sort_by_size);
+ toolbar.setNavigationOnClickListener(v -> {
+ if (drawerLayout != null) {
+ drawerLayout.openDrawer(findViewById(R.id.sidebar_fragment));
+ }
});
- sendBroadcast(intent);
- setResult(RESULT_OK, intent);
+ // 清除多选模式菜单
+ toolbar.getMenu().clear();
+
+ // 添加普通模式菜单(如果需要)
+ // getMenuInflater().inflate(R.menu.note_list_options, menu);
}
+
+
/**
- * 文件夹上下文菜单创建监听器
- *
- * 为文件夹项创建上下文菜单,提供查看、删除和重命名选项。
- *
+ * 显示删除确认对话框
*/
- private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- if (mFocusNoteDataItem != null) {
- menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
- menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view);
- menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete);
- menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
+ 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();
+ }
- @Override
- public void onContextMenuClosed(Menu menu) {
- super.onContextMenuClosed(menu);
+ /**
+ * 显示移动菜单
+ */
+ private void showMoveMenu() {
+ // TODO: 实现文件夹选择逻辑
+ Toast.makeText(this, "移动功能开发中", Toast.LENGTH_SHORT).show();
}
+ /**
+ * 活动结果回调方法
+ */
@Override
- public boolean onContextItemSelected(MenuItem item) {
- if (mFocusNoteDataItem == null) {
- Log.e(TAG, "The long click data item is null");
- return false;
- }
- switch (item.getItemId()) {
- case MENU_FOLDER_VIEW:
- openFolder(mFocusNoteDataItem);
- break;
- case MENU_FOLDER_DELETE:
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.alert_title_delete));
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(getString(R.string.alert_message_delete_folder));
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- deleteFolder(mFocusNoteDataItem.getId());
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
- break;
- case MENU_FOLDER_CHANGE_NAME:
- showCreateOrModifyFolderDialog(false);
- break;
- default:
- break;
- }
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
- return true;
+ if (resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE) {
+ viewModel.refreshNotes();
+ }
+ }
}
/**
- * 准备选项菜单
- *
- * 根据当前列表状态加载不同的菜单资源:
- *
- * 笔记列表:显示同步、设置、新建文件夹、导出、搜索等选项
- * 子文件夹:显示新建笔记选项
- * 通话记录文件夹:显示新建笔记选项
- *
- *
- * @param menu 选项菜单对象
- * @return true
+ * 创建选项菜单
*/
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.clear();
- if (mState == ListEditState.NOTE_LIST) {
- getMenuInflater().inflate(R.menu.note_list, menu);
- // set sync or sync_cancel
- menu.findItem(R.id.menu_sync).setTitle(
- GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
- } else if (mState == ListEditState.SUB_FOLDER) {
- getMenuInflater().inflate(R.menu.sub_folder, menu);
- } else if (mState == ListEditState.CALL_RECORD_FOLDER) {
- getMenuInflater().inflate(R.menu.call_record_folder, menu);
- } else {
- Log.e(TAG, "Wrong state:" + mState);
- }
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.note_list, menu);
return true;
}
/**
- * 选项菜单项选择处理
- *
- * 处理用户点击选项菜单的事件,包括:
- *
- * 新建文件夹
- * 导出笔记为文本
- * 同步或取消同步
- * 打开设置
- * 新建笔记
- * 搜索
- *
- *
- * @param item 被点击的菜单项
- * @return true
+ * 选项菜单项点击事件
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_new_folder: {
- showCreateOrModifyFolderDialog(true);
- break;
- }
- case R.id.menu_export_text: {
- exportNoteToText();
- break;
- }
- case R.id.menu_sync: {
- if (isSyncMode()) {
- if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
- GTaskSyncService.startSync(this);
- } else {
- GTaskSyncService.cancelSync(this);
- }
- } else {
- startPreferenceActivity();
- }
- break;
- }
- case R.id.menu_setting: {
- startPreferenceActivity();
- break;
- }
- case R.id.menu_new_note: {
- createNewNote();
- break;
- }
+ int itemId = item.getItemId();
+
+ switch (itemId) {
case R.id.menu_search:
- onSearchRequested();
- break;
- case R.id.menu_switch_layout:
- showLayoutSettingsDialog();
- break;
+ // TODO: 打开搜索对话框
+ Toast.makeText(this, "搜索功能开发中", Toast.LENGTH_SHORT).show();
+ 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;
default:
- break;
+ return super.onOptionsItemSelected(item);
}
- return true;
}
/**
- * 搜索请求处理
- *
- * 启动系统搜索界面,允许用户搜索笔记内容。
- *
- * @return true
+ * 上下文菜单创建
*/
@Override
- public boolean onSearchRequested() {
- startSearch(null, false, null /* appData */, false);
- return true;
+ public void onCreateContextMenu(android.view.ContextMenu menu, View v, android.view.ContextMenu.ContextMenuInfo menuInfo) {
+ getMenuInflater().inflate(R.menu.sub_folder, menu);
}
/**
- * 导出笔记为文本文件
- *
- * 在后台线程中将所有笔记导出为文本文件到SD卡。
- * 根据导出结果显示相应的提示对话框。
- *
+ * 上下文菜单项点击
*/
- private void exportNoteToText() {
- final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
- new AsyncTask() {
-
- @Override
- protected Integer doInBackground(Void... unused) {
- return backup.exportToText();
- }
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ // TODO: 处理文件夹上下文菜单
+ return super.onContextItemSelected(item);
+ }
- @Override
- protected void onPostExecute(Integer result) {
- if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(NotesListActivity.this
- .getString(R.string.failed_sdcard_export));
- builder.setMessage(NotesListActivity.this
- .getString(R.string.error_sdcard_unmounted));
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
- } else if (result == BackupUtils.STATE_SUCCESS) {
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(NotesListActivity.this
- .getString(R.string.success_sdcard_export));
- builder.setMessage(NotesListActivity.this.getString(
- R.string.format_exported_file_location, backup
- .getExportedTextFileName(), backup.getExportedTextFileDir()));
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
- } else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
- AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
- builder.setTitle(NotesListActivity.this
- .getString(R.string.failed_sdcard_export));
- builder.setMessage(NotesListActivity.this
- .getString(R.string.error_sdcard_export));
- builder.setPositiveButton(android.R.string.ok, null);
- builder.show();
+ /**
+ * 活动销毁时的清理
+ */
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // 清理资源
+ }
+
+ 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");
}
+ } else {
+ Log.e("NotesListActivity", "note is NULL at position: " + position);
+ }
+ Log.d("NotesListActivity", "===== updateSelectionState END =====");
+ }
+
+ // ==================== SidebarFragment.OnSidebarItemSelectedListener 实现 ====================
- }.execute();
+ @Override
+ public void onFolderSelected(long folderId) {
+ // 跳转到指定文件夹
+ viewModel.enterFolder(folderId);
+ // 关闭侧栏
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ }
}
- /**
- * 显示布局设置对话框
- *
- * 打开布局设置对话框,允许用户选择布局类型、网格列数和项目间距。
- *
- */
- private void showLayoutSettingsDialog() {
- LayoutSettingsDialog dialog = new LayoutSettingsDialog(this, mLayoutManagerController);
- dialog.show();
+ @Override
+ public void onTrashSelected() {
+ // TODO: 实现跳转到回收站
+ Log.d(TAG, "Trash selected");
+ // 关闭侧栏
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ }
}
- /**
- * 显示Toast提示
- *
- * 显示简短的提示信息,自动消失。
- *
- * @param message 提示信息
- */
- private void showToast(String message) {
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ @Override
+ public void onSyncSelected() {
+ // TODO: 实现同步功能
+ Log.d(TAG, "Sync selected");
+ Toast.makeText(this, "同步功能待实现", Toast.LENGTH_SHORT).show();
}
- /**
- * 检查是否处于同步模式
- *
- * 判断是否已设置同步账户,如果已设置则表示处于同步模式。
- *
- * @return true表示处于同步模式,false表示未同步
- */
- private boolean isSyncMode() {
- return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
+ @Override
+ public void onLoginSelected() {
+ // TODO: 实现登录功能
+ Log.d(TAG, "Login selected");
+ Toast.makeText(this, "登录功能待实现", Toast.LENGTH_SHORT).show();
}
- /**
- * 启动设置Activity
- *
- * 启动NotesPreferenceActivity进行应用设置。
- *
- */
- private void startPreferenceActivity() {
- Activity from = getParent() != null ? getParent() : this;
- Intent intent = new Intent(from, NotesPreferenceActivity.class);
- from.startActivityIfNeeded(intent, -1);
+ @Override
+ public void onExportSelected() {
+ // TODO: 实现导出功能
+ Log.d(TAG, "Export selected");
+ Toast.makeText(this, "导出功能待实现", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onSettingsSelected() {
+ // TODO: 实现设置功能
+ Log.d(TAG, "Settings selected");
+ Toast.makeText(this, "设置功能待实现", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onCreateFolder() {
+ // 显示创建文件夹对话框
+ showCreateFolderDialog();
}
/**
- * 列表项点击监听器
- *
- * 处理笔记列表项的点击事件,根据当前状态和项类型执行相应操作:
- *
- * 多选模式:切换选中状态
- * 笔记列表:打开文件夹或笔记
- * 子文件夹/通话记录文件夹:打开笔记
- *
- *
- */
- private class OnListItemClickListener implements NotesRecyclerViewAdapter.OnItemClickListener {
-
- /**
- * 列表项点击事件处理
- *
- * @param view 被点击的视图
- * @param position 列表项位置
- * @param id 列表项ID
+ * 显示创建文件夹对话框
*/
- public void onItemClick(View view, int position, long id) {
- Cursor cursor = (Cursor) mNotesListAdapter.getItem(position);
- if (cursor != null) {
- NoteItemData item = new NoteItemData(NotesListActivity.this, cursor);
- if (mNotesListAdapter.isInChoiceMode()) {
- if (item.getType() == Notes.TYPE_NOTE) {
- mModeCallBack.onItemCheckedStateChanged(null, position, id,
- !mNotesListAdapter.isSelectedItem(position));
- }
- return;
- }
+ private void showCreateFolderDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.dialog_create_folder_title);
- switch (mState) {
- case NOTE_LIST:
- if (item.getType() == Notes.TYPE_FOLDER
- || item.getType() == Notes.TYPE_SYSTEM) {
- openFolder(item);
- } else if (item.getType() == Notes.TYPE_NOTE) {
- openNode(item);
- } else {
- Log.e(TAG, "Wrong note type in NOTE_LIST");
- }
- break;
- case SUB_FOLDER:
- case CALL_RECORD_FOLDER:
- if (item.getType() == Notes.TYPE_NOTE) {
- openNode(item);
- } else {
- Log.e(TAG, "Wrong note type in SUB_FOLDER");
- }
- break;
- default:
- break;
- }
+ 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();
}
- /**
- * 启动查询目标文件夹
- *
- * 查询所有可用的文件夹,用于显示在移动笔记的对话框中。
- * 排除垃圾箱文件夹和当前文件夹。
- *
- */
- private void startQueryDestinationFolders() {
- String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
- selection = (mState == ListEditState.NOTE_LIST) ? selection:
- "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
-
- mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
- null,
- Notes.CONTENT_NOTE_URI,
- FoldersListAdapter.PROJECTION,
- selection,
- new String[] {
- String.valueOf(Notes.TYPE_FOLDER),
- String.valueOf(Notes.ID_TRASH_FOLER),
- String.valueOf(mCurrentFolderId)
- },
- NoteColumns.MODIFIED_DATE + " DESC");
+ @Override
+ public void onCloseSidebar() {
+ // 关闭侧栏
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ }
}
/**
- * 列表项长按事件处理
- *
- * @param view 被长按的视图
- * @param position 列表项位置
- * @param id 列表项ID
- * @return true表示事件已处理
+ * 返回键按下事件处理
+ *
+ * 多选模式:退出多选模式
+ * 子文件夹:返回上一级文件夹
+ * 根文件夹:最小化应用
+ *
*/
- public boolean onItemLongClick(View view, int position, long id) {
- Cursor cursor = (Cursor) mNotesListAdapter.getItem(position);
- if (cursor != null) {
- mFocusNoteDataItem = new NoteItemData(NotesListActivity.this, cursor);
- if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
- if (startActionMode(mModeCallBack) != null) {
- mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
- view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- } else {
- Log.e(TAG, "startActionMode fails");
- }
- } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
- registerForContextMenu(view);
- openContextMenu(view);
+ @Override
+ public void onBackPressed() {
+ if (isMultiSelectMode) {
+ // 多选模式:退出多选模式
+ exitMultiSelectMode();
+ } else if (drawerLayout != null && drawerLayout.isDrawerOpen(findViewById(R.id.sidebar_fragment))) {
+ // 侧栏打开:关闭侧栏
+ drawerLayout.closeDrawer(findViewById(R.id.sidebar_fragment));
+ } else if (viewModel.getCurrentFolderId() != Notes.ID_ROOT_FOLDER &&
+ viewModel.getCurrentFolderId() != Notes.ID_CALL_RECORD_FOLDER) {
+ // 子文件夹:返回上一级
+ if (!viewModel.navigateUp()) {
+ // 如果没有导航历史,返回根文件夹
+ viewModel.loadNotes(Notes.ID_ROOT_FOLDER);
}
+ } else {
+ // 根文件夹:最小化应用
+ moveTaskToBack(true);
}
- return false;
}
}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerViewAdapter.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerViewAdapter.java
deleted file mode 100644
index 2711272..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesRecyclerViewAdapter.java
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * 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.content.Context;
-import android.database.Cursor;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import androidx.recyclerview.widget.RecyclerView;
-
-import net.micode.notes.R;
-import net.micode.notes.data.Notes;
-import net.micode.notes.tool.DataUtils;
-import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
-
-import java.util.HashSet;
-
-/**
- * 笔记RecyclerView适配器
- *
- * 这个类继承自RecyclerView.Adapter,用于将数据库中的笔记数据绑定到RecyclerView中显示。
- * 它支持笔记的选择模式、批量操作以及与桌面小部件的关联。
- * 相比原NotesListAdapter,使用RecyclerView带来更好的性能和动画支持。
- *
- * 主要功能:
- * 1. 将笔记数据绑定到NoteViewHolder视图
- * 2. 支持多选模式和批量选择操作
- * 3. 获取选中的笔记ID和关联的桌面小部件信息
- * 4. 统计笔记数量和选中数量
- * 5. 支持局部刷新和内置动画
- *
- * @see NoteViewHolder
- * @see NoteItemData
- */
-public class NotesRecyclerViewAdapter extends RecyclerView.Adapter {
- private static final String TAG = "NotesRecyclerViewAdapter";
-
- private Context mContext;
- private Cursor mCursor;
- private SparseArray mSelectedIndex;
- private int mNotesCount;
- private boolean mChoiceMode;
- private OnItemClickListener mOnItemClickListener;
- private OnItemLongClickListener mOnItemLongClickListener;
-
- /**
- * 桌面小部件属性类
- *
- * 用于存储桌面小部件的ID和类型信息
- */
- public static class AppWidgetAttribute {
- public int widgetId;
- public int widgetType;
- }
-
- /**
- * 点击监听器接口
- */
- public interface OnItemClickListener {
- void onItemClick(View view, int position, long id);
- }
-
- /**
- * 长按监听器接口
- */
- public interface OnItemLongClickListener {
- boolean onItemLongClick(View view, int position, long id);
- }
-
- /**
- * 构造器
- *
- * 初始化笔记列表适配器,创建选中状态Map和计数器
- *
- * @param context 应用上下文,不能为 null
- */
- public NotesRecyclerViewAdapter(Context context) {
- mSelectedIndex = new SparseArray<>();
- mContext = context;
- mNotesCount = 0;
- }
-
- /**
- * 创建ViewHolder
- *
- * @param parent 父视图组
- * @param viewType 视图类型
- * @return 新创建的NoteViewHolder对象
- */
- @Override
- public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(mContext)
- .inflate(R.layout.note_item, parent, false);
- final NoteViewHolder holder = new NoteViewHolder(view);
-
- // 设置点击监听
- view.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOnItemClickListener != null) {
- int position = holder.getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- long id = getItemId(position);
- mOnItemClickListener.onItemClick(v, position, id);
- }
- }
- }
- });
-
- // 设置长按监听
- view.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mOnItemLongClickListener != null) {
- int position = holder.getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- long id = getItemId(position);
- return mOnItemLongClickListener.onItemLongClick(v, position, id);
- }
- }
- return false;
- }
- });
-
- return holder;
- }
-
- /**
- * 绑定数据到ViewHolder
- *
- * 将数据库游标中的数据绑定到已存在的ViewHolder上
- *
- * @param holder 需要绑定数据的ViewHolder
- * @param position 列表项位置
- */
- @Override
- public void onBindViewHolder(NoteViewHolder holder, int position) {
- if (mCursor != null && mCursor.moveToPosition(position)) {
- NoteItemData itemData = new NoteItemData(mContext, mCursor);
-
- // 绑定数据到视图
- bindViewHolder(holder, itemData, position);
- }
- }
-
- /**
- * 绑定ViewHolder数据
- *
- * @param holder ViewHolder对象
- * @param data 笔记数据
- * @param position 位置
- */
- private void bindViewHolder(NoteViewHolder holder, NoteItemData data, int position) {
- // 处理复选框
- if (mChoiceMode && data.getType() == Notes.TYPE_NOTE) {
- holder.mCheckBox.setVisibility(View.VISIBLE);
- holder.mCheckBox.setChecked(isSelectedItem(position));
- } else {
- holder.mCheckBox.setVisibility(View.GONE);
- }
-
- // 处理提醒图标
- if (data.hasAlert()) {
- holder.mAlert.setImageResource(R.drawable.clock);
- holder.mAlert.setVisibility(View.VISIBLE);
- } else {
- holder.mAlert.setVisibility(View.GONE);
- }
-
- // 处理标题
- if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
- holder.mCallName.setVisibility(View.GONE);
- holder.mAlert.setVisibility(View.VISIBLE);
- holder.mTitle.setTextAppearance(mContext, R.style.TextAppearancePrimaryItem);
- holder.mTitle.setText(mContext.getString(R.string.call_record_folder_name)
- + mContext.getString(R.string.format_folder_files_count, data.getNotesCount()));
- holder.mAlert.setImageResource(R.drawable.call_record);
- } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
- holder.mCallName.setVisibility(View.VISIBLE);
- holder.mCallName.setText(data.getCallName());
- holder.mTitle.setTextAppearance(mContext, R.style.TextAppearanceSecondaryItem);
- holder.mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
- } else {
- holder.mCallName.setVisibility(View.GONE);
- holder.mTitle.setTextAppearance(mContext, R.style.TextAppearancePrimaryItem);
-
- if (data.getType() == Notes.TYPE_FOLDER) {
- holder.mTitle.setText(data.getSnippet()
- + mContext.getString(R.string.format_folder_files_count,
- data.getNotesCount()));
- holder.mAlert.setVisibility(View.GONE);
- } else {
- holder.mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
- if (data.hasAlert()) {
- holder.mAlert.setImageResource(R.drawable.clock);
- holder.mAlert.setVisibility(View.VISIBLE);
- } else {
- holder.mAlert.setVisibility(View.GONE);
- }
- }
- }
-
- // 设置时间
- holder.mTime.setText(android.text.format.DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
-
- // 设置背景
- setBackground(holder.itemView, data);
- }
-
- /**
- * 根据笔记项的位置和类型设置合适的背景资源
- * @param view 列表项视图
- * @param data 笔记数据
- */
- private void setBackground(View view, NoteItemData data) {
- int id = data.getBgColorId();
- if (data.getType() == Notes.TYPE_NOTE) {
- int bgRes;
- if (data.isSingle() || data.isOneFollowingFolder()) {
- bgRes = NoteItemBgResources.getNoteBgSingleRes(id);
- } else if (data.isLast()) {
- bgRes = NoteItemBgResources.getNoteBgLastRes(id);
- } else if (data.isFirst() || data.isMultiFollowingFolder()) {
- bgRes = NoteItemBgResources.getNoteBgFirstRes(id);
- } else {
- bgRes = NoteItemBgResources.getNoteBgNormalRes(id);
- }
- view.setBackgroundResource(bgRes);
- } else {
- view.setBackgroundResource(NoteItemBgResources.getFolderBgRes());
- }
- }
-
- /**
- * 获取列表项数量
- *
- * @return 列表项数量,如果游标为null则返回0
- */
- @Override
- public int getItemCount() {
- return mCursor != null ? mCursor.getCount() : 0;
- }
-
- /**
- * 获取指定位置的列表项ID
- *
- * @param position 列表项位置
- * @return 列表项ID,如果游标为null或位置无效则返回0
- */
- public long getItemId(int position) {
- if (mCursor != null && mCursor.moveToPosition(position)) {
- return mCursor.getLong(mCursor.getColumnIndexOrThrow(Notes.NoteColumns.ID));
- }
- return 0;
- }
-
- /**
- * 获取指定位置的列表项
- *
- * @param position 列表项位置
- * @return 列表项对象,如果游标为null或位置无效则返回null
- */
- public Object getItem(int position) {
- if (mCursor != null && mCursor.moveToPosition(position)) {
- return mCursor;
- }
- return null;
- }
-
- /**
- * 设置指定位置的选中状态
- *
- * @param position 列表项位置,从0开始
- * @param checked 是否选中
- */
- public void setCheckedItem(int position, boolean checked) {
- mSelectedIndex.put(position, checked);
- notifyItemChanged(position);
- }
-
- /**
- * 判断是否处于选择模式
- *
- * @return 如果处于选择模式返回true,否则返回false
- */
- public boolean isInChoiceMode() {
- return mChoiceMode;
- }
-
- /**
- * 设置选择模式
- *
- * @param mode true表示进入选择模式,false表示退出选择模式
- */
- public void setChoiceMode(boolean mode) {
- mSelectedIndex.clear();
- mChoiceMode = mode;
- notifyDataSetChanged();
- }
-
- /**
- * 全选或取消全选所有笔记
- *
- * @param checked true表示全选,false表示取消全选
- */
- public void selectAll(boolean checked) {
- if (mCursor != null) {
- for (int i = 0; i < getItemCount(); i++) {
- if (mCursor.moveToPosition(i)) {
- if (NoteItemData.getNoteType(mCursor) == Notes.TYPE_NOTE) {
- mSelectedIndex.put(i, checked);
- }
- }
- }
- notifyDataSetChanged();
- }
- }
-
- /**
- * 获取所有选中项的笔记ID集合
- *
- * @return 包含所有选中笔记ID的HashSet集合,如果没有选中项则返回空集合
- */
- public HashSet getSelectedItemIds() {
- HashSet itemSet = new HashSet<>();
- for (int i = 0; i < mSelectedIndex.size(); i++) {
- int key = mSelectedIndex.keyAt(i);
- if (mSelectedIndex.get(key)) {
- long id = getItemId(key);
- if (id == Notes.ID_ROOT_FOLDER) {
- Log.d(TAG, "Wrong item id, should not happen");
- } else {
- itemSet.add(id);
- }
- }
- }
- return itemSet;
- }
-
- /**
- * 获取所有选中项关联的桌面小部件集合
- *
- * @return 包含所有选中笔记关联的桌面小部件属性的HashSet集合,如果游标无效则返回null
- */
- public HashSet getSelectedWidget() {
- HashSet itemSet = new HashSet<>();
- for (int i = 0; i < mSelectedIndex.size(); i++) {
- int key = mSelectedIndex.keyAt(i);
- if (mSelectedIndex.get(key)) {
- Cursor c = (Cursor) getItem(key);
- if (c != null) {
- AppWidgetAttribute widget = new AppWidgetAttribute();
- NoteItemData item = new NoteItemData(mContext, c);
- widget.widgetId = item.getWidgetId();
- widget.widgetType = item.getWidgetType();
- itemSet.add(widget);
- } else {
- Log.e(TAG, "Invalid cursor");
- return null;
- }
- }
- }
- return itemSet;
- }
-
- /**
- * 获取选中项的数量
- *
- * @return 选中项的数量,如果没有选中项则返回0
- */
- public int getSelectedCount() {
- int count = 0;
- for (int i = 0; i < mSelectedIndex.size(); i++) {
- if (mSelectedIndex.get(mSelectedIndex.keyAt(i))) {
- count++;
- }
- }
- return count;
- }
-
- /**
- * 判断是否已全选所有笔记
- *
- * @return 如果所有笔记都被选中且至少有一个笔记则返回true,否则返回false
- */
- public boolean isAllSelected() {
- int checkedCount = getSelectedCount();
- return (checkedCount != 0 && checkedCount == mNotesCount);
- }
-
- /**
- * 判断指定位置的项是否被选中
- *
- * @param position 列表项位置,从0开始
- * @return 如果该项被选中返回true,否则返回false
- */
- public boolean isSelectedItem(int position) {
- return mSelectedIndex.get(position, false);
- }
-
- /**
- * 更换游标
- *
- * @param newCursor 新的数据库游标
- */
- public Cursor swapCursor(Cursor newCursor) {
- Cursor oldCursor = mCursor;
- mCursor = newCursor;
- calcNotesCount();
- notifyDataSetChanged();
- return oldCursor;
- }
-
- /**
- * 计算笔记数量
- */
- private void calcNotesCount() {
- mNotesCount = 0;
- if (mCursor != null) {
- for (int i = 0; i < getItemCount(); i++) {
- if (mCursor.moveToPosition(i)) {
- if (NoteItemData.getNoteType(mCursor) == Notes.TYPE_NOTE) {
- mNotesCount++;
- }
- }
- }
- }
- }
-
- /**
- * 设置点击监听器
- *
- * @param listener 点击监听器
- */
- public void setOnItemClickListener(OnItemClickListener listener) {
- mOnItemClickListener = listener;
- }
-
- /**
- * 设置长按监听器
- *
- * @param listener 长按监听器
- */
- public void setOnItemLongClickListener(OnItemLongClickListener listener) {
- mOnItemLongClickListener = listener;
- }
-}
diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/StaggeredGridSpacingItemDecoration.java b/src/Notesmaster/app/src/main/java/net/micode/notes/ui/StaggeredGridSpacingItemDecoration.java
deleted file mode 100644
index 8f73ff8..0000000
--- a/src/Notesmaster/app/src/main/java/net/micode/notes/ui/StaggeredGridSpacingItemDecoration.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.graphics.Rect;
-import android.view.View;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.StaggeredGridLayoutManager;
-
-/**
- * 瀑布流布局间距装饰类
- *
- * 为瀑布流布局的RecyclerView添加统一的间距,确保每个网格项之间有合适的间隔。
- * 支持是否包含边缘间距的配置。
- *
- */
-public class StaggeredGridSpacingItemDecoration extends RecyclerView.ItemDecoration {
- private int spanCount;
- private int spacing;
- private boolean includeEdge;
-
- /**
- * 构造函数
- * @param spanCount 网格列数
- * @param spacing 间距大小(像素)
- * @param includeEdge 是否包含边缘间距
- */
- public StaggeredGridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
- this.spanCount = spanCount;
- this.spacing = spacing;
- this.includeEdge = includeEdge;
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- StaggeredGridLayoutManager.LayoutParams params =
- (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
- int spanIndex = params.getSpanIndex();
-
- if (includeEdge) {
- outRect.left = spacing - spanIndex * spacing / spanCount;
- outRect.right = (spanIndex + 1) * spacing / spanCount;
-
- if (params.getViewAdapterPosition() < spanCount) {
- outRect.top = spacing;
- }
- outRect.bottom = spacing;
- } else {
- outRect.left = spanIndex * spacing / spanCount;
- outRect.right = spacing - (spanIndex + 1) * spacing / spanCount;
- if (params.getViewAdapterPosition() >= spanCount) {
- outRect.top = spacing;
- }
- }
- }
-}
diff --git a/src/Notesmaster/app/src/main/res/drawable/list_divider.xml b/src/Notesmaster/app/src/main/res/drawable/list_divider.xml
deleted file mode 100644
index 4a9645b..0000000
--- a/src/Notesmaster/app/src/main/res/drawable/list_divider.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/Notesmaster/app/src/main/res/layout/activity_main.xml b/src/Notesmaster/app/src/main/res/layout/activity_main.xml
index 86a5d97..80c956c 100644
--- a/src/Notesmaster/app/src/main/res/layout/activity_main.xml
+++ b/src/Notesmaster/app/src/main/res/layout/activity_main.xml
@@ -1,19 +1,37 @@
-
-
+
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Notesmaster/app/src/main/res/layout/layout_settings_dialog.xml b/src/Notesmaster/app/src/main/res/layout/layout_settings_dialog.xml
deleted file mode 100644
index 907ec21..0000000
--- a/src/Notesmaster/app/src/main/res/layout/layout_settings_dialog.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Notesmaster/app/src/main/res/layout/note_edit.xml b/src/Notesmaster/app/src/main/res/layout/note_edit.xml
index ac3cad0..8c449c4 100644
--- a/src/Notesmaster/app/src/main/res/layout/note_edit.xml
+++ b/src/Notesmaster/app/src/main/res/layout/note_edit.xml
@@ -1,45 +1,56 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+ android:fadingEdgeLength="0dp">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
-
-
+
-
-
-
+
-
+
-
-
-
-
+
-
+
+
+
+
-
-
-
-
+
-
+
+
+
+
-
-
-
-
+
-
+
+
+
+
-
-
+
-
+
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+ android:background="@drawable/font_size_selector_bg"
+ android:layout_gravity="bottom"
+ android:visibility="gone">
-
+ android:layout_weight="1">
-
-
-
-
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:gravity="center">
-
-
-
-
+
-
+
+
+ android:layout_gravity="bottom|right"
+ android:layout_marginRight="6dp"
+ android:layout_marginBottom="-7dp"
+ android:focusable="false"
+ android:visibility="gone"
+ android:src="@drawable/selected" />
+
+
+
-
-
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:gravity="center">
-
-
-
-
+
-
+
+
+ android:layout_gravity="bottom|right"
+ android:focusable="false"
+ android:visibility="gone"
+ android:layout_marginRight="6dp"
+ android:layout_marginBottom="-7dp"
+ android:src="@drawable/selected" />
+
+
+
-
-
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:gravity="center">
-
-
-
-
+
-
+
+
+ android:layout_gravity="bottom|right"
+ android:focusable="false"
+ android:visibility="gone"
+ android:layout_marginRight="6dp"
+ android:layout_marginBottom="-7dp"
+ android:src="@drawable/selected" />
+
+
+
-
-
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:gravity="center">
-
-
+
+
+
+
+
+
+
+
-
+
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 d541f6a..b23af8f 100644
--- a/src/Notesmaster/app/src/main/res/layout/note_item.xml
+++ b/src/Notesmaster/app/src/main/res/layout/note_item.xml
@@ -15,51 +15,62 @@
limitations under the License.
-->
-
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="12dp"
+ android:background="@null">
+
-
+ android:textSize="16sp"
+ android:textColor="@android:color/black"
+ android:textStyle="bold"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:singleLine="true" />
-
-
-
+
+
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
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 58fb839..c157627 100644
--- a/src/Notesmaster/app/src/main/res/layout/note_list.xml
+++ b/src/Notesmaster/app/src/main/res/layout/note_list.xml
@@ -1,59 +1,101 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Notesmaster/app/src/main/res/menu/note_list.xml b/src/Notesmaster/app/src/main/res/menu/note_list.xml
index 1e4b644..42ea736 100644
--- a/src/Notesmaster/app/src/main/res/menu/note_list.xml
+++ b/src/Notesmaster/app/src/main/res/menu/note_list.xml
@@ -36,8 +36,4 @@
-
-
diff --git a/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml b/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml
index 09f75ed..61b9801 100644
--- a/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values-zh-rCN/strings.xml
@@ -120,7 +120,17 @@
设置
取消
- %1$s 条符合“%2$s ”的搜索结果
+ %1$s 条符合"%2$s "的搜索结果
+
+ 我的便签
+ %d 个便签
+ 创建文件夹
+ 文件夹名称
+ 文件夹名称不能为空
+ 文件夹名称过长(最多50个字符)
+ 回收站
+ 创建文件夹成功
+
diff --git a/src/Notesmaster/app/src/main/res/values/colors.xml b/src/Notesmaster/app/src/main/res/values/colors.xml
index 123ffbf..82d81bf 100644
--- a/src/Notesmaster/app/src/main/res/values/colors.xml
+++ b/src/Notesmaster/app/src/main/res/values/colors.xml
@@ -17,4 +17,7 @@
#335b5b5b
+ #1976D2
+ #FFFFFF
+ #FAFAFA
diff --git a/src/Notesmaster/app/src/main/res/values/strings.xml b/src/Notesmaster/app/src/main/res/values/strings.xml
index 4b4439f..a4b4991 100644
--- a/src/Notesmaster/app/src/main/res/values/strings.xml
+++ b/src/Notesmaster/app/src/main/res/values/strings.xml
@@ -48,15 +48,6 @@
Search
Delete
Move to folder
- Switch Layout
- List Layout
- Grid Layout
- Staggered Layout
- Layout switched to %s
- Failed to switch layout: %s
- Layout Settings
- Grid Columns
- Item Spacing
%d selected
Nothing selected, the operation is invalid
Select all
@@ -90,6 +81,8 @@
The note is not exist
Sorry, can not set clock on empty note
Sorry, can not send and empty note to home
+ Invalid intent
+ Unsupported intent action
Export successful
Export fail
Export text file (%1$s) to SD (%2$s) directory
@@ -141,4 +134,24 @@
%1$s results for \"%2$s \"
+ 暂无便签,点击右下角按钮创建
+ 空便签图标
+ Edit note
+
+ Login
+ Export
+ Settings
+ Trash
+ My Notes
+ Close sidebar
+ Create folder
+ %d notes
+ Create folder
+ Folder name
+ Folder name cannot be empty
+ Folder name too long (max 50 characters)
+ Folder already exists
+ Folder created successfully
+ Pin
+ Unpin
diff --git a/src/Notesmaster/app/src/main/res/values/themes.xml b/src/Notesmaster/app/src/main/res/values/themes.xml
index 7c616ff..ca2f0be 100644
--- a/src/Notesmaster/app/src/main/res/values/themes.xml
+++ b/src/Notesmaster/app/src/main/res/values/themes.xml
@@ -1,9 +1,17 @@
+
+
+
\ No newline at end of file