|
|
/*
|
|
|
* 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();
|
|
|
}
|
|
|
} |