diff --git a/AlarmAlertActivity.java b/AlarmAlertActivity.java new file mode 100644 index 0000000..b13107a --- /dev/null +++ b/AlarmAlertActivity.java @@ -0,0 +1,159 @@ +/* + * 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; + +// AlarmAlertActivity 是一个用于显示便签提醒的 Activity。 +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteId; // 便签的ID。 + private String mSnippet; // 便签的预览文本。 + private static final int SNIPPET_PREW_MAX_LEN = 60; // 预览文本的最大长度。 + MediaPlayer mPlayer; // 用于播放提醒声音的 MediaPlayer。 + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求无标题栏的窗口特性。 + + final Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 锁屏时显示窗口。 + + if (!isScreenOn()) { + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | 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 中获取便签 ID。 + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 获取便签预览文本。 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; // 预览文本超出最大长度时截断。 + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); // 初始化 MediaPlayer。 + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog(); // 显示操作对话框。 + playAlarmSound(); // 播放提醒声音。 + } else { + finish(); // 如果便签不可见,则结束 Activity。 + } + } + + // 检查屏幕是否开启。 + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + 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) { + mPlayer.setAudioStreamType(silentModeStreams); + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + try { + mPlayer.setDataSource(this, url); + mPlayer.prepare(); + mPlayer.setLooping(true); + mPlayer.start(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 显示操作对话框。 + private void showActionDialog() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(R.string.app_name); + dialog.setMessage(mSnippet); + dialog.setPositiveButton(R.string.notealert_ok, this); + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); + } + dialog.show().setOnDismissListener(this); + } + + // DialogInterface.OnClickListener 的实现,处理对话框按钮点击事件。 + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + startActivity(intent); + break; + default: + break; + } + } + + // DialogInterface.OnDismissListener 的实现,处理对话框消失事件。 + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); + finish(); + } + + // 停止提醒声音。 + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop(); + mPlayer.release(); + mPlayer = null; + } + } +} \ No newline at end of file diff --git a/AlarmInitReceiver.java b/AlarmInitReceiver.java new file mode 100644 index 0000000..c1e7d08 --- /dev/null +++ b/AlarmInitReceiver.java @@ -0,0 +1,75 @@ +/* + * 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; // 引入AlarmManager类,用于设置闹钟 +import android.app.PendingIntent; // 引入PendingIntent类,表示延迟的Intent +import android.content.BroadcastReceiver; // 引入BroadcastReceiver类,用于接收广播 +import android.content.ContentUris; // 引入ContentUris类,用于生成URI +import android.content.Context; // 引入Context类,用于访问系统资源 +import android.content.Intent; // 引入Intent类,用于启动组件 +import android.database.Cursor; // 引入Cursor类,用于数据库查询 + +import net.micode.notes.data.Notes; // 引入Notes类,处理笔记数据 +import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns类,表示Notes表的列 + +public class AlarmInitReceiver extends BroadcastReceiver { // 定义一个继承自BroadcastReceiver的类 + + // 定义查询需要的列 + private static final String [] PROJECTION = new String [] { + NoteColumns.ID, // 获取笔记的ID列 + NoteColumns.ALERTED_DATE // 获取提醒时间列 + }; + + // 列索引常量 + private static final int COLUMN_ID = 0; // 第0列是ID列 + private static final int COLUMN_ALERTED_DATE = 1; // 第1列是提醒时间列 + + @Override + public void onReceive(Context context, Intent intent) { // 重写onReceive方法,接收广播 + long currentDate = System.currentTimeMillis(); // 获取当前的系统时间(以毫秒为单位) + + // 查询数据库,获取需要提醒的笔记,且提醒时间大于当前时间 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, // 查询URI为Notes.CONTENT_NOTE_URI的内容提供者 + 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); // 获取提醒时间 + Intent sender = new Intent(context, AlarmReceiver.class); // 创建一个新的Intent,用于发送广播 + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); // 设置Intent的数据(笔记的ID) + + // 创建PendingIntent,表示在指定时间执行AlarmReceiver广播 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + + // 获取系统的AlarmManager服务 + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + // 设置闹钟,使用RTC_WAKEUP模式,当到达指定的提醒时间时唤醒设备并触发PendingIntent + alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); // 如果查询结果中还有下一条记录,则继续处理 + } + c.close(); // 关闭Cursor + } + } +} diff --git a/AlarmReceiver.java b/AlarmReceiver.java new file mode 100644 index 0000000..c692e17 --- /dev/null +++ b/AlarmReceiver.java @@ -0,0 +1,35 @@ +/* + * 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; + +// AlarmReceiver 类继承自 BroadcastReceiver,用于接收便签提醒的广播事件。 +public class AlarmReceiver extends BroadcastReceiver { + // onReceive 方法是 BroadcastReceiver 的回调方法,当接收到广播时被调用。 + @Override + public void onReceive(Context context, Intent intent) { + // 设置 intent 的目标类为 AlarmAlertActivity。 + intent.setClass(context, AlarmAlertActivity.class); + // 为 intent 添加 FLAG_ACTIVITY_NEW_TASK 标志,使得在新的任务栈中启动 Activity。 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 在给定的上下文中启动 AlarmAlertActivity。 + context.startActivity(intent); + } +} \ No newline at end of file diff --git a/DateTimePicker.java b/DateTimePicker.java new file mode 100644 index 0000000..52692cf --- /dev/null +++ b/DateTimePicker.java @@ -0,0 +1,222 @@ +/* + * 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; + +// DateTimePicker 类继承自 FrameLayout,用于提供一个日期时间选择器。 +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; + // 24小时制和12小时制下小时选择器的最小值和最大值。 + 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; + + // 日期、小时、分钟和上下午的 NumberPicker 控件。 + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + // 用于存储日期的 Calendar 对象。 + private Calendar mDate; + + // 日期显示值数组。 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + // 表示是否为上午的布尔值。 + private boolean mIsAm; + + // 表示是否为24小时视图的布尔值。 + private boolean mIs24HourView; + + // 控件的启用状态。 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + // 正在初始化的布尔值。 + private boolean mInitialising; + + // 日期时间改变监听器。 + private OnDateTimeChangedListener mOnDateTimeChangedListener; + + // 日期改变监听器。 + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 更新日期并通知日期时间改变。 + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + updateDateControl(); + onDateTimeChanged(); + } + }; + + // 小时改变监听器。 + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 更新小时并通知日期时间改变。 + // 处理12小时制下上下午的转换。 + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_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) { + 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 || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + 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) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); + mHourSpinner.setValue(getCurrentHour()); + updateDateControl(); + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + 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(); + onDateTimeChanged(); + } + }; + + // 日期时间改变监听器接口。 + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + // DateTimePicker 的构造函数。 + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + } + + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + + // 初始化日期、小时、分钟和上下午的 NumberPicker 控件。 + 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); + + // \ No newline at end of file diff --git a/DateTimePickerDialog.java b/DateTimePickerDialog.java new file mode 100644 index 0000000..ceffc71 --- /dev/null +++ b/DateTimePickerDialog.java @@ -0,0 +1,98 @@ +/* + * 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; // 引入Calendar类用于处理日期和时间 + +import net.micode.notes.R; // 引入R文件,用于获取资源ID +import net.micode.notes.ui.DateTimePicker; // 引入自定义的DateTimePicker控件 +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; // 引入DateTimePicker控件的监听器 + +import android.app.AlertDialog; // 引入AlertDialog类,用于创建对话框 +import android.content.Context; // 引入Context类,用于访问应用程序上下文 +import android.content.DialogInterface; // 引入DialogInterface类,用于处理对话框的按钮事件 +import android.content.DialogInterface.OnClickListener; // 引入OnClickListener接口,用于按钮点击事件 +import android.text.format.DateFormat; // 引入DateFormat类,用于格式化日期和时间 +import android.text.format.DateUtils; // 引入DateUtils类,用于时间格式化与操作 + +// 定义一个自定义的DateTimePickerDialog类,继承自AlertDialog +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + private Calendar mDate = Calendar.getInstance(); // 用于保存当前日期和时间 + private boolean mIs24HourView; // 标记是否为24小时制显示 + private OnDateTimeSetListener mOnDateTimeSetListener; // 回调接口,用于在日期和时间设置后通知调用者 + private DateTimePicker mDateTimePicker; // 自定义的日期时间选择器 + + // 定义回调接口,用于在用户设置日期时间后进行处理 + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); // 回调方法,传递设置的日期时间 + } + + // 构造函数,初始化对话框,设置默认日期时间 + public DateTimePickerDialog(Context context, long date) { + super(context); // 调用父类构造函数,初始化AlertDialog + mDateTimePicker = new DateTimePicker(context); // 初始化DateTimePicker控件 + setView(mDateTimePicker); // 设置对话框的内容视图为DateTimePicker控件 + 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); // 将秒设置为0 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); // 设置DateTimePicker控件的初始日期 + setButton(context.getString(R.string.datetime_dialog_ok), this); // 设置“确认”按钮,并指定点击时的行为 + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); // 设置“取消”按钮 + set24HourView(DateFormat.is24HourFormat(this.getContext())); // 设置是否为24小时制显示 + 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; // 如果是24小时制,显示24小时格式 + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 格式化日期时间并设置为对话框标题 + } + + // 确认按钮点击事件的处理 + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener != null) { + // 调用回调接口,传递设置的日期时间 + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } +} diff --git a/DropdownMenu.java b/DropdownMenu.java new file mode 100644 index 0000000..fe6e47a --- /dev/null +++ b/DropdownMenu.java @@ -0,0 +1,63 @@ +/* + * 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; // 引入Context类,用于访问应用程序的上下文 +import android.view.Menu; // 引入Menu类,用于操作菜单 +import android.view.MenuItem; // 引入MenuItem类,用于菜单项的处理 +import android.view.View; // 引入View类,用于视图的操作 +import android.view.View.OnClickListener; // 引入OnClickListener接口,用于处理点击事件 +import android.widget.Button; // 引入Button类,用于显示按钮 +import android.widget.PopupMenu; // 引入PopupMenu类,用于显示弹出菜单 +import android.widget.PopupMenu.OnMenuItemClickListener; // 引入PopupMenu的菜单项点击监听器 + +public class DropdownMenu { + private Button mButton; // 用于显示的按钮,点击后弹出菜单 + private PopupMenu mPopupMenu; // 弹出菜单的实例 + private Menu mMenu; // 菜单项的集合 + + // 构造函数,初始化DropdownMenu,设置按钮及其弹出菜单 + public DropdownMenu(Context context, Button button, int menuId) { + mButton = button; // 获取传入的按钮对象 + mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮的背景图标为下拉菜单图标 + mPopupMenu = new PopupMenu(context, mButton); // 初始化PopupMenu,指定按钮作为弹出菜单的锚点 + mMenu = mPopupMenu.getMenu(); // 获取PopupMenu中的Menu对象 + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 使用传入的menuId加载菜单资源 + mButton.setOnClickListener(new OnClickListener() { // 为按钮设置点击事件监听器 + public void onClick(View v) { + mPopupMenu.show(); // 当按钮被点击时,显示弹出菜单 + } + }); + } + + // 设置菜单项点击事件监听器 + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); // 设置PopupMenu的菜单项点击监听器 + } + } + + // 根据菜单项的ID查找菜单项 + public MenuItem findItem(int id) { + return mMenu.findItem(id); // 在菜单中查找具有指定ID的菜单项 + } + + // 设置按钮的文本 + public void setTitle(CharSequence title) { + mButton.setText(title); // 设置按钮的文本为传入的title + } +} diff --git a/FoldersListAdapter.java b/FoldersListAdapter.java deleted file mode 100644 index 94bb583..0000000 --- a/FoldersListAdapter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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; - - -// FoldersListAdapter 是一个自定义的 CursorAdapter,用于显示文件夹列表。 -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; - - // FoldersListAdapter 的构造函数。 - public FoldersListAdapter(Context context, Cursor c) { - super(context, c); - // TODO Auto-generated constructor stub - } - - // 新建视图的方法,返回一个 FolderListItem 实例。 - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new FolderListItem(context); - } - - // 绑定视图与数据的方法。 - @Override - public void bindView(View view, Context context, Cursor cursor) { - 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) getItem(position); - return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context - .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - } - - // FolderListItem 是一个内部类,表示列表中的单个文件夹项。 - private class FolderListItem extends LinearLayout { - private TextView mName; // 用于显示文件夹名称的 TextView。 - - // FolderListItem 的构造函数。 - 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/NoteWidgetProvider.java b/NoteWidgetProvider.java new file mode 100644 index 0000000..0d6feb9 --- /dev/null +++ b/NoteWidgetProvider.java @@ -0,0 +1,144 @@ +/* + * 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.widget; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.util.Log; +import android.widget.RemoteViews; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NoteEditActivity; +import net.micode.notes.ui.NotesListActivity; + +// 继承自AppWidgetProvider的抽象类,用于管理便签小部件。 +public abstract class NoteWidgetProvider extends AppWidgetProvider { + // 定义查询数据库时需要的列。 + public static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; + + // 定义列索引常量。 + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + + // 日志标签,用于Logcat中识别日志来源。 + private static final String TAG = "NoteWidgetProvider"; + + // 当小部件被删除时调用,更新数据库中对应的笔记widget id为无效值。 + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + for (int i = 0; i < appWidgetIds.length; i++) { + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(appWidgetIds[i])}); + } + } + + // 获取小部件相关的笔记信息。 + private Cursor getNoteWidgetInfo(Context context, int widgetId) { + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + null); + } + + // 更新小部件显示。 + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + update(context, appWidgetManager, appWidgetIds, false); + } + + // 更新小部件显示,考虑隐私模式。 + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) { + for (int i = 0; i < appWidgetIds.length; i++) { + if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + int bgId = ResourceParser.getDefaultBgId(context); + String snippet = ""; + Intent intent = new Intent(context, NoteEditActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); + if (c != null && c.moveToFirst()) { + if (c.getCount() > 1) { + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + c.close(); + return; + } + snippet = c.getString(COLUMN_SNIPPET); + bgId = c.getInt(COLUMN_BG_COLOR_ID); + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + intent.setAction(Intent.ACTION_VIEW); + } else { + snippet = context.getResources().getString(R.string.widget_havenot_content); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + } + + if (c != null) { + c.close(); + } + + // 设置小部件的布局和行为。 + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + /** + * 生成启动宿主的PendingIntent。 + */ + PendingIntent pendingIntent = null; + if (privacyMode) { + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + } else { + rv.setTextViewText(R.id.widget_text, snippet); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + } + } + } + + // 获取背景资源ID的方法,需由子类实现。 + protected abstract int getBgResourceId(int bgId); + + // 获取布局ID的方法,需由子类实现。 + protected abstract int getLayoutId(); + + // 获取小部件类型的方法,需由子类实现。 + protected abstract int getWidgetType(); +} \ No newline at end of file diff --git a/NoteWidgetProvider_2x.java b/NoteWidgetProvider_2x.java new file mode 100644 index 0000000..b1967fe --- /dev/null +++ b/NoteWidgetProvider_2x.java @@ -0,0 +1,55 @@ +/* + * 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.widget; + +import android.appwidget.AppWidgetManager; +import android.content.Context; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.ResourceParser; + +// NoteWidgetProvider_2x 是 NoteWidgetProvider 的具体实现类,用于提供2x2网格大小的便签小部件。 +public class NoteWidgetProvider_2x extends NoteWidgetProvider { + // 当小部件需要更新时调用此方法。 + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用父类的 update 方法来执行更新。 + super.update(context, appWidgetManager, appWidgetIds); + } + + // 返回小部件的布局资源ID。 + @Override + protected int getLayoutId() { + // 返回2x2网格小部件的布局资源ID。 + return R.layout.widget_2x; + } + + // 根据传入的背景ID返回对应的资源ID。 + @Override + protected int getBgResourceId(int bgId) { + // 使用 ResourceParser 类的静态方法来获取2x2网格小部件的背景资源ID。 + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + } + + // 返回小部件的类型。 + @Override + protected int getWidgetType() { + // 返回小部件的类型为TYPE_WIDGET_2X,这是一个定义在Notes类中的常量。 + return Notes.TYPE_WIDGET_2X; + } +} \ No newline at end of file diff --git a/NoteWidgetProvider_4x.java b/NoteWidgetProvider_4x.java new file mode 100644 index 0000000..39ad28e --- /dev/null +++ b/NoteWidgetProvider_4x.java @@ -0,0 +1,55 @@ +/* + * 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.widget; + +import android.appwidget.AppWidgetManager; +import android.content.Context; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.ResourceParser; + +// NoteWidgetProvider_4x 类继承自 NoteWidgetProvider 类,用于提供4x4网格大小的便签小部件。 +public class NoteWidgetProvider_4x extends NoteWidgetProvider { + // 当小部件需要更新时,系统会调用此方法。 + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用父类的 update 方法来更新小部件。 + super.update(context, appWidgetManager, appWidgetIds); + } + + // 返回小部件的布局资源ID。 + @Override + protected int getLayoutId() { + // 返回4x4网格小部件的布局资源ID。 + return R.layout.widget_4x; + } + + // 根据传入的背景ID返回对应的资源ID。 + @Override + protected int getBgResourceId(int bgId) { + // 使用 ResourceParser 类的静态方法来获取4x4网格小部件的背景资源ID。 + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + // 返回小部件的类型。 + @Override + protected int getWidgetType() { + // 返回小部件的类型为TYPE_WIDGET_4X,这是一个定义在Notes类中的常量。 + return Notes.TYPE_WIDGET_4X; + } +} \ No newline at end of file