diff --git a/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..67e224d 100644 --- a/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -39,12 +39,15 @@ 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; // 摘要预览最大长度 + private MediaPlayer mPlayer; // 媒体播放器,用于播放提醒声音 @Override protected void onCreate(Bundle savedInstanceState) { @@ -52,8 +55,10 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD 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 @@ -61,20 +66,23 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); } + // 从Intent中获取便签ID和摘要 Intent intent = getIntent(); - try { 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; } catch (IllegalArgumentException e) { e.printStackTrace(); + finish(); // 解析失败则结束活动 return; } mPlayer = new MediaPlayer(); + // 检查便签是否可见,可见则显示提醒对话框并播放声音 if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { showActionDialog(); playAlarmSound(); @@ -83,55 +91,67 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 检查屏幕是否开启 + * @return 屏幕开启返回true,否则返回false + */ 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.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 + } catch (Exception 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.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 点击的按钮标识 + */ 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); @@ -143,11 +163,18 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + /** + * 对话框消失回调 + * @param dialog 对话框实例 + */ public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); + stopAlarmSound(); // 停止播放提醒声音 + finish(); // 结束活动 } + /** + * 停止播放提醒声音 + */ private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); @@ -155,4 +182,4 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD mPlayer = null; } } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..be90fba 100644 --- a/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -27,39 +27,52 @@ import android.database.Cursor; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 闹钟初始化广播接收器 + * 用于在系统启动或相关事件触发时初始化便签的提醒闹钟 + */ public class AlarmInitReceiver extends BroadcastReceiver { - private static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE + // 数据库查询投影,获取便签ID和提醒时间 + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE }; - private static final int COLUMN_ID = 0; - private static final int COLUMN_ALERTED_DATE = 1; + // 投影列索引 + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; @Override public void onReceive(Context context, Intent intent) { - long currentDate = System.currentTimeMillis(); + long currentDate = System.currentTimeMillis(); // 获取当前时间 + + // 查询所有设置了提醒时间且未过期的普通便签 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, - new String[] { String.valueOf(currentDate) }, + new String[]{String.valueOf(currentDate)}, null); if (c != null) { if (c.moveToFirst()) { do { - long alertDate = c.getLong(COLUMN_ALERTED_DATE); + long alertDate = c.getLong(COLUMN_ALERTED_DATE); // 获取便签提醒时间 + + // 创建指向AlarmReceiver的Intent,并附加便签ID 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 + + // 获取AlarmManager实例并设置提醒闹钟 + AlarmManager alarmManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); - alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); } while (c.moveToNext()); } c.close(); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java b/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java index 54e503b..da25692 100644 --- a/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -20,11 +20,17 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +/** + * 闹钟提醒广播接收器 + * 当便签提醒时间到达时触发,启动闹钟提醒活动 + */ public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + // 创建并启动闹钟提醒活动 intent.setClass(context, AlarmAlertActivity.class); + // 添加新任务标志,确保在新任务中启动活动 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..e78ba27 100644 --- a/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -19,7 +19,6 @@ package net.micode.notes.ui; import java.util.Calendar; import net.micode.notes.R; -import net.micode.notes.ui.DateTimePicker; import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; import android.app.AlertDialog; @@ -29,24 +28,45 @@ 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(); // 存储选择的日期时间 + private boolean mIs24HourView; // 是否使用24小时制 + private OnDateTimeSetListener mOnDateTimeSetListener; // 选择完成后的回调接口 + private DateTimePicker mDateTimePicker; // 日期时间选择器控件 - private Calendar mDate = Calendar.getInstance(); - 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) { + int dayOfMonth, int hourOfDay, int minute) { + // 更新日历对象并刷新标题 mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); @@ -55,36 +75,59 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener 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); + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener) null); + + // 设置时间显示格式(24小时制或12小时制) set24HourView(DateFormat.is24HourFormat(this.getContext())); updateTitle(mDate.getTimeInMillis()); } + /** + * 设置时间显示格式 + * @param is24HourView true表示24小时制,false表示12小时制 + */ 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 = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + /** + * 对话框按钮点击回调 + * @param arg0 对话框实例 + * @param arg1 点击的按钮标识 + */ public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { + // 触发日期时间设置完成回调 mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); } } - } \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/DropdownMenu.java b/app/src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..6d11111 100644 --- a/app/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/app/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -27,17 +27,31 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +/** + * 下拉菜单工具类 + * 封装PopupMenu实现下拉菜单功能,可绑定到按钮并设置菜单项点击事件 + */ public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; + 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); + // 创建PopupMenu实例并关联按钮 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,17 +59,30 @@ public class DropdownMenu { }); } + /** + * 设置下拉菜单项点击监听器 + * @param listener 菜单项点击监听器 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 根据ID查找菜单项 + * @param id 菜单项ID + * @return 对应的菜单项,未找到返回null + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置按钮标题 + * @param title 标题文本 + */ public void setTitle(CharSequence title) { mButton.setText(title); } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..b7ab79d 100644 --- a/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -28,53 +28,95 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 文件夹列表适配器 + * 用于将文件夹数据绑定到列表视图,支持文件夹名称的显示和自定义 + */ public class FoldersListAdapter extends CursorAdapter { - public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET + // 数据库查询投影,获取文件夹ID和名称 + public static final String[] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET }; + // 投影列索引 public static final int ID_COLUMN = 0; public static final int NAME_COLUMN = 1; + /** + * 构造函数 + * @param context 上下文 + * @param c 包含文件夹数据的Cursor + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); - // TODO Auto-generated constructor stub } + /** + * 创建列表项视图 + * @param context 上下文 + * @param cursor 数据Cursor + * @param parent 父视图组 + * @return 新创建的FolderListItem视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 绑定数据到视图 + * @param view 列表项视图 + * @param context 上下文 + * @param cursor 数据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); } + /** + * 文件夹列表项视图 + * 自定义LinearLayout,包含文件夹名称文本显示 + */ private class FolderListItem extends LinearLayout { - private TextView mName; + 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); } } - -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 2afe2a8..b31775d 100644 --- a/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -37,15 +37,21 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * 笔记编辑文本框 + * 扩展自EditText,提供增强的编辑功能和链接处理能力 + */ public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; - private int mIndex; - private int mSelectionStartBeforeDelete; + private int mIndex; // 文本框在列表中的索引 + private int mSelectionStartBeforeDelete; // 删除操作前的光标位置 + // 支持的链接协议 private static final String SCHEME_TEL = "tel:" ; private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_EMAIL = "mailto:" ; + // 链接协议与菜单资源ID映射 private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); @@ -54,56 +60,89 @@ public class NoteEditText extends EditText { } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 文本框变化监听器接口 + * 用于与父Activity通信,处理文本框的添加、删除和文本变化 */ public interface OnTextViewChangeListener { /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null + * 当按下删除键且文本为空时,删除当前文本框 + * @param index 当前文本框索引 + * @param text 当前文本内容 */ void onEditTextDelete(int index, String text); /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen + * 当按下回车键时,在当前文本框后添加新文本框 + * @param index 新文本框的索引 + * @param text 剩余未处理的文本内容 */ void onEditTextEnter(int index, String text); /** - * Hide or show item option when text change + * 当文本变化时,隐藏或显示选项 + * @param index 当前文本框索引 + * @param hasText 是否有文本内容 */ void onTextChange(int index, boolean hasText); } - private OnTextViewChangeListener mOnTextViewChangeListener; + private OnTextViewChangeListener mOnTextViewChangeListener; // 监听器实例 + /** + * 构造函数 + * @param context 上下文 + */ public NoteEditText(Context context) { super(context, null); mIndex = 0; } + /** + * 设置文本框索引 + * @param index 索引值 + */ public void setIndex(int index) { mIndex = index; } + /** + * 设置文本框变化监听器 + * @param listener 监听器实例 + */ public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + /** + * 构造函数 + * @param context 上下文 + * @param attrs 属性集 + */ public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + /** + * 构造函数 + * @param context 上下文 + * @param attrs 属性集 + * @param defStyle 默认样式 + */ public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO Auto-generated constructor stub } + /** + * 处理触摸事件 + * 确保点击时正确设置光标位置 + * @param event 触摸事件 + * @return 是否处理该事件 + */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - + // 计算点击位置对应的文本偏移量并设置光标 int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); @@ -121,15 +160,23 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + /** + * 处理按键按下事件 + * @param keyCode 按键码 + * @param event 按键事件 + * @return 是否处理该事件 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_ENTER: + // 回车键由onKeyUp处理 if (mOnTextViewChangeListener != null) { return false; } break; case KeyEvent.KEYCODE_DEL: + // 记录删除前的光标位置 mSelectionStartBeforeDelete = getSelectionStart(); break; default: @@ -138,12 +185,20 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + /** + * 处理按键释放事件 + * @param keyCode 按键码 + * @param event 按键事件 + * @return 是否处理该事件 + */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { case KeyEvent.KEYCODE_DEL: + // 处理删除操作 if (mOnTextViewChangeListener != null) { if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + // 光标在文本开头且不是第一个文本框时,触发删除 mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); return true; } @@ -152,10 +207,12 @@ public class NoteEditText extends EditText { } break; case KeyEvent.KEYCODE_ENTER: + // 处理回车键 if (mOnTextViewChangeListener != null) { int selectionStart = getSelectionStart(); String text = getText().subSequence(selectionStart, length()).toString(); setText(getText().subSequence(0, selectionStart)); + // 在当前位置后插入新文本框 mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); } else { Log.d(TAG, "OnTextViewChangeListener was not seted"); @@ -167,18 +224,31 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + /** + * 焦点变化回调 + * @param focused 是否获得焦点 + * @param direction 焦点移动方向 + * @param previouslyFocusedRect 之前焦点的矩形区域 + */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { if (!focused && TextUtils.isEmpty(getText())) { + // 失去焦点且文本为空时 mOnTextViewChangeListener.onTextChange(mIndex, false); } else { + // 有焦点或有文本时 mOnTextViewChangeListener.onTextChange(mIndex, true); } } super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单 + * 处理链接长按的上下文菜单 + * @param menu 上下文菜单 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { @@ -188,8 +258,10 @@ public class NoteEditText extends EditText { int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); + // 获取选中区域的URLSpan final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { + // 根据链接协议设置菜单项 int defaultResId = 0; for(String schema: sSchemaActionResMap.keySet()) { if(urls[0].getURL().indexOf(schema) >= 0) { @@ -202,10 +274,11 @@ public class NoteEditText extends EditText { defaultResId = R.string.note_link_other; } + // 添加菜单项并设置点击事件 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // goto a new intent + // 调用URLSpan的点击处理 urls[0].onClick(NoteEditText.this); return true; } @@ -214,4 +287,4 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/app/src/main/java/net/micode/notes/ui/NoteItemData.java index 0f5a878..c1953d1 100644 --- a/app/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/app/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -25,58 +25,71 @@ 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, + // 数据库查询投影,获取便签的各项属性 + static final String[] PROJECTION = new String[]{ + NoteColumns.ID, // 便签ID + NoteColumns.ALERTED_DATE, // 提醒日期 + NoteColumns.BG_COLOR_ID, // 背景颜色ID + NoteColumns.CREATED_DATE, // 创建日期 + NoteColumns.HAS_ATTACHMENT, // 是否有附件 + NoteColumns.MODIFIED_DATE, // 修改日期 + NoteColumns.NOTES_COUNT, // 子便签数量(用于文件夹) + NoteColumns.PARENT_ID, // 父文件夹ID + NoteColumns.SNIPPET, // 便签摘要 + NoteColumns.TYPE, // 便签类型 + NoteColumns.WIDGET_ID, // 小部件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; - + // 投影列索引 + 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; // 便签ID + private long mAlertDate; // 提醒日期 + private int mBgColorId; // 背景颜色ID + private long mCreatedDate; // 创建日期 + private boolean mHasAttachment; // 是否有附件 + private long mModifiedDate; // 修改日期 + private int mNotesCount; // 子便签数量(用于文件夹) + private long mParentId; // 父文件夹ID + private String mSnippet; // 便签摘要 + private int mType; // 便签类型 + private int mWidgetId; // 小部件ID + private int mWidgetType; // 小部件类型 + private String mName; // 联系人名称(用于通话记录便签) + private String mPhoneNumber; // 电话号码(用于通话记录便签) + + // 列表位置信息 + private boolean mIsLastItem; // 是否为列表最后一项 + private boolean mIsFirstItem; // 是否为列表第一项 + private boolean mIsOnlyOneItem; // 是否为列表唯一一项 + private boolean mIsOneNoteFollowingFolder; // 是否为文件夹后的单个便签 + private boolean mIsMultiNotesFollowingFolder; // 是否为文件夹后的多个便签 + + /** + * 构造函数 + * @param context 上下文 + * @param cursor 包含便签数据的Cursor + */ public NoteItemData(Context context, Cursor cursor) { + // 从Cursor中获取便签基本信息 mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); @@ -86,6 +99,7 @@ public class NoteItemData { 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); @@ -93,6 +107,7 @@ public class NoteItemData { mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mPhoneNumber = ""; + // 处理通话记录便签,获取电话号码和联系人名称 if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) { @@ -106,9 +121,14 @@ public class NoteItemData { if (mName == null) { mName = ""; } + // 检查便签在列表中的位置信息 checkPostion(cursor); } + /** + * 检查便签在列表中的位置 + * @param cursor 数据Cursor + */ private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; @@ -116,9 +136,11 @@ public class NoteItemData { 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) { if (cursor.getCount() > (position + 1)) { @@ -127,6 +149,7 @@ public class NoteItemData { mIsOneNoteFollowingFolder = true; } } + // 恢复Cursor位置 if (!cursor.moveToNext()) { throw new IllegalStateException("cursor move to previous but can't move back"); } @@ -134,91 +157,180 @@ public class NoteItemData { } } + /** + * 判断是否为文件夹后的单个便签 + * @return true表示是文件夹后的单个便签 + */ public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } + /** + * 判断是否为文件夹后的多个便签 + * @return true表示是文件夹后的多个便签 + */ public boolean isMultiFollowingFolder() { return mIsMultiNotesFollowingFolder; } + /** + * 判断是否为列表最后一项 + * @return true表示是列表最后一项 + */ public boolean isLast() { return mIsLastItem; } + /** + * 获取通话记录便签的联系人名称 + * @return 联系人名称或电话号码 + */ public String getCallName() { return mName; } + /** + * 判断是否为列表第一项 + * @return true表示是列表第一项 + */ public boolean isFirst() { return mIsFirstItem; } + /** + * 判断是否为列表唯一一项 + * @return true表示是列表唯一一项 + */ public boolean isSingle() { return mIsOnlyOneItem; } + /** + * 获取便签ID + * @return 便签ID + */ public long getId() { return mId; } + /** + * 获取提醒日期 + * @return 提醒日期(毫秒值) + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取创建日期 + * @return 创建日期(毫秒值) + */ public long getCreatedDate() { return mCreatedDate; } + /** + * 判断是否有附件 + * @return true表示有附件 + */ public boolean hasAttachment() { return mHasAttachment; } + /** + * 获取修改日期 + * @return 修改日期(毫秒值) + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取背景颜色ID + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取父文件夹ID + * @return 父文件夹ID + */ public long getParentId() { return mParentId; } + /** + * 获取子便签数量(用于文件夹) + * @return 子便签数量 + */ public int getNotesCount() { return mNotesCount; } - public long getFolderId () { + /** + * 获取文件夹ID(与父文件夹ID相同) + * @return 文件夹ID + */ + public long getFolderId() { return mParentId; } + /** + * 获取便签类型 + * @return 便签类型 + */ public int getType() { return mType; } + /** + * 获取小部件类型 + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 获取小部件ID + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取便签摘要 + * @return 便签摘要 + */ public String getSnippet() { return mSnippet; } + /** + * 判断是否设置了提醒 + * @return true表示设置了提醒 + */ public boolean hasAlert() { return (mAlertDate > 0); } + /** + * 判断是否为通话记录便签 + * @return true表示是通话记录便签 + */ public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } + /** + * 从Cursor中获取便签类型 + * @param cursor 数据Cursor + * @return 便签类型 + */ public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java index 0d67aba..3c0366c 100644 --- a/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -30,19 +30,30 @@ 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; // 是否为选择模式 + + /** + * 桌面小部件属性类 + * 存储小部件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(); @@ -50,38 +61,70 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } + /** + * 创建列表项视图 + * @param context 上下文 + * @param cursor 数据Cursor + * @param parent 父视图组 + * @return 新创建的NotesListItem视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } + /** + * 绑定数据到视图 + * @param view 列表项视图 + * @param context 上下文 + * @param cursor 数据Cursor + */ @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())); } } + /** + * 设置指定位置项的选中状态 + * @param position 位置索引 + * @param checked 选中状态 + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); - notifyDataSetChanged(); + notifyDataSetChanged(); // 通知适配器数据已更改 } + /** + * 判断是否处于选择模式 + * @return true表示处于选择模式 + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置选择模式 + * @param mode 选择模式状态 + */ public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); + mSelectedIndex.clear(); // 清除已选状态 mChoiceMode = mode; } + /** + * 全选或取消全选 + * @param checked 选中状态 + */ 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); } @@ -89,6 +132,10 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取选中项的ID集合 + * @return 选中项ID的HashSet + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -101,10 +148,13 @@ public class NotesListAdapter extends CursorAdapter { } } } - return itemSet; } + /** + * 获取选中的小部件属性集合 + * @return 小部件属性的HashSet + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -116,9 +166,6 @@ public class NotesListAdapter extends CursorAdapter { widget.widgetId = item.getWidgetId(); widget.widgetType = item.getWidgetType(); itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ } else { Log.e(TAG, "Invalid cursor"); return null; @@ -128,6 +175,10 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项的数量 + * @return 选中项数量 + */ public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -143,11 +194,20 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 判断是否全选 + * @return true表示全选 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 判断指定位置项是否选中 + * @param position 位置索引 + * @return true表示已选中 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,18 +215,28 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + /** + * 内容变化回调 + */ @Override protected void onContentChanged() { super.onContentChanged(); - calcNotesCount(); + calcNotesCount(); // 重新计算便签数量 } + /** + * 更换Cursor时回调 + * @param cursor 新Cursor + */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); - calcNotesCount(); + calcNotesCount(); // 重新计算便签数量 } + /** + * 计算便签项数量(仅统计便签类型) + */ private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { @@ -179,4 +249,4 @@ public class NotesListAdapter extends CursorAdapter { } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/app/src/main/java/net/micode/notes/ui/NotesListItem.java index 1221e80..74858eb 100644 --- a/app/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/app/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -29,18 +29,27 @@ 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; // 选择模式下的复选框 + /** + * 构造函数 + * @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 +57,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,6 +74,7 @@ public class NotesListItem extends LinearLayout { } mItemData = data; + // 处理通话记录文件夹的显示 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); @@ -64,27 +82,34 @@ public class NotesListItem extends LinearLayout { 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); - } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + } + // 处理通话记录便签的显示 + else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); - mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); + 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); } - } else { + } + // 处理普通便签和文件夹的显示 + 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())); + data.getNotesCount())); mAlert.setVisibility(View.GONE); } else { + // 便签显示摘要和提醒图标(如果有) mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); @@ -94,14 +119,22 @@ public class NotesListItem extends LinearLayout { } } } + // 显示相对时间(相对于当前时间) mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 设置便签项背景 setBackground(data); } + /** + * 设置便签项背景 + * 根据便签类型、位置和背景颜色ID设置不同的背景 + * @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,11 +145,16 @@ public class NotesListItem extends LinearLayout { setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } } else { + // 文件夹使用统一背景 setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /** + * 获取便签项数据模型 + * @return 便签项数据模型 + */ public NoteItemData getItemData() { return mItemData; } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..ce54a97 100644 --- a/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -47,53 +47,60 @@ 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; - + // 偏好设置相关常量 + 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; // 是否已添加新账户标记 + + /** + * 活动创建回调 + * @param icicle 保存的实例状态 + */ @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - /* using the app icon for navigation */ + // 设置ActionBar导航图标 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); } + /** + * 活动恢复回调 + */ @Override protected void onResume() { super.onResume(); - // 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) { @@ -113,17 +120,24 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 刷新界面 refreshUI(); } + /** + * 活动销毁回调 + */ @Override protected void onDestroy() { if (mReceiver != null) { - unregisterReceiver(mReceiver); + unregisterReceiver(mReceiver); // 取消注册广播接收器 } super.onDestroy(); } + /** + * 加载账户偏好设置 + */ private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -135,16 +149,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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(); } } else { Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); } return true; @@ -154,11 +167,14 @@ 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); - // set button state + // 设置按钮状态 if (GTaskSyncService.isSyncing()) { syncButton.setText(getString(R.string.preferences_button_sync_cancel)); syncButton.setOnClickListener(new View.OnClickListener() { @@ -176,7 +192,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - // set last sync time + // 设置上次同步时间 if (GTaskSyncService.isSyncing()) { lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setVisibility(View.VISIBLE); @@ -193,14 +209,21 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 刷新用户界面 + */ 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)); @@ -217,6 +240,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { mHasAddedAccount = false; if (accounts.length > 0) { + // 设置账户列表选项 CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; int checkedItem = -1; @@ -237,6 +261,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 添加"添加账户"视图 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); @@ -244,9 +269,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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" + "gmail-ls" }); startActivityForResult(intent, -1); dialog.dismiss(); @@ -254,9 +280,13 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 显示更换账户确认对话框 + */ 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, @@ -265,6 +295,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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), getString(R.string.preferences_menu_remove_account), @@ -283,11 +314,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); @@ -299,10 +338,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up last sync time + // 清除上次同步时间 setLastSyncTime(this, 0); - // clean up local gtask related info + // 清理本地GTask相关信息(在后台线程执行) new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -318,6 +357,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 移除同步账户 + */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -329,7 +371,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up local gtask related info + // 清理本地GTask相关信息(在后台线程执行) new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -340,12 +382,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,29 +406,41 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + /** + * 获取上次同步时间 + * @param context 上下文 + * @return 上次同步时间戳(毫秒),若无则返回0 + */ 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 public void onReceive(Context context, Intent intent) { - refreshUI(); + 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)); } - } } + /** + * 选项菜单点击处理 + * @param item 点击的菜单项 + * @return 是否处理该点击 + */ 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); @@ -385,4 +449,4 @@ public class NotesPreferenceActivity extends PreferenceActivity { return false; } } -} +} \ No newline at end of file