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.
MINotes/src/net/micode/notes/ui/NoteEditActivity.java

1138 lines
47 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 net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
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;
// 导入该类所依赖的类和接口
/**
* NoteEditActivity 是一个用于编辑笔记的 Activity 类。
*/
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
* HeadViewHolder 是一个用于存储笔记头部视图组件的辅助类。
*/
private class HeadViewHolder {
public TextView tvModified; // 修改日期文本视图
public ImageView ivAlertIcon; // 提醒图标
public TextView tvAlertDate; // 提醒日期文本视图
public ImageView ibSetBgColor; // 设置背景颜色按钮
}
/**
* sBgSelectorBtnsMap 是一个映射,用于存储背景颜色选择器按钮与其对应的颜色资源 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);
}
/**
* sBgSelectorSelectionMap 是一个映射,用于存储背景颜色选择器选中状态与其对应的视图 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);
}
/**
* sFontSizeBtnsMap 是一个映射,用于存储字体大小选择器按钮与其对应的字体大小资源 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);
}
/**
* sFontSelectorSelectionMap 是一个映射,用于存储字体大小选择器选中状态与其对应的视图 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);
}
/**
* TAG 是日志标签,用于在日志输出中标识当前类。
*/
private static final String TAG = "NoteEditActivity";
/**
* mNoteHeaderHolder 是用于存储笔记头部视图组件的辅助对象。
*/
private HeadViewHolder mNoteHeaderHolder;
/**
* mHeadViewPanel 是笔记头部视图面板。
*/
private View mHeadViewPanel;
/**
* mNoteBgColorSelector 是背景颜色选择器视图。
*/
private View mNoteBgColorSelector;
/**
* mFontSizeSelector 是字体大小选择器视图。
*/
private View mFontSizeSelector;
/**
* mNoteEditor 是笔记编辑器文本框。
*/
private EditText mNoteEditor;
/**
* mNoteEditorPanel 是笔记编辑器面板。
*/
private View mNoteEditorPanel;
/**
* mWorkingNote 是当前正在编辑的笔记对象。
*/
private WorkingNote mWorkingNote;
/**
* mSharedPrefs 是用于存储共享偏好设置的 SharedPreferences 对象。
*/
private SharedPreferences mSharedPrefs;
/**
* mFontSizeId 是当前选择的字体大小资源 ID。
*/
private int mFontSizeId;
/**
* PREFERENCE_FONT_SIZE 是字体大小偏好设置的键。
*/
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
/**
* SHORTCUT_ICON_TITLE_MAX_LEN 是发送到桌面的快捷方式图标标题的最大长度。
*/
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
/**
* TAG_CHECKED 是复选框选中状态的标记字符串。
*/
public static final String TAG_CHECKED = String.valueOf('\u221A');
/**
* TAG_UNCHECKED 是复选框未选中状态的标记字符串。
*/
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
/**
* mEditTextList 是用于存储复选框列表项的线性布局。
*/
private LinearLayout mEditTextList;
/**
* mUserQuery 是用户查询字符串。
*/
private String mUserQuery;
/**
* mPattern 是用于匹配用户查询字符串的正则表达式模式。
*/
private Pattern mPattern;
/**
* onCreate 方法是 Activity 的生命周期方法之一,用于初始化界面和数据。
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
}
/**
* onRestoreInstanceState 方法用于在 Activity 被系统杀死后恢复其状态。
*/
@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");
}
}
/**
* initActivityState 方法用于初始化 Activity 的状态。
* 它根据 Intent 的 Action 加载笔记数据、创建新笔记或跳转到其他页面,并设置界面状态。
*
* @param intent 启动 Activity 的 Intent
* @return 是否成功初始化 Activity 状态,失败则返回 false
*/
private boolean initActivityState(Intent intent) {
// 初始化当前正在编辑的笔记对象为 null
mWorkingNote = null;
// 如果 Intent 的 Action 是 ACTION_VIEW则加载现有笔记
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); // 获取笔记 ID
mUserQuery = ""; // 初始化用户查询字符串为空
/**
* 如果通过搜索结果启动,则从搜索结果中获取笔记 ID 和用户查询字符串
*/
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)) {
Intent jump = new Intent(this, NotesListActivity.class); // 跳转到笔记列表页面
startActivity(jump);
showToast(R.string.error_note_not_exist); // 显示“笔记不存在”的错误提示
finish(); // 结束当前 Activity
return false;
} else {
// 如果笔记存在,加载笔记数据
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
// 如果加载失败,记录错误日志并结束 Activity
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())) {
// 如果 Intent 的 Action 是 ACTION_INSERT_OR_EDIT则创建新笔记
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); // 获取文件夹 ID
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); // 获取小部件 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)); // 获取背景颜色 ID
// 检查是否是通话记录便签
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
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
// 如果找到对应的笔记,加载笔记数据
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
// 如果加载失败,记录错误日志并结束 Activity
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);
}
// 设置输入法模式为可见,并调整布局大小
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
// 如果 Intent 的 Action 未指定,记录错误日志并结束 Activity
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() {
// 设置笔记编辑器的字体样式
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()); // 光标设置到文本末尾
}
// 隐藏所有背景选择器选中状态的图标
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));
// 显示提醒头部信息
showAlertHeader();
}
/**
* 显示提醒信息的头部,如果有设置提醒则显示,否则隐藏。
*/
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); // 处理新的 Intent重新初始化 Activity 状态
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 如果当前笔记尚未保存到数据库,先保存以生成 ID
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
// 保存笔记的 ID 到状态中
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
/**
* 处理触摸事件,用于隐藏背景颜色选择器或字体大小选择器。
*/
@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); // 继续处理其他触摸事件
}
/**
* 检查触摸事件是否在指定视图的范围内。
*/
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;
}
/**
* 初始化资源,包括初始化视图组件、背景颜色选择器和字体大小选择器。
*/
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 = (EditText) findViewById(R.id.note_edit_view); // 笔记编辑框
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);
// 检查字体大小资源 ID 是否有效,否则设置为默认字体大小
if (mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); // 复选框列表布局
}
@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); // 更新 2x 小部件
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class); // 更新 4x 小部件
} else {
Log.e(TAG, "Unsupported widget type"); // 不支持的小部件类型
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent); // 发送广播通知小部件更新
setResult(RESULT_OK, intent); // 设置结果为成功
}
/**
* 处理按钮点击事件。
*/
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 {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); // 设置字体样式
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
/**
* 在按下返回键时调用。
* 首先尝试清除设置状态(如隐藏背景颜色选择器或字体大小选择器),如果清除成功,直接返回;
* 否则保存当前笔记并调用父类的 `onBackPressed` 方法。
*/
@Override
public void onBackPressed() {
if (clearSettingState()) {
return; // 如果清除了设置状态,不执行其他操作
}
saveNote(); // 保存当前笔记
super.onBackPressed(); // 调用父类的返回键处理逻辑
}
/**
* 清除设置状态。
* 如果背景颜色选择器或字体大小选择器可见,则隐藏它们并返回 true
* 否则返回 false。
*/
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; // 没有需要清除的设置状态
}
/**
* 当背景颜色更改时调用。
* 更新背景颜色选择器、编辑器面板背景以及标题背景。
*/
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.VISIBLE); // 显示选中的背景颜色图标
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); // 更新编辑器面板背景
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); // 更新标题背景
}
/**
* 准备选项菜单时调用。
* 清除设置状态并根据当前笔记的属性动态加载菜单选项。
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true; // 如果 Activity 正在结束,不执行任何操作
}
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;
}
/**
* 处理菜单选项点击事件。
* 根据选项的 ID 执行对应操作。
*/
@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(); // 结束 Activity
}
});
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(); // 显示日期时间选择器对话框
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
/**
* 将文本内容通过分享意图发送到其他应用程序。
*
* @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); // 启动分享意图
}
/**
* 创建新笔记。
* 保存当前正在编辑的笔记后,启动一个新的 NoteEditActivity 实例以编辑新笔记。
*/
private void createNewNote() {
saveNote(); // 保存当前编辑的笔记
// 启动一个新的 NoteEditActivity 实例
finish(); // 结束当前 Activity
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置操作类型为新建或编辑
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); // 传递文件夹 ID
startActivity(intent); // 启动新的 Activity
}
/**
* 删除当前笔记。
* 如果笔记存在于数据库中,根据同步模式将其从数据库删除或移动到回收站。
*/
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) { // 检查笔记是否存在于数据库中
HashSet<Long> ids = new HashSet<>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id); // 将笔记 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");
}
}
}
mWorkingNote.markDeleted(true); // 标记笔记为已删除
}
/**
* 检查当前是否启用了同步模式。
*
* @return 如果启用了同步模式,返回 true否则返回 false。
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 当提醒时间发生更改时调用。
* 如果笔记尚未保存,则先保存笔记;然后设置或取消闹钟提醒。
*
* @param date 提醒的时间
* @param set 是否设置提醒true 为设置false 为取消)
*/
public void onClockAlertChanged(long date, boolean set) {
// 如果笔记尚未保存,先保存笔记
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())); // 设置数据 URI
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader(); // 更新提醒头部显示
if (!set) {
alarmManager.cancel(pendingIntent); // 取消提醒
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); // 设置提醒时间
}
} else {
// 如果笔记无内容且未保存,提醒用户输入内容
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
/**
* 当小部件发生变化时调用,更新小部件内容。
*/
public void onWidgetChanged() {
updateWidget();
}
/**
* 当复选框文本被删除时调用。
* 调整复选框列表中的索引并将被删除文本拼接到相邻的复选框中。
*
* @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;
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); // 光标移动到末尾
}
/**
* 当用户按下 Enter 键时调用。
* 在当前复选框下方插入一个新复选框。
*
* @param index 当前复选框的索引
* @param text 当前复选框的文本
*/
public void onEditTextEnter(int index, String text) {
if (index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundary, 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) {
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); // 显示复选框列表
}
/**
* 获取高亮显示查询结果的 Spannable 对象。
*
* @param fullText 全部文本内容
* @param userQuery 用户查询关键字
* @return 包含高亮显示的 Spannable 对象
*/
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery); // 匹配查询关键字
Matcher m = mPattern.matcher(fullText);
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()).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()).trim();
}
// 设置监听器和索引
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
// 设置高亮显示的内容
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
/**
* 当复选框的文本内容发生变化时调用。
*
* @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);
}
}
/**
* 当列表模式发生变化时调用。
*
* @param oldMode 旧模式(如普通模式)
* @param newMode 新模式(如列表模式)
*/
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
// 切换到列表模式
switchToListMode(mNoteEditor.getText().toString());
} else {
// 切换到普通模式
if (!getWorkingText()) {
// 删除未选中的标记
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", ""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE); // 隐藏复选框列表
mNoteEditor.setVisibility(View.VISIBLE); // 显示普通文本编辑器
}
}
/**
* 获取当前的工作文本。
* 如果处于列表模式,将复选框列表的内容合并为字符串;否则直接获取普通文本内容。
*
* @return 如果存在选中的复选框项,返回 true否则返回 false。
*/
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 {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
/**
* 保存当前笔记内容。
* 如果保存成功,则设置 Activity 的结果为 RESULT_OK。
*
* @return 保存是否成功
*/
private boolean saveNote() {
getWorkingText(); // 获取当前工作文本
boolean saved = mWorkingNote.saveNote(); // 保存笔记到数据库
if (saved) {
setResult(RESULT_OK); // 设置保存成功的状态
}
return saved;
}
/**
* 将当前笔记发送到桌面快捷方式。
* 如果笔记尚未保存,先保存笔记;然后创建桌面快捷方式。
*/
private void sendToDesktop() {
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 {
// 如果笔记为空且未保存,提示用户输入内容
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, "").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();
}
}