diff --git a/src/ui/NoteEditActivity.java b/src/ui/NoteEditActivity.java index 3ce63a7..6375937 100644 --- a/src/ui/NoteEditActivity.java +++ b/src/ui/NoteEditActivity.java @@ -27,7 +27,13 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.Paint; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; @@ -35,6 +41,7 @@ import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; +import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -42,6 +49,7 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -51,6 +59,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import android.text.InputType; +import android.content.ContentValues; + import net.micode.notes.R; import net.micode.notes.data.Notes; @@ -65,26 +76,32 @@ import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; +import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import jp.wasabeef.richeditor.RichEditor; + public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { - private class HeadViewHolder {//头视图持有者类??? - public TextView tvModified;//文本修改 - public ImageView ivAlertIcon;//图像提醒按钮? - public TextView tvAlertDate;//文本提醒日期? - public ImageView ibSetBgColor;//设置背景颜色图像视图 + private class HeadViewHolder { + public TextView tvModified; + + public ImageView ivAlertIcon; + + public TextView tvAlertDate; + + public ImageView ibSetBgColor; } - private static final Map sBgSelectorBtnsMap = new HashMap();//背景颜色选择按钮映射 + private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); @@ -92,7 +109,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } - //上下是反的为啥?构建双向印射关系,方便根据颜色值获取对应的选择按钮ID(比如当前背景是红的,红的按钮就是按下去的),或者根据选择按钮ID获取对应的颜色值 + private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,14 +119,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } - private static final Map sFontSizeBtnsMap = new HashMap();//字体大小选择按钮映射 + private static final Map sFontSizeBtnsMap = new HashMap(); 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); } -//同颜色的映射关系 + private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -128,7 +145,6 @@ public class NoteEditActivity extends Activity implements OnClickListener, private View mFontSizeSelector; - private EditText mNoteEditor; private View mNoteEditorPanel; @@ -141,36 +157,38 @@ public class NoteEditActivity extends Activity implements OnClickListener, private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + private static final int PHOTO_REQUEST =100; //请求照片 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 RichEditor mNoteEditor; // 替换原来的EditText + private String mText; // 用于存储富文本内容 + private int mNoteLength; // 文本长度 + private ImageInsertHelper mImageInsertHelper; @Override - protected void onCreate(Bundle savedInstanceState) {//创建活动时调用 + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); - if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } - initResources();//初始化资源 + initResources(); } - /** * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state/ + * user load this activity, we should restore the former state */ @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) {//存储uid便于恢复活动状态时调用 - super.onRestoreInstanceState(savedInstanceState); //使用 Intent Extra 在组件之间传递uid + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { - Intent intent = new Intent(Intent.ACTION_VIEW); //创建一个意图,用于显示笔记详情,大小是(action_view显示数据功能) - intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));//将uid添加到意图中,用于之后再调出来 + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { finish(); return; @@ -185,56 +203,62 @@ public class NoteEditActivity extends Activity implements OnClickListener, * then jump to the NotesListActivity */ mWorkingNote = null; - if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {//如果意图是查看操作 - long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);//获取uid - mUserQuery = "";//初始化用户查询,用于后续搜索 + // 新增:Intent/Action 判空 + if (intent == null || TextUtils.isEmpty(intent.getAction())) { + Log.e(TAG, "Intent 或 Action 为空"); + finish(); + return false; + } + 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));//解析出笔记id - mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);//解析出用户查询 + 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)) {//如果笔记数据库中不存在该笔记 + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); - startActivity(jump);//执行跳转 - showToast(R.string.error_note_not_exist);//显示笔记不存在的提示 + startActivity(jump); + showToast(R.string.error_note_not_exist); finish(); return false; } else { - mWorkingNote = WorkingNote.load(this, noteId);//加载笔记 + 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())) {//如果意图是插入或编辑操作 + 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);//获取文件夹id + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID);//获取小部件id + AppWidgetManager.INVALID_APPWIDGET_ID); int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, - Notes.TYPE_WIDGET_INVALIDE);//获取小部件类型 + Notes.TYPE_WIDGET_INVALIDE); int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, - ResourceParser.getDefaultBgId(this));//获取背景资源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) {//如果调用日期和电话号码都不为空 + 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;//初始化笔记id + long noteId = 0; if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), - phoneNumber, callDate)) > 0) {//如果记录中根据电话号码和调用日期查询到笔记id - mWorkingNote = WorkingNote.load(this, noteId);//加载笔记 + phoneNumber, callDate)) > 0) { + mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load call note failed with note id" + noteId); finish(); @@ -242,15 +266,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, - widgetType, bgResId);//新建笔记 - mWorkingNote.convertToCallNote(phoneNumber, callDate);//将笔记转换为call笔记 + widgetType, bgResId); + mWorkingNote.convertToCallNote(phoneNumber, callDate); } } else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, - bgResId);//新建笔记 + bgResId); } - getWindow().setSoftInputMode(//设置软键盘操作 + getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); } else { @@ -258,51 +282,84 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } - mWorkingNote.setOnSettingStatusChangedListener(this);//设置笔记设置状态改变监听器 + mWorkingNote.setOnSettingStatusChangedListener(this); return true; } @Override - protected void onResume() {//已经oncreate后切出,再次调用时,初始化笔记界面 + protected void onResume() { super.onResume(); initNoteScreen(); } private void initNoteScreen() { - mNoteEditor.setTextAppearance(this, TextAppearanceResources - .getTexAppearanceResource(mFontSizeId));//设置文本字体外观资源 - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//根据笔记模式( 清单模式 或 普通模式 )初始化编辑器内容 - switchToListMode(mWorkingNote.getContent());//切换到列表清单模式 - } else { - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));//设置文本内容为高亮查询结果 - mNoteEditor.setSelection(mNoteEditor.getText().length());//将光标定位到文本末尾,方便用户继续编辑 + // 检查必要的视图是否已初始化 + if (mHeadViewPanel == null || mNoteEditorPanel == null || mNoteEditor == null) { + Log.e(TAG, "Some views are not initialized! Check initResources method."); + return; } - for (Integer id : sBgSelectorSelectionMap.keySet()) {//隐藏所有背景色选择器的 选中状态指示器 - findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); + + // 设置富文本编辑器字体大小 + setRichEditorFontSize(mFontSizeId); + + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + switchToListMode(mWorkingNote.getContent()); + mNoteEditor.setVisibility(View.GONE); + mEditTextList.setVisibility(View.VISIBLE); } - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//设置笔记标题界面的 背景资源 - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());设置笔记编辑区界面的 背景资源 + else { + // 切换到富文本模式 + mEditTextList.setVisibility(View.GONE); + mNoteEditor.setVisibility(View.VISIBLE); + + // 1. 获取笔记原始内容(为空则赋空字符串,避免空指针) + String content = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent(); - mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, //显示笔记的 最后修改日期 ,并格式化显示 + // 2. 旧文本(非 HTML)转换为 HTML,确保图片与换行能正确展示 + String finalHtml; + if (TextUtils.isEmpty(content)) { + finalHtml = ""; + } else if (isHtmlContent(content)) { + finalHtml = content; + } else { + finalHtml = convertLegacyContentToHtml(content); + } + + // 3. 核心:用 RichEditor 加载 HTML 内容 + mNoteEditor.setHtml(finalHtml); + } + + // 设置背景颜色 + for (Integer id : sBgSelectorSelectionMap.keySet()) { + View v = findViewById(sBgSelectorSelectionMap.get(id)); + if (v != null) { + v.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(); } - - private void showAlertHeader() {//显示笔记的 闹钟提醒 信息 + //RichEditor类文件 + public interface OnTextChangeListener { + void onTextChange(String text); + } + private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); if (time > mWorkingNote.getAlertDate()) { - mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);//显示已过期信息 + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( - mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));//显示剩余时间信息 + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); @@ -313,13 +370,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, } @Override - protected void onNewIntent(Intent intent) {//处理新的intent + protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } @Override - protected void onSaveInstanceState(Bundle outState) {//保存活动状态 + protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); /** * For new note without note id, we should firstly save it to @@ -334,23 +391,22 @@ public class NoteEditActivity extends Activity implements OnClickListener, } @Override - public boolean dispatchTouchEvent(MotionEvent ev) {//处理触摸事件 + public boolean dispatchTouchEvent(MotionEvent ev) { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mNoteBgColorSelector, ev)) {//如果背景色选择器可见,且触摸事件不在范围内? - mNoteBgColorSelector.setVisibility(View.GONE);//隐藏背景色选择器 + && !inRangeOfView(mNoteBgColorSelector, ev)) { + mNoteBgColorSelector.setVisibility(View.GONE); return true; } if (mFontSizeSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mFontSizeSelector, ev)) {//如果字体大小选择器可见,且触摸事件不在范围内? - mFontSizeSelector.setVisibility(View.GONE);//隐藏字体大小选择器 + && !inRangeOfView(mFontSizeSelector, ev)) { + mFontSizeSelector.setVisibility(View.GONE); return true; } - return super.dispatchTouchEvent(ev);//将事件传递给父类处理 + return super.dispatchTouchEvent(ev); } - - private boolean inRangeOfView(View view, MotionEvent ev) {//判断触摸事件是否在视图范围内 - int []location = new int[2];//获取视图在屏幕上的坐标,长度为2的整数数组 + private boolean inRangeOfView(View view, MotionEvent ev) { + int []location = new int[2]; view.getLocationOnScreen(location); int x = location[0]; int y = location[1]; @@ -358,12 +414,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, || ev.getX() > (x + view.getWidth()) || ev.getY() < y || ev.getY() > (y + view.getHeight())) { - return false; - } + return false; + } return true; } - private void initResources() {//初始化资源 + private void initResources() { + // 初始化mHeadViewPanel,这是关键修复 mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); @@ -371,43 +428,81 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); - mNoteEditor = (EditText) findViewById(R.id.note_edit_view); + + // 初始化富文本编辑器 + mNoteEditor = (RichEditor) findViewById(R.id.note_edit_view); + if (mNoteEditor == null) { + Log.e(TAG, "RichEditor is null! Check layout file."); + return; + } + + mImageInsertHelper = new ImageInsertHelper(this, PHOTO_REQUEST); + + // 初始化富文本编辑器配置 + initRichEditor(); + + // 设置富文本编辑器监听器 + mNoteEditor.setOnTextChangeListener(new RichEditor.OnTextChangeListener() { + @Override + public void onTextChange(String text) { + String safeText = text == null ? "" : text; + mText = safeText; + mNoteLength = safeText.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); + if (iv != null) { + iv.setOnClickListener(this); + } } mFontSizeSelector = findViewById(R.id.font_size_selector); for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); - view.setOnClickListener(this); + if (view != null) { + 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); + initRichEditorButtons(); } @Override - protected void onPause() {//在活动暂停时调用(切出去了) - super.onPause();//调用父类的onPause方法 + protected void onPause() { + super.onPause(); if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } - clearSettingState();//清除设置状态 + clearSettingState(); } - private void updateWidget() {//更新应用小部件 - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);//接受意图 + 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) { @@ -417,54 +512,53 @@ public class NoteEditActivity extends Activity implements OnClickListener, return; } - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {//添加应用小部件id - mWorkingNote.getWidgetId() + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { + mWorkingNote.getWidgetId() }); - sendBroadcast(intent);//发送广播更新应用小部件 - setResult(RESULT_OK, intent);//设置结果为成功,返回意图 + sendBroadcast(intent); + setResult(RESULT_OK, intent); } - public void onClick(View v) {//点击事件处理,语法细节没看 + public void onClick(View v) { int id = v.getId(); - if (id == R.id.btn_set_bg_color) {//如果点击的是设置背景颜色按钮 + 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)) {//如果点击的是背景颜色选择按钮 + 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)) {//如果点击的是字体大小选择按钮 + } 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 { - mNoteEditor.setTextAppearance(this, - TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + //mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + setRichEditorFontSize(mFontSizeId); } mFontSizeSelector.setVisibility(View.GONE); } } @Override - public void onBackPressed() {//返回键处理 + public void onBackPressed() { if(clearSettingState()) { return; } - saveNote();//保存笔记 + saveNote(); super.onBackPressed(); } - private boolean clearSettingState() {//清除设置状态 - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {// + private boolean clearSettingState() { + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { @@ -474,46 +568,51 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } - public void onBackgroundColorChanged() {// + public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE);//显示选中的背景颜色 - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());//设置笔记编辑面板的背景颜色 - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//设置标题栏的背景颜色 + View.VISIBLE); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } @Override - public boolean onPrepareOptionsMenu(Menu menu) {//准备选项菜单 + 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);//如果是通话记录文件夹, 通话记录转化为菜单 + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); + MenuItem addPicItem = menu.findItem(R.id.menu_picture_add); + if (addPicItem != null) { + addPicItem.setVisible(true); // 防止被默认隐藏 + } } if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);//如果是列表模式, 菜单标题为普通模式 + 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);//如果不是列表模式, 菜单标题为列表模式 + 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 { + } + else { menu.findItem(R.id.menu_delete_remind).setVisible(false); } return true; } @Override - public boolean onOptionsItemSelected(MenuItem item) {//处理选项菜单点击事件 + public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_new_note://新建笔记 + case R.id.menu_new_note: createNewNote(); break; - case R.id.menu_delete://删除笔记 - AlertDialog.Builder builder = new AlertDialog.Builder(this);//创建删除确认对话框 + 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)); @@ -527,50 +626,138 @@ public class NoteEditActivity extends Activity implements OnClickListener, builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; - case R.id.menu_font_size://字体大小选择 + case R.id.menu_font_size: mFontSizeSelector.setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; - case R.id.menu_list_mode://列表模式切换 + case R.id.menu_list_mode: mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; - case R.id.menu_share://分享笔记 + case R.id.menu_share: getWorkingText(); sendTo(this, mWorkingNote.getContent()); break; - case R.id.menu_send_to_desktop://发送到桌面 + case R.id.menu_send_to_desktop: sendToDesktop(); break; - case R.id.menu_alert://设置提醒 + case R.id.menu_alert: setReminder(); break; - case R.id.menu_delete_remind://删除提醒 + case R.id.menu_delete_remind: mWorkingNote.setAlertDate(0, false); break; + case R.id.menu_picture_add: + addPicture() ; + break; default: break; } return true; } -//下面是上文相关函数的具体实现 + private void setReminder() { + showReminderChoiceDialog(); + } + + private void showReminderChoiceDialog() { + final String[] options = new String[] { + getString(R.string.reminder_mode_absolute), + getString(R.string.reminder_mode_relative) + }; + new AlertDialog.Builder(this) + .setTitle(R.string.reminder_mode_title) + .setItems(options, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == 0) { + showAbsoluteReminderDialog(); + } else { + showRelativeReminderDialog(); + } + } + }) + .show(); + } + + private void showAbsoluteReminderDialog() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { public void OnDateTimeSet(AlertDialog dialog, long date) { - mWorkingNote.setAlertDate(date , true); + mWorkingNote.setAlertDate(date, true); } }); d.show(); } + private void showRelativeReminderDialog() { + final EditText hoursInput = new EditText(this); + final EditText minutesInput = new EditText(this); + final EditText secondsInput = new EditText(this); + hoursInput.setInputType(InputType.TYPE_CLASS_NUMBER); + minutesInput.setInputType(InputType.TYPE_CLASS_NUMBER); + secondsInput.setInputType(InputType.TYPE_CLASS_NUMBER); + hoursInput.setHint(R.string.reminder_hours_hint); + minutesInput.setHint(R.string.reminder_minutes_hint); + secondsInput.setHint(R.string.reminder_seconds_hint); + + LinearLayout container = new LinearLayout(this); + container.setOrientation(LinearLayout.VERTICAL); + int padding = (int) (getResources().getDisplayMetrics().density * 16); + container.setPadding(padding, padding, padding, padding); + container.addView(hoursInput); + container.addView(minutesInput); + container.addView(secondsInput); + + new AlertDialog.Builder(this) + .setTitle(getString(R.string.reminder_duration_title)) + .setView(container) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String hoursText = hoursInput.getText().toString().trim(); + String minutesText = minutesInput.getText().toString().trim(); + String secondsText = secondsInput.getText().toString().trim(); + if (TextUtils.isEmpty(hoursText) + && TextUtils.isEmpty(minutesText) + && TextUtils.isEmpty(secondsText)) { + showToast(R.string.reminder_duration_empty); + return; + } + int hours; + int minutes; + int seconds; + try { + hours = TextUtils.isEmpty(hoursText) ? 0 : Integer.parseInt(hoursText); + minutes = TextUtils.isEmpty(minutesText) ? 0 : Integer.parseInt(minutesText); + seconds = TextUtils.isEmpty(secondsText) ? 0 : Integer.parseInt(secondsText); + } catch (NumberFormatException e) { + showToast(R.string.reminder_duration_invalid); + return; + } + if (hours < 0 || minutes < 0 || seconds < 0 + || (hours == 0 && minutes == 0 && seconds == 0)) { + showToast(R.string.reminder_duration_invalid); + return; + } + long delta = hours * 60L * 60L * 1000L + + minutes * 60L * 1000L + + seconds * 1000L; + long target = System.currentTimeMillis() + delta; + mWorkingNote.setAlertDate(target, true); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + /** * Share note to apps that support {@link Intent#ACTION_SEND} action * and {@text/plain} type */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, info);//再intetn中添加标签为EXTRA_TEXT, 值为info + intent.putExtra(Intent.EXTRA_TEXT, info); intent.setType("text/plain"); context.startActivity(intent); } @@ -591,29 +778,37 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) {//如果不是根文件夹 - ids.add(id);//将笔记id添加到集合中 + 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)) {//批量删除笔记 - 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"); + long originFolderId = mWorkingNote.getFolderId(); + if (originFolderId <= 0) { + originFolderId = Notes.ID_ROOT_FOLDER; + } + if (!moveNoteToTrash(id, originFolderId)) { + if (!DataUtils.batchMoveToTrash(getContentResolver(), ids, originFolderId)) { + Log.e(TAG, "Move notes to trash folder error"); } } } mWorkingNote.markDeleted(true); } - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + private boolean moveNoteToTrash(long noteId, long originFolderId) { + ContentValues values = new ContentValues(); + values.put(Notes.NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER); + values.put(Notes.NoteColumns.ORIGIN_PARENT_ID, originFolderId); + values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1); + values.put(Notes.NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + int updated = getContentResolver().update( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + values, null, null); + return updated > 0; } - public void onClockAlertChanged(long date, boolean 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 @@ -624,7 +819,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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, 0); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); showAlertHeader(); if(!set) { @@ -647,34 +842,33 @@ public class NoteEditActivity extends Activity implements OnClickListener, updateWidget(); } - public void onEditTextDelete(int index, String text) {//处理清单模式下删除列表项 的操作 - int childCount = mEditTextList.getChildCount();//获取编辑框的子视图数量 + public void onEditTextDelete(int index, String text) { + int childCount = mEditTextList.getChildCount(); if (childCount == 1) { return; } - for (int i = index + 1; i < childCount; i++) {//遍历删除项(index处)之后的所有列表项 ,将它们的索引减1,不会影响到后续的列表项的索引,从而避免了索引错乱的问题 + for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i - 1); } - mEditTextList.removeViewAt(index);//删除index处的子图 - //亮点:小米笔记清单模式下的特殊设计 ,属于防误删的安全机制 ,主要为了保护用户内容,这是啥意思? + 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);// + R.id.et_edit_text); } - int length = edit.length();//获取删除项(index处)之前的列表项的文本长度 - edit.append(text);//将被删除项的文本追加到目标项末尾 - edit.requestFocus();//将焦点自动切换到目标项 - edit.setSelection(length);//将光标定位到合并前的文本末尾 + int length = edit.length(); + edit.append(text); + edit.requestFocus(); + edit.setSelection(length); } - public void onEditTextEnter(int index, String text) {//处理清单模式下添加列表项的操作 + public void onEditTextEnter(int index, String text) { /** * Should not happen, check for debug */ @@ -683,72 +877,139 @@ public class NoteEditActivity extends Activity implements OnClickListener, } View view = getListItem(text, index); - mEditTextList.addView(view, index);//将新的列表项视图添加到编辑框中,位置为index - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//获取新添加的列表项的编辑框 + 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++) {//索引加一 + edit.setSelection(0); + for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i); } } - private void switchToListMode(String text) {//将编辑框切换到清单模式 + private void switchToListMode(String 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(item, index)); index++; } } - mEditTextList.addView(getListItem("", index));//添加一个空列表项 - mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();//将焦点自动切换到新添加的空列表项的编辑框 + mEditTextList.addView(getListItem("", index)); + mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); - mNoteEditor.setVisibility(View.GONE);//隐藏编辑框 + mNoteEditor.setVisibility(View.GONE); mEditTextList.setVisibility(View.VISIBLE); } - private Spannable getHighlightQueryResult(String fullText, String userQuery) {//高亮显示用户查询的文本,这里用了spannable,是比较好的视图管理器 - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);//存储高亮显示的文本 - if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery, Pattern.CASE_INSENSITIVE);//编译正则表达式 - Matcher m = mPattern.matcher(fullText);//创建一个 Matcher 对象,用于对输入字符串进行匹配操作 + private Spannable getHighlightQueryResult(String fullText, String userQuery) { + // 1. 空值保护,避免空指针 + if (TextUtils.isEmpty(fullText)) { + return new SpannableString(""); + } + SpannableString spannable = new SpannableString(fullText); + String TAG = "NoteEdit"; + + // 2. 原有高亮逻辑(保留不动,仅补全空值判断) + if (!TextUtils.isEmpty(userQuery)) { + Pattern pattern = Pattern.compile(userQuery); + Matcher m = pattern.matcher(fullText); int start = 0; - while (m.find(start)) { //构建span字符串 + while (m.find(start)) { + // 兼容不同版本的颜色获取(避免R.color.user_query_highlight不存在) spannable.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), + new BackgroundColorSpan(getResources().getColor(R.color.user_query_highlight, getTheme())), + m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); start = m.end(); } } + + Pattern imagePattern = Pattern.compile("【图】([^\\n]+)"); + Matcher imageMatcher = imagePattern.matcher(spannable); + while (imageMatcher.find()) { + try { + // 修复1:判空避免trim()空指针 + String imagePath = imageMatcher.group(1); + if (TextUtils.isEmpty(imagePath)) continue; + imagePath = imagePath.trim(); + + File imageFile = new File(imagePath); + if (!imageFile.exists() || !imageFile.isFile()) { + Log.e(TAG, "图片文件不存在:" + imagePath); + continue; + } + + // 修复2:Bitmap预压缩,避免OOM + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // 先获取尺寸,不加载像素 + BitmapFactory.decodeFile(imagePath, options); + // 计算采样率(按200x200压缩) + options.inSampleSize = calculateInSampleSize(options, 200, 200); + options.inJustDecodeBounds = false; + // 解码压缩后的图片 + Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options); + if (bitmap == null) { + Log.e(TAG, "图片解码失败:" + imagePath); + continue; + } + // 缩放(可选,压缩后已足够小) + bitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true); + + ImageSpan imageSpan = new ImageSpan(this, bitmap); + spannable.setSpan( + imageSpan, + imageMatcher.start(), + imageMatcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + } catch (Exception e) { + Log.e(TAG, "解析图片失败", e); + continue; + } + } + return spannable; } + private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + final int width = options.outWidth; + final int height = options.outHeight; + int inSampleSize = 1; + if (height > reqHeight || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + while ((halfHeight / inSampleSize) >= reqHeight + && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } + return inSampleSize; + } - private View getListItem(String item, int index) { //获取清单模式下的列表项视图 + 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);//获取列表项的编辑框 + 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) {//PaintFlags是Android中用于控制文本绘制样式的位掩码 ,通过位运算组合多个绘制标志 - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);//添加删除线效果 + 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);//清除删除线效果 + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); } } }); - if (item.startsWith(TAG_CHECKED)) {//如果列表项以(TAG_CHECKED)开头,则将复选框设置为选中状态 + 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();// + 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);// + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); } @@ -758,94 +1019,122 @@ public class NoteEditActivity extends Activity implements OnClickListener, return view; } - public void onTextChange(int index, boolean hasText) {// 列表项文本变化事件 - if (index >= mEditTextList.getChildCount()) {//检查索引是否超出范围 + public void onTextChange(int index, boolean hasText) { + if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); return; } - if(hasText) {//如果列表项有文本 + 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); } } - public void onCheckListModeChanged(int oldMode, int newMode) {// 检查列表模式变化事件 - if (newMode == TextNote.MODE_CHECK_LIST) {//如果切换到检查列表模式 - switchToListMode(mNoteEditor.getText().toString()); + public void onCheckListModeChanged(int oldMode, int newMode) { + if (newMode == TextNote.MODE_CHECK_LIST) { + switchToListMode(mNoteEditor.getHtml()); } else { if (!getWorkingText()) { mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", - ""));//如果没有选中项,则将所有未选中项的文本删除 + "")); } - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + String content = mWorkingNote.getContent() == null ? "" : mWorkingNote.getContent(); + // 保留高亮逻辑,将结果转为HTML + Spannable highlightSpannable = getHighlightQueryResult(content, mUserQuery); + mNoteEditor.setHtml(highlightSpannable.toString()); mEditTextList.setVisibility(View.GONE); mNoteEditor.setVisibility(View.VISIBLE); } } - private boolean getWorkingText() {//获取选中项的文本 + private boolean getWorkingText() { boolean hasChecked = false; - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果是检查列表模式 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mEditTextList.getChildCount(); i++) {//遍历所有列表项 + 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");//如果列表项选中,则将其文本添加到 StringBuilder 中 + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); hasChecked = true; } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");//如果列表项未选中,则将其文本添加到 StringBuilder 中 + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); } } } mWorkingNote.setWorkingText(sb.toString()); } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + // 确保获取最新的富文本内容 + String currentHtml = normalizeEditorHtml(mNoteEditor.getHtml()); + if (TextUtils.isEmpty(currentHtml) && !TextUtils.isEmpty(mText)) { + currentHtml = normalizeEditorHtml(mText); + } + mWorkingNote.setWorkingText(currentHtml); + mText = currentHtml; // 更新mText变量,确保保存时使用最新内容 } return hasChecked; } - private boolean saveNote() {//保存笔记 + private boolean saveNote() { + // 总是调用getWorkingText()获取最新内容,确保保存的是最新编辑的内容 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() {//将笔记发送到桌面 + private void showImagePreview(String localImagePath) { + 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) -> { + boolean isSaved = saveNote(); + if (isSaved) { + Toast.makeText(this, "图片信息已保存!", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "保存失败,请重试", Toast.LENGTH_SHORT).show(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + } + + 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()) {//如果笔记不存在于数据库中 + if (!mWorkingNote.existInDatabase()) { saveNote(); } - if (mWorkingNote.getNoteId() > 0) {//如果笔记存在于数据库中 + if (mWorkingNote.getNoteId() > 0) { Intent sender = new Intent(); Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW);//设置意图操作行为为查看 + shortcutIntent.setAction(Intent.ACTION_VIEW); shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);//将意图添加到发送者意图中 + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent()));//将快捷图标标题添加到发送者意图中 + 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);//显示提示消息(成功) + sender.putExtra("duplicate", true); + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + showToast(R.string.info_note_enter_desktop); sendBroadcast(sender); } else { /** @@ -858,18 +1147,181 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } - private String makeShortcutIconTitle(String content) {//创建快捷图标标题 + 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; } - private void showToast(int resId) {//显示短时间的提示消息 - showToast(resId, Toast.LENGTH_SHORT);// + private void showToast(int resId) { + showToast(resId, Toast.LENGTH_SHORT); } - private void showToast(int resId, int duration) {//显示自定义时间长度的 Toast 消息 + private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } -} + + // ========== 新增:打开系统相册选择图片 ========== + private void addPicture() { + if (mImageInsertHelper != null) { + mImageInsertHelper.startPickImage(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (mImageInsertHelper == null) { + return; + } + ImageInsertHelper.Result result = mImageInsertHelper.handleActivityResult( + requestCode, resultCode, data, mNoteEditor); + if (result == null || !result.success) { + return; + } + mWorkingNote.setWorkingText(result.html); + mText = result.html; + showImagePreview(result.localPath); + } + + private String normalizeEditorHtml(String html) { + if (TextUtils.isEmpty(html) || "null".equalsIgnoreCase(html)) { + return ""; + } + return html; + } + + private boolean isHtmlContent(String content) { + if (TextUtils.isEmpty(content)) { + return false; + } + return content.contains(""); + } + // 自定义方法:给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); // 允许输入 + mNoteEditor.setBackgroundColor(Color.TRANSPARENT); + mNoteEditor.getSettings().setAllowContentAccess(true); + mNoteEditor.getSettings().setAllowFileAccess(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mNoteEditor.getSettings().setAllowFileAccessFromFileURLs(true); + mNoteEditor.getSettings().setAllowUniversalAccessFromFileURLs(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_doodle).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDoodleDialog(); + } + }); + + } + + private void showDoodleDialog() { + DoodleDialog dialog = new DoodleDialog(this, new DoodleDialog.OnDoodleSavedListener() { + @Override + public void onSaved(String localPath) { + insertImageFromLocal(localPath); + showImagePreview(localPath); + } + }); + dialog.show(); + } + + private void insertImageFromLocal(String localImagePath) { + if (mNoteEditor == null) { + return; + } + String imgUrl = Uri.fromFile(new File(localImagePath)).toString(); + String imgHtmlTag = "
"; + String curHtml = normalizeEditorHtml(mNoteEditor.getHtml()); + String newHtml = curHtml + imgHtmlTag; + mNoteEditor.setHtml(newHtml); + mNoteEditor.focusEditor(); + mText = newHtml; + mWorkingNote.setWorkingText(newHtml); + } + + +} \ No newline at end of file