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

364 lines
16 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.

/*
* 版权所有 (c) 2010-2011The MiCode Open Source Community (www.micode.net)
*
* 本软件根据 Apache 许可证 2.0 版("许可证")发布;
* 除非符合许可证,否则不得使用此文件。
* 您可以在以下网址获取许可证副本:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 除非法律要求或书面同意,软件
* 根据许可证分发的内容按"原样"提供,
* 不附带任何明示或暗示的保证或条件。
* 请参阅许可证,了解有关权限和限制的具体语言。
*/
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;
/**
* 笔记编辑活动(核心界面逻辑)
* 功能:
* 1. 支持新建/编辑文本笔记、待办事项、通话记录笔记
* 2. 实现笔记内容编辑、格式设置(背景颜色、字体大小)
* 3. 处理提醒设置、桌面小部件更新、快捷方式创建
* 4. 支持列表模式(待办事项)和普通文本模式切换
* 5. 集成搜索高亮、上下文菜单、多编辑项管理
*/
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
// 头部视图ViewHolder优化UI组件访问
private class HeadViewHolder {
public TextView tvModified; // 最后修改时间
public ImageView ivAlertIcon; // 提醒图标
public TextView tvAlertDate; // 提醒时间
public ImageView ibSetBgColor; // 设置背景颜色按钮
}
// 背景颜色选择按钮映射视图ID -> 资源ID
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
// 背景颜色选中状态映射资源ID -> 选中视图ID
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
// 字体大小按钮映射视图ID -> 资源ID
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
// 字体大小选中状态映射资源ID -> 选中视图ID
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<>();
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; // 当前字体大小资源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 = "\u221A"; // 待办事项选中标记
public static final String TAG_UNCHECKED = "\u25A1"; // 待办事项未选中标记
private LinearLayout mEditTextList; // 列表模式下的编辑项容器
private String mUserQuery; // 搜索查询词(用于高亮显示)
private Pattern mPattern; // 搜索匹配模式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_edit); // 加载编辑界面布局
// 恢复因内存不足被销毁的活动状态
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources(); // 初始化UI组件和资源
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
// 从已保存的状态中恢复笔记ID
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
}
}
}
/**
* 初始化活动状态(处理打开/新建笔记逻辑)
* @param intent 启动意图
* @return 是否初始化成功
*/
private boolean initActivityState(Intent intent) {
mWorkingNote = null;
if (TextUtils.equals(intent.getAction(), Intent.ACTION_VIEW)) {
// 打开已有笔记
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); // 处理搜索结果跳转
// 检查笔记是否存在且可见
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
showToast(R.string.error_note_not_exist);
startActivity(new Intent(this, NotesListActivity.class));
finish();
return false;
}
mWorkingNote = WorkingNote.load(this, noteId); // 加载笔记数据
if (mWorkingNote == null) {
Log.e(TAG, "加载笔记失败:" + noteId);
finish();
return false;
}
// 隐藏软键盘(查看模式)
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
} else if (TextUtils.equals(intent.getAction(), Intent.ACTION_INSERT_OR_EDIT)) {
// 新建笔记
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) {
long noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate);
if (noteId > 0) {
mWorkingNote = WorkingNote.load(this, noteId); // 加载已有通话记录笔记
} 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_STATE_VISIBLE);
} else {
Log.e(TAG, "不支持的意图操作");
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 bgId : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(bgId)).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 currentTime = System.currentTimeMillis();
if (currentTime > mWorkingNote.getAlertDate()) {
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); // 提醒已过期
} else {
// 显示相对时间如“10分钟后”
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), currentTime, 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();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 保存笔记ID
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 点击外部区域隐藏颜色/字体选择面板
if ((mNoteBgColorSelector.getVisibility() == View.VISIBLE || mFontSizeSelector.getVisibility() == View.VISIBLE) &&
!inRangeOfView(mNoteBgColorSelector, ev) && !inRangeOfView(mFontSizeSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return super.dispatchTouchEvent(ev);
}
/**
* 判断触摸点是否在指定视图范围内
* @param view 目标视图
* @param ev 触摸事件
* @return 是否在范围内
*/
private boolean inRangeOfView(View view, MotionEvent ev) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int x = location[0], y = location[1];
return ev.getX() >= x && ev.getX() <= x + view.getWidth() &&
ev.getY() >= y && ev.getY() <= y + view.getHeight();
}
/**
* 初始化UI组件和资源
*/
private void initResources() {
// 头部视图组件初始化
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); // 设置背景颜色按钮点击监听
// 文本编辑组件初始化
mNoteEditor = findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
// 注册背景颜色选择按钮点击监听
for (int btnId : sBgSelectorBtnsMap.keySet()) {
findViewById(btnId).setOnClickListener(this);
}
// 字体大小选择面板初始化
mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int sizeId : sFontSizeBtnsMap.keySet()) {
findViewById(sizeId).setOnClickListener(this);
}
// 加载字体大小偏好设置
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
//