You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MiNoteRead/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java

1569 lines
64 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, 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.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ImageButton;
import android.provider.MediaStore;
import android.net.Uri;
import java.io.File;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Editable;
import android.text.style.ImageSpan;
import android.graphics.drawable.Drawable;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import android.os.Environment;
import android.text.format.DateUtils;
import android.view.ViewGroup;
import android.content.ActivityNotFoundException;
import jp.wasabeef.richeditor.RichEditor;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.ChecklistManager;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 笔记编辑活动
* 负责处理笔记的创建、编辑和预览功能
* 实现了笔记设置变化监听器和文本视图变化监听器接口
*/
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
* 头部视图持有者类
* 用于缓存头部视图的控件引用,提高性能
*/
private class HeadViewHolder {
public TextView tvModified; // 最后修改时间文本视图
public ImageView ivAlertIcon; // 提醒图标
public TextView tvAlertDate; // 提醒日期文本视图
public ImageView ibSetBgColor; // 设置背景颜色按钮
}
/**
* 背景选择器按钮映射
* 用于将背景选择按钮ID映射到对应的背景颜色常量
*/
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
/**
* 背景选择器选中状态映射
* 用于将背景颜色常量映射到对应的选中状态图标ID
*/
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
/**
* 字体大小按钮映射
* 用于将字体大小选择按钮ID映射到对应的字体大小常量
*/
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
/**
* 字体选择器选中状态映射
* 用于将字体大小常量映射到对应的选中状态图标ID
*/
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity"; // 日志标签
private static final int REQUEST_CODE_PICK_IMAGE = 100; // 图片选择请求码 OMO
private static final int PHOTO_REQUEST = 100; // 请求照片
private HeadViewHolder mNoteHeaderHolder; // 头部视图持有者
private View mHeadViewPanel; // 头部视图面板
private View mNoteBgColorSelector; // 背景颜色选择器
private View mFontSizeSelector; // 字体大小选择器
private RichEditor mNoteEditor; // 富文本编辑器
private View mNoteEditorPanel; // 笔记编辑器面板
private WorkingNote mWorkingNote; // 工作笔记对象
private SharedPreferences mSharedPrefs; // 共享偏好设置
private int mFontSizeId; // 字体大小ID
private ImageButton mBtnInsertImage; // 插入图片按钮 OMO
private String mText; // 用于存储富文本内容
private int mNoteLength; // 文本长度
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷图标标题最大长度
public static final String TAG_CHECKED = String.valueOf('\u221A'); // 已勾选标记
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未勾选标记
private LinearLayout mEditTextList; // 编辑文本列表(用于清单模式)
private String mUserQuery; // 用户查询字符串
private Pattern mPattern; // 正则表达式模式(用于高亮查询结果)
private ChecklistManager mChecklistManager; // 清单管理器OMO
/**
* 创建活动时调用
* @param savedInstanceState 保存的实例状态
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit); // 设置布局
initResources(); // 先初始化资源再初始化活动状态OMO
// 初始化清单管理器
mChecklistManager = new ChecklistManager(getContentResolver());
// 如果没有保存的状态且无法初始化活动状态,则结束活动
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
}
/**
* 当前活动在内存不足时可能被杀死。一旦被杀死,当用户再次加载此活动时,我们应该恢复以前的状态
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
/**
* 初始化活动状态
* 根据传入的意图设置工作笔记的状态
* @param intent 传入的意图
* @return 是否初始化成功
*/
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
/**
* Starting from the searched result
*/
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
}
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE) && !DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_CHECKLIST)) {
Intent jump = new Intent(this, NotesListActivity.class);
startActivity(jump);
showToast(R.string.error_note_not_exist);
finish();
return false;
} else {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
finish();
return false;
}
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {
if (TextUtils.isEmpty(phoneNumber)) {
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate);
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId);
// 设置笔记类型
int noteType = intent.getIntExtra(Notes.INTENT_EXTRA_NOTE_TYPE, Notes.TYPE_NOTE);
// 使用ChecklistManager处理清单模式标记OMO
if (mChecklistManager != null) {
mChecklistManager.handleChecklistModeFromIntent(intent, mWorkingNote);
}
}
// 处理加密便签
boolean isEncrypted = intent.getBooleanExtra("is_encrypted", false);
if (isEncrypted) {
String password = intent.getStringExtra("password");
if (password != null && !password.isEmpty()) {
mWorkingNote.setEncrypted(true);
// 保存密码(需要在便签保存后)
mWorkingNote.setPassword(password);
}
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
Log.e(TAG, "Intent not specified action, should not support");
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);
return true;
}
/**
* 活动恢复时调用
*/
@Override
protected void onResume() {
super.onResume();
initNoteScreen(); // 初始化笔记屏幕
}
/**
* 初始化笔记屏幕
* 设置笔记编辑器的文本外观、内容和选择模式
*/
private void initNoteScreen() {
// 检查必要的视图是否已初始化
if (mHeadViewPanel == null || mNoteEditorPanel == null || mNoteEditor == null) {
Log.e(TAG, "Some views are not initialized! Check initResources method.");
return;
}
// 设置富文本编辑器字体大小
setRichEditorFontSize(mFontSizeId);
// 根据笔记模式设置显示方式
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent()); // 切换到清单模式
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
} else {
// 切换到富文本模式
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
// 获取笔记原始内容(为空则赋空字符串,避免空指针)
String content = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent();
// 检查内容是否已经是HTML格式如果是则直接加载
String finalHtml = content;
// 如果内容不是HTML格式比如旧笔记则处理图片标记
if (!TextUtils.isEmpty(content) && (!content.startsWith("<") || !content.contains("</"))) {
// 处理 [IMAGE] 标签,转换为 HTML img 标签
Pattern imgPattern = Pattern.compile("\\[IMAGE\\]([^\\[]+)\\[/IMAGE\\]");
Matcher imgMatcher = imgPattern.matcher(content);
StringBuffer htmlContent = new StringBuffer();
// 遍历替换所有[IMAGE]标记为<img>标签
while (imgMatcher.find()) {
String imgUri = imgMatcher.group(1); // 提取图片URI
// 检查是否为本地文件路径
if (imgUri.startsWith("file://") || imgUri.startsWith("/")) {
String imgPath = imgUri.startsWith("file://") ? imgUri.substring(7) : imgUri;
File imgFile = new File(imgPath);
if (imgFile.exists() && imgFile.isFile()) {
String imgHtmlUrl = "file://" + imgPath;
String imgHtmlTag = "<img src=\"" + imgHtmlUrl + "\" width=\"200\" height=\"200\"/><br/>";
imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
} else {
// 如果文件不存在,保留原标记
imgMatcher.appendReplacement(htmlContent, imgMatcher.group(0));
}
} else {
// URI格式直接使用
String imgHtmlTag = "<img src=\"" + imgUri + "\" width=\"200\" height=\"200\"/><br/>";
imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
}
}
imgMatcher.appendTail(htmlContent); // 拼接剩余文本
finalHtml = htmlContent.toString();
}
// 用RichEditor加载HTML内容
mNoteEditor.setHtml(finalHtml);
}
// 隐藏所有背景选择的选中状态
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
// 设置头部视图和编辑器面板的背景
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
// 设置最后修改时间
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader(); // 显示提醒头部
}
/**
* 将包含[IMAGE]标签的文本转换为包含ImageSpan的SpannableString
* @param text 包含[IMAGE]标签的文本
* @return 包含ImageSpan的SpannableString
*/
private SpannableString convertTextToSpannableWithImages(String text) {
if (TextUtils.isEmpty(text)) {
return new SpannableString("");
}
// 创建一个新的SpannableStringBuilder
SpannableStringBuilder builder = new SpannableStringBuilder();
// 查找所有[IMAGE]标签
int startIndex = 0;
int imageStart = text.indexOf("[IMAGE]", startIndex);
while (imageStart != -1) {
// 添加[IMAGE]标签之前的文本
builder.append(text.substring(startIndex, imageStart));
// 查找当前[IMAGE]标签的结束位置
int imageEnd = text.indexOf("[/IMAGE]", imageStart + "[IMAGE]".length());
if (imageEnd == -1) {
// 如果没有找到结束标签,添加剩余文本并退出循环
builder.append(text.substring(imageStart));
break;
}
// 提取图片URI
String imageUri = text.substring(imageStart + "[IMAGE]".length(), imageEnd);
Log.d(TAG, "Found image URI: " + imageUri);
// 创建一个占位符字符串用于插入ImageSpan
String placeholder = " ";
int placeholderStart = builder.length();
builder.append(placeholder);
// 尝试将图片URI转换为Drawable并创建ImageSpan
try {
ContentResolver resolver = getContentResolver();
InputStream inputStream = resolver.openInputStream(Uri.parse(imageUri));
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
inputStream.close();
// 计算图片的合适大小确保它能适应EditText的宽度
int screenWidth = getResources().getDisplayMetrics().widthPixels;
int imageWidth = bitmap.getWidth();
int imageHeight = bitmap.getHeight();
float scale = (float) screenWidth / (float) imageWidth;
int scaledHeight = (int) (imageHeight * scale);
// 缩放图片
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, screenWidth, scaledHeight, true);
Drawable drawable = new android.graphics.drawable.BitmapDrawable(getResources(), scaledBitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
// 创建ImageSpan并添加到SpannableStringBuilder中
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
builder.setSpan(imageSpan, placeholderStart, placeholderStart + placeholder.length(), SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
Log.e(TAG, "Error loading image: " + e.toString());
// 如果加载图片失败直接显示图片URI
builder.append("[IMAGE]");
builder.append(imageUri);
builder.append("[/IMAGE]");
}
// 更新索引,继续查找下一个[IMAGE]标签
startIndex = imageEnd + "[/IMAGE]".length();
imageStart = text.indexOf("[IMAGE]", startIndex);
}
// 添加剩余的文本
builder.append(text.substring(startIndex));
return new SpannableString(builder);
}
/**
* 显示提醒头部
* 根据笔记的提醒设置显示或隐藏提醒信息
*/
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis();
if (time > mWorkingNote.getAlertDate()) {
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);
} else {
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
};
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
/**
* 保存活动状态
* @param outState 保存状态的Bundle
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
/**
* 分发触摸事件
* 处理背景颜色选择器和字体大小选择器的点击外部关闭逻辑
* @param ev 触摸事件
* @return 是否消费了事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return super.dispatchTouchEvent(ev);
}
/**
* 检查触摸事件是否在视图范围内
* @param view 视图对象
* @param ev 触摸事件
* @return 是否在范围内
*/
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
}
/**
* 初始化资源
* 初始化所有UI控件和资源引用
*/
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
// 初始化富文本编辑器
mNoteEditor = (RichEditor) findViewById(R.id.note_edit_view);
if (mNoteEditor == null) {
Log.e(TAG, "RichEditor is null! Check layout file.");
return;
}
// 初始化富文本编辑器配置
initRichEditor();
// 设置富文本编辑器监听器
mNoteEditor.setOnTextChangeListener(new RichEditor.OnTextChangeListener() {
@Override
public void onTextChange(String text) {
mText = text;
mNoteLength = text.length();
// 更新修改时间和字符数显示
mNoteHeaderHolder.tvModified.setText(
DateUtils.formatDateTime(NoteEditActivity.this,
mWorkingNote.getModifiedDate(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE
| DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)
+ "\n字符数" + mNoteLength
);
}
});
// 开启图文混排支持
mNoteEditor.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
// 为所有背景选择按钮设置点击监听器
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);
// 为所有字体大小选择按钮设置点击监听器
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};
// 初始化共享偏好设置和字体大小
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
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()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
// 初始化插入图片按钮 OMO
mBtnInsertImage = (ImageButton) findViewById(R.id.btn_insert_image);
if (mBtnInsertImage != null) {
mBtnInsertImage.setOnClickListener(this);
}
// 初始化富文本功能按钮
initRichEditorButtons();
}
/**
* 活动暂停时调用
* 保存笔记并清除设置状态
*/
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
/**
* 更新小部件
* 更新与当前笔记关联的小部件内容
*/
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据小部件类型设置对应的小部件提供器
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent); // 发送广播更新小部件
setResult(RESULT_OK, intent);
}
/**
* 点击事件处理
* 处理各种UI控件的点击事件
* @param v 被点击的视图
*/
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
// 显示背景颜色选择器
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
// 处理背景颜色选择
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
// 处理字体大小选择
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
// 根据当前模式更新字体大小
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
// 设置富文本编辑器字体大小
setRichEditorFontSize(mFontSizeId);
}
mFontSizeSelector.setVisibility(View.GONE);
} else if (id == R.id.btn_insert_image) {
// 处理插入图片按钮点击事件 OMO
pickImageFromGallery();
}
}
/**
* 按下返回键时调用
*/
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
/**
* 清除设置状态
* 隐藏所有设置面板
* @return 是否有设置面板被隐藏
*/
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
/**
* 背景颜色改变时调用
* 实现NoteSettingChangedListener接口
*/
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
/**
* 准备菜单时调用
* @param menu 菜单对象
* @return 是否准备成功
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
/**
* 菜单项选择处理
* 处理各种菜单按钮的点击事件
* @param item 选择的菜单项
* @return 是否处理了该事件
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote(); // 创建新笔记
break;
case R.id.menu_delete:
// 显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote(); // 删除当前笔记
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
// 显示字体大小选择器
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
// 切换笔记模式(普通模式/清单模式)
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText(); // 获取当前工作文本
sendTo(this, mWorkingNote.getContent()); // 分享笔记内容
break;
case R.id.menu_send_to_desktop:
sendToDesktop(); // 发送到桌面
break;
case R.id.menu_alert:
setReminder(); // 设置提醒
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false); // 删除提醒
break;
default:
break;
}
return true;
}
/**
* 设置提醒
* 显示日期时间选择器,设置笔记的提醒时间
*/
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);
}
});
d.show();
}
/**
* 分享笔记
* 分享笔记内容到支持{@link Intent#ACTION_SEND}动作和{@text/plain}类型的应用
* @param context 上下文对象
* @param info 要分享的内容
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
/**
* 创建新笔记
* 保存当前笔记并创建一个新的笔记
*/
private void createNewNote() {
// 首先保存当前编辑的笔记
saveNote();
// 为了安全起见启动一个新的NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
/**
* 删除当前笔记
* 根据同步模式决定是直接删除还是移动到回收站
*/
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
// 非同步模式下直接删除
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids, NoteEditActivity.this)) {
Log.e(TAG, "Delete Note error");
}
} else {
// 同步模式下移动到回收站
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);
}
/**
* 检查是否为同步模式
* @return 是否为同步模式
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 提醒时间改变时调用
* 实现NoteSettingChangedListener接口
* @param date 提醒时间
* @param set 是否设置提醒
*/
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
/**
* 小部件改变时调用
* 实现NoteSettingChangedListener接口
*/
public void onWidgetChanged() {
updateWidget();
}
/**
* 编辑文本删除时调用
* 实现OnTextViewChangeListener接口
* @param index 删除的索引
* @param text 删除的文本
*/
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);
NoteEditText edit = null;
if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();
edit.setSelection(length);
}
/**
* 编辑文本回车时调用
* 实现OnTextViewChangeListener接口
* @param index 当前索引
* @param text 文本内容
*/
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
/**
* 切换到清单模式
* 将普通文本模式转换为清单模式
* @param text 要转换的文本内容
*/
private void switchToListMode(String text) {
// 处理null文本避免空指针异常OMO
if (text == null) {
text = "";
}
mEditTextList.removeAllViews(); // 清空编辑文本列表
String[] items = text.split("\n"); // 按行分割文本
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index)); // 添加列表项
index++;
}
}
mEditTextList.addView(getListItem("", index)); // 添加最后一个空列表项
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); // 设置焦点
mNoteEditor.setVisibility(View.GONE); // 隐藏普通编辑器
mEditTextList.setVisibility(View.VISIBLE); // 显示清单编辑器
}
/**
* 获取高亮查询结果
* 对文本中的查询关键词进行高亮处理
* @param fullText 完整文本
* @param userQuery 用户查询字符串
* @return 带有高亮效果的文本
*/
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
String text = fullText == null ? "" : fullText;
SpannableString spannable = new SpannableString(text);
// 处理查询关键词高亮
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery); // 编译正则表达式
Matcher m = mPattern.matcher(text); // 创建匹配器
int start = 0;
while (m.find(start)) {
// 设置高亮背景色
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
/**
* 获取列表项视图
* 创建并初始化清单模式下的单个列表项视图
* @param item 列表项文本
* @param index 列表项索引
* @return 列表项视图
*/
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
// 设置复选框的选中状态变化监听器
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
// 处理已勾选和未勾选的标记
if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
item = item.substring(TAG_CHECKED.length(), item.length()).trim();
} else if (item.startsWith(TAG_UNCHECKED)) {
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
/**
* 文本内容改变时调用
* 实现OnTextViewChangeListener接口
* @param index 当前索引
* @param hasText 是否有文本内容
*/
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
/**
* 清单模式改变时调用
* 实现NoteSettingChangedListener接口
* @param oldMode 旧模式
* @param newMode 新模式
*/
public void onCheckListModeChanged(int oldMode, int newMode) {
// 添加null检查避免在mNoteEditor初始化前调用导致闪退
if (mNoteEditor == null || mEditTextList == null) {
return;
}
String text = "";
if (newMode == TextNote.MODE_CHECK_LIST) {
// 安全获取文本避免空指针异常OMO
String htmlContent = mNoteEditor.getHtml();
if (htmlContent != null) {
text = htmlContent;
}
switchToListMode(text);
} else {
if (!getWorkingText()) {
String content = mWorkingNote.getContent();
if (content != null) {
mWorkingNote.setWorkingText(content.replace(TAG_UNCHECKED + " ", ""));
}
}
String content = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent();
// 处理内容为HTML格式
String finalHtml = content;
if (!TextUtils.isEmpty(content) && (!content.startsWith("<") || !content.contains("</"))) {
// 处理 [IMAGE] 标签
Pattern imgPattern = Pattern.compile("\\[IMAGE\\]([^\\[]+)\\[/IMAGE\\]");
Matcher imgMatcher = imgPattern.matcher(content);
StringBuffer htmlContent = new StringBuffer();
while (imgMatcher.find()) {
String imgUri = imgMatcher.group(1);
if (imgUri.startsWith("file://") || imgUri.startsWith("/")) {
String imgPath = imgUri.startsWith("file://") ? imgUri.substring(7) : imgUri;
File imgFile = new File(imgPath);
if (imgFile.exists() && imgFile.isFile()) {
String imgHtmlUrl = "file://" + imgPath;
String imgHtmlTag = "<img src=\"" + imgHtmlUrl + "\" width=\"200\" height=\"200\"/><br/>";
imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
} else {
imgMatcher.appendReplacement(htmlContent, imgMatcher.group(0));
}
} else {
String imgHtmlTag = "<img src=\"" + imgUri + "\" width=\"200\" height=\"200\"/><br/>";
imgMatcher.appendReplacement(htmlContent, imgHtmlTag);
}
}
imgMatcher.appendTail(htmlContent);
finalHtml = htmlContent.toString();
}
mNoteEditor.setHtml(finalHtml);
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
}
/**
* 获取当前工作文本
* 根据笔记模式获取当前编辑的文本内容
* @return 是否有已勾选的项目
*/
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true;
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
}
}
}
mWorkingNote.setWorkingText(sb.toString());
} else {
// 确保获取最新的富文本内容
String currentHtml = mNoteEditor.getHtml();
mWorkingNote.setWorkingText(currentHtml);
mText = currentHtml; // 更新mText变量确保保存时使用最新内容
}
return hasChecked;
}
/**
* 保存笔记
* @return 是否保存成功
*/
private boolean saveNote() {
getWorkingText(); // 获取当前工作文本
boolean saved = mWorkingNote.saveNote(); // 保存笔记到数据库
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
}
/**
* 发送到桌面
* 在桌面上创建当前笔记的快捷方式
*/
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent();
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
/**
* 创建快捷图标标题
* 处理快捷图标标题,移除标记并限制长度
* @param content 笔记内容
* @return 快捷图标标题
*/
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
/**
* 显示短时间提示
* @param resId 提示文本资源ID
*/
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
/**
* 显示提示
* @param resId 提示文本资源ID
* @param duration 提示持续时间
*/
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
/**
* 从相册选择图片 OMO
*/
private void pickImageFromGallery() {
try {
// 意图:打开系统相册选择图片
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType("image/*"); // 只显示图片类型
startActivityForResult(intent, PHOTO_REQUEST); // 启动相册,等待返回结果
} catch (ActivityNotFoundException e) {
// 如果没有相册应用,尝试使用通用选择器
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
try {
startActivityForResult(intent, PHOTO_REQUEST);
} catch (ActivityNotFoundException ex) {
showToast(R.string.error_note_not_exist);
Log.e(TAG, "No image picker available", ex);
}
}
}
/**
* 处理图片选择结果 OMO
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PHOTO_REQUEST && resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
String localImagePath = saveImageToLocal(uri);
if (localImagePath == null) return; // 保存失败就退出
// 核心修改适配RichEditor替换EditText的ImageSpan逻辑
// 1. 拼接RichEditor支持的<img>标签必须加file://前缀)
String imgUrl = "file://" + localImagePath;
String imgHtmlTag = "<img src=\"" + imgUrl + "\" width=\"200\" height=\"200\"/><br/>";
// 2. 插入图片到RichEditor
String curHtml = mNoteEditor.getHtml(); // 获取当前内容
String newHtml = curHtml + imgHtmlTag; // 追加图片标签
mNoteEditor.setHtml(newHtml); // 重新设置内容,实现插入
// 弹窗依然保留
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("图片选择成功!");
ImageView imageView = new ImageView(this);
imageView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)); // 加布局参数,避免图片显示不全
imageView.setImageURI(Uri.fromFile(new File(localImagePath)));//弹窗也显示本地图片
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); // 适配图片大小
builder.setView(imageView);
builder.setPositiveButton("确认保存", (dialog, which) -> {
String currentHtml = mNoteEditor.getHtml(); // 替换原EditText的getText()
String newContent = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent();
newContent += "\n[IMAGE]" + localImagePath + "[/IMAGE]"; // 保留原有[IMAGE]标记,供后续加载解析
Log.d("NoteDebug", "准备保存的内容:" + newContent); // 看Logcat里的输出
// 执行保存操作
mWorkingNote.setWorkingText(newContent);
boolean isSaved = mWorkingNote.saveNote();
// 根据保存结果提示(更友好)
if (isSaved) {
Toast.makeText(this, "图片信息已保存!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "保存失败,请重试", Toast.LENGTH_SHORT).show();
}
});
builder.show();
}
}
// 新增工具方法把临时URI的图片复制到应用私有目录返回真实路径
private String saveImageToLocal(Uri uri) {
try {
// 1. 创建应用专属图片目录(不会被系统清理)
File appDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "note_images");
if (!appDir.exists()) appDir.mkdirs();
// 2. 生成唯一文件名(避免重复)
String fileName = "note_" + System.currentTimeMillis() + ".jpg";
File targetFile = new File(appDir, fileName);
// 3. 复制图片文件从临时URI到本地目录
InputStream is = getContentResolver().openInputStream(uri);
OutputStream os = new FileOutputStream(targetFile);
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
is.close();
os.close();
// 返回图片的真实本地路径不是URI
return targetFile.getAbsolutePath();
} catch (Exception e) {
Log.e("NoteEdit", "保存图片失败", e);
Toast.makeText(this, "图片保存失败", Toast.LENGTH_SHORT).show();
return null;
}
}
// 自定义方法给RichEditor设置字体大小对应原EditText的setTextAppearance
private void setRichEditorFontSize(int fontSizeId) {
switch (fontSizeId) {
case ResourceParser.TEXT_SMALL:
mNoteEditor.setEditorFontSize(14); // 小字体
break;
case ResourceParser.TEXT_MEDIUM:
mNoteEditor.setEditorFontSize(18); // 中字体(默认)
break;
case ResourceParser.TEXT_LARGE:
mNoteEditor.setEditorFontSize(22); // 大字体
break;
case ResourceParser.TEXT_SUPER:
mNoteEditor.setEditorFontSize(26); // 超大字体
break;
default:
mNoteEditor.setEditorFontSize(18); // 默认值
}
}
// 初始化富文本编辑器配置
private void initRichEditor() {
mNoteEditor.setEditorHeight(600); // 设置编辑器高度
mNoteEditor.setEditorFontSize(16); // 字体大小
mNoteEditor.setEditorFontColor(Color.BLACK); // 字体颜色
mNoteEditor.setPadding(10, 10, 10, 10); // 内边距
mNoteEditor.setPlaceholder("请输入笔记内容..."); // 占位提示
mNoteEditor.setInputEnabled(true); // 允许输入
}
// 添加富文本功能按钮初始化方法
private void initRichEditorButtons() {
// 撤销功能
findViewById(R.id.action_undo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNoteEditor.undo();
}
});
// 加粗功能
findViewById(R.id.action_bold).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNoteEditor.setBold();
}
});
// 斜体功能
findViewById(R.id.action_italic).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNoteEditor.setItalic();
}
});
// 背景色功能
findViewById(R.id.action_bg_color).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNoteEditor.focusEditor(); // 获取焦点
new AlertDialog.Builder(NoteEditActivity.this)
.setTitle("选择字体背景颜色")
.setItems(new String[]{"红色", "黄色", "蓝色", "绿色", "黑色", "白色"},
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0: // 红
mNoteEditor.setTextBackgroundColor(Color.RED);
break;
case 1: // 黄
mNoteEditor.setTextBackgroundColor(Color.YELLOW);
break;
case 2: // 蓝
mNoteEditor.setTextBackgroundColor(Color.BLUE);
break;
case 3: // 绿
mNoteEditor.setTextBackgroundColor(Color.GREEN);
break;
case 4: // 黑
mNoteEditor.setTextBackgroundColor(Color.BLACK);
break;
case 5: // 白
mNoteEditor.setTextBackgroundColor(Color.WHITE);
break;
}
dialog.dismiss(); // 选择后关闭对话框
}
})
.show();
}
});
}
}