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" />