feat(scene): loading heads-up and a 5-minute retry timeout

The ArtistScene discovery fans out many MusicBrainz requests, so a throttled MB
can keep it loading a while. After ~10s the loading screen shows a "this can
take a few minutes" note; after 5 minutes it swaps the spinner for a Retry (a
late success still swaps in the grid on its own). Both derive from the existing
pulse-driven clock, so no new Timer and the repaint law holds. Two new strings
across all eight locales.

cc
This commit is contained in:
vicrodh committed 2026-09-10 13:23:53 -06:00
1 parent 8e8efb00fa
commit 89da9bbd13
9 files changed
+112 -1

No files matched your search

+64 -1
View File
@@ -492,6 +492,10 @@ Rectangle {
// lets this one reset to 0 without touching the shared clock.
property int ms: 0
property int lastPulse: 0
// Derived from the pulse-driven `ms`, so no extra Timer: a busy-
// connection heads-up after ~10s, and a 5-minute safety net.
readonly property bool longWait: loadingLayer.ms > 10000 && !loadingLayer.timedOut
readonly property bool timedOut: loadingLayer.ms > 300000
// S:1551-1554 — `@keyframes pulse`, 2s ease-in-out, 0%/100% scale(1)
// opacity .8, 50% scale(1.06) opacity 1. A raised cosine is that curve.
@@ -505,7 +509,7 @@ Rectangle {
Connections {
target: QbzShell
function onPulseMsChanged() {
if (!root.isLoading)
if (!root.isLoading || loadingLayer.timedOut)
return // a handler that writes nothing costs no frame
var d = QbzShell.pulseMs - loadingLayer.lastPulse
if (d < 0 || d > 500)
@@ -522,6 +526,7 @@ Rectangle {
}
Column {
visible: !loadingLayer.timedOut
anchors.centerIn: parent
spacing: 24 // S:1511-1517
@@ -599,6 +604,64 @@ Rectangle {
font.pixelSize: 12
}
}
// Busy-connection heads-up after the first ~10s so a slow
// MusicBrainz reads as expected, not as a hang.
Text {
visible: loadingLayer.longWait
anchors.horizontalCenter: parent.horizontalCenter
text: root.tr("This can take a few minutes when MusicBrainz is busy.")
color: theme.textMuted
font.pixelSize: 12
width: 320
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
// 5-minute safety net: if discovery is still running this long,
// MusicBrainz is throttling us hard. Offer Retry instead of an endless
// spinner; a late success still swaps in the grid on its own.
Column {
visible: loadingLayer.timedOut
anchors.centerIn: parent
spacing: 16
width: 360
Text {
width: parent.width
text: root.tr("This is taking longer than usual — MusicBrainz may be busy. Try again in a moment.")
color: theme.textSecondary
font.pixelSize: 14
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: sceneTimeoutLabel.implicitWidth + 40
height: root.kioskHost ? 64 : sceneTimeoutLabel.implicitHeight + 16
radius: 8
color: sceneTimeoutArea.containsMouse ? theme.surfaceElevated : theme.surfaceCard
Text {
id: sceneTimeoutLabel
anchors.centerIn: parent
text: root.tr("Retry")
color: theme.textPrimary
font.pixelSize: 13
}
MouseArea {
id: sceneTimeoutArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
// Reset the pulse clock too: the loading layer never went
// invisible, so onVisibleChanged would not re-arm `ms`.
onClicked: {
QbzScene.retry()
loadingLayer.ms = 0
loadingLayer.lastPulse = QbzShell.pulseMs
}
}
}
}
}