Skip to content

feat: add hover protection for notification bubbles#1502

Open
mhduiy wants to merge 1 commit intolinuxdeepin:masterfrom
mhduiy:noti
Open

feat: add hover protection for notification bubbles#1502
mhduiy wants to merge 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.

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

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 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

@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

@deepin-ci-robot
Copy link

deepin pr auto review

这份代码修改主要实现了通知气泡在鼠标悬停时阻止自动关闭的功能,涉及C++后端和QML前端的交互。以下是对代码的详细审查和改进建议:

1. 语法与逻辑审查

  • BubblePanel::setHoveredId (bubblepanel.cpp)

    • 问题:使用了 Qt::DirectConnectionsetBlockClosedIdm_notificationServer(推测为 NotifyServerApplet 实例)的一个槽函数。如果 BubblePanelm_notificationServer 位于不同的线程,使用 DirectConnection 会导致在调用者线程(UI线程)中直接执行槽函数代码。如果 NotificationManager 的操作耗时或涉及非线程安全的资源访问,可能会导致崩溃或数据竞争。
    • 改进建议:确认 m_notificationServer 所属线程。如果它运行在独立的工作线程中,建议使用 Qt::QueuedConnection 或者默认的 Qt::AutoConnection,以确保槽函数在接收者线程中执行,避免线程安全问题。
  • NotificationManager::setBlockClosedId (notificationmanager.cpp)

    • 逻辑疑点:在 setBlockClosedId 函数中,有一段逻辑用于处理"上一个被阻止关闭的气泡":
      auto findIter = std::find_if(m_pendingTimeoutEntities.begin(), m_pendingTimeoutEntities.end(), [this](const NotifyEntity &entity) {
          return entity.id() == m_blockClosedId;
      });
      // ...
      if(m_blockClosedId != NotifyEntity::InvalidId && findIter != m_pendingTimeoutEntities.end() && current > findIter.key()) {
          // 重新插入到超时队列中
          m_pendingTimeoutEntities.insert(QDateTime::currentMSecsSinceEpoch() + 1000, findIter.value());
          m_pendingTimeoutEntities.erase(findIter);
      }
      这段代码试图在切换悬停气泡时,将上一个气泡重新放入队列并延时1秒。
      1. 时机问题onHoveredChanged 在鼠标移出旧气泡和移入新气泡时会连续触发。当鼠标从气泡A移到气泡B时,会先调用 setBlockClosedId(0)(移出A),再调用 setBlockClosedId(id_B)(移入B)。上述逻辑是在 setBlockClosedId(id_B) 中检查 m_blockClosedId(此时已更新为 id_B)是否匹配队列中的实体。由于 m_blockClosedId 已经变了,这段逻辑实际上很难被触发(除非 id_B 恰好等于 id_A,这在鼠标移动中不可能)。
      2. 逻辑冗余:如果鼠标移出气泡A(调用 setBlockClosedId(0)),此时 m_blockClosedId 变为 0。onHandingPendingEntities 中的逻辑会检测到 id != 0,从而关闭气泡A。这段"重新插入"的逻辑看起来是为了处理某种边界情况,但在当前的调用流程下可能无效。
    • 改进建议:重新审视这段逻辑的意图。如果是为了在鼠标快速滑过时不立即关闭,应该由 onHandingPendingEntities 中的逻辑统一处理,而不是在 setBlockClosedId 中做复杂的查找和重排。如果确实需要处理"上一个气泡",应该在 setBlockClosedId(0) 被调用时处理,而不是在设置新ID时。
  • NotificationManager::onHandingPendingEntities (notificationmanager.cpp)

    • 逻辑问题
      if (item.id() == m_blockClosedId) {
          qDebug(notifyLog) << "bubble id:" << item.bubbleId() << "entity id:" << item.id();
          m_pendingTimeoutEntities.insert(current + 1000, item);
          continue;
      }
      这里将气泡重新插入队列并延时1000ms。这意味着如果用户一直悬停,气泡会每隔1秒被重新插入一次。虽然逻辑上是通的,但会产生频繁的 QMap 操作(插入和删除)。
    • 改进建议:这种实现方式是可行的,但要注意日志输出可能会非常频繁,建议在发布版本中移除或降低日志级别。

2. 代码质量

  • 魔法数字:代码中出现了硬编码的 1000(毫秒)。

    • 改进建议:将其定义为类常量或静态变量,例如 static const int HOVER_DELAY_MS = 1000;,便于统一管理和修改。
  • 版权年份更新:多处文件将版权年份从 2024 更新为 2024 - 2026。这是一个常规操作,但请确认是否所有修改过的文件确实需要更新到2026年。

  • 日志输出qDebug(notifyLog) 的使用。

    • 改进建议:确认 notifyLog 是否已定义。对于高频触发的逻辑(如 onHandingPendingEntities 中的循环),建议使用 qCDebug 并配合分类日志,或者仅在调试模式下编译。

3. 代码性能

  • QMap 操作:在 onHandingPendingEntities 中,对于被阻塞的气泡,执行了 inserterase(隐式,因为 insert 返回迭代器,且 QMap 键唯一)。QMap 的插入和删除操作时间复杂度为 O(log n)。如果通知非常多,可能会有轻微性能影响,但对于通知中心这种场景通常可以忽略。
  • 查找操作:在 setBlockClosedId 中使用了 std::find_if 遍历 m_pendingTimeoutEntities,复杂度为 O(n)。考虑到这是在 UI 事件(鼠标移动)触发的,且通知数量通常有限,性能影响不大,但值得注意。

4. 代码安全

  • 线程安全:如前所述,setBlockClosedId 通过 Qt::DirectConnection 调用。如果 m_notificationServer 运行在非主线程,且 NotificationManager 的成员变量(如 m_blockClosedIdm_pendingTimeoutEntities)没有加锁保护,这里存在严重的线程安全隐患。

    • 改进建议
      1. 确保 NotificationManager 及其容器访问是线程安全的(例如使用 QMutex 或 QReadWriteLock)。
      2. 或者,确保 setBlockClosedId 调用发生在 NotificationManager 所在的线程(使用 Qt::QueuedConnection)。
  • 边界检查setBlockClosedId 接收 qint64 id,代码中检查了 m_blockClosedId != NotifyEntity::InvalidId,这很好。但调用方传入 0 是合法的(表示取消悬停),需要确保 NotifyEntity::InvalidId 的值确实是 0 或者逻辑上兼容。

总结修改建议

  1. 修改 BubblePanel::setHoveredId 连接方式

    // 建议使用 QueuedConnection 或 AutoConnection
    QMetaObject::invokeMethod(m_notificationServer, "setBlockClosedId", Qt::QueuedConnection, Q_ARG(qint64, id));
  2. 优化 NotificationManager::setBlockClosedId 逻辑
    删除那段试图处理 m_blockClosedId(旧值)的复杂逻辑,因为根据调用流程,它可能永远不会按预期工作。只需简单更新 ID 即可:

    void NotificationManager::setBlockClosedId(qint64 id)
    {
        m_blockClosedId = id;
        // 如果需要立即处理队列(例如从0切换到ID时,防止刚好超时),可以保留调用
        onHandingPendingEntities();
    }
  3. 定义常量
    notificationmanager.h 中定义:

    private:
        static const int BLOCK_CLOSE_DELAY_MS = 1000;
  4. 线程安全保护(如果涉及多线程):
    NotificationManager 类中添加互斥锁,并在访问 m_pendingTimeoutEntitiesm_blockClosedId 时加锁。

  5. 版权年份:确认年份范围 2024 - 2026 的准确性。

这些修改将提高代码的健壮性、可维护性和安全性。

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.

Comment on lines +209 to +212
void BubblePanel::setHoveredId(qint64 id)
{
QMetaObject::invokeMethod(m_notificationServer, "setBlockClosedId", Qt::DirectConnection, Q_ARG(qint64, id));
}
Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
void BubblePanel::setHoveredId(qint64 id)
{
QMetaObject::invokeMethod(m_notificationServer, "setBlockClosedId", Qt::DirectConnection, Q_ARG(qint64, id));
}
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));
}

return m_setting->systemValue(static_cast<NotificationSetting::SystemConfigItem>(configItem));
}

void NotificationManager::setBlockClosedId(qint64 id)
Copy link

Choose a reason for hiding this comment

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

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:

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:

// 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:

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();
}
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.

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.

2 participants