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.
xiaomi/ui/NoteEditActivity.java

979 lines
44 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;
// 导入所需的Android类和接口
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; // 显示最后修改时间的TextView
public ImageView ivAlertIcon; // 闹钟提醒图标的ImageView
public TextView tvAlertDate; // 显示闹钟提醒日期的TextView
public ImageView ibSetBgColor; // 设置背景颜色的ImageView
}
// 定义了一些静态的Map用于存储背景颜色选择器按钮和字体大小选择器按钮的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);
}
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);
}
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);
}
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"; // 用于日志输出的TAG
private HeadViewHolder mNoteHeaderHolder; // 持有笔记头部视图的引用
private View mHeadViewPanel; // 笔记头部视图面板
private View mNoteBgColorSelector; // 笔记背景颜色选择器
private View mFontSizeSelector; // 字体大小选择器
private EditText mNoteEditor; // 笔记编辑器
private View mNoteEditorPanel; // 笔记编辑器面板
private WorkingNote mWorkingNote; // 正在编辑的笔记
private SharedPreferences mSharedPrefs; // SharedPreferences对象用于存储设置
private int mFontSizeId; // 字体大小ID
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; // 用于搜索的正则表达式模式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit); // 设置布局文件
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish(); // 如果无法初始化活动状态,则结束活动
return;
}
initResources(); // 初始化资源
}
/**
* 当活动被系统杀死后重新创建时,恢复之前的状态
*/
@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"); // 日志输出:从被杀死的活动恢复
}
}
}
private boolean initActivityState(Intent intent) {
mWorkingNote = null; // 初始化WorkingNote对象为null
// 如果Intent的Action是VIEW但是没有提供ID则跳转到笔记列表活动
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); // 获取笔记ID
mUserQuery = "";
// 如果是从搜索结果开始的
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); // 从搜索结果中获取笔记ID
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); // 获取用户查询字符串
}
// 如果笔记ID在数据库中不可见则跳转到笔记列表活动并显示错误信息
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();
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())) {
// 新建笔记
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;
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); // 创建空笔记
}
// 设置软键盘输入模式
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; // 返回true表示成功初始化
}
@Override
protected void onResume() {
super.onResume(); // 调用父类的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));
}
/**
* 显示提醒头部,如果笔记有设置闹钟提醒,则显示提醒日期和图标。
* 目前由于DateTimePicker未准备好所以该功能被禁用。
*/
showAlertHeader();
private void showAlertHeader() {
// 如果笔记有设置闹钟提醒
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis(); // 获取当前时间
// 如果当前时间超过了提醒时间
if (time > mWorkingNote.getAlertDate()) {
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); // 显示提醒已过期
} else {
// 显示相对时间例如“1小时后”
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); // 调用父类的onNewIntent方法
initActivityState(intent); // 重新初始化活动状态
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); // 调用父类的onSaveInstanceState方法
// 对于没有笔记ID的新笔记我们首先需要保存它以生成ID
if (!mWorkingNote.existInDatabase()) {
saveNote(); // 保存笔记
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 将笔记ID保存到Bundle中
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); // 日志输出保存的笔记ID
}
@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 如果触摸事件在视图范围内返回true否则返回false。
*/
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location); // 获取视图在屏幕上的位置
int x = location[0];
int y = location[1];
// 如果触摸事件的x或y坐标超出视图的范围
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); // 获取修改日期TextView
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); // 获取提醒图标ImageView
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); // 获取提醒日期TextView
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); // 获取设置背景颜色的ImageView
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); // 获取SharedPreferences对象
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); // 获取字体大小ID
// 如果字体大小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(); // 调用父类的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); // 创建更新小部件的Intent
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[] { // 添加小部件ID
mWorkingNote.getWidgetId()
});
sendBroadcast(intent); // 发送广播
setResult(RESULT_OK, intent); // 设置结果
}
@Override
public void onClick(View v) {
int id = v.getId(); // 获取点击的视图ID
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)); // 设置新的背景颜色ID
mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏背景颜色选择器
} else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); // 隐藏当前选中的字体大小
mFontSizeId = sFontSizeBtnsMap.get(id); // 设置新的字体大小ID
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 保存字体大小ID到SharedPreferences
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); // 隐藏字体大小选择器
}
}
@Override
public void onBackPressed() {
// 当用户按下返回键时如果设置了清除设置状态并返回true则直接返回
if (clearSettingState()) {
return;
}
// 保存笔记
saveNote();
// 调用父类的onBackPressed方法完成返回操作
super.onBackPressed();
}
/**
* 清除设置状态,如背景颜色选择器和字体大小选择器的可见状态。
* @return 如果设置了可见状态为不可见则返回true。
*/
private boolean clearSettingState() {
// 如果背景颜色选择器可见则隐藏并返回true
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
// 如果字体大小选择器可见则隐藏并返回true
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false; // 没有设置可见状态为不可见返回false
}
/**
* 当背景颜色改变时,更新界面元素的背景。
*/
public void onBackgroundColorChanged() {
// 显示当前选中的背景颜色
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.VISIBLE);
// 设置笔记编辑器面板的背景资源ID
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
// 设置笔记标题面板的背景资源ID
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// 如果活动正在结束则返回true
if (isFinishing()) {
return true;
}
// 清除设置状态
clearSettingState();
// 清除菜单项
menu.clear();
// 根据笔记所在的文件夹ID决定加载哪个菜单
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;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 根据点击的菜单项ID执行相应的操作
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();
}
/**
* 分享笔记内容到支持分享的应用。
* @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)) {
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表示应用处于同步模式。
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 当闹钟提醒时间发生变化时调用。
* @param date 提醒的时间戳。
* @param set 是否设置了提醒。
*/
public void onClockAlertChanged(long date, boolean set) {
// 如果笔记尚未保存,则先保存笔记
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
// 如果笔记有有效的ID则继续设置提醒
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);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
showAlertHeader(); // 更新提醒头部视图
// 如果没有设置提醒,则取消闹钟
if (!set) {
alarmManager.cancel(pendingIntent);
} else {
// 设置闹钟提醒
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
// 如果笔记没有有效的ID说明用户没有输入任何内容提醒用户输入内容
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 = 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);
}
/**
* 当编辑文本按下回车键时调用。
* @param index 当前文本的索引。
* @param text 当前文本的内容。
*/
public void onEditTextEnter(int index, String text) {
// 检查索引是否超出列表范围,如果超出,记录错误日志
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) {
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(), 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;
}
/**
* 当文本变化时调用。
* @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。
*/
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;
}
/**
* 保存当前笔记到数据库。
* @return 如果笔记保存成功返回true否则返回false。
*/
private boolean saveNote() {
// 获取工作文本,可能是从列表模式转换来的文本
getWorkingText();
// 调用mWorkingNote对象的saveNote方法保存笔记并接收返回值
boolean saved = mWorkingNote.saveNote();
// 如果保存成功设置activity的结果码为RESULT_OK用于标识创建或编辑状态
if (saved) {
setResult(RESULT_OK);
}
return saved; // 返回保存结果
}
/**
* 将当前编辑的笔记发送到桌面作为快捷方式。
*/
private void sendToDesktop() {
// 如果当前笔记不在数据库中(即新笔记),则先保存笔记
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
// 如果笔记有有效的ID说明笔记已保存可以创建快捷方式
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent(); // 创建Intent对象
Intent shortcutIntent = new Intent(this, NoteEditActivity.class); // 创建快捷方式的Intent
shortcutIntent.setAction(Intent.ACTION_VIEW); // 设置动作为查看
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 附加笔记ID
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); // 附加快捷方式Intent
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 {
// 如果笔记没有有效的ID说明笔记未保存记录错误日志并提示用户输入内容
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, ""); // 移除TAG_CHECKED标记
content = content.replace(TAG_UNCHECKED, ""); // 移除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(); // 显示提示信息
}