From 236339d25656851b76bd8f4d7da11bef074473f3 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Tue, 30 Sep 2025 17:05:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=9A=E7=9F=A5=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- positions/build.properties | 4 +- .../positions/adapters/PositionAdapter.java | 12 +- .../positions/utils/NotificationUtils.java | 130 ++++++++++++++++++ .../positions/views/PositionTaskListView.java | 45 ++++-- 4 files changed, 171 insertions(+), 20 deletions(-) create mode 100644 positions/src/main/java/cc/winboll/studio/positions/utils/NotificationUtils.java diff --git a/positions/build.properties b/positions/build.properties index f0dc16e..4b8cbb0 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Sep 30 08:01:34 GMT 2025 +#Tue Sep 30 09:04:55 GMT 2025 stageCount=3 libraryProject= baseVersion=15.0 publishVersion=15.0.2 -buildCount=47 +buildCount=59 baseBetaVersion=15.0.3 diff --git a/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java b/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java index 19d7d26..d0110fa 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java +++ b/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java @@ -32,6 +32,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; +import cc.winboll.studio.positions.utils.NotificationUtils; public class PositionAdapter extends RecyclerView.Adapter { // 常量定义(不变) @@ -151,6 +152,11 @@ public class PositionAdapter extends RecyclerView.Adapter task.getDiscussDistance(); + newBingoState = task.isEnable() && distanceM > task.getDiscussDistance(); } else if (task.isLessThan()) { - newBingoState = distanceM < task.getDiscussDistance(); + newBingoState = task.isEnable() && distanceM < task.getDiscussDistance(); } else { - newBingoState = true; + newBingoState = task.isEnable() && true; } // 仅当状态变化时,才更新任务状态并标记需要通知 diff --git a/positions/src/main/java/cc/winboll/studio/positions/utils/NotificationUtils.java b/positions/src/main/java/cc/winboll/studio/positions/utils/NotificationUtils.java new file mode 100644 index 0000000..35e393c --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/utils/NotificationUtils.java @@ -0,0 +1,130 @@ +package cc.winboll.studio.positions.utils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/09/30 16:09 + * @Describe NotificationUtils + */ +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import androidx.core.app.NotificationCompat; +import cc.winboll.studio.positions.R; +import cc.winboll.studio.positions.activities.LocationActivity; // 替换为你实际的LocationActivity路径 + +/** + * 通知栏工具类:专注于任务相关通知的显示与点击跳转 + * 核心功能:显示任务描述通知,点击后携带positionId/taskId跳转到LocationActivity + */ +public class NotificationUtils { + public static final String TAG = "NotificationUtils"; + // 1. 通知渠道ID(适配Android O及以上,必须唯一) + private static final String TASK_NOTIFICATION_CHANNEL_ID = "task_notification_channel_01"; + // 2. 通知渠道名称(用户在系统设置中看到的名称) + private static final String TASK_NOTIFICATION_CHANNEL_NAME = "任务通知"; + // 3. 通知ID(确保每次显示通知的ID唯一,这里用taskId哈希值避免重复) + private static int getNotificationId(String taskId) { + // 用taskId生成唯一ID(避免不同任务通知相互覆盖) + return taskId.hashCode() & 0xFFFFFF; // 限制ID在0-16777215范围内(系统推荐) + } + + /** + * 显示任务通知 + * @param context 上下文(如Activity/Fragment,确保非空) + * @param taskId 任务ID(用于跳转传递+生成唯一通知ID) + * @param positionId 位置ID(用于跳转传递给LocationActivity) + * @param taskDescription 通知显示内容(仅展示该文本给用户) + */ + public static void show(Context context, String taskId, String positionId, String taskDescription) { + // 安全校验:避免空指针(上下文/必要参数为空时不执行) + if (context == null || taskId == null || positionId == null) { + return; + } + + // 步骤1:初始化通知管理器(系统服务,负责发送/取消通知) + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + return; + } + + // 步骤2:创建通知渠道(Android O及以上必须,否则通知不显示) + createNotificationChannel(notificationManager); + + // 步骤3:构建“点击通知跳转”的Intent(携带positionId和taskId) + Intent jumpIntent = new Intent(context, LocationActivity.class); + // 传递参数:用Intent的Extra携带ID(键名建议定义为常量,避免拼写错误) + jumpIntent.putExtra("EXTRA_POSITION_ID", positionId); + jumpIntent.putExtra("EXTRA_TASK_ID", taskId); + // 配置Intent:清除栈内旧Activity,确保跳转后是最新页面(根据需求调整) + jumpIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + + // 步骤4:创建PendingIntent(授权系统在用户点击通知时执行jumpIntent) + // 注意:PendingIntent.FLAG_UPDATE_CURRENT 确保参数更新时覆盖旧Intent + PendingIntent pendingIntent = PendingIntent.getActivity( + context, + getNotificationId(taskId), // 请求码:与通知ID一致,确保唯一 + jumpIntent, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : + PendingIntent.FLAG_UPDATE_CURRENT + ); + + // 步骤5:构建通知内容(仅显示taskDescription,样式简洁) + Notification notification = new NotificationCompat.Builder(context, TASK_NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.mipmap.ic_launcher) // 通知小图标(必须设置,建议用应用图标) + .setContentTitle("任务提醒") // 通知标题(固定,用户快速识别通知类型) + .setContentText(taskDescription) // 通知内容(仅显示传入的任务描述) + .setContentIntent(pendingIntent) // 绑定点击跳转事件 + .setAutoCancel(true) // 点击通知后自动消失(符合用户习惯) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) // 通知优先级(默认,不抢占焦点) + .setDefaults(NotificationCompat.DEFAULT_SOUND) // 默认提示音(可选,根据需求关闭) + .build(); + + // 步骤6:发送通知(用唯一ID确保不同任务通知不覆盖) + notificationManager.notify(getNotificationId(taskId), notification); + } + + /** + * 创建通知渠道(Android O/API 26及以上必须) + * 作用:用户可在系统设置中管理该渠道的通知(如关闭声音/屏蔽) + */ + private static void createNotificationChannel(NotificationManager notificationManager) { + // 仅在Android O及以上执行(低版本不需要渠道) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 配置渠道:重要性为“默认”(不弹窗,仅通知栏显示+提示音) + NotificationChannel channel = new NotificationChannel( + TASK_NOTIFICATION_CHANNEL_ID, + TASK_NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT + ); + // 可选:配置渠道其他属性(根据需求调整) + channel.setDescription("接收任务相关提醒,点击可查看任务详情"); // 渠道描述(系统设置中显示) + channel.enableVibration(true); // 是否震动(默认开启,可关闭) + channel.setVibrationPattern(new long[]{0, 300}); // 震动模式(0延迟,震动300ms) + + // 注册渠道到系统(仅需注册一次,重复注册会覆盖旧配置) + notificationManager.createNotificationChannel(channel); + } + } + + /** + * (可选)取消指定任务的通知 + * 场景:任务完成后关闭已显示的通知(如bingo状态取消后) + */ + public static void cancel(Context context, String taskId) { + if (context == null || taskId == null) { + return; + } + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + notificationManager.cancel(getNotificationId(taskId)); + } + } +} + diff --git a/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java b/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java index 3e4cea9..c9046dc 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java +++ b/positions/src/main/java/cc/winboll/studio/positions/views/PositionTaskListView.java @@ -212,14 +212,14 @@ public class PositionTaskListView extends LinearLayout { // 核心:根据 isBingo 控制红点显示(true=显示,false=隐藏) simpleHolder.vBingoDot.setVisibility(task.isBingo() ? View.VISIBLE : View.GONE); } - // 编辑模式:沿用原有绑定逻辑(不变) + // 编辑模式:沿用原有绑定逻辑(核心修复在此处) else if (holder instanceof TaskContentViewHolder) { TaskContentViewHolder contentHolder = (TaskContentViewHolder) holder; bindTaskData(contentHolder, task, position); } } - // ---------------------- 原有编辑模式绑定逻辑(完全不变) ---------------------- + // ---------------------- 核心修复:编辑模式绑定逻辑(解决布局中通知异常) ---------------------- private void bindTaskData(final TaskContentViewHolder holder, final PositionTaskModel task, final int position) { String taskDesc = (task.getTaskDescription() == null) ? "未设置描述" : task.getTaskDescription(); holder.tvTaskDesc.setText(String.format("任务:%s", taskDesc)); @@ -227,6 +227,8 @@ public class PositionTaskListView extends LinearLayout { String distanceCondition = task.isGreaterThan() ? "大于" : "小于"; holder.tvTaskDistance.setText(String.format("条件:%s %d 米", distanceCondition, task.getDiscussDistance())); + // 修复点1:先移除开关监听,再设置状态(避免设值时触发回调导致异常) + holder.cbTaskEnable.setOnCheckedChangeListener(null); holder.cbTaskEnable.setChecked(task.isEnable()); holder.cbTaskEnable.setEnabled(mCurrentViewMode == VIEW_MODE_EDIT); @@ -234,6 +236,7 @@ public class PositionTaskListView extends LinearLayout { holder.btnEditTask.setVisibility(View.VISIBLE); holder.btnDeleteTask.setVisibility(View.VISIBLE); + // 删除按钮逻辑(不变,本身在点击时执行,不涉及布局中通知) holder.btnDeleteTask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -247,6 +250,7 @@ public class PositionTaskListView extends LinearLayout { } }); + // 编辑按钮逻辑(不变,弹窗保存时修复通知时机) holder.btnEditTask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -254,15 +258,21 @@ public class PositionTaskListView extends LinearLayout { } }); - holder.cbTaskEnable.setOnCheckedChangeListener(null); + // 修复点2:开关监听-用 RecyclerView.post 延迟执行 notify(避免布局中调用) holder.cbTaskEnable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - task.setIsEnable(isChecked); - notifyItemChanged(position); - if (mOnTaskUpdatedListener != null && mBindPositionId != null) { - mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList(mData)); - } + task.setIsEnable(isChecked); // 先更新数据源(必须执行) + // 关键:通过 mRvTasks.post 延迟通知,确保在布局计算/滚动结束后执行 + mRvTasks.post(new Runnable() { + @Override + public void run() { + notifyItemChanged(position); + if (mOnTaskUpdatedListener != null && mBindPositionId != null) { + mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList(mData)); + } + } + }); } }); } else { @@ -272,7 +282,7 @@ public class PositionTaskListView extends LinearLayout { } } - // ---------------------- 原有编辑弹窗逻辑(完全不变) ---------------------- + // ---------------------- 修复:编辑弹窗保存逻辑(延迟通知,避免极端场景异常) ---------------------- private void showTaskEditDialog(final PositionTaskModel task, final int position) { final Context context = getContext(); View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_edit_task, null); @@ -335,11 +345,16 @@ public class PositionTaskListView extends LinearLayout { task.setIsGreaterThan(isGreater); task.setPositionId(mBindPositionId); - notifyItemChanged(position); - - if (mOnTaskUpdatedListener != null && mBindPositionId != null) { - mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList(mData)); - } + // 修复点3:弹窗保存后延迟通知(同开关逻辑,避免列表滚动时异常) + mRvTasks.post(new Runnable() { + @Override + public void run() { + notifyItemChanged(position); + if (mOnTaskUpdatedListener != null && mBindPositionId != null) { + mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList(mData)); + } + } + }); dialog.dismiss(); Toast.makeText(context, "任务已更新", Toast.LENGTH_SHORT).show(); @@ -347,7 +362,7 @@ public class PositionTaskListView extends LinearLayout { }); } - // ---------------------- ViewHolder 定义(新增简单模式 Holder,适配红点) ---------------------- + // ---------------------- ViewHolder 定义(完全不变) ---------------------- // 基础抽象 ViewHolder(不变) public abstract class TaskViewHolder extends RecyclerView.ViewHolder { public TaskViewHolder(@NonNull View itemView) {