feat(purchases): readable dates, a release-date column and sort, column labels
The list printed the purchase date as "9/1/26" and nothing else, so a row said neither when the record came out nor, at a glance, what the number meant. Rows now carry the catalog's original release date next to the purchase date, both as "Sep 1, 2026", under a column-label strip like the album list's, and the toolbar sorts by release date (`released`) beside the four existing keys. cc
This commit is contained in:
13 files changed
+209
-11
No files matched your search
@@ -39,6 +39,8 @@ Item {
|
||||
readonly property int cardGap: 16
|
||||
readonly property int listRowHeight: 60
|
||||
readonly property int listGap: 4
|
||||
/// The column-label strip above the rows (PurchaseListHeader), list arm only.
|
||||
readonly property int listHeaderHeight: 28
|
||||
|
||||
readonly property int columns: Math.max(
|
||||
1, Math.floor((width + root.cardGap) / (root.cardWidth + root.cardGap)))
|
||||
@@ -49,7 +51,8 @@ Item {
|
||||
// Content-sized: the page Flickable reads this through its Column.
|
||||
height: root.viewMode === "list"
|
||||
? (root.albums.length > 0
|
||||
? root.albums.length * root.listRowHeight
|
||||
? root.listHeaderHeight + root.listGap
|
||||
+ root.albums.length * root.listRowHeight
|
||||
+ (root.albums.length - 1) * root.listGap
|
||||
: 0)
|
||||
: (root.gridRows > 0
|
||||
@@ -83,6 +86,11 @@ Item {
|
||||
visible: root.viewMode === "list"
|
||||
anchors.fill: parent
|
||||
spacing: root.listGap
|
||||
PurchaseListHeader {
|
||||
visible: root.viewMode === "list" && root.albums.length > 0
|
||||
width: parent.width
|
||||
height: root.listHeaderHeight
|
||||
}
|
||||
Repeater {
|
||||
model: root.viewMode === "list" ? root.albums : []
|
||||
delegate: PurchaseListRow {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// PurchaseListHeader — the column labels above the purchases list rows.
|
||||
// Widths mirror PurchaseListRow (art 48 · stretch · quality · released ·
|
||||
// purchased, 12px gaps, 8/12 margins) so each label sits on its column, the
|
||||
// way views/AlbumListHeader.qml does for the album list.
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../../theme"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
function t(s) { return QbzSession.tr(s, QbzSession.trRev) }
|
||||
|
||||
readonly property int colQuality: 110
|
||||
readonly property int colReleased: 120
|
||||
readonly property int colPurchased: 130
|
||||
|
||||
width: parent ? parent.width : 0
|
||||
height: 28
|
||||
|
||||
component ColLabel: Text {
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
font.letterSpacing: 0.5
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 12
|
||||
spacing: 12
|
||||
Item { width: 48; height: 1 }
|
||||
ColLabel {
|
||||
width: Math.max(0, parent.width - 48
|
||||
- root.colQuality - root.colReleased - root.colPurchased - 4 * 12)
|
||||
text: root.t("Title").toUpperCase()
|
||||
}
|
||||
ColLabel { width: root.colQuality; text: root.t("Quality").toUpperCase() }
|
||||
ColLabel { width: root.colReleased; text: root.t("Release date").toUpperCase() }
|
||||
ColLabel { width: root.colPurchased; text: root.t("Purchased").toUpperCase() }
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,21 @@ Rectangle {
|
||||
|
||||
function t(s) { return QbzSession.tr(s, QbzSession.trRev) }
|
||||
|
||||
/// Epoch -> the viewer's short date. "" for a row that carries no stamp,
|
||||
/// so the column collapses to blank rather than to 1970.
|
||||
/// Column widths, shared with PurchaseListHeader so the labels sit on
|
||||
/// their columns.
|
||||
readonly property int colQuality: 110
|
||||
readonly property int colReleased: 120
|
||||
readonly property int colPurchased: 130
|
||||
|
||||
/// A date the way a person reads one ("Sep 1, 2026"), never the numeric
|
||||
/// short form ("9/1/26") that the first port used: the two date columns
|
||||
/// sit side by side and a reader has to tell them apart at a glance.
|
||||
function readableDate(d) {
|
||||
return Qt.formatDate(d, "MMM d, yyyy")
|
||||
}
|
||||
|
||||
/// Epoch -> readable date. "" for a row that carries no stamp, so the
|
||||
/// column collapses to blank rather than to 1970.
|
||||
function purchaseDate() {
|
||||
var ts = root.album.purchasedAt || 0
|
||||
if (ts <= 0)
|
||||
@@ -42,7 +55,20 @@ Rectangle {
|
||||
// Defensive: the seam says seconds, but a producer that ever hands
|
||||
// milliseconds through would otherwise print a date in the year 55000.
|
||||
var ms = ts > 100000000000 ? ts : ts * 1000
|
||||
return new Date(ms).toLocaleDateString(Qt.locale(), Locale.ShortFormat)
|
||||
return root.readableDate(new Date(ms))
|
||||
}
|
||||
|
||||
/// ISO `YYYY-MM-DD` -> readable date; a bare year stays a year; "" stays "".
|
||||
function releaseDate() {
|
||||
var iso = root.album.releaseDate || ""
|
||||
if (iso === "")
|
||||
return root.album.year || ""
|
||||
var parts = iso.split("-")
|
||||
if (parts.length < 3)
|
||||
return parts[0]
|
||||
// Local-time construction: `new Date("YYYY-MM-DD")` parses as UTC and
|
||||
// prints the previous day west of Greenwich.
|
||||
return root.readableDate(new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])))
|
||||
}
|
||||
|
||||
width: parent ? parent.width : 0
|
||||
@@ -95,9 +121,10 @@ Rectangle {
|
||||
|
||||
// Title over artist — the stretch column.
|
||||
Column {
|
||||
width: Math.max(0, parent.width - 48 - 110 - 110
|
||||
width: Math.max(0, parent.width - 48
|
||||
- root.colQuality - root.colReleased - root.colPurchased
|
||||
- (unavailLabel.visible ? unavailLabel.width + 12 : 0)
|
||||
- 3 * 12)
|
||||
- 4 * 12)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
Text {
|
||||
@@ -120,24 +147,34 @@ Rectangle {
|
||||
// Quality column — the SAME badge the album list rows show
|
||||
// (views/AlbumListRow.qml): tier label over the exact quality line.
|
||||
Item {
|
||||
width: 110
|
||||
width: root.colQuality
|
||||
height: parent.height
|
||||
QualityBadgeFull {
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
tier: root.album.qualityTier || ""
|
||||
detail: root.album.qualityDetail || ""
|
||||
}
|
||||
}
|
||||
|
||||
// Release-date column (the catalog's original release).
|
||||
Text {
|
||||
width: root.colReleased
|
||||
height: parent.height
|
||||
text: root.releaseDate()
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontLegal
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Purchase-date column.
|
||||
Text {
|
||||
width: 110
|
||||
width: root.colPurchased
|
||||
height: parent.height
|
||||
text: root.purchaseDate()
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
@@ -324,6 +324,7 @@ Item {
|
||||
label: root.sortKey === "artist" ? root.t("Sort: Artist")
|
||||
: root.sortKey === "album" ? root.t("Sort: Album")
|
||||
: root.sortKey === "quality" ? root.t("Sort: Quality")
|
||||
: root.sortKey === "released" ? root.t("Sort: Release date")
|
||||
: root.t("Sort: Date")
|
||||
onClicked: sortMenu.openBelowLeft(sortBtn)
|
||||
|
||||
@@ -352,6 +353,12 @@ Item {
|
||||
arrow: sortBtn.arrowFor("purchased")
|
||||
onPicked: sortBtn.pick("purchased")
|
||||
}
|
||||
MenuRow {
|
||||
label: root.t("Release date")
|
||||
selected: root.sortKey === "released"
|
||||
arrow: sortBtn.arrowFor("released")
|
||||
onPicked: sortBtn.pick("released")
|
||||
}
|
||||
MenuRow {
|
||||
label: root.t("Artist")
|
||||
selected: root.sortKey === "artist"
|
||||
|
||||
Reference in new issue
Block a user