From aaea3063be97a09fd79c3d6c6a91be1a2fa2813c Mon Sep 17 00:00:00 2001 From: syl <2050387554@qq.com> Date: Sun, 24 Dec 2023 22:37:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=87=E6=B3=A8=E2=80=9C?= =?UTF-8?q?=20git=20commit=20-m=20=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=87=E6=B3=A8=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 代码标注/AlarmAlertActivity.java | 166 ++++ 代码标注/AlarmInitReceiver.java | 65 ++ 代码标注/AlarmReceiver.java | 31 + 代码标注/DateTimePicker.java | 515 +++++++++++ 代码标注/DateTimePickerDialog.java | 101 +++ 代码标注/DropdownMenu.java | 79 ++ 代码标注/FoldersListAdapter.java | 92 ++ 代码标注/NoteEditActivity.java | 1116 ++++++++++++++++++++++++ 8 files changed, 2165 insertions(+) create mode 100644 代码标注/AlarmAlertActivity.java create mode 100644 代码标注/AlarmInitReceiver.java create mode 100644 代码标注/AlarmReceiver.java create mode 100644 代码标注/DateTimePicker.java create mode 100644 代码标注/DateTimePickerDialog.java create mode 100644 代码标注/DropdownMenu.java create mode 100644 代码标注/FoldersListAdapter.java create mode 100644 代码标注/NoteEditActivity.java diff --git a/代码标注/AlarmAlertActivity.java b/代码标注/AlarmAlertActivity.java new file mode 100644 index 0000000..2016db8 --- /dev/null +++ b/代码标注/AlarmAlertActivity.java @@ -0,0 +1,166 @@ +/* + * 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.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Window; +import android.view.WindowManager; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; + +import java.io.IOException; + + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + + private long mNoteId; // 声明一个long类型的变量mNoteId + private String mSnippet; // 声明一个String类型的变量mSnippet + private static final int SNIPPET_PREW_MAX_LEN = 60; // 声明一个int类型的变量SNIPPET_PREW_MAX_LEN,值为60 + MediaPlayer mPlayer; // 声明一个MediaPlayer类型的变量mPlayer + + + @Override // 重写onCreate方法,用于初始化Activity + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); // 设置当前Activity的标题为空 + final Window win = getWindow(); // 获取当前Activity的Window对象 + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 设置当前Activity的Window对象的属性,使其在锁定状态时保持常亮 + + if (!isScreenOn()) { // 判断当前屏幕是否处于锁定状态 + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON // 如果当前屏幕处于锁定状态,则设置当前Activity的Window对象的属性,使其在锁定状态时保持常亮,并允许锁定屏幕 + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + } + + Intent intent = getIntent(); // 获取当前Activity的Intent + + try { + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 从Intent中获取mNoteId + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 从数据库中获取mSnippet + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, // 如果mSnippet的长度大于SNIPPET_PREW_MAX_LEN,则截取mSnippet的前SNIPPET_PREW_MAX_LEN个字符,并加上getResources().getString(R.string.notelist_string_info) + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); // 初始化mPlayer + + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { // 判断mNoteId对应的Notes表中的TYPE_NOTE字段是否可见 + showActionDialog(); // 如果可见,则显示ActionDialog + playAlarmSound(); // 播放闹钟音效 + } else { + finish(); // 如果不可见,则结束当前Activity + } + } + + + private boolean isScreenOn() { // 判断当前屏幕是否处于锁定状态 + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); // 获取当前系统的PowerManager实例 + return pm.isScreenOn(); // 判断当前屏幕是否处于锁定状态 + } + + + private void playAlarmSound() { // 播放闹钟音效 + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); // 获取当前系统默认的闹钟铃声 + + int silentModeStreams = Settings.System.getInt(getContentResolver(), // 获取当前系统设置的静音模式 + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { // 判断当前系统设置的静音模式是否包含AudioManager.STREAM_ALARM + mPlayer.setAudioStreamType(silentModeStreams); // 如果包含,则设置mPlayer的音频流类型为silentModeStreams + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); // 如果不包含,则设置mPlayer的音频流类型为AudioManager.STREAM_ALARM + } + try { + mPlayer.setDataSource(this, url); // 设置mPlayer的数据源 + mPlayer.prepare(); // 准备播放 + mPlayer.setLooping(true); // 设置mPlayer循环播放 + mPlayer.start(); // 开始播放 + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + private void showActionDialog() { // 显示ActionDialog + AlertDialog.Builder dialog = new AlertDialog.Builder(this); // 创建一个AlertDialog.Builder实例 + dialog.setTitle(R.string.app_name); // 设置AlertDialog的标题 + dialog.setMessage(mSnippet); // 设置AlertDialog的消息 + dialog.setPositiveButton(R.string.notealert_ok, this); // 设置AlertDialog的确定按钮 + if (isScreenOn()) { // 判断当前屏幕是否处于锁定状态 + dialog.setNegativeButton(R.string.notealert_enter, this); // 如果当前屏幕处于锁定状态,则设置AlertDialog的取消按钮 + } + dialog.show().setOnDismissListener(this); // 显示AlertDialog + } + + + public void onClick(DialogInterface dialog, int which) { // 重写onClick方法,用于处理用户点击Dialog中的按钮 + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + Intent intent = new Intent(this, NoteEditActivity.class); // 创建一个Intent,用于启动NoteEditActivity + intent.setAction(Intent.ACTION_VIEW); // 设置Intent的action + intent.putExtra(Intent.EXTRA_UID, mNoteId); // 设置Intent的参数 + startActivity(intent); // 启动NoteEditActivity + break; + default: + break; + } + } + + + public void onDismiss(DialogInterface dialog) { // 重写onDismiss方法,用于处理用户关闭Dialog + stopAlarmSound(); // 停止播放闹钟音效 + finish(); // 结束当前Activity + } + + + private void stopAlarmSound() { // 停止播放闹钟音效 + if (mPlayer != null) { // 判断mPlayer是否为空 + mPlayer.stop(); // 如果不为空,则停止播放 + mPlayer.release(); // 释放mPlayer + mPlayer = null; // 将mPlayer置为空 + } + } +} \ No newline at end of file diff --git a/代码标注/AlarmInitReceiver.java b/代码标注/AlarmInitReceiver.java new file mode 100644 index 0000000..383848a --- /dev/null +++ b/代码标注/AlarmInitReceiver.java @@ -0,0 +1,65 @@ +/* + * 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.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class AlarmInitReceiver extends BroadcastReceiver { + +private static final String [] PROJECTION = new String [] { // 声明一个私有静态变量PROJECTION,它是一个字符串数组,其中包含NoteColumns.ID和NoteColumns.ALERTED_DATE两个字符串 + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + @Override + public void onReceive(Context context, Intent intent) { + long currentDate = System.currentTimeMillis(); //获取当前时间 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, //查询数据库中alerted_date大于当前时间,type为note的记录 + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[] { String.valueOf(currentDate) }, + null); + + if (c != null) { + if (c.moveToFirst()) { + do { + long alertDate = c.getLong(COLUMN_ALERTED_DATE); //获取alerted_date + Intent sender = new Intent(context, AlarmReceiver.class); //创建一个Intent,用于传递数据 + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); //将id传递给AlarmReceiver + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); //创建一个PendingIntent,用于传递Intent + AlarmManager alermManager = (AlarmManager) context //获取AlarmManager实例 + .getSystemService(Context.ALARM_SERVICE); + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); //设置定时器,在alerted_date时间点,发送pendingIntent + } while (c.moveToNext()); + } + c.close(); + } + } +} \ No newline at end of file diff --git a/代码标注/AlarmReceiver.java b/代码标注/AlarmReceiver.java new file mode 100644 index 0000000..4be6587 --- /dev/null +++ b/代码标注/AlarmReceiver.java @@ -0,0 +1,31 @@ +/* + * 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.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + + +public class AlarmReceiver extends BroadcastReceiver { // 创建一个AlarmReceiver类,继承自BroadcastReceiver + @Override + public void onReceive(Context context, Intent intent) { // 重写onReceive方法,接收Intent类型的参数 + intent.setClass(context, AlarmAlertActivity.class); // 设置AlarmAlertActivity的Class + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 设置AlarmAlertActivity的Flag + context.startActivity(intent); // 启动AlarmAlertActivity + } +} diff --git a/代码标注/DateTimePicker.java b/代码标注/DateTimePicker.java new file mode 100644 index 0000000..4270956 --- /dev/null +++ b/代码标注/DateTimePicker.java @@ -0,0 +1,515 @@ +/* + * 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 java.text.DateFormatSymbols; +import java.util.Calendar; + +import net.micode.notes.R; + + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +public class DateTimePicker extends FrameLayout { + + private static final boolean DEFAULT_ENABLE_STATE = true; + //声明变量常量 + private static final int HOURS_IN_HALF_DAY = 12; + private static final int HOURS_IN_ALL_DAY = 24; + private static final int DAYS_IN_ALL_WEEK = 7; + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + private static final int MINUT_SPINNER_MIN_VAL = 0; + private static final int MINUT_SPINNER_MAX_VAL = 59; + private static final int AMPM_SPINNER_MIN_VAL = 0; + private static final int AMPM_SPINNER_MAX_VAL = 1; + + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + private Calendar mDate; + + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + private boolean mIsAm; + + private boolean mIs24HourView; + + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + private boolean mInitialising; + + private OnDateTimeChangedListener mOnDateTimeChangedListener; + +private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { // 创建一个NumberPicker.OnValueChangeListener的匿名内部类,用于监听NumberPicker的值改变 + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); // 计算新旧值之间的差值,并将其加到mDate中 + updateDateControl(); // 更新日期控件 + onDateTimeChanged(); // 更新时间控件 + } + }; + + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { // 判断是否是24小时制 + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + if (mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { // 如果当前是上午,且当前小时是half day,则设置当前时间为下午 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { // 如果当前是下午,且当前小时是half day,则设置当前时间为上午 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || // 如果当前是上午,且当前小时是half day,则设置当前时间为下午 + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { // 如果当前是24小时制,且当前小时是all day,则设置当前时间为次日 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } + else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { // 如果当前是24小时制,且当前小时是all day,则设置当前时间为次日 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); // 获取当前小时 + mDate.set(Calendar.HOUR_OF_DAY, newHour); // 设置当前时间 + onDateTimeChanged(); + if (isDateChanged) { // 如果日期改变,则更新当前日期 + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); // 获取分钟数字选择器的最小值 + int maxValue = mMinuteSpinner.getMaxValue(); // 获取分钟数字选择器的最大值 + int offset = 0; + + if (oldVal == maxValue && newVal == minValue) { // 如果旧的值是最大值,新的值是最小值,则偏移量加1 + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { // 如果旧的值是最小值,新的值是最大值,则偏移量减1 + offset -= 1; + } + + if (offset != 0) { // 如果偏移量不等于0,则添加偏移量 + mDate.add(Calendar.HOUR_OF_DAY, offset); // 添加偏移量 + mHourSpinner.setValue(getCurrentHour()); // 设置小时数字选择器的值 + updateDateControl(); // 更新日期控件 + int newHour = getCurrentHourOfDay(); // 获取当前的小时 + if (newHour >= HOURS_IN_HALF_DAY) { // 如果当前的小时大于等于12,则设置AM为false + mIsAm = false; + updateAmPmControl(); // 更新AM/PM控件 + } else { // 否则,设置AM为true + mIsAm = true; + updateAmPmControl(); // 更新AM/PM控件 + } + } + mDate.set(Calendar.MINUTE, newVal); // 设置分钟数字选择器的值 + onDateTimeChanged(); // 更新日期控件 + } + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm = !mIsAm; // 判断当前是上午还是下午 + if (mIsAm) { // 根据当前是上午还是下午,调整时间 + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + + updateAmPmControl(); // 更新AM/PM控制 + onDateTimeChanged(); // 更新时间 + } + }; + + +public interface OnDateTimeChangedListener { // 添加中文注释后 + + void onDateTimeChanged(DateTimePicker view, int year, int month, // 当时间改变时调用 + int dayOfMonth, int hourOfDay, int minute); + } + + + public DateTimePicker(Context context) { // 构造函数,传入上下文和当前时间戳 + this(context, System.currentTimeMillis()); + } + + +public DateTimePicker(Context context, long date) { // 创建一个DateTimePicker对象,参数为context和date,dateFormat默认为24小时制 + this(context, date, DateFormat.is24HourFormat(context)); + } + + +public DateTimePicker(Context context, long date, boolean is24HourView) { // 创建DateTimePicker对象,传入上下文、日期和是否24小时视图 + super(context); + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + + + mDateSpinner = (NumberPicker) findViewById(R.id.date); //获取日期选择器 + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); //设置最小值 + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); //设置最大值 + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); //设置日期改变监听器 + + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); // 设置AM/PM选择器 + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); + mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + + updateDateControl(); // 更新日期控制 + updateHourControl(); // 更新小时控制 + updateAmPmControl(); // 更新AM/PM控制 + + set24HourView(is24HourView); // 设置24小时视图 + setCurrentDate(date); // 设置当前日期 + setEnabled(isEnabled()); // 设置是否可用 + + mInitialising = false; + } + + @Override + public void setEnabled(boolean enabled) { + + if (mIsEnabled == enabled) { // 判断当前状态是否和传入的参数一致 + return; + } + + super.setEnabled(enabled); // 调用父类的setEnabled方法 + mDateSpinner.setEnabled(enabled); // 设置日期选择器是否可用 + mMinuteSpinner.setEnabled(enabled); // 设置分钟选择器是否可用 + mHourSpinner.setEnabled(enabled); // 设置小时选择器是否可用 + mAmPmSpinner.setEnabled(enabled); // 设置上午/下午选择器是否可用 + mIsEnabled = enabled; // 更新状态 + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * Get the current date in millis + * + * @return the current date in millis + */ + +public long getCurrentDateInTimeMillis() { //获取当前日期的时间戳 + return mDate.getTimeInMillis(); + } + /** + * Set the current date + * + * @param date The current date in millis + */ + +public void setCurrentDate(long date) { //设置当前日期 + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); + } + + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + +public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { //设置当前日期 + setCurrentYear(year); //设置当前年份 + setCurrentMonth(month); //设置当前月份 + setCurrentDay(dayOfMonth); //设置当前日期 + setCurrentHour(hourOfDay); //设置当前小时 + setCurrentMinute(minute); //设置当前分钟 + } + + /** + * Get current year + * + * @return The current year + */ + +public int getCurrentYear() { //获取当前年份 + return mDate.get(Calendar.YEAR); + } + + /** + * Set current year + * + * @param year The current year + */ + +public void setCurrentYear(int year) { //设置当前年份 + if (!mInitialising && year == getCurrentYear()) { //如果初始化完成且当前年份和传入年份相同,则直接返回 + return; + } + + mDate.set(Calendar.YEAR, year); //设置当前年份 + updateDateControl(); //更新日期控件 + onDateTimeChanged(); //日期改变时调用 + } + + /** + * Get current month in the year + * + * @return The current month in the year + */ + +public int getCurrentMonth() { //获取当前月份 + return mDate.get(Calendar.MONTH); + } + + /** + * Set current month in the year + * + * @param month The month in the year + */ + +public void setCurrentMonth(int month) { //设置当前月份 + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current day of the month + * + * @return The day of the month + */ + +public int getCurrentDay() { //获取当前日期 + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + +public void setCurrentDay(int dayOfMonth) { //设置当前日期 + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + +public int getCurrentHourOfDay() { //获取当前小时 + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ // 24小时模式 + return getCurrentHourOfDay(); + } else { // 12小时模式 + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { // 当前小时大于12小时,返回当前小时减去12小时 + return hour - HOURS_IN_HALF_DAY; + } else { // 当前小时小于等于12小时,返回当前小时 + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * Set current hour in 24 hour mode, in the range (0~23) + * + * @param hourOfDay + */ + +public void setCurrentHour(int hourOfDay) { //设置当前小时 + + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { //如果初始化完成且当前小时和传入的参数相等,则直接返回 + return; + } + + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); //设置当前小时 + if (!mIs24HourView) { //如果不是24小时制 + if (hourOfDay >= HOURS_IN_HALF_DAY) { //如果传入的参数大于等于12 + mIsAm = false; //设置AM为false + if (hourOfDay > HOURS_IN_HALF_DAY) { //如果传入的参数大于12 + hourOfDay -= HOURS_IN_HALF_DAY; //减去12 + } + } else { + mIsAm = true; //设置AM为true + if (hourOfDay == 0) { //如果传入的参数等于0 + hourOfDay = HOURS_IN_HALF_DAY; //设置为12 + } + } + updateAmPmControl(); //更新AM/PM控制 + } + mHourSpinner.setValue(hourOfDay); //设置小时值 + onDateTimeChanged(); //日期改变时调用 + } + + /** + * Get currentMinute + * + * @return The Current Minute + */ + +public int getCurrentMinute() { //获取当前分钟 + return mDate.get(Calendar.MINUTE); + } + + /** + * Set current minute + */ + +public void setCurrentMinute(int minute) { //设置当前分钟 + if (!mInitialising && minute == getCurrentMinute()) { //如果初始化未完成,且分钟数相同,则返回 + return; + } + mMinuteSpinner.setValue(minute); //设置分钟数 + mDate.set(Calendar.MINUTE, minute); //设置日期 + onDateTimeChanged(); //更新时间 + } + + /** + * @return true if this is in 24 hour view else false. + */ + +public boolean is24HourView () { //获取是否是24小时视图 + return mIs24HourView; + } + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + +public void set24HourView(boolean is24HourView) { //设置24小时视图 + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); //设置时间控制条的可见性 + int hour = getCurrentHourOfDay(); //获取当前的小时 + updateHourControl(); //更新小时控制条 + setCurrentHour(hour); //设置当前的小时 + updateAmPmControl(); //更新上午/下午控制条 + } + + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); // 获取当前时间 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); // 设置当前时间所在的星期 + mDateSpinner.setDisplayedValues(null); // 设置显示的值 + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { // 遍历所有的星期 + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); // 设置显示的值 + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置当前时间所在的星期 + mDateSpinner.invalidate(); + } + + private void updateAmPmControl() { + if (mIs24HourView) { // 如果24小时视图是可见的,则隐藏AM/PM控制 + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; // 否则,如果当前是AM,则设置索引为Calendar.AM,否则设置为Calendar.PM + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + private void updateHourControl() { + if (mIs24HourView) { // 根据mIs24HourView的值来更新mHourSpinner的值 + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + +public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { //设置日期时间改变监听器 + mOnDateTimeChangedListener = callback; + } + + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { // 当日期时间改变时调用 + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} diff --git a/代码标注/DateTimePickerDialog.java b/代码标注/DateTimePickerDialog.java new file mode 100644 index 0000000..8a38c75 --- /dev/null +++ b/代码标注/DateTimePickerDialog.java @@ -0,0 +1,101 @@ +/* + * 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 java.util.Calendar; + +import net.micode.notes.R; +import net.micode.notes.ui.DateTimePicker; +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.text.format.DateFormat; +import android.text.format.DateUtils; + + +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + // 创建一个Calendar实例 + private Calendar mDate = Calendar.getInstance(); + // 是否是24小时制 + private boolean mIs24HourView; + // 设置时间监听器 + private OnDateTimeSetListener mOnDateTimeSetListener; + // 创建一个DateTimePicker实例 + private DateTimePicker mDateTimePicker; + + // 定义一个接口,用于设置时间 + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + + // 构造函数,传入上下文和时间 + public DateTimePickerDialog(Context context, long date) { + super(context); + mDateTimePicker = new DateTimePicker(context); + setView(mDateTimePicker); + mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { + public void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + mDate.set(Calendar.YEAR, year); + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + updateTitle(mDate.getTimeInMillis()); + } + }); + mDate.setTimeInMillis(date); + mDate.set(Calendar.SECOND, 0); + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + setButton(context.getString(R.string.datetime_dialog_ok), this); + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + set24HourView(DateFormat.is24HourFormat(this.getContext())); + updateTitle(mDate.getTimeInMillis()); + } + + // 设置是否是24小时制 + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + + // 设置时间监听器 + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; + } + + // 更新标题 + private void updateTitle(long date) { + int flag = + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); + } + + // 点击事件 + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener != null) { + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } + +} \ No newline at end of file diff --git a/代码标注/DropdownMenu.java b/代码标注/DropdownMenu.java new file mode 100644 index 0000000..74a3fea --- /dev/null +++ b/代码标注/DropdownMenu.java @@ -0,0 +1,79 @@ +/* + * 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.content.Context; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; + +import net.micode.notes.R; + +public class DropdownMenu { + // 声明一个Button变量 + private Button mButton; + // 声明一个PopupMenu变量 + private PopupMenu mPopupMenu; + // 声明一个Menu变量 + private Menu mMenu; + + // 构造函数,传入上下文、Button和菜单ID + public DropdownMenu(Context context, Button button, int menuId) { + // 将传入的Button赋值给mButton + mButton = button; + // 为mButton设置背景资源 + mButton.setBackgroundResource(R.drawable.dropdown_icon); + // 创建一个PopupMenu,传入上下文和mButton + mPopupMenu = new PopupMenu(context, mButton); + // 获取mPopupMenu的Menu + mMenu = mPopupMenu.getMenu(); + // 为mMenu设置菜单ID + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 为mButton设置点击事件 + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // 显示mPopupMenu + mPopupMenu.show(); + } + }); + } + + // 设置点击事件 + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + // 如果mPopupMenu不为空 + if (mPopupMenu != null) { + // 为mPopupMenu设置点击事件 + mPopupMenu.setOnMenuItemClickListener(listener); + } + } + + // 查找菜单项 + public MenuItem findItem(int id) { + // 返回mMenu中id对应的菜单项 + return mMenu.findItem(id); + } + + // 设置标题 + public void setTitle(CharSequence title) { + // 设置mButton的文本 + mButton.setText(title); + } +} diff --git a/代码标注/FoldersListAdapter.java b/代码标注/FoldersListAdapter.java new file mode 100644 index 0000000..b01947b --- /dev/null +++ b/代码标注/FoldersListAdapter.java @@ -0,0 +1,92 @@ +/* + * 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.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class FoldersListAdapter extends CursorAdapter { + // 定义查询列 + public static final String [] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + }; + + // 定义查询列的索引 + public static final int ID_COLUMN = 0; + public static final int NAME_COLUMN = 1; + + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // 创建一个新的View + return new FolderListItem(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // 绑定View + if (view instanceof FolderListItem) { + // 获取文件夹名 + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + // 绑定文件夹名 + ((FolderListItem) view).bind(folderName); + } + } + + public String getFolderName(Context context, int position) { + // 获取指定位置的Cursor + Cursor cursor = (Cursor) getItem(position); + // 返回文件夹名 + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + } + + private class FolderListItem extends LinearLayout { + // 定义文本框 + private TextView mName; + + public FolderListItem(Context context) { + super(context); + // 加载布局 + inflate(context, R.layout.folder_list_item, this); + // 获取文本框 + mName = (TextView) findViewById(R.id.tv_folder_name); + } + + public void bind(String name) { + // 绑定文本框 + mName.setText(name); + } + } + +} \ No newline at end of file diff --git a/代码标注/NoteEditActivity.java b/代码标注/NoteEditActivity.java new file mode 100644 index 0000000..18ae05f --- /dev/null +++ b/代码标注/NoteEditActivity.java @@ -0,0 +1,1116 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.app.SearchManager; +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Paint; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.style.BackgroundColorSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.TextNote; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.tool.ResourceParser.TextAppearanceResources; +import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; +import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener { + private class HeadViewHolder { + // 修改时间 + public TextView tvModified; + + // 警报图标 + public ImageView ivAlertIcon; + + // 警报日期 + public TextView tvAlertDate; + + // 设置背景颜色 + public ImageView ibSetBgColor; + } + + private static final Map sBgSelectorBtnsMap = new HashMap(); + static { + // 将R.id.iv_bg_yellow对应的ResourceParser.YELLOW添加到sBgSelectorBtnsMap中 + sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); + // 将R.id.iv_bg_red对应的ResourceParser.RED添加到sBgSelectorBtnsMap中 + sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); + // 将R.id.iv_bg_blue对应的ResourceParser.BLUE添加到sBgSelectorBtnsMap中 + sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); + // 将R.id.iv_bg_green对应的ResourceParser.GREEN添加到sBgSelectorBtnsMap中 + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); + // 将R.id.iv_bg_white对应的ResourceParser.WHITE添加到sBgSelectorBtnsMap中 + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); + } + + private static final Map sBgSelectorSelectionMap = new HashMap(); + static { + // 将YELLOW放入sBgSelectorSelectionMap中 + sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); + // 将RED放入sBgSelectorSelectionMap中 + sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); + // 将BLUE放入sBgSelectorSelectionMap中 + sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); + // 将GREEN放入sBgSelectorSelectionMap中 + sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); + // 将WHITE放入sBgSelectorSelectionMap中 + sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); + } + + private static final Map sFontSizeBtnsMap = new HashMap(); + static { + // 将R.id.ll_font_large和ResourceParser.TEXT_LARGE放入sFontSizeBtnsMap中 + sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); + // 将R.id.ll_font_small和ResourceParser.TEXT_SMALL放入sFontSizeBtnsMap中 + sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); + // 将R.id.ll_font_normal和ResourceParser.TEXT_MEDIUM放入sFontSizeBtnsMap中 + sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); + // 将R.id.ll_font_super和ResourceParser.TEXT_SUPER放入sFontSizeBtnsMap中 + sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); + } + + private static final Map sFontSelectorSelectionMap = new HashMap(); + static { + // 将ResourceParser.TEXT_LARGE和R.id.iv_large_select放入sFontSelectorSelectionMap中 + sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); + // 将ResourceParser.TEXT_SMALL和R.id.iv_small_select放入sFontSelectorSelectionMap中 + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); + // 将ResourceParser.TEXT_MEDIUM和R.id.iv_medium_select放入sFontSelectorSelectionMap中 + sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); + // 将ResourceParser.TEXT_SUPER和R.id.iv_super_select放入sFontSelectorSelectionMap中 + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); + } + + private static final String TAG = "NoteEditActivity"; + + private HeadViewHolder mNoteHeaderHolder; + + private View mHeadViewPanel; + + private View mNoteBgColorSelector; + + private View mFontSizeSelector; + + private EditText mNoteEditor; + + private View mNoteEditorPanel; + + private WorkingNote mWorkingNote; + + private SharedPreferences mSharedPrefs; + private int mFontSizeId; + + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + + public static final String TAG_CHECKED = String.valueOf('\u221A'); + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + + private LinearLayout mEditTextList; + + private String mUserQuery; + private Pattern mPattern; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 设置当前页面的布局 + this.setContentView(R.layout.note_edit); + + // 如果savedInstanceState为空,且initActivityState(getIntent())返回false,则结束当前Activity + if (savedInstanceState == null && !initActivityState(getIntent())) { + finish(); + return; + } + // 初始化资源 + initResources(); + } + + /** + * Current activity may be killed when the memory is low. Once it is killed, for another time + * user load this activity, we should restore the former state + */ + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + // 从保存的实例状态中恢复 + 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"); + } + } + + private boolean initActivityState(Intent intent) { + /** + * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, + * then jump to the NotesListActivity + */ + mWorkingNote = null; + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + // 获取传入的noteId + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); + // 设置查询条件 + mUserQuery = ""; + + /** + * Starting from the searched result + */ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + } + + // 检查note是否可见 + 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 { + // 加载note + 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())) { + // New note + // 获取传入的文件夹id + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + // 获取传入的小部件id + 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); + // 获取传入的小部件背景资源id + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this)); + + // Parse call-record note + // 获取传入的电话号码 + 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; + // 获取电话号码和通话日期的noteId + if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), + phoneNumber, callDate)) > 0) { + // 加载note + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load call note failed with note id" + noteId); + finish(); + return false; + } + } else { + // 创建一个新的call note + 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 { + // 设置高亮查询结果 + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + // 设置光标位置 + mNoteEditor.setSelection(mNoteEditor.getText().length()); + } + // 隐藏所有背景选择器 + for (Integer id : sBgSelectorSelectionMap.keySet()) { + findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); + } + // 设置标题背景 + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + // 设置笔记背景 + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + + // 设置修改日期 + mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, + mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR)); + + /** + * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker + * is not ready + */ + // 显示提醒头部 + showAlertHeader(); + } + + private void showAlertHeader() { + // 检查工作笔记是否有闹钟 + if (mWorkingNote.hasClockAlert()) { + // 获取当前时间 + long time = System.currentTimeMillis(); + // 如果当前时间大于闹钟时间 + if (time > mWorkingNote.getAlertDate()) { + // 设置提示文字为已过期 + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); + } else { + // 设置提示文字为剩余时间 + mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); + } + // 设置提示文字可见 + mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); + // 设置提示图标可见 + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); + } else { + // 设置提示文字不可见 + mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); + // 设置提示图标不可见 + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); + }; + } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + // 重新初始化活动状态 + initActivityState(intent); + } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + /** + * For new note without note id, we should firstly save it to + * generate a id. If the editing note is not worth saving, there + * is no id which is equivalent to create new note + */ + // 如果当前笔记不存在数据库中,则保存笔记 + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + // 将当前笔记的ID存储到Bundle中 + outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + // 如果mNoteBgColorSelector可见,且ev不在mNoteBgColorSelector范围内,则将mNoteBgColorSelector设置为不可见 + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mNoteBgColorSelector, ev)) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } + + // 如果mFontSizeSelector可见,且ev不在mFontSizeSelector范围内,则将mFontSizeSelector设置为不可见 + if (mFontSizeSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mFontSizeSelector, ev)) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + // 调用父类的dispatchTouchEvent方法 + 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]; + //判断触摸点是否在View的范围内 + if (ev.getX() < x + || ev.getX() > (x + view.getWidth()) + || ev.getY() < y + || ev.getY() > (y + view.getHeight())) { + return false; + } + return true; + } + + private void initResources() { + // 初始化头部视图 + mHeadViewPanel = findViewById(R.id.note_title); + // 初始化头部视图的holder + 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); + //遍历sFontSizeBtnsMap中的键,找到对应的View + for (int id : sFontSizeBtnsMap.keySet()) { + View view = findViewById(id); + //为View设置点击事件 + view.setOnClickListener(this); + }; + //获取SharedPreferences + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + //获取字体大小 + mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { + mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; + } + //获取布局中的LinearLayout + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + } + + @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 intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 根据小部件类型,设置Intent的class + if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + Log.e(TAG, "Unspported widget type"); + return; + } + + // 将小部件ID放入Intent中 + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { + mWorkingNote.getWidgetId() + }); + + // 发送Intent + sendBroadcast(intent); + // 设置更新结果 + setResult(RESULT_OK, intent); + } + + 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); + // 设置当前笔记的背景颜色id + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); + // 设置背景颜色选择器不可见 + mNoteBgColorSelector.setVisibility(View.GONE); + // 如果点击的是字体大小选择器中的按钮 + } else if (sFontSizeBtnsMap.containsKey(id)) { + // 设置字体大小选择器中的按钮不可见 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + // 设置当前笔记的字体大小id + mFontSizeId = sFontSizeBtnsMap.get(id); + // 保存当前笔记的字体大小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(); + //调用父类的onBackPressed()方法 + super.onBackPressed(); + } + + private boolean clearSettingState() { + //检查mNoteBgColorSelector的可见性 + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { + //将mNoteBgColorSelector设置为不可见 + mNoteBgColorSelector.setVisibility(View.GONE); + //返回true + return true; + //检查mFontSizeSelector的可见性 + } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + //将mFontSizeSelector设置为不可见 + mFontSizeSelector.setVisibility(View.GONE); + //返回true + return true; + } + //返回false + return false; + } + + //当背景颜色改变时调用 +public void onBackgroundColorChanged() { + //设置背景颜色选择器可见 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.VISIBLE); + //设置笔记编辑面板的背景资源 + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + //设置头部视图面板的背景资源 + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + // 如果正在关闭,则返回true + if (isFinishing()) { + return true; + } + // 清除设置状态 + clearSettingState(); + // 清除菜单 + menu.clear(); + // 如果文件夹ID等于Notes.ID_CALL_RECORD_FOLDER,则使用R.menu.call_note_edit,否则使用R.menu.note_edit + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_note_edit, menu); + } else { + getMenuInflater().inflate(R.menu.note_edit, menu); + } + // 如果检查列表模式为TextNote.MODE_CHECK_LIST,则设置菜单标题为R.string.menu_normal_mode,否则设置菜单标题为R.string.menu_list_mode + 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); + } + // 如果有闹钟,则隐藏菜单中的menu_alert,否则隐藏菜单中的menu_delete_remind + if (mWorkingNote.hasClockAlert()) { + menu.findItem(R.id.menu_alert).setVisible(false); + } else { + menu.findItem(R.id.menu_delete_remind).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_note: + // 创建新笔记 + createNewNote(); + 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对象,传入当前时间 + DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + // 为DateTimePickerDialog设置OnDateTimeSetListener,当设置时间时调用 + d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + public void OnDateTimeSet(AlertDialog dialog, long date) { + // 设置mWorkingNote的alertDate为传入的date,并设置为true + mWorkingNote.setAlertDate(date , true); + } + }); + // 显示DateTimePickerDialog + d.show(); + } + + /** + * Share note to apps that support {@link Intent#ACTION_SEND} action + * and {@text/plain} type + */ + private void sendTo(Context context, String info) { + Intent intent = new Intent(Intent.ACTION_SEND); + // 将要发送的信息放入Intent中 + 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 + HashSet ids = new HashSet(); + // 获取当前笔记的id + long id = mWorkingNote.getNoteId(); + // 如果当前笔记的id不是根文件夹的id + if (id != Notes.ID_ROOT_FOLDER) { + // 将当前笔记的id添加到HashSet中 + ids.add(id); + } else { + Log.d(TAG, "Wrong note id, should not happen"); + } + // 如果不是同步模式 + if (!isSyncMode()) { + // 尝试批量删除笔记 + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "Delete Note error"); + } + } else { + // 尝试批量移动到垃圾文件夹 + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + } + // 标记当前笔记已删除 + mWorkingNote.markDeleted(true); + } + + //获取NotesPreferenceActivity中的同步账户名,并判断是否大于0 +private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + + public void onClockAlertChanged(long date, boolean set) { + /** + * User could set clock to an unsaved note, so before setting the + * alert clock, we should save the note first + */ + 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 { + /** + * 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, "Clock alert setting error"); + showToast(R.string.error_note_empty_for_clock); + } + } + + //当小部件改变时调用 +public void onWidgetChanged() { + updateWidget(); + } + + // 添加中文注释后 +public void onEditTextDelete(int index, String text) { + // 获取EditText列表的子视图的数量 + int childCount = mEditTextList.getChildCount(); + // 如果只有一个子视图,则直接返回 + if (childCount == 1) { + return; + } + + // 从index+1开始,将EditText列表中的每一个子视图的index属性设置为i-1 + for (int i = index + 1; i < childCount; i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i - 1); + } + + // 从EditText列表中移除指定位置的子视图 + mEditTextList.removeViewAt(index); + NoteEditText edit = null; + // 如果移除的是第一个子视图,则获取EditText列表的第一个子视图 + if(index == 0) { + edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( + R.id.et_edit_text); + // 否则,获取EditText列表中指定位置的子视图 + } else { + edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( + R.id.et_edit_text); + } + // 获取EditText中的文本长度 + int length = edit.length(); + // 将文本添加到EditText中 + edit.append(text); + // 设置EditText获取焦点 + edit.requestFocus(); + // 设置EditText中的光标位置 + edit.setSelection(length); + } + + public void onEditTextEnter(int index, String text) { + /** + * Should not happen, check for debug + */ + // 检查索引是否超出mEditTextList的范围 + if(index > mEditTextList.getChildCount()) { + Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + } + + // 获取指定索引的View + View view = getListItem(text, index); + // 将View添加到mEditTextList中 + mEditTextList.addView(view, index); + // 获取EditText + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + // 设置焦点 + edit.requestFocus(); + // 设置光标位置 + edit.setSelection(0); + // 遍历mEditTextList,设置每个EditText的索引 + 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中的所有视图 + mEditTextList.removeAllViews(); + // 将文本按换行符分割成字符串数组 + String[] items = text.split("\n"); + int index = 0; + // 遍历字符串数组,将每一个字符串添加到mEditTextList中 + for (String item : items) { + if(!TextUtils.isEmpty(item)) { + mEditTextList.addView(getListItem(item, index)); + index++; + } + } + // 将一个空字符串添加到mEditTextList中 + mEditTextList.addView(getListItem("", index)); + // 将焦点设置到mEditTextList中最后一个添加的视图中 + mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + + // 将mNoteEditor设置为不可见,mEditTextList设置为可见 + mNoteEditor.setVisibility(View.GONE); + mEditTextList.setVisibility(View.VISIBLE); + } + + private Spannable getHighlightQueryResult(String fullText, String userQuery) { + //创建一个SpannableString对象,用于存储高亮后的文本 + 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); + // 获取edit文本框 + 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); + } + } + }); + + // 判断item是否以TAG_CHECKED开头 + if (item.startsWith(TAG_CHECKED)) { + // 设置复选框为选中状态 + cb.setChecked(true); + // 设置文本框的画笔标志 + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + // 截取item + 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 = 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) { + //如果索引大于等于EditText列表的子元素数量,则打印错误信息 + 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(); + // 遍历EditTextList中的每一个View + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + // 获取EditText + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + // 判断EditText是否为空 + if (!TextUtils.isEmpty(edit.getText())) { + // 获取复选框 + if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + // 将复选框中的文本添加到StringBuilder中 + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + hasChecked = true; + } else { + // 将未复选框中的文本添加到StringBuilder中 + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); + } + } + } + // 将StringBuilder中的文本设置为当前工作文本 + mWorkingNote.setWorkingText(sb.toString()); + } else { + // 将NoteEditor中的文本设置为当前工作文本 + 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 + Intent sender = new Intent(); + // 创建一个Intent,用于跳转到NoteEditActivity + Intent shortcutIntent = new Intent(this, NoteEditActivity.class); + // 设置Intent的Action + shortcutIntent.setAction(Intent.ACTION_VIEW); + // 设置Intent的参数,用于跳转到NoteEditActivity + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + // 将Intent添加到sender中 + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + // 设置Intent的名称 + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent())); + // 设置Intent的图标 + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); + // 设置Intent的参数,用于标记是否是重复创建 + sender.putExtra("duplicate", true); + // 设置Intent的Action + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + // 显示提示信息 + showToast(R.string.info_note_enter_desktop); + // 发送Intent + 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) { + // 移除TAG_CHECKED和TAG_UNCHECKED + content = content.replace(TAG_CHECKED, ""); + content = content.replace(TAG_UNCHECKED, ""); + // 如果内容长度大于SHORTCUT_ICON_TITLE_MAX_LEN,则截取前SHORTCUT_ICON_TITLE_MAX_LEN个字符 + return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, + SHORTCUT_ICON_TITLE_MAX_LEN) : content; + } + + private void showToast(int resId) { + // 显示Toast,持续时间为Toast.LENGTH_SHORT + showToast(resId, Toast.LENGTH_SHORT); + } + + private void showToast(int resId, int duration) { + // 创建Toast,显示resId,持续时间为duration + Toast.makeText(this, resId, duration).show(); + } +}