From 207cea7ccb9dfb559d4be6af76ee656836fe7e38 Mon Sep 17 00:00:00 2001 From: ranhao <2352406715@qq.com> Date: Tue, 27 Jan 2026 11:53:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=AC=AC=E4=BA=8C?= =?UTF-8?q?=E4=B8=AAissue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/notes/ui/NotesListActivity.java | 447 +++++++++++++++++++++- src/notes/ui/NotesListItem.java | 29 +- src/notes/ui/NotesPreferenceActivity.java | 259 ++++++++++++- 3 files changed, 715 insertions(+), 20 deletions(-) diff --git a/src/notes/ui/NotesListActivity.java b/src/notes/ui/NotesListActivity.java index 414a16c..1aa408b 100644 --- a/src/notes/ui/NotesListActivity.java +++ b/src/notes/ui/NotesListActivity.java @@ -23,11 +23,16 @@ import android.appwidget.AppWidgetManager; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; +import android.net.Uri; +import android.text.InputType; +import android.widget.EditText; +import android.widget.LinearLayout; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; @@ -92,6 +97,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private static final int MENU_FOLDER_DELETE = 0; // 文件夹删除菜单项 private static final int MENU_FOLDER_VIEW = 1; // 文件夹查看菜单项 private static final int MENU_FOLDER_CHANGE_NAME = 2; // 文件夹改名菜单项 + private static final int MENU_FOLDER_ENCRYPT = 3; // 文件夹加密菜单项 + private static final int MENU_FOLDER_DECRYPT = 4; // 文件夹取消加密菜单项 // 首次使用引导标记的偏好设置键 private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; @@ -138,11 +145,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt // 普通查询条件(非根文件夹) private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; - // 根文件夹查询条件(包含通话记录文件夹) + // 根文件夹查询条件(包含通话记录文件夹和回收站) private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" - + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + + 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_FOLDER + ")"; // 请求码常量 private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开笔记请求码 @@ -156,6 +164,58 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt // 首次使用时插入应用介绍笔记 setAppInfoFromRawRes(); + + // 清理一周前的回收站笔记 + cleanOldTrashNotes(); + } + + /** + * 清理一周前的回收站笔记 + */ + private void cleanOldTrashNotes() { + long oneWeekAgo = System.currentTimeMillis() - (7 * 24 * 60 * 60 * 1000); + String selection = NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLDER + " AND " + + NoteColumns.MODIFIED_DATE + "<" + oneWeekAgo; + + try { + int deletedCount = getContentResolver().delete(Notes.CONTENT_NOTE_URI, selection, null); + if (deletedCount > 0) { + Log.d(TAG, "Cleaned " + deletedCount + " old trash notes"); + } + } catch (Exception e) { + Log.e(TAG, "Error cleaning old trash notes", e); + } + } + + /** + * 恢复回收站中的笔记 + * @param itemData 要恢复的笔记数据项 + */ + private void restoreNote(final NoteItemData itemData) { + if (itemData == null || itemData.getType() != Notes.TYPE_NOTE) { + return; + } + + // 显示确认对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("恢复笔记"); + builder.setMessage("确定要恢复这条笔记吗?"); + builder.setIcon(android.R.drawable.ic_dialog_info); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 恢复笔记到根文件夹 + HashSet ids = new HashSet(); + ids.add(itemData.getId()); + if (DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_ROOT_FOLDER)) { + Toast.makeText(NotesListActivity.this, "笔记已恢复", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(NotesListActivity.this, "恢复失败", Toast.LENGTH_SHORT).show(); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); } @Override @@ -279,6 +339,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt // 加载操作模式菜单 getMenuInflater().inflate(R.menu.note_list_options, menu); menu.findItem(R.id.delete).setOnMenuItemClickListener(this); + menu.findItem(R.id.pin).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); // 根据条件设置移动菜单可见性 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER @@ -392,6 +453,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; + case R.id.pin: + // 批量置顶笔记 + batchPinNotes(); + break; case R.id.move: // 批量移动到文件夹 startQueryDestinationFolders(); @@ -551,19 +616,54 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt protected HashSet doInBackground(Void... unused) { // 获取选中笔记的小部件属性 HashSet widgets = mNotesListAdapter.getSelectedWidget(); - if (!isSyncMode()) { - // 非同步模式下直接删除笔记 - if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter - .getSelectedItemIds())) { - // 删除成功 - } else { - Log.e(TAG, "Delete notes error, should not happens"); + // 无论是否同步模式,都将笔记移动到回收站 + if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter + .getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + return widgets; + } + + @Override + protected void onPostExecute(HashSet widgets) { + // 更新相关小部件 + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } } - } else { - // 同步模式下将笔记移动到回收站 - if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter - .getSelectedItemIds(), Notes.ID_TRASH_FOLDER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + mModeCallBack.finishActionMode(); // 结束多选模式 + } + }.execute(); + } + + /** + * 批量置顶笔记 + */ + private void batchPinNotes() { + new AsyncTask>() { + @Override + protected HashSet doInBackground(Void... unused) { + // 获取选中笔记的小部件属性 + HashSet widgets = mNotesListAdapter.getSelectedWidget(); + HashSet selectedIds = mNotesListAdapter.getSelectedItemIds(); + + // 批量更新笔记的alerted_date字段为一个很大的值,表示置顶 + for (Long noteId : selectedIds) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.ALERTED_DATE, 9999999999999L); // 很大的值,表示置顶 + values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + + try { + getContentResolver().update( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + values, null, null); + } catch (Exception e) { + Log.e(TAG, "Error pinning note: " + noteId, e); } } return widgets; @@ -580,6 +680,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } + Toast.makeText(NotesListActivity.this, "已置顶" + mNotesListAdapter.getSelectedCount() + "条笔记", Toast.LENGTH_SHORT).show(); mModeCallBack.finishActionMode(); // 结束多选模式 } }.execute(); @@ -616,6 +717,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } + + /** * 打开笔记进行编辑 @@ -631,18 +734,30 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt * 打开文件夹浏览 */ private void openFolder(NoteItemData data) { + // 检查文件夹是否加密 + if (data.getType() == Notes.TYPE_FOLDER && isFolderEncrypted(data.getId())) { + // 加密文件夹需要输入密码才能进入 + showFolderPasswordDialog(data.getId()); + return; + } + mCurrentFolderId = data.getId(); startAsyncNotesListQuery(); // 查询文件夹内容 // 根据文件夹类型设置状态 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mState = ListEditState.CALL_RECORD_FOLDER; mAddNewNote.setVisibility(View.GONE); // 通话记录文件夹不能新建笔记 + } else if (data.getId() == Notes.ID_TRASH_FOLDER) { + mState = ListEditState.SUB_FOLDER; + mAddNewNote.setVisibility(View.GONE); // 回收站不能新建笔记 } else { mState = ListEditState.SUB_FOLDER; } // 设置标题栏 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mTitleBar.setText(R.string.call_record_folder_name); + } else if (data.getId() == Notes.ID_TRASH_FOLDER) { + mTitleBar.setText("回收站"); } else { mTitleBar.setText(data.getSnippet()); } @@ -829,6 +944,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + // 添加加密选项 + if (isFolderEncrypted(mFocusNoteDataItem.getId())) { + menu.add(0, MENU_FOLDER_DECRYPT, 0, "取消加密"); + } else { + menu.add(0, MENU_FOLDER_ENCRYPT, 0, "加密文件夹"); + } } } }; @@ -870,6 +991,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt case MENU_FOLDER_CHANGE_NAME: showCreateOrModifyFolderDialog(false); // 显示修改文件夹名对话框 break; + case MENU_FOLDER_ENCRYPT: + // 加密文件夹 + showEncryptFolderDialog(mFocusNoteDataItem.getId()); + break; + case MENU_FOLDER_DECRYPT: + // 取消加密文件夹 + decryptFolder(mFocusNoteDataItem.getId()); + break; default: break; } @@ -1029,11 +1158,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } break; case SUB_FOLDER: + // 检查是否是回收站中的笔记 + if (item.getType() == Notes.TYPE_NOTE && mCurrentFolderId == Notes.ID_TRASH_FOLDER) { + // 回收站中的笔记点击时显示恢复选项 + restoreNote(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); // 打开笔记 + } else { + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; case CALL_RECORD_FOLDER: if (item.getType() == Notes.TYPE_NOTE) { openNode(item); // 打开笔记 } else { - Log.e(TAG, "Wrong note type in SUB_FOLDER"); + Log.e(TAG, "Wrong note type in CALL_RECORD_FOLDER"); } break; default: @@ -1085,4 +1224,280 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return false; } + + /** + * 检查文件夹是否已加密 + * @param folderId 文件夹ID + * @return 是否已加密 + */ + private boolean isFolderEncrypted(long folderId) { + SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE); + String password = preferences.getString("folder_" + folderId + "_password", ""); + return !password.isEmpty(); + } + + /** + * 显示加密文件夹的对话框 + * @param folderId 文件夹ID + */ + private void showEncryptFolderDialog(final long folderId) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("加密文件夹"); + builder.setIcon(android.R.drawable.ic_lock_idle_lock); + + // 创建密码输入框 + final EditText passwordInput = new EditText(this); + passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + passwordInput.setHint("请设置密码"); + builder.setView(passwordInput); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String password = passwordInput.getText().toString(); + if (!password.isEmpty()) { + // 保存密码 + SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("folder_" + folderId + "_password", password); + editor.apply(); + + // 显示提示 + Toast.makeText(NotesListActivity.this, "文件夹已加密", Toast.LENGTH_SHORT).show(); + } else { + // 密码为空,显示提示 + Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 添加忘记密码按钮 + builder.setNeutralButton("忘记密码", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 显示通过密保问题重置密码的对话框 + showResetPasswordWithSecurityQuestionsDialog(folderId); + } + }); + + // 显示对话框 + builder.show(); + } + + /** + * 取消加密文件夹 + * @param folderId 文件夹ID + */ + private void decryptFolder(final long folderId) { + // 显示密码输入对话框,验证身份 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("取消加密"); + builder.setIcon(android.R.drawable.ic_lock_idle_lock); + + // 创建密码输入框 + final EditText passwordInput = new EditText(this); + passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + passwordInput.setHint("请输入密码"); + builder.setView(passwordInput); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String password = passwordInput.getText().toString(); + if (checkFolderPassword(folderId, password)) { + // 密码正确,取消加密 + SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove("folder_" + folderId + "_password"); + editor.apply(); + + // 显示提示 + Toast.makeText(NotesListActivity.this, "文件夹已取消加密", Toast.LENGTH_SHORT).show(); + } else { + // 密码错误,显示提示 + Toast.makeText(NotesListActivity.this, "密码错误", Toast.LENGTH_SHORT).show(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 添加忘记密码按钮 + builder.setNeutralButton("忘记密码", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 显示通过密保问题重置密码的对话框 + showResetPasswordWithSecurityQuestionsDialog(folderId); + } + }); + + // 显示对话框 + builder.show(); + } + + /** + * 检查文件夹密码 + * @param folderId 文件夹ID + * @param password 输入的密码 + * @return 密码是否正确 + */ + private boolean checkFolderPassword(long folderId, String password) { + SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE); + String savedPassword = preferences.getString("folder_" + folderId + "_password", ""); + return savedPassword.equals(password); + } + + /** + * 进入加密文件夹 + * @param folderId 文件夹ID + */ + private void enterEncryptedFolder(long folderId) { + mCurrentFolderId = folderId; + startAsyncNotesListQuery(); // 查询文件夹内容 + mState = ListEditState.SUB_FOLDER; + } + + /** + * 显示文件夹密码输入对话框 + * @param folderId 文件夹ID + */ + private void showFolderPasswordDialog(final long folderId) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("输入密码"); + builder.setIcon(android.R.drawable.ic_lock_idle_lock); + + // 创建密码输入框 + final EditText passwordInput = new EditText(this); + passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + passwordInput.setHint("请输入文件夹密码"); + builder.setView(passwordInput); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String password = passwordInput.getText().toString(); + if (checkFolderPassword(folderId, password)) { + // 密码正确,进入文件夹 + enterEncryptedFolder(folderId); + } else { + // 密码错误,显示提示 + Toast.makeText(NotesListActivity.this, "密码错误,请重新输入", Toast.LENGTH_SHORT).show(); + // 重新显示密码输入对话框 + showFolderPasswordDialog(folderId); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 显示对话框 + builder.show(); + } + + /** + * 通过密保问题重置文件夹密码 + * @param folderId 文件夹ID + */ + private void showResetPasswordWithSecurityQuestionsDialog(final long folderId) { + // 检查是否设置了密保问题 + if (!NotesPreferenceActivity.hasSecurityQuestionsSet(this)) { + Toast.makeText(this, "请先在设置中设置密保问题", Toast.LENGTH_SHORT).show(); + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("忘记密码"); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage("请回答密保问题以重置密码"); + + // 创建布局 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(40, 20, 40, 20); + + // 创建姓名输入框 + final EditText nameInput = new EditText(this); + nameInput.setHint("请输入姓名"); + layout.addView(nameInput); + + // 创建生日输入框 + final EditText birthdayInput = new EditText(this); + birthdayInput.setHint("请输入生日 (YYYY-MM-DD)"); + layout.addView(birthdayInput); + + builder.setView(layout); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = nameInput.getText().toString(); + String birthday = birthdayInput.getText().toString(); + if (NotesPreferenceActivity.verifySecurityQuestions(NotesListActivity.this, name, birthday)) { + // 验证成功,显示设置新密码的对话框 + showSetNewFolderPasswordDialog(folderId); + } else { + // 验证失败,显示提示 + Toast.makeText(NotesListActivity.this, "密保问题回答错误", Toast.LENGTH_SHORT).show(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 显示对话框 + builder.show(); + } + + /** + * 显示设置新文件夹密码的对话框 + * @param folderId 文件夹ID + */ + private void showSetNewFolderPasswordDialog(final long folderId) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("设置新密码"); + builder.setIcon(android.R.drawable.ic_dialog_info); + + // 创建密码输入框 + final EditText passwordInput = new EditText(this); + passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + passwordInput.setHint("请输入新密码"); + builder.setView(passwordInput); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String newPassword = passwordInput.getText().toString(); + if (!newPassword.isEmpty()) { + // 保存新密码 + SharedPreferences preferences = getSharedPreferences("notes_prefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("folder_" + folderId + "_password", newPassword); + editor.apply(); + + // 显示提示 + Toast.makeText(NotesListActivity.this, "密码重置成功", Toast.LENGTH_SHORT).show(); + } else { + // 密码为空,显示提示 + Toast.makeText(NotesListActivity.this, "密码不能为空", Toast.LENGTH_SHORT).show(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 显示对话框 + builder.show(); + } } diff --git a/src/notes/ui/NotesListItem.java b/src/notes/ui/NotesListItem.java index 83e8f00..d049ed1 100644 --- a/src/notes/ui/NotesListItem.java +++ b/src/notes/ui/NotesListItem.java @@ -84,6 +84,16 @@ 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_FOLDER) { + // 回收站文件夹的特殊显示 + 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(android.R.drawable.ic_delete); } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { // 通话记录笔记的显示 mCallName.setVisibility(View.VISIBLE); @@ -121,8 +131,13 @@ public class NotesListItem extends LinearLayout { } } - // 显示相对时间(如"2小时前") - mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 显示相对时间(如"2小时前")- 回收站中的笔记和回收站文件夹本身都不显示时间 + if (data.getParentId() != Notes.ID_TRASH_FOLDER && data.getId() != Notes.ID_TRASH_FOLDER) { + mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + mTime.setVisibility(View.VISIBLE); + } else { + mTime.setVisibility(View.GONE); + } // 根据数据类型和位置设置背景 setBackground(data); @@ -134,8 +149,9 @@ public class NotesListItem extends LinearLayout { */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); // 获取背景颜色ID + if (data.getType() == Notes.TYPE_NOTE) { - // 笔记类型的背景设置 + // 普通笔记类型的背景设置 if (data.isSingle() || data.isOneFollowingFolder()) { // 单一条目或紧跟在文件夹后的单个笔记:使用单个笔记背景 setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); @@ -153,6 +169,13 @@ public class NotesListItem extends LinearLayout { // 文件夹类型:使用文件夹背景 setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } + + // 回收站中的笔记使用半透明效果,与普通笔记区分 + if (data.getType() == Notes.TYPE_NOTE && data.getParentId() == Notes.ID_TRASH_FOLDER) { + setAlpha(0.6f); // 设置透明度为60% + } else { + setAlpha(1.0f); // 普通笔记使用完全不透明 + } } /** diff --git a/src/notes/ui/NotesPreferenceActivity.java b/src/notes/ui/NotesPreferenceActivity.java index 8b229d0..82a0547 100644 --- a/src/notes/ui/NotesPreferenceActivity.java +++ b/src/notes/ui/NotesPreferenceActivity.java @@ -39,6 +39,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; @@ -79,13 +81,21 @@ public class NotesPreferenceActivity extends PreferenceActivity { /* 使用应用图标作为导航按钮 */ getActionBar().setDisplayHomeAsUpEnabled(true); + // 检查并设置密保问题 + if (!hasSecurityQuestionsSet()) { + showSetSecurityQuestionsDialog(); + } else { + // 验证密保问题才能进入设置 + showVerifySecurityQuestionsDialog(); + } + // 从XML文件加载偏好设置 addPreferencesFromResource(R.xml.preferences); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mReceiver = new GTaskReceiver(); // 创建广播接收器 IntentFilter filter = new IntentFilter(); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); // 注册同步服务广播 - registerReceiver(mReceiver, filter); // 注册广播接收器 + registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED); // 注册广播接收器 mOriAccounts = null; // 添加设置界面的头部视图 @@ -129,6 +139,183 @@ public class NotesPreferenceActivity extends PreferenceActivity { } super.onDestroy(); } + + /** + * 检查是否设置了密保问题 + * @return 是否已设置 + */ + private boolean hasSecurityQuestionsSet() { + SharedPreferences preferences = getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE); + String name = preferences.getString("security_question_name", ""); + String birthday = preferences.getString("security_question_birthday", ""); + return !name.isEmpty() && !birthday.isEmpty(); + } + + /** + * 显示设置密保问题的对话框 + */ + private void showSetSecurityQuestionsDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("设置密保问题"); + builder.setIcon(android.R.drawable.ic_dialog_info); + builder.setMessage("请设置您的个人信息作为密保,用于重置密码时验证身份"); + + // 创建布局 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(40, 20, 40, 20); + + // 创建姓名输入框 + final EditText nameInput = new EditText(this); + nameInput.setHint("请输入姓名"); + layout.addView(nameInput); + + // 创建生日输入框 + final EditText birthdayInput = new EditText(this); + birthdayInput.setHint("请输入生日 (YYYY-MM-DD)"); + layout.addView(birthdayInput); + + builder.setView(layout); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = nameInput.getText().toString(); + String birthday = birthdayInput.getText().toString(); + if (!name.isEmpty() && !birthday.isEmpty()) { + // 保存密保信息 + SharedPreferences preferences = getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("security_question_name", name); + editor.putString("security_question_birthday", birthday); + editor.apply(); + + // 显示提示 + Toast.makeText(NotesPreferenceActivity.this, "密保设置成功", Toast.LENGTH_SHORT).show(); + } else { + // 信息为空,显示提示 + Toast.makeText(NotesPreferenceActivity.this, "请完整填写个人信息", Toast.LENGTH_SHORT).show(); + // 重新显示对话框 + showSetSecurityQuestionsDialog(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 如果用户取消,退出设置界面 + finish(); + } + }); + + // 设置对话框不可取消 + builder.setCancelable(false); + + // 显示对话框 + builder.show(); + } + + /** + * 显示验证密保问题的对话框 + */ + private void showVerifySecurityQuestionsDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("验证身份"); + builder.setIcon(android.R.drawable.ic_lock_idle_lock); + builder.setMessage("请回答您的密保问题以验证身份"); + + // 创建布局 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(40, 20, 40, 20); + + // 创建姓名输入框 + final EditText nameInput = new EditText(this); + nameInput.setHint("请输入姓名"); + layout.addView(nameInput); + + // 创建生日输入框 + final EditText birthdayInput = new EditText(this); + birthdayInput.setHint("请输入生日 (YYYY-MM-DD)"); + layout.addView(birthdayInput); + + builder.setView(layout); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = nameInput.getText().toString(); + String birthday = birthdayInput.getText().toString(); + if (verifySecurityQuestions(name, birthday)) { + // 验证成功,允许进入设置 + Toast.makeText(NotesPreferenceActivity.this, "验证成功", Toast.LENGTH_SHORT).show(); + } else { + // 验证失败,显示提示 + Toast.makeText(NotesPreferenceActivity.this, "验证失败,请重新输入", Toast.LENGTH_SHORT).show(); + // 重新显示验证对话框 + showVerifySecurityQuestionsDialog(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // 如果用户取消,退出设置界面 + finish(); + } + }); + + // 设置对话框不可取消 + builder.setCancelable(false); + + // 显示对话框 + builder.show(); + } + + /** + * 验证密保问题 + * @param name 姓名 + * @param birthday 生日 + * @return 验证是否成功 + */ + private boolean verifySecurityQuestions(String name, String birthday) { + SharedPreferences preferences = getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE); + String savedName = preferences.getString("security_question_name", ""); + String savedBirthday = preferences.getString("security_question_birthday", ""); + return savedName.equals(name) && savedBirthday.equals(birthday); + } + + /** + * 验证密保问题(静态方法,供其他类调用) + * @param context 上下文 + * @param name 姓名 + * @param birthday 生日 + * @return 验证是否成功 + */ + public static boolean verifySecurityQuestions(Context context, String name, String birthday) { + SharedPreferences preferences = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + String savedName = preferences.getString("security_question_name", ""); + String savedBirthday = preferences.getString("security_question_birthday", ""); + return savedName.equals(name) && savedBirthday.equals(birthday); + } + + /** + * 检查是否设置了密保问题(静态方法,供其他类调用) + * @param context 上下文 + * @return 是否已设置 + */ + public static boolean hasSecurityQuestionsSet(Context context) { + SharedPreferences preferences = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + String name = preferences.getString("security_question_name", ""); + String birthday = preferences.getString("security_question_birthday", ""); + return !name.isEmpty() && !birthday.isEmpty(); + } /** * 加载账户偏好设置项 @@ -161,7 +348,77 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); + // 添加修改密保问题的选项 + Preference securityPref = new Preference(this); + securityPref.setTitle("修改密保问题"); + securityPref.setSummary("修改用于重置密码的个人信息"); + securityPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + // 显示修改密保问题的对话框 + showChangeSecurityQuestionsDialog(); + return true; + } + }); + mAccountCategory.addPreference(accountPref); // 添加到设置类别 + mAccountCategory.addPreference(securityPref); // 添加密保设置选项 + } + + /** + * 显示修改密保问题的对话框 + */ + private void showChangeSecurityQuestionsDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("修改密保问题"); + builder.setIcon(android.R.drawable.ic_dialog_info); + builder.setMessage("请重新设置您的个人信息作为密保"); + + // 创建布局 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(40, 20, 40, 20); + + // 创建姓名输入框 + final EditText nameInput = new EditText(this); + nameInput.setHint("请输入姓名"); + layout.addView(nameInput); + + // 创建生日输入框 + final EditText birthdayInput = new EditText(this); + birthdayInput.setHint("请输入生日 (YYYY-MM-DD)"); + layout.addView(birthdayInput); + + builder.setView(layout); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = nameInput.getText().toString(); + String birthday = birthdayInput.getText().toString(); + if (!name.isEmpty() && !birthday.isEmpty()) { + // 保存新的密保信息 + SharedPreferences preferences = getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("security_question_name", name); + editor.putString("security_question_birthday", birthday); + editor.apply(); + + // 显示提示 + Toast.makeText(NotesPreferenceActivity.this, "密保修改成功", Toast.LENGTH_SHORT).show(); + } else { + // 信息为空,显示提示 + Toast.makeText(NotesPreferenceActivity.this, "请完整填写个人信息", Toast.LENGTH_SHORT).show(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 显示对话框 + builder.show(); } /** From 9c06b0dac5198881518bf0f7e978ea3782207f19 Mon Sep 17 00:00:00 2001 From: ranhao <2352406715@qq.com> Date: Tue, 27 Jan 2026 16:00:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/notes/data/NotesProvider.java | 242 +++++++++++++++++++++- src/notes/tool/ElderModeUtils.java | 109 ++++++++++ src/notes/ui/NoteEditActivity.java | 188 ++++++++++++++++- src/notes/ui/NoteItemData.java | 5 + src/notes/ui/NotesPreferenceActivity.java | 61 +++++- 5 files changed, 590 insertions(+), 15 deletions(-) create mode 100644 src/notes/tool/ElderModeUtils.java diff --git a/src/notes/data/NotesProvider.java b/src/notes/data/NotesProvider.java index 8810f7a..77446df 100644 --- a/src/notes/data/NotesProvider.java +++ b/src/notes/data/NotesProvider.java @@ -26,6 +26,8 @@ import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; +import android.os.Bundle; +import android.text.Html; import android.text.TextUtils; import android.util.Log; @@ -188,8 +190,12 @@ public class NotesProvider extends ContentProvider { try { searchString = String.format("%%%s%%", searchString); - c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + Cursor rawCursor = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); + // 包装Cursor以处理HTML内容 + if (rawCursor != null) { + c = new HtmlCursorWrapper(rawCursor); + } } catch (IllegalStateException ex) { Log.e(TAG, "got exception: " + ex.toString()); } @@ -203,6 +209,240 @@ public class NotesProvider extends ContentProvider { return c; } + /** + * Cursor包装类,用于处理搜索结果中的HTML内容 + * 将HTML格式转换为普通文本,解决富文本编辑后搜索结果显示乱码的问题 + */ + private class HtmlCursorWrapper implements Cursor { + private Cursor mCursor; + + public HtmlCursorWrapper(Cursor cursor) { + mCursor = cursor; + } + + @Override + public int getCount() { + return mCursor.getCount(); + } + + @Override + public int getPosition() { + return mCursor.getPosition(); + } + + @Override + public boolean move(int offset) { + return mCursor.move(offset); + } + + @Override + public boolean moveToPosition(int position) { + return mCursor.moveToPosition(position); + } + + @Override + public boolean moveToFirst() { + return mCursor.moveToFirst(); + } + + @Override + public boolean moveToLast() { + return mCursor.moveToLast(); + } + + @Override + public boolean moveToNext() { + return mCursor.moveToNext(); + } + + @Override + public boolean moveToPrevious() { + return mCursor.moveToPrevious(); + } + + @Override + public boolean isFirst() { + return mCursor.isFirst(); + } + + @Override + public boolean isLast() { + return mCursor.isLast(); + } + + @Override + public boolean isBeforeFirst() { + return mCursor.isBeforeFirst(); + } + + @Override + public boolean isAfterLast() { + return mCursor.isAfterLast(); + } + + @Override + public int getColumnIndex(String columnName) { + return mCursor.getColumnIndex(columnName); + } + + @Override + public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { + return mCursor.getColumnIndexOrThrow(columnName); + } + + @Override + public String getColumnName(int columnIndex) { + return mCursor.getColumnName(columnIndex); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public int getColumnCount() { + return mCursor.getColumnCount(); + } + + @Override + public byte[] getBlob(int columnIndex) { + return mCursor.getBlob(columnIndex); + } + + @Override + public String getString(int columnIndex) { + String value = mCursor.getString(columnIndex); + // 处理搜索结果中的HTML内容 + if (value != null && ( + columnIndex == mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1) || + columnIndex == mCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2)) + ) { + // 将HTML转换为普通文本 + return Html.fromHtml(value).toString(); + } + return value; + } + + @Override + public short getShort(int columnIndex) { + return mCursor.getShort(columnIndex); + } + + @Override + public int getInt(int columnIndex) { + return mCursor.getInt(columnIndex); + } + + @Override + public long getLong(int columnIndex) { + return mCursor.getLong(columnIndex); + } + + @Override + public float getFloat(int columnIndex) { + return mCursor.getFloat(columnIndex); + } + + @Override + public double getDouble(int columnIndex) { + return mCursor.getDouble(columnIndex); + } + + @Override + public int getType(int columnIndex) { + return mCursor.getType(columnIndex); + } + + @Override + public boolean isNull(int columnIndex) { + return mCursor.isNull(columnIndex); + } + + @Override + public void deactivate() { + mCursor.deactivate(); + } + + @Override + public boolean requery() { + return mCursor.requery(); + } + + @Override + public void close() { + mCursor.close(); + } + + @Override + public boolean isClosed() { + return mCursor.isClosed(); + } + + @Override + public void registerContentObserver(android.database.ContentObserver observer) { + mCursor.registerContentObserver(observer); + } + + @Override + public void unregisterContentObserver(android.database.ContentObserver observer) { + mCursor.unregisterContentObserver(observer); + } + + @Override + public void registerDataSetObserver(android.database.DataSetObserver observer) { + mCursor.registerDataSetObserver(observer); + } + + @Override + public void unregisterDataSetObserver(android.database.DataSetObserver observer) { + mCursor.unregisterDataSetObserver(observer); + } + + @Override + public void setNotificationUri(android.content.ContentResolver cr, Uri uri) { + mCursor.setNotificationUri(cr, uri); + } + + @Override + public Uri getNotificationUri() { + return mCursor.getNotificationUri(); + } + + @Override + public boolean getWantsAllOnMoveCalls() { + return mCursor.getWantsAllOnMoveCalls(); + } + + @Override + public void setExtras(Bundle extras) { + mCursor.setExtras(extras); + } + + @Override + public Bundle getExtras() { + return mCursor.getExtras(); + } + + @Override + public Bundle respond(Bundle extras) { + return mCursor.respond(extras); + } + + @Override + public void copyStringToBuffer(int columnIndex, android.database.CharArrayBuffer buffer) { + String value = getString(columnIndex); + if (value != null) { + char[] data = value.toCharArray(); + if (buffer.data == null || buffer.data.length < data.length) { + buffer.data = new char[data.length]; + } + System.arraycopy(data, 0, buffer.data, 0, data.length); + buffer.sizeCopied = data.length; + } + } + } + /** * 插入数据 * @param uri 插入的URI diff --git a/src/notes/tool/ElderModeUtils.java b/src/notes/tool/ElderModeUtils.java new file mode 100644 index 0000000..11f4132 --- /dev/null +++ b/src/notes/tool/ElderModeUtils.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2026, 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.tool; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesPreferenceActivity; + +/** + * 老年人模式工具类,用于管理老年人模式的设置和应用 + */ +public class ElderModeUtils { + /** + * 老年人模式的字体缩放比例 + */ + private static final float ELDER_MODE_FONT_SCALE = 1.1f; + + /** + * 检查是否启用了老年人模式 + * + * @param context 上下文 + * @return 是否启用了老年人模式 + */ + public static boolean isElderModeEnabled(Context context) { + // 使用默认的SharedPreferences,因为CheckBoxPreference会自动保存到这里 + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + return preferences.getBoolean(NotesPreferenceActivity.PREFERENCE_ELDER_MODE_KEY, false); + } + + /** + * 应用老年人模式到指定的View + * + * @param context 上下文 + * @param view 要应用老年人模式的View + */ + public static void applyElderMode(Context context, View view) { + if (!isElderModeEnabled(context)) { + return; + } + + // 只对用户输入的内容应用字体增大,系统信息保持不变 + if (view instanceof TextView) { + TextView textView = (TextView) view; + int id = textView.getId(); + + // 只增大用户输入内容的字体 + if (id == R.id.tv_title || // 笔记列表中的标题(用户输入内容) + id == R.id.note_edit_view) { // 编辑界面中的内容(用户输入) + textView.setTextSize(textView.getTextSize() * ELDER_MODE_FONT_SCALE); + } + } else if (view instanceof ViewGroup) { + // 递归处理ViewGroup中的所有子View + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + applyElderMode(context, viewGroup.getChildAt(i)); + } + } + } + + /** + * 清除老年人模式的应用 + * + * @param context 上下文 + * @param view 要清除老年人模式的View + */ + public static void clearElderMode(Context context, View view) { + if (!isElderModeEnabled(context)) { + return; + } + + // 只对用户输入的内容应用字体恢复,系统信息保持不变 + if (view instanceof TextView) { + TextView textView = (TextView) view; + int id = textView.getId(); + + // 只恢复用户输入内容的字体 + if (id == R.id.tv_title || // 笔记列表中的标题(用户输入内容) + id == R.id.note_edit_view) { // 编辑界面中的内容(用户输入) + textView.setTextSize(textView.getTextSize() / ELDER_MODE_FONT_SCALE); + } + } else if (view instanceof ViewGroup) { + // 递归处理ViewGroup中的所有子View + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + clearElderMode(context, viewGroup.getChildAt(i)); + } + } + } +} \ No newline at end of file diff --git a/src/notes/ui/NoteEditActivity.java b/src/notes/ui/NoteEditActivity.java index afd6110..7507430 100644 --- a/src/notes/ui/NoteEditActivity.java +++ b/src/notes/ui/NoteEditActivity.java @@ -30,11 +30,16 @@ import android.content.SharedPreferences; import android.graphics.Paint; import android.os.Bundle; import android.preference.PreferenceManager; +import android.text.Html; import android.text.Spannable; import android.text.SpannableString; +import android.text.Spanned; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -298,7 +303,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { switchToListMode(mWorkingNote.getContent()); // 切换到列表模式 } else { - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + // 加载富文本内容 + String content = mWorkingNote.getContent(); + CharSequence htmlContent = Html.fromHtml(content != null ? content : ""); + mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery)); mNoteEditor.setSelection(mNoteEditor.getText().length()); // 光标移到最后 } @@ -643,6 +651,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, case R.id.menu_delete_remind: mWorkingNote.setAlertDate(0, false); // 删除提醒 break; + case R.id.menu_bold: + applyRichTextStyle(android.graphics.Typeface.BOLD); + break; + case R.id.menu_italic: + applyRichTextStyle(android.graphics.Typeface.ITALIC); + break; + case R.id.menu_underline: + applyUnderlineStyle(); + break; + case R.id.menu_strikethrough: + applyStrikethroughStyle(); + break; default: break; } @@ -846,9 +866,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** * 获取高亮搜索结果的Spannable文本 */ - private Spannable getHighlightQueryResult(String fullText, String userQuery) { + private Spannable getHighlightQueryResult(CharSequence fullText, String userQuery) { SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); - if (!TextUtils.isEmpty(userQuery)) { + if (!TextUtils.isEmpty(userQuery) && !TextUtils.isEmpty(fullText)) { mPattern = Pattern.compile(userQuery); Matcher m = mPattern.matcher(fullText); int start = 0; @@ -900,7 +920,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, // 设置编辑文本变化监听器 edit.setOnTextViewChangeListener(this); edit.setIndex(index); // 设置索引 - edit.setText(getHighlightQueryResult(item, mUserQuery)); // 设置文本(支持高亮) + // 列表模式下不使用富文本,直接设置文本 + edit.setText(item); // 设置文本 return view; } @@ -954,17 +975,22 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (!TextUtils.isEmpty(edit.getText())) { // 根据复选框状态添加不同的标记 if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { - sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + sb.append(TAG_CHECKED).append(" " ).append(edit.getText()).append("\n" ); hasChecked = true; } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); + sb.append(TAG_UNCHECKED).append(" " ).append(edit.getText()).append("\n" ); } } } mWorkingNote.setWorkingText(sb.toString()); } else { - // 普通模式:直接获取编辑框文本 - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + // 普通模式:将富文本转换为HTML格式保存 + if (mNoteEditor.getText() instanceof Spanned) { + String htmlContent = Html.toHtml((Spanned) mNoteEditor.getText()); + mWorkingNote.setWorkingText(htmlContent); + } else { + mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + } } return hasChecked; } @@ -986,6 +1012,152 @@ public class NoteEditActivity extends Activity implements OnClickListener, return saved; } + /** + * 应用富文本样式(粗体或斜体) + */ + private void applyRichTextStyle(int style) { + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 列表模式下,获取当前焦点的编辑框 + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + NoteEditText editText = (NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text); + if (editText.hasFocus()) { + applyStyleToEditText(editText, style); + break; + } + } + } else { + // 普通模式下,直接应用到主编辑框 + applyStyleToEditText(mNoteEditor, style); + } + } + + /** + * 应用下划线样式 + */ + private void applyUnderlineStyle() { + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 列表模式下,获取当前焦点的编辑框 + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + NoteEditText editText = (NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text); + if (editText.hasFocus()) { + applyUnderlineToEditText(editText); + break; + } + } + } else { + // 普通模式下,直接应用到主编辑框 + applyUnderlineToEditText(mNoteEditor); + } + } + + /** + * 应用删除线样式 + */ + private void applyStrikethroughStyle() { + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 列表模式下,获取当前焦点的编辑框 + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + NoteEditText editText = (NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text); + if (editText.hasFocus()) { + applyStrikethroughToEditText(editText); + break; + } + } + } else { + // 普通模式下,直接应用到主编辑框 + applyStrikethroughToEditText(mNoteEditor); + } + } + + /** + * 向EditText应用样式(粗体或斜体) + */ + private void applyStyleToEditText(EditText editText, int style) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + + if (start != end) { + SpannableString spannable = new SpannableString(editText.getText()); + StyleSpan[] existingSpans = spannable.getSpans(start, end, StyleSpan.class); + + // 检查是否已经应用了相同的样式 + boolean hasStyle = false; + for (StyleSpan span : existingSpans) { + if (span.getStyle() == style) { + hasStyle = true; + break; + } + } + + if (hasStyle) { + // 如果已经应用了相同的样式,则移除 + for (StyleSpan span : existingSpans) { + if (span.getStyle() == style) { + spannable.removeSpan(span); + } + } + } else { + // 如果没有应用相同的样式,则添加 + spannable.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + + editText.setText(spannable); + editText.setSelection(end); + } + } + + /** + * 向EditText应用下划线样式 + */ + private void applyUnderlineToEditText(EditText editText) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + + if (start != end) { + SpannableString spannable = new SpannableString(editText.getText()); + UnderlineSpan[] existingSpans = spannable.getSpans(start, end, UnderlineSpan.class); + + if (existingSpans.length > 0) { + // 如果已经应用了下划线,则移除 + for (UnderlineSpan span : existingSpans) { + spannable.removeSpan(span); + } + } else { + // 如果没有应用下划线,则添加 + spannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + + editText.setText(spannable); + editText.setSelection(end); + } + } + + /** + * 向EditText应用删除线样式 + */ + private void applyStrikethroughToEditText(EditText editText) { + int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + + if (start != end) { + SpannableString spannable = new SpannableString(editText.getText()); + StrikethroughSpan[] existingSpans = spannable.getSpans(start, end, StrikethroughSpan.class); + + if (existingSpans.length > 0) { + // 如果已经应用了删除线,则移除 + for (StrikethroughSpan span : existingSpans) { + spannable.removeSpan(span); + } + } else { + // 如果没有应用删除线,则添加 + spannable.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + + editText.setText(spannable); + editText.setSelection(end); + } + } + /** * 发送到桌面(创建快捷方式) */ diff --git a/src/notes/ui/NoteItemData.java b/src/notes/ui/NoteItemData.java index 41a2b4b..25173b8 100644 --- a/src/notes/ui/NoteItemData.java +++ b/src/notes/ui/NoteItemData.java @@ -18,6 +18,7 @@ package net.micode.notes.ui; import android.content.Context; import android.database.Cursor; +import android.text.Html; import android.text.TextUtils; import net.micode.notes.data.Contact; @@ -102,6 +103,10 @@ public class NoteItemData { mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); mParentId = cursor.getLong(PARENT_ID_COLUMN); mSnippet = cursor.getString(SNIPPET_COLUMN); + // 将HTML格式转换为普通文本,解决富文本编辑后显示乱码的问题 + if (!TextUtils.isEmpty(mSnippet)) { + mSnippet = Html.fromHtml(mSnippet).toString(); + } // 移除摘要中的复选框标记(已勾选和未勾选) mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( NoteEditActivity.TAG_UNCHECKED, ""); diff --git a/src/notes/ui/NotesPreferenceActivity.java b/src/notes/ui/NotesPreferenceActivity.java index 82a0547..c1d0294 100644 --- a/src/notes/ui/NotesPreferenceActivity.java +++ b/src/notes/ui/NotesPreferenceActivity.java @@ -81,12 +81,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { /* 使用应用图标作为导航按钮 */ getActionBar().setDisplayHomeAsUpEnabled(true); - // 检查并设置密保问题 + // 检查并设置密保问题(只在首次设置时显示) if (!hasSecurityQuestionsSet()) { showSetSecurityQuestionsDialog(); - } else { - // 验证密保问题才能进入设置 - showVerifySecurityQuestionsDialog(); } // 从XML文件加载偏好设置 @@ -355,8 +352,8 @@ public class NotesPreferenceActivity extends PreferenceActivity { securityPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - // 显示修改密保问题的对话框 - showChangeSecurityQuestionsDialog(); + // 验证当前密保才能修改 + showVerifySecurityQuestionsForModificationDialog(); return true; } }); @@ -420,6 +417,58 @@ public class NotesPreferenceActivity extends PreferenceActivity { // 显示对话框 builder.show(); } + + /** + * 显示用于修改密保的验证对话框 + */ + private void showVerifySecurityQuestionsForModificationDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("验证身份"); + builder.setIcon(android.R.drawable.ic_lock_idle_lock); + builder.setMessage("请回答您的密保问题以验证身份,验证成功后才能修改密保"); + + // 创建布局 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(40, 20, 40, 20); + + // 创建姓名输入框 + final EditText nameInput = new EditText(this); + nameInput.setHint("请输入姓名"); + layout.addView(nameInput); + + // 创建生日输入框 + final EditText birthdayInput = new EditText(this); + birthdayInput.setHint("请输入生日 (YYYY-MM-DD)"); + layout.addView(birthdayInput); + + builder.setView(layout); + + // 设置确定按钮 + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = nameInput.getText().toString(); + String birthday = birthdayInput.getText().toString(); + if (verifySecurityQuestions(name, birthday)) { + // 验证成功,显示修改密保对话框 + Toast.makeText(NotesPreferenceActivity.this, "验证成功", Toast.LENGTH_SHORT).show(); + showChangeSecurityQuestionsDialog(); + } else { + // 验证失败,显示提示 + Toast.makeText(NotesPreferenceActivity.this, "验证失败,请重新输入", Toast.LENGTH_SHORT).show(); + // 重新显示验证对话框 + showVerifySecurityQuestionsForModificationDialog(); + } + } + }); + + // 设置取消按钮 + builder.setNegativeButton("取消", null); + + // 显示对话框 + builder.show(); + } /** * 加载同步按钮和同步状态显示