Merge pull request '11' (#7) from dev into main

main
ph7f8nofa 2 months ago
commit d660f20ec9

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="selectedTabId" value="Android Vitals" />
</component>
</project>

@ -4,14 +4,6 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-06-07T15:56:39.281897900Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Song91\.android\avd\Pixel_8_Pro.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

@ -6,7 +6,6 @@
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$PROJECT_DIR$/../../Android/Gradle/gradle-8.13" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="PWA">
<option name="enabled" value="true" />
<option name="wasEnabledAtLeastOnce" value="true" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>

@ -1,3 +0,0 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

@ -33,6 +33,8 @@ import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -48,12 +50,14 @@ import android.text.style.BackgroundColorSpan;
import android.text.style.ImageSpan; import android.text.style.ImageSpan;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
@ -79,6 +83,7 @@ import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -169,7 +174,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private final int PHOTO_REQUEST=1; private final int PHOTO_REQUEST=1;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit); this.setContentView(R.layout.note_edit);
@ -180,6 +184,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
initResources(); initResources();
// 初始化字体大小
mNoteEditor.setTextAppearance(this, ResourceParser.TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
//根据id获取添加图片按钮 //根据id获取添加图片按钮
final ImageButton add_img_btn = (ImageButton) findViewById(R.id.add_img_btn); final ImageButton add_img_btn = (ImageButton) findViewById(R.id.add_img_btn);
//为点击图片按钮设置监听器 //为点击图片按钮设置监听器
@ -187,13 +194,27 @@ public class NoteEditActivity extends Activity implements OnClickListener,
@Override @Override
public void onClick(View view) { public void onClick(View view) {
Log.d(TAG, "onClick: click add image button"); Log.d(TAG, "onClick: click add image button");
//ACTION_GET_CONTENT: 允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音) // 隐藏输入法
Intent loadImage = new Intent(Intent.ACTION_GET_CONTENT); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
//Category属性用于指定当前动作Action被执行的环境. if (imm != null) {
//CATEGORY_OPENABLE; 用来指示一个ACTION_GET_CONTENT的intent imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
loadImage.addCategory(Intent.CATEGORY_OPENABLE); }
loadImage.setType("image/*");
startActivityForResult(loadImage, PHOTO_REQUEST); // 延迟启动图片选择器,等待输入法完全隐藏
view.postDelayed(new Runnable() {
@Override
public void run() {
try {
Intent loadImage = new Intent(Intent.ACTION_GET_CONTENT);
loadImage.addCategory(Intent.CATEGORY_OPENABLE);
loadImage.setType("image/*");
startActivityForResult(loadImage, PHOTO_REQUEST);
} catch (Exception e) {
Log.e(TAG, "启动图片选择器失败", e);
Toast.makeText(NoteEditActivity.this, "启动图片选择器失败", Toast.LENGTH_SHORT).show();
}
}
}, 100); // 延迟100毫秒
} }
}); });
} }
@ -297,6 +318,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
initNoteScreen(); initNoteScreen();
// 转换文本中的图片标记为实际图片
convertToImage();
} }
private void initNoteScreen() { private void initNoteScreen() {
@ -449,17 +472,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
mFontSizeSelector = findViewById(R.id.font_size_selector); mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int id : sFontSizeBtnsMap.keySet()) { // 为字体大小选择器的每个选项设置点击事件
View view = findViewById(id); findViewById(R.id.ll_font_small).setOnClickListener(this);
view.setOnClickListener(this); findViewById(R.id.ll_font_normal).setOnClickListener(this);
}; findViewById(R.id.ll_font_large).setOnClickListener(this);
findViewById(R.id.ll_font_super).setOnClickListener(this);
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
} }
@ -494,43 +514,75 @@ public class NoteEditActivity extends Activity implements OnClickListener,
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);
} }
@Override
public void onClick(View v) { public void onClick(View v) {
int id = v.getId(); int id = v.getId();
if (id == R.id.btn_set_bg_color) { if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE); if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( mNoteBgColorSelector.setVisibility(View.GONE);
View.VISIBLE); } else {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
mFontSizeSelector.setVisibility(View.GONE);
}
} else if (sBgSelectorBtnsMap.containsKey(id)) { } else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE); View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE); mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) { } else if (sFontSizeBtnsMap.containsKey(id)) {
// 隐藏之前选中的字体大小图标
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); int fontSizeId = sFontSizeBtnsMap.get(id);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); mFontSizeId = fontSizeId;
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, fontSizeId).commit();
mNoteEditor.setTextAppearance(this, ResourceParser.TextAppearanceResources.getTexAppearanceResource(fontSizeId));
// 显示新选中的字体大小图标
findViewById(sFontSelectorSelectionMap.get(fontSizeId)).setVisibility(View.VISIBLE);
mFontSizeSelector.setVisibility(View.GONE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText(); getWorkingText();
switchToListMode(mWorkingNote.getContent()); switchToListMode(mWorkingNote.getContent());
} else { } else {
mNoteEditor.setTextAppearance(this, mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); TextAppearanceResources.getTexAppearanceResource(fontSizeId));
} }
mFontSizeSelector.setVisibility(View.GONE);
} }
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return;
}
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return;
}
if(clearSettingState()) { if(clearSettingState()) {
return; return;
} }
saveNote(); saveNote();
super.onBackPressed(); super.onBackPressed();
} }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
}
return super.onKeyDown(keyCode, event);
}
private boolean clearSettingState() { private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE); mNoteBgColorSelector.setVisibility(View.GONE);
@ -575,7 +627,6 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_new_note: case R.id.menu_new_note:
@ -597,9 +648,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
builder.show(); builder.show();
break; break;
case R.id.menu_font_size: case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE); if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); mFontSizeSelector.setVisibility(View.GONE);
break; } else {
mFontSizeSelector.setVisibility(View.VISIBLE);
mNoteBgColorSelector.setVisibility(View.GONE);
// 显示当前选中的字体大小
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
}
return true;
case R.id.menu_list_mode: case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0); TextNote.MODE_CHECK_LIST : 0);
@ -620,7 +677,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
default: default:
break; break;
} }
return true; return super.onOptionsItemSelected(item);
} }
private void setReminder() { private void setReminder() {
@ -1034,45 +1091,198 @@ public class NoteEditActivity extends Activity implements OnClickListener,
ContentResolver resolver = getContentResolver(); ContentResolver resolver = getContentResolver();
switch (requestCode) { switch (requestCode) {
case PHOTO_REQUEST: case PHOTO_REQUEST:
Uri originalUri = intent.getData(); //1.获得图片的真实路径 if (resultCode != RESULT_OK) {
Bitmap bitmap = null; Log.e(TAG, "获取图片失败: resultCode = " + resultCode);
try { Toast.makeText(NoteEditActivity.this, "获取图片失败: 用户取消或操作失败", Toast.LENGTH_SHORT).show();
bitmap = BitmapFactory.decodeStream(resolver.openInputStream(originalUri));//2.解码图片 return;
} catch (FileNotFoundException e) {
Log.d(TAG, "onActivityResult: get file_exception");
e.printStackTrace();
} }
if (bitmap != null) { if (intent == null) {
//3.根据Bitmap对象创建ImageSpan对象 Log.e(TAG, "获取图片失败: intent为null");
Log.d(TAG, "onActivityResult: bitmap is not null"); Toast.makeText(NoteEditActivity.this, "获取图片失败: 未获取到图片数据", Toast.LENGTH_SHORT).show();
ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); return;
}
Uri originalUri = intent.getData();
if (originalUri == null) {
Log.e(TAG, "获取图片失败: originalUri为null");
Toast.makeText(NoteEditActivity.this, "获取图片失败: 图片URI无效", Toast.LENGTH_SHORT).show();
return;
}
try {
Log.d(TAG, "开始加载图片: Uri = " + originalUri);
// 获取图片的真实路径
String path = getPath(this, originalUri); String path = getPath(this, originalUri);
//4.使用[local][/local]将path括起来用于之后方便识别图片路径在note中的位置 Log.d(TAG, "图片路径: " + path);
String img_fragment = "[local]" + path + "[/local]";
//创建一个SpannableString对象以便插入用ImageSpan对象封装的图像 // 尝试多种方式加载图片
SpannableString spannableString = new SpannableString(img_fragment); Bitmap bitmap = null;
spannableString.setSpan(imageSpan, 0, img_fragment.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//5.将选择的图片追加到EditText中光标所在位置 // 方式1直接从Uri加载
NoteEditText e = (NoteEditText) findViewById(R.id.note_edit_view); try {
int index = e.getSelectionStart(); //获取光标所在位置 bitmap = MediaStore.Images.Media.getBitmap(resolver, originalUri);
Log.d(TAG, "Index是: " + index); Log.d(TAG, "从Uri加载图片成功");
Editable edit_text = e.getEditableText(); } catch (Exception e) {
edit_text.insert(index, spannableString); //将图片插入到光标所在位置 Log.e(TAG, "从Uri加载图片失败", e);
}
mWorkingNote.mContent = e.getText().toString();
//6.把改动提交到数据库中,两个数据库表都要改的 // 方式2如果方式1失败尝试从文件路径加载
if (bitmap == null && path != null) {
try {
bitmap = BitmapFactory.decodeFile(path);
Log.d(TAG, "从文件路径加载图片成功");
} catch (Exception e) {
Log.e(TAG, "从文件路径加载图片失败", e);
}
}
// 方式3如果前两种方式都失败尝试使用ContentResolver
if (bitmap == null) {
try {
InputStream inputStream = resolver.openInputStream(originalUri);
if (inputStream != null) {
bitmap = BitmapFactory.decodeStream(inputStream);
inputStream.close();
Log.d(TAG, "从ContentResolver加载图片成功");
}
} catch (Exception e) {
Log.e(TAG, "从ContentResolver加载图片失败", e);
}
}
if (bitmap == null) {
Log.e(TAG, "所有加载方式都失败");
Toast.makeText(NoteEditActivity.this, "获取图片失败: 无法加载图片", Toast.LENGTH_SHORT).show();
return;
}
Log.d(TAG, "图片加载成功,尺寸: " + bitmap.getWidth() + "x" + bitmap.getHeight());
// 调整图片大小
int maxSize = 1024;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if (width > maxSize || height > maxSize) {
float scale = Math.min((float) maxSize / width, (float) maxSize / height);
width = Math.round(width * scale);
height = Math.round(height * scale);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
bitmap.recycle(); // 释放原始bitmap
bitmap = scaledBitmap;
Log.d(TAG, "图片已调整大小: " + width + "x" + height);
}
// 创建ImageSpan对象
Log.d(TAG, "开始创建ImageSpanbitmap尺寸: " + bitmap.getWidth() + "x" + bitmap.getHeight());
// 调整图片大小
int maxWidth = 768; // 最大宽度
float scale = (float) maxWidth / bitmap.getWidth();
int newWidth = (int) (bitmap.getWidth() * scale);
int newHeight = (int) (bitmap.getHeight() * scale);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
bitmap.recycle();
bitmap = scaledBitmap;
Log.d(TAG, "调整后的图片尺寸: " + newWidth + "x" + newHeight);
// 创建Drawable
Drawable drawable = new BitmapDrawable(getResources(), bitmap);
drawable.setBounds(0, 0, newWidth, newHeight);
// 创建ImageSpan
ImageSpan imageSpan = new ImageSpan(drawable);
Log.d(TAG, "ImageSpan创建成功");
// 获取EditText和光标位置
NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view);
if (noteEditText == null) {
Log.e(TAG, "noteEditText为null");
return;
}
Log.d(TAG, "获取到noteEditText");
int index = noteEditText.getSelectionStart();
if (index < 0) {
index = 0;
}
Log.d(TAG, "当前光标位置: " + index);
// 获取当前文本
Editable edit_text = noteEditText.getEditableText();
if (edit_text == null) {
Log.e(TAG, "edit_text为null");
return;
}
Log.d(TAG, "获取到edit_text长度: " + edit_text.length());
// 创建新的SpannableString
SpannableString spannableString = new SpannableString(" ");
spannableString.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Log.d(TAG, "SpannableString创建成功");
// 插入图片
edit_text.insert(index, spannableString);
Log.d(TAG, "图片插入成功,当前文本长度: " + edit_text.length());
// 保存图片路径到数据库
String imagePath = "[local]" + path + "[/local]";
mWorkingNote.mContent = noteEditText.getText().toString();
Log.d(TAG, "保存的图片路径: " + imagePath);
// 更新数据库
ContentResolver contentResolver = getContentResolver(); ContentResolver contentResolver = getContentResolver();
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
final long id = mWorkingNote.getNoteId(); final long id = mWorkingNote.getNoteId();
contentValues.put("snippet", String.valueOf(mWorkingNote)); contentValues.put("snippet", String.valueOf(mWorkingNote));
contentValues.put("has_attachment", 1); // 标记有附件
contentResolver.update(Uri.parse("content://micode_notes/note"), contentValues, "_id=?", new String[]{"" + id}); contentResolver.update(Uri.parse("content://micode_notes/note"), contentValues, "_id=?", new String[]{"" + id});
ContentValues contentValues1 = new ContentValues(); ContentValues contentValues1 = new ContentValues();
contentValues1.put("content", mWorkingNote.mContent); contentValues1.put("content", mWorkingNote.mContent);
contentValues1.put("mime_type", "vnd.android.cursor.item/text_note");
contentResolver.update(Uri.parse("content://micode_notes/data"), contentValues1, "mime_type=? and note_id=?", new String[]{"vnd.android.cursor.item/text_note", "" + id}); contentResolver.update(Uri.parse("content://micode_notes/data"), contentValues1, "mime_type=? and note_id=?", new String[]{"vnd.android.cursor.item/text_note", "" + id});
} else { // 保存图片数据
Toast.makeText(NoteEditActivity.this, "获取图片失败", Toast.LENGTH_SHORT).show(); ContentValues imageValues = new ContentValues();
imageValues.put("note_id", id);
imageValues.put("mime_type", "image/*");
imageValues.put("content", path); // 使用content列存储图片路径
imageValues.put("data1", 0); // 使用data1存储图片类型
imageValues.put("data3", ""); // 使用data3存储额外信息
contentResolver.insert(Uri.parse("content://micode_notes/data"), imageValues);
Log.d(TAG, "数据库更新成功");
// 强制刷新EditText显示
noteEditText.invalidate();
noteEditText.requestLayout();
Log.d(TAG, "EditText刷新完成");
// 检查图片是否真的插入
Spannable spannable = noteEditText.getText();
ImageSpan[] spans = spannable.getSpans(0, spannable.length(), ImageSpan.class);
Log.d(TAG, "当前文本中的ImageSpan数量: " + spans.length);
if (spans.length > 0) {
Log.d(TAG, "第一个ImageSpan的位置: " + spannable.getSpanStart(spans[0]) + " - " + spannable.getSpanEnd(spans[0]));
Drawable d = spans[0].getDrawable();
if (d != null) {
Log.d(TAG, "ImageSpan drawable尺寸: " + d.getIntrinsicWidth() + "x" + d.getIntrinsicHeight());
}
}
// 重新设置文本以确保ImageSpan被保存
noteEditText.setText(noteEditText.getText());
} catch (SecurityException e) {
Log.e(TAG, "获取图片失败: 权限不足", e);
Toast.makeText(NoteEditActivity.this, "获取图片失败: 应用没有足够的权限", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "获取图片失败: 未知错误", e);
Toast.makeText(NoteEditActivity.this, "获取图片失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
} }
break; break;
default: default:
@ -1126,10 +1336,47 @@ public class NoteEditActivity extends Activity implements OnClickListener,
continue; continue;
} }
// 尝试加载图片 // 尝试多种方式加载图片
Bitmap bitmap = BitmapFactory.decodeFile(path); Bitmap bitmap = null;
// 方式1直接从文件加载
try {
bitmap = BitmapFactory.decodeFile(path);
Log.d(TAG, "Successfully loaded image from file");
} catch (Exception e) {
Log.e(TAG, "Failed to load image from file", e);
}
// 方式2如果方式1失败尝试使用ContentResolver
if (bitmap == null) {
try {
Uri uri = Uri.fromFile(imageFile);
InputStream inputStream = getContentResolver().openInputStream(uri);
if (inputStream != null) {
bitmap = BitmapFactory.decodeStream(inputStream);
inputStream.close();
Log.d(TAG, "Successfully loaded image using ContentResolver");
}
} catch (Exception e) {
Log.e(TAG, "Failed to load image using ContentResolver", e);
}
}
if (bitmap != null) { if (bitmap != null) {
ImageSpan imageSpan = new ImageSpan(this, bitmap); // 调整图片大小
int maxSize = 1024;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if (width > maxSize || height > maxSize) {
float scale = Math.min((float) maxSize / width, (float) maxSize / height);
width = Math.round(width * scale);
height = Math.round(height * scale);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
bitmap.recycle();
bitmap = scaledBitmap;
}
ImageSpan imageSpan = new ImageSpan(this, bitmap, ImageSpan.ALIGN_BASELINE);
SpannableString spannableString = new SpannableString(matcher.group()); SpannableString spannableString = new SpannableString(matcher.group());
spannableString.setSpan(imageSpan, 0, spannableString.length(), spannableString.setSpan(imageSpan, 0, spannableString.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

@ -17,11 +17,16 @@
package net.micode.notes.ui; package net.micode.notes.ui;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Layout; import android.text.Layout;
import android.text.Selection; import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned; import android.text.Spanned;
import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
@ -34,13 +39,17 @@ import android.widget.EditText;
import net.micode.notes.R; import net.micode.notes.R;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText { public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText {
private static final String TAG = "NoteEditText"; private static final String TAG = "NoteEditText";
private int mIndex; private int mIndex;
private int mSelectionStartBeforeDelete; private int mSelectionStartBeforeDelete;
private long mLastDrawTime = 0;
private static final long DRAW_INTERVAL = 16; // 约60fps
private static final String SCHEME_TEL = "tel:" ; private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_HTTP = "http:" ;
@ -77,26 +86,37 @@ public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText {
private OnTextViewChangeListener mOnTextViewChangeListener; private OnTextViewChangeListener mOnTextViewChangeListener;
public NoteEditText(Context context) { private SpannableString mSpannableString;
super(context, null); private List<ImageSpan> mImageSpans;
mIndex = 0;
}
public void setIndex(int index) { public NoteEditText(Context context) {
mIndex = index; super(context);
init();
} }
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { private void init() {
mOnTextViewChangeListener = listener; mImageSpans = new ArrayList<>();
mSpannableString = null;
mIndex = 0;
Log.d(TAG, "NoteEditText初始化完成");
} }
public NoteEditText(Context context, AttributeSet attrs) { public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle); super(context, attrs);
init();
} }
public NoteEditText(Context context, AttributeSet attrs, int defStyle) { public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
// TODO Auto-generated constructor stub init();
}
public void setIndex(int index) {
mIndex = index;
}
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
} }
@Override @Override
@ -214,4 +234,126 @@ public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText {
} }
super.onCreateContextMenu(menu); super.onCreateContextMenu(menu);
} }
@Override
protected void onDraw(Canvas canvas) {
long currentTime = System.currentTimeMillis();
if (currentTime - mLastDrawTime < DRAW_INTERVAL) {
super.onDraw(canvas);
return;
}
mLastDrawTime = currentTime;
super.onDraw(canvas);
// 只在有ImageSpan时进行处理
if (mImageSpans != null && !mImageSpans.isEmpty() && mSpannableString != null) {
Layout layout = getLayout();
if (layout == null) return;
for (ImageSpan span : mImageSpans) {
Drawable drawable = span.getDrawable();
if (drawable != null) {
int start = mSpannableString.getSpanStart(span);
int end = mSpannableString.getSpanEnd(span);
if (start >= 0 && end > start) {
int line = layout.getLineForOffset(start);
int baseline = layout.getLineBaseline(line);
// 计算图片位置
float x = layout.getPrimaryHorizontal(start);
float y = baseline - drawable.getIntrinsicHeight();
// 设置图片边界
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
// 保存画布状态
canvas.save();
// 移动到正确的位置
canvas.translate(x, y);
// 绘制图片
drawable.draw(canvas);
// 恢复画布状态
canvas.restore();
Log.d(TAG, "绘制图片: 位置(" + x + "," + y + "), 尺寸: " +
drawable.getIntrinsicWidth() + "x" + drawable.getIntrinsicHeight());
}
}
}
}
}
@Override
public void setText(CharSequence text, BufferType type) {
if (mImageSpans == null) {
mImageSpans = new ArrayList<>();
}
mImageSpans.clear();
if (text instanceof SpannableString) {
mSpannableString = (SpannableString) text;
ImageSpan[] spans = mSpannableString.getSpans(0, mSpannableString.length(), ImageSpan.class);
for (ImageSpan span : spans) {
mImageSpans.add(span);
}
Log.d(TAG, "setText: 从SpannableString中获取到 " + spans.length + " 个ImageSpan");
} else if (text instanceof Spannable) {
Spannable spannable = (Spannable) text;
ImageSpan[] spans = spannable.getSpans(0, spannable.length(), ImageSpan.class);
// 创建新的SpannableString并保留所有span
mSpannableString = new SpannableString(spannable);
for (ImageSpan span : spans) {
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
int flags = spannable.getSpanFlags(span);
mSpannableString.setSpan(span, start, end, flags);
mImageSpans.add(span);
}
Log.d(TAG, "setText: 从Spannable中获取到 " + spans.length + " 个ImageSpan");
} else {
mSpannableString = new SpannableString(text);
}
super.setText(mSpannableString, BufferType.SPANNABLE);
// 验证ImageSpan是否保留
if (mSpannableString != null) {
ImageSpan[] spans = mSpannableString.getSpans(0, mSpannableString.length(), ImageSpan.class);
Log.d(TAG, "setText后验证: SpannableString中的ImageSpan数量: " + spans.length);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure被调用宽度: " + getMeasuredWidth() + ", 高度: " + getMeasuredHeight());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.d(TAG, "onLayout被调用changed: " + changed);
// 强制重新计算布局
if (changed) {
requestLayout();
}
}
@Override
public void invalidate() {
// 减少不必要的重绘
if (mImageSpans != null && !mImageSpans.isEmpty()) {
super.invalidate();
}
}
@Override
public void requestLayout() {
// 减少不必要的布局请求
if (mImageSpans != null && !mImageSpans.isEmpty()) {
super.requestLayout();
}
}
} }

@ -555,6 +555,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
private void openNode(NoteItemData data) { private void openNode(NoteItemData data) {
// 检查便签是否为私密便签且当前不在私密模式下
if (data.isPrivate && !mIsPrivacyMode) {
Toast.makeText(this, R.string.cannot_open_private_note, Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(this, NoteEditActivity.class); Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId()); intent.putExtra(Intent.EXTRA_UID, data.getId());

@ -97,6 +97,9 @@
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:linksClickable="false" android:linksClickable="false"
android:minLines="12" android:minLines="12"
android:textIsSelectable="true"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:textAppearance="@style/TextAppearancePrimaryItem" /> android:textAppearance="@style/TextAppearancePrimaryItem" />
<LinearLayout <LinearLayout

@ -148,4 +148,6 @@
<string name="menu_new_folder">新建文件夹</string> <string name="menu_new_folder">新建文件夹</string>
<string name="cannot_open_private_note">无法在非私密模式下打开私密便签</string>
</resources> </resources>

Loading…
Cancel
Save