Skip to content

fix(notification): fix invisible delegates after collapsing expanded …#1476

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
re2zero:bugfix
Mar 5, 2026
Merged

fix(notification): fix invisible delegates after collapsing expanded …#1476
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
re2zero:bugfix

Conversation

@re2zero
Copy link
Contributor

@re2zero re2zero commented Mar 4, 2026

…notifications

Remove emit layoutChanged() which breaks animations and add refresh mechanism to trigger ListView re-render of displaced delegates.

删除emit layoutChanged()避免破坏动画,通过模拟微小滚动触发ListView重新渲染被移位的delegate。

PMS: BUG-351731 BUG-351729
Log: 修复收起超一屏应用通知时下方应用不显示的问题
Influence: 修复后收起应用通知下方应用正常显示,无需手动刷新

Summary by Sourcery

Bug Fixes:

  • Ensure notification delegates below a collapsed or expanded app group become visible without requiring manual refresh.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 4, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Replaces a disruptive layoutChanged() emit with a lightweight ListView refresh mechanism by simulating a tiny scroll after collapsing/expanding notification app groups, ensuring displaced delegates are re-rendered without breaking animations.

Sequence diagram for refreshed delegates after notification collapse/expand

sequenceDiagram
    actor User
    participant NotifyViewDelegate
    participant NotifyModel
    participant NotifyView_ListView

    User->>NotifyViewDelegate: clickCollapse(appGroup)
    NotifyViewDelegate->>NotifyModel: collapseApp(index)
    NotifyModel-->>NotifyViewDelegate: collapseCompleted
    NotifyViewDelegate->>NotifyView_ListView: refreshAfterCollapse()
    activate NotifyView_ListView
    NotifyView_ListView->>NotifyView_ListView: Qt.callLater(callback)
    Note over NotifyView_ListView: callback executes shortly after
    NotifyView_ListView->>NotifyView_ListView: contentY++ (tiny scroll)
    NotifyView_ListView->>NotifyView_ListView: re-render displaced delegates
    deactivate NotifyView_ListView

    User->>NotifyViewDelegate: clickExpand(appGroup)
    NotifyViewDelegate->>NotifyModel: expandApp(index)
    NotifyModel-->>NotifyViewDelegate: expandCompleted
    NotifyViewDelegate->>NotifyView_ListView: refreshAfterCollapse()
    activate NotifyView_ListView
    NotifyView_ListView->>NotifyView_ListView: Qt.callLater(callback)
    NotifyView_ListView->>NotifyView_ListView: contentY++ (tiny scroll)
    NotifyView_ListView->>NotifyView_ListView: re-render displaced delegates
    deactivate NotifyView_ListView

    NotifyViewDelegate->>NotifyView_ListView: requestFocusOnExpand(targetIndex)
Loading

File-Level Changes

Change Details Files
Add a View-level helper to refresh ListView delegates after collapsing/expanding notification groups by simulating a minimal scroll.
  • Introduce refreshAfterCollapse() on the root view to adjust contentY by 1 in a Qt.callLater callback, forcing the ListView to re-render affected delegates.
  • Document the intent of the refresh helper to clarify its purpose and timing relative to collapse/expand transitions.
panels/notification/center/NotifyView.qml
Invoke the new refresh helper whenever an app notification group is collapsed or expanded so displaced delegates become visible again.
  • Call view.refreshAfterCollapse() immediately after notifyModel.collapseApp() in the collapse handler.
  • Call view.refreshAfterCollapse() immediately after notifyModel.expandApp() in the expand handler, before requesting focus.
panels/notification/center/NotifyViewDelegate.qml
Remove the layoutChanged() signal emission from the model collapse logic to avoid breaking animations.
  • Delete emit layoutChanged() at the end of NotifyModel::collapseApp so that view/layout updates rely on the new refresh mechanism instead of a full layout change.
panels/notification/center/notifymodel.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 refreshAfterCollapse implementation permanently increments contentY on every call; consider storing the current value and restoring it (or doing a small up/down adjustment in the same callLater) to avoid cumulative drift and unexpected jumps for users who collapse/expand frequently.
  • Since refreshAfterCollapse is also called after expand, you might want to rename it to something more generic (e.g. refreshAfterToggle) and document any assumptions about root.view (that it directly exposes contentY and is a ListView) to make the coupling clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `refreshAfterCollapse` implementation permanently increments `contentY` on every call; consider storing the current value and restoring it (or doing a small up/down adjustment in the same `callLater`) to avoid cumulative drift and unexpected jumps for users who collapse/expand frequently.
- Since `refreshAfterCollapse` is also called after expand, you might want to rename it to something more generic (e.g. `refreshAfterToggle`) and document any assumptions about `root.view` (that it directly exposes `contentY` and is a `ListView`) to make the coupling clearer.

## Individual Comments

### Comment 1
<location path="panels/notification/center/NotifyView.qml" line_range="87-89" />
<code_context>
+        // Refresh delegates after collapse/expand by simulating a tiny scroll
+        // This triggers ListView to re-render displaced delegates that may be invisible
+        // Called immediately after collapseApp/expandApp, before transition starts
+        function refreshAfterCollapse() {
+            Qt.callLater(function() {
+                contentY++
+            })
+        }
</code_context>
<issue_to_address>
**issue (bug_risk):** refreshAfterCollapse permanently shifts scroll position by 1 unit on every call

Because `contentY` is only incremented and never reset, each collapse/expand shifts the viewport down by 1 unit, gradually drifting the view from the user’s expected position. If you just need to trigger a re-render, use a net-zero scroll (e.g., `contentY += 1; contentY -= 1`, possibly via two `Qt.callLater` calls) or a more direct layout invalidation API so the visible scroll position stays stable.
</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.

@re2zero re2zero force-pushed the bugfix branch 4 times, most recently from 0aa40d0 to 6b3cfd0 Compare March 4, 2026 09:39
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 PR fixes a UI issue in the notification center where delegates below a collapsed/expanded app group can become invisible, by avoiding a model layoutChanged() emission (which breaks animations) and adding a QML-side refresh mechanism to force the ListView to re-render displaced delegates.

Changes:

  • Removed emit layoutChanged() from NotifyModel::collapseApp() to avoid breaking ListView transitions/animations.
  • Added a ListView.refreshAfterCollapse() helper in QML that simulates a tiny scroll to trigger delegate re-rendering.
  • Invoked the refresh helper after group collapse/expand actions in the delegate.

Reviewed changes

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

File Description
panels/notification/center/notifymodel.cpp Stops emitting layoutChanged() during collapse to preserve animations.
panels/notification/center/NotifyViewDelegate.qml Calls the new view refresh hook after collapse/expand actions.
panels/notification/center/NotifyView.qml Adds refreshAfterCollapse() implementation via small contentY nudge.

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

…notifications

Remove emit layoutChanged() which breaks animations and add refresh mechanism
to trigger ListView re-render of displaced delegates.

删除emit layoutChanged()避免破坏动画,通过模拟微小滚动触发ListView
重新渲染被移位的delegate。

PMS: BUG-351731 BUG-351729
Log: 修复收起超一屏应用通知时下方应用不显示的问题
Influence: 修复后收起应用通知下方应用正常显示,无需手动刷新
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码的修改主要涉及三个方面:版权年份更新、新增了一个强制ListView重绘的函数jiggleUpdate,以及在通知折叠/展开操作中调用该函数。同时,C++侧移除了emit layoutChanged()的调用。

以下是对代码的详细审查和改进建议:

1. 语法与逻辑审查

  • 版权年份:

    • 现状: 将版权年份从 2024 更新为 2024 - 2026
    • 意见: 这是一个常规的维护操作,符合规范。通常建议使用当前年份作为结束年份,或者使用 2024 - present(如果项目支持),但在具体项目中遵循项目规范即可。
  • jiggleUpdate 函数逻辑:

    • 现状: 通过微调 contentY(±1px)来触发 ListView 的重新渲染。
    • 意见: 这种方法利用了 Qt 的副作用来触发重绘,属于一种"Hack"(变通方案)。逻辑上,它通过改变滚动位置强制 ListView 重新计算委托的位置,从而解决视觉上的错位问题。
    • 风险: Qt.callLater 是异步的。如果在极短时间内连续调用(例如快速点击折叠/展开),可能会导致 contentY 的状态不一致,或者动画卡顿。
  • C++ 侧 layoutChanged() 的移除:

    • 现状: 移除了 collapseApp 函数末尾的 emit layoutChanged()
    • 意见: 这是一个非常关键的改动。
      • layoutChanged() 信号会导致 ListView 认为整个模型的结构或数据发生了重大变化,通常会触发 ListView 的完全重置(丢失当前的滚动位置、选中状态等)。
      • QML 侧新增的 jiggleUpdate 显然是为了替代 layoutChanged() 的视觉效果,但保留了滚动位置和焦点。
      • 逻辑一致性: 修改是合理的,因为 collapseApp 内部使用了 beginInsertRowsendInsertRows,这本身就会通知视图更新对应区域,不需要再发送全量 layoutChanged 信号。

2. 代码质量

  • 命名:

    • jiggleUpdate 这个名字非常形象地描述了函数的行为(抖动更新),作为临时修复方案是可以接受的。但建议在代码注释中明确标注这是一个 Workaround,并说明是为了解决什么具体的 Bug(例如:解决 ListView 在模型局部刷新时委托不更新的渲染问题)。
  • 注释:

    • // Jiggles scroll ±1px to force ListView to re-render displaced delegates 注释写得很清楚,解释了"做什么"和"为什么"。

3. 代码性能

  • 性能影响:
    • 正面: 移除 C++ 的 layoutChanged() 极大地提升了性能。layoutChanged 会强制销毁并重建所有可见的 Delegate,而 jiggleUpdate 只需要触发重绘,开销小得多。
    • 负面: jiggleUpdate 修改了 contentY。虽然只有 1px,但如果 ListView 绑定了 onContentYChanged 信号处理复杂的逻辑,可能会触发不必要的计算。此外,频繁的异步调用可能导致轻微的 UI 抖动。

4. 代码安全

  • 边界条件:
    • 现状: if (contentY > 0) 检查防止了负坐标滚动。
    • 意见: 逻辑是安全的。ListView 的 contentY 通常不会小于 0(除非设置了特殊的内容高度或边界模式)。
  • 竞态条件:
    • Qt.callLater 将操作推迟到事件循环。如果在 callLater 回调执行前,contentY 被其他逻辑(如用户手动滚动)修改了,可能会导致最终的位置偏差。但在 ±1px 的级别下,这种偏差通常是可以忽略的。

5. 改进建议

  1. 完善注释:
    NotifyView.qml 中,建议为 jiggleUpdate 添加更详细的背景说明,以便后续维护者理解为什么需要这个 Hack。

    // Jiggles scroll ±1px to force ListView to re-render displaced delegates
    // Workaround: 解决模型数据变化(如折叠/展开)后,ListView 部分委托未及时更新位置的问题
    // 替代了 C++ 侧开销较大的 layoutChanged() 信号,以保持滚动位置和焦点状态
    function jiggleUpdate() {
        // ... existing code ...
    }
  2. 增加防抖或标志位 (可选):
    如果用户快速连续点击折叠/展开,可能会导致 jiggleUpdate 被多次快速调用。虽然 Qt.callLater 有一定的队列作用,但为了更稳健,可以考虑增加一个简单的标志位防止重入:

    property bool jiggling: false
    
    function jiggleUpdate() {
        if (jiggling) return;
        jiggling = true;
        
        // 保存原始意图,虽然这里是简单的 +/- 1,但逻辑上可以更清晰
        let originalContentY = contentY;
        
        if (contentY > 0) {
            contentY--;
            Qt.callLater(function() { 
                contentY++; 
                jiggling = false; // 重置标志
            });
        } else {
            contentY++;
            Qt.callLater(function() { 
                contentY--; 
                jiggling = false; // 重置标志
            });
        }
    }

    注意:这取决于具体场景,如果每次折叠/展开都需要强制刷新,则不需要加锁,因为 ListView 的内部机制可能会处理掉多余的滚动事件。

  3. C++ 侧的潜在问题:
    notifymodel.cpp 中,移除了 layoutChanged()。请确认 expandApp 函数(如果存在且对应展开逻辑)是否也做了类似的处理(即只使用 begin/endInsertRowsbegin/endRemoveRows,而不发送 layoutChanged)。如果 expandApp 仍然发送 layoutChanged,那么 collapseApp 的行为不一致可能会导致 UI 表现不一致。

总结

这段代码修改是一个优化性质的修复。它通过 QML 层的一个低开销 Hack 替代了 C++ 层的高开销全量刷新信号,解决了潜在的渲染卡顿或状态丢失问题,同时修复了视觉上的渲染错误。逻辑是自洽的,只要注意上述的边界情况和后续维护即可。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@re2zero
Copy link
Contributor Author

re2zero commented Mar 5, 2026

/forcemerge

@deepin-bot
Copy link

deepin-bot bot commented Mar 5, 2026

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit 2db55ef into linuxdeepin:master Mar 5, 2026
11 of 12 checks passed
@re2zero re2zero deleted the bugfix branch March 5, 2026 01:34
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