Skip to content

feat: add hover protection for notification bubbles#1502

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
mhduiy:noti
Mar 17, 2026
Merged

feat: add hover protection for notification bubbles#1502
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
mhduiy:noti

Conversation

@mhduiy
Copy link
Contributor

@mhduiy mhduiy commented Mar 14, 2026

Added bubble ID tracking to prevent premature closure when hovering over notifications. The BubbleModel now exposes bubbleId role for QML access. When a bubble is hovered, its ID is passed to the notification server which blocks timeout-based closure for that specific bubble. This prevents notifications from disappearing while users are interacting with them.

Log: Notification bubbles now remain visible when hovered over, preventing accidental closure

feat: 为通知气泡添加悬停保护功能

在 BubbleModel 中添加了 bubbleId 角色供 QML 访问。当气泡被悬停时,其 ID 会传递给通知服务器,服务器会阻止该特定气泡的超时关闭。这防止了用户与通知
交互时通知意外消失的问题。

Log: 通知气泡在悬停时保持可见,防止意外关闭

PMS: BUG-352577

Summary by Sourcery

Add hover-based protection to notification bubbles to prevent them from closing while the user is interacting with them.

New Features:

  • Forward hovered bubble IDs from QML to the notification server so that specific notifications are protected from timeout-based closure while hovered.

Enhancements:

  • Extend notification timeout handling to defer expiration of the currently hovered notification bubble.

Chores:

  • Update SPDX copyright years for notification-related components to cover 2024–2026.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 14, 2026

Reviewer's Guide

Implements hover-based protection for notification bubbles by tracking the currently hovered bubble id from QML through the bubble panel and applet into the NotificationManager, which defers timeout-based closure for that specific notification entity while it is hovered, plus minor metadata updates.

Sequence diagram for hover-based notification bubble protection

sequenceDiagram
    actor User
    participant BubbleQml as Bubble_QML
    participant BubblePanel
    participant NotifyServerApplet
    participant NotificationManager
    participant TimeoutHandler as Timeout_processing

    User ->> BubbleQml: hover enter on bubble
    BubbleQml ->> BubblePanel: setHoveredId(bubble.id)
    BubblePanel ->> NotifyServerApplet: setBlockClosedId(id)
    NotifyServerApplet ->> NotificationManager: setBlockClosedId(id)
    NotificationManager ->> NotificationManager: update m_blockClosedId

    loop Existing blocked bubble handling
        TimeoutHandler ->> NotificationManager: setBlockClosedId(new_id)
        NotificationManager ->> NotificationManager: find previous m_blockClosedId in m_pendingTimeoutEntities
        alt previous blocked entity is pending and expired
            NotificationManager ->> NotificationManager: reinsert previous entity with current+1000ms
        end
        NotificationManager ->> NotificationManager: m_blockClosedId = new_id
        NotificationManager ->> NotificationManager: onHandingPendingEntities()
    end

    loop Periodic timeout processing
        TimeoutHandler ->> NotificationManager: onHandingPendingEntities()
        NotificationManager ->> NotificationManager: iterate m_pendingTimeoutEntities
        alt entity.id == m_blockClosedId
            NotificationManager ->> NotificationManager: reinsert entity with current+1000ms
        else entity expired and not hovered
            NotificationManager ->> NotificationManager: notificationClosed(entity.id, entity.bubbleId, Expired)
        end
    end
Loading

Updated class diagram for notification hover blocking

classDiagram
    class BubblePanel {
        +setEnabled(newEnabled bool) void
        +setHoveredId(id qint64) void
        +close(bubbleIndex int, reason int) void
        +delayProcess(bubbleIndex int) void
    }

    class NotifyServerApplet {
        +removeNotifications(appName QString) void
        +removeNotifications() void
        +removeExpiredNotifications() void
        +setBlockClosedId(id qint64) void
        -m_manager NotificationManager*
    }

    class NotificationManager {
        +SetSystemInfo(configItem uint, value QVariant) void
        +GetSystemInfo(configItem uint) QVariant
        +setBlockClosedId(id qint64) void
        -isDoNotDisturb() bool
        -recordNotification(entity NotifyEntity) bool
        -onHandingPendingEntities() void
        -m_blockClosedId qint64
    }

    class NotifyEntity {
        +id() qint64
        +bubbleId() qint64
        +appName() QString
        <<enum>> InvalidId
    }

    BubblePanel --> NotifyServerApplet : calls_setBlockClosedId
    NotifyServerApplet --> NotificationManager : forwards_setBlockClosedId
    NotificationManager ..> NotifyEntity : manages_pending_timeout_entities
Loading

File-Level Changes

Change Details Files
Add server-side tracking of a 'blocked from closing' notification id and use it to defer timeout-based closure while hovered.
  • Introduce setBlockClosedId slot in NotificationManager and store current blocked id in m_blockClosedId.
  • When a new block id is set, detect if the previously blocked entity is pending timeout and, if overdue, reschedule it 1s into the future instead of closing it.
  • In onHandingPendingEntities, skip closing entities whose id matches m_blockClosedId and requeue them 1s later to keep them alive while hovered.
panels/notification/server/notificationmanager.cpp
panels/notification/server/notificationmanager.h
Plumb hovered bubble id from QML bubble UI down to the notification manager so the server knows which bubble is currently protected from timeout.
  • Add setHoveredId slot to BubblePanel that forwards the given id to the notification server via QMetaObject::invokeMethod("setBlockClosedId").
  • Expose setBlockClosedId slot on NotifyServerApplet and delegate to underlying NotificationManager.
  • Update Bubble.qml hover handler to call Applet.setHoveredId with the current bubble id when hovered and 0 when unhovered, replacing the previous delayRemovedBubble mechanism.
panels/notification/bubble/bubblepanel.cpp
panels/notification/bubble/bubblepanel.h
panels/notification/server/notifyserverapplet.cpp
panels/notification/server/notifyserverapplet.h
panels/notification/bubble/package/Bubble.qml
Refresh SPDX copyright year ranges across touched notification-related files.
  • Update SPDX-FileCopyrightText year range from '2024' to '2024 - 2026' in all modified notification server, applet, and bubble panel files.
panels/notification/server/notificationmanager.cpp
panels/notification/server/notifyserverapplet.cpp
panels/notification/bubble/bubblepanel.cpp
panels/notification/server/notificationmanager.h
panels/notification/bubble/bubblepanel.h
panels/notification/server/notifyserverapplet.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In NotificationManager::setBlockClosedId, the std::find_if lambda compares against m_blockClosedId instead of the incoming id parameter, which means it will search using the old value rather than the new one and likely not behave as intended when changing the hovered bubble.
  • The std::find_if predicate in setBlockClosedId takes a const NotifyEntity & argument, but m_pendingTimeoutEntities appears to be a keyed container (e.g. QMap<qint64, NotifyEntity>), so the lambda parameter should be adjusted (or replaced with a simple iterator loop) to match the actual element type and avoid compilation or runtime issues when accessing entity.id() and findIter.key()/value().
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `NotificationManager::setBlockClosedId`, the `std::find_if` lambda compares against `m_blockClosedId` instead of the incoming `id` parameter, which means it will search using the old value rather than the new one and likely not behave as intended when changing the hovered bubble.
- The `std::find_if` predicate in `setBlockClosedId` takes a `const NotifyEntity &` argument, but `m_pendingTimeoutEntities` appears to be a keyed container (e.g. `QMap<qint64, NotifyEntity>`), so the lambda parameter should be adjusted (or replaced with a simple iterator loop) to match the actual element type and avoid compilation or runtime issues when accessing `entity.id()` and `findIter.key()/value()`.

## Individual Comments

### Comment 1
<location path="panels/notification/bubble/bubblepanel.cpp" line_range="209-212" />
<code_context>
     setVisible(!isEmpty && enabled());
 }

+void BubblePanel::setHoveredId(qint64 id)
+{
+    QMetaObject::invokeMethod(m_notificationServer, "setBlockClosedId", Qt::DirectConnection, Q_ARG(qint64, id));
+}
 }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Using `Qt::DirectConnection` here could be unsafe if `m_notificationServer` lives in another thread.

`QMetaObject::invokeMethod` with `Qt::DirectConnection` will run `setBlockClosedId` in the caller’s thread. If `BubblePanel` and `m_notificationServer` ever end up in different threads, this will break Qt’s threading rules and can cause race conditions.

If cross-thread calls are possible (now or later), prefer the default connection type or `Qt::QueuedConnection`. If they are guaranteed to share a thread, either call the slot directly or add a short comment documenting that assumption to avoid future misuse.

```suggestion
void BubblePanel::setHoveredId(qint64 id)
{
    // Use a queued connection to be safe if BubblePanel and m_notificationServer live in different threads.
    // This avoids violating Qt's threading rules if this ever becomes a cross-thread call.
    QMetaObject::invokeMethod(
        m_notificationServer,
        "setBlockClosedId",
        Qt::QueuedConnection,
        Q_ARG(qint64, id));
}
```
</issue_to_address>

### Comment 2
<location path="panels/notification/server/notificationmanager.cpp" line_range="350" />
<code_context>
     return m_setting->systemValue(static_cast<NotificationSetting::SystemConfigItem>(configItem));
 }

+void NotificationManager::setBlockClosedId(qint64 id)
+{
+    auto findIter = std::find_if(m_pendingTimeoutEntities.begin(), m_pendingTimeoutEntities.end(), [this](const NotifyEntity &entity) {
</code_context>
<issue_to_address>
**issue (complexity):** Consider clarifying which id is used in the timeout search and extracting the common 1000ms postponement logic into a helper to make the behavior more explicit and maintainable.

You can keep the feature as‑is but reduce the cognitive load a bit by (1) avoiding hidden state in the `find_if` lambda and (2) centralising the “postpone by N ms” logic.

### 1. Make it clear which id `find_if` is using

Right now the lambda closes over `this` and uses `m_blockClosedId` while the function takes a new `id` parameter. This is exactly the “which id is this using?” confusion the other reviewer mentioned.

You can make the intent explicit by capturing the previous id in a local and capturing that:

```cpp
void NotificationManager::setBlockClosedId(qint64 id)
{
    const auto previousBlockedId = m_blockClosedId;
    const auto current = QDateTime::currentMSecsSinceEpoch();

    auto findIter = std::find_if(m_pendingTimeoutEntities.begin(),
                                 m_pendingTimeoutEntities.end(),
                                 [previousBlockedId](const NotifyEntity &entity) {
                                     return entity.id() == previousBlockedId;
                                 });

    if (previousBlockedId != NotifyEntity::InvalidId
        && findIter != m_pendingTimeoutEntities.end()
        && current > findIter.key()) {

        qDebug(notifyLog) << "Block close bubble id:" << previousBlockedId
                          << "for the new block bubble id:" << id;

        m_pendingTimeoutEntities.insert(current + 1000, findIter.value());
        m_pendingTimeoutEntities.erase(findIter);
    }

    m_blockClosedId = id;
    onHandingPendingEntities();
}
```

This keeps all behaviour intact but removes the implicit dependency on mutable member state inside the lambda.

### 2. Centralise the “postpone by 1000ms” behaviour

You have the same “insert with `+ 1000`” logic in two places. A tiny helper (or local lambda) will make the intent obvious and avoid subtle divergence later:

```cpp
// Member helper (header + cpp) if you prefer:
void NotificationManager::postponeTimeout(const NotifyEntity &entity,
                                          qint64 baseTimeMs,
                                          qint64 delayMs)
{
    m_pendingTimeoutEntities.insert(baseTimeMs + delayMs, entity);
}
```

Use it in both call sites:

```cpp
void NotificationManager::setBlockClosedId(qint64 id)
{
    const auto previousBlockedId = m_blockClosedId;
    const auto current = QDateTime::currentMSecsSinceEpoch();

    auto findIter = std::find_if(m_pendingTimeoutEntities.begin(),
                                 m_pendingTimeoutEntities.end(),
                                 [previousBlockedId](const NotifyEntity &entity) {
                                     return entity.id() == previousBlockedId;
                                 });

    if (previousBlockedId != NotifyEntity::InvalidId
        && findIter != m_pendingTimeoutEntities.end()
        && current > findIter.key()) {

        qDebug(notifyLog) << "Block close bubble id:" << previousBlockedId
                          << "for the new block bubble id:" << id;

        postponeTimeout(findIter.value(), current, 1000);
        m_pendingTimeoutEntities.erase(findIter);
    }

    m_blockClosedId = id;
    onHandingPendingEntities();
}
```

```cpp
for (const auto &item : timeoutEntities) {
    if (!item.isValid()) {
        // ...
        continue;
    }

    if (item.id() == m_blockClosedId) {
        qDebug(notifyLog) << "bubble id:" << item.bubbleId()
                          << "entity id:" << item.id();
        postponeTimeout(item, current, 1000);
        continue;
    }

    qDebug(notifyLog) << "Expired for the notification " << item.id()
                      << item.appName();
    notificationClosed(item.id(), item.bubbleId(), NotifyEntity::Expired);
}
```

This keeps:

- the “reschedule previous blocked entity if already expired” behaviour, and  
- the “keep postponing the currently blocked entity by 1s on each timeout pass”

but makes the postponement rule explicit and removes duplicated “`current + 1000`” logic.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@mhduiy mhduiy force-pushed the noti branch 4 times, most recently from cfc8603 to 08bfe0e Compare March 17, 2026 05:31
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, mhduiy

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Added bubble ID tracking to prevent premature closure when hovering
over notifications. The BubbleModel now exposes bubbleId role for QML
access. When a bubble is hovered, its ID is passed to the notification
server which blocks timeout-based closure for that specific bubble. This
prevents notifications from disappearing while users are interacting
with them.

Log: Notification bubbles now remain visible when hovered over,
preventing accidental closure

feat: 为通知气泡添加悬停保护功能

在 BubbleModel 中添加了 bubbleId 角色供 QML 访问。当气泡被悬停时,其 ID
会传递给通知服务器,服务器会阻止该特定气泡的超时关闭。这防止了用户与通知
交互时通知意外消失的问题。

Log: 通知气泡在悬停时保持可见,防止意外关闭

PMS: BUG-352577
@deepin-ci-robot
Copy link

deepin pr auto review

代码审查意见

总体评价

本次代码变更重构了通知气泡的延迟移除机制,将原本在 BubbleModel 中实现的延迟移除逻辑迁移到了 NotificationManager 中,并使用 setBlockClosedId 来控制气泡的关闭行为。整体架构改进了职责分离,使逻辑更加清晰。

详细审查意见

1. 语法和逻辑

1.1 BubbleModel 类改进

  • 优点:移除了 delayRemovedBubble 属性和相关成员变量(m_delayBubbles, m_delayRemovedBubble),简化了模型类的职责
  • 建议:在 clear() 方法中,m_delayBubbles.clear()m_delayRemovedBubble = NotifyEntity::InvalidId 的移除是合理的,但建议添加注释说明这些成员已被废弃

1.2 BubblePanel 类改进

  • 优点:新增 setHoveredId 方法,通过 QMetaObject::invokeMethod 调用通知服务器的接口,实现了更好的解耦
  • 潜在问题setHoveredId 方法缺少参数有效性检查,建议添加:
    void BubblePanel::setHoveredId(qint64 id)
    {
        if (id < 0) {
            qWarning() << "Invalid hover id:" << id;
            return;
        }
        QMetaObject::invokeMethod(m_notificationServer, "setBlockClosedId", Qt::DirectConnection, Q_ARG(qint64, id));
    }

1.3 NotificationManager 类改进

  • 优点:新增 setBlockClosedId 方法,集中管理气泡的延迟关闭逻辑
  • 潜在问题
    1. setBlockClosedId 方法中的时间比较逻辑可能存在问题:

      if (current > findIter.key() - BlockItemTimeout) {

      这里应该检查是否接近超时时间,但逻辑可能不够清晰。建议改为:

      const qint64 remainingTime = findIter.key() - current;
      if (remainingTime < BlockItemTimeout) {
          qDebug(notifyLog) << "Delay close bubble id:" << m_blockClosedId << "for the new block bubble id:" << id;
          m_pendingTimeoutEntities.insert(current + BlockItemTimeout, findIter.value());
          m_pendingTimeoutEntities.erase(findIter);
      }
    2. onHandingPendingEntities 方法中新增的阻塞逻辑:

      if (item.id() == m_blockClosedId) {
          qDebug(notifyLog) << "bubble id:" << item.bubbleId() << "entity id:" << item.id();
          m_pendingTimeoutEntities.insert(current, item);
          continue;
      }

      这段代码会导致被阻塞的气泡不断重新插入队列,可能造成性能问题。建议添加最大重试次数或时间限制:

      static const int MaxBlockRetries = 5;
      static QMap<qint64, int> blockRetryCount;
      
      if (item.id() == m_blockClosedId) {
          int &retryCount = blockRetryCount[item.id()];
          if (retryCount < MaxBlockRetries) {
              qDebug(notifyLog) << "Blocking bubble id:" << item.bubbleId() << "entity id:" << item.id() 
                               << "retry:" << retryCount;
              m_pendingTimeoutEntities.insert(current + BlockItemTimeout, item);
              retryCount++;
              continue;
          } else {
              blockRetryCount.remove(item.id());
              qDebug(notifyLog) << "Max retries reached for bubble id:" << item.id();
          }
      }

2. QML 代码改进

2.1 Bubble.qml 改进

  • 优点:移除了 onHoveredChanged 处理,简化了组件逻辑
  • 建议:虽然移除了该处理,但建议在组件文档中说明悬停行为现在由父组件处理

2.2 main.qml 改进

  • 优点:使用 HoverHandler 集中处理悬停事件,更加符合 QML 最佳实践
  • 潜在问题
    1. onPointChanged 处理可能过于频繁,导致性能问题。建议添加防抖动机制:

      Timer {
          id: hoverDebounceTimer
          interval: 100
          onTriggered: {
              const local = point.position
              let hoveredItem = bubbleView.itemAt(local.x, local.y)
              if (hoveredItem && hoveredItem.bubble) {
                  Applet.setHoveredId(hoveredItem.bubble.id)
              } else {
                  Applet.setHoveredId(0)
              }
          }
      }
      
      HoverHandler {
          id: hoverHandler
          onPointChanged: {
              hoverDebounceTimer.restart()
          }
          
          onHoveredChanged: {
              if (!hovered) {
                  hoverDebounceTimer.stop()
                  Applet.setHoveredId(0)
              }
          }
      }
    2. itemAt 方法可能返回不可见的项,建议添加可见性检查:

      let hoveredItem = bubbleView.itemAt(local.x, local.y)
      if (hoveredItem && hoveredItem.bubble && hoveredItem.visible) {
          Applet.setHoveredId(hoveredItem.bubble.id)
      }

3. 代码质量

  • 优点

    • 代码结构更清晰,职责分离更好
    • 移除了不必要的复杂逻辑
    • 添加了适当的日志输出
  • 建议

    1. 为新增的 setBlockClosedId 方法添加详细注释,说明其工作原理和使用场景
    2. BlockItemTimeout 常量添加注释,说明为什么选择1000ms作为超时时间
    3. 考虑添加单元测试来验证新的延迟关闭逻辑

4. 性能考虑

  1. onHandingPendingEntities 中的阻塞逻辑可能导致性能问题,如前所述,建议添加重试限制
  2. QML 中的 onPointChanged 处理可能过于频繁,建议添加防抖动机制
  3. setBlockClosedId 方法中的查找操作 std::find_if 在大量通知情况下可能成为性能瓶颈,考虑使用 QHashQMap 来优化查找

5. 安全性

  • 当前代码没有明显的安全问题,但建议:
    1. setBlockClosedId 的输入参数进行有效性检查
    2. 考虑添加最大阻塞时间限制,防止恶意或异常情况下气泡无法关闭

总结

这次重构整体上改进了代码结构和职责分离,使延迟关闭逻辑更加集中和清晰。主要的改进点包括:

  1. 将延迟关闭逻辑从 BubbleModel 移到 NotificationManager
  2. 使用 HoverHandler 集中处理 QML 中的悬停事件
  3. 通过 setBlockClosedId 方法统一管理气泡的关闭行为

但也有一些需要改进的地方,特别是在性能和边界条件处理方面。建议在合并前考虑上述改进意见,特别是关于性能优化和边界条件处理的部分。

@mhduiy
Copy link
Contributor Author

mhduiy commented Mar 17, 2026

/forcemerge

@deepin-ci-robot
Copy link

deepin pr auto review

Git Diff 代码审查报告

概述

本次代码变更主要重构了通知气泡的延迟关闭机制,将原来在 BubbleModel 中实现的延迟关闭逻辑移动到了 NotificationManager 中,并改变了交互方式。同时更新了版权年份范围。

语法逻辑分析

  1. BubbleModel 类变更

    • 移除了 delayRemovedBubble 属性及相关成员变量和方法
    • 移除了 m_delayBubbles 列表和 m_delayRemovedBubble 变量
    • 清理了 clear()removeById() 方法中的相关逻辑
  2. BubblePanel 类变更

    • 新增了 setHoveredId(qint64 id) 方法,用于通知服务器当前悬停的气泡ID
  3. QML 变更

    • 移除了 Bubble.qml 中的 onHoveredChanged 处理
    • main.qml 中添加了 HoverHandler 来检测鼠标悬停位置
  4. NotificationManager 类变更

    • 新增了 setBlockClosedId(qint64 id) 方法
    • 新增了 m_blockClosedId 成员变量
    • 新增了 BlockItemTimeout 常量(1000ms)
  5. NotifyServerApplet 类变更

    • 新增了 setBlockClosedId(qint64 id) 方法,作为对 NotificationManager 的封装

代码质量评估

  1. 优点

    • 将延迟关闭逻辑集中到 NotificationManager 中,使职责更清晰
    • 使用 HoverHandler 替代 onHoveredChanged 可能更准确地处理悬停状态
    • 移除了 BubbleModel 中不必要的延迟逻辑,简化了模型类
  2. 潜在问题

    • main.qml 中的 HoverHandler 实现:

      onPointChanged: {
          const local = point.position
          let hoveredItem = bubbleView.itemAt(local.x, local.y)
          if (hoveredItem && hoveredItem.bubble) {
              Applet.setHoveredId(hoveredItem.bubble.id)
          } else {
              Applet.setHoveredId(0)
          }
      }
      • 每次鼠标移动都会调用 setHoveredId,可能造成不必要的性能开销
      • itemAt() 方法可能返回 null,需要确保正确处理
    • NotificationManager::setBlockClosedId 方法中:

      if(m_blockClosedId != NotifyEntity::InvalidId) {
          auto findIter = std::find_if(m_pendingTimeoutEntities.begin(), m_pendingTimeoutEntities.end(), [this](const NotifyEntity &entity) {
              return entity.id() == m_blockClosedId;
          });
      • 使用了 std::find_if 而不是 QMapfind 方法,效率较低

代码性能建议

  1. 优化 HoverHandler 的实现

    • 添加防抖(debounce)逻辑,减少频繁调用 setHoveredId
    • 可以考虑只在鼠标进入/离开气泡时更新状态,而不是每次移动
  2. 优化 NotificationManager::setBlockClosedId

    • 使用 QMapfind 方法替代 std::find_if
      auto findIter = m_pendingTimeoutEntities.begin();
      while (findIter != m_pendingTimeoutEntities.end()) {
          if (findIter.value().id() == m_blockClosedId) {
              break;
          }
          ++findIter;
      }
    • 或者维护一个额外的 QHash 来存储 ID 到迭代器的映射,提高查找效率
  3. 考虑使用信号/槽机制

    • 可以在 BubblePanel 中添加信号 hoveredIdChanged,让 NotificationManager 连接该信号,而不是直接调用方法

代码安全建议

  1. ID 验证

    • setHoveredIdsetBlockClosedId 方法中添加对输入 ID 的有效性验证
    • 确保不会因为无效 ID 导致程序异常
  2. 线程安全

    • 如果这些方法可能在不同线程调用,需要添加适当的同步机制
    • 考虑使用 QMutexQReadWriteLock 保护共享数据
  3. 资源管理

    • 确保在 NotificationManager 析构时清理所有定时器和待处理的通知
    • 避免内存泄漏
  4. 异常处理

    • setBlockClosedId 方法中添加对 QDateTime::currentMSecsSinceEpoch() 返回值的检查
    • 确保时间戳比较不会因为系统时间调整而出现问题

其他建议

  1. 版权年份更新

    • 版权年份从 2024 更新为 2024-2026 是合理的,但需要确认是否真的计划支持到 2026 年
  2. 日志记录

    • 在关键操作点添加更多日志记录,便于调试和问题追踪
  3. 单元测试

    • 为新增的 setBlockClosedId 方法添加单元测试,覆盖各种边界情况
  4. 文档更新

    • 更新相关类的文档,说明新的延迟关闭机制的工作原理

总结

本次代码变更将延迟关闭逻辑从 BubbleModel 移动到 NotificationManager,使职责划分更清晰。但在实现细节上还有一些可以优化的地方,特别是在性能和安全性方面。建议在合并前进行适当的优化和测试,确保代码的健壮性和性能。

@deepin-bot
Copy link

deepin-bot bot commented Mar 17, 2026

This pr force merged! (status: blocked)

@deepin-bot deepin-bot bot merged commit 77fa893 into linuxdeepin:master Mar 17, 2026
9 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants