Skip to content

Conversation

@qxp930712
Copy link

@qxp930712 qxp930712 commented Jan 31, 2026

  1. Introduce a pending queue mechanism in BubbleModel to serialize bubble addition
  2. Add timer to process next bubble insertion after animation completes
  3. Replace XAnimator with NumberAnimation in QML for better control
  4. Add addDisplaced transition to smooth out existing bubble movement
  5. Prevent visual glitches when receiving rapid notifications

Log: Fixed overlapping animations when multiple notifications arrive quickly

Influence:

  1. Send multiple notifications rapidly to test queue mechanism
  2. Verify bubble animations play sequentially without overlapping
  3. Ensure existing bubbles move smoothly when new ones are added
  4. Verify behavior when bubble count exceeds maximum limit

pms: BUG-320719

Summary by Sourcery

Serialize notification bubble insertions to avoid overlapping animations and improve visual behavior when multiple notifications arrive quickly.

Bug Fixes:

  • Prevent overlapping and conflicting bubble animations when multiple notifications are added in rapid succession.

Enhancements:

  • Introduce a pending queue and timed processing for bubble insertions in BubbleModel to control animation sequencing.
  • Switch bubble add transitions to NumberAnimation and add displaced-item animations for smoother bubble movement in the notification list.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 31, 2026

Reviewer's Guide

Implements a queued bubble insertion mechanism in BubbleModel synchronized with QML animation timing, and updates the QML ListView transitions to use NumberAnimation and addDisplaced for smoother, non-overlapping notification bubble animations.

Sequence diagram for queued bubble insertion and animation

sequenceDiagram
    actor User
    participant App as AppNotificationSource
    participant BubbleModel
    participant QML as NotificationListView

    User ->> App: Perform action that triggers notifications
    App ->> BubbleModel: push(bubble1)
    activate BubbleModel
    BubbleModel ->> BubbleModel: append bubble1 to m_pendingBubbles
    alt not m_isAddingBubble
        BubbleModel ->> BubbleModel: processPendingBubbles()
    end

    BubbleModel ->> BubbleModel: takeFirst() -> bubble1
    BubbleModel ->> QML: insert bubble1 into model
    QML ->> QML: run add Transition NumberAnimation(x)
    QML ->> QML: run addDisplaced Transition on existing items
    BubbleModel ->> BubbleModel: start m_addBubbleTimer(BubbleAnimationDuration)
    deactivate BubbleModel

    App ->> BubbleModel: push(bubble2)
    activate BubbleModel
    BubbleModel ->> BubbleModel: append bubble2 to m_pendingBubbles
    note over BubbleModel: m_isAddingBubble is true, do not process immediately
    deactivate BubbleModel

    BubbleModel ->> BubbleModel: m_addBubbleTimer.timeout()
    activate BubbleModel
    BubbleModel ->> BubbleModel: processPendingBubbles()
    BubbleModel ->> BubbleModel: takeFirst() -> bubble2
    BubbleModel ->> QML: insert bubble2 into model
    QML ->> QML: run add Transition for bubble2
    QML ->> QML: run addDisplaced Transition on existing items
    alt m_pendingBubbles not empty
        BubbleModel ->> BubbleModel: start m_addBubbleTimer(BubbleAnimationDuration)
    else m_pendingBubbles empty
        BubbleModel ->> BubbleModel: m_isAddingBubble = false
    end
    deactivate BubbleModel
Loading

Updated class diagram for BubbleModel notification queue

classDiagram
    class QAbstractListModel

    class BubbleItem

    class NotifySetting {
        +static NotifySetting* instance()
        +signals contentRowCountChanged(int rowCount)
        +signals bubbleCountChanged(int count)
    }

    class QTimer {
        +setInterval(int msec) void
        +setSingleShot(bool singleShot) void
        +start() void
        +start(int msec) void
        +timeout() void
    }

    class BubbleModel {
        +BubbleModel(QObject *parent)
        +~BubbleModel()
        +push(BubbleItem *bubble) void
        +isReplaceBubble(const BubbleItem *bubble) const bool
        +updateLevel() void
        +updateBubbleTimeTip() void
        +updateContentRowCount(int rowCount) void
        +processPendingBubbles() void
        -QTimer *m_updateTimeTipTimer
        -QTimer *m_addBubbleTimer
        -QList<BubbleItem *> m_pendingBubbles
        -QList<qint64> m_delayBubbles
        -qint64 m_delayRemovedBubble
        -bool m_isAddingBubble
        -const int DelayRemovBubbleTime
        -const int BubbleAnimationDuration
    }

    BubbleModel --|> QAbstractListModel
    BubbleModel --> QTimer : uses m_updateTimeTipTimer
    BubbleModel --> QTimer : uses m_addBubbleTimer
    BubbleModel --> BubbleItem : manages
    BubbleModel --> NotifySetting : observes signals
Loading

File-Level Changes

Change Details Files
Serialize bubble insertions via a pending queue and timer in BubbleModel to align model updates with animation duration.
  • Instantiate and configure m_addBubbleTimer as a single-shot QTimer bound to processPendingBubbles().
  • Modify push() to enqueue new BubbleItem instances in m_pendingBubbles and trigger processing only when not already adding a bubble.
  • Introduce processPendingBubbles() to dequeue items, perform the existing insert/remove logic, and schedule the next insertion based on BubbleAnimationDuration.
  • Track insertion state with m_isAddingBubble and clear it when the queue is empty.
  • Add BubbleModel members for the pending queue, insertion timer, and BubbleAnimationDuration constant, ensuring consistency with the QML animation length.
panels/notification/bubble/bubblemodel.cpp
panels/notification/bubble/bubblemodel.h
Refine QML ListView animations to avoid overlapping and ensure smooth movement of existing bubbles.
  • Set cacheBuffer to 0 on the ListView to avoid layout caching during animations.
  • Replace the add transition's XAnimator with a NumberAnimation targeting the x property, explicitly animating from the item width to 0 over 600ms with easing.
  • Introduce an addDisplaced transition that animates x and y properties of displaced items with a 600ms easing animation to smooth repositioning when bubbles are added.
panels/notification/bubble/package/main.qml

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 queueing logic in BubbleModel relies on a hard-coded BubbleAnimationDuration that must stay in sync with the QML duration; consider centralizing this duration (e.g., a shared constant or configuration) to avoid subtle desynchronization when one side changes.
  • Storing raw BubbleItem* in m_pendingBubbles can make ownership and lifetime unclear, especially if items are destroyed or replaced before dequeuing; consider using smart pointers or clearer ownership guarantees to avoid dangling pointers.
  • Using m_addBubbleTimer to approximate animation completion may be brittle if animation timings or easing change dynamically; if possible, consider driving processPendingBubbles from an animation-completed signal or a QML-to-C++ callback instead of a fixed timer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The queueing logic in `BubbleModel` relies on a hard-coded `BubbleAnimationDuration` that must stay in sync with the QML duration; consider centralizing this duration (e.g., a shared constant or configuration) to avoid subtle desynchronization when one side changes.
- Storing raw `BubbleItem*` in `m_pendingBubbles` can make ownership and lifetime unclear, especially if items are destroyed or replaced before dequeuing; consider using smart pointers or clearer ownership guarantees to avoid dangling pointers.
- Using `m_addBubbleTimer` to approximate animation completion may be brittle if animation timings or easing change dynamically; if possible, consider driving `processPendingBubbles` from an animation-completed signal or a QML-to-C++ callback instead of a fixed timer.

## Individual Comments

### Comment 1
<location> `panels/notification/bubble/bubblemodel.cpp:54` </location>
<code_context>
         m_updateTimeTipTimer->start();
     }

+    // Add bubble to pending queue to prevent animation conflicts
+    m_pendingBubbles.append(bubble);
+    
</code_context>

<issue_to_address>
**issue (complexity):** Consider removing the separate m_isAddingBubble flag and deriving the insertion state solely from the pending queue and timer activity to simplify the bubble insertion logic.

You can drop `m_isAddingBubble` entirely and let the queue + timer state be the sole source of truth. That removes the implicit state machine and duplicated flag management while keeping the serialized insertion behavior.

Concretely:

- Drive everything off `m_pendingBubbles` and `m_addBubbleTimer->isActive()`
- Let `processPendingBubbles()` always handle “when to start the timer”
- Keep `push()` simple: just enqueue and, if idle, process once

Example refactor:

```cpp
void BubbleModel::push(BubbleItem *bubble)
{
    if (!m_updateTimeTipTimer->isActive()) {
        m_updateTimeTipTimer->start();
    }

    // Enqueue bubble
    m_pendingBubbles.append(bubble);

    // If no timer is running, we're idle: insert first bubble immediately
    if (!m_addBubbleTimer->isActive()) {
        processPendingBubbles();
    }
}

void BubbleModel::processPendingBubbles()
{
    if (m_pendingBubbles.isEmpty()) {
        // Nothing to do; ensure timer is stopped
        m_addBubbleTimer->stop();
        return;
    }

    BubbleItem *bubble = m_pendingBubbles.takeFirst();

    bool more = displayRowCount() >= BubbleMaxCount;
    if (more) {
        beginRemoveRows(QModelIndex(), BubbleMaxCount - 1, BubbleMaxCount - 1);
        endRemoveRows();
    }

    beginInsertRows(QModelIndex(), 0, 0);
    m_bubbles.prepend(bubble);
    endInsertRows();

    updateLevel();

    // If there are more pending bubbles, schedule the next one
    if (!m_pendingBubbles.isEmpty()) {
        m_addBubbleTimer->start(BubbleAnimationDuration);
    } else {
        m_addBubbleTimer->stop();
    }
}
```

This:

- Removes `m_isAddingBubble` and its duplicated set/clear logic
- Makes the protocol explicit: `push()` only enqueues and kicks off processing if idle; `processPendingBubbles()` owns the timing and queue advancement
- Keeps the existing behavior of spacing inserts by `BubbleAnimationDuration` without changing functionality.
</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.

m_updateTimeTipTimer->start();
}

// Add bubble to pending queue to prevent animation conflicts
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 removing the separate m_isAddingBubble flag and deriving the insertion state solely from the pending queue and timer activity to simplify the bubble insertion logic.

You can drop m_isAddingBubble entirely and let the queue + timer state be the sole source of truth. That removes the implicit state machine and duplicated flag management while keeping the serialized insertion behavior.

Concretely:

  • Drive everything off m_pendingBubbles and m_addBubbleTimer->isActive()
  • Let processPendingBubbles() always handle “when to start the timer”
  • Keep push() simple: just enqueue and, if idle, process once

Example refactor:

void BubbleModel::push(BubbleItem *bubble)
{
    if (!m_updateTimeTipTimer->isActive()) {
        m_updateTimeTipTimer->start();
    }

    // Enqueue bubble
    m_pendingBubbles.append(bubble);

    // If no timer is running, we're idle: insert first bubble immediately
    if (!m_addBubbleTimer->isActive()) {
        processPendingBubbles();
    }
}

void BubbleModel::processPendingBubbles()
{
    if (m_pendingBubbles.isEmpty()) {
        // Nothing to do; ensure timer is stopped
        m_addBubbleTimer->stop();
        return;
    }

    BubbleItem *bubble = m_pendingBubbles.takeFirst();

    bool more = displayRowCount() >= BubbleMaxCount;
    if (more) {
        beginRemoveRows(QModelIndex(), BubbleMaxCount - 1, BubbleMaxCount - 1);
        endRemoveRows();
    }

    beginInsertRows(QModelIndex(), 0, 0);
    m_bubbles.prepend(bubble);
    endInsertRows();

    updateLevel();

    // If there are more pending bubbles, schedule the next one
    if (!m_pendingBubbles.isEmpty()) {
        m_addBubbleTimer->start(BubbleAnimationDuration);
    } else {
        m_addBubbleTimer->stop();
    }
}

This:

  • Removes m_isAddingBubble and its duplicated set/clear logic
  • Makes the protocol explicit: push() only enqueues and kicks off processing if idle; processPendingBubbles() owns the timing and queue advancement
  • Keeps the existing behavior of spacing inserts by BubbleAnimationDuration without changing functionality.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a queueing mechanism to prevent overlapping bubble insertion animations when multiple notifications arrive in rapid succession. The changes serialize bubble additions and coordinate C++ model updates with QML animations.

Changes:

  • Add pending queue in BubbleModel to serialize bubble insertions with timer-based coordination
  • Replace XAnimator with NumberAnimation for explicit animation control
  • Add displaced item transitions for smoother bubble movement

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
panels/notification/bubble/bubblemodel.h Add pending bubble queue, timer, and state flag for animation coordination
panels/notification/bubble/bubblemodel.cpp Implement queue mechanism with processPendingBubbles() to serialize additions
panels/notification/bubble/package/main.qml Switch to NumberAnimation, add displaced transitions, set cacheBuffer to 0
Comments suppressed due to low confidence (2)

panels/notification/bubble/bubblemodel.cpp:77

  • Memory leak: When the bubble count exceeds BubbleMaxCount, the code removes a bubble from the model without deleting the BubbleItem object. The removed bubble at index BubbleMaxCount - 1 needs to be retrieved from m_bubbles and deleted before calling beginRemoveRows.

The pattern used elsewhere in the codebase (see remove() at line 128-142) shows that you should call takeAt() to get the bubble pointer and then call deleteLater() on it. Without this, the BubbleItem objects that are removed when at max capacity will leak memory.

    bool more = displayRowCount() >= BubbleMaxCount;
    if (more) {
        beginRemoveRows(QModelIndex(), BubbleMaxCount - 1, BubbleMaxCount - 1);
        endRemoveRows();
    }

panels/notification/bubble/bubblemodel.cpp:46

  • Memory leak on cleanup: The destructor and clear() methods don't clean up pending bubbles in m_pendingBubbles. If the BubbleModel is destroyed or cleared while there are bubbles waiting in the pending queue, those BubbleItem objects will leak memory since they're not in m_bubbles yet.

You should add qDeleteAll(m_pendingBubbles) and m_pendingBubbles.clear() to both the destructor (after line 44) and the clear() method (after line 117).

BubbleModel::~BubbleModel()
{
    qDeleteAll(m_bubbles);
    m_bubbles.clear();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

interactive: false
verticalLayoutDirection: ListView.BottomToTop

// Disable automatic layout updates during animations
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

Misleading comment: The comment states "Disable automatic layout updates during animations" but cacheBuffer controls the size of the buffer zone outside the visible area where delegates are instantiated, not layout updates during animations. Setting cacheBuffer to 0 means delegates are only created for visible items, which can cause performance issues during scrolling.

This setting doesn't prevent layout updates during animations. If you're experiencing layout issues during animations, consider using ListView.delayRemove or adjusting the displacement property instead.

Suggested change
// Disable automatic layout updates during animations
// Do not cache offscreen delegates (cacheBuffer controls offscreen instantiation, not layout updates/animations)

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

done

id: addTrans
XAnimator { target: addTrans.ViewTransition.item; from: addTrans.ViewTransition.item.width; duration: 600; easing.type: Easing.OutExpo }
// Use NumberAnimation with explicit to value
NumberAnimation {
Copy link
Contributor

Choose a reason for hiding this comment

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

这里用XAnimator是为了规避动画卡顿的问题的,尤其是在录屏数据较大时的通知容易复现,XAnimator在子线程中执行,而NumberAnimation在主线程中执行,
看要不要保持XAnimator,


// Setup timer for processing pending bubbles
m_addBubbleTimer->setSingleShot(true);
connect(m_addBubbleTimer, &QTimer::timeout, this, &BubbleModel::processPendingBubbles);
Copy link
Contributor

Choose a reason for hiding this comment

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

通知的超时是在这里实现的,

void NotificationManager::onHandingPendingEntities()

如果这里再加动画显示,可能出现另一个问题,这个通知的数据超时已经删掉了,但通知横幅还未显示,导致显示的通知横幅操作失败,

18202781743
18202781743 previously approved these changes Jan 31, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 73 to 93
// 在动画开始前,强制完成所有现有弹框的动画
ScriptAction {
script: {
// 遍历所有弹框,将它们的x强制设置为0
for (let i = 0; i < bubbleView.count; i++) {
let item = bubbleView.itemAtIndex(i)
if (item) {
if (item !== addTrans.ViewTransition.item) {
item.x = 0
}
}
}
}
}
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The PR description claims "Replace XAnimator with NumberAnimation in QML for better control", but this change still uses XAnimator. If the goal is to use NumberAnimation for better control as stated, the XAnimator should be replaced. Additionally, the PR description mentions adding an "addDisplaced transition" which is not present in this diff - a displaced transition would handle the movement of existing items when new items are added.

Copilot uses AI. Check for mistakes.
Comment on lines 73 to 93
// 在动画开始前,强制完成所有现有弹框的动画
ScriptAction {
script: {
// 遍历所有弹框,将它们的x强制设置为0
for (let i = 0; i < bubbleView.count; i++) {
let item = bubbleView.itemAtIndex(i)
if (item) {
if (item !== addTrans.ViewTransition.item) {
item.x = 0
}
}
}
}
}
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The PR description states "Introduce a pending queue mechanism in BubbleModel to serialize bubble addition" and "Add timer to process next bubble insertion after animation completes", but there are no changes to BubbleModel in this PR. The BubbleModel class still uses the same immediate insertion logic in the push() method without any queue or timer mechanism. This means rapid notifications will still trigger multiple simultaneous insertions, which contradicts the stated goal of serializing bubble additions.

Copilot uses AI. Check for mistakes.
Comment on lines 74 to 85
ScriptAction {
script: {
// 遍历所有弹框,将它们的x强制设置为0
for (let i = 0; i < bubbleView.count; i++) {
let item = bubbleView.itemAtIndex(i)
if (item) {
if (item !== addTrans.ViewTransition.item) {
item.x = 0
}
}
}
}
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The ScriptAction iterates through all bubbles on every insertion to force their x position to 0. This approach has performance implications as it creates O(n) operations for each insertion. More importantly, this forceful snapping can create visual discontinuities if animations are mid-flight. A better approach would be to either complete the animations gracefully or use a displaced transition to handle the movement of existing items, as mentioned in the PR description.

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +92
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The explicit to: 0 parameter was added to the XAnimator, which is good for clarity. However, according to the PR description, XAnimator should be replaced with NumberAnimation (as seen in panels/notification/center/NotifyView.qml:127). NumberAnimation provides better control for synchronization and allows the animation to be properly interrupted or completed, which is essential for the queue mechanism mentioned in the PR description.

Copilot uses AI. Check for mistakes.
@18202781743
Copy link
Contributor

代码逻辑改了,提交信息重新生成下,

@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

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +92
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The PR description states "Replace XAnimator with NumberAnimation in QML for better control" but the code still uses XAnimator. This is a discrepancy between the PR description and the implementation. If NumberAnimation is needed for better control, the XAnimator should be replaced as described.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +86
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
if (bubbleView.count > 1) {
let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
if (prevItem) {
// Directly set x to 0 to forcibly complete the animation
prevItem.x = 0
}
}
}
}
XAnimator {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

Forcibly setting prevItem.x to 0 while an XAnimator is running may not properly stop the animator. XAnimator runs on the render thread and directly manipulating the property it's animating can lead to race conditions and unpredictable behavior. The animator may continue running or may snap back to the animated value. To properly stop an animation, you should either use the Animation.stop() method or ensure the animator completes naturally. Consider using NumberAnimation instead of XAnimator if you need to synchronously control the animated property, or store a reference to the running animation so it can be properly stopped.

Suggested change
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
if (bubbleView.count > 1) {
let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
if (prevItem) {
// Directly set x to 0 to forcibly complete the animation
prevItem.x = 0
}
}
}
}
XAnimator {
// If a previous add animation is still running, complete it cleanly
if (addXAnimator.running) {
addXAnimator.complete();
}
}
}
XAnimator {
id: addXAnimator

Copilot uses AI. Check for mistakes.
// Before starting the new animation, forcibly complete the previous notification bubble's animation
ScriptAction {
script: {
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The comment states "at index count - 1" but the code actually accesses "bubbleView.count - 2". The comment is misleading because when a new item is being added in the add transition, bubbleView.count has already been incremented to include the new item. So count - 2 is indeed the previous bubble (the one that was last before this addition), not count - 1. The comment should be corrected to say "at index count - 2" or better yet, explain that since count includes the newly added item, count - 2 gives us the previously added bubble.

Suggested change
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
// Only handle the previous notification bubble; since count already includes the newly added bubble, the previous one is at index count - 2, so no need to iterate through all of them

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +82
let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
if (prevItem) {
// Directly set x to 0 to forcibly complete the animation
prevItem.x = 0
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

Accessing bubbleView.itemAtIndex during an add transition may be unreliable. During the add transition, the ListView is in the process of laying out items, and itemAtIndex may return null or an incorrect item reference, especially when multiple items are being animated simultaneously. This could result in the ScriptAction failing silently when prevItem is null, which defeats the purpose of this code. Consider adding additional null checks or using a more robust method to track and stop previous animations, such as maintaining a reference to the currently running animation.

Copilot uses AI. Check for mistakes.
1. Modified the bubble addition transition logic in panels/notification/bubble/package/main.qml.
2. Updated the logic to only handle the previous bubble at index count - 2.
3. Improved pre-animation processing performance, reducing complexity from O(N) to O(1).
4. Ensured that when a new bubble appears, only the position of the immediately preceding bubble needs to be fixed to meet layout requirements.

Log: Fixed overlapping animations when multiple notifications arrive rapidly.

Influence:
1. Send multiple notifications in quick succession to test the queuing mechanism.
2. Verify that bubble animations play sequentially without overlapping.
3. Ensure existing bubbles move smoothly when new ones are added.
4. Verify correct behavior when the number of bubbles exceeds the maximum limit.

pms: BUG-320719
@deepin-ci-robot
Copy link

deepin pr auto review

这份代码修改主要针对通知气泡的进入动画进行了优化,旨在解决多个通知连续出现时动画重叠或视觉混乱的问题。以下是对该修改的详细审查和改进意见:

1. 语法逻辑审查

  • 基本语法正确:QML 语法使用正确,ScriptActionXAnimator 的组合符合 Qt Quick 的动画框架规范。
  • 逻辑意图清晰:代码意图是在新通知气泡开始滑入动画之前,强制将上一个通知气泡(如果存在)立即移动到最终位置(x=0)。这可以防止上一个气泡还在动画过程中时,新气泡的加入导致视觉上的错位或重叠。
  • 潜在逻辑风险
    • itemAtIndex 的有效性:虽然代码中检查了 prevItem 是否存在,但在 ListView 中,itemAtIndex 返回的是当前已创建的 delegate 实例。如果列表项在屏幕外(被缓存或销毁),可能会返回 null 或无效对象。不过,由于 add 事件发生在插入时,且 count > 1 意味着至少有两个项,通常前一项是可见的,风险较低。
    • ViewTransition.item 的作用域addTrans.ViewTransition.item 的使用是正确的,它引用了当前正在被添加的项。

2. 代码质量审查

  • 可读性:添加了注释解释了 ScriptAction 的目的,这很好。但 let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2) 这一行逻辑稍显紧凑,建议在复杂逻辑处增加空行或更详细的注释。
  • 可维护性:硬编码 duration: 600easing.type: Easing.OutExpo 是可以的,但如果项目中多处使用相同的动画参数,建议提取为公共属性或单独的组件,以便统一修改。
  • 代码风格XAnimator 的多行格式化做得很好,属性分行显示,易于阅读。

3. 代码性能审查

  • itemAtIndex 的开销itemAtIndex 是 O(1) 操作,性能影响极小。
  • 动画打断:直接设置 prevItem.x = 0 会打断上一个气泡的动画。虽然这解决了视觉问题,但从性能角度看,如果上一个气泡正在进行复杂的属性绑定或计算,强制修改属性可能会触发额外的重绘或计算。不过,对于简单的 x 属性动画,这种开销是可以忽略不计的。
  • 动画性能XAnimator 通常比 PropertyAnimation 性能更好,因为它可以使用动画线程。这里的选择是合理的。

4. 代码安全审查

  • 空指针检查:代码中 if (prevItem) 的检查是必要的,防止在 itemAtIndex 返回 null 时发生崩溃。
  • 状态一致性:强制设置 x = 0 确保了 UI 状态的一致性,避免了动画未完成时状态不一致导致的潜在 Bug(如点击区域错位)。
  • 边界条件if (bubbleView.count > 1) 很好地处理了列表为空或只有一个元素的情况,避免了数组越界访问。

改进建议

  1. 显式定义动画终点
    原代码中 XAnimator 没有显式指定 to 属性(虽然默认通常是目标属性的当前值或隐式值)。修改后的代码显式添加了 to: 0,这是一个非常好的改进,使动画意图更加明确,不依赖于隐式行为。

  2. 封装逻辑
    如果这种“打断前一个动画”的逻辑在多个地方复用,建议将其封装为一个自定义的 BehaviorTransition 组件。

  3. 考虑使用 populate Transition
    如果 ListView 初始化时就有多个项,add transition 只会对后来添加的项生效。如果希望初始化时也有类似效果,需要考虑 populate transition。

  4. 代码微调(可选)
    为了代码的绝对健壮性,可以增加对 target 是否有效的检查(虽然 ViewTransition 通常保证其有效)。

    XAnimator { 
        target: addTrans.ViewTransition.item
        from: addTrans.ViewTransition.item.width
        to: 0
        duration: 600
        easing.type: Easing.OutExpo
        // 可选:确保目标有效
        running: addTrans.ViewTransition.item !== null
    }

总结

这段代码修改是积极且有效的。它通过显式设置动画终点和强制完成前一个动画,显著提升了通知气泡列表在连续添加时的视觉稳定性和用户体验。代码逻辑清晰,安全性考虑周全,性能开销可控。显式添加 to: 0 是一个值得肯定的细节改进。

@18202781743 18202781743 merged commit beda402 into linuxdeepin:master Feb 2, 2026
10 of 11 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.

4 participants