From 9289dd03a6489315219fb0381556917fc9b3d4bd Mon Sep 17 00:00:00 2001 From: STRIV1 <1476836209@qq.com> Date: Mon, 30 Dec 2024 19:15:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlarmAlertActivity.java | 159 ++++++++++++++++++++++++++ AlarmInitReceiver.java | 75 +++++++++++++ AlarmReceiver.java | 35 ++++++ DateTimePicker.java | 222 +++++++++++++++++++++++++++++++++++++ DateTimePickerDialog.java | 98 ++++++++++++++++ DropdownMenu.java | 63 +++++++++++ FoldersListAdapter.java | 89 --------------- NoteWidgetProvider.java | 144 ++++++++++++++++++++++++ NoteWidgetProvider_2x.java | 55 +++++++++ NoteWidgetProvider_4x.java | 55 +++++++++ 10 files changed, 906 insertions(+), 89 deletions(-) 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 delete mode 100644 FoldersListAdapter.java create mode 100644 NoteWidgetProvider.java create mode 100644 NoteWidgetProvider_2x.java create mode 100644 NoteWidgetProvider_4x.java 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