尝试解决合并冲突,修复因合并冲突产生的bug #42

Merged
p7fulywfa merged 3 commits from tangbo_branch into master 4 weeks ago

@ -48,7 +48,7 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
</intent-filter >>
<intent-filter >
<action android:name="android.intent.action.INSERT_OR_EDIT" />
@ -67,13 +67,6 @@
android:resource="@xml/searchable" />
</activity>
<activity
android:name=".ui.TemplateSelectActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/NoteTheme"
android:exported="false">
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
@ -132,7 +125,7 @@
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:name=".ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
@ -140,6 +133,13 @@
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<activity
android:name=".ui.TemplateSelectActivity"
android:label="@string/menu_template"
android:theme="@style/NoteTheme"
android:launchMode="singleTop">
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >

@ -264,6 +264,13 @@ public class Notes {
* <P> 0便1 </P>
*/
public static final String LOCK_TYPE = "lock_type";
/**
* 便
* <P> : INTEGER (long) </P>
* <P> 0 </P>
*/
public static final String DELETED_DATE = "deleted_date";
}
public interface DataColumns {

@ -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");
}
}

@ -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

@ -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<Long> ids = new HashSet<Long>();
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");
}
}

@ -333,7 +333,7 @@ public class WorkingNote {
* <p>
*
*
*
*
* </p>
*
* @return truefalse
@ -359,12 +359,13 @@ public class WorkingNote {
}
return true;
} else {
// 添加删除逻辑:如果笔记内容为空且已存在于数据库,则删除该笔记
// 添加删除逻辑:如果笔记内容为空且已存在于数据库,则将其移动到回收站
if (existInDatabase() && TextUtils.isEmpty(mContent)) {
HashSet<Long> ids = new HashSet<Long>();
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;
}

@ -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());
}
}
}
}
/**
*
* <p>
@ -1408,6 +1382,55 @@ public class NoteEditActivity extends Activity implements OnClickListener,
showToast(resId, Toast.LENGTH_SHORT);
}
/**
*
* <p>
* TemplateSelectActivity便
* </p>
*/
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);
}
/**
*
* <p>
* TemplateSelectActivity
*
* </p>
* @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
* <p>

@ -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 truefalse
*/
public boolean isDeleted() {
return (mDeletedDate > 0);
}
/**
*
* @param cursor

@ -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;
/**
*
* <p>
*
* 1.
* 2.
* 3. ID
* 4.
* 5.
* 6.
* 7.
* 8.
* 9.
* 10.
* </p>
* @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;
*
* <p>
*
* 1.
* 2.
* 3. ID
* 4.
* 5.
* 6.
* 7.
* 8.
* 9.
* 10.
* </p>
* @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
}
/**
*
* <p>
*
* 1.
* 2. ID
* - batchDelete
* - startQueryDestinationFolders
* </p>
* @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;
}
*
* <p>
*
* 1.
* 2. ID
* - batchDelete
* - startQueryDestinationFolders
* - batchRestore
* </p>
* @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
* <p>
*
* 1.
* 2.
* 3.
* 2.
* 3.
* 4.
* 5.
* </p>
@ -923,16 +964,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> 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();
}
/**
*
* <p>
*
* 1. ID
* 2. ID
* 3.
* 4.
* 5.
* </p>
*/
private void batchRestore() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
HashSet<Long> 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<Long> ids = new HashSet<Long>();
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<AppWidgetAttribute> 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. IDID
* 2.
* 3.
* 4.
* 3.
* 4.
* 5.
* </p>
* @param folderId ID
@ -1026,13 +1162,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
ids.add(folderId);
HashSet<AppWidgetAttribute> 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));

@ -149,6 +149,7 @@ public class NotesListAdapter extends CursorAdapter {
*
* <p>
*
*
* </p>
* @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);
}
}

@ -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);
}

@ -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);
}
}
}

@ -57,8 +57,8 @@
<item
android:id="@+id/menu_unlock"
android:title="@string/menu_unlock" />
<item
android:id="@+id/menu_template"
android:title="@string/menu_template" />
android:id="@+id/menu_note_template"
android:title="@string/menu_note_template" />
</menu>

@ -22,6 +22,12 @@
android:title="@string/menu_move"
android:icon="@drawable/menu_move"
android:showAsAction="always|withText" />
<item
android:id="@+id/restore"
android:title="Restore"
android:icon="@drawable/menu_move"
android:showAsAction="always|withText" />
<item
android:id="@+id/delete"

@ -28,4 +28,25 @@
<item>Messaging</item>
<item>Email</item>
</string-array>
<!-- Trash retention days -->
<string-array name="trash_retention_days_entries">
<item>1 day</item>
<item>3 days</item>
<item>7 days</item>
<item>14 days</item>
<item>30 days (default)</item>
<item>60 days</item>
<item>90 days</item>
</string-array>
<string-array name="trash_retention_days_values">
<item>1</item>
<item>3</item>
<item>7</item>
<item>14</item>
<item>30</item>
<item>60</item>
<item>90</item>
</string-array>
</resources>

@ -52,21 +52,11 @@
<string name="menu_unpin">Unpin</string>
<string name="menu_lock">Lock</string>
<string name="menu_unlock">Unlock</string>
<string name="menu_template">Note Template</string>
<string name="save_as_template">Save as Template</string>
<string name="template_type_system">System Template</string>
<string name="template_type_custom">Custom Template</string>
<string name="dialog_enter_password">Enter Password</string>
<string name="hint_enter_password">Please enter password</string>
<string name="error_wrong_password">Incorrect password</string>
<string name="message_note_locked">Note locked successfully</string>
<string name="message_note_unlocked">Note unlocked successfully</string>
<string name="enter_template_name">Enter Template Name</string>
<string name="error_note_empty">Note is empty</string>
<string name="error_template_name_empty">Template name cannot be empty</string>
<string name="message_template_saved">Template saved successfully</string>
<string name="message_template_deleted">Template deleted successfully</string>
<string name="alert_message_delete_template">Are you sure you want to delete this template?</string>
<string name="menu_select_title">%d selected</string>
<string name="menu_select_none">Nothing selected, the operation is invalid</string>
<string name="menu_select_all">Select all</string>
@ -93,6 +83,7 @@
<string name="alert_title_delete">Delete selected notes</string>
<string name="alert_message_delete_notes">Confirm to delete the selected %d notes?</string>
<string name="alert_message_delete_note">Confirm to delete this note?</string>
<string name="alert_message_delete_notes_permanently">Confirm to permanently delete the selected %d notes?</string>
<string name="format_move_notes_to_folder">Have moved selected %1$d notes to %2$s folder</string>
<!-- Error information -->
<string name="error_sdcard_unmounted">SD card busy, not available now</string>
@ -134,10 +125,14 @@
<string name="preferences_toast_cannot_change_account">Cannot change the account because sync is in progress</string>
<string name="preferences_toast_success_set_accout">%1$s has been set as the sync account</string>
<string name="preferences_bg_random_appear_title">New note background color random</string>
<string name="preferences_trash_retention_days_title">Trash retention days</string>
<string name="preferences_trash_retention_days_summary">Number of days to keep deleted items before automatic cleanup</string>
<string name="preferences_trash_retention_days_dialog_title">Select retention days</string>
<string name="button_delete">Delete</string>
<string name="call_record_folder_name">Call notes</string>
<string name="hint_foler_name">Input name</string>
<string name="deleted_time_format">Deleted %s</string>
<string name="search_label">Searching Notes</string>
<string name="search_hint">Search notes</string>
@ -146,9 +141,22 @@
<string name="datetime_dialog_ok">set</string>
<string name="datetime_dialog_cancel">cancel</string>
<plurals name="search_results_title">
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for "<xliff:g id="search" example="???">%2$s</xliff:g>"</item>
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for "<xliff:g id="search" example="???">%2$s</xliff:g>"</item>
</plurals>
<!-- Template related strings -->
<string name="template_type_system">System</string>
<string name="template_type_custom">Custom</string>
<string name="menu_template">Templates</string>
<string name="save_as_template">Save as Template</string>
<string name="enter_template_name">Enter template name</string>
<string name="error_note_empty">Note is empty</string>
<string name="error_template_name_empty">Template name cannot be empty</string>
<string name="message_template_saved">Template saved successfully</string>
<string name="message_template_deleted">Template deleted successfully</string>
<string name="alert_message_delete_template">Are you sure you want to delete this template?</string>
<string name="menu_note_template">Note Template</string>
</resources>

@ -27,4 +27,15 @@
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory>
<ListPreference
android:key="pref_key_trash_retention_days"
android:title="@string/preferences_trash_retention_days_title"
android:summary="@string/preferences_trash_retention_days_summary"
android:dialogTitle="@string/preferences_trash_retention_days_dialog_title"
android:entries="@array/trash_retention_days_entries"
android:entryValues="@array/trash_retention_days_values"
android:defaultValue="30" />
</PreferenceCategory>
</PreferenceScreen>

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

Loading…
Cancel
Save