From b9d371a241747f44acd4e395bf99d188337823b0 Mon Sep 17 00:00:00 2001 From: gy <2293314358@qq.com> Date: Fri, 30 Jan 2026 19:43:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86NotesListItemAdapter?= =?UTF-8?q?=E7=B1=BB=EF=BC=8C=E5=8D=8F=E5=90=8CNotesListAdapter=E8=B4=9F?= =?UTF-8?q?=E8=B4=A3=E5=B0=86=E7=AC=94=E8=AE=B0=E6=95=B0=E6=8D=AE=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E5=88=B0=E5=88=97=E8=A1=A8=E8=A7=86=E5=9B=BE=E3=80=82?= =?UTF-8?q?=20=E5=8F=AF=E9=92=88=E5=AF=B9=E7=89=B9=E5=AE=9A=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=AC=94=E8=AE=B0=E5=A4=84=E7=90=86=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E7=9A=84=E8=BE=85=E5=8A=A9=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=99=A8=E3=80=82=20=E4=BF=AE=E6=94=B9=E4=BA=86=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E7=B1=BB=E4=B8=AD=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=BE=BF=E7=AD=BE=E7=BD=AE=E9=A1=B6?= =?UTF-8?q?/=E5=8F=96=E6=B6=88=E7=BD=AE=E9=A1=B6=E3=80=81=E6=92=A4?= =?UTF-8?q?=E5=9B=9E/=E5=8F=96=E6=B6=88=E6=92=A4=E5=9B=9E=E3=80=81?= =?UTF-8?q?=E8=AE=BE=E4=B8=BA=E7=A7=81=E5=AF=86/=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E7=A7=81=E5=AF=86=E3=80=81=E5=AF=8C=E6=96=87=E6=9C=AC=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E3=80=81=E5=88=97=E8=A1=A8=E8=A7=86=E5=9B=BE=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=8A=9F=E8=83=BD=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../micode/notes/ui/AlarmAlertActivity.java | 41 +- .../net/micode/notes/ui/DateTimePicker.java | 67 +-- .../src/net/micode/notes/ui/DropdownMenu.java | 32 -- .../src/net/micode/notes/ui/NoteEditText.java | 96 +--- .../src/net/micode/notes/ui/NoteItemData.java | 35 +- .../micode/notes/ui/NotesListActivity.java | 504 +++++++++++++++--- .../net/micode/notes/ui/NotesListAdapter.java | 110 +--- .../net/micode/notes/ui/NotesListItem.java | 98 ++-- .../notes/ui/NotesPreferenceActivity.java | 202 +++---- 9 files changed, 590 insertions(+), 595 deletions(-) diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java index 4a78928..85723be 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -40,26 +40,13 @@ 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; @Override - /** - * 创建活动实例,初始化窗口参数、获取笔记信息并显示提醒 - * @param savedInstanceState 保存的实例状态 - */ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -96,19 +83,11 @@ 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 url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); @@ -126,20 +105,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD 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); @@ -151,11 +130,6 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD dialog.show().setOnDismissListener(this); } - /** - * 处理对话框按钮的点击事件 - * @param dialog 对话框对象 - * @param which 被点击的按钮ID - */ public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: @@ -169,18 +143,11 @@ 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/Notes-master/src/net/micode/notes/ui/DateTimePicker.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java index 2f4d9fa..496b0cd 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java @@ -28,11 +28,6 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; -/** - * 日期时间选择器控件,用于选择日期和时间 - * 该控件提供了日期、小时、分钟和AM/PM的选择功能,支持12小时制和24小时制显示, - * 并能根据用户的选择自动更新日期和时间。 - */ public class DateTimePicker extends FrameLayout { private static final boolean DEFAULT_ENABLE_STATE = true; @@ -163,72 +158,39 @@ public class DateTimePicker extends FrameLayout { } }; - /** - * 日期时间改变监听器接口,用于监听日期或时间的变化事件 - */ 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)); } - /** - * 构造方法,使用指定时间和时间格式初始化日期时间选择器 - * @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); - // 初始化上午/下午选择器 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); @@ -236,21 +198,19 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // 更新控件到初始状态 + // update controls to initial state updateDateControl(); updateHourControl(); updateAmPmControl(); - // 设置时间格式(12小时制或24小时制) set24HourView(is24HourView); - // 设置初始日期时间 + // set to current time setCurrentDate(date); - // 设置控件是否可用 setEnabled(isEnabled()); - // 设置内容描述 + // set the content descriptions mInitialising = false; } @@ -388,10 +348,6 @@ public class DateTimePicker extends FrameLayout { return mDate.get(Calendar.HOUR_OF_DAY); } - /** - * 获取当前小时(根据时间格式调整) - * @return 12小时制下返回1-12,24小时制下返回0-23 - */ private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); @@ -478,47 +434,30 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } - /** - * 更新日期选择器控件的显示内容 - * 显示当前日期前后一周的日期选项,格式为"MM.dd EEEE" - */ 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(); } - /** - * 更新上午/下午选择器控件的显示状态 - * 在24小时制下隐藏AM/PM选择器,在12小时制下显示并设置正确的选中状态 - */ private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); } else { - // 根据当前是上午还是下午设置选中状态 int index = mIsAm ? Calendar.AM : Calendar.PM; mAmPmSpinner.setValue(index); mAmPmSpinner.setVisibility(View.VISIBLE); } } - /** - * 更新小时选择器控件的显示范围 - * 在24小时制下显示0-23,在12小时制下显示1-12 - */ private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); diff --git a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java index 177b9b4..613dc74 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java +++ b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java @@ -27,36 +27,17 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; -/** - * 下拉菜单封装类,用于创建和管理带有下拉菜单的按钮 - * 该类封装了Android的PopupMenu功能,提供了一个简单的接口来创建带有下拉菜单的按钮, - * 点击按钮时会显示下拉菜单。 - */ 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(); @@ -64,29 +45,16 @@ 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); } diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java index e301941..2afe2a8 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java @@ -37,31 +37,16 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; -/** - * 自定义的便签编辑框 - * 扩展自EditText,支持链接检测与处理(电话、网页、邮件)、 - * 键盘事件监听(删除键、回车键)以及与NoteEditActivity的交互。 - */ public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; - - /** 当前编辑框在列表中的索引位置 */ 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(); - - /** 初始化链接协议与操作资源ID的映射关系 */ static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); @@ -69,8 +54,7 @@ public class NoteEditText extends EditText { } /** - * 编辑框变化监听器接口 - * 由{@link NoteEditActivity}调用,用于处理编辑框的删除、添加和文本变化事件。 + * Call by the {@link NoteEditActivity} to delete or add edit text */ public interface OnTextViewChangeListener { /** @@ -91,64 +75,35 @@ public class NoteEditText extends EditText { void onTextChange(int index, boolean hasText); } - /** 编辑框变化监听器实例 */ 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(); @@ -156,7 +111,6 @@ public class NoteEditText extends EditText { x += getScrollX(); y += getScrollY(); - // 根据坐标获取对应的行和偏移量,并设置光标位置 Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); @@ -167,24 +121,15 @@ 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: @@ -193,19 +138,10 @@ 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()); @@ -216,14 +152,10 @@ 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"); @@ -235,47 +167,29 @@ 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) { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); - // 获取选择区域的起始和结束位置 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) { - // 根据链接协议获取对应的操作资源ID int defaultResId = 0; for(String schema: sSchemaActionResMap.keySet()) { if(urls[0].getURL().indexOf(schema) >= 0) { @@ -284,16 +198,14 @@ public class NoteEditText extends EditText { } } - // 如果没有匹配的协议,则使用默认操作 if (defaultResId == 0) { defaultResId = R.string.note_link_other; } - // 添加上下文菜单项并设置点击事件 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // 执行链接点击操作 + // goto a new intent urls[0].onClick(NoteEditText.this); return true; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java index 2cfec51..cc35539 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java @@ -57,6 +57,8 @@ public class NoteItemData { private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_TYPE_COLUMN = 11; private static final int TITLE_COLUMN = 12; + // [新增] 定义列索引,对应 PROJECTION 中的顺序 (PROJECTION 的最后一个元素是 NoteColumns.IS_PINNED) + private static final int IS_PINNED_COLUMN = 13; private long mId; private long mAlertDate; @@ -73,6 +75,8 @@ public class NoteItemData { private String mName; private String mPhoneNumber; private String mTitle; + // [新增] 成员变量 + private boolean mIsPinned; private boolean mIsLastItem; private boolean mIsFirstItem; @@ -89,9 +93,27 @@ public class NoteItemData { mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); mParentId = cursor.getLong(PARENT_ID_COLUMN); - mSnippet = cursor.getString(SNIPPET_COLUMN); - mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( - NoteEditActivity.TAG_UNCHECKED, ""); + // [新增修复] 读取数据库内容后,立即剥离 HTML 标签,只保留纯文本用于列表展示 + String rawSnippet = cursor.getString(SNIPPET_COLUMN); + if (rawSnippet != null && rawSnippet.contains("<")) { + try { + // 将 HTML 转换为 Spanned,再 toString() 即为纯文本 + mSnippet = android.text.Html.fromHtml(rawSnippet).toString(); + } catch (Exception e) { + mSnippet = rawSnippet; + } + } else { + mSnippet = rawSnippet; + } + // 原有的清洗逻辑(去除清单符号)可以保留在后面 + if (mSnippet != null) { + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "") + .replace(NoteEditActivity.TAG_UNCHECKED, "") + .replace("✅", "") + .replace("⬜", "") + .trim(); // 去除首尾空格和换行 + } + mType = cursor.getInt(TYPE_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); @@ -118,6 +140,8 @@ public class NoteItemData { if (mName == null) { mName = ""; } + // [新增] 读取数据库中的置顶状态 (注意:checkPostion(cursor) 之前) + mIsPinned = cursor.getInt(IS_PINNED_COLUMN) > 0; checkPostion(cursor); } @@ -146,6 +170,11 @@ public class NoteItemData { } } + // [新增] Getter 方法 + public boolean isPinned() { + return mIsPinned; + } + public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index bf8d2b8..6eab673 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -61,6 +61,8 @@ import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; import android.app.Activity; +import android.net.Uri; // [新增] 修复错误 1 & 2 +import android.graphics.drawable.Drawable; // [新增] 修复错误 3 & 4 import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; @@ -102,7 +104,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; private enum ListEditState { - NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER, PRIVATE_FOLDER, RECYCLE_BIN }; private ListEditState mState; @@ -131,6 +133,10 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe private ModeCallback mModeCallBack; + private View mFolderNavScrollView; + + private androidx.swiperefreshlayout.widget.SwipeRefreshLayout mSwipeRefreshLayout; + private static final String TAG = "NotesListActivity"; public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; @@ -213,6 +219,103 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } } + // [新增] 进入回收站 + private void enterRecycleBin() { + mState = ListEditState.RECYCLE_BIN; + mCurrentFolderId = Notes.ID_TRASH_FOLER; // 关键:指向垃圾桶 ID (-3) + + mTitleBar.setVisibility(View.VISIBLE); + mTitleBar.setText(R.string.title_recycle_bin); + + // 1. 隐藏多余 UI + mAddNewNote.hide(); + if (mFolderNavScrollView != null) mFolderNavScrollView.setVisibility(View.GONE); + if (mSwipeRefreshLayout != null) mSwipeRefreshLayout.setEnabled(false); + + // 2. 强制切换为瀑布流 (Staggered Grid) + // 无论用户设置是什么,回收站强制瀑布流 + mRecyclerView.setLayoutManager(new androidx.recyclerview.widget.StaggeredGridLayoutManager( + 2, androidx.recyclerview.widget.StaggeredGridLayoutManager.VERTICAL)); + // 添加间距装饰器 (防止重复添加需先移除) + while (mRecyclerView.getItemDecorationCount() > 0) { + mRecyclerView.removeItemDecorationAt(0); + } + mRecyclerView.addItemDecoration(new WaterfallItemDecoration(20)); + mAdapter.notifyDataSetChanged(); + + // 3. 刷新菜单 (隐藏系统菜单) + invalidateOptionsMenu(); + + // 4. 查询数据 + startAsyncNotesListQuery(); + } + + @Override + public void onBackPressed() { + if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) { + getIntent().setAction(Intent.ACTION_MAIN); + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mAddNewNote.show(); + mTitleBar.setVisibility(View.GONE); + startAsyncNotesListQuery(); + return; + } + switch (mState) { + case SUB_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + startAsyncNotesListQuery(); + mTitleBar.setVisibility(View.GONE); + break; + case CALL_RECORD_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + mAddNewNote.show(); + mTitleBar.setVisibility(View.GONE); + startAsyncNotesListQuery(); + break; + case PRIVATE_FOLDER: + mState = ListEditState.NOTE_LIST; + mTitleBar.setVisibility(View.GONE); + // [修改] 恢复 FAB + mAddNewNote.show(); + // [新增] 恢复顶部文件夹导航栏 + if (mFolderNavScrollView != null) { + mFolderNavScrollView.setVisibility(View.VISIBLE); + } + // [修改] 恢复下拉刷新 + if (mSwipeRefreshLayout != null) { + mSwipeRefreshLayout.setEnabled(true); + } + // [新增] 刷新菜单栏(恢复显示) + invalidateOptionsMenu(); + + startAsyncNotesListQuery(); + break; + case NOTE_LIST: + super.onBackPressed(); + break; + case RECYCLE_BIN: // [新增] + mState = ListEditState.NOTE_LIST; + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mTitleBar.setVisibility(View.GONE); + + // 恢复 UI + mAddNewNote.show(); + if (mFolderNavScrollView != null) mFolderNavScrollView.setVisibility(View.VISIBLE); + if (mSwipeRefreshLayout != null) mSwipeRefreshLayout.setEnabled(true); + + // 恢复用户之前的视图设置 (列表或瀑布流) + updateViewMode(); + + invalidateOptionsMenu(); + startAsyncNotesListQuery(); + break; + default: + break; + } + } + @Override protected void onStart() { super.onStart(); @@ -223,9 +326,32 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe @Override protected void onResume() { super.onResume(); + applyCustomBackground(); updateViewMode(); } + private void applyCustomBackground() { + SharedPreferences settings = getSharedPreferences(NotesPreferenceActivity.PREFERENCE_NAME, Context.MODE_PRIVATE); + String uriString = settings.getString("custom_list_background_uri", ""); + View rootView = findViewById(R.id.note_list_root); + + if (rootView != null) { + if (!TextUtils.isEmpty(uriString)) { + try { + InputStream is = getContentResolver().openInputStream(Uri.parse(uriString)); + Drawable d = Drawable.createFromStream(is, uriString); + rootView.setBackground(d); + if (is != null) is.close(); + return; // 如果有自定义图,直接返回 + } catch (Exception e) { + // 加载失败则继续向下执行 fallback + } + } + + rootView.setBackgroundResource(R.drawable.list_background); + } + } + private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); @@ -233,6 +359,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mRecyclerView = findViewById(R.id.notes_list_recycler); mAdapter = new NotesListItemAdapter(this); mRecyclerView.setAdapter(mAdapter); + mFolderNavScrollView = findViewById(R.id.folder_nav_scroll); // 1. 绑定 Toolbar Toolbar toolbar = findViewById(R.id.toolbar); @@ -321,6 +448,87 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mTitleBar = (TextView) findViewById(R.id.tv_title_bar); mState = ListEditState.NOTE_LIST; mFolderNavContainer = (LinearLayout) findViewById(R.id.folder_nav_container); + + // [新增] 初始化下拉组件 + mSwipeRefreshLayout = findViewById(R.id.swipe_refresh_layout); + mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright, + android.R.color.holo_green_light, + android.R.color.holo_orange_light, + android.R.color.holo_red_light); + + mSwipeRefreshLayout.setOnRefreshListener(new androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + // 下拉触发,直接尝试进入私密模式 + showPasswordDialog(); + } + }); + } + + private void showPasswordDialog() { + final EditText et = new EditText(this); + et.setInputType(android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD); + et.setHint("1234"); // 提示当前硬编码密码 + + AlertDialog dialog = new AlertDialog.Builder(this) + .setTitle(R.string.input_password) + .setView(et) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String pwd = et.getText().toString(); + if ("1234".equals(pwd)) { + enterPrivateFolder(); + } else { + Toast.makeText(NotesListActivity.this, R.string.password_error, Toast.LENGTH_SHORT).show(); + } + // 无论成功失败,停止刷新动画 + mSwipeRefreshLayout.setRefreshing(false); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mSwipeRefreshLayout.setRefreshing(false); + } + }) + .create(); + + // 监听取消事件,防止动画一直转 + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + mSwipeRefreshLayout.setRefreshing(false); + } + }); + dialog.show(); + } + + // [新增] 进入私密模式逻辑 + private void enterPrivateFolder() { + mState = ListEditState.PRIVATE_FOLDER; + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + + mTitleBar.setVisibility(View.VISIBLE); + mTitleBar.setText(R.string.title_private_folder); + + // [修改] 隐藏 FAB + mAddNewNote.hide(); + + // [新增] 隐藏顶部文件夹导航栏 + if (mFolderNavScrollView != null) { + mFolderNavScrollView.setVisibility(View.GONE); + } + + // [修改] 禁用下拉刷新 + if (mSwipeRefreshLayout != null) { + mSwipeRefreshLayout.setEnabled(false); + } + + // [新增] 刷新菜单栏(触发 onPrepareOptionsMenu 以隐藏菜单) + invalidateOptionsMenu(); + + startAsyncNotesListQuery(); } private void updateViewMode() { @@ -372,7 +580,8 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe } @Override - public void getItemOffsets(Rect outRect, View view, androidx.recyclerview.widget.RecyclerView parent, androidx.recyclerview.widget.RecyclerView.State state) { + public void getItemOffsets(Rect outRect, View view, androidx.recyclerview.widget.RecyclerView parent, + androidx.recyclerview.widget.RecyclerView.State state) { // 获取当前条目的布局参数 androidx.recyclerview.widget.StaggeredGridLayoutManager.LayoutParams lp = (androidx.recyclerview.widget.StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams(); @@ -403,21 +612,62 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.note_list_options, menu); - menu.findItem(R.id.delete).setOnMenuItemClickListener(this); - menu.findItem(R.id.menu_rename).setOnMenuItemClickListener(this); + + // 获取所有菜单项的引用 + MenuItem deleteItem = menu.findItem(R.id.delete); + MenuItem restoreItem = menu.findItem(R.id.menu_restore); + MenuItem renameItem = menu.findItem(R.id.menu_rename); + MenuItem pinItem = menu.findItem(R.id.menu_pin); + MenuItem unpinItem = menu.findItem(R.id.menu_unpin); + MenuItem privateItem = menu.findItem(R.id.menu_private); mMoveMenu = menu.findItem(R.id.move); - // [Fix Start] 增加 null 检查 - if (mFocusNoteDataItem == null || mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER - || DataUtils.getUserFolderCount(mContentResolver) == 0) { - mMoveMenu.setVisible(false); + + // 统一绑定监听器 + if (deleteItem != null) deleteItem.setOnMenuItemClickListener(this); + if (restoreItem != null) restoreItem.setOnMenuItemClickListener(this); + if (renameItem != null) renameItem.setOnMenuItemClickListener(this); + if (pinItem != null) pinItem.setOnMenuItemClickListener(this); + if (unpinItem != null) unpinItem.setOnMenuItemClickListener(this); + if (privateItem != null) privateItem.setOnMenuItemClickListener(this); + if (mMoveMenu != null) mMoveMenu.setOnMenuItemClickListener(this); + + // [新增] 根据状态控制菜单显隐 + if (mState == ListEditState.RECYCLE_BIN) { + // === 回收站模式 === + // 显示:还原、删除(彻底删除) + if (restoreItem != null) restoreItem.setVisible(true); + if (deleteItem != null) deleteItem.setVisible(true); + + // 隐藏:移动、置顶、私密、重命名 + if (mMoveMenu != null) mMoveMenu.setVisible(false); + if (renameItem != null) renameItem.setVisible(false); + if (pinItem != null) pinItem.setVisible(false); + if (unpinItem != null) unpinItem.setVisible(false); + if (privateItem != null) privateItem.setVisible(false); + } else { - mMoveMenu.setVisible(true); - mMoveMenu.setOnMenuItemClickListener(this); + // === 普通模式 / 私密模式 === + // 隐藏:还原 + if (restoreItem != null) restoreItem.setVisible(false); + + // 处理 Move 菜单的特殊逻辑 + if (mMoveMenu != null) { + if (mState == ListEditState.PRIVATE_FOLDER) { + // 私密模式强制显示移动(用于移出) + mMoveMenu.setVisible(true); + } else if (mFocusNoteDataItem == null || mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER + || DataUtils.getUserFolderCount(mContentResolver) == 0) { + mMoveMenu.setVisible(false); + } else { + mMoveMenu.setVisible(true); + } + } + + // Pin/Unpin/Private/Rename 的显隐会在 updateMenu() 中进一步动态刷新,这里保持默认即可 } - // [Fix End] + mActionMode = mode; mAdapter.setChoiceMode(true); - // mRecyclerView.setLongClickable(false); // RecyclerView doesn't have this, Adapter handles it mAddNewNote.hide(); View customView = LayoutInflater.from(NotesListActivity.this).inflate( @@ -426,13 +676,12 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe mDropDownMenu = new DropdownMenu(NotesListActivity.this, (Button) customView.findViewById(R.id.selection_menu), R.menu.note_list_dropdown); - mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ + mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { mAdapter.selectAll(!mAdapter.isAllSelected()); updateMenu(); return true; } - }); return true; } @@ -455,9 +704,22 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe // Update ActionMode menu if (mActionMode != null) { Menu menu = mActionMode.getMenu(); - MenuItem renameItem = menu.findItem(R.id.menu_rename); - if (renameItem != null) { - renameItem.setVisible(selectedCount == 1); + // [检查] 这里的 ID 必须和 XML 里的 ID 完全一致 + MenuItem pinItem = menu.findItem(R.id.menu_pin); + MenuItem unpinItem = menu.findItem(R.id.menu_unpin); // 确保 XML 里叫 menu_unpin + + if (pinItem != null && unpinItem != null) { + boolean isAllPinned = mAdapter.isAllSelectedItemsPinned(); + // [Debug] 打印最终判断结果 + android.util.Log.e("NoteDebug", "UI Logic: isAllPinned = " + isAllPinned); + + if (isAllPinned) { + pinItem.setVisible(false); + unpinItem.setVisible(true); + } else { + pinItem.setVisible(true); + unpinItem.setVisible(false); + } } } } @@ -495,34 +757,125 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe return true; } + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + switch (item.getItemId()) { case R.id.delete: - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_notes, - mAdapter.getSelectedCount())); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - batchDelete(); - } - }); + + // [修改] 区分回收站模式和普通模式 + if (mState == ListEditState.RECYCLE_BIN) { + // 回收站内 -> 彻底删除 + builder.setMessage(getString(R.string.alert_delete_forever)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + batchDelete(); // 执行物理删除 + } + }); + } else { + // 普通模式 -> 移入回收站 (软删除) + builder.setMessage(getString(R.string.alert_message_delete_notes, + mAdapter.getSelectedCount())); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + batchMoveToTrash(); // [调用新写的移入回收站方法] + } + }); + } builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; + + case R.id.menu_restore: + // [新增] 还原操作 + batchRestore(); + break; + case R.id.move: startQueryDestinationFolders(); break; + case R.id.menu_rename: showRenameDialog(); break; + + case R.id.menu_pin: + updateNoteStatus(NoteColumns.IS_PINNED, 1); + break; + + case R.id.menu_unpin: + updateNoteStatus(NoteColumns.IS_PINNED, 0); + break; + + case R.id.menu_private: + builder.setTitle(getString(R.string.menu_private)); + builder.setMessage(getString(R.string.alert_move_to_private)); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + updateNoteStatus(NoteColumns.IS_PRIVATE, 1); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + default: return false; } return true; } + + // [新增] 移入回收站 + private void batchMoveToTrash() { + new AsyncTask() { + protected Void doInBackground(Void... unused) { + // 调用 DataUtils 新增的方法 + DataUtils.batchMoveToTrash(mContentResolver, mAdapter.getSelectedItemIds()); + return null; + } + @Override + protected void onPostExecute(Void v) { + mModeCallBack.finishActionMode(); + startAsyncNotesListQuery(); // 刷新 + } + }.execute(); + } + + // [新增] 从回收站还原 + private void batchRestore() { + new AsyncTask() { + protected Void doInBackground(Void... unused) { + DataUtils.batchRestoreFromTrash(mContentResolver, mAdapter.getSelectedItemIds()); + return null; + } + @Override + protected void onPostExecute(Void v) { + mModeCallBack.finishActionMode(); + startAsyncNotesListQuery(); // 刷新 + } + }.execute(); + } + + // [新增] 辅助方法:批量更新状态 + private void updateNoteStatus(final String column, final int value) { + new AsyncTask() { + protected Void doInBackground(Void... unused) { + DataUtils.batchUpdateField(mContentResolver, mAdapter.getSelectedItemIds(), column, value); + return null; + } + + @Override + protected void onPostExecute(Void v) { + mModeCallBack.finishActionMode(); + // [关键新增] 操作完成后,强制重新查询数据库以刷新列表排序 + startAsyncNotesListQuery(); + } + }.execute(); + } } private void showRenameDialog() { @@ -534,22 +887,29 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe final long noteId = ids.iterator().next(); final EditText et = new EditText(this); - + new AlertDialog.Builder(this) - .setTitle("重命名便签标题") + .setTitle("重命名便签标题") // 建议后续提取到 strings.xml .setView(et) - .setPositiveButton("确定", new DialogInterface.OnClickListener() { + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String newTitle = et.getText().toString(); ContentValues values = new ContentValues(); values.put(NoteColumns.TITLE, newTitle); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, - NoteColumns.ID + "=?", new String[]{String.valueOf(noteId)}); - mModeCallBack.finishActionMode(); // 退出选择模式 + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 建议同时更新修改时间或标记 + + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, + NoteColumns.ID + "=?", new String[]{String.valueOf(noteId)}); + + mModeCallBack.finishActionMode(); + + // [Fix Start] 新增:重命名后立即刷新列表 + startAsyncNotesListQuery(); + // [Fix End] } }) - .setNegativeButton("取消", null) + .setNegativeButton(android.R.string.cancel, null) .show(); } @@ -572,17 +932,25 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe selectionArgs = new String[] { "%" + query + "%" }; + } else if (mState == ListEditState.PRIVATE_FOLDER) { + // [新增] 私密模式查询:查询所有 is_private = 1 的便签 + selection = NoteColumns.IS_PRIVATE + "=1 AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + selectionArgs = null; } else { + // [修改] 普通模式:必须增加 is_private = 0 的条件,防止私密便签显示在普通列表中 selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; + // 拼接过滤私密的条件 + selection += " AND " + NoteColumns.IS_PRIVATE + "=0"; + selectionArgs = new String[] { - String.valueOf(mCurrentFolderId) + String.valueOf(mCurrentFolderId) }; } mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, selectionArgs, - NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + NoteColumns.TYPE + " DESC," + NoteColumns.IS_PINNED + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } private final class BackgroundQueryHandler extends AsyncQueryHandler { @@ -626,8 +994,16 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { + // 1. 执行移动操作 (更新 parent_id) DataUtils.batchMoveToFolder(mContentResolver, mAdapter.getSelectedItemIds(), adapter.getItemId(which)); + + // [新增] 2. 如果当前是私密模式,移动意味着"移出",必须解除私密状态 (is_private = 0) + if (mState == ListEditState.PRIVATE_FOLDER) { + DataUtils.batchUpdateField(mContentResolver, + mAdapter.getSelectedItemIds(), NoteColumns.IS_PRIVATE, 0); + } + Toast.makeText( NotesListActivity.this, getString(R.string.format_move_notes_to_folder, @@ -635,6 +1011,9 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe adapter.getFolderName(NotesListActivity.this, which)), Toast.LENGTH_SHORT).show(); mModeCallBack.finishActionMode(); + + // [关键] 强制刷新列表,否则移走的便签还会残留在界面上 + startAsyncNotesListQuery(); } }); builder.show(); @@ -802,7 +1181,7 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe values.put(NoteColumns.LOCAL_MODIFIED, 1); mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) + String.valueOf(mFocusNoteDataItem.getId()) }); } } else if (!TextUtils.isEmpty(name)) { @@ -811,6 +1190,14 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); } + // [Fix Start] 关键修复:刷新数据 + // 1. 刷新下方的列表 (虽然文件夹不在列表里显示,但保持数据一致性) + startAsyncNotesListQuery(); + + // 2. [新增] 刷新顶部的文件夹导航栏 Tab + rebuildFolderNavigation(); + // [Fix End] + dialog.dismiss(); } }); @@ -842,37 +1229,6 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe }); } - @Override - public void onBackPressed() { - if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) { - getIntent().setAction(Intent.ACTION_MAIN); - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mAddNewNote.show(); - mTitleBar.setVisibility(View.GONE); - startAsyncNotesListQuery(); - return; - } - switch (mState) { - case SUB_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - startAsyncNotesListQuery(); - mTitleBar.setVisibility(View.GONE); - break; - case CALL_RECORD_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - mAddNewNote.show(); - mTitleBar.setVisibility(View.GONE); - startAsyncNotesListQuery(); - break; - case NOTE_LIST: - super.onBackPressed(); - break; - default: - break; - } - } private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); @@ -959,6 +1315,11 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe @Override public boolean onPrepareOptionsMenu(Menu menu) { + // [修改] 私密模式 OR 回收站模式,都清空菜单 + if (mState == ListEditState.PRIVATE_FOLDER || mState == ListEditState.RECYCLE_BIN) { + menu.clear(); + return true; + } // 移除之前的 menu.clear(),因为这会清空 onCreate 加载的内容 // 仅保留原有的动态逻辑,例如同步按钮的状态切换 if (mState == ListEditState.NOTE_LIST) { @@ -1012,9 +1373,14 @@ public class NotesListActivity extends AppCompatActivity implements OnClickListe createNewNote(); break; } - case R.id.menu_search: + case R.id.menu_search: { onSearchRequested(); break; + } + case R.id.menu_recycle_bin: { + enterRecycleBin(); + break; + } default: break; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java index e5239ba..51c9cb9 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java @@ -31,39 +31,18 @@ import java.util.HashSet; import java.util.Iterator; -/** - * NotesListAdapter - 便签列表适配器 - * 继承自CursorAdapter,负责为便签列表提供数据绑定和视图管理功能。 - * 支持多选模式、便签数量统计、选中项目管理以及应用小部件属性处理。 - * - * 主要功能: - * - 创建和绑定NotesListItem视图 - * - 管理多选模式下的选中状态 - * - 统计普通便签的数量 - * - 获取选中项目的ID集合 - * - 处理应用小部件相关的属性 - */ public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; // 日志标签 - private Context mContext; // 上下文环境 - private HashMap mSelectedIndex; // 记录选中项目的位置映射 - private int mNotesCount; // 普通便签的数量 - private boolean mChoiceMode; // 是否处于多选模式 + private static final String TAG = "NotesListAdapter"; + private Context mContext; + private HashMap mSelectedIndex; + private int mNotesCount; + private boolean mChoiceMode; - /** - * AppWidgetAttribute - 应用小部件属性类 - * 用于存储应用小部件的ID和类型信息,便于在多选操作中管理相关小部件。 - */ public static class AppWidgetAttribute { - public int widgetId; // 小部件ID - public int widgetType; // 小部件类型 + public int widgetId; + public int widgetType; }; - /** - * 构造方法 - 创建便签列表适配器 - * 初始化适配器,设置上下文环境和选中项目的映射表。 - * @param context 上下文环境 - */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); @@ -71,26 +50,11 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } - /** - * 创建新的视图 - 生成NotesListItem实例 - * 当列表需要显示新的项目时调用此方法,创建一个新的NotesListItem视图。 - * @param context 上下文环境 - * @param cursor 当前位置的游标 - * @param parent 父视图组 - * @return 创建的新视图 - */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } - /** - * 绑定视图 - 将游标数据绑定到NotesListItem - * 将当前游标的数据转换为NoteItemData对象,并绑定到NotesListItem视图中。 - * @param view 要绑定的视图 - * @param context 上下文环境 - * @param cursor 包含数据的游标 - */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -100,41 +64,20 @@ public class NotesListAdapter extends CursorAdapter { } } - /** - * 设置选中项目 - 更新指定位置的选中状态 - * 更新指定位置的选中状态,并通知数据集合变化。 - * @param position 项目位置 - * @param checked 选中状态 - */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); notifyDataSetChanged(); } - /** - * 检查是否处于多选模式 - * 返回当前适配器是否处于多选模式。 - * @return true表示处于多选模式,false表示不处于多选模式 - */ public boolean isInChoiceMode() { return mChoiceMode; } - /** - * 设置多选模式 - 开启或关闭多选模式 - * 设置适配器的多选模式状态,并清空已选项目的映射表。 - * @param mode true表示开启多选模式,false表示关闭多选模式 - */ public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } - /** - * 全选/取消全选 - 选择或取消选择所有普通便签 - * 遍历所有项目,选择或取消选择所有类型为普通便签的项目。 - * @param checked true表示全选,false表示取消全选 - */ public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { @@ -146,11 +89,6 @@ public class NotesListAdapter extends CursorAdapter { } } - /** - * 获取选中项目的ID集合 - * 返回所有选中项目的ID集合,排除根文件夹ID。 - * @return 选中项目的ID集合 - */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -167,11 +105,6 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } - /** - * 获取选中项目的应用小部件属性 - * 返回所有选中项目的应用小部件属性集合,包括小部件ID和类型。 - * @return 选中项目的应用小部件属性集合,无效时返回null - */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -195,11 +128,6 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } - /** - * 获取选中项目的数量 - * 统计当前选中项目的数量。 - * @return 选中项目的数量 - */ public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -215,22 +143,11 @@ public class NotesListAdapter extends CursorAdapter { return count; } - /** - * 检查是否全选 - * 检查当前是否所有普通便签都被选中。 - * @return true表示所有普通便签都被选中,false表示不是 - */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } - /** - * 检查指定位置的项目是否被选中 - * 检查指定位置的项目在选中项目映射表中的状态。 - * @param position 项目位置 - * @return true表示项目被选中,false表示未被选中 - */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -238,31 +155,18 @@ 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/Notes-master/src/net/micode/notes/ui/NotesListItem.java b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java index c4eaf38..2210af0 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java @@ -30,53 +30,30 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; -/** - * NotesListItem - 便签列表项自定义视图组件 - * 继承自LinearLayout,用于在便签列表中显示单个便签或文件夹项。 - * 负责根据不同类型的便签数据(普通便签、文件夹、通话记录便签)展示不同的UI样式和内容。 - * 支持多选模式下的复选框显示和状态管理。 - * - * 主要功能: - * - 根据便签数据类型(普通便签、文件夹、通话记录)显示不同的UI样式 - * - 支持多选模式下的复选框显示和状态控制 - * - 显示便签的标题、时间、提醒图标等信息 - * - 根据便签背景色ID设置适当的背景样式 - * - 管理通话记录便签的特殊显示需求 - */ +import android.text.TextUtils; + 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 mNoteTitle; + private NoteItemData mItemData; + private CheckBox mCheckBox; + private ImageView mPin; - /** - * 构造方法 - 创建便签列表项视图 - * 初始化视图组件,从布局文件中加载UI元素。 - * @param context 上下文环境,用于加载布局和资源 - */ public NotesListItem(Context context) { super(context); inflate(context, R.layout.note_item, this); mAlert = (ImageView) findViewById(R.id.iv_alert_icon); + mNoteTitle = (TextView) findViewById(R.id.tv_note_title); mTitle = (TextView) findViewById(R.id.tv_title); mTime = (TextView) findViewById(R.id.tv_time); - mCallName = (TextView) findViewById(R.id.tv_name); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); + // [新增] 初始化 View + mPin = (ImageView) findViewById(R.id.iv_pin_icon); } - /** - * 绑定便签数据到视图组件 - * 根据便签数据的类型和属性,配置视图组件的显示内容和样式。 - * 支持处理普通便签、文件夹和通话记录便签三种类型的数据。 - * @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); @@ -84,21 +61,25 @@ public class NotesListItem extends LinearLayout { mCheckBox.setVisibility(View.GONE); } + // [新增] 绑定置顶状态 + // 只有当类型是普通便签(TYPE_NOTE)且 isPinned 为 true 时才显示 + if (data.getType() == Notes.TYPE_NOTE && data.isPinned()) { + mPin.setVisibility(View.VISIBLE); + } else { + mPin.setVisibility(View.GONE); + } + mItemData = data; - - // 处理通话记录文件夹的特殊显示 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.GONE); + mNoteTitle.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setText(context.getString(R.string.call_record_folder_name) + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setImageResource(R.drawable.call_record); - } - // 处理通话记录便签的特殊显示 - else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.VISIBLE); - mCallName.setText(data.getCallName()); + } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + mNoteTitle.setVisibility(View.VISIBLE); + mNoteTitle.setText(data.getCallName()); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); if (data.hasAlert()) { @@ -107,10 +88,8 @@ public class NotesListItem extends LinearLayout { } else { mAlert.setVisibility(View.GONE); } - } - // 处理普通便签和文件夹的显示 - else { - mCallName.setVisibility(View.GONE); + } else { + mNoteTitle.setVisibility(View.VISIBLE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); if (data.getType() == Notes.TYPE_FOLDER) { @@ -119,6 +98,9 @@ public class NotesListItem extends LinearLayout { data.getNotesCount())); mAlert.setVisibility(View.GONE); } else { + String title = data.getTitle(); + mNoteTitle.setText(TextUtils.isEmpty(title) ? "" : title); + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); @@ -128,24 +110,13 @@ public class NotesListItem extends LinearLayout { } } } - - // 设置便签的修改时间(相对时间格式) mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - - // 设置背景样式 + setBackground(data); } - /** - * 设置列表项的背景样式 - * 根据便签数据的类型、背景色ID和位置(首项、末项、单项等)选择合适的背景资源。 - * 普通便签和文件夹使用不同的背景资源,普通便签还会根据位置和数量选择不同的边角样式。 - * @param data 便签数据对象,包含背景色ID、类型和位置信息 - */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); - - // 为普通便签设置背景 if (data.getType() == Notes.TYPE_NOTE) { if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); @@ -156,18 +127,11 @@ public class NotesListItem extends LinearLayout { } else { setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } - } - // 为文件夹设置背景 - else { + } else { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } - /** - * 获取当前列表项绑定的便签数据 - * 返回当前视图组件所绑定的NoteItemData对象,用于获取便签的详细信息。 - * @return 当前列表项绑定的便签数据对象 - */ public NoteItemData getItemData() { return mItemData; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java index 45e58dd..287cddb 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -41,6 +41,10 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import android.net.Uri; // [新增] 修复错误 1 & 2 + import net.micode.notes.R; import net.micode.notes.data.Notes; @@ -48,48 +52,31 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; -/** - * NotesPreferenceActivity - 便签应用的设置界面 - * 继承自PreferenceActivity,用于管理应用的各种设置选项。 - * 主要提供Google账户同步设置和应用外观设置功能,支持账户添加、切换和删除操作。 - * 通过广播接收器监听同步服务状态更新,实时刷新UI。 - * - * 主要功能: - * - Google账户同步设置(添加、切换、删除账户) - * - 手动同步控制和同步状态显示 - * - 背景颜色随机显示设置 - * - 同步时间记录和显示 - */ 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"; + public static final String PREFERENCE_VIEW_MODE = "pref_view_mode"; // 0: 列表, 1: 瀑布流 + + public 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; // Google任务同步广播接收器 - private Account[] mOriAccounts; // 原始账户列表 - private boolean mHasAddedAccount; // 是否已添加新账户标记 - - /** - * Activity生命周期方法:创建活动时调用 - * 初始化设置界面,加载偏好设置资源,注册同步服务广播接收器, - * 设置导航栏图标和添加自定义头部视图。 - * @param icicle 保存的实例状态 - */ + private PreferenceCategory mAccountCategory; + + private GTaskReceiver mReceiver; + + private Account[] mOriAccounts; + + private boolean mHasAddedAccount; + + private static final int REQUEST_CODE_SELECT_BG = 1001; // [新增] 定义请求码 + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -102,18 +89,37 @@ public class NotesPreferenceActivity extends PreferenceActivity { mReceiver = new GTaskReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); + registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED); mOriAccounts = null; View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); getListView().addHeaderView(header, null, true); + + // 修改 onCreate 中的点击事件逻辑 + Preference customBgPref = findPreference("pref_key_custom_list_bg"); + if (customBgPref != null) { + customBgPref.setOnPreferenceClickListener(p -> { + // 使用 ACTION_OPEN_DOCUMENT 以获取可持久化的 URI 权限 + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + startActivityForResult(intent, REQUEST_CODE_SELECT_BG); // [核心修改] 使用传统启动方式 + return true; + }); + } + + // 在 onCreate 绑定点击事件的地方添加: + Preference resetBgPref = findPreference("pref_key_reset_list_bg"); + if (resetBgPref != null) { + resetBgPref.setOnPreferenceClickListener(p -> { + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + settings.edit().remove("custom_list_background_uri").apply(); // 移除键值 + Toast.makeText(this, "列表背景已恢复默认", Toast.LENGTH_SHORT).show(); + return true; + }); + } } - /** - * Activity生命周期方法:活动可见时调用 - * 检查是否添加了新账户,如果是则自动设置为同步账户。 - * 刷新UI界面以显示最新的同步状态和账户信息。 - */ @Override protected void onResume() { super.onResume(); @@ -142,10 +148,6 @@ public class NotesPreferenceActivity extends PreferenceActivity { refreshUI(); } - /** - * Activity生命周期方法:活动销毁时调用 - * 取消注册同步服务广播接收器,释放资源。 - */ @Override protected void onDestroy() { if (mReceiver != null) { @@ -154,11 +156,33 @@ public class NotesPreferenceActivity extends PreferenceActivity { super.onDestroy(); } - /** - * 加载账户偏好设置项 - * 移除现有的账户设置项,创建新的账户设置偏好项,并设置点击事件监听器。 - * 根据当前是否已设置同步账户,决定显示账户选择对话框还是账户更改确认对话框。 - */ + // [新增方法] 在类内部添加 + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_CODE_SELECT_BG && resultCode == RESULT_OK && data != null) { + Uri uri = data.getData(); + if (uri != null) { + try { + // 1. 获取永久访问权限(非常重要:否则手机重启后背景会变黑) + final int takeFlags = data.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + getContentResolver().takePersistableUriPermission(uri, takeFlags); + + // 2. 保存 URI 字符串到 SharedPreferences + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + settings.edit().putString("custom_list_background_uri", uri.toString()).apply(); + + Toast.makeText(this, "列表背景更新成功", Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(this, "图片授权失败", Toast.LENGTH_SHORT).show(); + } + } + } + } + private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -168,17 +192,16 @@ public class NotesPreferenceActivity extends PreferenceActivity { accountPref.setSummary(getString(R.string.preferences_account_summary)); accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { - // 检查是否正在同步 if (!GTaskSyncService.isSyncing()) { if (TextUtils.isEmpty(defaultAccount)) { - // 首次设置账户 + // the first time to set account showSelectAccountAlertDialog(); } else { - // 已设置账户,需要提示用户更改账户的风险 + // if the account has already been set, we need to promp + // user about the risk showChangeAccountConfirmAlertDialog(); } } else { - // 正在同步,无法更改账户 Toast.makeText(NotesPreferenceActivity.this, R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); @@ -190,18 +213,12 @@ 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() { public void onClick(View v) { @@ -209,7 +226,6 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); } else { - // 未同步,显示立即同步按钮 syncButton.setText(getString(R.string.preferences_button_sync_immediately)); syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -217,10 +233,9 @@ 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); @@ -237,24 +252,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } - /** - * 刷新用户界面 - * 重新加载账户偏好设置和同步按钮状态,确保UI显示最新的设置信息和同步状态。 - */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } - /** - * 显示账户选择对话框 - * 创建并显示一个自定义对话框,列出所有可用的Google账户供用户选择。 - * 支持添加新账户功能,保存当前账户列表用于后续检测新添加的账户。 - */ 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)); @@ -267,11 +272,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { Account[] accounts = getGoogleAccounts(); String defAccount = getSyncAccountName(this); - // 保存当前账户列表和状态 mOriAccounts = accounts; mHasAddedAccount = false; - // 如果有可用账户,显示单选列表 if (accounts.length > 0) { CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; @@ -286,7 +289,6 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - // 设置选择的账户并刷新UI setSyncAccount(itemMapping[which].toString()); dialog.dismiss(); refreshUI(); @@ -294,12 +296,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } - // 添加新账户视图 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); final AlertDialog dialog = dialogBuilder.show(); - // 设置添加账户点击事件 addAccountView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mHasAddedAccount = true; @@ -313,15 +313,9 @@ 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, @@ -330,7 +324,6 @@ 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), @@ -339,35 +332,21 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { - // 更改账户 showSelectAccountAlertDialog(); } else if (which == 1) { - // 删除账户 removeSyncAccount(); refreshUI(); } - // which == 2 为取消操作,不做处理 } }); dialogBuilder.show(); } - /** - * 获取设备上所有的Google账户 - * 通过AccountManager获取设备上注册的所有Google类型账户。 - * @return Google账户数组 - */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } - /** - * 设置同步账户 - * 更新偏好设置中的同步账户名称,清除上次同步时间, - * 并在后台线程中清除本地便签的Google任务相关信息。 - * @param account 要设置的Google账户名称 - */ private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -398,11 +377,6 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } - /** - * 移除同步账户 - * 从偏好设置中删除同步账户信息和最后同步时间, - * 并在后台线程中清除所有本地便签的Google任务相关信息。 - */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -425,24 +399,12 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } - /** - * 获取同步账户名称 - * 从应用偏好设置中读取当前设置的Google同步账户名称。 - * @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); @@ -451,22 +413,12 @@ 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); } - /** - * Google任务同步广播接收器 - * 监听Google任务同步服务发出的广播,更新同步状态显示。 - */ private class GTaskReceiver extends BroadcastReceiver { @Override @@ -481,12 +433,6 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } - /** - * 处理选项菜单点击事件 - * 当前仅处理Home键点击事件,返回到便签列表主界面。 - * @param item 被点击的菜单项 - * @return 是否消费了该事件 - */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: -- 2.34.1