feat(qbz-qt): add global compact album headers
cx
This commit is contained in:
15 files changed
+287
-36
No files matched your search
@@ -5985,3 +5985,15 @@ msgstr "Titel-Wellenform"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "Zeigt die Wellenform des gesamten Titels in der Suchleiste des Players an."
|
msgstr "Zeigt die Wellenform des gesamten Titels in der Suchleiste des Players an."
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "Kompakter Kopfbereich"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Weniger Daten in Albumkopfbereichen anzeigen."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Vollständigen Albumkopf anzeigen"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Kompakten Albumkopf anzeigen"
|
||||||
@@ -1587,3 +1587,15 @@ msgstr "Show the full-track waveform in the player seek bar."
|
|||||||
|
|
||||||
msgid "The typeface used across the app. System follows your operating system; the other choices are bundled with QBZ (requires restart)"
|
msgid "The typeface used across the app. System follows your operating system; the other choices are bundled with QBZ (requires restart)"
|
||||||
msgstr "The typeface used across the app. System follows your operating system; the other choices are bundled with QBZ (requires restart)"
|
msgstr "The typeface used across the app. System follows your operating system; the other choices are bundled with QBZ (requires restart)"
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "Compact header"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Show less data in album headers."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Show full album header"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Show compact album header"
|
||||||
@@ -5991,3 +5991,15 @@ msgstr "Forma de onda de la pista"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "Muestra la forma de onda completa de la pista en la barra de búsqueda del reproductor."
|
msgstr "Muestra la forma de onda completa de la pista en la barra de búsqueda del reproductor."
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "Encabezado compacto"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Muestra menos datos en los encabezados de los álbumes."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Mostrar el encabezado completo del álbum"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Mostrar el encabezado compacto del álbum"
|
||||||
@@ -5983,3 +5983,15 @@ msgstr "Forme d’onde du morceau"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "Affiche la forme d’onde complète du morceau dans la barre de lecture."
|
msgstr "Affiche la forme d’onde complète du morceau dans la barre de lecture."
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "En-tête compact"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Afficher moins d’informations dans les en-têtes d’album."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Afficher l’en-tête complet de l’album"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Afficher l’en-tête compact de l’album"
|
||||||
@@ -5967,3 +5967,15 @@ msgstr "トラック波形"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "プレーヤーのシークバーにトラック全体の波形を表示します。"
|
msgstr "プレーヤーのシークバーにトラック全体の波形を表示します。"
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "コンパクトヘッダー"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "アルバムヘッダーに表示する情報を減らします。"
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "アルバムヘッダーをすべて表示"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "アルバムヘッダーをコンパクトに表示"
|
||||||
@@ -5973,3 +5973,15 @@ msgstr "Golfvorm van nummer"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "Toon de golfvorm van het volledige nummer in de zoekbalk van de speler."
|
msgstr "Toon de golfvorm van het volledige nummer in de zoekbalk van de speler."
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "Compacte kop"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Toon minder gegevens in albumkoppen."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Volledige albumkop tonen"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Compacte albumkop tonen"
|
||||||
@@ -5983,3 +5983,15 @@ msgstr "Forma de onda da faixa"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "Mostra a forma de onda completa da faixa na barra de busca do player."
|
msgstr "Mostra a forma de onda completa da faixa na barra de busca do player."
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "Cabeçalho compacto"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Mostrar menos dados nos cabeçalhos de álbuns."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Mostrar cabeçalho completo do álbum"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Mostrar cabeçalho compacto do álbum"
|
||||||
@@ -6003,3 +6003,15 @@ msgstr "Форма волны трека"
|
|||||||
|
|
||||||
msgid "Show the full-track waveform in the player seek bar."
|
msgid "Show the full-track waveform in the player seek bar."
|
||||||
msgstr "Показывать форму волны всего трека на шкале воспроизведения."
|
msgstr "Показывать форму волны всего трека на шкале воспроизведения."
|
||||||
|
|
||||||
|
msgid "Compact header"
|
||||||
|
msgstr "Компактный заголовок"
|
||||||
|
|
||||||
|
msgid "Show less data in album headers."
|
||||||
|
msgstr "Показывать меньше данных в заголовках альбомов."
|
||||||
|
|
||||||
|
msgid "Show full album header"
|
||||||
|
msgstr "Показать полный заголовок альбома"
|
||||||
|
|
||||||
|
msgid "Show compact album header"
|
||||||
|
msgstr "Показать компактный заголовок альбома"
|
||||||
@@ -117,6 +117,14 @@ Column {
|
|||||||
onToggled: function (v) { QbzBridge.settingsBool("album-header-gradient", v) }
|
onToggled: function (v) { QbzBridge.settingsBool("album-header-gradient", v) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SettingRow {
|
||||||
|
label: QbzSession.tr("Compact header", QbzSession.trRev)
|
||||||
|
description: QbzSession.tr("Show less data in album headers.", QbzSession.trRev)
|
||||||
|
QbzToggle {
|
||||||
|
checked: root.doc.compactAlbumHeader === true
|
||||||
|
onToggled: function (v) { QbzBridge.settingsBool("compact-album-header", v) }
|
||||||
|
}
|
||||||
|
}
|
||||||
SettingRow {
|
SettingRow {
|
||||||
label: QbzSession.tr("Dynamic background", QbzSession.trRev)
|
label: QbzSession.tr("Dynamic background", QbzSession.trRev)
|
||||||
description: QbzSession.tr("Animated album-art background behind the whole app. High resource use — GPU accelerated.", QbzSession.trRev)
|
description: QbzSession.tr("Animated album-art background behind the whole app. High resource use — GPU accelerated.", QbzSession.trRev)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Rectangle {
|
|||||||
/// twin and both text columns all measure off it — they drifted apart at
|
/// twin and both text columns all measure off it — they drifted apart at
|
||||||
/// five separate literals before. The Row has no explicit height, so the
|
/// five separate literals before. The Row has no explicit height, so the
|
||||||
/// header grows with this on its own.
|
/// header grows with this on its own.
|
||||||
readonly property int coverPx: 260
|
readonly property int coverPx: compactHeaderPref ? 130 : 260
|
||||||
|
|
||||||
/// How far past the header divider the artwork atmosphere reaches: the 8px
|
/// How far past the header divider the artwork atmosphere reaches: the 8px
|
||||||
/// spacer below the divider plus half of the 52px track toolbar band, so
|
/// spacer below the divider plus half of the 52px track toolbar band, so
|
||||||
@@ -106,6 +106,21 @@ Rectangle {
|
|||||||
|
|
||||||
readonly property var albumHeader: album.header || ({})
|
readonly property var albumHeader: album.header || ({})
|
||||||
readonly property var tracks: album.tracks || []
|
readonly property var tracks: album.tracks || []
|
||||||
|
// One global opt-in for every album page. `settingsJson` is the live
|
||||||
|
// channel (including clicks made in another open AlbumView); the album
|
||||||
|
// document carries the persisted cold-start fallback until that snapshot
|
||||||
|
// arrives.
|
||||||
|
readonly property bool compactHeaderPref: {
|
||||||
|
var raw = QbzBridge.settingsJson
|
||||||
|
if (raw && raw.length > 2) {
|
||||||
|
try {
|
||||||
|
var d = JSON.parse(raw)
|
||||||
|
if (d.compactAlbumHeader !== undefined)
|
||||||
|
return d.compactAlbumHeader === true
|
||||||
|
} catch (e) { /* fall through to the document copy */ }
|
||||||
|
}
|
||||||
|
return album.compactHeader === true
|
||||||
|
}
|
||||||
// `header.awards`, NOT `album.awards`. It is a field of AlbumHeader
|
// `header.awards`, NOT `album.awards`. It is a field of AlbumHeader
|
||||||
// (src/album_qt.rs:171), not of AlbumViewData — read one level too high it
|
// (src/album_qt.rs:171), not of AlbumViewData — read one level too high it
|
||||||
// is `undefined`, `|| []` swallows that, and the AWARDS block simply never
|
// is `undefined`, `|| []` swallows that, and the AWARDS block simply never
|
||||||
@@ -785,13 +800,36 @@ Rectangle {
|
|||||||
anchors.topMargin: 4
|
anchors.topMargin: 4
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Text {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: albumHeader.title || ""
|
height: Math.max(albumTitle.implicitHeight, 32)
|
||||||
color: root.hdrStrong
|
Text {
|
||||||
font.pixelSize: theme.fontSection
|
id: albumTitle
|
||||||
font.weight: theme.weightBold
|
anchors.left: parent.left
|
||||||
elide: Text.ElideRight
|
anchors.right: headerModeButton.left
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: albumHeader.title || ""
|
||||||
|
color: root.hdrStrong
|
||||||
|
font.pixelSize: theme.fontSection
|
||||||
|
font.weight: theme.weightBold
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
QbzCircleAction {
|
||||||
|
id: headerModeButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: root.compactHeaderPref ? "maximize-2" : "minimize-2"
|
||||||
|
overlay: root.hdrOverlay
|
||||||
|
onClicked: QbzBridge.settingsBool(
|
||||||
|
"compact-album-header", !root.compactHeaderPref)
|
||||||
|
HoverHandler { id: headerModeHover }
|
||||||
|
ToolTip.visible: headerModeHover.hovered
|
||||||
|
ToolTip.text: root.compactHeaderPref
|
||||||
|
? QbzSession.tr("Show full album header", QbzSession.trRev)
|
||||||
|
: QbzSession.tr("Show compact album header", QbzSession.trRev)
|
||||||
|
ToolTip.delay: 350
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Item { width: 1; height: 4 }
|
Item { width: 1; height: 4 }
|
||||||
// Credited-artist line (links + role suffixes).
|
// Credited-artist line (links + role suffixes).
|
||||||
@@ -799,7 +837,9 @@ Rectangle {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Repeater {
|
Repeater {
|
||||||
model: albumHeader.credits || []
|
model: root.compactHeaderPref
|
||||||
|
? [[albumHeader.artist || "", albumHeader.artistId || "", ""]]
|
||||||
|
: (albumHeader.credits || [])
|
||||||
delegate: Row {
|
delegate: Row {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
@@ -834,11 +874,13 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item { width: 1; height: 10 }
|
Item { width: 1; height: root.compactHeaderPref ? 4 : 10 }
|
||||||
// Meta line (label as a clickable link when navigable).
|
// Meta line (label as a clickable link when navigable).
|
||||||
Row {
|
Row {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
visible: (albumHeader.labelId || "") !== "" && (albumHeader.label || "") !== ""
|
visible: !root.compactHeaderPref
|
||||||
|
&& (albumHeader.labelId || "") !== ""
|
||||||
|
&& (albumHeader.label || "") !== ""
|
||||||
Text {
|
Text {
|
||||||
visible: (albumHeader.metaPre || "") !== ""
|
visible: (albumHeader.metaPre || "") !== ""
|
||||||
text: (albumHeader.metaPre || "") + " • "
|
text: (albumHeader.metaPre || "") + " • "
|
||||||
@@ -865,27 +907,51 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
visible: (albumHeader.labelId || "") === "" || (albumHeader.label || "") === ""
|
visible: !root.compactHeaderPref
|
||||||
|
&& ((albumHeader.labelId || "") === ""
|
||||||
|
|| (albumHeader.label || "") === "")
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: albumHeader.infoLine || ""
|
text: albumHeader.infoLine || ""
|
||||||
color: root.hdrBody
|
color: root.hdrBody
|
||||||
font.pixelSize: theme.fontBody
|
font.pixelSize: theme.fontBody
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
Text {
|
||||||
|
visible: root.compactHeaderPref
|
||||||
|
width: parent.width
|
||||||
|
text: albumHeader.compactInfoLine || ""
|
||||||
|
color: root.hdrBody
|
||||||
|
font.pixelSize: theme.fontBody
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
// Editorial description + Read more.
|
// Editorial description + Read more.
|
||||||
Item { visible: (albumHeader.description || "") !== ""; width: 1; height: 12 }
|
Item {
|
||||||
|
visible: !root.compactHeaderPref
|
||||||
|
&& (albumHeader.description || "") !== ""
|
||||||
|
width: 1
|
||||||
|
height: 12
|
||||||
|
}
|
||||||
Text {
|
Text {
|
||||||
visible: (albumHeader.description || "") !== ""
|
visible: !root.compactHeaderPref
|
||||||
|
&& (albumHeader.description || "") !== ""
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: albumHeader.descriptionShort || ""
|
text: albumHeader.descriptionShort || ""
|
||||||
color: root.hdrBody
|
color: root.hdrBody
|
||||||
font.pixelSize: theme.fontLegal
|
font.pixelSize: theme.fontLegal
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
Item { visible: (albumHeader.description || "") !== (albumHeader.descriptionShort || ""); width: 1; height: 4 }
|
Item {
|
||||||
|
visible: !root.compactHeaderPref
|
||||||
|
&& (albumHeader.description || "")
|
||||||
|
!== (albumHeader.descriptionShort || "")
|
||||||
|
width: 1
|
||||||
|
height: 4
|
||||||
|
}
|
||||||
Text {
|
Text {
|
||||||
visible: (albumHeader.description || "") !== (albumHeader.descriptionShort || "")
|
visible: !root.compactHeaderPref
|
||||||
|
&& (albumHeader.description || "")
|
||||||
|
!== (albumHeader.descriptionShort || "")
|
||||||
text: QbzSession.tr("Read more", QbzSession.trRev)
|
text: QbzSession.tr("Read more", QbzSession.trRev)
|
||||||
color: readMoreArea.containsMouse ? theme.accentHover : theme.accent
|
color: readMoreArea.containsMouse ? theme.accentHover : theme.accent
|
||||||
font.pixelSize: theme.fontLegal
|
font.pixelSize: theme.fontLegal
|
||||||
@@ -902,7 +968,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { width: 1; height: 20 }
|
Item { width: 1; height: root.compactHeaderPref ? 8 : 20 }
|
||||||
// Action row — AlbumPageView.slint:504-640. One shared
|
// Action row — AlbumPageView.slint:504-640. One shared
|
||||||
// CircleAction for every button including Play (the
|
// CircleAction for every button including Play (the
|
||||||
// hand-rolled 44px disc it used to be drifted from the
|
// hand-rolled 44px disc it used to be drifted from the
|
||||||
|
|||||||
@@ -44,6 +44,20 @@ Rectangle {
|
|||||||
readonly property var album: doc ? doc.album : null
|
readonly property var album: doc ? doc.album : null
|
||||||
readonly property var tracks: doc ? doc.tracks : []
|
readonly property var tracks: doc ? doc.tracks : []
|
||||||
readonly property var versions: album && album.versions ? album.versions : []
|
readonly property var versions: album && album.versions ? album.versions : []
|
||||||
|
// Same global preference as the Qobuz AlbumView. The document value is
|
||||||
|
// only the cold-start fallback; settingsJson makes a toggle in any open
|
||||||
|
// album update every other album immediately.
|
||||||
|
readonly property bool compactHeaderPref: {
|
||||||
|
var raw = QbzBridge.settingsJson
|
||||||
|
if (raw && raw.length > 2) {
|
||||||
|
try {
|
||||||
|
var d = JSON.parse(raw)
|
||||||
|
if (d.compactAlbumHeader !== undefined)
|
||||||
|
return d.compactAlbumHeader === true
|
||||||
|
} catch (e) { /* fall through to the document copy */ }
|
||||||
|
}
|
||||||
|
return doc && doc.compactHeader === true
|
||||||
|
}
|
||||||
readonly property var allArtists: {
|
readonly property var allArtists: {
|
||||||
if (!album) return []
|
if (!album) return []
|
||||||
var raw = (album.allArtists || "").split(",")
|
var raw = (album.allArtists || "").split(",")
|
||||||
@@ -66,6 +80,14 @@ Rectangle {
|
|||||||
if ((album.format || "") !== "") parts.push(album.format.toUpperCase())
|
if ((album.format || "") !== "") parts.push(album.format.toUpperCase())
|
||||||
return parts.join(" • ")
|
return parts.join(" • ")
|
||||||
}
|
}
|
||||||
|
readonly property string compactInfoLine: {
|
||||||
|
if (!album) return ""
|
||||||
|
var parts = []
|
||||||
|
if ((album.year || "") !== "") parts.push(album.year)
|
||||||
|
parts.push((album.trackCount || 0) + " "
|
||||||
|
+ QbzSession.tr("tracks", QbzSession.trRev))
|
||||||
|
return parts.join(" • ")
|
||||||
|
}
|
||||||
|
|
||||||
property string trackQuery: ""
|
property string trackQuery: ""
|
||||||
// Client-side track search: an album's track list is bounded, so the
|
// Client-side track search: an album's track list is bounded, so the
|
||||||
@@ -378,9 +400,9 @@ Rectangle {
|
|||||||
visible: root.pageLoading && !root.album
|
visible: root.pageLoading && !root.album
|
||||||
variant: "header"
|
variant: "header"
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 224
|
height: root.compactHeaderPref ? 128 : 224
|
||||||
coverSize: 224
|
coverSize: root.compactHeaderPref ? 128 : 224
|
||||||
coverGap: 32
|
coverGap: root.compactHeaderPref ? 20 : 32
|
||||||
actionCount: 5
|
actionCount: 5
|
||||||
phase: root.skelPhase
|
phase: root.skelPhase
|
||||||
}
|
}
|
||||||
@@ -390,12 +412,16 @@ Rectangle {
|
|||||||
album: root.album
|
album: root.album
|
||||||
allArtists: root.allArtists
|
allArtists: root.allArtists
|
||||||
infoLine: root.infoLine
|
infoLine: root.infoLine
|
||||||
|
compactInfoLine: root.compactInfoLine
|
||||||
|
compact: root.compactHeaderPref
|
||||||
versions: root.versions
|
versions: root.versions
|
||||||
coverSource: root.album ? (root.artMap[root.album.artKey] || "") : ""
|
coverSource: root.album ? (root.artMap[root.album.artKey] || "") : ""
|
||||||
coverPending: root.coverPending
|
coverPending: root.coverPending
|
||||||
skelPhase: root.skelPhase
|
skelPhase: root.skelPhase
|
||||||
artSettleMs: root.artSettleMs
|
artSettleMs: root.artSettleMs
|
||||||
onOpenArtist: function (name) { root.openArtist(name) }
|
onOpenArtist: function (name) { root.openArtist(name) }
|
||||||
|
onCompactToggled: QbzBridge.settingsBool(
|
||||||
|
"compact-album-header", !root.compactHeaderPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { width: 1; height: 20 }
|
Item { width: 1; height: 20 }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// Slint watches its own mirror property for exactly this (:156).
|
// Slint watches its own mirror property for exactly this (:156).
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
import com.blitzfc.qbz
|
import com.blitzfc.qbz
|
||||||
import "../../controls"
|
import "../../controls"
|
||||||
import "../../theme"
|
import "../../theme"
|
||||||
@@ -18,6 +19,8 @@ Row {
|
|||||||
/// De-duplicated contributor list (>1 turns the expander on).
|
/// De-duplicated contributor list (>1 turns the expander on).
|
||||||
property var allArtists: []
|
property var allArtists: []
|
||||||
property string infoLine: ""
|
property string infoLine: ""
|
||||||
|
property string compactInfoLine: ""
|
||||||
|
property bool compact: false
|
||||||
property var versions: []
|
property var versions: []
|
||||||
property string coverSource: ""
|
property string coverSource: ""
|
||||||
/// Per-item cover state, owned by the page (LocalAlbumView).
|
/// Per-item cover state, owned by the page (LocalAlbumView).
|
||||||
@@ -33,17 +36,19 @@ Row {
|
|||||||
property bool skelPhase: false
|
property bool skelPhase: false
|
||||||
property int artSettleMs: 0
|
property int artSettleMs: 0
|
||||||
signal openArtist(string name)
|
signal openArtist(string name)
|
||||||
|
signal compactToggled()
|
||||||
|
|
||||||
QbzTheme { id: theme }
|
QbzTheme { id: theme }
|
||||||
|
|
||||||
property bool artistsExpanded: false
|
property bool artistsExpanded: false
|
||||||
onAlbumChanged: artistsExpanded = false
|
onAlbumChanged: artistsExpanded = false
|
||||||
|
|
||||||
spacing: 32
|
readonly property int coverPx: compact ? 128 : 224
|
||||||
|
spacing: compact ? 20 : 32
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 224
|
width: root.coverPx
|
||||||
height: 224
|
height: root.coverPx
|
||||||
radius: 12
|
radius: 12
|
||||||
color: theme.surfaceElevated
|
color: theme.surfaceElevated
|
||||||
clip: true
|
clip: true
|
||||||
@@ -68,17 +73,38 @@ Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width - 224 - 32
|
width: parent.width - root.coverPx - root.spacing
|
||||||
topPadding: 4
|
topPadding: 4
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Text {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: root.album ? (root.album.title || "") : ""
|
height: Math.max(localAlbumTitle.implicitHeight, 32)
|
||||||
color: theme.textPrimary
|
Text {
|
||||||
font.pixelSize: theme.fontSection
|
id: localAlbumTitle
|
||||||
font.weight: theme.weightBold
|
anchors.left: parent.left
|
||||||
elide: Text.ElideRight
|
anchors.right: localHeaderModeButton.left
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.album ? (root.album.title || "") : ""
|
||||||
|
color: theme.textPrimary
|
||||||
|
font.pixelSize: theme.fontSection
|
||||||
|
font.weight: theme.weightBold
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
QbzCircleAction {
|
||||||
|
id: localHeaderModeButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: root.compact ? "maximize-2" : "minimize-2"
|
||||||
|
onClicked: root.compactToggled()
|
||||||
|
HoverHandler { id: localHeaderModeHover }
|
||||||
|
ToolTip.visible: localHeaderModeHover.hovered
|
||||||
|
ToolTip.text: root.compact
|
||||||
|
? QbzSession.tr("Show full album header", QbzSession.trRev)
|
||||||
|
: QbzSession.tr("Show compact album header", QbzSession.trRev)
|
||||||
|
ToolTip.delay: 350
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Item { width: 1; height: 4 }
|
Item { width: 1; height: 4 }
|
||||||
Row {
|
Row {
|
||||||
@@ -99,7 +125,7 @@ Row {
|
|||||||
// Multi-artist album (compilations): "+N more artists" expands
|
// Multi-artist album (compilations): "+N more artists" expands
|
||||||
// the full track-artist list below.
|
// the full track-artist list below.
|
||||||
Text {
|
Text {
|
||||||
visible: root.allArtists.length > 1
|
visible: !root.compact && root.allArtists.length > 1
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: root.artistsExpanded
|
text: root.artistsExpanded
|
||||||
? QbzSession.tr("Show less", QbzSession.trRev)
|
? QbzSession.tr("Show less", QbzSession.trRev)
|
||||||
@@ -119,7 +145,8 @@ Row {
|
|||||||
// Expanded track-artist list — each entry routes to the Local
|
// Expanded track-artist list — each entry routes to the Local
|
||||||
// Library Artists tab (name-based).
|
// Library Artists tab (name-based).
|
||||||
Column {
|
Column {
|
||||||
visible: root.artistsExpanded && root.allArtists.length > 1
|
visible: !root.compact && root.artistsExpanded
|
||||||
|
&& root.allArtists.length > 1
|
||||||
width: parent.width
|
width: parent.width
|
||||||
topPadding: 6
|
topPadding: 6
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@@ -143,15 +170,15 @@ Row {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item { width: 1; height: 10 }
|
Item { width: 1; height: root.compact ? 4 : 10 }
|
||||||
Text {
|
Text {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: root.infoLine
|
text: root.compact ? root.compactInfoLine : root.infoLine
|
||||||
color: theme.textSecondary
|
color: theme.textSecondary
|
||||||
font.pixelSize: theme.fontBody
|
font.pixelSize: theme.fontBody
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
Item { width: 1; height: 20 }
|
Item { width: 1; height: root.compact ? 8 : 20 }
|
||||||
|
|
||||||
// ---- Local action row ----
|
// ---- Local action row ----
|
||||||
Row {
|
Row {
|
||||||
@@ -186,12 +213,12 @@ Row {
|
|||||||
|
|
||||||
// ---- Version picker (multiple physical copies only) ----
|
// ---- Version picker (multiple physical copies only) ----
|
||||||
Item {
|
Item {
|
||||||
visible: root.versions.length > 1
|
visible: !root.compact && root.versions.length > 1
|
||||||
width: 1
|
width: 1
|
||||||
height: visible ? 16 : 0
|
height: visible ? 16 : 0
|
||||||
}
|
}
|
||||||
Row {
|
Row {
|
||||||
visible: root.versions.length > 1
|
visible: !root.compact && root.versions.length > 1
|
||||||
spacing: 8
|
spacing: 8
|
||||||
Text {
|
Text {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ pub struct AlbumHeader {
|
|||||||
pub credits: Vec<(String, String, String)>,
|
pub credits: Vec<(String, String, String)>,
|
||||||
#[serde(rename = "infoLine")]
|
#[serde(rename = "infoLine")]
|
||||||
pub info_line: String,
|
pub info_line: String,
|
||||||
|
/// Compact header metadata: release date + track count only. Label,
|
||||||
|
/// genre and duration intentionally stay in the expanded header.
|
||||||
|
#[serde(rename = "compactInfoLine")]
|
||||||
|
pub compact_info_line: String,
|
||||||
#[serde(rename = "metaPre")]
|
#[serde(rename = "metaPre")]
|
||||||
pub meta_pre: String,
|
pub meta_pre: String,
|
||||||
#[serde(rename = "metaPost")]
|
#[serde(rename = "metaPost")]
|
||||||
@@ -234,6 +238,11 @@ pub struct AlbumHeader {
|
|||||||
pub struct AlbumViewData {
|
pub struct AlbumViewData {
|
||||||
pub header: AlbumHeader,
|
pub header: AlbumHeader,
|
||||||
pub tracks: Vec<TrackRow>,
|
pub tracks: Vec<TrackRow>,
|
||||||
|
/// Cold-start fallback for the live `settingsJson` preference consumed by
|
||||||
|
/// AlbumView.qml. The view switches to the settings document as soon as it
|
||||||
|
/// is published, so toggling one open album updates every other instance.
|
||||||
|
#[serde(rename = "compactHeader")]
|
||||||
|
pub compact_header: bool,
|
||||||
#[serde(rename = "moreFromArtist")]
|
#[serde(rename = "moreFromArtist")]
|
||||||
pub more_from_artist: Vec<AlbumCardData>,
|
pub more_from_artist: Vec<AlbumCardData>,
|
||||||
pub suggestions: Vec<AlbumCardData>,
|
pub suggestions: Vec<AlbumCardData>,
|
||||||
@@ -601,6 +610,11 @@ pub async fn load_album(
|
|||||||
}
|
}
|
||||||
let meta_pre = pre_parts.join(" • ");
|
let meta_pre = pre_parts.join(" • ");
|
||||||
let meta_post = post_parts.join(" • ");
|
let meta_post = post_parts.join(" • ");
|
||||||
|
let mut compact_parts = pre_parts.clone();
|
||||||
|
if let Some(tc) = &tracks_str {
|
||||||
|
compact_parts.push(tc.clone());
|
||||||
|
}
|
||||||
|
let compact_info_line = compact_parts.join(" • ");
|
||||||
let mut all_parts = pre_parts.clone();
|
let mut all_parts = pre_parts.clone();
|
||||||
if let Some(l) = &label_name {
|
if let Some(l) = &label_name {
|
||||||
all_parts.push(l.clone());
|
all_parts.push(l.clone());
|
||||||
@@ -679,6 +693,7 @@ pub async fn load_album(
|
|||||||
artist_id,
|
artist_id,
|
||||||
credits,
|
credits,
|
||||||
info_line,
|
info_line,
|
||||||
|
compact_info_line,
|
||||||
meta_pre,
|
meta_pre,
|
||||||
meta_post,
|
meta_post,
|
||||||
quality_tier: home_qt::quality_tier_from_depth(album.maximum_bit_depth).to_string(),
|
quality_tier: home_qt::quality_tier_from_depth(album.maximum_bit_depth).to_string(),
|
||||||
@@ -1229,6 +1244,7 @@ pub async fn load_album_view(
|
|||||||
// lands as a patch a moment later. `headerColor` stays "" until then and
|
// lands as a patch a moment later. `headerColor` stays "" until then and
|
||||||
// the band simply does not paint — no colour pop.
|
// the band simply does not paint — no colour pop.
|
||||||
data.header_gradient = crate::settings_qt::pref_bool("album_header_gradient", true);
|
data.header_gradient = crate::settings_qt::pref_bool("album_header_gradient", true);
|
||||||
|
data.compact_header = crate::settings_qt::pref_bool("compact_album_header", false);
|
||||||
let header_art = data.header.artwork_url.clone();
|
let header_art = data.header.artwork_url.clone();
|
||||||
|
|
||||||
let json = serde_json::to_string(&data).map_err(|e| e.to_string())?;
|
let json = serde_json::to_string(&data).map_err(|e| e.to_string())?;
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ pub struct AlbumDetailDoc {
|
|||||||
/// EMPTY for a single-disc album. Present only so a multi-disc box can
|
/// EMPTY for a single-disc album. Present only so a multi-disc box can
|
||||||
/// label and illustrate its dividers — see `DiscRow`.
|
/// label and illustrate its dividers — see `DiscRow`.
|
||||||
pub discs: Vec<DiscRow>,
|
pub discs: Vec<DiscRow>,
|
||||||
|
/// Cold-start fallback for the global compact-header appearance pref.
|
||||||
|
/// LocalAlbumView switches to the live settings document once published.
|
||||||
|
#[serde(rename = "compactHeader")]
|
||||||
|
pub compact_header: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The bounded payload consumed by one expanded row in the Genres browser.
|
/// The bounded payload consumed by one expanded row in the Genres browser.
|
||||||
@@ -346,6 +350,7 @@ pub fn version_doc(id: &str, index: usize) -> Option<AlbumDetailDoc> {
|
|||||||
},
|
},
|
||||||
tracks: rows,
|
tracks: rows,
|
||||||
discs,
|
discs,
|
||||||
|
compact_header: crate::settings_qt::pref_bool("compact_album_header", false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1658,6 +1658,8 @@ pub struct SettingsDoc {
|
|||||||
// Appearance (phase 19; the ui_prefs defaults mirror ui_prefs.rs)
|
// Appearance (phase 19; the ui_prefs defaults mirror ui_prefs.rs)
|
||||||
#[serde(rename = "albumHeaderGradient")]
|
#[serde(rename = "albumHeaderGradient")]
|
||||||
pub album_header_gradient: bool,
|
pub album_header_gradient: bool,
|
||||||
|
#[serde(rename = "compactAlbumHeader")]
|
||||||
|
pub compact_album_header: bool,
|
||||||
#[serde(rename = "appBackgroundModes")]
|
#[serde(rename = "appBackgroundModes")]
|
||||||
pub app_background_modes: Vec<String>,
|
pub app_background_modes: Vec<String>,
|
||||||
#[serde(rename = "appBackgroundIndex")]
|
#[serde(rename = "appBackgroundIndex")]
|
||||||
@@ -2111,6 +2113,7 @@ pub async fn publish_snapshot() {
|
|||||||
qconnect_device_name: qconnect_load_device_name().unwrap_or_default(),
|
qconnect_device_name: qconnect_load_device_name().unwrap_or_default(),
|
||||||
qconnect_device_name_default: qconnect_default_name(),
|
qconnect_device_name_default: qconnect_default_name(),
|
||||||
album_header_gradient: pref_bool("album_header_gradient", true),
|
album_header_gradient: pref_bool("album_header_gradient", true),
|
||||||
|
compact_album_header: pref_bool("compact_album_header", false),
|
||||||
app_background_modes: APP_BACKGROUND_LABELS
|
app_background_modes: APP_BACKGROUND_LABELS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|l| qbz_i18n::t(l))
|
.map(|l| qbz_i18n::t(l))
|
||||||
@@ -2401,6 +2404,10 @@ pub async fn settings_bool(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &str,
|
|||||||
save_pref("album_header_gradient", serde_json::json!(value));
|
save_pref("album_header_gradient", serde_json::json!(value));
|
||||||
Ok(Apply::None)
|
Ok(Apply::None)
|
||||||
}
|
}
|
||||||
|
"compact-album-header" => {
|
||||||
|
save_pref("compact_album_header", serde_json::json!(value));
|
||||||
|
Ok(Apply::None)
|
||||||
|
}
|
||||||
"intelligent-search" => {
|
"intelligent-search" => {
|
||||||
save_pref("intelligent_search", serde_json::json!(value));
|
save_pref("intelligent_search", serde_json::json!(value));
|
||||||
// Flip the LIVE kill switch too, not just the pref: the service is
|
// Flip the LIVE kill switch too, not just the pref: the service is
|
||||||
|
|||||||
Reference in new issue
Block a user