From 54dca572fd21f5b5b89e76b13c2c7bc48ea3eeec Mon Sep 17 00:00:00 2001 From: whale Date: Sat, 24 Jan 2026 08:24:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=BE=E7=89=87=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/AndroidManifest.xml | 1 + .../java/net/micode/notes/data/Notes.java | 6 + .../net/micode/notes/tool/ImageHelper.java | 130 +++++++ .../notes/tool/ImageStorageManager.java | 110 ++++++ .../micode/notes/tool/NoteImageHelper.java | 152 ++++++++ .../micode/notes/tool/SimpleImageHelper.java | 125 +++++++ .../net/micode/notes/ui/NoteEditActivity.java | 345 +++++++++++++++++- src/main/res/drawable/bg_btn_insert_image.xml | 47 +++ src/main/res/drawable/ic_insert_image.xml | 21 ++ src/main/res/layout/note_edit.xml | 17 +- src/main/res/values/strings.xml | 13 +- .../notes/search/SearchHistoryTest.java | 194 ---------- .../notes/search/SearchManagerTest.java | 146 -------- .../micode/notes/search/SearchResultTest.java | 137 ------- 14 files changed, 957 insertions(+), 487 deletions(-) create mode 100644 src/main/java/net/micode/notes/tool/ImageHelper.java create mode 100644 src/main/java/net/micode/notes/tool/ImageStorageManager.java create mode 100644 src/main/java/net/micode/notes/tool/NoteImageHelper.java create mode 100644 src/main/java/net/micode/notes/tool/SimpleImageHelper.java create mode 100644 src/main/res/drawable/bg_btn_insert_image.xml create mode 100644 src/main/res/drawable/ic_insert_image.xml delete mode 100644 src/main/src/test/java/net/micode/notes/search/SearchHistoryTest.java delete mode 100644 src/main/src/test/java/net/micode/notes/search/SearchManagerTest.java delete mode 100644 src/main/src/test/java/net/micode/notes/search/SearchResultTest.java diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index fe700b6..2443da8 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + diff --git a/src/main/java/net/micode/notes/data/Notes.java b/src/main/java/net/micode/notes/data/Notes.java index 69927ad..901fd1f 100644 --- a/src/main/java/net/micode/notes/data/Notes.java +++ b/src/main/java/net/micode/notes/data/Notes.java @@ -349,6 +349,12 @@ public class Notes { */ public static final String MODE = DATA1; + /** + * 图片路径,用于存储插入的图片文件路径 + * 映射到Data表的DATA3字段 + */ + public static final String IMAGE_PATH = DATA3; + /** * Checklist模式常量 */ diff --git a/src/main/java/net/micode/notes/tool/ImageHelper.java b/src/main/java/net/micode/notes/tool/ImageHelper.java new file mode 100644 index 0000000..2286f1c --- /dev/null +++ b/src/main/java/net/micode/notes/tool/ImageHelper.java @@ -0,0 +1,130 @@ +package net.micode.notes.tool; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ImageSpan; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class ImageHelper { + private static final String TAG = "ImageHelper"; + private static final String IMAGE_DIR = "note_images"; + + private Context mContext; + + public ImageHelper(Context context) { + mContext = context; + } + + private File getImageDir() { + File dir = new File(mContext.getFilesDir(), IMAGE_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + + public String saveImage(Bitmap bitmap, long noteId) { + try { + File imageDir = getImageDir(); + String fileName = "note_" + noteId + "_" + System.currentTimeMillis() + ".jpg"; + File imageFile = new File(imageDir, fileName); + + FileOutputStream fos = new FileOutputStream(imageFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + fos.flush(); + fos.close(); + + Log.d(TAG, "Image saved to: " + imageFile.getAbsolutePath()); + return imageFile.getAbsolutePath(); + } catch (IOException e) { + Log.e(TAG, "Failed to save image", e); + return null; + } + } + + public String saveBitmapToFile(Bitmap bitmap) { + try { + File imageDir = getImageDir(); + String fileName = System.currentTimeMillis() + ".jpg"; + File imageFile = new File(imageDir, fileName); + + FileOutputStream fos = new FileOutputStream(imageFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + fos.flush(); + fos.close(); + + Log.d(TAG, "Image saved to: " + imageFile.getAbsolutePath()); + return imageFile.getAbsolutePath(); + } catch (IOException e) { + Log.e(TAG, "Failed to save image", e); + return null; + } + } + + public Bitmap loadImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return null; + } + + try { + File imageFile = new File(imagePath); + if (!imageFile.exists()) { + Log.w(TAG, "Image file not found: " + imagePath); + return null; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + + Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + if (bitmap != null) { + Log.d(TAG, "Image loaded from: " + imagePath); + } + return bitmap; + } catch (Exception e) { + Log.e(TAG, "Failed to load image: " + imagePath, e); + return null; + } + } + + public boolean deleteImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return false; + } + + try { + File imageFile = new File(imagePath); + if (imageFile.exists()) { + boolean deleted = imageFile.delete(); + if (deleted) { + Log.d(TAG, "Image deleted: " + imagePath); + } + return deleted; + } + return false; + } catch (Exception e) { + Log.e(TAG, "Failed to delete image: " + imagePath, e); + return false; + } + } + + public void deleteNoteImages(long noteId) { + File imageDir = getImageDir(); + File[] files = imageDir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.getName().startsWith("note_" + noteId + "_")) { + file.delete(); + Log.d(TAG, "Deleted image: " + file.getName()); + } + } + } + } +} diff --git a/src/main/java/net/micode/notes/tool/ImageStorageManager.java b/src/main/java/net/micode/notes/tool/ImageStorageManager.java new file mode 100644 index 0000000..489f613 --- /dev/null +++ b/src/main/java/net/micode/notes/tool/ImageStorageManager.java @@ -0,0 +1,110 @@ +package net.micode.notes.tool; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class ImageStorageManager { + private static final String TAG = "ImageStorageManager"; + private static final String IMAGE_DIR = "note_images"; + private static final int MAX_IMAGE_WIDTH = 800; + private static final int MAX_IMAGE_HEIGHT = 800; + + private Context mContext; + + public ImageStorageManager(Context context) { + mContext = context; + } + + private File getImageDir() { + File dir = new File(mContext.getFilesDir(), IMAGE_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + + public String saveImage(Bitmap bitmap, long noteId) { + try { + File imageDir = getImageDir(); + String fileName = "note_" + noteId + "_" + System.currentTimeMillis() + ".jpg"; + File imageFile = new File(imageDir, fileName); + + FileOutputStream fos = new FileOutputStream(imageFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + fos.flush(); + fos.close(); + + Log.d(TAG, "Image saved to: " + imageFile.getAbsolutePath()); + return imageFile.getAbsolutePath(); + } catch (IOException e) { + Log.e(TAG, "Failed to save image", e); + return null; + } + } + + public Bitmap loadImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return null; + } + + try { + File imageFile = new File(imagePath); + if (!imageFile.exists()) { + Log.w(TAG, "Image file not found: " + imagePath); + return null; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + + Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + if (bitmap != null) { + Log.d(TAG, "Image loaded from: " + imagePath); + } + return bitmap; + } catch (Exception e) { + Log.e(TAG, "Failed to load image: " + imagePath, e); + return null; + } + } + + public boolean deleteImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return false; + } + + try { + File imageFile = new File(imagePath); + if (imageFile.exists()) { + boolean deleted = imageFile.delete(); + if (deleted) { + Log.d(TAG, "Image deleted: " + imagePath); + } + return deleted; + } + return false; + } catch (Exception e) { + Log.e(TAG, "Failed to delete image: " + imagePath, e); + return false; + } + } + + public void deleteNoteImages(long noteId) { + File imageDir = getImageDir(); + File[] files = imageDir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.getName().startsWith("note_" + noteId + "_")) { + file.delete(); + Log.d(TAG, "Deleted image: " + file.getName()); + } + } + } + } +} diff --git a/src/main/java/net/micode/notes/tool/NoteImageHelper.java b/src/main/java/net/micode/notes/tool/NoteImageHelper.java new file mode 100644 index 0000000..975a648 --- /dev/null +++ b/src/main/java/net/micode/notes/tool/NoteImageHelper.java @@ -0,0 +1,152 @@ +package net.micode.notes.tool; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ImageSpan; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NoteImageHelper { + private static final String TAG = "NoteImageHelper"; + private static final String IMAGE_DIR = "note_images"; + private static final String IMAGE_MARKER_START = "[IMAGE:"; + private static final String IMAGE_MARKER_END = "]"; + + private Context mContext; + + public NoteImageHelper(Context context) { + mContext = context; + } + + private File getImageDir() { + File dir = new File(mContext.getFilesDir(), IMAGE_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + + public String saveImage(Bitmap bitmap, long noteId) { + try { + File imageDir = getImageDir(); + String fileName = "note_" + noteId + "_" + System.currentTimeMillis() + ".jpg"; + File imageFile = new File(imageDir, fileName); + + FileOutputStream fos = new FileOutputStream(imageFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + fos.flush(); + fos.close(); + + Log.d(TAG, "Image saved to: " + imageFile.getAbsolutePath()); + return imageFile.getAbsolutePath(); + } catch (IOException e) { + Log.e(TAG, "Failed to save image", e); + return null; + } + } + + public Bitmap loadImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return null; + } + + try { + File imageFile = new File(imagePath); + if (!imageFile.exists()) { + Log.w(TAG, "Image file not found: " + imagePath); + return null; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + + Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + if (bitmap != null) { + Log.d(TAG, "Image loaded from: " + imagePath); + } + return bitmap; + } catch (Exception e) { + Log.e(TAG, "Failed to load image: " + imagePath, e); + return null; + } + } + + public Spannable insertImageMarker(Spannable original, String imagePath) { + String text = original.toString(); + String marker = IMAGE_MARKER_START + imagePath + IMAGE_MARKER_END; + String newText = text + marker; + + SpannableString result = new SpannableString(newText); + + return result; + } + + public Spannable processImages(Spannable spannable) { + String text = spannable.toString(); + + Pattern imagePattern = Pattern.compile(Pattern.quote(IMAGE_MARKER_START) + "([^\\]]+)" + Pattern.quote(IMAGE_MARKER_END)); + Matcher matcher = imagePattern.matcher(text); + + SpannableString result = new SpannableString(text); + + while (matcher.find()) { + String imagePath = matcher.group(1); + Bitmap bitmap = loadImage(imagePath); + + if (bitmap == null) { + Log.w(TAG, "Failed to load image: " + imagePath); + continue; + } + + int start = matcher.start(); + int end = matcher.end(); + + ImageSpan imageSpan = new ImageSpan(mContext, bitmap); + result.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return result; + } + + public boolean deleteImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return false; + } + + try { + File imageFile = new File(imagePath); + if (imageFile.exists()) { + boolean deleted = imageFile.delete(); + if (deleted) { + Log.d(TAG, "Image deleted: " + imagePath); + } + return deleted; + } + return false; + } catch (Exception e) { + Log.e(TAG, "Failed to delete image: " + imagePath, e); + return false; + } + } + + public void deleteNoteImages(long noteId) { + File imageDir = getImageDir(); + File[] files = imageDir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.getName().startsWith("note_" + noteId + "_")) { + file.delete(); + Log.d(TAG, "Deleted image: " + file.getName()); + } + } + } + } +} diff --git a/src/main/java/net/micode/notes/tool/SimpleImageHelper.java b/src/main/java/net/micode/notes/tool/SimpleImageHelper.java new file mode 100644 index 0000000..a56dee6 --- /dev/null +++ b/src/main/java/net/micode/notes/tool/SimpleImageHelper.java @@ -0,0 +1,125 @@ +package net.micode.notes.tool; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ImageSpan; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class SimpleImageHelper { + private static final String TAG = "SimpleImageHelper"; + private static final String IMAGE_DIR = "note_images"; + + private Context mContext; + + public SimpleImageHelper(Context context) { + mContext = context; + } + + private File getImageDir() { + File dir = new File(mContext.getFilesDir(), IMAGE_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + + public String saveImage(Bitmap bitmap, long noteId) { + try { + File imageDir = getImageDir(); + String fileName = "note_" + noteId + "_" + System.currentTimeMillis() + ".jpg"; + File imageFile = new File(imageDir, fileName); + + FileOutputStream fos = new FileOutputStream(imageFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); + fos.flush(); + fos.close(); + + Log.d(TAG, "Image saved to: " + imageFile.getAbsolutePath()); + return imageFile.getAbsolutePath(); + } catch (IOException e) { + Log.e(TAG, "Failed to save image", e); + return null; + } + } + + public Bitmap loadImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return null; + } + + try { + File imageFile = new File(imagePath); + if (!imageFile.exists()) { + Log.w(TAG, "Image file not found: " + imagePath); + return null; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + + Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + if (bitmap != null) { + Log.d(TAG, "Image loaded from: " + imagePath); + } + return bitmap; + } catch (Exception e) { + Log.e(TAG, "Failed to load image: " + imagePath, e); + return null; + } + } + + public Spannable insertImage(Spannable original, Bitmap bitmap) { + String text = original.toString(); + String imagePlaceholder = "\uFFFC"; + String newText = text + imagePlaceholder; + + SpannableString result = new SpannableString(newText); + + int start = text.length(); + ImageSpan imageSpan = new ImageSpan(mContext, bitmap); + result.setSpan(imageSpan, start, start + imagePlaceholder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + return result; + } + + public boolean deleteImage(String imagePath) { + if (imagePath == null || imagePath.isEmpty()) { + return false; + } + + try { + File imageFile = new File(imagePath); + if (imageFile.exists()) { + boolean deleted = imageFile.delete(); + if (deleted) { + Log.d(TAG, "Image deleted: " + imagePath); + } + return deleted; + } + return false; + } catch (Exception e) { + Log.e(TAG, "Failed to delete image: " + imagePath, e); + return false; + } + } + + public void deleteNoteImages(long noteId) { + File imageDir = getImageDir(); + File[] files = imageDir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.getName().startsWith("note_" + noteId + "_")) { + file.delete(); + Log.d(TAG, "Deleted image: " + file.getName()); + } + } + } + } +} diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java index e6805e3..8149d67 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -28,15 +28,21 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Paint; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextWatcher; import android.text.Editable; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; +import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -49,20 +55,27 @@ 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.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import java.io.InputStream; + +import android.database.Cursor; + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.model.WorkingNote; import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ImageStorageManager; import net.micode.notes.tool.LockPasswordUtils; import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser.TextAppearanceResources; +import net.micode.notes.tool.ImageHelper; import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; @@ -98,6 +111,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, public ImageView ivAlertIcon; // 提醒图标 public TextView tvAlertDate; // 提醒日期显示 public ImageView ibSetBgColor; // 设置背景色按钮 + public ImageButton ibInsertImage; // 插入图片按钮 public TextView tvTitleHint; // 标题提示文字 public EditText etTitle; // 标题输入框 public TextView tvTitleCount; // 字符数提示 @@ -167,6 +181,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好键 private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷方式标题最大长度 + private static final int PHOTO_REQUEST = 1001; // 选择图片请求码 + private static final int REQUEST_CODE_READ_EXTERNAL_STORAGE = 1002; // 读取外部存储权限请求码 + public static final String TAG_CHECKED = String.valueOf('\u221A'); // 已勾选标记 public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未勾选标记 @@ -343,6 +360,23 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + // 添加全局布局监听器,确保在视图布局完成后再处理图片显示 + mNoteEditor.getViewTreeObserver().addOnGlobalLayoutListener( + new android.view.ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + mNoteEditor.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + String content = mNoteEditor.getText().toString(); + if (content.contains("[IMAGE:")) { + android.text.Spannable spannable = convertToImage(content); + mNoteEditor.setText(spannable); + } + } + }); + // 加载标题内容 String title = mWorkingNote.getTitle(); if (title != null && !title.isEmpty()) { @@ -373,7 +407,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mNoteHeaderHolder.titleArea.setBackgroundResource(mWorkingNote.getTitleBgResId()); - + int textColor = ResourceParser.NoteBgResources.getNoteTextColor(mWorkingNote.getBgColorId()); mNoteHeaderHolder.tvWordCountLabel.setTextColor(getResources().getColor(textColor)); mNoteHeaderHolder.tvWordCount.setTextColor(getResources().getColor(textColor)); @@ -521,6 +555,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + mNoteHeaderHolder.ibInsertImage = (ImageButton) findViewById(R.id.add_img_btn); + mNoteHeaderHolder.ibInsertImage.setOnClickListener(this); mNoteHeaderHolder.tvTitleHint = (TextView) findViewById(R.id.tv_title_hint); mNoteHeaderHolder.etTitle = (EditText) findViewById(R.id.et_title); mNoteHeaderHolder.etTitle.addTextChangedListener(new TextWatcher() { @@ -669,6 +705,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } mFontSizeSelector.setVisibility(View.GONE); + } else if (id == R.id.add_img_btn) { + insertImage(); } } @@ -719,7 +757,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteHeaderHolder.titleArea.setBackgroundResource(mWorkingNote.getTitleBgResId()); - + int textColor = ResourceParser.NoteBgResources.getNoteTextColor(mWorkingNote.getBgColorId()); mNoteHeaderHolder.tvWordCountLabel.setTextColor(getResources().getColor(textColor)); mNoteHeaderHolder.tvWordCount.setTextColor(getResources().getColor(textColor)); @@ -1070,20 +1108,22 @@ public class NoteEditActivity extends Activity implements OnClickListener, } private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + SpannableStringBuilder builder = new SpannableStringBuilder(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { mPattern = Pattern.compile(userQuery); Matcher m = mPattern.matcher(fullText); int start = 0; while (m.find(start)) { - spannable.setSpan( + builder.setSpan( new BackgroundColorSpan(this.getResources().getColor( R.color.user_query_highlight)), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); start = m.end(); } } - return spannable; + // 使用统一的图片处理逻辑 + processImageConversion(builder, builder.toString()); + return builder; } private View getListItem(String item, int index) { @@ -1182,7 +1222,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { content = mNoteEditor.getText().toString(); } - + int wordCount = content != null ? content.length() : 0; mNoteHeaderHolder.tvWordCount.setText(String.valueOf(wordCount)); @@ -1262,4 +1302,297 @@ public class NoteEditActivity extends Activity implements OnClickListener, private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } + + private void insertImage() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M + && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) { + if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) + != android.content.pm.PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, + REQUEST_CODE_READ_EXTERNAL_STORAGE); + return; + } + } + openImagePicker(); + } + + private void openImagePicker() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + + if (intent.resolveActivity(getPackageManager()) != null) { + startActivityForResult(intent, PHOTO_REQUEST); + } else { + showToast(R.string.error_no_image_picker); + } + } + + private String getPath(Uri uri) { + if (uri == null) { + return null; + } + + String selection = null; + String[] selectionArgs = null; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(uri, null, null); + } else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + } else { + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = { android.provider.MediaStore.Images.Media.DATA }; + Cursor cursor = null; + try { + cursor = getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int column_index = cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media.DATA); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + return null; + } + + private String getDataColumn(Uri uri, String selection, String[] selectionArgs) { + String column = "_data"; + String[] projection = { column }; + + Cursor cursor = null; + try { + cursor = getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CODE_READ_EXTERNAL_STORAGE) { + if (grantResults.length > 0 && grantResults[0] == android.content.pm.PackageManager.PERMISSION_GRANTED) { + openImagePicker(); + } else { + showToast(R.string.error_permission_denied); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == PHOTO_REQUEST) { + if (resultCode == RESULT_OK && data != null) { + try { + Uri selectedImageUri = data.getData(); + if (selectedImageUri == null) { + showToast(R.string.error_no_image_selected); + return; + } + + String imagePath = getPath(selectedImageUri); + if (imagePath == null) { + showToast(R.string.error_insert_image_failed); + Log.e(TAG, "Failed to get image path from Uri"); + return; + } + + InputStream inputStream = getContentResolver().openInputStream(selectedImageUri); + if (inputStream == null) { + showToast(R.string.error_insert_image_failed); + Log.e(TAG, "Failed to open input stream for image"); + return; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(inputStream, null, options); + inputStream.close(); + + int imageWidth = options.outWidth; + int imageHeight = options.outHeight; + + int maxWidth = 800; + int maxHeight = 800; + + int sampleSize = 1; + if (imageWidth > maxWidth || imageHeight > maxHeight) { + int widthRatio = Math.round((float) imageWidth / maxWidth); + int heightRatio = Math.round((float) imageHeight / maxHeight); + sampleSize = Math.min(widthRatio, heightRatio); + } + + options = new BitmapFactory.Options(); + options.inSampleSize = sampleSize; + options.inPreferredConfig = Bitmap.Config.RGB_565; + + inputStream = getContentResolver().openInputStream(selectedImageUri); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options); + inputStream.close(); + + if (bitmap == null) { + showToast(R.string.error_image_format_not_supported); + Log.e(TAG, "Failed to decode image, format may not be supported"); + return; + } + + insertImageToNote(bitmap); + showToast(R.string.info_image_inserted); + + } catch (OutOfMemoryError e) { + Log.e(TAG, "Out of memory when inserting image", e); + showToast(R.string.error_out_of_memory); + } catch (SecurityException e) { + Log.e(TAG, "Security exception when accessing image", e); + showToast(R.string.error_permission_denied); + } catch (Exception e) { + Log.e(TAG, "Insert image error", e); + showToast(R.string.error_insert_image_failed); + } + } else if (resultCode == RESULT_CANCELED) { + Log.d(TAG, "Image selection cancelled"); + } + } + } + + private Spannable convertToImage(String text) { + if (text == null || text.isEmpty()) { + return new SpannableString(""); + } + + SpannableStringBuilder builder = new SpannableStringBuilder(text); + processImageConversion(builder, text); + return builder; + } + + private void processImageConversion(SpannableStringBuilder builder, String text) { + ImageHelper imageHelper = new ImageHelper(this); + int startIndex = 0; + + while (true) { + int imageStart = text.indexOf("[IMAGE:", startIndex); + if (imageStart == -1) { + break; + } + + int imageEnd = text.indexOf("]", imageStart); + if (imageEnd == -1) { + break; + } + + String imagePath = text.substring(imageStart + 7, imageEnd); + Bitmap bitmap = imageHelper.loadImage(imagePath); + + if (bitmap == null) { + Log.w(TAG, "Failed to load image: " + imagePath); + startIndex = imageEnd + 1; + continue; + } + + Bitmap scaledBitmap = scaleBitmapToFitView(bitmap, mNoteEditor.getWidth()); + if (scaledBitmap != null) { + ImageSpan imageSpan = new ImageSpan(this, scaledBitmap); + builder.setSpan(imageSpan, imageStart, imageEnd + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + startIndex = imageEnd + 1; + } + } + + private Bitmap scaleBitmapToFitView(Bitmap bitmap, int viewWidth) { + if (bitmap == null || viewWidth <= 0) { + return bitmap; + } + + int bitmapWidth = bitmap.getWidth(); + int bitmapHeight = bitmap.getHeight(); + + if (bitmapWidth <= viewWidth) { + return bitmap; + } + + float scale = (float) viewWidth / bitmapWidth; + int newWidth = viewWidth; + int newHeight = (int) (bitmapHeight * scale); + + try { + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + } catch (OutOfMemoryError e) { + Log.e(TAG, "Out of memory when scaling bitmap", e); + return bitmap; + } + } + + private void insertImageToNote(Bitmap bitmap) { + int maxWidth = 800; + int maxHeight = 800; + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + float scale = Math.min((float) maxWidth / width, (float) maxHeight / height); + + Bitmap scaledBitmap = null; + try { + scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) (width * scale), (int) (height * scale), true); + + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + + ImageHelper imageHelper = new ImageHelper(this); + String imagePath = imageHelper.saveBitmapToFile(scaledBitmap); + + if (imagePath == null) { + showToast(R.string.error_insert_image_failed); + Log.e(TAG, "Failed to save image to file"); + return; + } + + int cursorStart = mNoteEditor.getSelectionStart(); + int cursorEnd = mNoteEditor.getSelectionEnd(); + + Editable editable = mNoteEditor.getEditableText(); + if (editable == null) { + editable = new SpannableStringBuilder(mNoteEditor.getText()); + } + + String imageMarker = "[IMAGE:" + imagePath + "]"; + editable.replace(cursorStart, cursorEnd, imageMarker); + + ImageSpan imageSpan = new ImageSpan(this, scaledBitmap); + editable.setSpan(imageSpan, cursorStart, cursorStart + imageMarker.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + mNoteEditor.setSelection(cursorStart + imageMarker.length()); + + // 更新WorkingNote的内容,确保图片被保存到数据库 + getWorkingText(); + updateWordCount(); + + Log.d(TAG, "Image inserted with path: " + imagePath); + } catch (OutOfMemoryError e) { + Log.e(TAG, "Out of memory when scaling image", e); + if (scaledBitmap != null && !scaledBitmap.isRecycled()) { + scaledBitmap.recycle(); + } + showToast(R.string.error_out_of_memory); + } + } } diff --git a/src/main/res/drawable/bg_btn_insert_image.xml b/src/main/res/drawable/bg_btn_insert_image.xml new file mode 100644 index 0000000..2c1b81c --- /dev/null +++ b/src/main/res/drawable/bg_btn_insert_image.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/res/drawable/ic_insert_image.xml b/src/main/res/drawable/ic_insert_image.xml new file mode 100644 index 0000000..ed6dc49 --- /dev/null +++ b/src/main/res/drawable/ic_insert_image.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/src/main/res/layout/note_edit.xml b/src/main/res/layout/note_edit.xml index 26c908f..29e9cc2 100644 --- a/src/main/res/layout/note_edit.xml +++ b/src/main/res/layout/note_edit.xml @@ -59,10 +59,21 @@ android:textAppearance="@style/TextAppearanceSecondaryItem" /> + + android:layout_marginLeft="8dp" + android:background="@drawable/bg_btn_insert_image" + android:contentDescription="@string/menu_insert_image" + android:padding="12dp" /> Bold Italic Normal - + Insert Image + + + Failed to insert image + No image selected + Permission denied. Please grant storage permission to insert images. + Image format not supported + Image is too large + Out of memory. Please try a smaller image. + Image inserted successfully + No image picker app available + Word count: Word count normal diff --git a/src/main/src/test/java/net/micode/notes/search/SearchHistoryTest.java b/src/main/src/test/java/net/micode/notes/search/SearchHistoryTest.java deleted file mode 100644 index 48f4c2f..0000000 --- a/src/main/src/test/java/net/micode/notes/search/SearchHistoryTest.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2024, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.search; - -import static org.junit.Assert.*; - -import android.content.Context; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.List; - -/** - * SearchHistory的单元测试类 - */ -@RunWith(MockitoJUnitRunner.class) -public class SearchHistoryTest { - @Mock - private Context mMockContext; - private SearchHistory mSearchHistory; - - @Before - public void setUp() { - // 初始化SearchHistory - mSearchHistory = SearchHistory.getInstance(mMockContext); - } - - /** - * 测试单例模式 - */ - @Test - public void testSingleton() { - SearchHistory instance1 = SearchHistory.getInstance(mMockContext); - SearchHistory instance2 = SearchHistory.getInstance(mMockContext); - assertSame("SearchHistory should be a singleton", instance1, instance2); - } - - /** - * 测试添加和获取搜索历史 - */ - @Test - public void testAddAndGetSearchHistory() { - // 清除现有历史记录 - mSearchHistory.clearSearchHistory(); - - // 添加搜索历史 - String keyword1 = "test1"; - String keyword2 = "test2"; - String keyword3 = "test3"; - - mSearchHistory.addSearchHistory(keyword1); - mSearchHistory.addSearchHistory(keyword2); - mSearchHistory.addSearchHistory(keyword3); - - // 获取搜索历史 - List history = mSearchHistory.getRecentSearches(10); - assertNotNull("Search history should not be null", history); - - // 验证历史记录顺序(最新的在前面) - assertEquals("Latest search should be first", keyword3, history.get(0)); - assertEquals("Second latest search should be second", keyword2, history.get(1)); - assertEquals("Oldest search should be last", keyword1, history.get(2)); - - // 测试添加重复关键词 - mSearchHistory.addSearchHistory(keyword1); - history = mSearchHistory.getRecentSearches(10); - assertEquals("Duplicate keyword should be moved to front", keyword1, history.get(0)); - } - - /** - * 测试获取匹配的搜索历史 - */ - @Test - public void testGetMatchingSearches() { - // 清除现有历史记录 - mSearchHistory.clearSearchHistory(); - - // 添加搜索历史 - mSearchHistory.addSearchHistory("test1"); - mSearchHistory.addSearchHistory("test2"); - mSearchHistory.addSearchHistory("example"); - mSearchHistory.addSearchHistory("test3"); - - // 获取匹配的搜索历史 - List matching = mSearchHistory.getMatchingSearches("test", 5); - assertNotNull("Matching searches should not be null", matching); - assertTrue("Should find matching searches", matching.size() >= 3); - - // 验证所有匹配的关键词都包含"test" - for (String keyword : matching) { - assertTrue("Matching keyword should contain 'test'", keyword.contains("test")); - } - - // 测试不匹配的关键词 - matching = mSearchHistory.getMatchingSearches("nonexistent", 5); - assertTrue("Should not find matching searches for nonexistent keyword", matching.isEmpty()); - - // 测试空关键词 - matching = mSearchHistory.getMatchingSearches("", 5); - assertTrue("Should return empty list for empty keyword", matching.isEmpty()); - } - - /** - * 测试删除搜索历史 - */ - @Test - public void testDeleteSearchHistory() { - // 清除现有历史记录 - mSearchHistory.clearSearchHistory(); - - // 添加搜索历史 - String keyword1 = "test1"; - String keyword2 = "test2"; - - mSearchHistory.addSearchHistory(keyword1); - mSearchHistory.addSearchHistory(keyword2); - - // 删除一个关键词 - mSearchHistory.deleteSearchHistory(keyword1); - List history = mSearchHistory.getRecentSearches(10); - assertEquals("Should have one less item after deletion", 1, history.size()); - assertFalse("Deleted keyword should not be in history", history.contains(keyword1)); - assertTrue("Remaining keyword should be in history", history.contains(keyword2)); - } - - /** - * 测试清除搜索历史 - */ - @Test - public void testClearSearchHistory() { - // 添加搜索历史 - mSearchHistory.addSearchHistory("test1"); - mSearchHistory.addSearchHistory("test2"); - - // 清除搜索历史 - mSearchHistory.clearSearchHistory(); - List history = mSearchHistory.getRecentSearches(10); - assertNotNull("Search history should not be null after clearing", history); - } - - /** - * 测试最大历史记录数限制 - */ - @Test - public void testMaxHistoryCount() { - // 清除现有历史记录 - mSearchHistory.clearSearchHistory(); - - // 设置最大历史记录数 - int maxCount = 5; - mSearchHistory.setMaxHistoryCount(maxCount); - - // 添加超过最大数量的搜索历史 - for (int i = 0; i < maxCount + 3; i++) { - mSearchHistory.addSearchHistory("test" + i); - } - - // 获取搜索历史 - List history = mSearchHistory.getRecentSearches(10); - assertTrue("Search history should not exceed max count", history.size() <= maxCount); - } - - /** - * 测试添加空关键词 - */ - @Test - public void testAddEmptyKeyword() { - // 清除现有历史记录 - mSearchHistory.clearSearchHistory(); - - // 添加空关键词 - mSearchHistory.addSearchHistory(""); - List history = mSearchHistory.getRecentSearches(10); - assertTrue("Empty keyword should not be added to history", history.isEmpty()); - } -} \ No newline at end of file diff --git a/src/main/src/test/java/net/micode/notes/search/SearchManagerTest.java b/src/main/src/test/java/net/micode/notes/search/SearchManagerTest.java deleted file mode 100644 index 34a759e..0000000 --- a/src/main/src/test/java/net/micode/notes/search/SearchManagerTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2024, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.search; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.List; - -/** - * SearchManager的单元测试类 - */ -@RunWith(MockitoJUnitRunner.class) -public class SearchManagerTest { - @Mock - private Context mMockContext; - @Mock - private ContentResolver mMockContentResolver; - @Mock - private Cursor mMockCursor; - - private SearchManager mSearchManager; - - @Before - public void setUp() { - // 配置Context和ContentResolver的mock行为 - when(mMockContext.getApplicationContext()).thenReturn(mMockContext); - when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); - - // 初始化SearchManager - mSearchManager = SearchManager.getInstance(mMockContext); - } - - /** - * 测试单例模式 - */ - @Test - public void testSingleton() { - SearchManager instance1 = SearchManager.getInstance(mMockContext); - SearchManager instance2 = SearchManager.getInstance(mMockContext); - assertSame("SearchManager should be a singleton", instance1, instance2); - } - - /** - * 测试空关键词搜索 - */ - @Test - public void testEmptyKeywordSearch() { - List results = mSearchManager.search("", SearchManager.SortBy.RELEVANCE, 10); - assertTrue("Search with empty keyword should return empty list", results.isEmpty()); - } - - /** - * 测试搜索建议功能 - */ - @Test - public void testSearchSuggestions() { - // 测试空关键词建议 - List suggestions = mSearchManager.getSearchSuggestions("", 5); - assertNotNull("Search suggestions should not be null", suggestions); - - // 测试非空关键词建议 - suggestions = mSearchManager.getSearchSuggestions("test", 5); - assertNotNull("Search suggestions should not be null", suggestions); - } - - /** - * 测试搜索历史功能 - */ - @Test - public void testSearchHistory() { - // 测试添加搜索历史 - mSearchManager.getSearchHistory(10); - - // 测试获取搜索历史 - List history = mSearchManager.getSearchHistory(10); - assertNotNull("Search history should not be null", history); - - // 测试清除搜索历史 - mSearchManager.clearSearchHistory(); - history = mSearchManager.getSearchHistory(10); - assertNotNull("Search history should not be null after clearing", history); - } - - /** - * 测试关键词高亮功能 - */ - @Test - public void testHighlightKeyword() { - String text = "This is a test text for highlighting keyword test"; - String keyword = "test"; - String highlighted = mSearchManager.highlightKeyword(text, keyword); - - // 验证高亮结果 - assertNotNull("Highlighted text should not be null", highlighted); - assertTrue("Highlighted text should contain HTML tags", highlighted.contains(" 9.0f); - - // 测试部分匹配标题 - mSearchResult.calculateRelevanceScore("Test"); - float partialTitleScore = mSearchResult.getRelevanceScore(); - assertTrue("Partial title match should have high score", partialTitleScore > 4.0f); - - // 测试内容匹配 - mSearchResult.calculateRelevanceScore("content"); - float contentScore = mSearchResult.getRelevanceScore(); - assertTrue("Content match should have moderate score", contentScore > 0.0f); - - // 测试多次匹配 - mSearchResult.calculateRelevanceScore("test"); - float multipleMatchScore = mSearchResult.getRelevanceScore(); - assertTrue("Multiple matches should have higher score", multipleMatchScore > contentScore); - - // 测试不匹配 - mSearchResult.calculateRelevanceScore("nonexistent"); - float noMatchScore = mSearchResult.getRelevanceScore(); - assertEquals("No match should have zero score", 0.0f, noMatchScore, 0.001f); - - // 测试空关键词 - mSearchResult.calculateRelevanceScore(""); - float emptyKeywordScore = mSearchResult.getRelevanceScore(); - assertEquals("Empty keyword should have zero score", 0.0f, emptyKeywordScore, 0.001f); - } - - /** - * 测试SearchResult的getter和setter方法 - */ - @Test - public void testGetterSetterMethods() { - // 测试NoteId - long noteId = 12345; - mSearchResult.setNoteId(noteId); - assertEquals("NoteId should be set correctly", noteId, mSearchResult.getNoteId()); - - // 测试Title - String title = "Test Title"; - mSearchResult.setTitle(title); - assertEquals("Title should be set correctly", title, mSearchResult.getTitle()); - - // 测试Content - String content = "Test Content"; - mSearchResult.setContent(content); - assertEquals("Content should be set correctly", content, mSearchResult.getContent()); - - // 测试Snippet - String snippet = "Test Snippet"; - mSearchResult.setSnippet(snippet); - assertEquals("Snippet should be set correctly", snippet, mSearchResult.getSnippet()); - - // 测试CreatedDate - long createdDate = System.currentTimeMillis(); - mSearchResult.setCreatedDate(createdDate); - assertEquals("CreatedDate should be set correctly", createdDate, mSearchResult.getCreatedDate()); - - // 测试ModifiedDate - long modifiedDate = System.currentTimeMillis(); - mSearchResult.setModifiedDate(modifiedDate); - assertEquals("ModifiedDate should be set correctly", modifiedDate, mSearchResult.getModifiedDate()); - - // 测试BgColorId - int bgColorId = 1; - mSearchResult.setBgColorId(bgColorId); - assertEquals("BgColorId should be set correctly", bgColorId, mSearchResult.getBgColorId()); - - // 测试RelevanceScore - float relevanceScore = 8.5f; - mSearchResult.setRelevanceScore(relevanceScore); - assertEquals("RelevanceScore should be set correctly", relevanceScore, mSearchResult.getRelevanceScore(), 0.001f); - } - - /** - * 测试toString方法 - */ - @Test - public void testToString() { - mSearchResult.setNoteId(123); - mSearchResult.setTitle("Test Title"); - mSearchResult.setRelevanceScore(5.5f); - - String resultString = mSearchResult.toString(); - assertNotNull("toString should not return null", resultString); - assertTrue("toString should contain noteId", resultString.contains("123")); - assertTrue("toString should contain title", resultString.contains("Test Title")); - assertTrue("toString should contain relevanceScore", resultString.contains("5.5")); - } -} \ No newline at end of file -- 2.34.1