新增富文本编辑和标签功能

pull/15/head
chroe 1 month ago
parent 4eef6ca901
commit 97d4a685ba

@ -259,6 +259,12 @@ public class Notes {
* <P> : INTEGER </P>
*/
public static final int TITLE_MAX_LENGTH = 50;
/**
* 便
* <P> : TEXT </P>
*/
public static final String TAGS = "tags";
}
/**

@ -36,7 +36,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
// 数据库版本号
private static final int DB_VERSION = 8;
private static final int DB_VERSION = 9;
// 数据库表名定义
public interface TABLE {
@ -94,7 +94,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.IS_PINNED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.PIN_PRIORITY + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.IS_LOCKED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCK_PASSWORD + " TEXT NOT NULL DEFAULT ''" +
NoteColumns.LOCK_PASSWORD + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TAGS + " TEXT NOT NULL DEFAULT ''" +
")";
// 创建数据表的SQL语句
@ -436,6 +437,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
upgradeToV8(db);
oldVersion++;
}
if (oldVersion == 8) {
upgradeToV9(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
@ -546,4 +552,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TITLE
+ " TEXT NOT NULL DEFAULT ''");
}
/**
* v8v9
* 便
* @param db SQLite
*/
private void upgradeToV9(SQLiteDatabase db) {
// 为笔记表添加标签字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TAGS
+ " TEXT NOT NULL DEFAULT ''");
}
}

@ -27,12 +27,17 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@ -120,6 +125,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public View titleArea; // 标题区域
public TextView tvWordCountLabel; // 字数统计标签
public TextView tvWordCount; // 字数统计数字
public ImageButton ibBold; // 加粗按钮
public ImageButton ibItalic; // 斜体按钮
public ImageButton ibUnderline; // 下划线按钮
public ImageButton ibHighlight; // 高亮按钮
public ImageButton ibTextColor; // 文本颜色按钮
}
/**
@ -174,7 +184,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private View mHeadViewPanel; // 头部视图面板
private View mNoteBgColorSelector; // 背景颜色选择器
private View mFontSizeSelector; // 字体大小选择器
private EditText mNoteEditor; // 便签编辑器
private NoteEditText mNoteEditor; // 便签编辑器
private View mNoteEditorPanel; // 编辑器面板
private WorkingNote mWorkingNote; // 当前工作便签
private SharedPreferences mSharedPrefs; // 共享偏好设置
@ -402,7 +412,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
switchToListMode(mWorkingNote.getContent());
} else {
// 普通文本模式,显示高亮搜索结果
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
String content = mWorkingNote.getContent();
// 直接使用getHighlightQueryResult方法获取处理后的Spannable对象
Spannable spannedContent = getHighlightQueryResult(content, mUserQuery);
mNoteEditor.setText(spannedContent);
// 将光标定位到文本末尾
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
@ -598,9 +613,21 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mNoteHeaderHolder.titleArea = findViewById(R.id.note_title_area);
mNoteHeaderHolder.tvWordCountLabel = (TextView) findViewById(R.id.tv_word_count_label);
mNoteHeaderHolder.tvWordCount = (TextView) findViewById(R.id.tv_word_count);
// 初始化富文本工具栏按钮
mNoteHeaderHolder.ibBold = (ImageButton) findViewById(R.id.btn_bold);
mNoteHeaderHolder.ibBold.setOnClickListener(this);
mNoteHeaderHolder.ibItalic = (ImageButton) findViewById(R.id.btn_italic);
mNoteHeaderHolder.ibItalic.setOnClickListener(this);
mNoteHeaderHolder.ibUnderline = (ImageButton) findViewById(R.id.btn_underline);
mNoteHeaderHolder.ibUnderline.setOnClickListener(this);
mNoteHeaderHolder.ibHighlight = (ImageButton) findViewById(R.id.btn_highlight);
mNoteHeaderHolder.ibHighlight.setOnClickListener(this);
mNoteHeaderHolder.ibTextColor = (ImageButton) findViewById(R.id.btn_text_color);
mNoteHeaderHolder.ibTextColor.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);
// 初始化背景颜色选择器
@ -718,7 +745,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
/**
*
* <p>
* UI
* UI
* </p>
*
* @param v
@ -738,7 +765,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} else if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
@ -759,6 +786,21 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mFontSizeSelector.setVisibility(View.GONE);
} else if (id == R.id.add_img_btn) {
insertImage();
} else if (id == R.id.btn_bold) {
// 处理加粗按钮点击
mNoteEditor.toggleBold();
} else if (id == R.id.btn_italic) {
// 处理斜体按钮点击
mNoteEditor.toggleItalic();
} else if (id == R.id.btn_underline) {
// 处理下划线按钮点击
mNoteEditor.toggleUnderline();
} else if (id == R.id.btn_highlight) {
// 处理高亮按钮点击
showColorPickerDialog(true);
} else if (id == R.id.btn_text_color) {
// 处理文本颜色按钮点击,显示颜色选择对话框
showColorPickerDialog(false);
}
}
@ -1160,21 +1202,55 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableStringBuilder builder = new SpannableStringBuilder(fullText == null ? "" : fullText);
// 确保fullText不为null
if (fullText == null) {
return new SpannableString("");
}
// 先将纯文本转换为SpannableString
SpannableStringBuilder builder = new SpannableStringBuilder(fullText);
// 只处理HTML格式的内容避免重复处理
if (fullText.contains("<") && fullText.contains(">")) {
try {
// 如果是HTML格式先转换为Spanned对象
Spanned spannedText = Html.fromHtml(fullText);
builder = new SpannableStringBuilder(spannedText);
} catch (Exception e) {
Log.e(TAG, "Error parsing HTML: " + e.getMessage());
// 如果HTML解析失败使用纯文本
builder = new SpannableStringBuilder(fullText);
}
}
// 处理搜索高亮
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
builder.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
try {
mPattern = Pattern.compile(userQuery, Pattern.CASE_INSENSITIVE);
Matcher m = mPattern.matcher(builder.toString());
int start = 0;
while (m.find(start)) {
builder.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
} catch (Exception e) {
Log.e(TAG, "Error highlighting search query: " + e.getMessage());
}
}
// 只在有图片标记时才处理图片转换
if (fullText.contains("[IMAGE:")) {
try {
// 使用统一的图片处理逻辑
processImageConversion(builder, builder.toString());
} catch (Exception e) {
Log.e(TAG, "Error processing images: " + e.getMessage());
}
}
// 使用统一的图片处理逻辑
processImageConversion(builder, builder.toString());
return builder;
}
@ -1203,9 +1279,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
// 对于checklist模式将HTML转换为纯文本
String plainText = Html.fromHtml(item).toString();
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
edit.setText(getHighlightQueryResult(plainText, mUserQuery));
return view;
}
@ -1223,12 +1302,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
// 从富文本模式切换到checklist模式将HTML转换为纯文本
String plainText = Html.fromHtml(mNoteEditor.getText().toString()).toString();
switchToListMode(plainText);
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
// 从checklist模式切换回富文本模式将纯文本转换为HTML
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
@ -1253,7 +1335,26 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
// 处理富文本内容特殊处理ImageSpan
Editable editable = mNoteEditor.getEditableText();
if (editable != null && editable instanceof Spanned) {
Spanned spanned = (Spanned) editable;
// 检查是否包含ImageSpan
ImageSpan[] imageSpans = spanned.getSpans(0, spanned.length(), ImageSpan.class);
if (imageSpans.length > 0) {
// 对于包含图片的内容,使用原始文本格式存储,保留[IMAGE:path]标记
mWorkingNote.setWorkingText(spanned.toString());
} else {
// 对于不包含图片的内容转换为HTML格式存储
String htmlContent = Html.toHtml(spanned);
mWorkingNote.setWorkingText(htmlContent);
}
} else {
// 对于普通文本转换为HTML格式存储
String htmlContent = Html.toHtml(mNoteEditor.getText());
mWorkingNote.setWorkingText(htmlContent);
}
}
updateWordCount();
return hasChecked;
@ -1272,7 +1373,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
content = sb.toString();
} else {
content = mNoteEditor.getText().toString();
// 对于富文本模式,获取纯文本内容进行字数统计
Spanned spannedContent = mNoteEditor.getText();
content = spannedContent.toString();
}
// 过滤掉所有 [IMAGE:图片路径] 格式的图片标记,只统计纯文本字符数
@ -1487,6 +1590,108 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
openImagePicker();
}
/**
*
* @param isHighlight truefalse
*/
private void showColorPickerDialog(final boolean isHighlight) {
// 定义常用颜色数组
final int[] colors = new int[] {
Color.BLACK, Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW,
Color.CYAN, Color.MAGENTA, Color.GRAY, Color.DKGRAY, Color.LTGRAY,
Color.WHITE, Color.rgb(255, 165, 0), // Orange
Color.rgb(128, 0, 128), // Purple
Color.rgb(0, 128, 128), // Teal
Color.rgb(255, 192, 203) // Pink
};
// 创建颜色选择器对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(isHighlight ? R.string.menu_highlight : R.string.menu_text_color);
// 创建颜色选择网格布局
LinearLayout colorPickerLayout = new LinearLayout(this);
colorPickerLayout.setOrientation(LinearLayout.VERTICAL);
colorPickerLayout.setPadding(20, 20, 20, 20);
// 创建3行5列的颜色选择网格
int columns = 5;
int rows = (int) Math.ceil((double) colors.length / columns);
for (int i = 0; i < rows; i++) {
LinearLayout rowLayout = new LinearLayout(this);
rowLayout.setOrientation(LinearLayout.HORIZONTAL);
rowLayout.setWeightSum(columns);
for (int j = 0; j < columns; j++) {
final int index = i * columns + j;
if (index < colors.length) {
ImageView colorButton = new ImageView(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0, 60, 1);
params.setMargins(5, 5, 5, 5);
colorButton.setLayoutParams(params);
// 创建带边框的颜色选择按钮
// 内层是颜色填充,外层是黑色边框
GradientDrawable colorDrawable = new GradientDrawable();
colorDrawable.setColor(colors[index]);
GradientDrawable borderDrawable = new GradientDrawable();
borderDrawable.setColor(Color.TRANSPARENT);
borderDrawable.setStroke(2, Color.BLACK);
borderDrawable.setShape(GradientDrawable.RECTANGLE);
// 创建LayerDrawable来叠加两个Drawable
Drawable[] layers = {borderDrawable, colorDrawable};
LayerDrawable layerDrawable = new LayerDrawable(layers);
// 设置内边距,留出边框空间
int padding = 2;
layerDrawable.setLayerInset(1, padding, padding, padding, padding);
colorButton.setBackground(layerDrawable);
colorButton.setScaleType(ImageView.ScaleType.CENTER);
colorButton.setClickable(true);
colorButton.setFocusable(true);
// 添加点击事件
colorButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 应用选择的颜色
if (isHighlight) {
// 高亮模式
mNoteEditor.toggleHighlight(colors[index]);
} else {
// 文本颜色模式
mNoteEditor.toggleTextColor(colors[index]);
}
}
});
rowLayout.addView(colorButton);
}
}
colorPickerLayout.addView(rowLayout);
}
builder.setView(colorPickerLayout);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
// 创建对话框并设置为不可取消,只能通过取消按钮关闭
AlertDialog dialog = builder.create();
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
private void openImagePicker() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

@ -17,12 +17,19 @@
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
@ -303,4 +310,192 @@ public class NoteEditText extends EditText {
}
super.onCreateContextMenu(menu);
}
/**
*
*/
public void toggleBold() {
applyStyle(android.graphics.Typeface.BOLD);
}
/**
*
*/
public void toggleItalic() {
applyStyle(android.graphics.Typeface.ITALIC);
}
/**
* 线
*/
public void toggleUnderline() {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end) {
return;
}
Spannable str = getText();
UnderlineSpan[] spans = str.getSpans(start, end, UnderlineSpan.class);
if (spans.length > 0) {
// 如果已经有下划线,移除
for (UnderlineSpan span : spans) {
str.removeSpan(span);
}
} else {
// 如果没有下划线,添加
str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
setText(str);
setSelection(end);
}
/**
*
*/
public void toggleHighlight() {
toggleHighlight(Color.YELLOW);
}
/**
*
* @param color
*/
public void toggleHighlight(int color) {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end) {
return;
}
Spannable str = getText();
BackgroundColorSpan[] spans = str.getSpans(start, end, BackgroundColorSpan.class);
// 检查是否已经应用了相同颜色的高亮
boolean hasSameColor = false;
for (BackgroundColorSpan span : spans) {
if (span.getBackgroundColor() == color) {
hasSameColor = true;
break;
}
}
// 移除所有高亮
for (BackgroundColorSpan span : spans) {
str.removeSpan(span);
}
// 如果没有相同颜色的高亮,则添加新颜色的高亮
if (!hasSameColor) {
str.setSpan(new BackgroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
}
/**
*
* @param color
*/
public void toggleTextColor(int color) {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end) {
return;
}
Spannable str = getText();
ForegroundColorSpan[] spans = str.getSpans(start, end, ForegroundColorSpan.class);
// 检查是否已经应用了相同颜色
boolean hasSameColor = false;
for (ForegroundColorSpan span : spans) {
if (span.getForegroundColor() == color) {
hasSameColor = true;
break;
}
}
// 移除所有文本颜色
for (ForegroundColorSpan span : spans) {
str.removeSpan(span);
}
// 如果没有相同颜色,则添加新颜色
if (!hasSameColor) {
str.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
}
/**
*
*/
public void clearFormatting() {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end) {
return;
}
Spannable str = getText();
// 移除所有格式
StyleSpan[] styleSpans = str.getSpans(start, end, StyleSpan.class);
for (StyleSpan span : styleSpans) {
str.removeSpan(span);
}
UnderlineSpan[] underlineSpans = str.getSpans(start, end, UnderlineSpan.class);
for (UnderlineSpan span : underlineSpans) {
str.removeSpan(span);
}
BackgroundColorSpan[] backgroundSpans = str.getSpans(start, end, BackgroundColorSpan.class);
for (BackgroundColorSpan span : backgroundSpans) {
str.removeSpan(span);
}
ForegroundColorSpan[] foregroundSpans = str.getSpans(start, end, ForegroundColorSpan.class);
for (ForegroundColorSpan span : foregroundSpans) {
str.removeSpan(span);
}
setText(str);
setSelection(end);
}
/**
*
* @param style
*/
private void applyStyle(int style) {
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end) {
return;
}
Spannable str = getText();
StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class);
boolean hasStyle = false;
for (StyleSpan span : spans) {
if (span.getStyle() == style) {
str.removeSpan(span);
hasStyle = true;
}
}
if (!hasStyle) {
str.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
setText(str);
setSelection(end);
}
}

@ -50,6 +50,7 @@ public class NoteItemData {
NoteColumns.WIDGET_TYPE,
NoteColumns.IS_LOCKED,
NoteColumns.TITLE,
NoteColumns.TAGS,
};
/**
@ -116,6 +117,10 @@ public class NoteItemData {
*
*/
private static final int TITLE_COLUMN = 15;
/**
*
*/
private static final int TAGS_COLUMN = 16;
/**
* ID
@ -181,6 +186,10 @@ public class NoteItemData {
*
*/
private String mTitle;
/**
*
*/
private String mTags;
/**
*
*/
@ -236,6 +245,7 @@ public class NoteItemData {
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mIsLocked = (cursor.getInt(IS_LOCKED_COLUMN) > 0);
mTitle = cursor.getString(TITLE_COLUMN);
mTags = cursor.getString(TAGS_COLUMN);
mPhoneNumber = "";
// 如果是通话记录文件夹,获取电话号码和联系人姓名
@ -497,4 +507,12 @@ public class NoteItemData {
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
/**
*
* @return
*/
public String getTags() {
return mTags;
}
}

@ -291,6 +291,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mSearchEditText = (EditText) findViewById(R.id.search_edit_text);
mSearchClear = (ImageView) findViewById(R.id.search_clear);
mSearchButton = (ImageView) findViewById(R.id.search_button);
ImageView mSearchTagsButton = (ImageView) findViewById(R.id.search_tags_button);
// 设置标签选择按钮点击事件
mSearchTagsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showTagsSelectorDialog();
}
});
// 设置搜索文本变化监听,恢复实时搜索功能
mSearchEditText.addTextChangedListener(new TextWatcher() {
@ -392,6 +401,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
pinMenu.setOnMenuItemClickListener(this);
}
// 添加标签设置菜单项
MenuItem tagsMenu = menu.findItem(R.id.tags);
if (tagsMenu != null) {
tagsMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
mNotesListView.setLongClickable(false);
@ -483,6 +497,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
startQueryDestinationFolders();
} else if (item.getItemId() == R.id.pin) {
togglePinStatus();
} else if (item.getItemId() == R.id.tags) {
showTagsDialog();
} else {
return false;
}
@ -552,39 +568,93 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
};
// 当前选中的标签,用于筛选
private String mSelectedTag = "";
private void startAsyncNotesListQuery() {
String selection;
String[] selectionArgs;
// 构建基础查询条件
String baseSelection = "";
ArrayList<String> argsList = new ArrayList<>();
// 根据是否有搜索关键词构建不同的查询条件
if (!TextUtils.isEmpty(mSearchQuery)) {
// 有搜索关键词的情况
if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) {
// 根文件夹:需要将搜索条件应用到两个部分
selection = "((" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=? AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ?))"
+ " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0 AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ?)))";
selectionArgs = new String[] {
String.valueOf(mCurrentFolderId),
"%" + mSearchQuery + "%",
"%" + mSearchQuery + "%",
"%" + mSearchQuery + "%",
"%" + mSearchQuery + "%"
};
baseSelection = "((" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=? AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ? OR " + NoteColumns.TAGS + " LIKE ?))"
+ " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0 AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ? OR " + NoteColumns.TAGS + " LIKE ?)))";
argsList.add(String.valueOf(mCurrentFolderId));
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
} else {
// 普通文件夹
selection = NORMAL_SELECTION + " AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ?)";
selectionArgs = new String[] {
String.valueOf(mCurrentFolderId),
"%" + mSearchQuery + "%",
"%" + mSearchQuery + "%"
};
baseSelection = NORMAL_SELECTION + " AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ? OR " + NoteColumns.TAGS + " LIKE ?)";
argsList.add(String.valueOf(mCurrentFolderId));
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
}
} else {
// 没有搜索关键词的情况
selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION;
selectionArgs = new String[] { String.valueOf(mCurrentFolderId) };
baseSelection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION;
argsList.add(String.valueOf(mCurrentFolderId));
}
// 添加标签筛选条件
if (!TextUtils.isEmpty(mSelectedTag)) {
if (mCurrentFolderId == Notes.ID_ROOT_FOLDER) {
// 根文件夹需要特殊处理
String tagCondition = " AND " + NoteColumns.TAGS + " LIKE ?";
// 根据是否有搜索关键词构建不同的标签筛选条件
if (!TextUtils.isEmpty(mSearchQuery)) {
// 有搜索关键词的情况
baseSelection = "((" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=? AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ? OR " + NoteColumns.TAGS + " LIKE ?)" + tagCondition + ")"
+ " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0 AND (" + NoteColumns.TITLE + " LIKE ? OR " + NoteColumns.SNIPPET + " LIKE ? OR " + NoteColumns.TAGS + " LIKE ?)" + tagCondition + "))";
// 重新构建参数列表
argsList.clear();
argsList.add(String.valueOf(mCurrentFolderId));
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSelectedTag + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSearchQuery + "%");
argsList.add("%" + mSelectedTag + "%");
} else {
// 没有搜索关键词的情况
baseSelection = "((" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?" + tagCondition + ")"
+ " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0" + tagCondition + "))";
// 重新构建参数列表
argsList.clear();
argsList.add(String.valueOf(mCurrentFolderId));
argsList.add("%" + mSelectedTag + "%");
argsList.add("%" + mSelectedTag + "%");
}
} else {
// 普通文件夹
if (baseSelection.contains("AND")) {
baseSelection += " AND " + NoteColumns.TAGS + " LIKE ?";
} else {
baseSelection += " WHERE " + NoteColumns.TAGS + " LIKE ?";
}
argsList.add("%" + mSelectedTag + "%");
}
}
selection = baseSelection;
selectionArgs = argsList.toArray(new String[argsList.size()]);
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs,
NoteColumns.IS_PINNED + " DESC," + NoteColumns.PIN_PRIORITY + " DESC," +
@ -1107,4 +1177,181 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
return false;
}
/**
*
*/
private void showTagsDialog() {
// 查询所有唯一标签
Cursor cursor = mContentResolver.query(
Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.TAGS },
NoteColumns.TAGS + " <> '' AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
null,
NoteColumns.TAGS + " ASC"
);
// 提取所有唯一标签
HashSet<String> tagSet = new HashSet<>();
if (cursor != null) {
while (cursor.moveToNext()) {
String tags = cursor.getString(0);
if (tags != null && !tags.isEmpty()) {
// 处理多标签情况,使用逗号分隔
String[] tagArray = tags.split(",");
for (String tag : tagArray) {
tag = tag.trim();
if (!tag.isEmpty()) {
tagSet.add(tag);
}
}
}
}
cursor.close();
}
// 创建对话框
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etTags = (EditText) view.findViewById(R.id.et_foler_name);
etTags.setHint(R.string.hint_tags);
// 设置当前标签
if (mFocusNoteDataItem != null && mFocusNoteDataItem.getTags() != null) {
etTags.setText(mFocusNoteDataItem.getTags());
}
builder.setTitle(getString(R.string.menu_tags));
builder.setView(view);
// 如果有现有标签,添加标签选择按钮
if (!tagSet.isEmpty()) {
final ArrayList<String> tagList = new ArrayList<>(tagSet);
builder.setNeutralButton("选择标签", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 显示标签选择对话框
AlertDialog.Builder tagSelectorBuilder = new AlertDialog.Builder(NotesListActivity.this);
tagSelectorBuilder.setTitle("选择标签");
tagSelectorBuilder.setItems(tagList.toArray(new String[0]), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String selectedTag = tagList.get(which);
String currentTags = etTags.getText().toString().trim();
// 检查标签是否已存在
if (currentTags.isEmpty()) {
etTags.setText(selectedTag);
} else {
// 检查标签是否已存在
String[] existingTags = currentTags.split(",");
boolean tagExists = false;
for (String tag : existingTags) {
if (tag.trim().equals(selectedTag)) {
tagExists = true;
break;
}
}
if (!tagExists) {
etTags.setText(currentTags + ", " + selectedTag);
}
}
}
});
tagSelectorBuilder.show();
}
});
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etTags);
}
});
final Dialog dialog = builder.show();
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etTags);
String tags = etTags.getText().toString().trim();
// 更新标签
ContentValues values = new ContentValues();
values.put(NoteColumns.TAGS, tags);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
mContentResolver.update(Notes.CONTENT_NOTE_URI, values,
NoteColumns.ID + "=?",
new String[] { String.valueOf(mFocusNoteDataItem.getId()) });
dialog.dismiss();
mModeCallBack.finishActionMode();
startAsyncNotesListQuery();
}
});
}
/**
*
*/
private void showTagsSelectorDialog() {
// 查询所有唯一标签
Cursor cursor = mContentResolver.query(
Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.TAGS },
NoteColumns.TAGS + " <> '' AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
null,
NoteColumns.TAGS + " ASC"
);
if (cursor == null) {
Toast.makeText(this, getString(R.string.error_sdcard_unmounted), Toast.LENGTH_SHORT).show();
return;
}
// 提取所有唯一标签
HashSet<String> tagSet = new HashSet<>();
while (cursor.moveToNext()) {
String tags = cursor.getString(0);
if (tags != null && !tags.isEmpty()) {
// 处理多标签情况,使用逗号分隔
String[] tagArray = tags.split(",");
for (String tag : tagArray) {
tag = tag.trim();
if (!tag.isEmpty()) {
tagSet.add(tag);
}
}
}
}
cursor.close();
// 将标签转换为数组
final ArrayList<String> tagList = new ArrayList<>(tagSet);
tagList.add(0, "全部"); // 添加"全部"选项
// 创建对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.menu_tags));
builder.setItems(tagList.toArray(new String[0]), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 选择了"全部",清除标签筛选
mSelectedTag = "";
} else {
// 选择了某个标签
mSelectedTag = tagList.get(which);
}
// 刷新便签列表
startAsyncNotesListQuery();
}
});
builder.show();
}
}

@ -136,20 +136,22 @@ public class NotesListItem extends LinearLayout {
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
// 普通笔记
String title = data.getTitle();
if (title != null && !title.isEmpty()) {
// 如果有标题,显示标题
mTitle.setText(title);
} else {
// 如果没有标题显示正文前20个字符
String snippet = data.getSnippet();
if (snippet != null && snippet.length() > 20) {
mTitle.setText(snippet.substring(0, 20) + "...");
// 普通笔记
String title = data.getTitle();
if (title != null && !title.isEmpty()) {
// 如果有标题,显示标题
mTitle.setText(title);
} else {
mTitle.setText(snippet);
// 如果没有标题显示正文前20个字符
String snippet = data.getSnippet();
// 将HTML转换为纯文本移除HTML标签
String plainText = android.text.Html.fromHtml(snippet).toString();
if (plainText != null && plainText.length() > 20) {
mTitle.setText(plainText.substring(0, 20) + "...");
} else {
mTitle.setText(plainText);
}
}
}
// 设置提醒图标
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#000000" android:pathData="M6,5v14h2V5H6zm5,0v14h2V5h-2zm5,0v14h2V5h-2z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#000000" android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
<path android:fillColor="#000000" android:pathData="M3,4h18v2H3V4Z"/>
<path android:fillColor="#000000" android:pathData="M3,18h18v2H3V18Z"/>
<path android:fillColor="#000000" android:pathData="M12,10h8v2h-8V10Z"/>
<path android:fillColor="#000000" android:pathData="M12,14h8v2h-8V14Z"/>
<path android:fillColor="#000000" android:pathData="M3,10h8v2H3V10Z"/>
<path android:fillColor="#000000" android:pathData="M3,14h8v2H3V14Z"/>
</vector>

@ -0,0 +1,4 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFF00" android:pathData="M6,5h12v14H6V5z"/>
<path android:fillColor="#000000" android:pathData="M8,7h8v10H8V7z"/>
</vector>

@ -0,0 +1,4 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#000000" android:pathData="M6,5v14h2V5H6zm5,0v14h2V5h-2zm5,0v14h2V5h-2z"/>
<path android:fillColor="#FF0000" android:pathData="M12,3v2h8V3H12zm0,18h8v-2h-8v2z"/>
</vector>

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#000000" android:pathData="M10,5v14l-5-7l5-7z"/>
</vector>

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#000000" android:pathData="M6,5h12v2H6V5zm0,10h12v2H6V15z"/>
</vector>

@ -143,6 +143,75 @@
android:background="#E0E0E0" />
</LinearLayout>
<!-- 富文本格式化工具栏 -->
<LinearLayout
android:id="@+id/rich_text_toolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="4dp"
android:background="#F5F5F5">
<!-- 加粗按钮 -->
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color"
android:padding="12dp"
android:contentDescription="@string/menu_bold"
android:src="@drawable/ic_format_bold" />
<!-- 斜体按钮 -->
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_btn_set_color"
android:padding="12dp"
android:contentDescription="@string/menu_italic"
android:src="@drawable/ic_format_italic" />
<!-- 下划线按钮 -->
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_btn_set_color"
android:padding="12dp"
android:contentDescription="@string/menu_underline"
android:src="@drawable/ic_format_underlined" />
<!-- 文本颜色按钮 -->
<ImageButton
android:id="@+id/btn_text_color"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_btn_set_color"
android:padding="12dp"
android:contentDescription="@string/menu_text_color"
android:src="@drawable/ic_format_color_text" />
<!-- 高亮按钮 -->
<ImageButton
android:id="@+id/btn_highlight"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_btn_set_color"
android:padding="12dp"
android:contentDescription="@string/menu_highlight"
android:src="@drawable/ic_format_color_fill" />
</LinearLayout>
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="fill_parent"

@ -75,6 +75,17 @@
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:tint="#808080"
android:visibility="gone" />
<!-- 标签选择按钮 -->
<ImageView
android:id="@+id/search_tags_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_more"
android:tint="#808080"
android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
android:contentDescription="标签" />
<ImageView
android:id="@+id/search_button"
android:layout_width="24dp"

@ -34,4 +34,10 @@
android:title="@string/menu_delete"
android:icon="@drawable/menu_delete"
android:showAsAction="always|withText" />
<item
android:id="@+id/tags"
android:title="@string/menu_tags"
android:icon="@android:drawable/ic_menu_edit"
android:showAsAction="always|withText" />
</menu>

@ -50,6 +50,8 @@
<string name="menu_move">Move to folder</string>
<string name="menu_pin">Pin</string>
<string name="menu_unpin">Unpin</string>
<string name="menu_tags">Tags</string>
<string name="hint_tags">Enter tags</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>
@ -206,6 +208,10 @@
<string name="note_title_warning">Title has reached maximum length</string>
<string name="menu_bold">Bold</string>
<string name="menu_italic">Italic</string>
<string name="menu_underline">Underline</string>
<string name="menu_highlight">Highlight</string>
<string name="menu_text_color">Text Color</string>
<string name="menu_clear_format">Clear Format</string>
<string name="menu_normal">Normal</string>
<string name="menu_insert_image">Insert Image</string>
<string name="menu_undo">Undo</string>

Loading…
Cancel
Save