Merge pull request '增加了图片插入功能' (#13) from cuijiaxiang_branch into master
commit
043492fe97
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试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"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue