Skip to content

fix: improve notification bubble panel hide animation#1490

Open
qxp930712 wants to merge 1 commit intolinuxdeepin:masterfrom
qxp930712:master
Open

fix: improve notification bubble panel hide animation#1490
qxp930712 wants to merge 1 commit intolinuxdeepin:masterfrom
qxp930712:master

Conversation

@qxp930712
Copy link

@qxp930712 qxp930712 commented Mar 10, 2026

Changed the bubble panel visibility logic to include a 400ms delay before hiding when there are no notifications. This prevents the panel from disappearing abruptly when the last bubble is removed. The QML ListView now includes a remove transition with smooth exit animation for bubbles.

Added QTimer include for delayed hide functionality. Modified the ListView height calculation to use maximum of contentHeight and childrenRect.height to ensure proper layout during animations. Implemented a sequential animation for bubble removal with x-axis slide- out effect.

Log: Improved notification bubble animations with smoother hide effects

Influence:

  1. Test notification bubble appearance and disappearance
  2. Verify panel remains visible during bubble removal animations
  3. Check that multiple bubbles animate correctly
  4. Test edge cases with rapid notification additions/removals
  5. Verify panel properly hides after all bubbles are removed
  6. Test animation timing and smoothness

PMS: BUG-284659

Summary by Sourcery

Improve notification bubble panel visibility and removal animations to provide a smoother hide experience when bubbles are cleared.

New Features:

  • Add a ListView remove transition with a sliding exit animation for notification bubbles.

Bug Fixes:

  • Delay hiding the notification bubble panel by 400ms after the last bubble is removed to prevent abrupt disappearance.
  • Adjust ListView height calculation to account for both contentHeight and childrenRect.height so layout remains correct during animations.

Enhancements:

  • Integrate delayed hide logic using QTimer in the bubble panel to coordinate panel visibility with bubble removal animations.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 10, 2026

Reviewer's Guide

Improves the notification bubble panel’s disappearance behavior by delaying panel hide until after a 400ms bubble removal animation, and adjusts ListView sizing to better accommodate animated content.

Sequence diagram for delayed hide of notification bubble panel

sequenceDiagram
    participant NotificationSource
    participant BubbleModel
    participant BubblePanel
    participant QML_BubbleView
    participant QTimer

    NotificationSource->>BubbleModel: removeNotification(id)
    BubbleModel-->>BubblePanel: bubbleCountChanged()

    BubblePanel->>BubblePanel: onBubbleCountChanged()
    BubblePanel->>BubblePanel: isEmpty = m_bubbles.items().isEmpty()
    BubblePanel->>BubblePanel: visible = !isEmpty && enabled()

    alt visible is true
        BubblePanel->>BubblePanel: setVisible(true)
        BubblePanel-->>QML_BubbleView: remain visible
    else visible is false
        BubblePanel->>QTimer: singleShot(400ms, this, lambda)
        QML_BubbleView->>QML_BubbleView: ListView remove Transition
        QML_BubbleView->>QML_BubbleView: SequentialAnimation
        QML_BubbleView->>QML_BubbleView: ListView.delayRemove = true
        QML_BubbleView->>QML_BubbleView: NumberAnimation x -> 360 (400ms)
        QML_BubbleView->>QML_BubbleView: ListView.delayRemove = false
        QTimer-->>BubblePanel: timeout after 400ms
        BubblePanel->>BubblePanel: setVisible(false)
    end
Loading

Updated class diagram for BubblePanel notification visibility logic

classDiagram
    class BubblePanel {
        - m_bubbles
        + onNotificationStateChanged(id, processedType) void
        + onBubbleCountChanged() void
        + addBubble(id) void
        + setVisible(visible) void
        + enabled() bool
    }

    class QTimer {
        + singleShot(msec, receiver, slot) void
    }

    BubblePanel ..> QTimer : uses_singleShot_for_delayed_hide
Loading

File-Level Changes

Change Details Files
Adjust ListView sizing so animated bubble content is fully accounted for during layout.
  • Change ListView height binding to use the maximum of contentHeight and childrenRect.height to avoid clipping during animations
panels/notification/bubble/package/main.qml
Add a smooth slide-out remove transition for notification bubbles in the ListView.
  • Define a remove Transition on the ListView using a SequentialAnimation
  • Use PropertyAction to enable ListView.delayRemove before the animation and disable it after
  • Animate bubble x-property to 360 over 400ms with an InExpo easing curve inside a ParallelAnimation
panels/notification/bubble/package/main.qml
Delay hiding the bubble panel by 400ms when the last bubble is removed to let the exit animation complete.
  • Include QTimer header to support delayed operations
  • Update onBubbleCountChanged to compute desired visibility based on emptiness and enabled state
  • When the panel should become invisible, schedule setVisible(false) via QTimer::singleShot(400, this, ...) instead of hiding immediately
  • Ensure that when there are bubbles and the panel is enabled, visibility is set immediately
panels/notification/bubble/bubblepanel.cpp

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 1 issue, and left some high level feedback:

  • The QTimer::singleShot in onBubbleCountChanged() will hide the panel unconditionally after 400ms even if new bubbles arrived in the meantime; consider checking the current bubble count/enabled state in the lambda or cancelling/reusing a single timer to avoid hiding an active panel.
  • In the ListView remove transition, the hardcoded value to: 360 for the x animation couples the animation to a magic number; using to: width or a shared constant would make the slide-out behavior robust to future width/layout changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The QTimer::singleShot in onBubbleCountChanged() will hide the panel unconditionally after 400ms even if new bubbles arrived in the meantime; consider checking the current bubble count/enabled state in the lambda or cancelling/reusing a single timer to avoid hiding an active panel.
- In the ListView remove transition, the hardcoded value `to: 360` for the x animation couples the animation to a magic number; using `to: width` or a shared constant would make the slide-out behavior robust to future width/layout changes.

## Individual Comments

### Comment 1
<location path="panels/notification/bubble/bubblepanel.cpp" line_range="114-120" />
<code_context>
 {
     bool isEmpty = m_bubbles->items().isEmpty();
-    setVisible(!isEmpty && enabled());
+    const bool visible = !isEmpty && enabled();
+    if (!visible) {
+        QTimer::singleShot(400, this, [this]() {
+            setVisible(false);
+        });
+    } else {
+        setVisible(visible);
+    }
 }
</code_context>
<issue_to_address>
**issue (bug_risk):** Delayed hide timer can incorrectly hide the panel after it becomes visible again.

Because `QTimer::singleShot` is unconditional, a call with `visible == false` will still invoke `setVisible(false)` after 400 ms even if a later call with `visible == true` has already made the panel visible. This creates a race where a stale hide timer overrides a newer show.

To fix this, either cancel/gate the hide in the lambda (e.g. re-check `!m_bubbles->items().isEmpty()` and `enabled()` before calling `setVisible(false)`), or track a hide request ID/sequence and only apply the latest one.
</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 +114 to +120
const bool visible = !isEmpty && enabled();
if (!visible) {
QTimer::singleShot(400, this, [this]() {
setVisible(false);
});
} else {
setVisible(visible);
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Delayed hide timer can incorrectly hide the panel after it becomes visible again.

Because QTimer::singleShot is unconditional, a call with visible == false will still invoke setVisible(false) after 400 ms even if a later call with visible == true has already made the panel visible. This creates a race where a stale hide timer overrides a newer show.

To fix this, either cancel/gate the hide in the lambda (e.g. re-check !m_bubbles->items().isEmpty() and enabled() before calling setVisible(false)), or track a hide request ID/sequence and only apply the latest one.

{
bool isEmpty = m_bubbles->items().isEmpty();
setVisible(!isEmpty && enabled());
const bool visible = !isEmpty && enabled();
Copy link
Contributor

Choose a reason for hiding this comment

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

这个代码要优化下吧,例如它放在qml端,
另外,这个在暂存区域显示的时候,enabled()为false,实际上它不需要延迟隐藏,

@qxp930712 qxp930712 force-pushed the master branch 2 times, most recently from e651239 to c22b928 Compare March 10, 2026 10:43
bool isEmpty = m_bubbles->items().isEmpty();
setVisible(!isEmpty && enabled());
const bool visible = !isEmpty && enabled();
if (!visible) {
Copy link
Contributor

Choose a reason for hiding this comment

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

这里换成isEmpty吧,enabled变化应该不需要延迟隐藏,

#include <QQueue>

#include <appletbridge.h>
#include <qtimer.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

@qxp930712 qxp930712 force-pushed the master branch 2 times, most recently from 728400c to 7a5aded Compare March 10, 2026 11:54
18202781743
18202781743 previously approved these changes Mar 10, 2026
Changed the bubble panel visibility logic to include a 400ms delay
before hiding when there are no notifications. This prevents the panel
from disappearing abruptly when the last bubble is removed. The QML
ListView now includes a remove transition with smooth exit animation
for bubbles.

Added QTimer include for delayed hide functionality. Modified the
ListView height calculation to use maximum of contentHeight and
childrenRect.height to ensure proper layout during animations.
Implemented a sequential animation for bubble removal with x-axis slide-
out effect.

Log: Improved notification bubble animations with smoother hide effects

Influence:
1. Test notification bubble appearance and disappearance
2. Verify panel remains visible during bubble removal animations
3. Check that multiple bubbles animate correctly
4. Test edge cases with rapid notification additions/removals
5. Verify panel properly hides after all bubbles are removed
6. Test animation timing and smoothness

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

deepin pr auto review

这段代码的修改主要是为了优化通知气泡移除时的视觉效果,增加了一个平滑的退出动画。以下是对这段diff的详细审查和改进建议:

1. 代码逻辑与功能分析

BubblePanel.cpp 部分:

  • 版权年份更新:从2024更新至2024-2026,这是常规维护操作。
  • 引入QTimer:在气泡列表为空时,延迟400ms隐藏面板。
  • onBubbleCountChanged逻辑修改
    • 原逻辑:直接根据isEmpty和enabled状态设置可见性。
    • 新逻辑:当isEmpty为true时,延迟400ms隐藏;否则立即显示。

main.qml 部分:

  • 添加移除动画:为ListView的delegate添加了ListView.onRemove动画,使气泡在移除时有平滑的横向滑动效果,持续400ms。

2. 代码质量与性能问题

问题1:时序不匹配

cpp中延迟400ms隐藏面板,而qml中动画也是400ms。虽然看似匹配,但存在潜在问题:

  • 如果动画未完成就隐藏面板,可能导致动画被截断。
  • 如果动画完成但面板还未隐藏,会有短暂空白期。

问题2:内存与性能

  • 使用QTimer::singleShot创建lambda表达式,每次调用都会生成新的临时对象,频繁调用时可能造成性能开销。
  • QML中使用SequentialAnimation和PropertyAction是合理的,但要注意动画期间对ListView.delayRemove的处理。

问题3:状态管理

  • cpp中通过isEmpty判断状态,但未考虑动画期间的状态。如果动画正在进行,面板可能提前隐藏。

3. 安全性问题

  • 线程安全:QTimer在主线程中执行,没有线程安全问题。
  • 内存泄漏风险:lambda捕获this指针,如果BubblePanel对象在400ms内被销毁,可能导致悬空指针访问。

4. 改进建议

建议1:优化时序控制

void BubblePanel::onBubbleCountChanged()
{
    bool isEmpty = m_bubbles->items().isEmpty();
    if (isEmpty) {
        // 确保动画完成后再隐藏,可以适当增加余量
        QTimer::singleShot(450, this, [this]() {
            if (m_bubbles->items().isEmpty()) { // 再次检查状态
                setVisible(false);
            }
        });
    } else {
        setVisible(true && enabled());
    }
}

建议2:增加状态跟踪

在BubblePanel类中添加成员变量跟踪动画状态:

// 在头文件中添加
private:
    bool m_isAnimating = false;
    QTimer* m_hideTimer = nullptr;

// 在构造函数中初始化
m_hideTimer = new QTimer(this);
m_hideTimer->setSingleShot(true);
connect(m_hideTimer, &QTimer::timeout, this, [this]() {
    if (!m_isAnimating && m_bubbles->items().isEmpty()) {
        setVisible(false);
    }
});

// 修改onBubbleCountChanged
void BubblePanel::onBubbleCountChanged()
{
    bool isEmpty = m_bubbles->items().isEmpty();
    if (isEmpty && !m_isAnimating) {
        m_hideTimer->start(450);
    } else {
        m_hideTimer->stop();
        setVisible(true && enabled());
    }
}

建议3:QML动画优化

ListView.onRemove: SequentialAnimation {
    PropertyAction {
        target: delegateItem
        property: "ListView.delayRemove"
        value: true
    }
    NumberAnimation {
        target: delegateItem
        property: "x"
        to: 360
        duration: 400
        easing.type: Easing.InExpo
        onRunningChanged: {
            if (!running) {
                // 通知cpp动画完成
                BubblePanel.animationFinished()
            }
        }
    }
    PropertyAction {
        target: delegateItem
        property: "ListView.delayRemove"
        value: false
    }
}

建议4:增加边界检查

在cpp中增加对BubblePanel对象生命周期的检查:

QTimer::singleShot(400, this, &BubblePanel::safeHide);

void BubblePanel::safeHide()
{
    if (isVisible() && m_bubbles->items().isEmpty()) {
        setVisible(false);
    }
}

5. 总结

这段代码的主要改进方向应该是:

  1. 确保动画和面板隐藏的时序精确同步
  2. 增加对动画状态的跟踪
  3. 防止对象生命周期问题
  4. 优化性能,避免不必要的定时器创建

建议采用状态机模式来管理气泡面板的显示/隐藏和动画状态,这样可以更清晰地处理各种状态转换。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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.

4 participants