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