新增富文本编辑功能 #16

Merged
p7fulywfa merged 1 commits from tangbo_branch into master 1 month ago

@ -99,6 +99,16 @@ public class Notes {
* 4x4便
*/
public static final int TYPE_WIDGET_4X = 1;
/**
* 便
*/
public static final int LOCK_TYPE_NOTE = 0;
/**
*
*/
public static final int LOCK_TYPE_FOLDER = 1;
/**
* 便
@ -234,6 +244,26 @@ public class Notes {
* <P> 01 </P>
*/
public static final String PINNED = "pinned";
/**
*
* <P> : INTEGER </P>
* <P> 01 </P>
*/
public static final String IS_LOCKED = "is_locked";
/**
*
* <P> : TEXT </P>
*/
public static final String LOCK_PASSWORD = "lock_password";
/**
*
* <P> : INTEGER </P>
* <P> 0便1 </P>
*/
public static final String LOCK_TYPE = "lock_type";
}
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 = 5;
private static final int DB_VERSION = 6;
/**
*
@ -75,7 +75,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.PINNED + " INTEGER NOT NULL DEFAULT 0" +
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" +
")";
/**
@ -385,6 +388,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
oldVersion++;
}
// 版本5升级到版本6添加锁相关字段
if (oldVersion == 5) {
upgradeToV6(db);
oldVersion++;
}
// 如果需要,重新创建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
@ -445,4 +454,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.PINNED
+ " INTEGER NOT NULL DEFAULT 0");
}
/**
* 6便
* @param db
*/
private void upgradeToV6(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.IS_LOCKED
+ " INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_PASSWORD
+ " TEXT NOT NULL DEFAULT ''");
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.LOCK_TYPE
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -504,19 +504,45 @@ public class WorkingNote {
}
}
/**
*
* <p>
* NotesetNoteValue
* </p>
*
* @param key NoteColumns
* @param value
*/
public void setNoteValue(String key, String value) {
mNote.setNoteValue(key, value);
}
/**
*
* <p>
*
* </p>
*
* @param text Spannable
*/
public void setWorkingText(CharSequence text) {
String textStr = text.toString();
if (!TextUtils.equals(mContent, textStr)) {
mContent = textStr;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
/**
*
* <p>
*
* </p>
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
setWorkingText((CharSequence) text);
}
/**

@ -30,6 +30,7 @@ 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.TextUtils;
@ -43,11 +44,17 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.database.Cursor;
import android.content.ContentResolver;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@ -91,7 +98,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public TextView tvAlertDate;
public ImageView ibSetBgColor;
public ImageButton ibSetBgColor;
}
/** 背景颜色选择按钮映射表将按钮ID映射到颜色ID */
@ -141,14 +148,25 @@ public class NoteEditActivity extends Activity implements OnClickListener,
/** 头部视图面板 */
private View mHeadViewPanel;
/** 粗体按钮 */
private Button mBtnBold;
/** 斜体按钮 */
private Button mBtnItalic;
/** 下划线按钮 */
private Button mBtnUnderline;
/** 字体颜色按钮 */
private Button mBtnTextColor;
/** 背景颜色选择器视图 */
private View mNoteBgColorSelector;
/** 字体颜色选择器视图 */
private View mNoteTextColorSelector;
/** 字体大小选择器视图 */
private View mFontSizeSelector;
/** 笔记编辑文本框 */
private EditText mNoteEditor;
private NoteEditText mNoteEditor;
/** 笔记编辑面板 */
private View mNoteEditorPanel;
@ -322,7 +340,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));
// 解析HTML格式的富文本内容添加null检查防止闪退
String content = mWorkingNote.getContent();
CharSequence htmlContent = Html.fromHtml(content == null ? "" : content);
mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
@ -408,6 +429,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
if (mNoteTextColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteTextColorSelector, ev)) {
mNoteTextColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
@ -446,9 +473,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor = (ImageButton) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditor = (NoteEditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
@ -456,7 +483,28 @@ public class NoteEditActivity extends Activity implements OnClickListener,
iv.setOnClickListener(this);
}
// 初始化富文本按钮
mBtnBold = findViewById(R.id.btn_bold);
mBtnItalic = findViewById(R.id.btn_italic);
mBtnUnderline = findViewById(R.id.btn_underline);
mBtnTextColor = findViewById(R.id.btn_set_text_color);
mBtnBold.setOnClickListener(this);
mBtnItalic.setOnClickListener(this);
mBtnUnderline.setOnClickListener(this);
mBtnTextColor.setOnClickListener(this);
mFontSizeSelector = findViewById(R.id.font_size_selector);
mNoteTextColorSelector = findViewById(R.id.note_text_color_selector);
// 初始化字体颜色选择器的颜色选项
ImageView ivTextBlack = findViewById(R.id.iv_text_black);
ImageView ivTextWhite = findViewById(R.id.iv_text_white);
ImageView ivTextRed = findViewById(R.id.iv_text_red);
ImageView ivTextBlue = findViewById(R.id.iv_text_blue);
ivTextBlack.setOnClickListener(this);
ivTextWhite.setOnClickListener(this);
ivTextRed.setOnClickListener(this);
ivTextBlue.setOnClickListener(this);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
@ -472,6 +520,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
// 将光标改为文本选中光标
mNoteEditor.setTextIsSelectable(true);
}
/**
@ -516,15 +567,39 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
// 显示背景颜色选择器,隐藏其他选择器
mNoteBgColorSelector.setVisibility(View.VISIBLE);
mNoteTextColorSelector.setVisibility(View.GONE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
View.VISIBLE);
} else if (id == R.id.btn_set_text_color) {
// 显示字体颜色选择器,隐藏其他选择器
mNoteTextColorSelector.setVisibility(View.VISIBLE);
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
// 处理背景颜色选择
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_black) {
// 应用黑色字体
mNoteEditor.applyTextColor(0xFF000000);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_white) {
// 应用白色字体
mNoteEditor.applyTextColor(0xFFFFFFFF);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_red) {
// 应用红色字体
mNoteEditor.applyTextColor(0xFFFF0000);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_blue) {
// 应用蓝色字体
mNoteEditor.applyTextColor(0xFF0000FF);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
// 处理字体大小选择
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
@ -537,6 +612,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
} else if (id == R.id.btn_bold) {
// 应用粗体
mNoteEditor.applyBold();
} else if (id == R.id.btn_italic) {
// 应用斜体
mNoteEditor.applyItalic();
} else if (id == R.id.btn_underline) {
// 应用下划线
mNoteEditor.applyUnderline();
}
}
@ -565,6 +649,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mNoteTextColorSelector.getVisibility() == View.VISIBLE) {
mNoteTextColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
@ -671,6 +758,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
case R.id.menu_lock:
showPasswordDialogForLock();
break;
case R.id.menu_unlock:
showPasswordDialogForUnlock();
break;
default:
break;
}
@ -709,6 +802,139 @@ public class NoteEditActivity extends Activity implements OnClickListener,
context.startActivity(intent);
}
/**
*
* <p>
*
* </p>
*/
private void showPasswordDialogForLock() {
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);
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_enter_password)
.setView(passwordEditText)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordEditText.getText().toString();
if (!TextUtils.isEmpty(password)) {
lockCurrentNote(password);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/**
*
* <p>
*
* </p>
*/
private void showPasswordDialogForUnlock() {
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);
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_enter_password)
.setView(passwordEditText)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordEditText.getText().toString();
if (!TextUtils.isEmpty(password)) {
unlockCurrentNote(password);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/**
*
* <p>
*
* </p>
* @param password
*/
private void lockCurrentNote(String password) {
// 设置锁定状态为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));
// 保存笔记
saveNote();
// 显示提示信息
Toast.makeText(this, getString(R.string.message_note_locked), Toast.LENGTH_SHORT).show();
}
/**
*
* <p>
*
* </p>
* @param password
*/
private void unlockCurrentNote(String password) {
// 使用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(password))) {
// 密码正确设置锁定状态为0未锁定
mWorkingNote.setNoteValue(NoteColumns.IS_LOCKED, "0");
// 清空密码
mWorkingNote.setNoteValue(NoteColumns.LOCK_PASSWORD, "");
// 保存笔记
saveNote();
// 显示提示信息
Toast.makeText(this, getString(R.string.message_note_unlocked), Toast.LENGTH_SHORT).show();
} else {
// 密码错误,显示错误信息
Toast.makeText(this, getString(R.string.error_wrong_password), Toast.LENGTH_SHORT).show();
}
}
/**
*
* <p>
* 使Base64ANDROID_ID
* </p>
* @param password
* @return
*/
private String encryptPassword(String password) {
try {
// 获取设备的ANDROID_ID作为加密密钥
String androidId = android.provider.Settings.Secure.getString(getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID);
// 使用简单的加密算法将密码与ANDROID_ID拼接后进行Base64编码
String combined = password + androidId;
return android.util.Base64.encodeToString(combined.getBytes(), android.util.Base64.DEFAULT);
} catch (Exception e) {
Log.e(TAG, "Password encryption failed", e);
return password; // 加密失败时返回原始密码
}
}
/**
*
* <p>
@ -910,11 +1136,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* <p>
*
* </p>
* @param fullText
* @param fullText CharSequence
* @param userQuery
* @return 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)) {
mPattern = Pattern.compile(userQuery);
@ -1048,7 +1274,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
// 保留富文本格式,将 Spanned 转换为 HTML 字符串保存
mWorkingNote.setWorkingText(Html.toHtml(mNoteEditor.getText()));
}
return hasChecked;
}

@ -20,9 +20,15 @@ import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.text.style.ForegroundColorSpan;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
@ -316,4 +322,108 @@ public class NoteEditText extends EditText {
}
super.onCreateContextMenu(menu);
}
/**
*
*/
public void applyBold() {
applyStyle(Typeface.BOLD);
}
/**
*
*/
public void applyItalic() {
applyStyle(Typeface.ITALIC);
}
/**
* 线
*/
public void applyUnderline() {
SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
// 检查是否已存在下划线样式
UnderlineSpan[] existingUnderlines = ssb.getSpans(start, end, UnderlineSpan.class);
if (existingUnderlines.length > 0) {
// 移除下划线
for (UnderlineSpan span : existingUnderlines) {
ssb.removeSpan(span);
}
} else {
// 添加下划线
ssb.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
setText(ssb);
setSelection(start, end);
}
}
/**
*
* @param color
*/
public void applyTextColor(int color) {
SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
// 移除已有的文字颜色
ForegroundColorSpan[] existingSpans = ssb.getSpans(start, end, ForegroundColorSpan.class);
for (ForegroundColorSpan span : existingSpans) {
ssb.removeSpan(span);
}
// 添加新的文字颜色
ssb.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
setText(ssb);
setSelection(start, end);
}
}
/**
*
*/
private void applyStyle(int style) {
SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
// 获取当前选中文本的所有样式
StyleSpan[] existingSpans = ssb.getSpans(start, end, StyleSpan.class);
// 计算当前选中文本的总样式
int currentStyle = Typeface.NORMAL;
if (existingSpans.length > 0) {
// 如果有样式,取第一个样式(假设所有样式一致)
currentStyle = existingSpans[0].getStyle();
}
// 计算新的字体样式
int newStyle;
if ((currentStyle & style) != 0) {
// 移除该样式
newStyle = currentStyle & ~style;
} else {
// 添加该样式
newStyle = currentStyle | style;
}
// 移除旧的样式
for (StyleSpan span : existingSpans) {
ssb.removeSpan(span);
}
// 添加新的样式
ssb.setSpan(new StyleSpan(newStyle), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
setText(ssb);
setSelection(start, end);
}
}
}

@ -54,6 +54,9 @@ public class NoteItemData {
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.PINNED,
NoteColumns.IS_LOCKED,
NoteColumns.LOCK_PASSWORD,
NoteColumns.LOCK_TYPE,
};
/**
@ -120,6 +123,21 @@ public class NoteItemData {
*
*/
private static final int PINNED_COLUMN = 12;
/**
*
*/
private static final int IS_LOCKED_COLUMN = 13;
/**
*
*/
private static final int LOCK_PASSWORD_COLUMN = 14;
/**
*
*/
private static final int LOCK_TYPE_COLUMN = 15;
/**
* ID
@ -131,6 +149,21 @@ public class NoteItemData {
*/
private boolean mPinned;
/**
*
*/
private boolean mIsLocked;
/**
*
*/
private String mLockPassword;
/**
*
*/
private int mLockType;
/**
*
*/
@ -236,12 +269,16 @@ public class NoteItemData {
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 先替换标签保留HTML格式以便在列表中显示富文本
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mPinned = (cursor.getInt(PINNED_COLUMN) > 0) ? true : false;
mIsLocked = (cursor.getInt(IS_LOCKED_COLUMN) > 0) ? true : false;
mLockPassword = cursor.getString(LOCK_PASSWORD_COLUMN);
mLockType = cursor.getInt(LOCK_TYPE_COLUMN);
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
@ -475,6 +512,54 @@ public class NoteItemData {
public void setPinned(boolean pinned) {
mPinned = pinned;
}
/**
*
* @return truefalse
*/
public boolean isLocked() {
return mIsLocked;
}
/**
*
* @param locked truefalse
*/
public void setLocked(boolean locked) {
mIsLocked = locked;
}
/**
*
* @return
*/
public String getLockPassword() {
return mLockPassword;
}
/**
*
* @param password
*/
public void setLockPassword(String password) {
mLockPassword = password;
}
/**
*
* @return 01
*/
public int getLockType() {
return mLockType;
}
/**
*
* @param type 01
*/
public void setLockType(int type) {
mLockType = type;
}
/**
*

@ -115,6 +115,127 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
*/
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
}
/**
*
* <p>
*
* </p>
*/
private void showPasswordDialog() {
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);
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_enter_password)
.setView(passwordEditText)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordEditText.getText().toString();
if (!TextUtils.isEmpty(password)) {
toggleLockedStatus(password);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/**
*
* <p>
* 使
* </p>
* @param password
*/
private void toggleLockedStatus(final String password) {
final HashSet<Long> selectedIds = mNotesListAdapter.getSelectedItemIds();
final int selectedCount = mNotesListAdapter.getSelectedCount();
new AsyncTask<Void, Void, Integer>() {
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},
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();
}
} else {
// 如果是解锁操作,清空密码
values.put(NoteColumns.LOCK_PASSWORD, "");
}
mContentResolver.update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.ID + "=?",
new String[]{String.valueOf(noteId)});
cursor.close();
}
}
return finalLockedStatus;
}
@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();
}
}.execute();
}
/**
*
* <p>
* 使Base64ANDROID_ID
* </p>
* @param password
* @return
*/
private String encryptPassword(String password) {
try {
// 获取设备的ANDROID_ID作为加密密钥
String androidId = android.provider.Settings.Secure.getString(getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID);
// 使用简单的加密算法将密码与ANDROID_ID拼接后进行Base64编码
String combined = password + androidId;
return android.util.Base64.encodeToString(combined.getBytes(), android.util.Base64.DEFAULT);
} catch (Exception e) {
Log.e(TAG, "Password encryption failed", e);
return password; // 加密失败时返回原始密码
}
};
/** 当前列表编辑状态 */
@ -195,6 +316,52 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
// 上移底部系统栏,解决写便签按钮与底部系统栏之间的多余间距
View decorView = getWindow().getDecorView();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
// Android 11+ 使用新的WindowInsetsController API
android.view.WindowInsetsController insetsController = decorView.getWindowInsetsController();
if (insetsController != null) {
// 隐藏底部导航栏
insetsController.hide(android.view.WindowInsets.Type.navigationBars());
// 设置系统栏行为,允许滑动显示
insetsController.setSystemBarsBehavior(
android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
// Android 4.1-10 使用旧的系统UI可见性API
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
}
// 添加系统UI可见性变化监听器确保系统栏保持上移状态
decorView.setOnSystemUiVisibilityChangeListener(
new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
// 当系统UI可见性变化时重新设置系统UI可见性
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
// Android 11+ 使用新API
android.view.WindowInsetsController insetsController = decorView.getWindowInsetsController();
if (insetsController != null) {
insetsController.hide(android.view.WindowInsets.Type.navigationBars());
}
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
// Android 4.1-10 使用旧API
if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
}
}
}
}
);
initResources();
/**
@ -379,6 +546,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
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);
@ -541,6 +713,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
case R.id.pin:
togglePinnedStatus();
break;
case R.id.lock:
showPasswordDialog();
break;
default:
return false;
}
@ -875,11 +1050,72 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
* </p>
* @param data
*/
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
private void openNode(final NoteItemData data) {
// 检查笔记是否被锁定
if (data.isLocked()) {
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);
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_enter_password)
.setView(passwordEditText)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String password = passwordEditText.getText().toString();
if (!TextUtils.isEmpty(password)) {
verifyPasswordAndOpenNote(data.getId(), password);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
} else {
// 未锁定,直接打开笔记
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
}
/**
*
* <p>
*
* 1.
* 2.
* 3.
* </p>
* @param noteId ID
* @param password
*/
private void verifyPasswordAndOpenNote(final long noteId, String password) {
// 查询笔记的加密密码
String[] projection = {NoteColumns.LOCK_PASSWORD};
String selection = NoteColumns.ID + "=?";
String[] selectionArgs = {String.valueOf(noteId)};
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(password))) {
// 密码正确,打开笔记
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, noteId);
startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
} else {
// 密码错误,显示错误信息
Toast.makeText(this, getString(R.string.error_wrong_password), Toast.LENGTH_SHORT).show();
}
}
/**

@ -289,15 +289,29 @@ public class NotesListAdapter extends CursorAdapter {
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
Cursor cursor = getCursor();
if (cursor != null && !cursor.isClosed()) {
// 使用 try-finally 块确保 cursor 位置正确恢复
int originalPosition = cursor.getPosition();
try {
if (cursor.moveToFirst()) {
do {
try {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} catch (Exception e) {
// 捕获可能的异常,避免崩溃
Log.e(TAG, "Error calculating note count: " + e.getMessage());
break;
}
} while (cursor.moveToNext());
}
} finally {
// 恢复 cursor 到原始位置
if (!cursor.isClosed()) {
cursor.moveToPosition(originalPosition);
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}

@ -17,6 +17,7 @@
package net.micode.notes.ui;
import android.content.Context;
import android.text.Html;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
@ -103,7 +104,7 @@ public class NotesListItem extends LinearLayout {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
mTitle.setText(Html.fromHtml(DataUtils.getFormattedSnippet(data.getSnippet())));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
@ -120,7 +121,7 @@ public class NotesListItem extends LinearLayout {
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
mTitle.setText(Html.fromHtml(DataUtils.getFormattedSnippet(data.getSnippet())));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);

@ -57,11 +57,81 @@
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<!-- 富文本编辑按钮 -->
<Button
android:id="@+id/btn_bold"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="B"
android:textSize="16sp"
android:textStyle="bold"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<Button
android:id="@+id/btn_italic"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="I"
android:textSize="16sp"
android:textStyle="italic"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<Button
android:id="@+id/btn_underline"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="U"
android:textSize="16sp"
android:textStyle="normal"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<!-- 字体颜色选择按钮 -->
<Button
android:id="@+id/btn_set_text_color"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="8dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="A"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#FF0000"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<ImageButton
android:id="@+id/btn_set_bg_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
android:background="@drawable/bg_btn_set_color"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
<LinearLayout
@ -118,12 +188,7 @@
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
<LinearLayout
android:id="@+id/note_bg_color_selector"
@ -239,6 +304,113 @@
</FrameLayout>
</LinearLayout>
<!-- 字体颜色选择器 -->
<LinearLayout
android:id="@+id/note_text_color_selector"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="@drawable/note_edit_color_selector_panel"
android:layout_marginTop="30dip"
android:layout_marginRight="8dip"
android:layout_gravity="top|right"
android:visibility="gone">
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_black"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_black_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_white_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="3dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_red"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_red_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="2dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_blue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000FF"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_blue_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/font_size_selector"
android:layout_width="fill_parent"

@ -56,5 +56,5 @@
android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="bottom"
android:layout_marginBottom="16dp" />
android:layout_marginBottom="0dp" />
</FrameLayout>

@ -49,4 +49,12 @@
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
<item
android:id="@+id/menu_lock"
android:title="@string/menu_lock" />
<item
android:id="@+id/menu_unlock"
android:title="@string/menu_unlock" />
</menu>

@ -33,4 +33,9 @@
android:id="@+id/pin"
android:title="@string/menu_pin"
android:showAsAction="always|withText" />
<item
android:id="@+id/lock"
android:title="@string/menu_lock"
android:showAsAction="always|withText" />
</menu>

@ -50,6 +50,13 @@
<string name="menu_move">Move to folder</string>
<string name="menu_pin">Pin</string>
<string name="menu_unpin">Unpin</string>
<string name="menu_lock">Lock</string>
<string name="menu_unlock">Unlock</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="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>

Loading…
Cancel
Save