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

1822 lines
70 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.text.Editable;
import android.text.TextWatcher;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Button;
import android.app.Dialog;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.Editable;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import android.graphics.drawable.ColorDrawable;
import android.widget.RadioGroup;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.graphics.Rect;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import android.database.Cursor;
import android.content.ContentValues;
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 {
/**
* 标题栏视图持有者
* 用于管理笔记标题区域的UI组件
*/
private class HeadViewHolder {
public TextView tvModified; // 显示最后修改时间
public ImageView ivAlertIcon; // 提醒图标
public TextView tvAlertDate; // 提醒时间
public ImageView ibSetBgColor; // 设置背景颜色按钮
}
// 背景颜色选择器按钮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);
}
// 背景颜色与选中状态指示器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);
}
// 字体大小选择器按钮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);
}
// 字体大小与选中状态指示器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);
}
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 = String.valueOf('\u221A'); // 勾选标记 √
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未勾选标记 □
private LinearLayout mEditTextList; // 清单模式下的编辑列表容器
private String mUserQuery; // 搜索查询词(用于高亮显示)
private Pattern mPattern; // 搜索高亮正则表达式模式
private TextView mWordCountView; // 字数统计显示控件
private Button mStudyTimerButton; // 学习计时器按钮
// 富文本工具栏相关变量
private View mRichTextToolbar; // 富文本工具栏
private Button mBtnBold; // 加粗按钮
private Button mBtnItalic; // 斜体按钮
private Button mBtnUnderline; // 下划线按钮
private Map<Integer, Integer> mColorMap; // 颜色按钮ID与颜色值的映射
// 学习计时器相关变量
private boolean mIsTimerRunning = false; // 计时器是否正在运行
private boolean mIsCountdownMode = false; // 是否为逆向计时模式
private long mTimerCurrentTime = 0; // 当前计时时间(毫秒)
private long mTimerDuration = 0; // 逆向计时时长(毫秒)
private android.os.Handler mTimerHandler = new android.os.Handler();
private android.os.Handler mDisplayHandler = new android.os.Handler();
private Runnable mTimerRunnable = new Runnable() {
@Override
public void run() {
if (mIsTimerRunning) {
if (mIsCountdownMode) {
// 逆向计时
long elapsed = System.currentTimeMillis() - mTimerStartTime;
mTimerCurrentTime = mTimerDuration - elapsed;
if (mTimerCurrentTime <= 0) {
// 计时结束
mTimerCurrentTime = mTimerDuration; // 设置为完整时长,确保计入专注时间
mIsTimerRunning = false;
updateTimerDisplay();
Toast.makeText(NoteEditActivity.this, "学习时间结束!", Toast.LENGTH_LONG).show();
return;
}
} else {
// 正向计时
mTimerCurrentTime = System.currentTimeMillis() - mTimerStartTime;
}
updateTimerDisplay();
mTimerHandler.postDelayed(this, 1000); // 每秒更新一次
}
}
};
private long mTimerStartTime = 0; // 计时器开始时间
// 富文本颜色选择器按钮ID与颜色值的映射
private static final Map<Integer, Integer> sColorMap = new HashMap<Integer, Integer>();
static {
sColorMap.put(R.id.color_black, Color.BLACK);
sColorMap.put(R.id.color_red, Color.RED);
sColorMap.put(R.id.color_blue, Color.BLUE);
sColorMap.put(R.id.color_green, Color.GREEN);
sColorMap.put(R.id.color_orange, Color.rgb(255, 165, 0));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit); // 设置布局
// 初始化活动状态,如果失败则结束活动
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
// 接收并处理传递的计时器状态
Intent intent = getIntent();
if (intent != null) {
mIsTimerRunning = intent.getBooleanExtra("is_timer_running", false);
mTimerCurrentTime = intent.getLongExtra("timer_current_time", 0);
mIsCountdownMode = intent.getBooleanExtra("is_countdown_mode", false);
mTimerDuration = intent.getLongExtra("timer_duration", 0);
// 如果计时器正在运行,启动计时器
if (mIsTimerRunning) {
if (mIsCountdownMode) {
// 逆向计时模式:计算已经过去的时间
long elapsedTime = mTimerDuration - mTimerCurrentTime;
mTimerStartTime = System.currentTimeMillis() - elapsedTime;
} else {
// 正向计时模式:使用当前时间减去已计时时间
mTimerStartTime = System.currentTimeMillis() - mTimerCurrentTime;
}
mTimerHandler.post(mTimerRunnable);
}
}
initResources(); // 初始化资源
}
/**
* 当活动被系统杀死后恢复时调用
* 用于恢复之前的状态
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 恢复笔记ID并重新初始化活动状态
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");
}
}
/**
* 初始化活动状态
* @param intent 启动活动的意图
* @return 初始化是否成功
*/
private boolean initActivityState(Intent intent) {
mWorkingNote = null;
// 处理查看笔记的意图
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
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);
}
// 检查笔记是否存在
// 对于回收站中的便签使用existInNoteDatabase而不是visibleInNoteDatabase
if (!DataUtils.existInNoteDatabase(getContentResolver(), noteId)) {
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);
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;
}
@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 {
// 普通模式,显示内容并高亮搜索词
// 将HTML格式转换为SpannableString以保留富文本格式
String content = mWorkingNote.getContent();
CharSequence styledText = Html.fromHtml(content != null ? content : "");
mNoteEditor.setText(getHighlightQueryResult(styledText, mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
// 更新字数统计
updateWordCount();
}
// 隐藏所有背景选择指示器
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 {
// 显示相对时间(如"10分钟后"
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的新笔记需要先保存以生成ID
* 如果编辑的笔记不值得保存则没有ID相当于创建新笔记
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
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;
}
/**
* 初始化UI资源和视图
*/
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.ib_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
// 初始化学习计时器按钮
mStudyTimerButton = (Button) findViewById(R.id.btn_study_timer);
if (mStudyTimerButton != null) {
mStudyTimerButton.setOnClickListener(this);
// 检查是否为学习文件夹中的笔记
long folderId = mWorkingNote.getFolderId();
if (folderId > 0) {
// 检查文件夹是否为学习文件夹
if (isStudyFolder(folderId)) {
mStudyTimerButton.setVisibility(View.VISIBLE);
} else {
mStudyTimerButton.setVisibility(View.GONE);
}
} else {
mStudyTimerButton.setVisibility(View.GONE);
}
}
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的bug。
* 如果ID大于资源长度则返回默认字体大小
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
// 初始化字数统计控件
mWordCountView = (TextView) findViewById(R.id.tv_word_count);
// 设置文本变化监听器
mNoteEditor.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 文本变化前的处理,暂不需要
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 文本变化时更新字数统计
updateWordCount();
}
@Override
public void afterTextChanged(Editable s) {
// 文本变化后的处理,暂不需要
}
});
// 初始化富文本工具栏
initRichTextToolbar();
// 监听触摸事件,点击空白区域时隐藏工具栏
findViewById(R.id.sv_note_edit).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 点击编辑器面板空白区域时隐藏工具栏
if (v.getId() == R.id.sv_note_edit) {
int selStart = mNoteEditor.getSelectionStart();
int selEnd = mNoteEditor.getSelectionEnd();
// 只有当没有文本被选择时才隐藏工具栏
if (selStart == selEnd) {
hideRichTextToolbar();
}
}
return false;
}
});
// 添加全局触摸监听器,检测点击空白区域
findViewById(android.R.id.content).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 检查点击是否在编辑器外部
Rect editorRect = new Rect();
mNoteEditor.getGlobalVisibleRect(editorRect);
if (!editorRect.contains((int) event.getRawX(), (int) event.getRawY())) {
// 点击在编辑器外部,隐藏工具栏
hideRichTextToolbar();
}
}
return false;
}
});
// 自定义文本选择动作模式,只显示我们的富文本工具栏
mNoteEditor.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 清除所有系统菜单项
menu.clear();
// 显示我们的富文本工具栏
showRichTextToolbar();
// 返回true表示创建动作模式但不显示系统菜单
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// 清除所有系统菜单项
menu.clear();
// 确保富文本工具栏显示
showRichTextToolbar();
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// 当选择取消时,隐藏工具栏
hideRichTextToolbar();
}
});
}
/**
* 初始化富文本工具栏
*/
private void initRichTextToolbar() {
mRichTextToolbar = findViewById(R.id.rich_text_toolbar);
mBtnBold = (Button) findViewById(R.id.btn_bold);
mBtnItalic = (Button) findViewById(R.id.btn_italic);
mBtnUnderline = (Button) findViewById(R.id.btn_underline);
// 设置加粗按钮点击事件
mBtnBold.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleBold();
}
});
// 设置斜体按钮点击事件
mBtnItalic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleItalic();
}
});
// 设置下划线按钮点击事件
mBtnUnderline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleUnderline();
}
});
// 设置颜色选择器按钮点击事件
for (int id : sColorMap.keySet()) {
ImageButton btn = (ImageButton) findViewById(id);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int color = sColorMap.get(v.getId());
setTextColor(color);
}
});
}
}
/**
* 显示富文本工具栏
*/
private void showRichTextToolbar() {
mRichTextToolbar.setVisibility(View.VISIBLE);
}
/**
* 隐藏富文本工具栏
*/
private void hideRichTextToolbar() {
mRichTextToolbar.setVisibility(View.GONE);
}
/**
* 切换文本加粗状态
*/
private void toggleBold() {
// 保存当前选择范围
int start = mNoteEditor.getSelectionStart();
int end = mNoteEditor.getSelectionEnd();
if (start == end) {
return;
}
// 直接获取当前可编辑文本避免使用setText()导致ActionMode丢失
Editable editable = mNoteEditor.getText();
boolean hasBold = false;
// 检查选中的文本是否已经加粗
StyleSpan[] spans = editable.getSpans(start, end, StyleSpan.class);
for (StyleSpan span : spans) {
if (span.getStyle() == Typeface.BOLD) {
editable.removeSpan(span);
hasBold = true;
}
}
if (!hasBold) {
// 如果没有加粗,则添加加粗样式
editable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
// 保存笔记,但不破坏当前选择状态
saveNote();
}
/**
* 切换文本斜体状态
*/
private void toggleItalic() {
// 保存当前选择范围
int start = mNoteEditor.getSelectionStart();
int end = mNoteEditor.getSelectionEnd();
if (start == end) {
return;
}
// 直接获取当前可编辑文本避免使用setText()导致ActionMode丢失
Editable editable = mNoteEditor.getText();
boolean hasItalic = false;
// 检查选中的文本是否已经斜体
StyleSpan[] spans = editable.getSpans(start, end, StyleSpan.class);
for (StyleSpan span : spans) {
if (span.getStyle() == Typeface.ITALIC) {
editable.removeSpan(span);
hasItalic = true;
}
}
if (!hasItalic) {
// 如果没有斜体,则添加斜体样式
editable.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
// 保存笔记,但不破坏当前选择状态
saveNote();
}
/**
* 切换文本下划线状态
*/
private void toggleUnderline() {
// 保存当前选择范围
int start = mNoteEditor.getSelectionStart();
int end = mNoteEditor.getSelectionEnd();
if (start == end) {
return;
}
// 直接获取当前可编辑文本避免使用setText()导致ActionMode丢失
Editable editable = mNoteEditor.getText();
boolean hasUnderline = false;
// 检查选中的文本是否已经有下划线
UnderlineSpan[] spans = editable.getSpans(start, end, UnderlineSpan.class);
if (spans.length > 0) {
// 如果有下划线,则移除下划线
for (UnderlineSpan span : spans) {
editable.removeSpan(span);
}
hasUnderline = true;
}
if (!hasUnderline) {
// 如果没有下划线,则添加下划线
editable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
// 保存笔记,但不破坏当前选择状态
saveNote();
}
/**
* 设置文本颜色
*/
private void setTextColor(int color) {
// 保存当前选择范围
int start = mNoteEditor.getSelectionStart();
int end = mNoteEditor.getSelectionEnd();
if (start == end) {
return;
}
// 直接获取当前可编辑文本避免使用setText()导致ActionMode丢失
Editable editable = mNoteEditor.getText();
// 移除原有的颜色样式
ForegroundColorSpan[] oldSpans = editable.getSpans(start, end, ForegroundColorSpan.class);
for (ForegroundColorSpan span : oldSpans) {
editable.removeSpan(span);
}
// 添加新的颜色样式
editable.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
// 保存笔记,但不破坏当前选择状态
saveNote();
}
@Override
protected void onPause() {
super.onPause();
// 暂停时保存笔记
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState(); // 清除设置状态
// 暂停计时器显示更新
mDisplayHandler.removeCallbacksAndMessages(null);
}
/**
* 更新字数统计显示
*/
private void updateWordCount() {
if (mWordCountView != null) {
String content = getCurrentNoteContent();
int wordCount = DataUtils.getTotalWordCount(content);
mWordCountView.setText(getString(R.string.note_word_count, wordCount));
}
}
/**
* 获取当前便签的内容,根据不同模式选择不同的获取方式
* @return 当前便签的内容
*/
private String getCurrentNoteContent() {
StringBuilder content = new StringBuilder();
// 检查当前模式
if (mNoteEditor.getVisibility() == View.VISIBLE) {
// 普通模式,直接获取编辑框内容
content.append(mNoteEditor.getText().toString());
} else if (mEditTextList.getVisibility() == View.VISIBLE) {
// 清单模式,收集所有列表项的内容
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View itemView = mEditTextList.getChildAt(i);
EditText editText = (EditText) itemView.findViewById(R.id.et_edit_text);
if (editText != null && !TextUtils.isEmpty(editText.getText())) {
// 添加当前列表项的内容
content.append(editText.getText().toString());
// 添加换行符,保持原有格式
if (i < mEditTextList.getChildCount() - 1) {
content.append("\n");
}
}
}
}
return content.toString();
}
/**
* 更新桌面小部件
*/
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;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
// 计时器对话框变量
private Dialog mTimerDialog = null;
/**
* 点击事件处理
*/
public void onClick(View v) {
int id = v.getId();
// 学习计时器按钮
if (id == R.id.btn_study_timer) {
if (mIsTimerRunning) {
// 如果计时器正在运行,切换计时控制界面显示/隐藏
if (mTimerDialog != null && mTimerDialog.isShowing()) {
// 如果对话框已显示,则隐藏
mTimerDialog.dismiss();
} else {
// 如果对话框未显示,则显示
showTimerControlDialog();
}
}
}
// 背景颜色设置按钮
else if (id == R.id.ib_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(); // 保存笔记
// 传递计时器状态回 NotesListActivity
Intent intent = new Intent();
intent.putExtra("is_timer_running", mIsTimerRunning);
intent.putExtra("timer_current_time", mTimerCurrentTime);
intent.putExtra("is_countdown_mode", mIsCountdownMode);
intent.putExtra("timer_duration", mTimerDuration);
setResult(RESULT_OK, intent);
super.onBackPressed();
}
/**
* 清除设置状态(隐藏选择器)
*/
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;
}
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);
}
// 根据便签的置顶状态设置菜单项标题
MenuItem pinItem = menu.findItem(R.id.menu_pin_note);
if (pinItem != null) {
if (mWorkingNote.isPinned()) {
pinItem.setTitle(R.string.menu_unpin_note);
} else {
pinItem.setTitle(R.string.menu_pin_note);
}
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note: // 新建笔记
createNewNote();
break;
case R.id.menu_delete: // 删除笔记
showDeleteConfirmDialog();
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_alert: // 设置提醒
setReminder();
break;
case R.id.menu_delete_remind: // 删除提醒
mWorkingNote.setAlertDate(0, false);
break;
case R.id.menu_pin_note: // 置顶/取消置顶
mWorkingNote.setPinned(!mWorkingNote.isPinned());
break;
default:
break;
}
return true;
}
/**
* 显示删除确认对话框
*/
private void showDeleteConfirmDialog() {
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();
}
/**
* 设置提醒
*/
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();
}
/**
* 分享笔记到支持ACTION_SEND的应用
*/
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 (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
mWorkingNote.markDeleted(true);
}
/**
* 检查是否处于同步模式
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 闹钟提醒变化回调
*/
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()));
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();
}
/**
* 编辑框删除事件回调(清单模式)
*/
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);
}
/**
* 编辑框回车事件回调(清单模式)
*/
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);
}
}
/**
* 切换到清单模式
*/
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
*/
private Spannable getHighlightQueryResult(CharSequence fullText, String userQuery) {
SpannableString spannable;
if (fullText instanceof SpannableString) {
spannable = (SpannableString) fullText;
} else {
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;
}
/**
* 创建清单项目视图
*/
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;
}
/**
* 文本变化回调(清单模式)
*/
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);
}
// 更新字数统计
updateWordCount();
}
/**
* 清单模式变化回调
*/
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);
}
// 更新字数统计
updateWordCount();
}
/**
* 获取工作文本从UI提取到数据模型
*/
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 {
// 普通模式保存富文本格式为HTML
String htmlText = Html.toHtml(mNoteEditor.getText());
mWorkingNote.setWorkingText(htmlText);
}
return hasChecked;
}
/**
* 保存笔记
*/
private boolean saveNote() {
getWorkingText(); // 获取当前工作文本
boolean saved = mWorkingNote.saveNote(); // 保存到数据库
if (saved) {
/**
* 从列表视图到编辑视图有两种模式:打开笔记和创建/编辑笔记。
* 打开笔记需要返回到列表中的原始位置,
* 而创建新笔记需要返回到列表顶部。
* 使用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);
}
}
/**
* 生成快捷方式图标标题
*/
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;
}
/**
* 显示Toast提示
*/
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
/**
* 更新计时器显示
*/
private void updateTimerDisplay() {
long totalSeconds = mTimerCurrentTime / 1000;
int hours = (int) (totalSeconds / 3600);
int minutes = (int) ((totalSeconds % 3600) / 60);
int seconds = (int) (totalSeconds % 60);
String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
/**
* 启动计时器显示更新
*/
private void startTimerDisplayUpdate() {
mDisplayHandler.removeCallbacksAndMessages(null);
final Runnable updateRunnable = new Runnable() {
@Override
public void run() {
if (mIsTimerRunning) {
updateTimerDisplay();
mDisplayHandler.postDelayed(this, 1000);
}
}
};
mDisplayHandler.postDelayed(updateRunnable, 1000);
}
/**
* 显示计时器控制对话框
*/
private void showTimerControlDialog() {
// 创建一个非模态对话框
final Dialog timerDialog = new Dialog(this, android.R.style.Theme_Dialog);
timerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
timerDialog.setCancelable(true);
timerDialog.setCanceledOnTouchOutside(true);
// 设置对话框大小和位置
Window window = timerDialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.BOTTOM;
params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
window.setAttributes(params);
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
View view = LayoutInflater.from(this).inflate(R.layout.dialog_timer_control, null);
final TextView timerDisplayView = (TextView) view.findViewById(R.id.timer_display);
// 隐藏所有控制按钮
view.findViewById(R.id.btn_pause_resume).setVisibility(View.GONE);
view.findViewById(R.id.btn_stop).setVisibility(View.GONE);
// 更新计时器显示
long totalSeconds;
if (mIsCountdownMode) {
// 逆向计时模式:显示剩余时间
totalSeconds = mTimerCurrentTime / 1000;
} else {
// 正向计时模式:显示已用时间
totalSeconds = mTimerCurrentTime / 1000;
}
int hours = (int) (totalSeconds / 3600);
int minutes = (int) ((totalSeconds % 3600) / 60);
int seconds = (int) (totalSeconds % 60);
String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds);
timerDisplayView.setText(timeStr);
// 添加实时更新计时器显示的逻辑
final android.os.Handler displayHandler = new android.os.Handler();
final Runnable updateRunnable = new Runnable() {
@Override
public void run() {
if (mIsTimerRunning && timerDialog.isShowing()) {
long totalSeconds;
if (mIsCountdownMode) {
// 逆向计时模式:显示剩余时间
totalSeconds = mTimerCurrentTime / 1000;
} else {
// 正向计时模式:显示已用时间
totalSeconds = mTimerCurrentTime / 1000;
}
int hours = (int) (totalSeconds / 3600);
int minutes = (int) ((totalSeconds % 3600) / 60);
int seconds = (int) (totalSeconds % 60);
String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds);
timerDisplayView.setText(timeStr);
displayHandler.postDelayed(this, 1000);
}
}
};
// 启动实时更新
if (mIsTimerRunning) {
displayHandler.postDelayed(updateRunnable, 1000);
}
// 对话框关闭时停止更新
timerDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
displayHandler.removeCallbacks(updateRunnable);
}
});
timerDialog.setContentView(view);
timerDialog.show();
mTimerDialog = timerDialog;
}
/**
* 暂停学习计时器
*/
private void pauseStudyTimer() {
mIsTimerRunning = false;
mTimerHandler.removeCallbacks(mTimerRunnable);
// 暂停计时器显示更新
mDisplayHandler.removeCallbacksAndMessages(null);
}
/**
* 恢复学习计时器
*/
private void resumeStudyTimer() {
if (!mIsTimerRunning) {
if (mIsCountdownMode) {
// 倒计时模式:计算已经过去的时间
long elapsedTime = mTimerDuration - mTimerCurrentTime;
mTimerStartTime = System.currentTimeMillis() - elapsedTime;
} else {
// 正向计时模式:使用当前时间减去已计时时间
mTimerStartTime = System.currentTimeMillis() - mTimerCurrentTime;
}
mIsTimerRunning = true;
mTimerHandler.post(mTimerRunnable);
// 重启计时器显示更新
startTimerDisplayUpdate();
}
}
/**
* 停止学习计时器
*/
private void stopStudyTimer() {
mIsTimerRunning = false;
mTimerHandler.removeCallbacks(mTimerRunnable);
mDisplayHandler.removeCallbacksAndMessages(null);
// 验证学习有效性
if (mTimerCurrentTime >= 60000) {
// 学习时间超过1分钟视为有效学习
updateFocusDuration(mTimerCurrentTime);
}
// 重置计时器变量
mTimerCurrentTime = 0;
mTimerDuration = 0;
// 关闭计时器对话框
if (mTimerDialog != null && mTimerDialog.isShowing()) {
mTimerDialog.dismiss();
mTimerDialog = null;
}
}
/**
* 检查文件夹是否为学习文件夹
*/
private boolean isStudyFolder(long folderId) {
// 查询文件夹的 IS_STUDY 字段
Cursor cursor = getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{Notes.NoteColumns.IS_STUDY},
Notes.NoteColumns.ID + "=? AND " + Notes.NoteColumns.TYPE + "=?",
new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)},
null
);
boolean isStudy = false;
if (cursor != null && cursor.moveToFirst()) {
isStudy = (cursor.getInt(0) > 0);
cursor.close();
}
return isStudy;
}
/**
* 更新专注时长
*/
private void updateFocusDuration(long duration) {
// 获取当前笔记所属的文件夹ID
long folderId = mWorkingNote.getFolderId();
if (folderId <= 0) {
return;
}
// 查询当前文件夹的专注时长
Cursor cursor = getContentResolver().query(
Notes.CONTENT_NOTE_URI,
new String[]{Notes.NoteColumns.FOCUS_DURATION},
Notes.NoteColumns.ID + "=? AND " + Notes.NoteColumns.TYPE + "=?",
new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)},
null
);
long currentDuration = 0;
if (cursor != null && cursor.moveToFirst()) {
currentDuration = cursor.getLong(0);
cursor.close();
}
// 更新专注时长
long newDuration = currentDuration + duration;
ContentValues values = new ContentValues();
values.put(Notes.NoteColumns.FOCUS_DURATION, newDuration);
values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1);
getContentResolver().update(
Notes.CONTENT_NOTE_URI,
values,
Notes.NoteColumns.ID + "=? AND " + Notes.NoteColumns.TYPE + "=?",
new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)}
);
}
/**
* 显示学习计时器对话框
*/
private void showStudyTimerDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_study_timer, null);
final RadioGroup timerModeGroup = (RadioGroup) view.findViewById(R.id.timer_mode_group);
final EditText hoursEdit = (EditText) view.findViewById(R.id.edit_hours);
final EditText minutesEdit = (EditText) view.findViewById(R.id.edit_minutes);
final EditText secondsEdit = (EditText) view.findViewById(R.id.edit_seconds);
builder.setTitle("设置学习计时器");
builder.setView(view);
builder.setPositiveButton("开始", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 获取计时模式
int selectedModeId = timerModeGroup.getCheckedRadioButtonId();
mIsCountdownMode = (selectedModeId == R.id.radio_countdown);
// 获取时间设置
int hours = Integer.parseInt(!TextUtils.isEmpty(hoursEdit.getText()) ? hoursEdit.getText().toString() : "0");
int minutes = Integer.parseInt(!TextUtils.isEmpty(minutesEdit.getText()) ? minutesEdit.getText().toString() : "0");
int seconds = Integer.parseInt(!TextUtils.isEmpty(secondsEdit.getText()) ? secondsEdit.getText().toString() : "0");
// 计算总时长(毫秒)
mTimerDuration = (hours * 3600 + minutes * 60 + seconds) * 1000;
// 验证时间设置
if (mIsCountdownMode && mTimerDuration < 60000) {
// 逆向计时时长小于1分钟
Toast.makeText(NoteEditActivity.this, "逆向计时时长必须大于等于1分钟", Toast.LENGTH_SHORT).show();
return;
}
// 验证时间设置
if (!mIsCountdownMode && mTimerDuration == 0) {
// 正向计时时长为0
Toast.makeText(NoteEditActivity.this, "正向计时时长必须大于0", Toast.LENGTH_SHORT).show();
return;
}
// 开始计时
startStudyTimer();
dialog.dismiss();
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
/**
* 开始学习计时器
*/
private void startStudyTimer() {
mTimerStartTime = System.currentTimeMillis();
mIsTimerRunning = true;
mTimerHandler.post(mTimerRunnable);
// 显示计时控制对话框
showTimerControlDialog();
}
}