Files
qbz/crates/qbz-qt/qml/settings/BlacklistSettings.qml
T
vicrodh 494596616e feat(qt): port the Blacklist manager for artists and albums
The port had a 67-line settings row showing two counters and a header saying
the Manage chevron was "NOT shipped — this port has no Blacklist Manager view
for it to open". The Slint has 1,114 lines of manager plus the consumer surfaces
that make the feature mean anything.

- src/artist_blacklist.rs is a 1:1 port of the reference's per-user lifecycle,
  including the fail-open asymmetry (is_enabled reads TRUE when there is no
  session, everything else false/empty) and the byte-identical NO_SESSION_ERR.
  src/reco_dismiss_qt.rs carries the reco-dismissal store. Both are bound in
  ALL THREE session-activation paths — the offline one included, because the
  reference records that Tauri never bound it offline and blacklisted artists
  leaked into offline surfaces — and torn down on logout, or the next account
  inherits them.
- The manager view: three tabs (bordered, ADR-008 — no pills), search, sort,
  the enable toggle, the disabled warning banner, the four body branches per
  tab in their load-bearing order, windowed lists, and the clear-all confirm.
  Reached from Settings only, exactly like the Slint; no sidebar entry.
- Every one of the ten store mutations publishes TWICE — its own document and
  publish_settings() — because the Settings counters live in a different
  document and nav_qt::back() re-runs no per-view load. Without it,
  Manage -> unblock -> Back showed pre-mutation counters. The Slint gets this
  free from a shared global.
- The search field is driven imperatively, not by a binding: QbzLineEdit's X
  used to destroy the binding, after which the box went blank while the list
  stayed filtered.
- Consumer surfaces, which is where the feature actually lives: the album card
  and album list row gained "Block this album", the artist card gained the
  3-arg "Not interested", the album header menu gained Block/Unblock swapping
  on state, and the artist page gained the Show/Blacklist flip plus the amber
  hidden banner with its "Show artist" button. album_qt and artist_qt now seed
  isAlbumBlocked / isBlacklisted at document build time — without the seed an
  already-blocked album offered "Block this album" and the first click
  UN-blocked it, the same lie the pin glyph told before isPinned existed.
- search_qt passed &Default::default() for both blacklist snapshots, so nothing
  was filtered anywhere and a greyed row still played. It now passes the real
  snapshots at both call sites.
- The 17 toasts the spec had dropped for want of a toast host are wired: that
  premise stopped being true when toast_qt landed, and a Clear All that failed
  with NO_SESSION_ERR was completely silent. Their msgids are in all eight
  catalogues; the five album-axis ones existed only in es and nl.

Also fixed in passing, pre-existing on this branch and unrelated to the
blacklist: ArtistView's library tab read `libTracks` unqualified from a child
of the object that declares it, which QML resolves against the component root,
not the enclosing object — so the "Tracks" heading threw a ReferenceError and
never rendered. Found by forcing the artist route in the offscreen smoke, which
no earlier gate did.

cc
2026-07-29 21:24:12 -06:00

105 lines
4.3 KiB
QML

// Settings > Blacklist — the QML port of crates/qbz-ui/ui/settings/
// ContentFilteringSettings.slint: ONE 64px row (blind-eye glyph + "Blacklist" +
// "{n} artists, {m} albums blocked", with a "(disabled)" suffix when the
// feature is off) and a right-aligned "Manage" button that opens the full-page
// Blacklist Manager. There is no enable toggle here in the Slint either
// (:13-16) — the switch lives inside the manager.
//
// Layout, verbatim from the reference (:66-115): outer spacing 4; a 64px row
// with spacing 24; the left group horizontal-stretch 1 / spacing 12 with the
// blind-eye 18x18 tint secondary, then a text column at spacing 3 holding
// "Blacklist" (text-primary 15 medium) over the status line (text-muted, a
// LITERAL 12px — not a Typography token); the ManageButton right-aligned and
// vertically centred.
//
// ManageButton (:26-64) is `width: max(content + 32, 160)`, height 34, Radius.sm,
// 1px border-subtle, surfaceHover on hover else surfaceElevated, content centred
// with spacing 6: label "Manage" text-secondary 15 medium, then chevron-right
// 16x16 tint secondary. controls/SettingsButton.qml already matches every one of
// those metrics, so it is reused with a trailing-icon slot rather than forked.
// Accepted deltas (the port's settings-button voice): content padding 24 vs 32,
// icon 15 vs 16, label textPrimary vs textSecondary.
//
// The counters come from the per-user blacklist singleton
// (crate::artist_blacklist, published into the settings document by
// settings_qt/devtools.rs::blacklist_counts), so they tell the truth
// immediately after a mutation — every blacklist mutation republishes the
// settings document as well as its own, which is what makes
// Manage -> unblock -> Back show fresh numbers.
import QtQuick
import com.blitzfc.qbz
import "../controls"
import "../theme"
Column {
id: root
property var doc: ({})
readonly property var dev: doc.dev || ({})
QbzTheme { id: theme }
spacing: 4
Item {
width: parent.width
height: 64
Row {
anchors.fill: parent
// The Slint's 24px gap between the left group and the button.
spacing: 24
Row {
id: leftGroup
width: Math.max(0, parent.width - 24 - manageBtn.width)
anchors.verticalCenter: parent.verticalCenter
spacing: 12
QbzIcon {
anchors.verticalCenter: parent.verticalCenter
name: "blind-eye"
width: 18
height: 18
tintName: "secondary"
}
Column {
// 30 = the 18px glyph + the 12px row spacing.
width: Math.max(0, parent.width - 30)
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Text {
width: parent.width
text: QbzSession.tr("Blacklist", QbzSession.trRev)
color: theme.textPrimary
font.pixelSize: theme.fontBody
font.weight: theme.weightMedium
}
Text {
width: parent.width
// Two placeholders, replaced left to right:
// artists first, then albums.
text: (root.dev.blacklistEnabled === false
? QbzSession.tr("{} artists, {} albums blocked (disabled)", QbzSession.trRev)
: QbzSession.tr("{} artists, {} albums blocked", QbzSession.trRev))
.replace("{}", root.dev.blacklistArtists || 0)
.replace("{}", root.dev.blacklistAlbums || 0)
color: theme.textMuted
font.pixelSize: 12
wrapMode: Text.WordWrap
}
}
}
SettingsButton {
id: manageBtn
anchors.verticalCenter: parent.verticalCenter
text: QbzSession.tr("Manage", QbzSession.trRev)
trailingIconName: "chevron-right"
onClicked: QbzBlacklist.openManager()
}
}
}
}