|
|
|
|
@ -33,8 +33,12 @@ import android.preference.PreferenceManager;
|
|
|
|
|
import android.text.Spannable;
|
|
|
|
|
import android.text.SpannableString;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.text.Html;
|
|
|
|
|
import android.text.format.DateUtils;
|
|
|
|
|
import android.text.style.BackgroundColorSpan;
|
|
|
|
|
import android.text.style.StyleSpan;
|
|
|
|
|
import android.text.style.UnderlineSpan;
|
|
|
|
|
import android.graphics.Typeface;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
|
import android.view.Menu;
|
|
|
|
|
@ -168,6 +172,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
private SharedPreferences mSharedPrefs; // 共享偏好设置
|
|
|
|
|
private int mFontSizeId; // 字体大小ID
|
|
|
|
|
private ImageButton mBtnInsertImage; // 插入图片按钮 OMO
|
|
|
|
|
|
|
|
|
|
// 富文本工具栏按钮
|
|
|
|
|
private ImageButton mBtnBold; // 加粗按钮
|
|
|
|
|
private ImageButton mBtnItalic; // 斜体按钮
|
|
|
|
|
private ImageButton mBtnUnderline; // 下划线按钮
|
|
|
|
|
|
|
|
|
|
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键
|
|
|
|
|
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷图标标题最大长度
|
|
|
|
|
@ -351,11 +360,84 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
String content = mWorkingNote.getContent();
|
|
|
|
|
Log.d(TAG, "Initializing note content: " + content);
|
|
|
|
|
|
|
|
|
|
// 将文本内容转换为包含ImageSpan的SpannableString
|
|
|
|
|
SpannableString spannableString = convertTextToSpannableWithImages(content);
|
|
|
|
|
mNoteEditor.setText(spannableString);
|
|
|
|
|
// 将光标定位到文本末尾
|
|
|
|
|
mNoteEditor.setSelection(spannableString.length());
|
|
|
|
|
if (TextUtils.isEmpty(content)) {
|
|
|
|
|
mNoteEditor.setText("");
|
|
|
|
|
mNoteEditor.setSelection(0);
|
|
|
|
|
} else {
|
|
|
|
|
Spannable spannable;
|
|
|
|
|
|
|
|
|
|
// 检查是否是HTML格式(包含HTML标签)
|
|
|
|
|
if (content.contains("<") && content.contains(">")) {
|
|
|
|
|
try {
|
|
|
|
|
// 从HTML格式恢复SpannableString,保留样式(加粗、斜体、下划线)
|
|
|
|
|
// Html.fromHtml会自动将HTML标签转换为对应的Span
|
|
|
|
|
spannable = (Spannable) Html.fromHtml(content);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 如果HTML解析失败,使用普通文本
|
|
|
|
|
Log.e(TAG, "Error parsing HTML content: " + e.getMessage());
|
|
|
|
|
spannable = new SpannableString(content);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 普通文本格式,直接创建SpannableString
|
|
|
|
|
spannable = new SpannableString(content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理图片:将包含[IMAGE]标签的文本转换为包含ImageSpan的SpannableString
|
|
|
|
|
// 注意:convertTextToSpannableWithImages会处理[IMAGE]标记,但会保留文本内容
|
|
|
|
|
String textContent = spannable.toString();
|
|
|
|
|
SpannableString finalSpannable = convertTextToSpannableWithImages(textContent);
|
|
|
|
|
|
|
|
|
|
// 将样式从原始Spannable复制到处理后的SpannableString
|
|
|
|
|
// 由于convertTextToSpannableWithImages可能改变了文本长度(用空格替换了[IMAGE]标签),
|
|
|
|
|
// 我们需要基于文本内容来映射样式位置
|
|
|
|
|
if (textContent.length() == finalSpannable.length()) {
|
|
|
|
|
// 文本长度相同,直接复制样式
|
|
|
|
|
StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
|
|
|
|
|
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
|
|
|
|
|
|
|
|
|
|
for (StyleSpan span : styleSpans) {
|
|
|
|
|
int start = spannable.getSpanStart(span);
|
|
|
|
|
int end = spannable.getSpanEnd(span);
|
|
|
|
|
if (start >= 0 && end <= finalSpannable.length() && start < end) {
|
|
|
|
|
finalSpannable.setSpan(new StyleSpan(span.getStyle()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (UnderlineSpan span : underlineSpans) {
|
|
|
|
|
int start = spannable.getSpanStart(span);
|
|
|
|
|
int end = spannable.getSpanEnd(span);
|
|
|
|
|
if (start >= 0 && end <= finalSpannable.length() && start < end) {
|
|
|
|
|
finalSpannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 文本长度不同,说明有图片被处理了
|
|
|
|
|
// 这种情况下,样式位置需要重新计算
|
|
|
|
|
// 为了简化,我们基于原始文本位置映射样式(可能会有偏差,但应该能处理大部分情况)
|
|
|
|
|
StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
|
|
|
|
|
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
|
|
|
|
|
|
|
|
|
|
for (StyleSpan span : styleSpans) {
|
|
|
|
|
int start = Math.max(0, Math.min(spannable.getSpanStart(span), finalSpannable.length()));
|
|
|
|
|
int end = Math.max(start, Math.min(spannable.getSpanEnd(span), finalSpannable.length()));
|
|
|
|
|
if (start < end) {
|
|
|
|
|
finalSpannable.setSpan(new StyleSpan(span.getStyle()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (UnderlineSpan span : underlineSpans) {
|
|
|
|
|
int start = Math.max(0, Math.min(spannable.getSpanStart(span), finalSpannable.length()));
|
|
|
|
|
int end = Math.max(start, Math.min(spannable.getSpanEnd(span), finalSpannable.length()));
|
|
|
|
|
if (start < end) {
|
|
|
|
|
finalSpannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mNoteEditor.setText(finalSpannable);
|
|
|
|
|
// 将光标定位到文本末尾
|
|
|
|
|
mNoteEditor.setSelection(finalSpannable.length());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 隐藏所有背景选择的选中状态
|
|
|
|
|
@ -597,6 +679,22 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
if (mBtnInsertImage != null) {
|
|
|
|
|
mBtnInsertImage.setOnClickListener(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始化富文本工具栏按钮
|
|
|
|
|
mBtnBold = (ImageButton) findViewById(R.id.btn_bold);
|
|
|
|
|
mBtnItalic = (ImageButton) findViewById(R.id.btn_italic);
|
|
|
|
|
mBtnUnderline = (ImageButton) findViewById(R.id.btn_underline);
|
|
|
|
|
|
|
|
|
|
// 为富文本工具栏按钮设置点击监听器
|
|
|
|
|
if (mBtnBold != null) {
|
|
|
|
|
mBtnBold.setOnClickListener(this);
|
|
|
|
|
}
|
|
|
|
|
if (mBtnItalic != null) {
|
|
|
|
|
mBtnItalic.setOnClickListener(this);
|
|
|
|
|
}
|
|
|
|
|
if (mBtnUnderline != null) {
|
|
|
|
|
mBtnUnderline.setOnClickListener(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -674,6 +772,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
} else if (id == R.id.btn_insert_image) {
|
|
|
|
|
// 处理插入图片按钮点击事件 OMO
|
|
|
|
|
pickImageFromGallery();
|
|
|
|
|
} else if (id == R.id.btn_bold) {
|
|
|
|
|
// 富文本工具栏 - 加粗按钮
|
|
|
|
|
applyBoldStyle();
|
|
|
|
|
} else if (id == R.id.btn_italic) {
|
|
|
|
|
// 富文本工具栏 - 斜体按钮
|
|
|
|
|
applyItalicStyle();
|
|
|
|
|
} else if (id == R.id.btn_underline) {
|
|
|
|
|
// 富文本工具栏 - 下划线按钮
|
|
|
|
|
applyUnderlineStyle();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1159,7 +1266,16 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
}
|
|
|
|
|
mWorkingNote.setWorkingText(sb.toString());
|
|
|
|
|
} else {
|
|
|
|
|
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
|
|
|
|
|
// 保存时,将SpannableString转换为HTML格式以保留样式信息
|
|
|
|
|
Spannable spannable = mNoteEditor.getText();
|
|
|
|
|
if (spannable != null && spannable.length() > 0) {
|
|
|
|
|
// 将Spannable转换为HTML格式,保留加粗、斜体、下划线等样式
|
|
|
|
|
// Html.toHtml会自动处理StyleSpan和UnderlineSpan
|
|
|
|
|
String htmlContent = Html.toHtml(spannable);
|
|
|
|
|
mWorkingNote.setWorkingText(htmlContent);
|
|
|
|
|
} else {
|
|
|
|
|
mWorkingNote.setWorkingText("");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return hasChecked;
|
|
|
|
|
}
|
|
|
|
|
@ -1298,4 +1414,173 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
Log.d(TAG, "Image selection canceled or failed, resultCode: " + resultCode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用文本样式(加粗、斜体、下划线)
|
|
|
|
|
* @param styleType 样式类型:Typeface.BOLD, Typeface.ITALIC, 或 -1 表示下划线
|
|
|
|
|
*/
|
|
|
|
|
private void applyTextStyle(int styleType) {
|
|
|
|
|
if (mNoteEditor == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否在清单模式,如果是则提示用户切换到普通模式
|
|
|
|
|
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
|
|
|
|
|
showToast(R.string.error_formatting_not_supported_in_list_mode);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Editable editable = mNoteEditor.getText();
|
|
|
|
|
if (editable == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int selectionStart = mNoteEditor.getSelectionStart();
|
|
|
|
|
int selectionEnd = mNoteEditor.getSelectionEnd();
|
|
|
|
|
|
|
|
|
|
// 如果没有选中文本,则选中光标所在的单词或当前字符
|
|
|
|
|
if (selectionStart == selectionEnd) {
|
|
|
|
|
// 尝试选中光标所在的单词
|
|
|
|
|
String text = editable.toString();
|
|
|
|
|
int start = selectionStart;
|
|
|
|
|
int end = selectionEnd;
|
|
|
|
|
|
|
|
|
|
// 向前查找单词开始位置
|
|
|
|
|
while (start > 0 && Character.isLetterOrDigit(text.charAt(start - 1))) {
|
|
|
|
|
start--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 向后查找单词结束位置
|
|
|
|
|
while (end < text.length() && Character.isLetterOrDigit(text.charAt(end))) {
|
|
|
|
|
end++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果找到了单词,则选中它;否则选中当前字符
|
|
|
|
|
if (start < end) {
|
|
|
|
|
selectionStart = start;
|
|
|
|
|
selectionEnd = end;
|
|
|
|
|
mNoteEditor.setSelection(selectionStart, selectionEnd);
|
|
|
|
|
} else {
|
|
|
|
|
// 如果没有找到单词,提示用户先选择文本
|
|
|
|
|
showToast(R.string.error_please_select_text);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保选择范围有效
|
|
|
|
|
if (selectionStart < 0 || selectionEnd > editable.length() || selectionStart >= selectionEnd) {
|
|
|
|
|
showToast(R.string.error_please_select_text);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 应用样式
|
|
|
|
|
if (styleType == -1) {
|
|
|
|
|
// 下划线样式
|
|
|
|
|
UnderlineSpan[] underlineSpans = editable.getSpans(selectionStart, selectionEnd, UnderlineSpan.class);
|
|
|
|
|
if (underlineSpans.length > 0) {
|
|
|
|
|
// 移除下划线
|
|
|
|
|
for (UnderlineSpan span : underlineSpans) {
|
|
|
|
|
int spanStart = editable.getSpanStart(span);
|
|
|
|
|
int spanEnd = editable.getSpanEnd(span);
|
|
|
|
|
editable.removeSpan(span);
|
|
|
|
|
// 如果移除的span范围大于选中范围,需要重新应用样式到剩余部分
|
|
|
|
|
if (spanStart < selectionStart) {
|
|
|
|
|
editable.setSpan(new UnderlineSpan(), spanStart, selectionStart, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
if (spanEnd > selectionEnd) {
|
|
|
|
|
editable.setSpan(new UnderlineSpan(), selectionEnd, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 添加下划线
|
|
|
|
|
editable.setSpan(new UnderlineSpan(), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 加粗或斜体样式
|
|
|
|
|
StyleSpan[] styleSpans = editable.getSpans(selectionStart, selectionEnd, StyleSpan.class);
|
|
|
|
|
|
|
|
|
|
// 检查选中范围内是否已经有该样式
|
|
|
|
|
boolean hasStyle = false;
|
|
|
|
|
int existingStyle = 0;
|
|
|
|
|
|
|
|
|
|
// 收集选中范围内的所有样式
|
|
|
|
|
for (StyleSpan span : styleSpans) {
|
|
|
|
|
int spanStart = editable.getSpanStart(span);
|
|
|
|
|
int spanEnd = editable.getSpanEnd(span);
|
|
|
|
|
|
|
|
|
|
// 如果span与选中范围重叠
|
|
|
|
|
if (spanStart < selectionEnd && spanEnd > selectionStart) {
|
|
|
|
|
int spanStyle = span.getStyle();
|
|
|
|
|
existingStyle |= spanStyle;
|
|
|
|
|
|
|
|
|
|
// 检查是否包含目标样式
|
|
|
|
|
if ((spanStyle & styleType) == styleType) {
|
|
|
|
|
hasStyle = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 移除选中范围内的所有StyleSpan,以便重新应用
|
|
|
|
|
for (int i = styleSpans.length - 1; i >= 0; i--) {
|
|
|
|
|
StyleSpan span = styleSpans[i];
|
|
|
|
|
int spanStart = editable.getSpanStart(span);
|
|
|
|
|
int spanEnd = editable.getSpanEnd(span);
|
|
|
|
|
|
|
|
|
|
// 如果span与选中范围重叠
|
|
|
|
|
if (spanStart < selectionEnd && spanEnd > selectionStart) {
|
|
|
|
|
editable.removeSpan(span);
|
|
|
|
|
|
|
|
|
|
// 处理span范围大于选中范围的情况,保留未选中部分的样式
|
|
|
|
|
int spanStyle = span.getStyle();
|
|
|
|
|
if (spanStart < selectionStart) {
|
|
|
|
|
// 保留选中范围之前的样式
|
|
|
|
|
editable.setSpan(new StyleSpan(spanStyle), spanStart, selectionStart, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
if (spanEnd > selectionEnd) {
|
|
|
|
|
// 保留选中范围之后的样式
|
|
|
|
|
editable.setSpan(new StyleSpan(spanStyle), selectionEnd, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 切换样式:如果有则移除,如果没有则添加
|
|
|
|
|
int finalStyle = existingStyle;
|
|
|
|
|
if (hasStyle) {
|
|
|
|
|
// 移除目标样式
|
|
|
|
|
finalStyle &= ~styleType;
|
|
|
|
|
} else {
|
|
|
|
|
// 添加目标样式
|
|
|
|
|
finalStyle |= styleType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 应用最终样式
|
|
|
|
|
if (finalStyle != 0) {
|
|
|
|
|
editable.setSpan(new StyleSpan(finalStyle), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保持选中状态
|
|
|
|
|
mNoteEditor.setSelection(selectionStart, selectionEnd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用加粗样式
|
|
|
|
|
*/
|
|
|
|
|
private void applyBoldStyle() {
|
|
|
|
|
applyTextStyle(Typeface.BOLD);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用斜体样式
|
|
|
|
|
*/
|
|
|
|
|
private void applyItalicStyle() {
|
|
|
|
|
applyTextStyle(Typeface.ITALIC);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用下划线样式
|
|
|
|
|
*/
|
|
|
|
|
private void applyUnderlineStyle() {
|
|
|
|
|
applyTextStyle(-1); // -1 表示下划线
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|