Merge pull request '增加了图片插入功能' (#13) from cuijiaxiang_branch into master

pull/14/head
pr9ixgmc2 1 month ago
commit 043492fe97

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />

@ -349,6 +349,12 @@ public class Notes {
*/
public static final String MODE = DATA1;
/**
*
* DataDATA3
*/
public static final String IMAGE_PATH = DATA3;
/**
* Checklist
*/

@ -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());
}
}
}
}
}

@ -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());
}
}
}
}
}

@ -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());
}
}
}
}
}

@ -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());
}
}
}
}
}

@ -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);
}
}
}

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#F5F5F5" />
<corners android:radius="4dp" />
<stroke android:width="1dp" android:color="#BDBDBD" />
</shape>
</item>
<item android:gravity="center">
<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="#616161"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
</item>
</layer-list>
</item>
<item>
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="4dp" />
<stroke android:width="1dp" android:color="#E0E0E0" />
</shape>
</item>
<item android:gravity="center">
<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="#757575"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
</item>
</layer-list>
</item>
</selector>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="4dp" />
<stroke android:width="1dp" android:color="#E0E0E0" />
</shape>
</item>
<item android:gravity="center">
<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="#757575"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
</item>
</layer-list>

@ -59,10 +59,21 @@
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_set_bg_color"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color"
android:padding="12dp" />
<ImageButton
android:id="@+id/add_img_btn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
android:layout_marginLeft="8dp"
android:background="@drawable/bg_btn_insert_image"
android:contentDescription="@string/menu_insert_image"
android:padding="12dp" />
</LinearLayout>
<LinearLayout

@ -207,7 +207,18 @@
<string name="menu_bold">Bold</string>
<string name="menu_italic">Italic</string>
<string name="menu_normal">Normal</string>
<string name="menu_insert_image">Insert Image</string>
<!-- Image insertion strings -->
<string name="error_insert_image_failed">Failed to insert image</string>
<string name="error_no_image_selected">No image selected</string>
<string name="error_permission_denied">Permission denied. Please grant storage permission to insert images.</string>
<string name="error_image_format_not_supported">Image format not supported</string>
<string name="error_image_too_large">Image is too large</string>
<string name="error_out_of_memory">Out of memory. Please try a smaller image.</string>
<string name="info_image_inserted">Image inserted successfully</string>
<string name="error_no_image_picker">No image picker app available</string>
<!-- Word count strings -->
<string name="word_count_label">Word count: </string>
<string name="word_count_normal">Word count normal</string>

@ -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<String> 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<String> 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<String> 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<String> 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<String> history = mSearchHistory.getRecentSearches(10);
assertTrue("Search history should not exceed max count", history.size() <= maxCount);
}
/**
*
*/
@Test
public void testAddEmptyKeyword() {
// 清除现有历史记录
mSearchHistory.clearSearchHistory();
// 添加空关键词
mSearchHistory.addSearchHistory("");
List<String> history = mSearchHistory.getRecentSearches(10);
assertTrue("Empty keyword should not be added to history", history.isEmpty());
}
}

@ -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<SearchResult> results = mSearchManager.search("", SearchManager.SortBy.RELEVANCE, 10);
assertTrue("Search with empty keyword should return empty list", results.isEmpty());
}
/**
*
*/
@Test
public void testSearchSuggestions() {
// 测试空关键词建议
List<String> 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<String> 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("<font"));
assertTrue("Highlighted text should contain keyword", highlighted.contains(keyword));
// 测试空文本高亮
highlighted = mSearchManager.highlightKeyword(null, keyword);
assertNull("Highlighted text should be null for null input", highlighted);
// 测试空关键词高亮
highlighted = mSearchManager.highlightKeyword(text, null);
assertEquals("Highlighted text should be same as input for null keyword", text, highlighted);
}
/**
*
*/
@Test
public void testSortByEnum() {
assertEquals("RELEVANCE should be 0", 0, SearchManager.SortBy.RELEVANCE.ordinal());
assertEquals("CREATED_DATE should be 1", 1, SearchManager.SortBy.CREATED_DATE.ordinal());
assertEquals("MODIFIED_DATE should be 2", 2, SearchManager.SortBy.MODIFIED_DATE.ordinal());
}
}

@ -1,137 +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 org.junit.Before;
import org.junit.Test;
/**
* SearchResult
*/
public class SearchResultTest {
private SearchResult mSearchResult;
@Before
public void setUp() {
mSearchResult = new SearchResult();
}
/**
*
*/
@Test
public void testRelevanceScoreCalculation() {
// 设置测试数据
mSearchResult.setTitle("Test Title");
mSearchResult.setContent("This is a test content for testing relevance score calculation");
mSearchResult.setSnippet("Test snippet");
// 测试完全匹配标题
mSearchResult.calculateRelevanceScore("Test Title");
float score = mSearchResult.getRelevanceScore();
assertTrue("Exact title match should have high score", score > 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);
}
/**
* SearchResultgettersetter
*/
@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"));
}
}
Loading…
Cancel
Save