添加通知栏
This commit is contained in:
		| @@ -1,8 +1,8 @@ | |||||||
| #Created by .winboll/winboll_app_build.gradle | #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 | stageCount=3 | ||||||
| libraryProject= | libraryProject= | ||||||
| baseVersion=15.0 | baseVersion=15.0 | ||||||
| publishVersion=15.0.2 | publishVersion=15.0.2 | ||||||
| buildCount=47 | buildCount=59 | ||||||
| baseBetaVersion=15.0.3 | baseBetaVersion=15.0.3 | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ import java.util.Iterator; | |||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Timer; | import java.util.Timer; | ||||||
| import java.util.TimerTask; | import java.util.TimerTask; | ||||||
|  | import cc.winboll.studio.positions.utils.NotificationUtils; | ||||||
|  |  | ||||||
| public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||||
|     // 常量定义(不变) |     // 常量定义(不变) | ||||||
| @@ -151,6 +152,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
| 										bindRealDistance(distanceView, targetModel); | 										bindRealDistance(distanceView, targetModel); | ||||||
| 									} | 									} | ||||||
| 								} | 								} | ||||||
|  | 								for (PositionTaskModel task : mAllPositionTasks) { | ||||||
|  | 									if (task.isBingo() && task.isEnable()) { | ||||||
|  | 										NotificationUtils.show(mContext, task.getTaskId(), task.getPositionId(), task.getTaskDescription()); | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
| 							} | 							} | ||||||
| 						}); | 						}); | ||||||
| 				} | 				} | ||||||
| @@ -467,11 +473,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|  |  | ||||||
| // 根据任务条件判断新触发状态 | // 根据任务条件判断新触发状态 | ||||||
| 				if (task.isGreaterThan()) { | 				if (task.isGreaterThan()) { | ||||||
| 					newBingoState = distanceM > task.getDiscussDistance(); | 					newBingoState = task.isEnable() && distanceM > task.getDiscussDistance(); | ||||||
| 				} else if (task.isLessThan()) { | 				} else if (task.isLessThan()) { | ||||||
| 					newBingoState = distanceM < task.getDiscussDistance(); | 					newBingoState = task.isEnable() && distanceM < task.getDiscussDistance(); | ||||||
| 				} else { | 				} else { | ||||||
| 					newBingoState = true; | 					newBingoState = task.isEnable() && true; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| // 仅当状态变化时,才更新任务状态并标记需要通知 | // 仅当状态变化时,才更新任务状态并标记需要通知 | ||||||
|   | |||||||
| @@ -0,0 +1,130 @@ | |||||||
|  | package cc.winboll.studio.positions.utils; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||||
|  |  * @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)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -212,14 +212,14 @@ public class PositionTaskListView extends LinearLayout { | |||||||
|                 // 核心:根据 isBingo 控制红点显示(true=显示,false=隐藏) |                 // 核心:根据 isBingo 控制红点显示(true=显示,false=隐藏) | ||||||
|                 simpleHolder.vBingoDot.setVisibility(task.isBingo() ? View.VISIBLE : View.GONE); |                 simpleHolder.vBingoDot.setVisibility(task.isBingo() ? View.VISIBLE : View.GONE); | ||||||
|             } |             } | ||||||
|             // 编辑模式:沿用原有绑定逻辑(不变) |             // 编辑模式:沿用原有绑定逻辑(核心修复在此处) | ||||||
|             else if (holder instanceof TaskContentViewHolder) { |             else if (holder instanceof TaskContentViewHolder) { | ||||||
|                 TaskContentViewHolder contentHolder = (TaskContentViewHolder) holder; |                 TaskContentViewHolder contentHolder = (TaskContentViewHolder) holder; | ||||||
|                 bindTaskData(contentHolder, task, position); |                 bindTaskData(contentHolder, task, position); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // ---------------------- 原有编辑模式绑定逻辑(完全不变) ---------------------- |         // ---------------------- 核心修复:编辑模式绑定逻辑(解决布局中通知异常) ---------------------- | ||||||
|         private void bindTaskData(final TaskContentViewHolder holder, final PositionTaskModel task, final int position) { |         private void bindTaskData(final TaskContentViewHolder holder, final PositionTaskModel task, final int position) { | ||||||
|             String taskDesc = (task.getTaskDescription() == null) ? "未设置描述" : task.getTaskDescription(); |             String taskDesc = (task.getTaskDescription() == null) ? "未设置描述" : task.getTaskDescription(); | ||||||
|             holder.tvTaskDesc.setText(String.format("任务:%s", taskDesc)); |             holder.tvTaskDesc.setText(String.format("任务:%s", taskDesc)); | ||||||
| @@ -227,6 +227,8 @@ public class PositionTaskListView extends LinearLayout { | |||||||
|             String distanceCondition = task.isGreaterThan() ? "大于" : "小于"; |             String distanceCondition = task.isGreaterThan() ? "大于" : "小于"; | ||||||
|             holder.tvTaskDistance.setText(String.format("条件:%s %d 米", distanceCondition, task.getDiscussDistance())); |             holder.tvTaskDistance.setText(String.format("条件:%s %d 米", distanceCondition, task.getDiscussDistance())); | ||||||
|  |  | ||||||
|  |             // 修复点1:先移除开关监听,再设置状态(避免设值时触发回调导致异常) | ||||||
|  |             holder.cbTaskEnable.setOnCheckedChangeListener(null); | ||||||
|             holder.cbTaskEnable.setChecked(task.isEnable()); |             holder.cbTaskEnable.setChecked(task.isEnable()); | ||||||
|             holder.cbTaskEnable.setEnabled(mCurrentViewMode == VIEW_MODE_EDIT); |             holder.cbTaskEnable.setEnabled(mCurrentViewMode == VIEW_MODE_EDIT); | ||||||
|  |  | ||||||
| @@ -234,6 +236,7 @@ public class PositionTaskListView extends LinearLayout { | |||||||
|                 holder.btnEditTask.setVisibility(View.VISIBLE); |                 holder.btnEditTask.setVisibility(View.VISIBLE); | ||||||
|                 holder.btnDeleteTask.setVisibility(View.VISIBLE); |                 holder.btnDeleteTask.setVisibility(View.VISIBLE); | ||||||
|  |  | ||||||
|  |                 // 删除按钮逻辑(不变,本身在点击时执行,不涉及布局中通知) | ||||||
|                 holder.btnDeleteTask.setOnClickListener(new View.OnClickListener() { |                 holder.btnDeleteTask.setOnClickListener(new View.OnClickListener() { | ||||||
| 						@Override | 						@Override | ||||||
| 						public void onClick(View v) { | 						public void onClick(View v) { | ||||||
| @@ -247,6 +250,7 @@ public class PositionTaskListView extends LinearLayout { | |||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
|  |  | ||||||
|  |                 // 编辑按钮逻辑(不变,弹窗保存时修复通知时机) | ||||||
|                 holder.btnEditTask.setOnClickListener(new View.OnClickListener() { |                 holder.btnEditTask.setOnClickListener(new View.OnClickListener() { | ||||||
| 						@Override | 						@Override | ||||||
| 						public void onClick(View v) { | 						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() { |                 holder.cbTaskEnable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { | ||||||
| 						@Override | 						@Override | ||||||
| 						public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { | 						public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { | ||||||
| 							task.setIsEnable(isChecked); | 							task.setIsEnable(isChecked); // 先更新数据源(必须执行) | ||||||
| 							notifyItemChanged(position); | 							// 关键:通过 mRvTasks.post 延迟通知,确保在布局计算/滚动结束后执行 | ||||||
| 							if (mOnTaskUpdatedListener != null && mBindPositionId != null) { | 							mRvTasks.post(new Runnable() { | ||||||
| 								mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData)); | 									@Override | ||||||
| 							} | 									public void run() { | ||||||
|  | 										notifyItemChanged(position); | ||||||
|  | 										if (mOnTaskUpdatedListener != null && mBindPositionId != null) { | ||||||
|  | 											mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData)); | ||||||
|  | 										} | ||||||
|  | 									} | ||||||
|  | 								}); | ||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
|             } else { |             } else { | ||||||
| @@ -272,7 +282,7 @@ public class PositionTaskListView extends LinearLayout { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // ---------------------- 原有编辑弹窗逻辑(完全不变) ---------------------- |         // ---------------------- 修复:编辑弹窗保存逻辑(延迟通知,避免极端场景异常) ---------------------- | ||||||
|         private void showTaskEditDialog(final PositionTaskModel task, final int position) { |         private void showTaskEditDialog(final PositionTaskModel task, final int position) { | ||||||
|             final Context context = getContext(); |             final Context context = getContext(); | ||||||
|             View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_edit_task, null); |             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.setIsGreaterThan(isGreater); | ||||||
| 						task.setPositionId(mBindPositionId); | 						task.setPositionId(mBindPositionId); | ||||||
|  |  | ||||||
| 						notifyItemChanged(position); | 						// 修复点3:弹窗保存后延迟通知(同开关逻辑,避免列表滚动时异常) | ||||||
|  | 						mRvTasks.post(new Runnable() { | ||||||
| 						if (mOnTaskUpdatedListener != null && mBindPositionId != null) { | 								@Override | ||||||
| 							mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData)); | 								public void run() { | ||||||
| 						} | 									notifyItemChanged(position); | ||||||
|  | 									if (mOnTaskUpdatedListener != null && mBindPositionId != null) { | ||||||
|  | 										mOnTaskUpdatedListener.onTaskUpdated(mBindPositionId, new ArrayList<PositionTaskModel>(mData)); | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							}); | ||||||
|  |  | ||||||
| 						dialog.dismiss(); | 						dialog.dismiss(); | ||||||
| 						Toast.makeText(context, "任务已更新", Toast.LENGTH_SHORT).show(); | 						Toast.makeText(context, "任务已更新", Toast.LENGTH_SHORT).show(); | ||||||
| @@ -347,7 +362,7 @@ public class PositionTaskListView extends LinearLayout { | |||||||
| 				}); | 				}); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // ---------------------- ViewHolder 定义(新增简单模式 Holder,适配红点) ---------------------- |         // ---------------------- ViewHolder 定义(完全不变) ---------------------- | ||||||
|         // 基础抽象 ViewHolder(不变) |         // 基础抽象 ViewHolder(不变) | ||||||
|         public abstract class TaskViewHolder extends RecyclerView.ViewHolder { |         public abstract class TaskViewHolder extends RecyclerView.ViewHolder { | ||||||
|             public TaskViewHolder(@NonNull View itemView) { |             public TaskViewHolder(@NonNull View itemView) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen