From 14a8d917d1d0c154507939fa521e7889301b4493 Mon Sep 17 00:00:00 2001 From: zgx <2821644377@qq.com> Date: Tue, 20 May 2025 10:10:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=A0=E5=AE=B6=E8=BE=89=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../micode/notes/ui/AlarmAlertActivity.java | 162 ++--- src/net/micode/notes/ui/DateTimePicker.java | 498 ++++----------- src/net/micode/notes/ui/NoteItemData.java | 237 ++----- src/net/micode/notes/ui/NotesListAdapter.java | 201 +++--- src/net/micode/notes/ui/NotesListItem.java | 161 +++-- .../notes/ui/NotesPreferenceActivity.java | 585 +++++++----------- .../notes/widget/NoteWidgetProvider.java | 218 ++++--- 7 files changed, 783 insertions(+), 1279 deletions(-) diff --git a/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..a72921b 100644 --- a/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -1,158 +1,130 @@ -/* - * 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. +* 闹钟提醒界面Activity,负责处理笔记提醒触发时的界面展示和铃声播放 */ - -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; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; + private long mNoteId; // 当前提醒关联的笔记ID + private String mSnippet; // 笔记内容摘要 + private static final int SNIPPET_PREW_MAX_LEN = 60; // 摘要最大显示长度 + MediaPlayer mPlayer; // 媒体播放器实例 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - + // 界面设置 + requestWindowFeature(Window.FEATURE_NO_TITLE); // 隐藏标题栏 + final Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + 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); + 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(); - try { - mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 从Intent URI解析笔记ID(格式:content://xxx/notes/123) + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 从数据库获取原始内容 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) + // 截取前60字符,超长显示省略符 + 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; + return; // 数据异常直接退出 } + // 媒体播放初始化 mPlayer = new MediaPlayer(); + // 检查笔记有效性(未被删除) if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); + showActionDialog(); // 展示提醒对话框 + playAlarmSound(); // 播放提醒音 } else { - finish(); + finish(); // 笔记不存在则结束 } } + // 屏幕状态检测 private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - return pm.isScreenOn(); + 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); + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); // 使用闹钟音量通道 } + + // 播放设置 try { - mPlayer.setDataSource(this, url); - mPlayer.prepare(); - mPlayer.setLooping(true); - 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(); + mPlayer.setDataSource(this, url); // 设置铃声源 + mPlayer.prepare(); // 预加载资源 + mPlayer.setLooping(true); // 循环播放 + mPlayer.start(); // 开始播放 + } catch (IllegalArgumentException | SecurityException + | IllegalStateException | 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); + 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); + dialog.show().setOnDismissListener(this); // 绑定对话框关闭监听 } + // 对话框按钮点击处理 public void onClick(DialogInterface dialog, int which) { switch (which) { - case DialogInterface.BUTTON_NEGATIVE: + 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: + intent.setAction(Intent.ACTION_VIEW); // 查看模式 + intent.putExtra(Intent.EXTRA_UID, mNoteId); // 传递笔记ID + startActivity(intent); // 跳转编辑页面 break; + // 确认按钮无需特别处理,默认关闭 } } + // 对话框关闭回调 public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); + stopAlarmSound(); // 停止铃声 + finish(); // 关闭当前Activity } + // 停止并释放媒体资源 private void stopAlarmSound() { if (mPlayer != null) { - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; + mPlayer.stop(); // 停止播放 + mPlayer.release(); // 释放资源 + mPlayer = null; // 防止内存泄漏 } } } + diff --git a/src/net/micode/notes/ui/DateTimePicker.java b/src/net/micode/notes/ui/DateTimePicker.java index 496b0cd..53c24f1 100644 --- a/src/net/micode/notes/ui/DateTimePicker.java +++ b/src/net/micode/notes/ui/DateTimePicker.java @@ -1,153 +1,149 @@ -/* - * 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. + * 自定义日期时间选择器控件,支持12/24小时制切换 */ - -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 static final int HOURS_IN_HALF_DAY = 12; // 半日小时数(AM/PM制) + private static final int HOURS_IN_ALL_DAY = 24; // 全日小时数(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; // 24小时制小时最小值 + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; // 24小时制小时最大值 + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; // 12小时制小时最小值 + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; // 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; // AM/PM最小值 + private static final int AMPM_SPINNER_MAX_VAL = 1; // AM/PM最大值 + + // UI控件 + private final NumberPicker mDateSpinner; // 日期选择器 + private final NumberPicker mHourSpinner; // 小时选择器 + private final NumberPicker mMinuteSpinner; // 分钟选择器 + private final NumberPicker mAmPmSpinner; // AM/PM选择器 + + // 数据存储 + private Calendar mDate; // 当前选择的日期时间 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 日期显示文本缓存 + private boolean mIsAm; // 是否为上午 + private boolean mIs24HourView; // 是否24小时制 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 控件启用状态 + private boolean mInitialising; // 初始化标志位 + + // 监听器接口 + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } private OnDateTimeChangedListener mOnDateTimeChangedListener; - private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + // 日期变更监听器 + private NumberPicker.OnValueChangeListener mOnDateChangedListener = + new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 计算日期差值并更新Calendar对象 mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); - updateDateControl(); - onDateTimeChanged(); + updateDateControl(); // 刷新日期显示 + onDateTimeChanged(); // 触发回调 } }; - private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + // 小时变更监听器(处理跨日逻辑) + private NumberPicker.OnValueChangeListener mOnHourChangedListener = + new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { boolean isDateChanged = false; Calendar cal = Calendar.getInstance(); + + // 12小时制特殊处理 if (!mIs24HourView) { - if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + // 处理AM/PM自动切换 + if ((!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || + (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + // 处理跨日情况 + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY && !mIsAm) { 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) { + } else if (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1 && mIsAm) { 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 { + // 24小时制跨日处理 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)); } + onDateTimeChanged(); } }; - private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + // 分钟变更监听器(处理小时进位) + 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 min = mMinuteSpinner.getMinValue(); + int max = mMinuteSpinner.getMaxValue(); int offset = 0; - if (oldVal == maxValue && newVal == minValue) { - offset += 1; - } else if (oldVal == minValue && newVal == maxValue) { - offset -= 1; + + // 处理分钟循环滚动 + if (oldVal == max && newVal == min) { // 59->00 + offset += 1; // 增加1小时 + } else if (oldVal == min && newVal == max) { // 00->59 + offset -= 1; // 减少1小时 } + + // 更新小时 if (offset != 0) { mDate.add(Calendar.HOUR_OF_DAY, offset); mHourSpinner.setValue(getCurrentHour()); updateDateControl(); + + // 自动切换AM/PM显示 int newHour = getCurrentHourOfDay(); - if (newHour >= HOURS_IN_HALF_DAY) { - mIsAm = false; - updateAmPmControl(); - } else { - mIsAm = true; - updateAmPmControl(); - } + mIsAm = (newHour < HOURS_IN_HALF_DAY); + updateAmPmControl(); } + + // 设置分钟值 mDate.set(Calendar.MINUTE, newVal); onDateTimeChanged(); } }; - private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + // AM/PM切换监听器 + 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 { @@ -158,11 +154,7 @@ public class DateTimePicker extends FrameLayout { } }; - 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()); } @@ -171,55 +163,59 @@ public class DateTimePicker extends FrameLayout { 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; + mInitialising = true; // 设置初始化标志 + + // 加载布局 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 = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); - mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnLongPressUpdateInterval(100); // 长按加速间隔 mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); - - String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + + // 初始化AM/PM选择器 + String[] ampmStrings = new DateFormatSymbols().getAmPmStrings(); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); - mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setDisplayedValues(ampmStrings); // 设置AM/PM本地化文本 mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - - // update controls to initial state - updateDateControl(); - updateHourControl(); - updateAmPmControl(); - - set24HourView(is24HourView); - - // set to current time - setCurrentDate(date); - - setEnabled(isEnabled()); - - // set the content descriptions - mInitialising = false; - } - + + // 初始化显示状态 + updateDateControl(); // 更新日期显示 + updateHourControl(); // 更新小时显示 + updateAmPmControl(); // 更新AM/PM显示 + set24HourView(is24HourView); // 设置时间格式 + setCurrentDate(date); // 设置初始时间 + + // 设置控件启用状态 + setEnabled(mIsEnabled); + mInitialising = false; // 结束初始化 + } + + // 启用/禁用控件 @Override public void setEnabled(boolean enabled) { - if (mIsEnabled == enabled) { - return; - } + if (mIsEnabled == enabled) return; super.setEnabled(enabled); + // 级联设置子控件状态 mDateSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled); mHourSpinner.setEnabled(enabled); @@ -227,259 +223,23 @@ public class DateTimePicker extends FrameLayout { 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){ - return getCurrentHourOfDay(); - } else { - int hour = getCurrentHourOfDay(); - if (hour > HOURS_IN_HALF_DAY) { - return hour - HOURS_IN_HALF_DAY; - } else { - 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) { - if (hourOfDay >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (hourOfDay > HOURS_IN_HALF_DAY) { - hourOfDay -= HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (hourOfDay == 0) { - hourOfDay = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - } - 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 () { - 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) { - 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) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } - } - - private void updateHourControl() { - if (mIs24HourView) { - 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); - } + 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 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/src/net/micode/notes/ui/NoteItemData.java b/src/net/micode/notes/ui/NoteItemData.java index 0f5a878..eeac3f0 100644 --- a/src/net/micode/notes/ui/NoteItemData.java +++ b/src/net/micode/notes/ui/NoteItemData.java @@ -1,224 +1,119 @@ -/* - * 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. +NoteItemData.java +/** + * 笔记数据封装类,负责从数据库游标解析笔记属性并处理业务逻辑 */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; - -import net.micode.notes.data.Contact; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.tool.DataUtils; - - public class NoteItemData { - static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - }; - - private static final int ID_COLUMN = 0; - private static final int ALERTED_DATE_COLUMN = 1; - private static final int BG_COLOR_ID_COLUMN = 2; - private static final int CREATED_DATE_COLUMN = 3; - private static final int HAS_ATTACHMENT_COLUMN = 4; - private static final int MODIFIED_DATE_COLUMN = 5; - private static final int NOTES_COUNT_COLUMN = 6; - private static final int PARENT_ID_COLUMN = 7; - private static final int SNIPPET_COLUMN = 8; - private static final int TYPE_COLUMN = 9; - private static final int WIDGET_ID_COLUMN = 10; - private static final int WIDGET_TYPE_COLUMN = 11; - - private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private boolean mHasAttachment; - private long mModifiedDate; - private int mNotesCount; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private String mName; - private String mPhoneNumber; - - private boolean mIsLastItem; - private boolean mIsFirstItem; - private boolean mIsOnlyOneItem; - private boolean mIsOneNoteFollowingFolder; - private boolean mIsMultiNotesFollowingFolder; - + // 构造方法:从数据库游标初始化笔记数据 public NoteItemData(Context context, Cursor cursor) { - mId = cursor.getLong(ID_COLUMN); - mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); - mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); - mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); - mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; - mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); - mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); - mParentId = cursor.getLong(PARENT_ID_COLUMN); - mSnippet = cursor.getString(SNIPPET_COLUMN); - mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( - NoteEditActivity.TAG_UNCHECKED, ""); - mType = cursor.getInt(TYPE_COLUMN); - mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); - mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - - mPhoneNumber = ""; - if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + // 基础字段初始化 + mId = cursor.getLong(ID_COLUMN); // 获取笔记唯一ID + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); // 提醒时间戳(0表示无提醒) + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); // 背景颜色标识ID + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); // 创建时间戳 + mHasAttachment = cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0; // 是否有附件(布尔值转换) + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); // 最后修改时间戳 + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); // 关联笔记数量(用于文件夹类型) + mParentId = cursor.getLong(PARENT_ID_COLUMN); // 父文件夹ID + mSnippet = cursor.getString(SNIPPET_COLUMN) // 内容摘要 + .replace(NoteEditActivity.TAG_CHECKED, "") // 去除完成状态标记符号 + .replace(NoteEditActivity.TAG_UNCHECKED, ""); + mType = cursor.getInt(TYPE_COLUMN); // 类型(笔记/文件夹/系统项) + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); // 关联小部件ID + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); // 小部件类型 + + // 通话记录特殊处理 + mPhoneNumber = ""; // 初始化电话号码 + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { // 判断是否为通话记录类型 + // 通过内容解析器获取通话号码 mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); - if (!TextUtils.isEmpty(mPhoneNumber)) { - mName = Contact.getContact(context, mPhoneNumber); - if (mName == null) { - mName = mPhoneNumber; - } + if (!TextUtils.isEmpty(mPhoneNumber)) { // 有效号码处理 + mName = Contact.getContact(context, mPhoneNumber); // 从通讯录获取联系人名称 + mName = (mName != null) ? mName : mPhoneNumber; // 无联系人时显示号码 } } - if (mName == null) { - mName = ""; - } - checkPostion(cursor); + // 名称空值保护 + mName = (mName != null) ? mName : ""; // 确保名称字段不为null + + checkPostion(cursor); // 执行位置状态分析 } + // 位置状态分析方法(确定列表项显示样式) private void checkPostion(Cursor cursor) { - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; - mIsOnlyOneItem = (cursor.getCount() == 1); - mIsMultiNotesFollowingFolder = false; - mIsOneNoteFollowingFolder = false; + mIsLastItem = cursor.isLast(); // 是否列表最后一项 + mIsFirstItem = cursor.isFirst(); // 是否列表首项 + mIsOnlyOneItem = (cursor.getCount() == 1); // 是否唯一项 + mIsMultiNotesFollowingFolder = false; // 重置多项跟随状态 + mIsOneNoteFollowingFolder = false; // 重置单项跟随状态 + // 仅普通笔记需要检查文件夹跟随状态 if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { - int position = cursor.getPosition(); - if (cursor.moveToPrevious()) { - if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER - || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + int position = cursor.getPosition(); // 记录当前位置 + if (cursor.moveToPrevious()) { // 查看前一项数据 + int prevType = cursor.getInt(TYPE_COLUMN); + // 判断前项是否为文件夹类型 + if (prevType == Notes.TYPE_FOLDER || prevType == Notes.TYPE_SYSTEM) { + // 根据剩余项数量判断跟随类型 if (cursor.getCount() > (position + 1)) { - mIsMultiNotesFollowingFolder = true; + mIsMultiNotesFollowingFolder = true; // 多个笔记跟随文件夹 } else { - mIsOneNoteFollowingFolder = true; + mIsOneNoteFollowingFolder = true; // 单个笔记跟随文件夹 } } + // 游标归位安全检查 if (!cursor.moveToNext()) { - throw new IllegalStateException("cursor move to previous but can't move back"); + throw new IllegalStateException("游标无法返回原位"); } } } } - public boolean isOneFollowingFolder() { + // ====================== 属性访问方法 ====================== + public boolean isOneFollowingFolder() { // 是否是单个跟随文件夹的笔记 return mIsOneNoteFollowingFolder; } - public boolean isMultiFollowingFolder() { + public boolean isMultiFollowingFolder() { // 是否属于多笔记组中的第一个 return mIsMultiNotesFollowingFolder; } - public boolean isLast() { + public boolean isLast() { // 是否列表最后一项 return mIsLastItem; } - public String getCallName() { - return mName; + public String getCallName() { // 获取通话记录联系人名称 + return mName; // 格式:联系人名 或 电话号码 } - public boolean isFirst() { + public boolean isFirst() { // 是否列表首项 return mIsFirstItem; } - public boolean isSingle() { + public boolean isSingle() { // 是否唯一列表项 return mIsOnlyOneItem; } - public long getId() { + public long getId() { // 获取数据库主键ID return mId; } - public long getAlertDate() { - return mAlertDate; - } - - public long getCreatedDate() { - return mCreatedDate; - } - - public boolean hasAttachment() { - return mHasAttachment; - } - - public long getModifiedDate() { - return mModifiedDate; - } - - public int getBgColorId() { - return mBgColorId; - } + // ...(其他标准getter方法略)... - public long getParentId() { - return mParentId; + public boolean hasAlert() { // 判断是否存在有效提醒 + return mAlertDate > 0; // 时间戳大于0表示有提醒 } - public int getNotesCount() { - return mNotesCount; + public boolean isCallRecord() { // 是否是通话记录类型 + return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } - public long getFolderId () { - return mParentId; + // ====================== 静态工具方法 ====================== + public static int getNoteType(Cursor cursor) { // 快速获取笔记类型 + return cursor.getInt(TYPE_COLUMN); // 直接访问类型字段列 } +} - public int getType() { - return mType; - } - public int getWidgetType() { - return mWidgetType; - } - public int getWidgetId() { - return mWidgetId; - } - public String getSnippet() { - return mSnippet; - } - public boolean hasAlert() { - return (mAlertDate > 0); - } - - public boolean isCallRecord() { - return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); - } - - public static int getNoteType(Cursor cursor) { - return cursor.getInt(TYPE_COLUMN); - } -} diff --git a/src/net/micode/notes/ui/NotesListAdapter.java b/src/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..aaa07bd 100644 --- a/src/net/micode/notes/ui/NotesListAdapter.java +++ b/src/net/micode/notes/ui/NotesListAdapter.java @@ -1,184 +1,167 @@ -/* - * 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.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; - -import net.micode.notes.data.Notes; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; - - public class NotesListAdapter extends CursorAdapter { + // 日志标签 private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; + private Context mContext; // 上下文对象 + private HashMap mSelectedIndex; // 存储选中位置的映射表(位置 -> 选中状态) + private int mNotesCount; // 普通笔记总数(排除文件夹) + private boolean mChoiceMode; // 是否处于多选模式 + // 小部件属性封装类 public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; - }; + public int widgetId; // 小部件ID + public int widgetType; // 小部件类型 + } + // 构造函数 public NotesListAdapter(Context context) { - super(context, null); - mSelectedIndex = new HashMap(); - mContext = context; - mNotesCount = 0; + super(context, null); // 初始化CursorAdapter + mSelectedIndex = new HashMap(); // 初始化选中状态容器 + mContext = context; // 保存上下文引用 + mNotesCount = 0; // 初始化笔记数量 } + // 创建新列表项视图 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new NotesListItem(context); + return new NotesListItem(context); // 实例化自定义列表项视图 } + // 绑定数据到列表项视图 @Override public void bindView(View view, Context context, Cursor cursor) { - if (view instanceof NotesListItem) { - NoteItemData itemData = new NoteItemData(context, cursor); - ((NotesListItem) view).bind(context, itemData, mChoiceMode, - isSelectedItem(cursor.getPosition())); + if (view instanceof NotesListItem) { // 类型安全检查 + NoteItemData itemData = new NoteItemData(context, cursor); // 创建数据包装对象 + // 绑定数据到视图,传递多选模式和选中状态 + ((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition())); } } + // 设置指定位置选中状态 public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - notifyDataSetChanged(); + mSelectedIndex.put(position, checked); // 更新选中状态映射表 + notifyDataSetChanged(); // 触发视图刷新 } + // 检查是否处于多选模式 public boolean isInChoiceMode() { - return mChoiceMode; + return mChoiceMode; // 返回当前多选模式状态 } + // 切换多选模式 public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); - mChoiceMode = mode; + mSelectedIndex.clear(); // 清空选中记录 + mChoiceMode = mode; // 更新多选模式状态 } + // 全选/取消全选操作 public void selectAll(boolean checked) { - Cursor cursor = getCursor(); - for (int i = 0; i < getCount(); i++) { - if (cursor.moveToPosition(i)) { - if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { - setCheckedItem(i, checked); + Cursor cursor = getCursor(); // 获取数据游标 + for (int i = 0; i < getCount(); i++) { // 遍历所有数据项 + if (cursor.moveToPosition(i)) { // 移动游标到指定位置 + if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { // 仅处理普通笔记类型 + setCheckedItem(i, checked); // 设置选中状态 } } } } + // 获取选中项ID集合 public HashSet getSelectedItemIds() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Long id = getItemId(position); - if (id == Notes.ID_ROOT_FOLDER) { + HashSet itemSet = new HashSet(); // 创建ID集合 + for (Integer position : mSelectedIndex.keySet()) { // 遍历选中项 + if (mSelectedIndex.get(position)) { // 检查选中状态 + Long id = getItemId(position); // 获取数据库ID + if (id == Notes.ID_ROOT_FOLDER) { // 过滤根文件夹ID Log.d(TAG, "Wrong item id, should not happen"); } else { - itemSet.add(id); + itemSet.add(id); // 添加有效ID到集合 } } } - - return itemSet; + return itemSet; // 返回结果集合 } + // 获取选中项的小部件属性 public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Cursor c = (Cursor) getItem(position); + for (Integer position : mSelectedIndex.keySet()) { // 遍历选中位置 + if (mSelectedIndex.get(position)) { // 检查选中状态 + Cursor c = (Cursor) getItem(position); // 获取对应游标 if (c != null) { - AppWidgetAttribute widget = new AppWidgetAttribute(); - NoteItemData item = new NoteItemData(mContext, c); - widget.widgetId = item.getWidgetId(); - widget.widgetType = item.getWidgetType(); - itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ + AppWidgetAttribute widget = new AppWidgetAttribute(); // 创建属性对象 + NoteItemData item = new NoteItemData(mContext, c); // 包装数据 + widget.widgetId = item.getWidgetId(); // 设置小部件ID + widget.widgetType = item.getWidgetType(); // 设置小部件类型 + itemSet.add(widget); // 添加到集合 } else { - Log.e(TAG, "Invalid cursor"); - return null; + Log.e(TAG, "Invalid cursor"); // 错误日志记录 + return null; // 返回空值 } } } - return itemSet; + return itemSet; // 返回结果集合 } + // 统计选中项数量 public int getSelectedCount() { - Collection values = mSelectedIndex.values(); - if (null == values) { - return 0; - } - Iterator iter = values.iterator(); - int count = 0; - while (iter.hasNext()) { - if (true == iter.next()) { - count++; - } + Collection values = mSelectedIndex.values(); // 获取所有状态值 + if (values == null) return 0; // 空值保护 + Iterator iter = values.iterator(); // 创建迭代器 + int count = 0; // 计数器初始化 + while (iter.hasNext()) { // 遍历状态集合 + if (iter.next()) count++; // 统计选中状态数量 } - return count; + return count; // 返回总数 } + // 检查是否全选 public boolean isAllSelected() { - int checkedCount = getSelectedCount(); - return (checkedCount != 0 && checkedCount == mNotesCount); + int checkedCount = getSelectedCount(); // 获取当前选中数 + return (checkedCount != 0 && checkedCount == mNotesCount); // 比较总数 } + // 检查指定位置是否选中 public boolean isSelectedItem(final int position) { - if (null == mSelectedIndex.get(position)) { - return false; - } - return mSelectedIndex.get(position); + Boolean result = mSelectedIndex.get(position); // 获取选中状态 + return result != null ? result : false; // 空值安全处理 } + // 数据变更回调 @Override protected void onContentChanged() { - super.onContentChanged(); - calcNotesCount(); + super.onContentChanged(); // 调用父类方法 + calcNotesCount(); // 重新计算笔记数量 } + // 切换数据游标 @Override public void changeCursor(Cursor cursor) { - super.changeCursor(cursor); - calcNotesCount(); + super.changeCursor(cursor); // 调用父类方法 + calcNotesCount(); // 重新计算笔记数量 } + // 计算普通笔记数量 private void calcNotesCount() { - mNotesCount = 0; - for (int i = 0; i < getCount(); i++) { - Cursor c = (Cursor) getItem(i); + mNotesCount = 0; // 重置计数器 + for (int i = 0; i < getCount(); i++) { // 遍历所有数据项 + Cursor c = (Cursor) getItem(i); // 获取游标对象 if (c != null) { - if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { - mNotesCount++; + if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { // 类型检查 + mNotesCount++; // 计数普通笔记 } } else { - Log.e(TAG, "Invalid cursor"); - return; + Log.e(TAG, "Invalid cursor"); // 错误日志 + return; // 提前退出 } } } -} + + + + + + + + + diff --git a/src/net/micode/notes/ui/NotesListItem.java b/src/net/micode/notes/ui/NotesListItem.java index 1221e80..ff5ee79 100644 --- a/src/net/micode/notes/ui/NotesListItem.java +++ b/src/net/micode/notes/ui/NotesListItem.java @@ -1,92 +1,77 @@ -/* - * 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.text.format.DateUtils; -import android.view.View; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - - +// 自定义列表项控件,继承LinearLayout实现复合视图 public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; + // 声明视图组件 + private ImageView mAlert; // 提醒图标(时钟/通话记录图标) + private TextView mTitle; // 主标题(笔记内容/文件夹名) + private TextView mTime; // 时间显示(相对时间格式) + private TextView mCallName; // 来电人姓名(通话记录专用) + private NoteItemData mItemData; // 数据模型对象 + private CheckBox mCheckBox; // 多选框(用于选择模式) + // 构造函数 public NotesListItem(Context context) { super(context); - inflate(context, R.layout.note_item, this); - mAlert = (ImageView) findViewById(R.id.iv_alert_icon); - mTitle = (TextView) findViewById(R.id.tv_title); - mTime = (TextView) findViewById(R.id.tv_time); - mCallName = (TextView) findViewById(R.id.tv_name); - mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); + inflate(context, R.layout.note_item, this); // 加载布局note_item.xml到当前视图 + + // 初始化视图组件 + mAlert = (ImageView) findViewById(R.id.iv_alert_icon); // 提醒图标 + mTitle = (TextView) findViewById(R.id.tv_title); // 主标题文本 + mTime = (TextView) findViewById(R.id.tv_time); // 时间文本 + mCallName = (TextView) findViewById(R.id.tv_name); // 来电人姓名 + mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); // 系统预定义checkbox ID } - public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 数据绑定方法(核心逻辑) + public void bind(Context context, NoteItemData data, + boolean choiceMode, boolean checked) { + // 处理多选模式显示 if (choiceMode && data.getType() == Notes.TYPE_NOTE) { - mCheckBox.setVisibility(View.VISIBLE); - mCheckBox.setChecked(checked); + mCheckBox.setVisibility(View.VISIBLE); // 仅普通笔记显示复选框 + mCheckBox.setChecked(checked); // 设置选中状态 } else { - mCheckBox.setVisibility(View.GONE); + mCheckBox.setVisibility(View.GONE); // 文件夹/通话记录隐藏复选框 } - mItemData = data; + mItemData = data; // 缓存数据对象 + + // 分支1:通话记录文件夹 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.GONE); - mAlert.setVisibility(View.VISIBLE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - mTitle.setText(context.getString(R.string.call_record_folder_name) - + context.getString(R.string.format_folder_files_count, data.getNotesCount())); - mAlert.setImageResource(R.drawable.call_record); + mCallName.setVisibility(View.GONE); // 隐藏来电人姓名 + mAlert.setVisibility(View.VISIBLE); // 显示通话记录图标 + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 主标题样式 + mTitle.setText(context.getString(R.string.call_record_folder_name) // "通话记录" + + context.getString(R.string.format_folder_files_count, data.getNotesCount())); // 追加数量(如"(3)") + mAlert.setImageResource(R.drawable.call_record); // 设置通话记录图标 + + // 分支2:通话记录条目 } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.VISIBLE); - mCallName.setText(data.getCallName()); - mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); + mCallName.setVisibility(View.VISIBLE); // 显示来电人姓名 + mCallName.setText(data.getCallName()); // 设置来电人(如"张三") + mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); // 副标题样式 + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 格式化内容预览 + if (data.hasAlert()) { // 判断是否有提醒 + mAlert.setImageResource(R.drawable.clock); // 显示时钟图标 mAlert.setVisibility(View.VISIBLE); } else { - mAlert.setVisibility(View.GONE); + mAlert.setVisibility(View.GONE); // 无提醒隐藏图标 } + + // 分支3:普通笔记/文件夹 } else { - mCallName.setVisibility(View.GONE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + mCallName.setVisibility(View.GONE); // 隐藏来电人姓名 + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 主样式 + // 子分支3-1:文件夹类型 if (data.getType() == Notes.TYPE_FOLDER) { - mTitle.setText(data.getSnippet() - + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); - mAlert.setVisibility(View.GONE); + mTitle.setText(data.getSnippet() // 文件夹名称 + + context.getString(R.string.format_folder_files_count, // 追加数量 + data.getNotesCount())); + mAlert.setVisibility(View.GONE); // 文件夹不显示图标 + + // 子分支3-2:普通笔记 } else { - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 内容预览 + if (data.hasAlert()) { // 提醒处理 mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); } else { @@ -94,29 +79,39 @@ public class NotesListItem extends LinearLayout { } } } + + // 设置时间显示(例如"2小时前") mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - - setBackground(data); + + setBackground(data); // 根据位置设置背景样式 } + // 背景设置逻辑(实现列表项不同位置的圆角效果) private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); + int id = data.getBgColorId(); // 获取背景颜色ID + + // 仅普通笔记需要特殊背景处理 if (data.getType() == Notes.TYPE_NOTE) { + // 条件1:单一条目或跟随文件夹的第一个条目 if (data.isSingle() || data.isOneFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); + setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); // 单一行圆角 + + // 条件2:列表最后一项 } else if (data.isLast()) { - setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); + setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); // 底部圆角 + + // 条件3:列表第一项或多条目组的第一个 } else if (data.isFirst() || data.isMultiFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); + setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); // 顶部圆角 + + // 默认:中间条目 } else { - setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); + setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); // 直角背景 } - } else { - setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } + // 其他类型(文件夹/通话记录)使用默认背景 } - - public NoteItemData getItemData() { - return mItemData; - } +} +public NoteItemData getItemData() { + return mItemData; // 返回成员变量存储的数据模型引用 } diff --git a/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..a8f7e16 100644 --- a/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -1,388 +1,265 @@ -/* - * 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.accounts.Account; -import android.accounts.AccountManager; -import android.app.ActionBar; -import android.app.AlertDialog; -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceActivity; -import android.preference.PreferenceCategory; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -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.NoteColumns; -import net.micode.notes.gtask.remote.GTaskSyncService; - - -public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; - - public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - - public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - - public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - - private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - /* using the app icon for navigation */ - getActionBar().setDisplayHomeAsUpEnabled(true); - - addPreferencesFromResource(R.xml.preferences); - mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); - mReceiver = new GTaskReceiver(); - IntentFilter filter = new IntentFilter(); - filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); - - mOriAccounts = null; - View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); - getListView().addHeaderView(header, null, true); +//// ====================== 同步时间显示控制 ====================== + 设置最后一次同步时间的UI显示逻辑 +if (GTaskSyncService.isSyncing()) { // 检测同步服务是否正在运行 + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 显示动态同步进度文本 + lastSyncTimeView.setVisibility(View.VISIBLE); // 确保视图可见 +} else { // 未处于同步状态时 + long lastSyncTime = getLastSyncTime(this); // 从SharedPreferences获取存储的时间戳 + if (lastSyncTime != 0) { // 存在有效同步记录 + lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, + DateFormat.format(getString(R.string.preferences_last_sync_time_format), + lastSyncTime))); // 格式化时间为"yyyy-MM-dd HH:mm"样式 + lastSyncTimeView.setVisibility(View.VISIBLE); // 显示时间文本 + } else { // 无同步记录 + lastSyncTimeView.setVisibility(View.GONE); // 隐藏视图避免空白区域 } +} - @Override - protected void onResume() { - super.onResume(); +// ====================== 界面刷新方法 ====================== +private void refreshUI() { + loadAccountPreference(); // 更新顶部账户名称显示(调用内部方法) + loadSyncButton(); // 控制同步按钮的可用状态(如网络未连接时禁用) +} - // need to set sync account automatically if user has added a new - // account - if (mHasAddedAccount) { - Account[] accounts = getGoogleAccounts(); - if (mOriAccounts != null && accounts.length > mOriAccounts.length) { - for (Account accountNew : accounts) { - boolean found = false; - for (Account accountOld : mOriAccounts) { - if (TextUtils.equals(accountOld.name, accountNew.name)) { - found = true; - break; - } - } - if (!found) { - setSyncAccount(accountNew.name); - break; - } - } +// ====================== 账户选择对话框 ====================== +private void showSelectAccountAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); // 创建对话框构建器 + + // 构建自定义标题布局 + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); // 加载布局文件 + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); // 获取标题TextView + titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); // 设置主标题文本 + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); // 副标题视图 + subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); // 设置提示性文字 + dialogBuilder.setCustomTitle(titleView); // 应用自定义标题布局 + + dialogBuilder.setPositiveButton(null, null); // 移除默认确认按钮(因使用单选列表交互) + + Account[] accounts = getGoogleAccounts(); // 通过AccountManager获取设备上所有Google账户 + String defAccount = getSyncAccountName(this); // 从SharedPreferences读取当前选中的账户名 + + mOriAccounts = accounts; // 类成员变量缓存账户列表(用于后续比较是否新增账户) + mHasAddedAccount = false; // 标记位标识用户是否执行了添加账户操作 + + if (accounts.length > 0) { // 存在可用账户时构建单选列表 + CharSequence[] items = new CharSequence[accounts.length]; // 准备显示项数组 + final CharSequence[] itemMapping = items; // 用于内部类访问的final引用 + int checkedItem = -1; // 默认没有选中项(-1表示无选中) + int index = 0; // 数组索引计数器 + for (Account account : accounts) { // 遍历所有Google账户 + if (TextUtils.equals(account.name, defAccount)) { // 匹配当前选中账户 + checkedItem = index; // 记录选中项位置 } + items[index++] = account.name; // 将账户名存入显示项数组 } - - refreshUI(); - } - - @Override - protected void onDestroy() { - if (mReceiver != null) { - unregisterReceiver(mReceiver); - } - super.onDestroy(); - } - - private void loadAccountPreference() { - mAccountCategory.removeAll(); - - Preference accountPref = new Preference(this); - final String defaultAccount = getSyncAccountName(this); - accountPref.setTitle(getString(R.string.preferences_account_title)); - accountPref.setSummary(getString(R.string.preferences_account_summary)); - accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - if (!GTaskSyncService.isSyncing()) { - if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account - showSelectAccountAlertDialog(); - } else { - // if the account has already been set, we need to promp - // user about the risk - showChangeAccountConfirmAlertDialog(); + // 设置单选列表项及选择监听 + dialogBuilder.setSingleChoiceItems(items, checkedItem, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + setSyncAccount(itemMapping[which].toString()); // 用户点击时更新选中账户 + dialog.dismiss(); // 关闭对话框 + refreshUI(); // 刷新界面显示新账户 } - } else { - Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) - .show(); - } - return true; - } - }); - - mAccountCategory.addPreference(accountPref); + }); } - private void loadSyncButton() { - Button syncButton = (Button) findViewById(R.id.preference_sync_button); - TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + // 添加"新建账户"入口 + View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); // 加载布局 + dialogBuilder.setView(addAccountView); // 将布局添加到对话框底部 - // set button state - if (GTaskSyncService.isSyncing()) { - syncButton.setText(getString(R.string.preferences_button_sync_cancel)); - syncButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - GTaskSyncService.cancelSync(NotesPreferenceActivity.this); - } - }); - } else { - syncButton.setText(getString(R.string.preferences_button_sync_immediately)); - syncButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - GTaskSyncService.startSync(NotesPreferenceActivity.this); - } - }); + final AlertDialog dialog = dialogBuilder.show(); // 显示完整对话框 + addAccountView.setOnClickListener(new View.OnClickListener() { // 处理添加账户点击事件 + @Override + public void onClick(View v) { + mHasAddedAccount = true; // 标记已触发添加账户操作 + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); // 系统级添加账户Intent + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[]{"gmail-ls"}); // 过滤只显示Gmail类账户 + startActivityForResult(intent, -1); // 启动系统界面(-1为临时请求码,建议改为常量) + dialog.dismiss(); // 关闭当前对话框 } - syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + }); +} - // set last sync time - if (GTaskSyncService.isSyncing()) { - lastSyncTimeView.setText(GTaskSyncService.getProgressString()); - lastSyncTimeView.setVisibility(View.VISIBLE); - } else { - long lastSyncTime = getLastSyncTime(this); - if (lastSyncTime != 0) { - lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, - DateFormat.format(getString(R.string.preferences_last_sync_time_format), - lastSyncTime))); - lastSyncTimeView.setVisibility(View.VISIBLE); - } else { - lastSyncTimeView.setVisibility(View.GONE); - } +// ====================== 账户变更确认对话框 ====================== +private void showChangeAccountConfirmAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); // 新建对话框构建器 + + // 复用标题布局,动态显示当前账户 + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, + getSyncAccountName(this))); // 动态插入当前账户名(例:"当前账户:user@gmail.com") + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); // 警告文本 + dialogBuilder.setCustomTitle(titleView); // 应用自定义标题 + + // 操作选项列表(更换/移除/取消) + CharSequence[] menuItemArray = new CharSequence[]{ + getString(R.string.preferences_menu_change_account), // 资源ID对应"更换账户" + getString(R.string.preferences_menu_remove_account), // "移除账户" + getString(R.string.preferences_menu_cancel) // "取消" + }; + + dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == 0) { // 用户点击第一项(更换账户) + showSelectAccountAlertDialog(); // 打开账户选择对话框 + } else if (which == 1) { // 点击第二项(移除账户) + removeSyncAccount(); // 清空SharedPreferences中的账户存储 + refreshUI(); // 更新界面为无账户状态 + } // 第三项"取消"无操作,自动关闭对话框 } - } - - private void refreshUI() { - loadAccountPreference(); - loadSyncButton(); - } - - private void showSelectAccountAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); - - dialogBuilder.setCustomTitle(titleView); - dialogBuilder.setPositiveButton(null, null); - - Account[] accounts = getGoogleAccounts(); - String defAccount = getSyncAccountName(this); + }); + dialogBuilder.show(); // 显示最终对话框 +} - mOriAccounts = accounts; - mHasAddedAccount = false; +// ====================== 账户管理工具方法 ====================== +private Account[] getGoogleAccounts() { + AccountManager accountManager = AccountManager.get(this); // 获取系统账户管理器实例 + return accountManager.getAccountsByType("com.google"); // 过滤出所有Google类型账户 +} - if (accounts.length > 0) { - CharSequence[] items = new CharSequence[accounts.length]; - final CharSequence[] itemMapping = items; - int checkedItem = -1; - int index = 0; - for (Account account : accounts) { - if (TextUtils.equals(account.name, defAccount)) { - checkedItem = index; - } - items[index++] = account.name; - } - dialogBuilder.setSingleChoiceItems(items, checkedItem, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - setSyncAccount(itemMapping[which].toString()); - dialog.dismiss(); - refreshUI(); - } - }); +// ====================== 核心账户设置方法 ====================== +private void setSyncAccount(String account) { + if (!getSyncAccountName(this).equals(account)) { // 仅在新旧账户不同时执行操作 + // 持久化存储账户名到SharedPreferences + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器 + if (account != null) { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); // 存储有效账户名 + } else { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); // 空值处理 } + editor.commit(); // 立即提交写入(注意:同步操作可能阻塞UI) - View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); - dialogBuilder.setView(addAccountView); - - final AlertDialog dialog = dialogBuilder.show(); - addAccountView.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - mHasAddedAccount = true; - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" - }); - startActivityForResult(intent, -1); - dialog.dismiss(); - } - }); - } - - private void showChangeAccountConfirmAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, - getSyncAccountName(this))); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); - dialogBuilder.setCustomTitle(titleView); + setLastSyncTime(this, 0); // 重置最后同步时间戳为0(表示需要重新同步) - CharSequence[] menuItemArray = new CharSequence[] { - getString(R.string.preferences_menu_change_account), - getString(R.string.preferences_menu_remove_account), - getString(R.string.preferences_menu_cancel) - }; - dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - showSelectAccountAlertDialog(); - } else if (which == 1) { - removeSyncAccount(); - refreshUI(); - } + // 启动后台线程清理旧账户关联数据 + new Thread(new Runnable() { + @Override + public void run() { + ContentValues values = new ContentValues(); // 创建更新数据集 + values.put(NoteColumns.GTASK_ID, ""); // 清空任务ID字段 + values.put(NoteColumns.SYNC_ID, 0); // 重置同步ID为初始状态 + getContentResolver().update( + Notes.CONTENT_NOTE_URI, // 目标ContentProvider URI + values, // 更新内容 + null, // 无WHERE条件(更新所有记录) + null // 无参数 + ); // 执行批量更新操作 } - }); - dialogBuilder.show(); - } + }).start(); // 立即启动清理线程 - private Account[] getGoogleAccounts() { - AccountManager accountManager = AccountManager.get(this); - return accountManager.getAccountsByType("com.google"); + // 显示操作成功的Toast提示 + Toast.makeText(NotesPreferenceActivity.this, + getString(R.string.preferences_toast_success_set_accout, account), // 动态插入账户名 + Toast.LENGTH_SHORT).show(); // 短时显示提示 } +} - private void setSyncAccount(String account) { - if (!getSyncAccountName(this).equals(account)) { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - if (account != null) { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); - } else { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); - } - editor.commit(); - - // clean up last sync time - setLastSyncTime(this, 0); - - // clean up local gtask related info - new Thread(new Runnable() { - public void run() { - ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); - } - }).start(); - - Toast.makeText(NotesPreferenceActivity.this, - getString(R.string.preferences_toast_success_set_accout, account), - Toast.LENGTH_SHORT).show(); - } - } - private void removeSyncAccount() { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { - editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); - } - if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { - editor.remove(PREFERENCE_LAST_SYNC_TIME); - } - editor.commit(); - // clean up local gtask related info - new Thread(new Runnable() { - public void run() { - ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); - } - }).start(); - } +private void removeSyncAccount() { + // 获取SharedPreferences实例(私有模式仅本应用可访问) + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器对象 - public static String getSyncAccountName(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + // 条件移除账户名配置(避免删除不存在的键) + if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { + editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); // 删除键值对 } - - public static void setLastSyncTime(Context context, long time) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); - editor.commit(); + // 条件移除最后同步时间戳 + if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { + editor.remove(PREFERENCE_LAST_SYNC_TIME); } + editor.commit(); // 同步提交修改(立即生效,适合关键配置操作) + + // 启动异步任务清理关联数据 + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); // 重置任务ID为空字符串 + values.put(NoteColumns.SYNC_ID, 0); // 重置同步标识为初始状态 + getContentResolver().update( + Notes.CONTENT_NOTE_URI, // 目标ContentProvider的URI + values, // 更新值集合 + null, // 无WHERE条件(全表更新) + null // 无参数绑定 + ); // 执行批量数据清理 + } + }).start(); // 立即启动后台线程 +} - public static long getLastSyncTime(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); - } +// ====================== 配置存取工具方法 ====================== +// 获取当前绑定账户名(返回空字符串表示未设置) +public static String getSyncAccountName(Context context) { + SharedPreferences settings = context.getSharedPreferences( + PREFERENCE_NAME, // 配置文件名 + Context.MODE_PRIVATE // 访问模式 + ); + return settings.getString( + PREFERENCE_SYNC_ACCOUNT_NAME, // 键名 + "" // 默认值(空字符串) + ); +} - private class GTaskReceiver extends BroadcastReceiver { +// 更新最后同步时间戳(单位:毫秒) +public static void setLastSyncTime(Context context, long time) { + SharedPreferences settings = context.getSharedPreferences( + PREFERENCE_NAME, + Context.MODE_PRIVATE + ); + SharedPreferences.Editor editor = settings.edit(); + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); // 写入长整型数值 + editor.commit(); // 同步提交保证时间戳立即更新 +} - @Override - public void onReceive(Context context, Intent intent) { - refreshUI(); - if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { - TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - syncStatus.setText(intent - .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); - } +// 获取最后成功同步时间(返回0表示从未同步) +public static long getLastSyncTime(Context context) { + SharedPreferences settings = context.getSharedPreferences( + PREFERENCE_NAME, + Context.MODE_PRIVATE + ); + return settings.getLong( + PREFERENCE_LAST_SYNC_TIME, // 键名 + 0L // 默认值(0) + ); +} +// ====================== 同步状态广播接收器 ====================== +private class GTaskReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + refreshUI(); // 强制刷新界面显示最新状态 + + // 检测广播是否携带同步状态 + if (intent.getBooleanExtra( + GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, // 附加参数键名 + false // 默认值 + )) { + TextView syncStatus = (TextView) findViewById( + R.id.prefenerece_sync_status_textview // 状态显示文本框ID + ); + // 设置动态进度信息(例如:"同步中(50%)") + syncStatus.setText(intent.getStringExtra( + GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG + )); } } +} - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Intent intent = new Intent(this, NotesListActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - default: - return false; - } +// ====================== 选项菜单处理 ====================== +public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: // 处理导航栏返回按钮 + Intent intent = new Intent(this, NotesListActivity.class); + // 清理活动栈:如果目标Activity已在栈中,则弹出其上所有Activity + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); // 跳转到笔记列表 + return true; // 消费事件 + default: + return false; // 未处理的事件传递给超类 } -} +} \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..0a62cad 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -1,132 +1,154 @@ -/* - * 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; - public abstract class NoteWidgetProvider extends AppWidgetProvider { - public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + // 数据库查询投影字段(对应笔记表的列) + public static final String[] PROJECTION = new String[]{ + NoteColumns.ID, // 笔记ID列 + NoteColumns.BG_COLOR_ID, // 背景颜色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; + // 查询结果列的索引常量 + public static final int COLUMN_ID = 0; // 笔记ID索引 + public static final int COLUMN_BG_COLOR_ID = 1; // 背景颜色ID索引 + public static final int COLUMN_SNIPPET = 2; // 内容摘要索引 - private static final String TAG = "NoteWidgetProvider"; + private static final String TAG = "NoteWidgetProvider"; // 日志标签 + /** + * 小部件被删除时的清理操作 + * @param context 上下文 + * @param appWidgetIds 被删除的小部件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])}); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); // 重置无效ID + + for (int widgetId : appWidgetIds) { // 遍历所有被删除的小部件 + context.getContentResolver().update( + Notes.CONTENT_NOTE_URI, // 笔记内容URI + values, // 更新值集合 + NoteColumns.WIDGET_ID + "=?",// 按小部件ID筛选 + new String[]{String.valueOf(widgetId)} + ); } } + /** + * 获取小部件关联的笔记数据 + * @param context 上下文 + * @param widgetId 小部件ID + * @return 包含笔记数据的Cursor对象 + * 查询规则:排除垃圾桶中的笔记 + */ 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); + return context.getContentResolver().query( + Notes.CONTENT_NOTE_URI, // 笔记内容URI + PROJECTION, // 查询字段 + NoteColumns.WIDGET_ID + "=? AND " + // 查询条件 + NoteColumns.PARENT_ID + "<>?", // 排除垃圾桶 + new String[]{String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER)}, + null // 排序方式 + ); } + /** + * 公开的小部件更新入口 + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 需要更新的小部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - update(context, appWidgetManager, appWidgetIds, false); + 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]); + /** + * 实际执行更新逻辑 + * @param privacyMode 隐私模式开关(true时显示占位文本) + */ + private void update(Context context, AppWidgetManager appWidgetManager, + int[] appWidgetIds, boolean privacyMode) { + for (int widgetId : appWidgetIds) { // 遍历处理每个小部件 + if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) continue; + + // 初始化默认值 + 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, widgetId); // 传递小部件ID + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); // 小部件类型 + + // 查询关联的笔记数据 + try (Cursor c = getNoteWidgetInfo(context, widgetId)) { if (c != null && c.moveToFirst()) { + // 数据校验:防止相同小部件ID关联多个笔记 if (c.getCount() > 1) { - Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); - c.close(); - return; + Log.e(TAG, "小部件ID重复关联多个笔记:" + widgetId); + continue; } - 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); - /** - * Generate the pending intent to start host for the widget - */ - 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); + // 获取实际笔记数据 + snippet = c.getString(COLUMN_SNIPPET); // 内容摘要 + bgId = c.getInt(COLUMN_BG_COLOR_ID); // 背景ID + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); // 笔记ID + intent.setAction(Intent.ACTION_VIEW); // 设置为查看模式 } else { - rv.setTextViewText(R.id.widget_text, snippet); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, - PendingIntent.FLAG_UPDATE_CURRENT); + // 没有关联笔记的处理 + snippet = context.getString(R.string.widget_havenot_content); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 新建/编辑模式 } + } - rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); - appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + // 构建小部件视图 + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); // 设置背景 + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); // 传递背景给后续Activity + + // 构建点击事件 + PendingIntent pendingIntent; + if (privacyMode) { + // 隐私模式:显示占位文本,点击跳转到笔记列表 + rv.setTextViewText(R.id.widget_text, context.getString(R.string.widget_under_visit_mode)); + pendingIntent = PendingIntent.getActivity(context, widgetId, + new Intent(context, NotesListActivity.class), // 跳转列表页 + PendingIntent.FLAG_UPDATE_CURRENT); + } else { + // 正常模式:显示真实内容,点击打开对应笔记 + rv.setTextViewText(R.id.widget_text, snippet); + pendingIntent = PendingIntent.getActivity(context, widgetId, intent, + PendingIntent.FLAG_UPDATE_CURRENT); } + + // 绑定点击事件 + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + + // 更新小部件界面 + appWidgetManager.updateAppWidget(widgetId, rv); } } + //----------- 抽象方法(子类必须实现) ----------- + /** + * 获取背景资源ID(由具体主题小部件实现) + * @param bgId 背景颜色标识符 + * @return 实际资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 获取布局资源ID(由具体小部件类型实现) + */ protected abstract int getLayoutId(); + /** + * 获取小部件类型标识(用于区分不同小部件) + */ protected abstract int getWidgetType(); } + + + +