fix(library): retain Tracks anchor through model publish
Capture the visible semantic anchor before requesting the next compatibility page and retain it across bridge signal ordering and the ListView polish pass. This prevents the loading latch from dropping early and avoids recursive page requests during viewport restoration. cx
This commit is contained in:
2 files changed
+55
-10
No files matched your search
@@ -36,6 +36,11 @@ Item {
|
||||
// Programmatic viewport restoration emits the same Flickable signals as
|
||||
// user input. Keep those signals from recursively requesting another page.
|
||||
property bool restoringPageAnchor: false
|
||||
// Captured at REQUEST time, while the old model is unquestionably still
|
||||
// mounted. Bridge-property notifications may otherwise clear the loading
|
||||
// latch before `tracksVisible` re-evaluates and make rebuild miss the only
|
||||
// trustworthy viewport.
|
||||
property var pendingPageAnchor: null
|
||||
// Invalidates a deferred viewport restore when another document/model
|
||||
// replacement wins before Qt's next event-loop turn.
|
||||
property int entriesEpoch: 0
|
||||
@@ -61,13 +66,14 @@ Item {
|
||||
if (!force && !nearEnd)
|
||||
return
|
||||
|
||||
root.pendingPageAnchor = root.capturePageAnchor()
|
||||
root.pageRequestPending = true
|
||||
pageRequestRelease.stop()
|
||||
QbzLocal.tracksLoadMore()
|
||||
}
|
||||
|
||||
function capturePageAnchor() {
|
||||
if (root.nativeModelActive || !root.pageRequestPending
|
||||
|| root.entries.length === 0 || !list.visible)
|
||||
if (root.nativeModelActive || root.entries.length === 0 || !list.visible)
|
||||
return null
|
||||
|
||||
// Preserve identity, not merely pixels. Group modes globally reorder
|
||||
@@ -133,8 +139,11 @@ Item {
|
||||
var minY = list.originY
|
||||
var maxY = minY + Math.max(0, list.contentHeight - list.height)
|
||||
list.contentY = Math.max(minY, Math.min(wanted, maxY))
|
||||
root.restoringPageAnchor = false
|
||||
root.reportSoon()
|
||||
// Keep the guard through Qt's polish pass. contentY/model signals
|
||||
// can arrive after this callback returns; releasing synchronously
|
||||
// is what allowed two extra 500-row requests and the ~1,000-row
|
||||
// forward jump seen in the owner smoke.
|
||||
pageAnchorSettle.restart()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -147,7 +156,9 @@ Item {
|
||||
/// screen, by the number of headers scrolled past.
|
||||
property var rowIndex: []
|
||||
function rebuild() {
|
||||
var anchor = root.capturePageAnchor()
|
||||
var anchor = root.pendingPageAnchor
|
||||
if (!anchor && root.pageRequestPending)
|
||||
anchor = root.capturePageAnchor()
|
||||
root.entriesEpoch += 1
|
||||
var epoch = root.entriesEpoch
|
||||
if (root.nativeModelActive) {
|
||||
@@ -205,12 +216,43 @@ Item {
|
||||
function onLocalTracksNativeJumpsJsonChanged() { root.rebuild() }
|
||||
function onLocalTracksLoadingMoreChanged() {
|
||||
if (!QbzLocal.localTracksLoadingMore)
|
||||
root.pageRequestPending = false
|
||||
pageRequestRelease.restart()
|
||||
}
|
||||
function onLocalTracksLoadingChanged() {
|
||||
// Search/sort/reset supersedes any compatibility-page request.
|
||||
if (QbzLocal.localTracksLoading)
|
||||
if (QbzLocal.localTracksLoading) {
|
||||
pageAnchorSettle.stop()
|
||||
pageRequestRelease.stop()
|
||||
root.restoringPageAnchor = false
|
||||
root.pendingPageAnchor = null
|
||||
root.pageRequestPending = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: pageAnchorSettle
|
||||
interval: 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.restoringPageAnchor = false
|
||||
root.pendingPageAnchor = null
|
||||
root.pageRequestPending = false
|
||||
root.reportSoon()
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: pageRequestRelease
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.restoringPageAnchor) {
|
||||
restart()
|
||||
return
|
||||
}
|
||||
// Error/stale-result escape hatch: if no model publication ever
|
||||
// consumed the request-time anchor, the list must remain usable.
|
||||
root.pendingPageAnchor = null
|
||||
root.pageRequestPending = false
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
|
||||
@@ -531,9 +531,12 @@ mod phase_a_tests {
|
||||
assert!(qml.contains("cell.y - list.contentY"));
|
||||
assert!(!qml.contains("cell.mapToItem(list"));
|
||||
assert!(qml.contains("|| root.restoringPageAnchor"));
|
||||
assert!(qml.contains(
|
||||
"var wanted = list.contentY + currentScreenY - anchor.screenY"
|
||||
));
|
||||
assert!(qml.contains("root.pendingPageAnchor = root.capturePageAnchor()"));
|
||||
assert!(qml.contains("var anchor = root.pendingPageAnchor"));
|
||||
assert!(qml.contains("id: pageAnchorSettle"));
|
||||
assert!(qml.contains("id: pageRequestRelease"));
|
||||
assert!(qml.contains("pageAnchorSettle.restart()"));
|
||||
assert!(qml.contains("var wanted = list.contentY + currentScreenY - anchor.screenY"));
|
||||
assert!(qml.contains("function restorePageAnchor(anchor, nextEntries, epoch)"));
|
||||
assert!(qml.contains("Qt.callLater(function ()"));
|
||||
assert!(qml.contains("list.positionViewAtIndex(target, ListView.Beginning)"));
|
||||
|
||||
Reference in new issue
Block a user