diff --git a/src/main/java/net/micode/notes/data/Notes.java b/src/main/java/net/micode/notes/data/Notes.java index 297b7dc..33f8d3a 100644 --- a/src/main/java/net/micode/notes/data/Notes.java +++ b/src/main/java/net/micode/notes/data/Notes.java @@ -265,6 +265,13 @@ public class Notes { */ public static final String LOCK_TYPE = "lock_type"; + /** + * 密码提示 + *

类型: TEXT

+ *

用于帮助用户记忆密码的提示信息

+ */ + public static final String LOCK_HINT = "lock_hint"; + /** * 便签或文件夹被删除的时间 *

类型: INTEGER (long)

diff --git a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index 4edf21d..d31c1c5 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 = 7; + private static final int DB_VERSION = 8; /** * 数据库表名常量定义 @@ -79,6 +79,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { 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_HINT + " TEXT NOT NULL DEFAULT ''," + NoteColumns.DELETED_DATE + " INTEGER NOT NULL DEFAULT 0" + ")"; @@ -425,6 +426,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { oldVersion++; } + // 版本7升级到版本8:添加密码提示字段 + if (oldVersion == 7) { + upgradeToV8(db); + oldVersion++; + } + // 如果需要,重新创建触发器 if (reCreateTriggers) { reCreateNoteTableTriggers(db); @@ -530,4 +537,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=new." + NoteColumns.ID + ";" + " END"); } + + /** + * 升级到版本8:添加密码提示字段,用于密码记忆辅助 + * @param db 数据库实例 + */ + private void upgradeToV8(SQLiteDatabase db) { + // 添加LOCK_HINT字段,用于存储密码提示 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_HINT + + " TEXT NOT NULL DEFAULT ''"); + } } \ No newline at end of file diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java index c80949f..df2a73b 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -46,6 +46,9 @@ import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.Button; import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.util.TypedValue; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; @@ -172,6 +175,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** 笔记编辑面板 */ private View mNoteEditorPanel; + /** 字符统计显示文本框 */ + private TextView mTvCharCount; + /** 工作笔记对象,用于处理笔记数据 */ private WorkingNote mWorkingNote; @@ -350,6 +356,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery)); mNoteEditor.setSelection(mNoteEditor.getText().length()); } + // 初始化字符统计显示 + updateCharCount(); for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } @@ -480,7 +488,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.ibSetBgColor = (ImageButton) findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); mNoteEditor = (NoteEditText) findViewById(R.id.note_edit_view); + mNoteEditor.setOnTextViewChangeListener(this); mNoteEditorPanel = findViewById(R.id.sv_note_edit); + mTvCharCount = (TextView) findViewById(R.id.tv_char_count); mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); @@ -768,6 +778,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, case R.id.menu_unlock: showPasswordDialogForUnlock(); break; + case R.id.menu_change_password: + showChangePasswordDialog(); + break; case R.id.menu_note_template: openTemplateSelector(); break; @@ -812,23 +825,41 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** * 显示密码输入对话框用于锁定笔记 *

- * 该方法用于显示一个密码输入对话框,让用户输入密码来锁定当前编辑的笔记 + * 该方法用于显示一个密码输入对话框,让用户输入密码和密码提示来锁定当前编辑的笔记 *

*/ private void showPasswordDialogForLock() { + // 创建布局并添加密码输入框 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(60, 40, 60, 40); + layout.setPadding(60, 40, 60, 40); + // 设置子视图间距 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // 底部外边距作为间距 + final EditText passwordEditText = new EditText(this); passwordEditText.setHint(R.string.hint_enter_password); passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(passwordEditText); + + // 添加密码提示输入框 + final EditText hintEditText = new EditText(this); + hintEditText.setHint(R.string.hint_enter_password_hint); + layout.addView(hintEditText); new AlertDialog.Builder(this) .setTitle(R.string.dialog_enter_password) - .setView(passwordEditText) + .setView(layout) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String password = passwordEditText.getText().toString(); + String hint = hintEditText.getText().toString(); if (!TextUtils.isEmpty(password)) { - lockCurrentNote(password); + lockCurrentNote(password, hint); } } }) @@ -843,13 +874,50 @@ public class NoteEditActivity extends Activity implements OnClickListener, *

*/ private void showPasswordDialogForUnlock() { + // 查询当前笔记的密码提示 + String[] projection = {NoteColumns.LOCK_HINT}; + String selection = NoteColumns.ID + "=?"; + String[] selectionArgs = {String.valueOf(mWorkingNote.getNoteId())}; + + Cursor cursor = getContentResolver().query(Notes.CONTENT_NOTE_URI, projection, selection, selectionArgs, null); + String lockHint = null; + if (cursor != null && cursor.moveToFirst()) { + lockHint = cursor.getString(0); + cursor.close(); + } + + // 创建布局并添加密码输入框 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(60, 40, 60, 40); + + // 创建布局参数,用于设置子视图间距 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // 底部外边距作为间距 + final EditText passwordEditText = new EditText(this); passwordEditText.setHint(R.string.hint_enter_password); passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(passwordEditText, params); + + // 如果有密码提示,显示提示信息 + if (!TextUtils.isEmpty(lockHint)) { + TextView hintTextView = new TextView(this); + hintTextView.setText(getString(R.string.password_hint_prefix) + lockHint); + hintTextView.setTextColor(getResources().getColor(R.color.secondary_text_dark)); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + // 提示文本不需要底部间距 + LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + layout.addView(hintTextView, hintParams); + } new AlertDialog.Builder(this) .setTitle(R.string.dialog_enter_password) - .setView(passwordEditText) + .setView(layout) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -866,17 +934,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** * 锁定当前笔记 *

- * 该方法用于锁定当前编辑的笔记,更新其锁定状态、密码和锁定类型 + * 该方法用于锁定当前编辑的笔记,更新其锁定状态、密码、密码提示和锁定类型 *

* @param password 用于锁定的密码 + * @param hint 用于帮助记忆密码的提示 */ - private void lockCurrentNote(String password) { + private void lockCurrentNote(String password, String hint) { // 设置锁定状态为1(已锁定) mWorkingNote.setNoteValue(NoteColumns.IS_LOCKED, "1"); // 设置加密后的密码 mWorkingNote.setNoteValue(NoteColumns.LOCK_PASSWORD, encryptPassword(password)); // 设置锁定类型为笔记类型 mWorkingNote.setNoteValue(NoteColumns.LOCK_TYPE, String.valueOf(Notes.LOCK_TYPE_NOTE)); + // 设置密码提示 + mWorkingNote.setNoteValue(NoteColumns.LOCK_HINT, hint); // 保存笔记 saveNote(); // 显示提示信息 @@ -941,6 +1012,97 @@ public class NoteEditActivity extends Activity implements OnClickListener, return password; // 加密失败时返回原始密码 } } + + /** + * 显示修改密码对话框 + *

+ * 该方法用于显示一个修改密码对话框,让用户输入旧密码、新密码和新的密码提示 + *

+ */ + private void showChangePasswordDialog() { + // 创建布局并添加输入框 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(60, 40, 60, 40); + + // 创建布局参数,用于设置子视图间距 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // 底部外边距作为间距 + + // 旧密码输入框 + final EditText oldPasswordEditText = new EditText(this); + oldPasswordEditText.setHint(R.string.hint_enter_password); + oldPasswordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(oldPasswordEditText, params); + + // 新密码输入框 + final EditText newPasswordEditText = new EditText(this); + newPasswordEditText.setHint(R.string.hint_enter_password); + newPasswordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(newPasswordEditText, params); + + // 新密码提示输入框 + final EditText newHintEditText = new EditText(this); + newHintEditText.setHint(R.string.hint_enter_password_hint); + layout.addView(newHintEditText); + + new AlertDialog.Builder(this) + .setTitle(R.string.menu_change_password) + .setView(layout) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String oldPassword = oldPasswordEditText.getText().toString(); + String newPassword = newPasswordEditText.getText().toString(); + String newHint = newHintEditText.getText().toString(); + if (!TextUtils.isEmpty(oldPassword) && !TextUtils.isEmpty(newPassword)) { + changePassword(oldPassword, newPassword, newHint); + } + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + /** + * 修改密码 + *

+ * 该方法用于修改当前笔记的密码,需要验证旧密码,验证通过后更新新密码和密码提示 + *

+ * @param oldPassword 旧密码 + * @param newPassword 新密码 + * @param newHint 新的密码提示 + */ + private void changePassword(String oldPassword, String newPassword, String newHint) { + // 使用ContentResolver直接查询数据库获取加密密码 + String[] projection = {NoteColumns.LOCK_PASSWORD}; + String selection = NoteColumns.ID + "=?"; + String[] selectionArgs = {String.valueOf(mWorkingNote.getNoteId())}; + + Cursor cursor = getContentResolver().query(Notes.CONTENT_NOTE_URI, projection, selection, selectionArgs, null); + String encryptedPassword = null; + + if (cursor != null && cursor.moveToFirst()) { + encryptedPassword = cursor.getString(0); + cursor.close(); + } + + // 验证旧密码是否正确 + if (encryptedPassword != null && encryptedPassword.equals(encryptPassword(oldPassword))) { + // 旧密码正确,更新新密码和密码提示 + mWorkingNote.setNoteValue(NoteColumns.LOCK_PASSWORD, encryptPassword(newPassword)); + mWorkingNote.setNoteValue(NoteColumns.LOCK_HINT, newHint); + // 保存笔记 + saveNote(); + // 显示提示信息 + Toast.makeText(this, getString(R.string.message_password_changed), Toast.LENGTH_SHORT).show(); + } else { + // 旧密码错误,显示错误信息 + Toast.makeText(this, getString(R.string.error_wrong_password), Toast.LENGTH_SHORT).show(); + } + } /** * 创建新笔记 @@ -1229,6 +1391,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); } + // 更新字符统计 + updateCharCount(); + } + + /** + * 当文本内容变化时,更新字符统计 + */ + public void onContentChange() { + updateCharCount(); } /** @@ -1382,6 +1553,45 @@ public class NoteEditActivity extends Activity implements OnClickListener, showToast(resId, Toast.LENGTH_SHORT); } + /** + * 计算字符串中的有效字符数(不包括空格、换行符等空白字符) + * @param text 要计算的字符串 + * @return 有效字符数 + */ + private int countValidCharacters(String text) { + if (TextUtils.isEmpty(text)) { + return 0; + } + // 去除所有空白字符后计算长度 + return text.replaceAll("\\s", "").length(); + } + + /** + * 更新字符统计显示 + */ + private void updateCharCount() { + int charCount = 0; + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 列表模式:遍历所有列表项 + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + if (!TextUtils.isEmpty(edit.getText())) { + charCount += countValidCharacters(edit.getText().toString()); + } + } + } else { + // 普通文本模式:直接计算编辑框内容 + if (!TextUtils.isEmpty(mNoteEditor.getText())) { + charCount = countValidCharacters(mNoteEditor.getText().toString()); + } + } + // 更新显示 + if (mTvCharCount != null) { + mTvCharCount.setText(charCount + " 字符"); + } + } + /** * 打开模板选择页面 *

diff --git a/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/main/java/net/micode/notes/ui/NoteEditText.java index 5a6842e..cd75b4c 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -37,6 +37,7 @@ import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.view.MotionEvent; import android.widget.EditText; +import android.text.TextWatcher; import net.micode.notes.R; @@ -112,6 +113,11 @@ public class NoteEditText extends EditText { * 当文本变化时,隐藏或显示项目选项 */ void onTextChange(int index, boolean hasText); + + /** + * 当文本内容变化时,更新字符统计 + */ + void onContentChange(); } /** @@ -151,6 +157,8 @@ public class NoteEditText extends EditText { */ public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); + mIndex = 0; + initTextWatcher(); } /** @@ -161,7 +169,30 @@ public class NoteEditText extends EditText { */ public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO Auto-generated constructor stub + mIndex = 0; + initTextWatcher(); + } + + /** + * 初始化文本变化监听器 + */ + private void initTextWatcher() { + addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(android.text.Editable s) { + if (mOnTextViewChangeListener != null) { + mOnTextViewChangeListener.onContentChange(); + } + } + }); } /** @@ -207,7 +238,9 @@ public class NoteEditText extends EditText { public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { + // 只有在列表模式下(mIndex > 0)才阻止默认行为 + // 普通文本模式下让系统默认处理回车键 + if (mOnTextViewChangeListener != null && mIndex > 0) { return false; } break; @@ -244,10 +277,14 @@ public class NoteEditText extends EditText { break; case KeyEvent.KEYCODE_ENTER: if (mOnTextViewChangeListener != null) { - int selectionStart = getSelectionStart(); - String text = getText().subSequence(selectionStart, length()).toString(); - setText(getText().subSequence(0, selectionStart)); - mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + // 只有在列表模式下(mIndex > 0)才执行特殊处理 + // 普通文本模式下,让系统默认处理回车键 + if (mIndex > 0) { + int selectionStart = getSelectionStart(); + String text = getText().subSequence(selectionStart, length()).toString(); + setText(getText().subSequence(0, selectionStart)); + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } } else { Log.d(TAG, "OnTextViewChangeListener was not seted"); } diff --git a/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/main/java/net/micode/notes/ui/NoteItemData.java index 2dcdedf..9a51882 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.LOCK_HINT, NoteColumns.DELETED_DATE, }; @@ -140,10 +141,15 @@ public class NoteItemData { */ private static final int LOCK_TYPE_COLUMN = 15; + /** + * 查询结果中密码提示列的索引 + */ + private static final int LOCK_HINT_COLUMN = 16; + /** * 查询结果中删除日期列的索引 */ - private static final int DELETED_DATE_COLUMN = 16; + private static final int DELETED_DATE_COLUMN = 17; /** * 笔记ID @@ -170,6 +176,11 @@ public class NoteItemData { */ private int mLockType; + /** + * 密码提示 + */ + private String mLockHint; + /** * 提醒日期 */ @@ -290,6 +301,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); + mLockHint = cursor.getString(LOCK_HINT_COLUMN); mDeletedDate = cursor.getLong(DELETED_DATE_COLUMN); mPhoneNumber = ""; @@ -565,6 +577,22 @@ public class NoteItemData { return mLockType; } + /** + * 获取密码提示 + * @return 密码提示字符串 + */ + public String getLockHint() { + return mLockHint; + } + + /** + * 设置密码提示 + * @param hint 密码提示字符串 + */ + public void setLockHint(String hint) { + mLockHint = hint; + } + /** * 设置锁定类型 * @param type 锁定类型,0表示笔记,1表示文件夹 diff --git a/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/main/java/net/micode/notes/ui/NotesListActivity.java index ae6ab3f..7eb7a8a 100644 --- a/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -57,8 +57,10 @@ import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.PopupMenu; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import android.util.TypedValue; import net.micode.notes.R; import net.micode.notes.data.Notes; @@ -124,19 +126,37 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt *

*/ private void showPasswordDialog() { + // 创建布局并添加输入框 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(60, 40, 60, 40); + + // 创建布局参数,用于设置子视图间距 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // 底部外边距作为间距 + final EditText passwordEditText = new EditText(this); passwordEditText.setHint(R.string.hint_enter_password); passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(passwordEditText, params); + + // 添加密码提示输入框 + final EditText hintEditText = new EditText(this); + hintEditText.setHint(R.string.hint_enter_password_hint); + layout.addView(hintEditText); new AlertDialog.Builder(this) .setTitle(R.string.dialog_enter_password) - .setView(passwordEditText) + .setView(layout) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String password = passwordEditText.getText().toString(); + String hint = hintEditText.getText().toString(); if (!TextUtils.isEmpty(password)) { - toggleLockedStatus(password); + toggleLockedStatus(password, hint); } } }) @@ -150,8 +170,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt * 该方法用于切换选中笔记或文件夹的锁定状态,使用异步任务处理数据库操作 *

* @param password 用于锁定的密码 + * @param hint 用于帮助记忆密码的提示 */ - private void toggleLockedStatus(final String password) { + private void toggleLockedStatus(final String password, final String hint) { final HashSet selectedIds = mNotesListAdapter.getSelectedItemIds(); final int selectedCount = mNotesListAdapter.getSelectedCount(); @@ -159,45 +180,61 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt protected Integer doInBackground(Void... unused) { int finalLockedStatus = -1; // 默认为-1,表示未处理 for (Long noteId : selectedIds) { - // 查询当前便签的锁定状态 + // 查询当前便签的锁定状态和密码 Cursor cursor = mContentResolver.query(Notes.CONTENT_NOTE_URI, - new String[]{NoteColumns.IS_LOCKED}, + new String[]{NoteColumns.IS_LOCKED, NoteColumns.LOCK_PASSWORD}, NoteColumns.ID + "=?", new String[]{String.valueOf(noteId)}, null); if (cursor != null && cursor.moveToFirst()) { int currentLocked = cursor.getInt(0); - // 切换锁定状态 - int newLocked = currentLocked == 1 ? 0 : 1; - finalLockedStatus = newLocked; // 保存最终锁定状态 - - ContentValues values = new ContentValues(); - values.put(NoteColumns.IS_LOCKED, newLocked); - - if (newLocked == 1) { - // 如果是锁定操作,设置密码和锁定类型 - values.put(NoteColumns.LOCK_PASSWORD, encryptPassword(password)); - // 判断是笔记还是文件夹 - Cursor typeCursor = mContentResolver.query(Notes.CONTENT_NOTE_URI, - new String[]{NoteColumns.TYPE}, - NoteColumns.ID + "=?", - new String[]{String.valueOf(noteId)}, - null); - if (typeCursor != null && typeCursor.moveToFirst()) { - int type = typeCursor.getInt(0); - values.put(NoteColumns.LOCK_TYPE, type == Notes.TYPE_FOLDER ? Notes.LOCK_TYPE_FOLDER : Notes.LOCK_TYPE_NOTE); - typeCursor.close(); + String encryptedPassword = cursor.getString(1); + int newLocked = currentLocked; + + if (currentLocked == 1) { + // 如果当前是锁定状态,需要验证密码才能解锁 + if (encryptedPassword != null && encryptedPassword.equals(encryptPassword(password))) { + // 密码正确,解锁 + newLocked = 0; + finalLockedStatus = newLocked; } } else { - // 如果是解锁操作,清空密码 - values.put(NoteColumns.LOCK_PASSWORD, ""); + // 如果当前是未锁定状态,直接锁定 + newLocked = 1; + finalLockedStatus = newLocked; } - mContentResolver.update(Notes.CONTENT_NOTE_URI, - values, - NoteColumns.ID + "=?", - new String[]{String.valueOf(noteId)}); + if (newLocked != currentLocked) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.IS_LOCKED, newLocked); + + if (newLocked == 1) { + // 如果是锁定操作,设置密码、密码提示和锁定类型 + values.put(NoteColumns.LOCK_PASSWORD, encryptPassword(password)); + values.put(NoteColumns.LOCK_HINT, hint); + // 判断是笔记还是文件夹 + Cursor typeCursor = mContentResolver.query(Notes.CONTENT_NOTE_URI, + new String[]{NoteColumns.TYPE}, + NoteColumns.ID + "=?", + new String[]{String.valueOf(noteId)}, + null); + if (typeCursor != null && typeCursor.moveToFirst()) { + int type = typeCursor.getInt(0); + values.put(NoteColumns.LOCK_TYPE, type == Notes.TYPE_FOLDER ? Notes.LOCK_TYPE_FOLDER : Notes.LOCK_TYPE_NOTE); + typeCursor.close(); + } + } else { + // 如果是解锁操作,清空密码和密码提示 + values.put(NoteColumns.LOCK_PASSWORD, ""); + values.put(NoteColumns.LOCK_HINT, ""); + } + + mContentResolver.update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.ID + "=?", + new String[]{String.valueOf(noteId)}); + } cursor.close(); } @@ -207,11 +244,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onPostExecute(Integer finalLockedStatus) { - String message = finalLockedStatus == 1 ? getString(R.string.message_note_locked) : getString(R.string.message_note_unlocked); - Toast.makeText(NotesListActivity.this, message, Toast.LENGTH_SHORT).show(); - // 重新查询数据,更新列表 - startAsyncNotesListQuery(); - mModeCallBack.finishActionMode(); + if (finalLockedStatus == -1) { + // 密码错误,显示错误信息 + Toast.makeText(NotesListActivity.this, getString(R.string.error_wrong_password), Toast.LENGTH_SHORT).show(); + } else { + // 操作成功,显示结果 + String message = finalLockedStatus == 1 ? getString(R.string.message_note_locked) : getString(R.string.message_note_unlocked); + Toast.makeText(NotesListActivity.this, message, Toast.LENGTH_SHORT).show(); + // 重新查询数据,更新列表 + startAsyncNotesListQuery(); + mModeCallBack.finishActionMode(); + } } }.execute(); } @@ -1193,13 +1236,35 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private void openNode(final NoteItemData data) { // 检查笔记是否被锁定 if (data.isLocked()) { + // 创建布局并添加密码输入框 + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(60, 40, 60, 40); + + // 创建布局参数,用于设置子视图间距 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // 底部外边距作为间距 + final EditText passwordEditText = new EditText(this); passwordEditText.setHint(R.string.hint_enter_password); passwordEditText.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(passwordEditText, params); + + // 获取密码提示并显示 + String lockHint = data.getLockHint(); + if (!TextUtils.isEmpty(lockHint)) { + TextView hintTextView = new TextView(this); + hintTextView.setText(getString(R.string.password_hint_prefix) + lockHint); + hintTextView.setTextColor(getResources().getColor(R.color.secondary_text_dark)); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + layout.addView(hintTextView); + } new AlertDialog.Builder(this) .setTitle(R.string.dialog_enter_password) - .setView(passwordEditText) + .setView(layout) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { diff --git a/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/main/java/net/micode/notes/ui/NotesListItem.java index 069771a..32dfbcd 100644 --- a/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -99,20 +99,34 @@ public class NotesListItem extends LinearLayout { mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); 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); + // 检查是否被锁定 + if (data.isLocked()) { + mAlert.setImageResource(android.R.drawable.ic_lock_lock); + } else { + 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); + // 检查是否被锁定 + if (data.isLocked()) { + mAlert.setImageResource(android.R.drawable.ic_lock_lock); + } else { + mAlert.setImageResource(R.drawable.delete); + } } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); mTitle.setText(Html.fromHtml(DataUtils.getFormattedSnippet(data.getSnippet()))); - if (data.hasAlert()) { + // 检查是否被锁定 + if (data.isLocked()) { + mAlert.setImageResource(android.R.drawable.ic_lock_lock); + mAlert.setVisibility(View.VISIBLE); + } else if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); } else { @@ -126,10 +140,20 @@ public class NotesListItem extends LinearLayout { mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, data.getNotesCount())); - mAlert.setVisibility(View.GONE); + // 检查文件夹是否被锁定 + if (data.isLocked()) { + mAlert.setImageResource(android.R.drawable.ic_lock_lock); + mAlert.setVisibility(View.VISIBLE); + } else { + mAlert.setVisibility(View.GONE); + } } else { mTitle.setText(Html.fromHtml(DataUtils.getFormattedSnippet(data.getSnippet()))); - if (data.hasAlert()) { + // 检查笔记是否被锁定 + if (data.isLocked()) { + mAlert.setImageResource(android.R.drawable.ic_lock_lock); + mAlert.setVisibility(View.VISIBLE); + } else if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); } else { diff --git a/src/main/res/layout/note_edit.xml b/src/main/res/layout/note_edit.xml index 3344dcc..626d056 100644 --- a/src/main/res/layout/note_edit.xml +++ b/src/main/res/layout/note_edit.xml @@ -181,6 +181,17 @@ + + + + Unpin Lock Unlock + Change Password Enter Password + Enter Old Password + Enter New Password Please enter password + Please enter password hint + Hint: Incorrect password Note locked successfully Note unlocked successfully + Password changed successfully %d selected Nothing selected, the operation is invalid Select all