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.
git-test/src/ui/NoteEditActivity.java

1155 lines
50 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;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder {
public TextView tvModified; // 用于显示修改日期的TextView
public ImageView ivAlertIcon; // 用于显示提醒图标的ImageView
public TextView tvAlertDate; // 用于显示提醒日期的TextView
public ImageView ibSetBgColor; // 用于设置背景颜色的ImageView按钮
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
// 映射背景选择按钮的ID到相应的背景颜色
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>();
// 映射背景颜色到相应的选中状态按钮的ID
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>();
// 映射字体大小选择按钮的ID到相应的字体大小
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>();
// 映射字体大小到相应的选中状态按钮的ID
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity";
private HeadViewHolder mNoteHeaderHolder;
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
private WorkingNote mWorkingNote;
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
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); // 调用父类的onCreate方法以完成基本初始化工作
this.setContentView(R.layout.note_edit); // 设置当前活动的内容视图为note_edit.xml布局
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish(); // 如果没有保存的实例状态且初始化活动状态失败,则结束当前活动
return;
}
initResources(); // 初始化活动的资源
}
/**
* 当内存不足时,当前活动可能会被销毁。一旦被销毁,用户再次加载该活动时,应该还原先前的状态。
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); // 调用父类的onRestoreInstanceState方法以完成基本初始化工作
// 检查是否有保存的实例状态savedInstanceState以及是否包含关键信息Intent.EXTRA_UID
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"); // 打印日志以指示从已销毁的活动中还原状态
}
}
/**
* 初始化活动的状态根据传入的Intent执行不同操作
*
* 如果Intent的操作是{@link Intent#ACTION_VIEW},则尝试查看指定的笔记或搜索结果。
* 如果Intent的操作是{@link Intent#ACTION_INSERT_OR_EDIT},则创建新的笔记或处理通话记录笔记。
* 其他情况下,会记录错误日志并结束活动。
*
* @param intent 用于初始化活动状态的Intent对象
* @return 如果成功初始化活动状态返回true否则返回false
*/
private boolean initActivityState(Intent intent) {
mWorkingNote = null; // 初始化工作笔记为null
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
// 如果Intent的操作是查看笔记
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
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();
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())) {
// 如果Intent的操作是新建或编辑笔记
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this));
// 处理通话记录笔记
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();
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));
// TODO: 添加设置提醒的菜单。当前已禁用因为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 {
// 显示相对时间
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);
}
// 此方法用于保存活动的当前状态,包括任何未保存的数据。
@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, "保存工作笔记的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) {
// 检查触摸坐标是否在视图的范围内。
// 如果不是返回false否则返回true。
// 用于确定触摸是否在可见选择器的外部。
// ...
// 此方法初始化活动中使用的各种资源。
private void initResources() {
// 查找视图并为UI元素设置监听器。
// ...
// 获取共享偏好并检索字体大小设置。
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;
}
// 设置其他资源和UI元素。
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}
// 在活动暂停时调用此方法。通常在用户切换到其他应用程序或活动时触发。
@Override
protected void onPause() {
super.onPause();
// 调用 saveNote() 方法来保存笔记数据,如果保存成功,则记录保存的数据长度。
if (saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
// 清除设置状态,确保不再保留可能在活动中的临时设置。
clearSettingState();
}
// 更新小部件的方法。根据当前工作笔记的小部件类型,选择相应的小部件提供程序类并发送广播通知小部件更新。
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据当前工作笔记的小部件类型设置适当的小部件提供程序类。
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
// 如果小部件类型不受支持,记录错误并退出方法。
Log.e(TAG, "Unspported widget type");
return;
}
// 将小部件ID包含在广播意图中以指示哪个小部件需要更新。
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
// 发送广播通知系统更新小部件。
sendBroadcast(intent);
// 设置结果代码为RESULT_OK表示操作成功。
setResult(RESULT_OK, intent);
}
// 处理点击事件的方法根据视图的ID执行相应的操作。
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);
}
}
// 当用户按下返回按钮时调用,用于处理返回操作。
@Override
public void onBackPressed() {
if (clearSettingState()) {
// 如果设置状态被清除成功,直接返回,不执行后续操作。
return;
}
// 保存笔记内容。
saveNote();
// 执行默认的返回操作。
super.onBackPressed();
}
// 用于清除设置状态。如果背景颜色选择器或字体大小选择器可见,则隐藏它们,并返回 true 表示成功清除设置状态;否则返回 false。
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
// 如果背景颜色选择器可见,隐藏它并返回 true。
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
// 如果字体大小选择器可见,隐藏它并返回 true。
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
// 如果没有任何设置状态需要清除,返回 false。
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;
}
// 清除任何设置状态。
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;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
// 当选择新建笔记菜单项时调用createNewNote()方法
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()方法删除当前笔记,并结束活动
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; // 返回true表示菜单项已处理
}
/**
* 弹出日期和时间选择对话框,允许用户设置提醒日期和时间。
*/
private void setReminder() {
// 创建一个 DateTimePickerDialog 实例,传入当前活动和当前系统时间
DateTimePickerDialog dateTimePickerDialog = new DateTimePickerDialog(this, System.currentTimeMillis());
// 设置日期和时间选择监听器,当用户设置日期和时间时执行操作
dateTimePickerDialog.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
// 将用户选择的日期和时间设置为当前工作笔记的提醒日期,并启用提醒
mWorkingNote.setAlertDate(date, true);
}
});
// 显示日期和时间选择对话框,允许用户选择提醒日期和时间
dateTimePickerDialog.show();
}
/**
* 分享笔记给支持 {@link Intent#ACTION_SEND} 操作和 {@text/plain} 类型的应用程序。
*
* @param context 上下文对象,用于启动分享操作
* @param info 要分享的笔记内容
*/
private void sendTo(Context context, String info) {
// 创建一个意图,设置操作为发送操作
Intent intent = new Intent(Intent.ACTION_SEND);
// 将笔记内容作为额外文本添加到意图中
intent.putExtra(Intent.EXTRA_TEXT, info);
// 指定意图的数据类型为纯文本
intent.setType("text/plain");
// 启动分享操作,允许用户选择要分享的应用程序
context.startActivity(intent);
}
/**
* 创建新的笔记。
*
* 首先,保存当前正在编辑的笔记。
* 然后,为了安全起见,启动一个新的 NoteEditActivity。
* 最后,将新的笔记添加到当前文件夹中。
*/
private void createNewNote() {
// 首先,保存当前正在编辑的笔记
saveNote();
// 为了安全起见,结束当前活动
finish();
// 创建一个意图,指定启动 NoteEditActivity并传递相关参数
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
// 启动新的 NoteEditActivity
startActivity(intent);
}
/**
* 删除当前笔记。
*
* 如果当前笔记存在于数据库中,将其删除。如果处于同步模式下,
* 则将笔记移动到垃圾箱文件夹。如果不处于同步模式下,直接删除笔记。
*
* 在删除之后,将当前笔记标记为已删除。
*/
private void deleteCurrentNote() {
// 检查当前笔记是否存在于数据库中
if (mWorkingNote.existInDatabase()) {
// 创建一个包含当前笔记ID的 HashSet
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
// 如果笔记ID不是根文件夹ID则将其添加到 HashSet 中
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "错误的笔记ID不应该发生");
}
// 如果不处于同步模式下,直接批量删除笔记
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "删除笔记出错");
}
}
// 如果处于同步模式下,将笔记移动到垃圾箱文件夹
else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "将笔记移动到垃圾箱文件夹出错,不应该发生");
}
}
}
// 标记当前笔记为已删除
mWorkingNote.markDeleted(true);
}
/**
* 检查是否处于同步模式。
*
* 如果已设置同步账户名称,返回 true否则返回 false。
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 当时钟提醒设置发生变化时触发的方法。
*
* 如果用户要设置时钟提醒,但当前笔记尚未保存,先保存笔记。
* 然后根据笔记的ID创建一个定时提醒的意图将其与笔记关联。
* 最后,使用 AlarmManager 设置或取消提醒,并在设置提醒时显示提醒标题。
*
* @param date 要设置的提醒时间
* @param set 指定是否设置提醒
*/
public void onClockAlertChanged(long date, boolean set) {
if (!mWorkingNote.existInDatabase()) {
// 如果当前笔记尚未保存,先保存笔记
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
// 如果笔记ID大于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 设置提醒时间
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
// 如果没有笔记ID提醒用户应该输入内容
Log.e(TAG, "时钟提醒设置错误");
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) {
// 检查索引是否超出 mEditTextList 的范围,这种情况不应该发生
if (index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundary, should not happen");
}
// 创建新的视图并将其添加到 mEditTextList 中的指定索引位置
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;
// 遍历拆分后的列表项并将它们添加到 mEditTextList 中
for (String item : items) {
if (!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;
}
}
// 添加一个空的文本编辑框用于用户新增项目
mEditTextList.addView(getListItem("", index));
// 设置焦点在新增的空文本编辑框上
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
// 隐藏笔记编辑器视图
mNoteEditor.setVisibility(View.GONE);
// 显示文本列表视图
mEditTextList.setVisibility(View.VISIBLE);
}
/**
* 获取高亮查询结果的可变字符串。
*
* 该方法用于将用户的查询关键词在文本中高亮显示。它会创建一个包含完整文本的可变字符串,然后根据用户查询的关键词将匹配的部分高亮显示。
*
* @param fullText 完整文本内容
* @param userQuery 用户的查询关键词
* @return 一个可变字符串,其中匹配用户查询的关键词已被高亮显示
*/
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
// 创建一个可变字符串,并将完整文本初始化到其中
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);
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
// 设置文本样式根据字体大小
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
// 复选框状态变化监听器,处理文本样式
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)) {
// 文本以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)) {
// 文本以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否则返回 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 中
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true; // 更新已选中标记
} else {
// 如果项目未选中,添加未选中标记并文本内容到 sb 中
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(); // 获取工作文本
boolean saved = mWorkingNote.saveNote(); // 尝试保存笔记
if (saved) {
/**
* 有两种模式,从列表视图切换到编辑视图,打开一个笔记,创建/编辑一个节点。
* 打开节点需要在从编辑视图返回时返回到列表中的原始位置,而创建一个新节点需要返回到列表的顶部。
* 此代码 {@link #RESULT_OK} 用于标识创建/编辑状态。
*/
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 {
/**
* 有一种情况是用户没有输入任何内容笔记不值得保存小米便签没有笔记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, ""); // 移除已勾选标记
content = content.replace(TAG_UNCHECKED, ""); // 移除未勾选标记
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
/**
* 显示短时长的提示消息。该方法使用指定的资源ID显示短时长的提示消息。
*
* @param resId 资源ID用于获取提示消息内容。
*/
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT); // 调用带有时长参数的 showToast 方法,默认使用短时长
}
/**
* 显示指定时长的提示消息。该方法使用指定的资源ID和时长显示提示消息。
*
* @param resId 资源ID用于获取提示消息内容。
* @param duration 提示消息的显示时长,可以是 Toast.LENGTH_SHORT 或 Toast.LENGTH_LONG。
*/
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show(); // 创建 Toast 并显示指定时长的提示消息
}
}