diff --git a/doc/~$便签开源代码泛读报告.docx b/doc/~$便签开源代码泛读报告.docx new file mode 100644 index 0000000..a6f1950 Binary files /dev/null and b/doc/~$便签开源代码泛读报告.docx differ diff --git a/src/net/micode/notes/gtask/data/MetaData.java b/src/net/micode/notes/gtask/data/MetaData.java index 3a2050b..b4d8817 100644 --- a/src/net/micode/notes/gtask/data/MetaData.java +++ b/src/net/micode/notes/gtask/data/MetaData.java @@ -25,11 +25,27 @@ import org.json.JSONException; import org.json.JSONObject; +/** + * MetaData - 元数据类 + *
+ * 继承自Task类,用于存储Google Task的元数据信息 + * 主要用于关联本地便签和Google Task + *
+ */ public class MetaData extends Task { - private final static String TAG = MetaData.class.getSimpleName(); + private final static String TAG = MetaData.class.getSimpleName(); // 日志标签 - private String mRelatedGid = null; + private String mRelatedGid = null; // 关联的全局唯一标识符 + /** + * 设置元数据 + *+ * 将元数据信息存储到Task的notes字段中,并设置名称为META_NOTE_NAME + *
+ * + * @param gid 关联的全局唯一标识符 + * @param metaInfo 元数据JSON对象 + */ public void setMeta(String gid, JSONObject metaInfo) { try { metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); @@ -40,15 +56,36 @@ public class MetaData extends Task { setName(GTaskStringUtils.META_NOTE_NAME); } + /** + * 获取关联的全局唯一标识符 + * + * @return 关联的全局唯一标识符 + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断元数据是否值得保存 + *+ * 只要notes字段不为null就值得保存 + *
+ * + * @return 是否值得保存 + */ @Override public boolean isWorthSaving() { return getNotes() != null; } + /** + * 从远程JSON对象设置元数据内容 + *+ * 调用父类方法后,解析notes字段获取关联的GID + *
+ * + * @param js 远程JSON对象 + */ @Override public void setContentByRemoteJSON(JSONObject js) { super.setContentByRemoteJSON(js); @@ -63,17 +100,45 @@ public class MetaData extends Task { } } + /** + * 从本地JSON对象设置元数据内容 + *+ * 该方法不应该被调用,会抛出IllegalAccessError + *
+ * + * @param js 本地JSON对象 + * @throws IllegalAccessError 调用该方法时抛出 + */ @Override public void setContentByLocalJSON(JSONObject js) { // this function should not be called throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } + /** + * 从元数据内容生成本地JSON对象 + *+ * 该方法不应该被调用,会抛出IllegalAccessError + *
+ * + * @return 本地JSON对象 + * @throws IllegalAccessError 调用该方法时抛出 + */ @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 获取同步操作类型 + *+ * 该方法不应该被调用,会抛出IllegalAccessError + *
+ * + * @param c 游标对象 + * @return 同步操作类型 + * @throws IllegalAccessError 调用该方法时抛出 + */ @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); diff --git a/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..f5c5fb4 100644 --- a/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -40,20 +40,41 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; +/** + * 闹钟提醒活动,用于显示笔记的闹钟提醒 + */ public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + /** + * 笔记ID + */ private long mNoteId; + /** + * 笔记摘要 + */ private String mSnippet; + /** + * 摘要预览最大长度 + */ private static final int SNIPPET_PREW_MAX_LEN = 60; + /** + * 媒体播放器,用于播放闹钟铃声 + */ MediaPlayer mPlayer; + /** + * 活动创建时调用 + * @param savedInstanceState 保存的实例状态 + */ @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 @@ -64,8 +85,11 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD Intent intent = getIntent(); try { + // 从意图数据中获取笔记ID 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) : mSnippet; @@ -75,6 +99,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } mPlayer = new MediaPlayer(); + // 如果笔记存在于数据库中,显示提醒对话框并播放铃声 if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { showActionDialog(); playAlarmSound(); @@ -83,56 +108,73 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 检查屏幕是否开启 + * @return 屏幕是否开启 + */ private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + /** + * 播放闹钟铃声 + */ private void playAlarmSound() { + // 获取默认闹钟铃声URI 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) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } + /** + * 显示提醒对话框 + */ private void showActionDialog() { 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); } + /** + * 处理对话框点击事件 + * @param dialog 对话框对象 + * @param which 点击的按钮ID + */ 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); @@ -143,11 +185,19 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 处理对话框关闭事件 + * @param dialog 对话框对象 + */ public void onDismiss(DialogInterface dialog) { + // 停止闹钟铃声并结束活动 stopAlarmSound(); finish(); } + /** + * 停止闹钟铃声 + */ private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); diff --git a/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/net/micode/notes/ui/AlarmInitReceiver.java index f221202..2e92b8f 100644 --- a/src/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/net/micode/notes/ui/AlarmInitReceiver.java @@ -28,19 +28,37 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +/** + * 闹钟初始化广播接收器,用于在系统启动时重新设置所有未过期的闹钟 + */ public class AlarmInitReceiver extends BroadcastReceiver { + /** + * 查询投影,包括笔记ID和提醒日期 + */ private static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE }; + /** + * 笔记ID列索引 + */ private static final int COLUMN_ID = 0; + /** + * 提醒日期列索引 + */ private static final int COLUMN_ALERTED_DATE = 1; + /** + * 接收广播时调用 + * @param context 上下文对象 + * @param intent 接收的意图 + */ @Override public void onReceive(Context context, Intent intent) { long currentDate = System.currentTimeMillis(); + // 查询所有未过期的笔记提醒 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, @@ -50,15 +68,20 @@ public class AlarmInitReceiver extends BroadcastReceiver { if (c != null) { if (c.moveToFirst()) { do { + // 获取提醒日期和笔记ID long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 创建闹钟接收器意图 Intent sender = new Intent(context, AlarmReceiver.class); sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建PendingIntent PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获取闹钟管理器并设置闹钟 AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); } while (c.moveToNext()); } + // 关闭游标 c.close(); } } diff --git a/src/net/micode/notes/ui/AlarmReceiver.java b/src/net/micode/notes/ui/AlarmReceiver.java index 54e503b..709be3f 100644 --- a/src/net/micode/notes/ui/AlarmReceiver.java +++ b/src/net/micode/notes/ui/AlarmReceiver.java @@ -20,11 +20,22 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +/** + * 闹钟接收器,用于接收闹钟触发事件并启动闹钟提醒活动 + */ public class AlarmReceiver extends BroadcastReceiver { + /** + * 接收广播时调用 + * @param context 上下文对象 + * @param intent 接收的意图 + */ @Override public void onReceive(Context context, Intent intent) { + // 将意图的目标类设置为闹钟提醒活动 intent.setClass(context, AlarmAlertActivity.class); + // 添加新任务标志,允许在非活动上下文启动活动 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 启动闹钟提醒活动 context.startActivity(intent); } } diff --git a/src/net/micode/notes/ui/DateTimePicker.java b/src/net/micode/notes/ui/DateTimePicker.java index 496b0cd..706003f 100644 --- a/src/net/micode/notes/ui/DateTimePicker.java +++ b/src/net/micode/notes/ui/DateTimePicker.java @@ -28,85 +28,182 @@ 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; + /** + * 24小时制小时选择器最小值 + */ 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; + /** + * 12小时制小时选择器最小值 + */ 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; + /** + * 分钟选择器最小值 + */ private static final int MINUT_SPINNER_MIN_VAL = 0; + /** + * 分钟选择器最大值 + */ private static final int MINUT_SPINNER_MAX_VAL = 59; + /** + * AM/PM选择器最小值 + */ private static final int AMPM_SPINNER_MIN_VAL = 0; + /** + * AM/PM选择器最大值 + */ private static final int AMPM_SPINNER_MAX_VAL = 1; + /** + * 日期选择器 + */ private final NumberPicker mDateSpinner; + /** + * 小时选择器 + */ private final NumberPicker mHourSpinner; + /** + * 分钟选择器 + */ private final NumberPicker mMinuteSpinner; + /** + * AM/PM选择器 + */ private final NumberPicker mAmPmSpinner; + /** + * 当前日期时间 + */ 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) { boolean isDateChanged = false; Calendar cal = Calendar.getInstance(); if (!mIs24HourView) { + // 12小时制处理 if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + // 从下午11点到上午12点,日期+1 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) { + // 从上午12点到下午11点,日期-1 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } + // 切换AM/PM 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) { + // 从23点到0点,日期+1 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + // 从0点到23点,日期-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)); @@ -115,6 +212,9 @@ public class DateTimePicker extends FrameLayout { } }; + /** + * 分钟变化监听器 + */ private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { @@ -122,14 +222,19 @@ public class DateTimePicker extends FrameLayout { int maxValue = mMinuteSpinner.getMaxValue(); int offset = 0; if (oldVal == maxValue && newVal == minValue) { + // 从59分钟到0分钟,小时+1 offset += 1; } else if (oldVal == minValue && newVal == maxValue) { + // 从0分钟到59分钟,小时-1 offset -= 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; @@ -139,58 +244,100 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } } + // 更新分钟 mDate.set(Calendar.MINUTE, newVal); + // 通知日期时间变化 onDateTimeChanged(); } }; + /** + * AM/PM变化监听器 + */ private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 切换AM/PM mIsAm = !mIsAm; if (mIsAm) { + // 从PM切换到AM,小时-12 mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); } else { + // 从AM切换到PM,小时+12 mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); } + // 更新AM/PM控件 updateAmPmControl(); + // 通知日期时间变化 onDateTimeChanged(); } }; + /** + * 日期时间变化监听器接口 + */ public interface OnDateTimeChangedListener { + /** + * 日期时间变化时调用 + * @param view 日期时间选择器 + * @param year 年份 + * @param month 月份 + * @param dayOfMonth 日 + * @param hourOfDay 小时(24小时制) + * @param minute 分钟 + */ void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute); } + /** + * 使用当前时间初始化日期时间选择器 + * @param context 上下文 + */ public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + /** + * 使用指定时间初始化日期时间选择器 + * @param context 上下文 + * @param date 时间戳 + */ public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + /** + * 使用指定时间和24小时制标志初始化日期时间选择器 + * @param context 上下文 + * @param date 时间戳 + * @param is24HourView 是否为24小时制 + */ 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); + // 初始化日期选择器 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); + // 初始化AM/PM选择器 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); @@ -198,22 +345,28 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新控件到初始状态 updateDateControl(); updateHourControl(); updateAmPmControl(); + // 设置24小时制 set24HourView(is24HourView); - // set to current time + // 设置当前时间 setCurrentDate(date); + // 设置启用状态 setEnabled(isEnabled()); - // set the content descriptions + // 设置内容描述 mInitialising = false; } + /** + * 设置是否启用 + * @param enabled 是否启用 + */ @Override public void setEnabled(boolean enabled) { if (mIsEnabled == enabled) { @@ -227,24 +380,28 @@ public class DateTimePicker extends FrameLayout { mIsEnabled = enabled; } + /** + * 获取是否启用 + * @return 是否启用 + */ @Override public boolean isEnabled() { return mIsEnabled; } /** - * Get the current date in millis + * 获取当前时间戳 * - * @return the current date in millis + * @return 当前时间戳 */ public long getCurrentDateInTimeMillis() { return mDate.getTimeInMillis(); } /** - * Set the current date + * 设置当前时间 * - * @param date The current date in millis + * @param date 当前时间戳 */ public void setCurrentDate(long date) { Calendar cal = Calendar.getInstance(); @@ -254,13 +411,13 @@ public class DateTimePicker extends FrameLayout { } /** - * 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 + * @param year 当前年份 + * @param month 当前月份(0-11) + * @param dayOfMonth 当前日期(1-31) + * @param hourOfDay 当前小时(根据24小时制或12小时制) + * @param minute 当前分钟(0-59) */ public void setCurrentDate(int year, int month, int dayOfMonth, int hourOfDay, int minute) { @@ -272,18 +429,18 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current year + * 获取当前年份 * - * @return The current year + * @return 当前年份 */ public int getCurrentYear() { return mDate.get(Calendar.YEAR); } /** - * Set current year + * 设置当前年份 * - * @param year The current year + * @param year 当前年份 */ public void setCurrentYear(int year) { if (!mInitialising && year == getCurrentYear()) { @@ -295,18 +452,18 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current month in the year + * 获取当前月份(0-11) * - * @return The current month in the year + * @return 当前月份(0-11) */ public int getCurrentMonth() { return mDate.get(Calendar.MONTH); } /** - * Set current month in the year + * 设置当前月份(0-11) * - * @param month The month in the year + * @param month 当前月份(0-11) */ public void setCurrentMonth(int month) { if (!mInitialising && month == getCurrentMonth()) { @@ -318,18 +475,18 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current day of the month + * 获取当前日期(1-31) * - * @return The day of the month + * @return 当前日期(1-31) */ public int getCurrentDay() { return mDate.get(Calendar.DAY_OF_MONTH); } /** - * Set current day of the month + * 设置当前日期(1-31) * - * @param dayOfMonth The day of the month + * @param dayOfMonth 当前日期(1-31) */ public void setCurrentDay(int dayOfMonth) { if (!mInitialising && dayOfMonth == getCurrentDay()) { @@ -341,13 +498,17 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode + * 获取当前小时(根据24小时制或12小时制) + * @return 当前小时(根据24小时制或12小时制) */ public int getCurrentHourOfDay() { return mDate.get(Calendar.HOUR_OF_DAY); } + /** + * 获取当前小时(根据24小时制或12小时制) + * @return 当前小时(根据24小时制或12小时制) + */ private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); @@ -434,35 +595,53 @@ public class DateTimePicker extends FrameLayout { 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(); } + /** + * 更新AM/PM控件 + */ private void updateAmPmControl() { if (mIs24HourView) { + // 24小时制下隐藏AM/PM选择器 mAmPmSpinner.setVisibility(View.GONE); } else { + // 12小时制下设置AM/PM值并显示 int index = mIsAm ? Calendar.AM : Calendar.PM; mAmPmSpinner.setValue(index); mAmPmSpinner.setVisibility(View.VISIBLE); } } + /** + * 更新小时控件 + */ private void updateHourControl() { if (mIs24HourView) { + // 设置24小时制的小时范围 mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); } else { + // 设置12小时制的小时范围 mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); } @@ -476,6 +655,9 @@ public class DateTimePicker extends FrameLayout { mOnDateTimeChangedListener = callback; } + /** + * 通知日期时间变化 + */ private void onDateTimeChanged() { if (mOnDateTimeChangedListener != null) { mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), diff --git a/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..344c16a 100644 --- a/src/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,51 +29,99 @@ import android.content.DialogInterface.OnClickListener; import android.text.format.DateFormat; import android.text.format.DateUtils; +/** + * 日期时间选择对话框 + */ public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + /** + * 当前日期时间 + */ private Calendar mDate = Calendar.getInstance(); + /** + * 是否为24小时制 + */ private boolean mIs24HourView; + /** + * 日期时间设置监听器 + */ private OnDateTimeSetListener mOnDateTimeSetListener; + /** + * 日期时间选择器 + */ private DateTimePicker mDateTimePicker; + /** + * 日期时间设置监听器接口 + */ public interface OnDateTimeSetListener { + /** + * 日期时间设置时调用 + * @param dialog 对话框 + * @param date 日期时间戳 + */ void OnDateTimeSet(AlertDialog dialog, long date); } + /** + * 构造函数 + * @param context 上下文 + * @param date 初始日期时间戳 + */ public DateTimePickerDialog(Context context, long date) { super(context); + // 初始化日期时间选择器 mDateTimePicker = new DateTimePicker(context); setView(mDateTimePicker); + // 设置日期时间变化监听器 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute) { + // 更新日期时间 mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.MINUTE, minute); + // 更新标题 updateTitle(mDate.getTimeInMillis()); } }); + // 设置初始日期时间 mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + // 设置按钮 setButton(context.getString(R.string.datetime_dialog_ok), this); setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + // 设置24小时制 set24HourView(DateFormat.is24HourFormat(this.getContext())); + // 更新标题 updateTitle(mDate.getTimeInMillis()); } + /** + * 设置是否为24小时制 + * @param is24HourView 是否为24小时制 + */ public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + /** + * 设置日期时间设置监听器 + * @param callBack 监听器 + */ public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + /** + * 更新标题 + * @param date 日期时间戳 + */ private void updateTitle(long date) { - int flag = + int flag = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME; @@ -81,6 +129,11 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + /** + * 点击按钮时调用 + * @param arg0 对话框 + * @param arg1 按钮索引 + */ public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); diff --git a/src/net/micode/notes/ui/DropdownMenu.java b/src/net/micode/notes/ui/DropdownMenu.java index 613dc74..d7f7bff 100644 --- a/src/net/micode/notes/ui/DropdownMenu.java +++ b/src/net/micode/notes/ui/DropdownMenu.java @@ -27,17 +27,39 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +/** + * 下拉菜单类,用于创建和管理下拉菜单 + */ public class DropdownMenu { + /** + * 按钮控件,用于触发下拉菜单 + */ private Button mButton; + /** + * 弹出菜单 + */ private PopupMenu mPopupMenu; + /** + * 菜单对象 + */ private Menu mMenu; + /** + * 构造函数 + * @param context 上下文 + * @param button 触发下拉菜单的按钮 + * @param menuId 菜单资源ID + */ public DropdownMenu(Context context, Button button, int menuId) { mButton = button; + // 设置按钮背景为下拉图标 mButton.setBackgroundResource(R.drawable.dropdown_icon); + // 初始化弹出菜单 mPopupMenu = new PopupMenu(context, mButton); mMenu = mPopupMenu.getMenu(); + // 加载菜单资源 mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 设置按钮点击事件,点击时显示弹出菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +67,29 @@ public class DropdownMenu { }); } + /** + * 设置下拉菜单项点击监听器 + * @param listener 监听器 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 根据ID查找菜单项 + * @param id 菜单项ID + * @return 菜单项 + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置按钮标题 + * @param title 标题文本 + */ public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/src/net/micode/notes/ui/FoldersListAdapter.java b/src/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..b2de8e0 100644 --- a/src/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/net/micode/notes/ui/FoldersListAdapter.java @@ -29,50 +29,103 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +/** + * 文件夹列表适配器,用于显示文件夹列表 + */ public class FoldersListAdapter extends CursorAdapter { + /** + * 查询投影,包括文件夹ID和名称 + */ public static final String [] PROJECTION = { NoteColumns.ID, NoteColumns.SNIPPET }; + /** + * ID列索引 + */ public static final int ID_COLUMN = 0; + /** + * 名称列索引 + */ public static final int NAME_COLUMN = 1; + /** + * 构造函数 + * @param context 上下文 + * @param c 游标 + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); - // TODO Auto-generated constructor stub } + /** + * 创建新视图 + * @param context 上下文 + * @param cursor 游标 + * @param parent 父视图 + * @return 新创建的视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 绑定视图数据 + * @param view 视图 + * @param context 上下文 + * @param cursor 游标 + */ @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); } } + /** + * 获取文件夹名称 + * @param context 上下文 + * @param position 位置 + * @return 文件夹名称 + */ 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); } + /** + * 文件夹列表项内部类 + */ private class FolderListItem extends LinearLayout { + /** + * 文件夹名称文本视图 + */ private TextView mName; + /** + * 构造函数 + * @param context 上下文 + */ public FolderListItem(Context context) { super(context); + // 加载布局 inflate(context, R.layout.folder_list_item, this); + // 初始化文本视图 mName = (TextView) findViewById(R.id.tv_folder_name); } + /** + * 绑定数据 + * @param name 文件夹名称 + */ public void bind(String name) { + // 设置文件夹名称 mName.setText(name); } } diff --git a/src/net/micode/notes/ui/NoteEditActivity.java b/src/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..b0325c3 100644 --- a/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/net/micode/notes/ui/NoteEditActivity.java @@ -72,18 +72,34 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * NoteEditActivity - 便签编辑活动 + *+ * 该类负责处理便签的创建、编辑、保存等核心功能,支持普通文本模式和 checklist 模式 + * 实现了背景色切换、字体大小调整、提醒设置、分享等功能 + *
+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + /** + * HeadViewHolder - 便签头部视图持有者 + *+ * 用于缓存便签头部的UI组件,提高性能 + *
+ */ private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvAlertDate; - - public ImageView ibSetBgColor; + public TextView tvModified; // 修改时间显示 + public ImageView ivAlertIcon; // 提醒图标 + public TextView tvAlertDate; // 提醒日期显示 + public ImageView ibSetBgColor; // 设置背景色按钮 } + /** + * 背景选择按钮映射 - 将UI按钮ID映射到颜色常量 + */ private static final Map+ * 当活动创建时调用,设置布局并初始化活动状态 + *
+ * + * @param savedInstanceState 保存的实例状态,用于恢复活动 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); + // 如果没有保存的实例状态且初始化失败,则结束活动 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } + // 初始化资源 initResources(); } /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state + * 恢复活动状态 onRestoreInstanceState 方法 + *+ * 当活动因内存不足被杀死后重新加载时,恢复之前的状态 + *
+ * + * @param savedInstanceState 保存的实例状态 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); + // 如果保存了实例状态且包含UID,则恢复活动 if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); @@ -179,24 +210,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 初始化活动状态 + *+ * 根据传入的意图初始化便签编辑活动,处理查看、创建、编辑便签的情况 + *
+ * + * @param intent 传入的意图,包含操作类型和数据 + * @return 初始化是否成功 + */ private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ mWorkingNote = null; + + // 处理查看便签操作 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; - /** - * Starting from the searched result - */ + // 从搜索结果启动 if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } + // 检查便签是否存在 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); @@ -204,6 +241,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } else { + // 加载便签 mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); @@ -211,11 +249,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } } + // 设置软键盘模式为隐藏状态 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // New note + } + // 处理创建或编辑便签操作 + else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // 获取参数 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); @@ -224,7 +265,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); - // Parse call-record note + // 处理通话记录便签 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); if (callDate != 0 && phoneNumber != null) { @@ -232,6 +273,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.w(TAG, "The call record number is null"); } long noteId = 0; + // 检查是否已存在相同的通话记录便签 if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate)) > 0) { mWorkingNote = WorkingNote.load(this, noteId); @@ -241,119 +283,193 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } } else { + // 创建新的通话记录便签 mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); mWorkingNote.convertToCallNote(phoneNumber, callDate); } } else { + // 创建普通新便签 mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); } + // 设置软键盘模式为可见状态 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } else { + } + // 不支持的操作类型 + else { Log.e(TAG, "Intent not specified action, should not support"); finish(); return false; } + + // 设置设置状态变化监听器 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } + /** + * 活动恢复 onResume 方法 + *+ * 当活动恢复到前台时调用,初始化便签屏幕 + *
+ */ @Override protected void onResume() { super.onResume(); initNoteScreen(); } + /** + * 初始化便签屏幕 + *+ * 根据当前便签状态初始化UI,包括字体大小、背景颜色、内容显示等 + *
+ */ private void initNoteScreen() { + // 设置编辑器字体大小 mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + + // 根据便签模式显示内容 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 切换到列表模式 switchToListMode(mWorkingNote.getContent()); } else { + // 普通文本模式,显示高亮搜索结果 mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + // 将光标定位到文本末尾 mNoteEditor.setSelection(mNoteEditor.getText().length()); } + + // 隐藏所有背景选择状态 for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } + + // 设置头部和编辑器背景 mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + // 设置修改时间 mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)); - /** - * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready - */ + // 显示提醒头部 showAlertHeader(); } + /** + * 显示提醒头部 + *+ * 根据便签是否有提醒以及提醒是否过期,显示相应的提醒信息 + *
+ */ private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); if (time > mWorkingNote.getAlertDate()) { + // 提醒已过期 mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { + // 显示相对时间 mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } + // 显示提醒图标和日期 mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { + // 隐藏提醒图标和日期 mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); }; } + /** + * 处理新意图 onNewIntent 方法 + *+ * 当活动已经存在且收到新意图时调用,重新初始化活动状态 + *
+ * + * @param intent 新的意图 + */ @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } + /** + * 保存实例状态 onSaveInstanceState 方法 + *+ * 当活动即将被销毁时调用,保存当前便签状态 + *
+ * + * @param outState 用于保存状态的Bundle对象 + */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ + + // 对于新便签,先保存生成ID if (!mWorkingNote.existInDatabase()) { saveNote(); } + + // 保存便签ID outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } + /** + * 分发触摸事件 + *+ * 处理屏幕触摸事件,点击外部区域关闭背景选择器和字体大小选择器 + *
+ * + * @param ev 触摸事件对象 + * @return 是否消耗了该事件 + */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { + // 点击背景选择器外部,关闭背景选择器 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mNoteBgColorSelector, ev)) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } + // 点击字体大小选择器外部,关闭字体大小选择器 if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { mFontSizeSelector.setVisibility(View.GONE); return true; } + + // 其他情况交给父类处理 return super.dispatchTouchEvent(ev); } + /** + * 检查触摸事件是否在指定视图范围内 + * + * @param view 要检查的视图 + * @param ev 触摸事件对象 + * @return 是否在视图范围内 + */ private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); int x = location[0]; int y = location[1]; + + // 检查触摸坐标是否在视图范围内 if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y @@ -363,7 +479,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 初始化资源 + *+ * 初始化UI组件,设置点击事件监听器 + *
+ */ private void initResources() { + // 初始化头部视图 mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); @@ -371,32 +494,44 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + + // 初始化编辑器 mNoteEditor = (EditText) findViewById(R.id.note_edit_view); mNoteEditorPanel = findViewById(R.id.sv_note_edit); + + // 初始化背景颜色选择器 mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } + // 初始化字体大小选择器 mFontSizeSelector = findViewById(R.id.font_size_selector); for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); }; + + // 初始化共享偏好设置和字体大小 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ + + // 修复字体大小ID可能超出范围的问题 if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } + + // 初始化编辑列表 mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } + /** + * 活动暂停 onPause 方法 + *+ * 当活动进入后台时调用,保存便签并清除设置状态 + *
+ */ @Override protected void onPause() { super.onPause(); @@ -406,6 +541,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, clearSettingState(); } + /** + * 更新小部件 + *+ * 当便签更新时,更新对应的桌面小部件 + *
+ */ private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { @@ -425,6 +566,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, setResult(RESULT_OK, intent); } + /** + * 点击事件处理 + *+ * 处理UI组件的点击事件,包括设置背景色、选择背景色、选择字体大小 + *
+ * + * @param v 被点击的视图 + */ public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_set_bg_color) { @@ -452,6 +601,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 返回键处理 onBackPressed 方法 + *+ * 当用户点击返回键时调用,先尝试清除设置状态,否则保存便签并返回 + *
+ */ @Override public void onBackPressed() { if(clearSettingState()) { @@ -462,6 +617,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, super.onBackPressed(); } + /** + * 清除设置状态 + *+ * 关闭背景选择器和字体大小选择器 + *
+ * + * @return 是否清除了设置状态 + */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); @@ -473,6 +636,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + /** + * 背景颜色变化监听器 + *+ * 当背景颜色变化时调用,更新UI显示 + *
+ */ public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); @@ -480,23 +649,40 @@ public class NoteEditActivity extends Activity implements OnClickListener, mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + /** + * 准备选项菜单 + *+ * 在显示菜单前调用,根据当前便签状态调整菜单项 + *
+ * + * @param menu 菜单对象 + * @return 是否显示菜单 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { return true; } + + // 清除设置状态 clearSettingState(); + + // 根据便签类型加载不同菜单 menu.clear(); if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); } + + // 根据便签模式调整列表模式菜单项标题 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } + + // 根据是否有提醒调整提醒相关菜单项可见性 if (mWorkingNote.hasClockAlert()) { menu.findItem(R.id.menu_alert).setVisible(false); } else { @@ -505,6 +691,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 选项菜单点击处理 + *+ * 处理菜单选项的点击事件,包括新建便签、删除、字体大小、列表模式、分享等 + *
+ * + * @param item 被点击的菜单项 + * @return 是否处理了该事件 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -512,6 +707,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, createNewNote(); break; case R.id.menu_delete: + // 显示删除确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -527,24 +723,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, builder.show(); break; case R.id.menu_font_size: + // 显示字体大小选择器 mFontSizeSelector.setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: + // 切换便签模式 mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: + // 分享便签 getWorkingText(); sendTo(this, mWorkingNote.getContent()); break; case R.id.menu_send_to_desktop: + // 发送到桌面 sendToDesktop(); break; case R.id.menu_alert: + // 设置提醒 setReminder(); break; case R.id.menu_delete_remind: + // 删除提醒 mWorkingNote.setAlertDate(0, false); break; default: @@ -553,6 +755,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 设置提醒 + *+ * 显示日期时间选择对话框,设置便签提醒 + *
+ */ private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { @@ -564,8 +772,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, } /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type + * 分享便签 + *+ * 将便签内容分享给支持ACTION_SEND和text/plain类型的应用 + *
+ * + * @param context 上下文对象 + * @param info 要分享的内容 */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); @@ -574,11 +787,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + /** + * 创建新便签 + *+ * 保存当前便签,然后启动新的编辑活动 + *
+ */ private void createNewNote() { - // Firstly, save current editing notes + // 首先保存当前编辑的便签 saveNote(); - // For safety, start a new NoteEditActivity + // 安全起见,启动新的NoteEditActivity finish(); Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -586,6 +805,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + /** + * 删除当前便签 + *+ * 根据是否同步模式,直接删除或移到回收站 + *
+ */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet+ * 判断当前是否已配置同步账号 + *
+ * + * @return 是否为同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 提醒时间变化监听器 + *+ * 当提醒时间变化时调用,设置或取消系统闹钟 + *
+ * + * @param date 提醒日期时间 + * @param set 是否设置提醒 + */ public void onClockAlertChanged(long date, boolean set) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ + // 先保存便签,确保有Note ID if (!mWorkingNote.existInDatabase()) { saveNote(); } + if (mWorkingNote.getNoteId() > 0) { + // 创建闹钟意图 Intent intent = new Intent(this, AlarmReceiver.class); intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + + // 更新提醒头部 showAlertHeader(); + + // 设置或取消闹钟 if(!set) { alarmManager.cancel(pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ + // 便签为空,无法设置提醒 Log.e(TAG, "Clock alert setting error"); showToast(R.string.error_note_empty_for_clock); } } + /** + * 小部件变化监听器 + *+ * 当便签关联的小部件变化时调用,更新小部件 + *
+ */ public void onWidgetChanged() { updateWidget(); } diff --git a/src/net/micode/notes/ui/NoteEditText.java b/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..610623c 100644 --- a/src/net/micode/notes/ui/NoteEditText.java +++ b/src/net/micode/notes/ui/NoteEditText.java @@ -37,15 +37,39 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * 笔记编辑文本框,用于处理笔记编辑中的各种功能 + */ public class NoteEditText extends EditText { + /** + * 日志标签 + */ private static final String TAG = "NoteEditText"; + /** + * 文本框索引 + */ private int mIndex; + /** + * 删除前的选择起始位置 + */ private int mSelectionStartBeforeDelete; + /** + * 电话链接前缀 + */ private static final String SCHEME_TEL = "tel:" ; + /** + * HTTP链接前缀 + */ private static final String SCHEME_HTTP = "http:" ; + /** + * 邮箱链接前缀 + */ private static final String SCHEME_EMAIL = "mailto:" ; + /** + * 链接前缀与操作资源ID的映射 + */ private static final Map+ * 负责将数据库中的便签数据绑定到列表视图,支持多选模式和小部件属性管理 + *
+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap+ * 存储小部件的ID和类型信息 + *
+ */ public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; + public int widgetId; // 小部件ID + public int widgetType; // 小部件类型 }; + /** + * 构造函数 + *+ * 初始化适配器,设置上下文和选中项映射 + *
+ * + * @param context 上下文对象 + */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap+ * 当需要新视图时调用,创建一个新的NotesListItem + *
+ * + * @param context 上下文对象 + * @param cursor 游标对象 + * @param parent 父视图 + * @return 新创建的视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } + /** + * 绑定视图 + *+ * 将游标数据绑定到视图上 + *
+ * + * @param view 要绑定的视图 + * @param context 上下文对象 + * @param cursor 游标对象,包含数据 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -64,20 +108,53 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 设置选中项 + *+ * 设置指定位置的项是否被选中,并通知数据变化 + *
+ * + * @param position 位置索引 + * @param checked 是否选中 + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); notifyDataSetChanged(); } + /** + * 是否为选择模式 + *+ * 获取当前是否处于选择模式 + *
+ * + * @return 是否为选择模式 + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置选择模式 + *+ * 设置适配器的选择模式,清除现有选中状态 + *
+ * + * @param mode 是否为选择模式 + */ public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } + /** + * 全选/取消全选 + *+ * 选中或取消选中所有便签 + *
+ * + * @param checked 是否选中 + */ public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { @@ -89,6 +166,14 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取选中项ID + *+ * 获取所有选中项的ID集合 + *
+ * + * @return 选中项ID集合 + */ public HashSet+ * 获取所有选中项的小部件属性集合 + *
+ * + * @return 选中小部件属性集合 + */ public HashSet+ * 获取当前选中项的数量 + *
+ * + * @return 选中项数量 + */ public int getSelectedCount() { Collection+ * 检查是否所有便签都已被选中 + *
+ * + * @return 是否全选 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 是否选中项 + *+ * 检查指定位置的项是否被选中 + *
+ * + * @param position 位置索引 + * @return 是否选中 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,18 +271,38 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + /** + * 内容变化监听器 + *+ * 当内容变化时调用,重新计算便签数量 + *
+ */ @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + /** + * 改变游标 + *+ * 当游标变化时调用,重新计算便签数量 + *
+ * + * @param cursor 新的游标对象 + */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + /** + * 计算便签数量 + *+ * 遍历游标,计算便签数量 + *
+ */ private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { diff --git a/src/net/micode/notes/ui/NotesListItem.java b/src/net/micode/notes/ui/NotesListItem.java index 1221e80..7afd3c3 100644 --- a/src/net/micode/notes/ui/NotesListItem.java +++ b/src/net/micode/notes/ui/NotesListItem.java @@ -30,17 +30,44 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; -public class NotesListItem extends LinearLayout { +/** + * 笔记列表项,用于显示笔记列表中的单个笔记项 + */ +public class NotesListItem extends LinearLayout { + /** + * 提醒图标 + */ private ImageView mAlert; + /** + * 标题文本 + */ private TextView mTitle; + /** + * 时间文本 + */ private TextView mTime; + /** + * 联系人姓名文本 + */ private TextView mCallName; + /** + * 笔记项数据 + */ private NoteItemData mItemData; + /** + * 选择复选框 + */ private CheckBox mCheckBox; + /** + * 构造函数 + * @param context 上下文 + */ 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); @@ -48,7 +75,15 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + /** + * 绑定数据 + * @param context 上下文 + * @param data 笔记项数据 + * @param choiceMode 是否为选择模式 + * @param 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); @@ -57,7 +92,9 @@ public class NotesListItem extends LinearLayout { } mItemData = data; + // 根据不同类型的笔记设置不同的显示内容 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 通话记录文件夹 mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); @@ -65,10 +102,12 @@ public class NotesListItem extends LinearLayout { + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setImageResource(R.drawable.call_record); } 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); mAlert.setVisibility(View.VISIBLE); @@ -76,16 +115,20 @@ public class NotesListItem extends LinearLayout { mAlert.setVisibility(View.GONE); } } else { + // 普通笔记或文件夹 mCallName.setVisibility(View.GONE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); if (data.getType() == Notes.TYPE_FOLDER) { + // 文件夹 mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setVisibility(View.GONE); } else { + // 普通笔记 mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 设置提醒图标 if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); @@ -94,14 +137,21 @@ public class NotesListItem extends LinearLayout { } } } + // 设置修改时间 mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 设置背景 setBackground(data); } + /** + * 设置背景 + * @param data 笔记项数据 + */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { + // 根据笔记在列表中的位置设置不同的背景 if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); } else if (data.isLast()) { @@ -112,10 +162,15 @@ public class NotesListItem extends LinearLayout { setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } } else { + // 文件夹背景 setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /** + * 获取笔记项数据 + * @return 笔记项数据 + */ 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..d043fcc 100644 --- a/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -48,27 +48,44 @@ 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; + /** GTask同步广播接收器 */ private GTaskReceiver mReceiver; + /** 原始账户列表,用于检测新添加的账户 */ private Account[] mOriAccounts; + /** 是否添加了新账户 */ private boolean mHasAddedAccount; + /** + * 创建活动,初始化界面元素和接收器 + * @param icicle 保存的活动状态 + */ @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -88,6 +105,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { getListView().addHeaderView(header, null, true); } + /** + * 恢复活动,检查新添加的账户并刷新界面 + */ @Override protected void onResume() { super.onResume(); @@ -116,6 +136,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { refreshUI(); } + /** + * 销毁活动,取消注册接收器 + */ @Override protected void onDestroy() { if (mReceiver != null) { @@ -124,6 +147,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { super.onDestroy(); } + /** + * 加载账户偏好设置,创建账户选择项 + */ private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -154,6 +180,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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); @@ -193,11 +222,17 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 刷新UI,重新加载账户偏好设置和同步按钮 + */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + /** + * 显示选择账户的对话框,允许用户选择或添加Google账户 + */ private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); @@ -254,6 +289,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 显示更改账户的确认对话框,提示用户更改账户的风险 + */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); @@ -283,11 +321,19 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + /** + * 获取设备上的所有Google账户 + * @return Google账户数组 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + /** + * 设置同步账户,并清理相关同步信息 + * @param account 账户名 + */ private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -318,6 +364,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 移除同步账户,并清理相关同步信息 + */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -340,12 +389,22 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + /** + * 获取同步账户名 + * @param context 上下文 + * @return 账户名 + */ public static String getSyncAccountName(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } + /** + * 设置最后同步时间 + * @param context 上下文 + * @param time 同步时间 + */ public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -354,12 +413,20 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + /** + * 获取最后同步时间 + * @param context 上下文 + * @return 同步时间 + */ public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); } + /** + * GTask同步广播接收器,用于接收同步状态变化的广播 + */ private class GTaskReceiver extends BroadcastReceiver { @Override @@ -374,6 +441,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 处理菜单项点击事件 + * @param item 菜单项 + * @return 是否处理了事件 + */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: diff --git a/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..3f54f75 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -32,19 +32,32 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * 笔记应用部件的抽象基类,定义了部件的基本行为和接口 + */ public abstract class NoteWidgetProvider extends AppWidgetProvider { + /** 查询投影字段,包含笔记ID、背景颜色ID和摘要 */ public static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.BG_COLOR_ID, NoteColumns.SNIPPET }; + /** 投影字段中的ID列索引 */ public static final int COLUMN_ID = 0; + /** 投影字段中的背景颜色ID列索引 */ public static final int COLUMN_BG_COLOR_ID = 1; + /** 投影字段中的摘要列索引 */ public static final int COLUMN_SNIPPET = 2; + /** 日志标签 */ private static final String TAG = "NoteWidgetProvider"; + /** + * 当部件被删除时调用,更新数据库中相关笔记的widget_id为无效值 + * @param context 上下文 + * @param appWidgetIds 被删除的部件ID数组 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { ContentValues values = new ContentValues(); @@ -57,6 +70,12 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 获取特定widget ID对应的笔记信息 + * @param context 上下文 + * @param widgetId 部件ID + * @return 包含笔记信息的游标 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, @@ -65,10 +84,23 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { null); } + /** + * 更新部件,默认不使用隐私模式 + * @param context 上下文 + * @param appWidgetManager 部件管理器 + * @param appWidgetIds 要更新的部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 更新部件,支持隐私模式 + * @param context 上下文 + * @param appWidgetManager 部件管理器 + * @param appWidgetIds 要更新的部件ID数组 + * @param privacyMode 是否使用隐私模式 + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { for (int i = 0; i < appWidgetIds.length; i++) { @@ -124,9 +156,22 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 根据背景颜色ID获取对应的资源ID + * @param bgId 背景颜色ID + * @return 资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 获取部件布局ID + * @return 布局ID + */ protected abstract int getLayoutId(); + /** + * 获取部件类型 + * @return 部件类型 + */ protected abstract int getWidgetType(); } diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..4337a5c 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -24,22 +24,67 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +/** + * NoteWidgetProvider_2x - 2x大小便签小部件提供者 + *+ * 处理2x大小的便签小部件更新、布局和背景资源 + * 继承自NoteWidgetProvider,实现了特定大小的小部件配置 + *
+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class NoteWidgetProvider_2x extends NoteWidgetProvider { + /** + * 更新小部件 + *+ * 当小部件需要更新时调用,委托给父类的update方法 + *
+ * + * @param context 上下文对象 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取布局ID + *+ * 返回2x小部件的布局资源ID + *
+ * + * @return 布局资源ID + */ @Override protected int getLayoutId() { return R.layout.widget_2x; } + /** + * 获取背景资源ID + *+ * 根据背景ID获取2x小部件的背景资源 + *
+ * + * @param bgId 背景ID + * @return 背景资源ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); } + /** + * 获取小部件类型 + *+ * 返回2x小部件的类型常量 + *
+ * + * @return 小部件类型 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_2X; diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..3bb7835 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -24,21 +24,67 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +/** + * NoteWidgetProvider_4x - 4x大小便签小部件提供者 + *+ * 处理4x大小的便签小部件更新、布局和背景资源 + * 继承自NoteWidgetProvider,实现了特定大小的小部件配置 + *
+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class NoteWidgetProvider_4x extends NoteWidgetProvider { + /** + * 更新小部件 + *+ * 当小部件需要更新时调用,委托给父类的update方法 + *
+ * + * @param context 上下文对象 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取布局ID + *+ * 返回4x小部件的布局资源ID + *
+ * + * @return 布局资源ID + */ + @Override protected int getLayoutId() { return R.layout.widget_4x; } + /** + * 获取背景资源ID + *+ * 根据背景ID获取4x小部件的背景资源 + *
+ * + * @param bgId 背景ID + * @return 背景资源ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + /** + * 获取小部件类型 + *+ * 返回4x小部件的类型常量 + *
+ * + * @return 小部件类型 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X;