feat(qt): "Where you left off" reopens the exact page, its arguments and its tab

The startup pref only knew the last ROOT view: `last_view` lives in the
ui_prefs.json the Slint build shares, whose vocabulary has no room for a
detail, so a session closed on an album came back on Library > Releases.

page_restore_qt keeps the page itself in a per-profile document
(last_page_qt.json: root, view, args, state), written as the user moves.
Openers that can rebuild their page record their arguments through the
new nav_qt::record_with (album, artist, playlist, label and its releases,
mixtape/collection detail, award and its albums, mix, artist releases,
discover browse); the Settings section, the search query and tab and the
purchased album are noted where they live; the mounted view's tab comes
from QbzShell.reportNavState. At startup the shell mounts that page's
view (no Home flash), the history is seeded with the page's root so Back
leads there, and the session entry re-runs the opener — one-shot, behind
the same gates as the local album restore (remember pref, crash chain,
launcher link, kiosk), skipping catalog pages while offline and Purchases
unless it is switched on. Pages only their opener could rebuild (scene,
musician, disco builder, metadata editor) keep the previous restorable
page on record; local albums stay with local_restore_qt.

ContentRouter now reports the active tab of any view that has one and no
richer state of its own (Discover, Library) and applies a Back/Forward
destination's saved tab, so tabs survive both a restart and the history.

cc
This commit is contained in:
vicrodh committed 2026-09-13 23:56:10 -06:00
1 parent dae03e3834
commit e278d931b7
13 files changed
+801 -35

No files matched your search

+41
View File
@@ -44,6 +44,46 @@ Item {
}
}
// THE PAGE'S TAB, LIVE (2026-09-13). Any mounted view with an `activeTab`
// string that does not report a richer state of its own (Local Library
// does, and restores it itself) reports its tab on every change: nav_qt
// stamps it on the history entry, so Back/Forward land on the tab the
// user left, and page_restore_qt keeps it in the per-profile page
// document, so "Where you left off" reopens on it. Never during the
// two-phase route commit, when the item on screen is still the outgoing
// view while `currentView` already names the next one.
Connections {
target: viewLoader.item
ignoreUnknownSignals: true
function onActiveTabChanged() { root._reportTab() }
}
function _reportTab() {
var item = viewLoader.item
if (!item || root._armed || root.kiosk)
return
if (typeof item.activeTab !== "string" || typeof item.navigationStateJson === "string")
return
QbzShell.reportNavState(QbzShell.currentView, JSON.stringify({ "activeTab": item.activeTab }))
}
// The other direction: a Back/Forward destination whose entry carries a
// tab (armed by nav_qt::step before `currentView` is published) mounts
// on it. Views that restore their own state consume the arming
// themselves and are left alone.
function _restoreTab() {
var item = viewLoader.item
if (!item || root.kiosk || QbzShell.restoreStateScope === "")
return
if (QbzShell.restoreStateScope !== QbzShell.currentView)
return
if (typeof item.activeTab !== "string" || typeof item.restoreNavigationState === "function")
return
var saved = null
try { saved = JSON.parse(QbzShell.stateRestore) } catch (e) { saved = null }
QbzShell.restoreStateScope = ""
if (saved && typeof saved.activeTab === "string" && saved.activeTab !== "")
item.activeTab = saved.activeTab
}
// The mounted view. The replacement for AppShell's four `viewLoader.item`
// references (multi-select: the two Ctrl+A / Escape routers and the two
// duck-typed reporters) — a QML `id` is scoped to its own document, so
@@ -595,6 +635,7 @@ Item {
&& typeof viewLoader.item.activeTab === "string"
root._itemTabRequest = viewLoader.item !== null
&& typeof viewLoader.item.tabNavigationRequest !== "undefined"
root._restoreTab()
}
}