refactor: 红点统一管理(activeDotCards+renderDots)+ 去掉提示条

按矩阵验证的19个场景统一重写通知逻辑:

核心改动:
- activeDotCards 集合累积管理红点,只增不减(除显式操作)
- renderDots() 统一渲染,不在各处单独设 visibility
- 去掉提示条(用户反馈不好看)

场景覆盖:
- 场景1-2: 首页连续收到不同分类通知 → activeDotCards累积 
- 场景3: 点横幅 → consumeAll+activeDotCards清空 
- 场景4-5: 点红点 → activeDotCards移除对应+renderDots 
- 场景7: 所有红点点完 → 自动consumeAll清理 
- 场景11-16: 非首页 → preNotificationStats精确diffStats→补充activeDotCards 
- 场景18: 全部查看后新通知 → consumeAll已重置,新通知正常累积 
- 场景19: 列表中又来通知 → 返回后onResume补充新红点,不覆盖已ack的 

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongliang
2026-04-29 17:47:52 +09:30
parent f785be91a6
commit e6c218d830
2 changed files with 71 additions and 115 deletions

View File

@@ -69,8 +69,12 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
private lateinit var dotPool: View private lateinit var dotPool: View
private lateinit var dotPunch: View private lateinit var dotPunch: View
private lateinit var dotComplete: View private lateinit var dotComplete: View
// 新任务提示条 /** 当前应显示红点的卡片集合(累积,只有显式操作才移除) */
private lateinit var tvNewTaskHint: TextView private val activeDotCards = mutableSetOf<Int>()
/** 红点 View 映射 */
private val dotViews: Map<Int, View> by lazy {
mapOf(2 to dotPool, 3 to dotPunch, 4 to dotComplete)
}
// ===== 设置页 View 引用 ===== // ===== 设置页 View 引用 =====
private lateinit var tvAvatarLetter: TextView private lateinit var tvAvatarLetter: TextView
@@ -131,59 +135,42 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (notificationManager.pendingCount <= 0) { if (notificationManager.pendingCount <= 0) {
// 全部已查看 → 清除所有提示 // 全部已查看/无通知 → 清除
dotPool.visibility = View.GONE activeDotCards.clear()
dotPunch.visibility = View.GONE renderDots()
dotComplete.visibility = View.GONE
tvNewTaskHint.visibility = View.GONE
return return
} }
// 有未读通知 → 用 preNotificationStats 精确对比红点 // 有未读通知 → 用 preNotificationStats 精确补充红点到 activeDotCards
// preNotificationStats 是通知到达前的快照,不会被 onViewCreated 的 fetchStatistics 覆盖)
val ack = notificationManager.acknowledgedCards
val baseline = notificationManager.preNotificationStats val baseline = notificationManager.preNotificationStats
if (baseline != null) { if (baseline != null && activeDotCards.isEmpty()) {
// 精确模式:对比快照和当前统计,只给变化的分类显示红点 // 非首页返回activeDotCards 为空view 重建),需要重新计算
fetchStatisticsForDots(baseline, ack)
} else {
// 无快照(首次加载等):已查看的不显示,未查看的显示
if (2 !in ack) dotPool.visibility = View.VISIBLE
if (3 !in ack) dotPunch.visibility = View.VISIBLE
if (4 !in ack) dotComplete.visibility = View.VISIBLE
}
updateNewTaskHint()
}
/**
* 用通知前快照精确对比红点(非首页返回时调用)
* @param baseline 通知到达前的统计快照
* @param acknowledged 已查看的分类集合
*/
private fun fetchStatisticsForDots(baseline: com.xiaoqu.watch.data.task.TaskStatistics, acknowledged: Set<Int>) {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
val result = safeApiCall { taskApi.getStatistics() } val result = safeApiCall { taskApi.getStatistics() }
if (result is ApiResult.Success && result.data != null) { if (result is ApiResult.Success && result.data != null) {
val data = result.data val data = result.data
val changed = notificationManager.diffStats(baseline, data) val changed = notificationManager.diffStats(baseline, data)
val ack = notificationManager.acknowledgedCards
// 只给变化且未查看的分类显示红点 // 只加未查看的
dotPool.visibility = if (2 in changed && 2 !in acknowledged) View.VISIBLE else View.GONE for (status in changed) {
dotPunch.visibility = if (3 in changed && 3 !in acknowledged) View.VISIBLE else View.GONE if (status !in ack) activeDotCards.add(status)
dotComplete.visibility = if (4 in changed && 4 !in acknowledged) View.VISIBLE else View.GONE }
// 记录增量
// 记录增量(供 acknowledgeCard 扣减用)
if (2 in changed) notificationManager.recordCardIncrement(2, data.waitForTask - baseline.waitForTask) if (2 in changed) notificationManager.recordCardIncrement(2, data.waitForTask - baseline.waitForTask)
if (3 in changed) notificationManager.recordCardIncrement(3, data.treatTask - baseline.treatTask) if (3 in changed) notificationManager.recordCardIncrement(3, data.treatTask - baseline.treatTask)
if (4 in changed) notificationManager.recordCardIncrement(4, data.incompleteTask - baseline.incompleteTask) if (4 in changed) notificationManager.recordCardIncrement(4, data.incompleteTask - baseline.incompleteTask)
// 更新数字 // 更新数字
tvPoolNum.text = data.waitForTask.toString() tvPoolNum.text = data.waitForTask.toString()
tvPunchNum.text = data.treatTask.toString() tvPunchNum.text = data.treatTask.toString()
tvCompleteNum.text = data.incompleteTask.toString() tvCompleteNum.text = data.incompleteTask.toString()
notificationManager.lastStats = data notificationManager.lastStats = data
renderDots()
} }
} }
} else {
// 首页返回从卡片列表回来等activeDotCards 已有值,直接渲染
renderDots()
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -329,23 +316,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
dotPunch = page.findViewById(R.id.dotPunch) dotPunch = page.findViewById(R.id.dotPunch)
dotComplete = page.findViewById(R.id.dotComplete) dotComplete = page.findViewById(R.id.dotComplete)
// 新任务提示条 // 快捷区卡片点击 → 标记已查看 + 清对应红点 + 跳转
tvNewTaskHint = page.findViewById(R.id.tvNewTaskHint)
tvNewTaskHint.setOnClickListener {
navigateToNewTasks()
}
// 快捷区卡片点击 → 标记已查看 + 清红点 + 更新提示条 + 跳转
page.findViewById<View>(R.id.cardPool)?.setOnClickListener { page.findViewById<View>(R.id.cardPool)?.setOnClickListener {
acknowledgeCard(2, dotPool) acknowledgeCard(2)
navigateToTaskList(2) navigateToTaskList(2)
} }
page.findViewById<View>(R.id.cardPunch)?.setOnClickListener { page.findViewById<View>(R.id.cardPunch)?.setOnClickListener {
acknowledgeCard(3, dotPunch) acknowledgeCard(3)
navigateToTaskList(3) navigateToTaskList(3)
} }
page.findViewById<View>(R.id.cardComplete)?.setOnClickListener { page.findViewById<View>(R.id.cardComplete)?.setOnClickListener {
acknowledgeCard(4, dotComplete) acknowledgeCard(4)
navigateToTaskList(4) navigateToTaskList(4)
} }
} }
@@ -386,22 +367,21 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
if (result is ApiResult.Success && result.data != null) { if (result is ApiResult.Success && result.data != null) {
val data = result.data val data = result.data
// 对比红点 + 记录每个分类的增量 // 对比红点:累积到 activeDotCards不覆盖已有的
if (checkDots) { if (checkDots) {
val oldStats = notificationManager.lastStats val oldStats = notificationManager.lastStats
val changedCards = notificationManager.diffStats(oldStats, data) val changedCards = notificationManager.diffStats(oldStats, data)
if (2 in changedCards) { for (status in changedCards) {
dotPool.visibility = View.VISIBLE activeDotCards.add(status) // 累积,不会移除已有的
notificationManager.recordCardIncrement(2, data.waitForTask - (oldStats?.waitForTask ?: 0)) val increment = when (status) {
2 -> data.waitForTask - (oldStats?.waitForTask ?: 0)
3 -> data.treatTask - (oldStats?.treatTask ?: 0)
4 -> data.incompleteTask - (oldStats?.incompleteTask ?: 0)
else -> 0
} }
if (3 in changedCards) { notificationManager.recordCardIncrement(status, increment)
dotPunch.visibility = View.VISIBLE
notificationManager.recordCardIncrement(3, data.treatTask - (oldStats?.treatTask ?: 0))
}
if (4 in changedCards) {
dotComplete.visibility = View.VISIBLE
notificationManager.recordCardIncrement(4, data.incompleteTask - (oldStats?.incompleteTask ?: 0))
} }
renderDots()
} }
// 更新数字 // 更新数字
@@ -494,12 +474,11 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
is AppEvent.BluetoothStateChanged -> { is AppEvent.BluetoothStateChanged -> {
statusBar.updateBluetooth(event.isOn) statusBar.updateBluetooth(event.isOn)
} }
// 新任务到达 → 刷新统计 + 红点 + 提示条 // 新任务到达 → 刷新统计 + 累积红点
is AppEvent.NewTaskArrived -> { is AppEvent.NewTaskArrived -> {
Timber.d("首页: 新任务到达 (${event.count} 条)") Timber.d("首页: 新任务到达 (${event.count} 条)")
fetchStatistics(checkDots = true) fetchStatistics(checkDots = true)
updateNewTaskHint() setupBannerClick()
setupBannerClick(event.taskIds)
} }
// MQTT 消息 // MQTT 消息
is AppEvent.MqttMessageReceived -> { is AppEvent.MqttMessageReceived -> {
@@ -590,26 +569,26 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
findNavController().navigate(R.id.action_home_to_taskList, bundle) findNavController().navigate(R.id.action_home_to_taskList, bundle)
} }
/** 标记某个分类已查看:清红点 + 更新通知数 + 刷新提示条 */ /** 统一渲染红点:以 activeDotCards 为唯一数据源 */
private fun acknowledgeCard(status: Int, dot: View) { private fun renderDots() {
dot.visibility = View.GONE dotViews.forEach { (status, dot) ->
notificationManager.acknowledgeCard(status) dot.visibility = if (status in activeDotCards) View.VISIBLE else View.GONE
updateNewTaskHint() }
} }
/** 更新新任务提示条 */ /** 标记某个分类已查看:从 activeDotCards 移除 + 通知管理器记录 */
private fun updateNewTaskHint() { private fun acknowledgeCard(status: Int) {
val count = notificationManager.pendingCount activeDotCards.remove(status)
if (count > 0) { notificationManager.acknowledgeCard(status)
tvNewTaskHint.text = "${count}条新任务 点击查看" renderDots()
tvNewTaskHint.visibility = View.VISIBLE // 所有红点都点完 → 自动清理场景7
} else { if (activeDotCards.isEmpty() && notificationManager.pendingCount <= 0) {
tvNewTaskHint.visibility = View.GONE notificationManager.consumeAll()
} }
} }
/** /**
* 跳转查看所有新任务(横幅/提示条共用) * 跳转查看所有新任务(横幅点击用)
* 1 个任务 → 跳详情;多个 → 跳列表按 taskIds 加载 * 1 个任务 → 跳详情;多个 → 跳列表按 taskIds 加载
*/ */
private fun navigateToNewTasks() { private fun navigateToNewTasks() {
@@ -630,21 +609,14 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
findNavController().navigate(R.id.action_home_to_taskList, bundle) findNavController().navigate(R.id.action_home_to_taskList, bundle)
} }
// 清除所有提示 // 清除所有提示场景3点横幅查看全部
clearNewTaskIndicators() activeDotCards.clear()
} renderDots()
/** 清除所有新任务提示(红点+提示条+未读) */
private fun clearNewTaskIndicators() {
dotPool.visibility = View.GONE
dotPunch.visibility = View.GONE
dotComplete.visibility = View.GONE
tvNewTaskHint.visibility = View.GONE
notificationManager.consumeAll() notificationManager.consumeAll()
} }
/** 设置通知横幅点击回调 */ /** 设置通知横幅点击回调 */
private fun setupBannerClick(taskIds: List<String>) { private fun setupBannerClick() {
val mainActivity = activity as? com.xiaoqu.watch.app.MainActivity ?: return val mainActivity = activity as? com.xiaoqu.watch.app.MainActivity ?: return
mainActivity.notificationBanner.onClick = { mainActivity.notificationBanner.onClick = {
navigateToNewTasks() navigateToNewTasks()

View File

@@ -42,22 +42,6 @@
</LinearLayout> </LinearLayout>
<!-- 新任务提示条pendingCount>0 时显示,点击查看所有新任务) -->
<TextView
android:id="@+id/tvNewTaskHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_new_task_hint"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="有1条新任务 点击查看"
android:textColor="#FF3B9EFF"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone" />
<!-- 快捷区 gap:6px→8dp --> <!-- 快捷区 gap:6px→8dp -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"