/* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.micode.notes.ui; import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Paint; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.model.WorkingNote; import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser.TextAppearanceResources; import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * NoteEditActivity - 便签编辑活动 *
* 该类负责处理便签的创建、编辑、保存等核心功能,支持普通文本模式和 checklist 模式 * 实现了背景色切换、字体大小调整、提醒设置、分享等功能 *
* * @author MiCode Open Source Community * @version 1.0 */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { /** * HeadViewHolder - 便签头部视图持有者 ** 用于缓存便签头部的UI组件,提高性能 *
*/ private class HeadViewHolder { public TextView tvModified; // 修改时间显示 public ImageView ivAlertIcon; // 提醒图标 public TextView tvAlertDate; // 提醒日期显示 public ImageView ibSetBgColor; // 设置背景色按钮 } /** * 背景选择按钮映射 - 将UI按钮ID映射到颜色常量 */ private static final Map* 当活动创建时调用,设置布局并初始化活动状态 *
* * @param savedInstanceState 保存的实例状态,用于恢复活动 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); // 如果没有保存的实例状态且初始化失败,则结束活动 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } // 初始化资源 initResources(); } /** * 恢复活动状态 onRestoreInstanceState 方法 ** 当活动因内存不足被杀死后重新加载时,恢复之前的状态 *
* * @param savedInstanceState 保存的实例状态 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // 如果保存了实例状态且包含UID,则恢复活动 if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { finish(); return; } Log.d(TAG, "Restoring from killed activity"); } } /** * 初始化活动状态 ** 根据传入的意图初始化便签编辑活动,处理查看、创建、编辑便签的情况 *
* * @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); } // 检查便签是否存在 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); showToast(R.string.error_note_not_exist); finish(); return false; } else { // 加载便签 mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); finish(); return false; } } // 设置软键盘模式为隐藏状态 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } // 处理创建或编辑便签操作 else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { // 获取参数 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); 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; } /** * 活动恢复 onResume 方法 ** 当活动恢复到前台时调用,初始化便签屏幕 *
*/ @Override protected void onResume() { super.onResume(); initNoteScreen(); } /** * 初始化便签屏幕 ** 根据当前便签状态初始化UI,包括字体大小、背景颜色、内容显示等 *
*/ private void initNoteScreen() { // 设置编辑器字体大小 mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); // 根据便签模式显示内容 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 切换到列表模式 switchToListMode(mWorkingNote.getContent()); } else { // 普通文本模式,显示高亮搜索结果 mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); // 将光标定位到文本末尾 mNoteEditor.setSelection(mNoteEditor.getText().length()); } // 隐藏所有背景选择状态 for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } // 设置头部和编辑器背景 mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); // 设置修改时间 mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)); // 显示提醒头部 showAlertHeader(); } /** * 显示提醒头部 ** 根据便签是否有提醒以及提醒是否过期,显示相应的提醒信息 *
*/ private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); if (time > mWorkingNote.getAlertDate()) { // 提醒已过期 mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { // 显示相对时间 mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } // 显示提醒图标和日期 mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { // 隐藏提醒图标和日期 mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); }; } /** * 处理新意图 onNewIntent 方法 ** 当活动已经存在且收到新意图时调用,重新初始化活动状态 *
* * @param intent 新的意图 */ @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } /** * 保存实例状态 onSaveInstanceState 方法 ** 当活动即将被销毁时调用,保存当前便签状态 *
* * @param outState 用于保存状态的Bundle对象 */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 对于新便签,先保存生成ID if (!mWorkingNote.existInDatabase()) { saveNote(); } // 保存便签ID outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } /** * 分发触摸事件 ** 处理屏幕触摸事件,点击外部区域关闭背景选择器和字体大小选择器 *
* * @param ev 触摸事件对象 * @return 是否消耗了该事件 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { // 点击背景选择器外部,关闭背景选择器 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mNoteBgColorSelector, ev)) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } // 点击字体大小选择器外部,关闭字体大小选择器 if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { mFontSizeSelector.setVisibility(View.GONE); return true; } // 其他情况交给父类处理 return super.dispatchTouchEvent(ev); } /** * 检查触摸事件是否在指定视图范围内 * * @param view 要检查的视图 * @param ev 触摸事件对象 * @return 是否在视图范围内 */ 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.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); // 初始化编辑器 mNoteEditor = (EditText) findViewById(R.id.note_edit_view); mNoteEditorPanel = findViewById(R.id.sv_note_edit); // 初始化背景颜色选择器 mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } // 初始化字体大小选择器 mFontSizeSelector = findViewById(R.id.font_size_selector); for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); }; // 初始化共享偏好设置和字体大小 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); // 修复字体大小ID可能超出范围的问题 if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } // 初始化编辑列表 mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } /** * 活动暂停 onPause 方法 ** 当活动进入后台时调用,保存便签并清除设置状态 *
*/ @Override protected void onPause() { super.onPause(); if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } clearSettingState(); } /** * 更新小部件 ** 当便签更新时,更新对应的桌面小部件 *
*/ private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 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); } /** * 点击事件处理 ** 处理UI组件的点击事件,包括设置背景色、选择背景色、选择字体大小 *
* * @param v 被点击的视图 */ public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); } else if (sBgSelectorBtnsMap.containsKey(id)) { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); mNoteBgColorSelector.setVisibility(View.GONE); } else if (sFontSizeBtnsMap.containsKey(id)) { findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); mFontSizeId = sFontSizeBtnsMap.get(id); mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { getWorkingText(); switchToListMode(mWorkingNote.getContent()); } else { mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } mFontSizeSelector.setVisibility(View.GONE); } } /** * 返回键处理 onBackPressed 方法 ** 当用户点击返回键时调用,先尝试清除设置状态,否则保存便签并返回 *
*/ @Override public void onBackPressed() { if(clearSettingState()) { return; } saveNote(); super.onBackPressed(); } /** * 清除设置状态 ** 关闭背景选择器和字体大小选择器 *
* * @return 是否清除了设置状态 */ 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; } /** * 背景颜色变化监听器 ** 当背景颜色变化时调用,更新UI显示 *
*/ public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } /** * 准备选项菜单 ** 在显示菜单前调用,根据当前便签状态调整菜单项 *
* * @param menu 菜单对象 * @return 是否显示菜单 */ @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { return true; } // 清除设置状态 clearSettingState(); // 根据便签类型加载不同菜单 menu.clear(); if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); } // 根据便签模式调整列表模式菜单项标题 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } // 根据是否有提醒调整提醒相关菜单项可见性 if (mWorkingNote.hasClockAlert()) { menu.findItem(R.id.menu_alert).setVisible(false); } else { menu.findItem(R.id.menu_delete_remind).setVisible(false); } return true; } /** * 选项菜单点击处理 ** 处理菜单选项的点击事件,包括新建便签、删除、字体大小、列表模式、分享等 *
* * @param item 被点击的菜单项 * @return 是否处理了该事件 */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_note: createNewNote(); break; case R.id.menu_delete: // 显示删除确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setMessage(getString(R.string.alert_message_delete_note)); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { deleteCurrentNote(); finish(); } }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case R.id.menu_font_size: // 显示字体大小选择器 mFontSizeSelector.setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: // 切换便签模式 mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: // 分享便签 getWorkingText(); sendTo(this, mWorkingNote.getContent()); break; case R.id.menu_send_to_desktop: // 发送到桌面 sendToDesktop(); break; case R.id.menu_alert: // 设置提醒 setReminder(); break; case R.id.menu_delete_remind: // 删除提醒 mWorkingNote.setAlertDate(0, false); break; default: break; } return true; } /** * 设置提醒 ** 显示日期时间选择对话框,设置便签提醒 *
*/ private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { public void OnDateTimeSet(AlertDialog dialog, long date) { mWorkingNote.setAlertDate(date , true); } }); d.show(); } /** * 分享便签 ** 将便签内容分享给支持ACTION_SEND和text/plain类型的应用 *
* * @param context 上下文对象 * @param info 要分享的内容 */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TEXT, info); intent.setType("text/plain"); context.startActivity(intent); } /** * 创建新便签 ** 保存当前便签,然后启动新的编辑活动 *
*/ 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* 判断当前是否已配置同步账号 *
* * @return 是否为同步模式 */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } /** * 提醒时间变化监听器 ** 当提醒时间变化时调用,设置或取消系统闹钟 *
* * @param date 提醒日期时间 * @param set 是否设置提醒 */ public void onClockAlertChanged(long date, boolean set) { // 先保存便签,确保有Note ID 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 { // 便签为空,无法设置提醒 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) { /** * Should not happen, check for debug */ 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); } private Spannable getHighlightQueryResult(String fullText, String userQuery) { SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { mPattern = Pattern.compile(userQuery); Matcher m = mPattern.matcher(fullText); int start = 0; while (m.find(start)) { spannable.setSpan( new BackgroundColorSpan(this.getResources().getColor( R.color.user_query_highlight)), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); start = m.end(); } } return spannable; } 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); } } 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); } } private boolean getWorkingText() { boolean hasChecked = false; if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < mEditTextList.getChildCount(); i++) { View view = mEditTextList.getChildAt(i); NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); if (!TextUtils.isEmpty(edit.getText())) { if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); hasChecked = true; } else { sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); } } } mWorkingNote.setWorkingText(sb.toString()); } else { mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); } return hasChecked; } private boolean saveNote() { getWorkingText(); boolean saved = mWorkingNote.saveNote(); if (saved) { /** * There are two modes from List view to edit view, open one note, * create/edit a node. Opening node requires to the original * position in the list when back from edit view, while creating a * new node requires to the top of the list. This code * {@link #RESULT_OK} is used to identify the create/edit state */ setResult(RESULT_OK); } return saved; } private void sendToDesktop() { /** * Before send message to home, we should make sure that current * editing note is exists in databases. So, for new note, firstly * save it */ 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 { /** * There is the condition that user has input nothing (the note is * not worthy saving), we have no note id, remind the user that he * should input something */ 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; } private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } }