diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 2350512..243ffcc 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -48,7 +48,7 @@
-
+ >
@@ -67,13 +67,6 @@
android:resource="@xml/searchable" />
-
-
-
+
+
+
diff --git a/src/main/java/net/micode/notes/data/Notes.java b/src/main/java/net/micode/notes/data/Notes.java
index 11b031a..297b7dc 100644
--- a/src/main/java/net/micode/notes/data/Notes.java
+++ b/src/main/java/net/micode/notes/data/Notes.java
@@ -264,6 +264,13 @@ public class Notes {
* 0表示便签锁,1表示文件夹锁
*/
public static final String LOCK_TYPE = "lock_type";
+
+ /**
+ * 便签或文件夹被删除的时间
+ * 类型: INTEGER (long)
+ * 0表示未被删除,其他值表示删除时间戳
+ */
+ public static final String DELETED_DATE = "deleted_date";
}
public interface DataColumns {
diff --git a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java
index ce29b9f..4edf21d 100644
--- a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java
+++ b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java
@@ -35,7 +35,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库文件名
private static final String DB_NAME = "note.db";
// 数据库版本号,用于升级控制
- private static final int DB_VERSION = 6;
+ private static final int DB_VERSION = 7;
/**
* 数据库表名常量定义
@@ -78,7 +78,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.PINNED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.IS_LOCKED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCK_PASSWORD + " TEXT NOT NULL DEFAULT ''," +
- NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0" +
+ NoteColumns.LOCK_TYPE + " INTEGER NOT NULL DEFAULT 0," +
+ NoteColumns.DELETED_DATE + " INTEGER NOT NULL DEFAULT 0" +
")";
/**
@@ -263,6 +264,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
+ db.execSQL("DROP TRIGGER IF EXISTS record_deleted_date_on_trash");
+ db.execSQL("DROP TRIGGER IF EXISTS clear_deleted_date_on_restore");
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
@@ -271,6 +274,28 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
+
+ // 创建记录删除时间触发器
+ db.execSQL("CREATE TRIGGER record_deleted_date_on_trash " +
+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
+ " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
+ " AND old." + NoteColumns.PARENT_ID + "!=" + Notes.ID_TRASH_FOLER +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.DELETED_DATE + "=strftime('%s','now') * 1000" +
+ " WHERE " + NoteColumns.ID + "=new." + NoteColumns.ID + ";" +
+ " END");
+
+ // 创建清除删除时间触发器
+ db.execSQL("CREATE TRIGGER clear_deleted_date_on_restore " +
+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
+ " WHEN new." + NoteColumns.PARENT_ID + "!=" + Notes.ID_TRASH_FOLER +
+ " AND old." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.DELETED_DATE + "=0" +
+ " WHERE " + NoteColumns.ID + "=new." + NoteColumns.ID + ";" +
+ " END");
}
/**
@@ -394,6 +419,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
+ // 版本6升级到版本7:添加删除时间字段和相关触发器
+ if (oldVersion == 6) {
+ upgradeToV7(db);
+ oldVersion++;
+ }
+
// 如果需要,重新创建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
@@ -403,7 +434,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 验证升级是否完成
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
- + "fails");
+ + " fails");
}
}
@@ -467,4 +498,36 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_TYPE
+ " INTEGER NOT NULL DEFAULT 0");
}
+
+ /**
+ * 升级到版本7:添加删除时间字段和相关触发器,用于最近删除功能
+ * @param db 数据库实例
+ */
+ private void upgradeToV7(SQLiteDatabase db) {
+ // 添加DELETED_DATE字段,用于记录便签被删除的时间
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.DELETED_DATE
+ + " INTEGER NOT NULL DEFAULT 0");
+
+ // 创建触发器:当便签被移动到回收站时,自动记录删除时间
+ db.execSQL("CREATE TRIGGER record_deleted_date_on_trash " +
+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
+ " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
+ " AND old." + NoteColumns.PARENT_ID + "!=" + Notes.ID_TRASH_FOLER +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.DELETED_DATE + "=strftime('%s','now') * 1000" +
+ " WHERE " + NoteColumns.ID + "=new." + NoteColumns.ID + ";" +
+ " END");
+
+ // 创建触发器:当便签从回收站恢复时,自动清除删除时间
+ db.execSQL("CREATE TRIGGER clear_deleted_date_on_restore " +
+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
+ " WHEN new." + NoteColumns.PARENT_ID + "!=" + Notes.ID_TRASH_FOLER +
+ " AND old." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.DELETED_DATE + "=0" +
+ " WHERE " + NoteColumns.ID + "=new." + NoteColumns.ID + ";" +
+ " END");
+ }
}
\ No newline at end of file
diff --git a/src/main/java/net/micode/notes/data/NotesProvider.java b/src/main/java/net/micode/notes/data/NotesProvider.java
index 678534f..ceb1020 100644
--- a/src/main/java/net/micode/notes/data/NotesProvider.java
+++ b/src/main/java/net/micode/notes/data/NotesProvider.java
@@ -169,7 +169,11 @@ public class NotesProvider extends ContentProvider {
searchString = uri.getPathSegments().get(1);
}
} else {
- searchString = uri.getQueryParameter("pattern");
+ // 支持Android搜索框架的标准参数名"query"和当前实现使用的"pattern"
+ searchString = uri.getQueryParameter(SearchManager.QUERY);
+ if (TextUtils.isEmpty(searchString)) {
+ searchString = uri.getQueryParameter("pattern");
+ }
}
// 如果搜索关键词为空,返回null
diff --git a/src/main/java/net/micode/notes/model/RecentlyDeletedManager.java b/src/main/java/net/micode/notes/model/RecentlyDeletedManager.java
new file mode 100644
index 0000000..c025468
--- /dev/null
+++ b/src/main/java/net/micode/notes/model/RecentlyDeletedManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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.model;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.tool.DataUtils;
+
+import java.util.HashSet;
+
+/**
+ * 最近删除管理器,负责管理最近删除的便签和文件夹
+ * 采用单例模式设计,提供获取最近删除项、恢复项、永久删除项、清空回收站等功能
+ */
+public class RecentlyDeletedManager {
+ // 默认保留天数,超过此天数的已删除项将被自动清理
+ public static final int DEFAULT_RETENTION_DAYS = 30;
+ // 清理任务间隔(每天)
+ public static final long CLEANUP_INTERVAL = 24 * 60 * 60 * 1000;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ // 单例实例
+ private static RecentlyDeletedManager sInstance;
+ // 日志标签
+ private static final String TAG = "RecentlyDeletedManager";
+
+ /**
+ * 私有构造方法,防止外部实例化
+ * @param context 上下文对象
+ */
+ private RecentlyDeletedManager(Context context) {
+ mContext = context.getApplicationContext();
+ mContentResolver = mContext.getContentResolver();
+ }
+
+ /**
+ * 获取单例实例
+ * @param context 上下文对象
+ * @return 最近删除管理器实例
+ */
+ public static synchronized RecentlyDeletedManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new RecentlyDeletedManager(context);
+ }
+ return sInstance;
+ }
+
+ /**
+ * 获取最近删除的项列表
+ * @return 最近删除项的游标,按删除时间倒序排列
+ */
+ public Cursor getRecentlyDeletedItems() {
+ // 查询条件:已删除(DELETED_DATE > 0)且位于回收站文件夹
+ String selection = NoteColumns.DELETED_DATE + " > 0 AND " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER;
+ // 按删除时间倒序排序
+ String sortOrder = NoteColumns.DELETED_DATE + " DESC";
+
+ try {
+ return mContentResolver.query(
+ Notes.CONTENT_NOTE_URI,
+ null, // 查询所有列
+ selection,
+ null,
+ sortOrder
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get recently deleted items", e);
+ return null;
+ }
+ }
+
+ /**
+ * 恢复指定项
+ * @param itemIds 要恢复的项ID数组
+ * @return 成功恢复的项数量
+ */
+ public int restoreItems(long[] itemIds) {
+ if (itemIds == null || itemIds.length == 0) {
+ return 0;
+ }
+
+ int restoredCount = 0;
+
+ for (long itemId : itemIds) {
+ try {
+ // 获取原父文件夹ID
+ String[] projection = {NoteColumns.ORIGIN_PARENT_ID};
+ String selection = NoteColumns.ID + "=" + itemId;
+
+ Cursor cursor = mContentResolver.query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ null,
+ null
+ );
+
+ if (cursor != null && cursor.moveToFirst()) {
+ long originParentId = cursor.getLong(0);
+ cursor.close();
+
+ // 如果原父文件夹不存在或已被删除,使用根文件夹
+ long targetParentId = originParentId != Notes.ID_TRASH_FOLER ? originParentId : Notes.ID_ROOT_FOLDER;
+
+ // 使用现有的batchMoveToFolder方法来恢复项目
+ HashSet ids = new HashSet();
+ ids.add(itemId);
+ if (DataUtils.batchMoveToFolder(mContentResolver, ids, targetParentId)) {
+ restoredCount++;
+ }
+ } else if (cursor != null) {
+ cursor.close();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to restore item: " + itemId, e);
+ }
+ }
+
+ return restoredCount;
+ }
+
+ /**
+ * 永久删除指定项
+ * @param itemIds 要永久删除的项ID数组
+ * @return 成功永久删除的项数量
+ */
+ public int permanentlyDeleteItems(long[] itemIds) {
+ if (itemIds == null || itemIds.length == 0) {
+ return 0;
+ }
+
+ int deletedCount = 0;
+
+ for (long itemId : itemIds) {
+ try {
+ int rowsAffected = mContentResolver.delete(
+ Notes.CONTENT_NOTE_URI,
+ NoteColumns.ID + "=" + itemId,
+ null
+ );
+
+ if (rowsAffected > 0) {
+ deletedCount++;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to permanently delete item: " + itemId, e);
+ }
+ }
+
+ return deletedCount;
+ }
+
+ /**
+ * 清空回收站
+ * @return 成功清空的项数量
+ */
+ public int emptyTrash() {
+ try {
+ // 删除所有已删除且位于回收站的项
+ String selection = NoteColumns.DELETED_DATE + " > 0 AND " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER;
+
+ return mContentResolver.delete(
+ Notes.CONTENT_NOTE_URI,
+ selection,
+ null
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to empty trash", e);
+ return 0;
+ }
+ }
+
+ /**
+ * 清理超过保留天数的项
+ * @param retentionDays 保留天数
+ * @return 成功清理的项数量
+ */
+ public int cleanupOldItems(int retentionDays) {
+ if (retentionDays < 0) {
+ retentionDays = DEFAULT_RETENTION_DAYS;
+ }
+
+ try {
+ // 计算清理阈值:当前时间减去保留天数(毫秒)
+ long cleanupThreshold = System.currentTimeMillis() - (retentionDays * 24 * 60 * 60 * 1000);
+
+ // 删除条件:已删除且删除时间小于清理阈值
+ String selection = NoteColumns.DELETED_DATE + " > 0 AND " + NoteColumns.DELETED_DATE + " < " + cleanupThreshold;
+
+ return mContentResolver.delete(
+ Notes.CONTENT_NOTE_URI,
+ selection,
+ null
+ );
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to cleanup old items", e);
+ return 0;
+ }
+ }
+
+ /**
+ * 启动自动清理任务
+ * 注意:此方法在当前实现中仅作为占位符,实际自动清理逻辑由TrashCleanupWorker实现
+ */
+ public void startAutoCleanup() {
+ // 自动清理任务由TrashCleanupWorker处理,此处预留接口
+ Log.d(TAG, "Auto cleanup task is managed by TrashCleanupWorker");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/micode/notes/model/WorkingNote.java b/src/main/java/net/micode/notes/model/WorkingNote.java
index d303c30..c495432 100644
--- a/src/main/java/net/micode/notes/model/WorkingNote.java
+++ b/src/main/java/net/micode/notes/model/WorkingNote.java
@@ -333,7 +333,7 @@ public class WorkingNote {
*
* 将当前工作笔记保存到数据库中。如果笔记不存在,则创建新笔记;
* 如果笔记已存在,则更新现有笔记。
- * 如果笔记内容为空且已存在于数据库,则删除该笔记。
+ * 如果笔记内容为空且已存在于数据库,则将其移动到回收站。
*
*
* @return 保存成功返回true,否则返回false
@@ -359,12 +359,13 @@ public class WorkingNote {
}
return true;
} else {
- // 添加删除逻辑:如果笔记内容为空且已存在于数据库,则删除该笔记
+ // 添加删除逻辑:如果笔记内容为空且已存在于数据库,则将其移动到回收站
if (existInDatabase() && TextUtils.isEmpty(mContent)) {
HashSet ids = new HashSet();
ids.add(mNoteId);
- if (!DataUtils.batchDeleteNotes(mContext.getContentResolver(), ids)) {
- Log.e(TAG, "Delete empty note error");
+ // 无论是否在同步模式,都将空便签移动到回收站
+ if (!DataUtils.batchMoveToFolder(mContext.getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
+ Log.e(TAG, "Move empty note to trash error");
}
mIsDeleted = true;
}
diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java
index 2777247..c80949f 100644
--- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java
+++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java
@@ -69,6 +69,7 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
+import net.micode.notes.ui.TemplateSelectActivity;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
@@ -141,8 +142,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
/** 日志标签 */
private static final String TAG = "NoteEditActivity";
- /** 请求选择模板的请求码 */
- private static final int REQUEST_SELECT_TEMPLATE = 100;
/** 头部视图持有者 */
private HeadViewHolder mNoteHeaderHolder;
@@ -192,6 +191,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
/** 未选中标记 */
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
+ /** 模板选择请求码 */
+ private static final int REQUEST_CODE_TEMPLATE = 1001;
+
/** 清单模式下的编辑文本列表 */
private LinearLayout mEditTextList;
@@ -766,12 +768,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
case R.id.menu_unlock:
showPasswordDialogForUnlock();
break;
- case R.id.menu_template:
- // 启动模板选择Activity
- Intent intent = new Intent(this, TemplateSelectActivity.class);
- getWorkingText();
- intent.putExtra(TemplateSelectActivity.EXTRA_CURRENT_NOTE_CONTENT, mWorkingNote.getContent());
- startActivityForResult(intent, REQUEST_SELECT_TEMPLATE);
+ case R.id.menu_note_template:
+ openTemplateSelector();
break;
default:
break;
@@ -779,30 +777,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
- /**
- * 处理从其他Activity返回的结果
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_SELECT_TEMPLATE && resultCode == RESULT_OK && data != null) {
- // 获取选择的模板内容
- String templateContent = data.getStringExtra(TemplateSelectActivity.EXTRA_TEMPLATE_CONTENT);
- if (templateContent != null) {
- // 应用模板内容到当前笔记
- mWorkingNote.setWorkingText(templateContent);
- if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
- switchToListMode(templateContent);
- } else {
- // 解析HTML格式的富文本内容,确保粗体和回车能正确显示
- CharSequence htmlContent = Html.fromHtml(templateContent);
- mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery));
- mNoteEditor.setSelection(mNoteEditor.getText().length());
- }
- }
- }
- }
-
/**
* 设置笔记的提醒时间
*
@@ -1408,6 +1382,55 @@ public class NoteEditActivity extends Activity implements OnClickListener,
showToast(resId, Toast.LENGTH_SHORT);
}
+ /**
+ * 打开模板选择页面
+ *
+ * 该方法启动TemplateSelectActivity,让用户选择便签模板
+ *
+ */
+ private void openTemplateSelector() {
+ // 获取当前笔记内容
+ getWorkingText();
+ String currentContent = mWorkingNote.getContent();
+
+ Intent intent = new Intent(this, TemplateSelectActivity.class);
+ intent.putExtra(TemplateSelectActivity.EXTRA_CURRENT_NOTE_CONTENT, currentContent);
+ startActivityForResult(intent, REQUEST_CODE_TEMPLATE);
+ }
+
+ /**
+ * 处理从模板选择页面返回的结果
+ *
+ * 该方法在从TemplateSelectActivity返回时被调用,
+ * 如果用户选择了模板,则更新当前笔记的内容
+ *
+ * @param requestCode 请求码
+ * @param resultCode 结果码
+ * @param data 返回的Intent数据
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_CODE_TEMPLATE && resultCode == RESULT_OK) {
+ if (data != null) {
+ String templateContent = data.getStringExtra(TemplateSelectActivity.EXTRA_TEMPLATE_CONTENT);
+ if (!TextUtils.isEmpty(templateContent)) {
+ // 保存模板内容到WorkingNote,防止onResume时被覆盖
+ mWorkingNote.setWorkingText(templateContent);
+
+ // 更新笔记内容
+ if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
+ switchToListMode(templateContent);
+ } else {
+ mNoteEditor.setText(Html.fromHtml(templateContent));
+ mNoteEditor.setSelection(mNoteEditor.getText().length());
+ }
+ Toast.makeText(this, R.string.notealert_enter, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ }
+
/**
* 显示Toast提示信息
*
diff --git a/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/main/java/net/micode/notes/ui/NoteItemData.java
index e2dc7c6..2dcdedf 100644
--- a/src/main/java/net/micode/notes/ui/NoteItemData.java
+++ b/src/main/java/net/micode/notes/ui/NoteItemData.java
@@ -57,6 +57,7 @@ public class NoteItemData {
NoteColumns.IS_LOCKED,
NoteColumns.LOCK_PASSWORD,
NoteColumns.LOCK_TYPE,
+ NoteColumns.DELETED_DATE,
};
/**
@@ -138,6 +139,11 @@ public class NoteItemData {
* 查询结果中锁定类型列的索引
*/
private static final int LOCK_TYPE_COLUMN = 15;
+
+ /**
+ * 查询结果中删除日期列的索引
+ */
+ private static final int DELETED_DATE_COLUMN = 16;
/**
* 笔记ID
@@ -189,6 +195,11 @@ public class NoteItemData {
*/
private long mModifiedDate;
+ /**
+ * 删除日期
+ */
+ private long mDeletedDate;
+
/**
* 笔记数量(用于文件夹)
*/
@@ -279,6 +290,7 @@ public class NoteItemData {
mIsLocked = (cursor.getInt(IS_LOCKED_COLUMN) > 0) ? true : false;
mLockPassword = cursor.getString(LOCK_PASSWORD_COLUMN);
mLockType = cursor.getInt(LOCK_TYPE_COLUMN);
+ mDeletedDate = cursor.getLong(DELETED_DATE_COLUMN);
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
@@ -560,7 +572,23 @@ public class NoteItemData {
public void setLockType(int type) {
mLockType = type;
}
-
+
+ /**
+ * 获取删除日期
+ * @return 删除日期,以毫秒为单位
+ */
+ public long getDeletedDate() {
+ return mDeletedDate;
+ }
+
+ /**
+ * 判断是否为已删除项
+ * @return 是否已删除,true表示已删除,false表示未删除
+ */
+ public boolean isDeleted() {
+ return (mDeletedDate > 0);
+ }
+
/**
* 获取笔记类型(静态方法)
* @param cursor 包含笔记数据的游标
diff --git a/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/main/java/net/micode/notes/ui/NotesListActivity.java
index a32e94c..ae6ab3f 100644
--- a/src/main/java/net/micode/notes/ui/NotesListActivity.java
+++ b/src/main/java/net/micode/notes/ui/NotesListActivity.java
@@ -295,7 +295,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
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)";
+ + NoteColumns.NOTES_COUNT + ">0)" + " OR ("
+ + NoteColumns.ID + "=" + Notes.ID_TRASH_FOLER + ")"; // 添加回收站文件夹到根目录列表
/** 打开笔记请求码 */
private final static int REQUEST_CODE_OPEN_NODE = 102;
@@ -458,6 +459,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
protected void onStart() {
super.onStart();
startAsyncNotesListQuery();
+ // 启动回收站自动清理任务
+ net.micode.notes.worker.TrashCleanupWorker.doCleanup(this);
}
/**
@@ -512,66 +515,88 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private MenuItem mMoveMenu;
/**
- * 创建动作模式
- *
- * 该方法完成以下工作:
- * 1. 加载菜单资源
- * 2. 设置删除菜单的点击监听器
- * 3. 根据当前文件夹ID和用户文件夹数量设置移动菜单的可见性
- * 4. 保存动作模式实例
- * 5. 设置列表适配器为选择模式
- * 6. 禁用列表的长按功能
- * 7. 隐藏新建笔记按钮
- * 8. 设置自定义视图
- * 9. 创建下拉菜单
- * 10. 设置下拉菜单的点击监听器
- *
- * @param mode 动作模式
- * @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);
- }
- // 添加置顶菜单初始化
- MenuItem pinMenu = menu.findItem(R.id.pin);
- if (pinMenu != null) {
- pinMenu.setOnMenuItemClickListener(this);
- }
- // 添加锁定菜单初始化
- MenuItem lockMenu = menu.findItem(R.id.lock);
- if (lockMenu != null) {
- lockMenu.setOnMenuItemClickListener(this);
- }
- mActionMode = mode;
- mNotesListAdapter.setChoiceMode(true);
- mNotesListView.setLongClickable(false);
- 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(){
- public boolean onMenuItemClick(MenuItem item) {
- mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
- updateMenu();
- return true;
+ * 创建动作模式
+ *
+ * 该方法完成以下工作:
+ * 1. 加载菜单资源
+ * 2. 设置删除菜单的点击监听器
+ * 3. 根据当前文件夹ID和用户文件夹数量设置移动菜单或恢复菜单的可见性
+ * 4. 保存动作模式实例
+ * 5. 设置列表适配器为选择模式
+ * 6. 禁用列表的长按功能
+ * 7. 隐藏新建笔记按钮
+ * 8. 设置自定义视图
+ * 9. 创建下拉菜单
+ * 10. 设置下拉菜单的点击监听器
+ *
+ * @param mode 动作模式
+ * @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);
+
+ if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
+ // 在回收站内部,显示恢复按钮
+ MenuItem restoreMenu = menu.findItem(R.id.restore);
+ if (restoreMenu != null) {
+ restoreMenu.setVisible(true);
+ restoreMenu.setOnMenuItemClickListener(this);
+ }
+ // 隐藏移动按钮
+ MenuItem moveMenu = menu.findItem(R.id.move);
+ if (moveMenu != null) {
+ moveMenu.setVisible(false);
+ }
+ } else {
+ // 在回收站外部,显示移动按钮
+ 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);
+ }
+ // 隐藏恢复按钮
+ MenuItem restoreMenu = menu.findItem(R.id.restore);
+ if (restoreMenu != null) {
+ restoreMenu.setVisible(false);
+ }
+ }
+
+ // 添加置顶菜单初始化
+ MenuItem pinMenu = menu.findItem(R.id.pin);
+ if (pinMenu != null) {
+ pinMenu.setOnMenuItemClickListener(this);
}
+ // 添加锁定菜单初始化
+ MenuItem lockMenu = menu.findItem(R.id.lock);
+ if (lockMenu != null) {
+ lockMenu.setOnMenuItemClickListener(this);
+ }
+ mActionMode = mode;
+ mNotesListAdapter.setChoiceMode(true);
+ mNotesListView.setLongClickable(false);
+ 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(){
+ public boolean onMenuItemClick(MenuItem item) {
+ mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
+ updateMenu();
+ return true;
+ }
- });
- return true;
- }
+ });
+ return true;
+ }
/**
* 更新菜单
@@ -672,55 +697,71 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
/**
- * 处理菜单项点击事件
- *
- * 该方法完成以下工作:
- * 1. 检查是否有选中的笔记,如果没有则显示提示
- * 2. 根据菜单项ID处理不同的操作:
- * - 删除:显示删除确认对话框,点击确定后调用batchDelete方法
- * - 移动:调用startQueryDestinationFolders方法查询目标文件夹
- *
- * @param item 菜单项
- * @return 是否处理成功,返回true表示成功
- */
- 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;
- }
+ * 处理菜单项点击事件
+ *
+ * 该方法完成以下工作:
+ * 1. 检查是否有选中的笔记,如果没有则显示提示
+ * 2. 根据菜单项ID处理不同的操作:
+ * - 删除:显示删除确认对话框,点击确定后调用batchDelete方法
+ * - 移动:调用startQueryDestinationFolders方法查询目标文件夹
+ * - 恢复:调用batchRestore方法恢复选中的项目
+ *
+ * @param item 菜单项
+ * @return 是否处理成功,返回true表示成功
+ */
+ 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;
+ }
- 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;
- case R.id.pin:
- togglePinnedStatus();
- break;
- case R.id.lock:
- showPasswordDialog();
- break;
- default:
- return false;
+ switch (item.getItemId()) {
+ case R.id.delete:
+ AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
+
+ if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
+ // 在回收站内部,提示永久删除
+ 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_permanently,
+ mNotesListAdapter.getSelectedCount()));
+ } else {
+ // 在回收站外部,提示移动到回收站
+ 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;
+ case R.id.restore:
+ // 恢复选中的项目
+ batchRestore();
+ break;
+ case R.id.pin:
+ togglePinnedStatus();
+ break;
+ case R.id.lock:
+ showPasswordDialog();
+ break;
+ default:
+ return false;
+ }
+ return true;
}
- return true;
- }
}
/**
@@ -913,8 +954,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
*
* 该方法在异步任务中完成以下工作:
* 1. 获取选中的部件属性
- * 2. 如果不在同步模式,直接删除选中的笔记
- * 3. 如果在同步模式,将选中的笔记移动到回收站
+ * 2. 如果当前在回收站内部,直接永久删除选中的笔记
+ * 3. 如果当前不在回收站内部,将选中的笔记移动到回收站
* 4. 更新相关部件
* 5. 结束多选模式
*
@@ -923,16 +964,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
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 {
+
+ if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
+ // 在回收站内部,直接永久删除
+ if (!DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter.getSelectedItemIds())) {
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");
@@ -956,6 +995,103 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}.execute();
}
+ /**
+ * 批量恢复回收站中的项目
+ *
+ * 该方法在异步任务中完成以下工作:
+ * 1. 获取选中的项目ID
+ * 2. 对于每个项目,查询其原始父文件夹ID
+ * 3. 将项目移动回原始父文件夹(如果原始父文件夹不存在或也在回收站中,则移动到根文件夹)
+ * 4. 更新相关部件
+ * 5. 结束动作模式
+ *
+ */
+ private void batchRestore() {
+ new AsyncTask>() {
+ protected HashSet doInBackground(Void... unused) {
+ HashSet widgets = mNotesListAdapter.getSelectedWidget();
+ HashSet selectedIds = mNotesListAdapter.getSelectedItemIds();
+
+ for (Long itemId : selectedIds) {
+ try {
+ // 查询项目的原始父文件夹ID
+ String[] projection = {NoteColumns.ORIGIN_PARENT_ID, NoteColumns.ID};
+ String selection = NoteColumns.ID + "=" + itemId;
+
+ Cursor cursor = mContentResolver.query(
+ Notes.CONTENT_NOTE_URI,
+ projection,
+ selection,
+ null,
+ null
+ );
+
+ if (cursor != null && cursor.moveToFirst()) {
+ long originParentId = cursor.getLong(0);
+ long currentId = cursor.getLong(1);
+ cursor.close();
+
+ // 检查原始父文件夹是否存在且不在回收站中
+ long targetParentId = originParentId;
+
+ // 查询原始父文件夹的信息
+ Cursor folderCursor = mContentResolver.query(
+ Notes.CONTENT_NOTE_URI,
+ new String[]{NoteColumns.PARENT_ID},
+ NoteColumns.ID + "=" + originParentId,
+ null,
+ null
+ );
+
+ boolean isValidFolder = false;
+ if (folderCursor != null) {
+ if (folderCursor.moveToFirst()) {
+ long folderParentId = folderCursor.getLong(0);
+ // 检查原始父文件夹是否在回收站中
+ if (folderParentId != Notes.ID_TRASH_FOLER) {
+ isValidFolder = true;
+ }
+ }
+ folderCursor.close();
+ }
+
+ // 如果原始父文件夹无效,使用根文件夹
+ if (!isValidFolder) {
+ targetParentId = Notes.ID_ROOT_FOLDER;
+ }
+
+ // 移动项目回原始父文件夹或根文件夹
+ HashSet ids = new HashSet();
+ ids.add(currentId);
+ if (!DataUtils.batchMoveToFolder(mContentResolver, ids, targetParentId)) {
+ Log.e(TAG, "Restore item error: " + currentId);
+ }
+ } else if (cursor != null) {
+ cursor.close();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to restore item: " + itemId, e);
+ }
+ }
+
+ 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();
+ }
+
/**
* 切换选中便签的置顶状态
*/
@@ -1010,8 +1146,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
* 该方法完成以下工作:
* 1. 检查文件夹ID是否为根文件夹ID,如果是则返回错误
* 2. 获取文件夹的部件属性
- * 3. 如果不在同步模式,直接删除文件夹
- * 4. 如果在同步模式,将文件夹移动到回收站
+ * 3. 如果文件夹在回收站外部,将其移动到回收站
+ * 4. 如果文件夹在回收站内部,直接永久删除
* 5. 更新相关部件
*
* @param folderId 要删除的文件夹ID
@@ -1026,13 +1162,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
ids.add(folderId);
HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
- if (!isSyncMode()) {
- // if not synced, delete folder directly
- DataUtils.batchDeleteNotes(mContentResolver, ids);
+
+ if (mCurrentFolderId == Notes.ID_TRASH_FOLER) {
+ // 在回收站内部,直接永久删除
+ if (!DataUtils.batchDeleteNotes(mContentResolver, ids)) {
+ Log.e(TAG, "Delete folder error, should not happens");
+ }
} 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
@@ -1641,7 +1781,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) {
- if (item.getType() == Notes.TYPE_NOTE) {
+ // 在选择模式下,允许选择所有类型的项
+ // 但根文件夹不能被选中
+ if (item.getId() != Notes.ID_ROOT_FOLDER) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
diff --git a/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/src/main/java/net/micode/notes/ui/NotesListAdapter.java
index 138e3e7..5b1a583 100644
--- a/src/main/java/net/micode/notes/ui/NotesListAdapter.java
+++ b/src/main/java/net/micode/notes/ui/NotesListAdapter.java
@@ -149,6 +149,7 @@ public class NotesListAdapter extends CursorAdapter {
* 全选或取消全选
*
* 该方法用于在选择模式下全选或取消全选所有普通笔记项
+ * 在回收站内部时,也会选择文件夹类型的项
*
* @param checked 是否全选
*/
@@ -156,7 +157,14 @@ public class NotesListAdapter extends CursorAdapter {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
- if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
+ // 获取当前项的类型
+ int noteType = NoteItemData.getNoteType(cursor);
+ // 获取当前项的父文件夹ID
+ cursor.moveToPosition(i);
+ long parentId = cursor.getLong(8); // PARENT_ID列的索引是8
+
+ // 在回收站内部时,选择所有类型的项(除了根文件夹)
+ if (parentId == Notes.ID_TRASH_FOLER || noteType == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
diff --git a/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/main/java/net/micode/notes/ui/NotesListItem.java
index c08425c..069771a 100644
--- a/src/main/java/net/micode/notes/ui/NotesListItem.java
+++ b/src/main/java/net/micode/notes/ui/NotesListItem.java
@@ -100,6 +100,13 @@ public class NotesListItem extends LinearLayout {
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
+ } else if (data.getId() == Notes.ID_TRASH_FOLER) {
+ mCallName.setVisibility(View.GONE);
+ mAlert.setVisibility(View.VISIBLE);
+ mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
+ mTitle.setText("回收站"
+ + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
+ mAlert.setImageResource(R.drawable.delete);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
@@ -117,7 +124,7 @@ public class NotesListItem extends LinearLayout {
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
- + context.getString(R.string.format_folder_files_count,
+ + context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
@@ -130,7 +137,15 @@ public class NotesListItem extends LinearLayout {
}
}
}
- mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
+ // 根据笔记是否在回收站中显示不同的时间
+ if (data.getParentId() == Notes.ID_TRASH_FOLER || data.isDeleted()) {
+ // 回收站中的项显示删除时间
+ mTime.setText(context.getString(R.string.deleted_time_format,
+ DateUtils.getRelativeTimeSpanString(data.getDeletedDate())));
+ } else {
+ // 普通项显示修改时间
+ mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
+ }
setBackground(data);
}
diff --git a/src/main/java/net/micode/notes/worker/TrashCleanupWorker.java b/src/main/java/net/micode/notes/worker/TrashCleanupWorker.java
new file mode 100644
index 0000000..5efd14f
--- /dev/null
+++ b/src/main/java/net/micode/notes/worker/TrashCleanupWorker.java
@@ -0,0 +1,81 @@
+/*
+ * 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.worker;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import net.micode.notes.model.RecentlyDeletedManager;
+
+/**
+ * 回收站自动清理工作器,负责定期清理超过保留期限的已删除项
+ * 提供静态方法doCleanup()用于手动触发清理,可在应用启动时调用
+ */
+public class TrashCleanupWorker {
+ // 日志标签
+ private static final String TAG = "TrashCleanupWorker";
+ // 上次清理时间的偏好设置键
+ private static final String PREF_LAST_CLEANUP_TIME = "last_trash_cleanup_time";
+ // 回收站保留天数的偏好设置键
+ private static final String PREF_TRASH_RETENTION_DAYS = "pref_key_trash_retention_days";
+
+ /**
+ * 执行清理任务
+ * @param context 上下文对象
+ */
+ public static void doCleanup(Context context) {
+ try {
+ // 获取偏好设置
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ long lastCleanupTime = prefs.getLong(PREF_LAST_CLEANUP_TIME, 0);
+ long currentTime = System.currentTimeMillis();
+
+ // 检查是否需要清理:如果距离上次清理时间超过24小时
+ if (currentTime - lastCleanupTime < RecentlyDeletedManager.CLEANUP_INTERVAL) {
+ Log.d(TAG, "Skip cleanup, last cleanup was within the interval");
+ return;
+ }
+
+ // 获取用户设置的保留天数
+ int retentionDays = RecentlyDeletedManager.DEFAULT_RETENTION_DAYS;
+ try {
+ String retentionDaysStr = prefs.getString(PREF_TRASH_RETENTION_DAYS, String.valueOf(RecentlyDeletedManager.DEFAULT_RETENTION_DAYS));
+ retentionDays = Integer.parseInt(retentionDaysStr);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Invalid retention days setting, using default value", e);
+ retentionDays = RecentlyDeletedManager.DEFAULT_RETENTION_DAYS;
+ }
+
+ // 执行清理
+ RecentlyDeletedManager manager = RecentlyDeletedManager.getInstance(context);
+ int deletedCount = manager.cleanupOldItems(retentionDays);
+
+ if (deletedCount > 0) {
+ Log.i(TAG, "Cleaned up " + deletedCount + " old items from trash");
+ } else {
+ Log.d(TAG, "No old items to cleanup");
+ }
+
+ // 更新上次清理时间
+ prefs.edit().putLong(PREF_LAST_CLEANUP_TIME, currentTime).apply();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to perform trash cleanup", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/res/menu/note_edit.xml b/src/main/res/menu/note_edit.xml
index 2f4f66b..0cdb685 100644
--- a/src/main/res/menu/note_edit.xml
+++ b/src/main/res/menu/note_edit.xml
@@ -57,8 +57,8 @@
-
+
+ android:id="@+id/menu_note_template"
+ android:title="@string/menu_note_template" />
\ No newline at end of file
diff --git a/src/main/res/menu/note_list_options.xml b/src/main/res/menu/note_list_options.xml
index 165c7d2..9cd4088 100644
--- a/src/main/res/menu/note_list_options.xml
+++ b/src/main/res/menu/note_list_options.xml
@@ -22,6 +22,12 @@
android:title="@string/menu_move"
android:icon="@drawable/menu_move"
android:showAsAction="always|withText" />
+
+
- Messaging
- Email
+
+
+
+ - 1 day
+ - 3 days
+ - 7 days
+ - 14 days
+ - 30 days (default)
+ - 60 days
+ - 90 days
+
+
+
+ - 1
+ - 3
+ - 7
+ - 14
+ - 30
+ - 60
+ - 90
+
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index c959907..9c1996a 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -52,21 +52,11 @@
Unpin
Lock
Unlock
- Note Template
- Save as Template
- System Template
- Custom Template
Enter Password
Please enter password
Incorrect password
Note locked successfully
Note unlocked successfully
- Enter Template Name
- Note is empty
- Template name cannot be empty
- Template saved successfully
- Template deleted successfully
- Are you sure you want to delete this template?
%d selected
Nothing selected, the operation is invalid
Select all
@@ -93,6 +83,7 @@
Delete selected notes
Confirm to delete the selected %d notes?
Confirm to delete this note?
+ Confirm to permanently delete the selected %d notes?
Have moved selected %1$d notes to %2$s folder
SD card busy, not available now
@@ -134,10 +125,14 @@
Cannot change the account because sync is in progress
%1$s has been set as the sync account
New note background color random
+ Trash retention days
+ Number of days to keep deleted items before automatic cleanup
+ Select retention days
Delete
Call notes
Input name
+ Deleted %s
Searching Notes
Search notes
@@ -146,9 +141,22 @@
set
cancel
- - %1$s result for \"%2$s\"
+ - %1$s result for "%2$s"
- - %1$s results for \"%2$s\"
+ - %1$s results for "%2$s"
+
+ System
+ Custom
+ Templates
+ Save as Template
+ Enter template name
+ Note is empty
+ Template name cannot be empty
+ Template saved successfully
+ Template deleted successfully
+ Are you sure you want to delete this template?
+ Note Template
+
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index fe58f8f..53cfefa 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -27,4 +27,15 @@
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
+
+
+
+
diff --git a/src/main/res/xml/searchable.xml b/src/main/res/xml/searchable.xml
index bf74f14..03b467a 100644
--- a/src/main/res/xml/searchable.xml
+++ b/src/main/res/xml/searchable.xml
@@ -21,7 +21,7 @@
android:hint="@string/search_hint"
android:searchMode="queryRewriteFromText"
- android:searchSuggestAuthority="notes"
+ android:searchSuggestAuthority="micode_notes"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSettingsDescription="@string/search_setting_description"
android:includeInGlobalSearch="true" />