feat(offline): star in place — keep files in their show group

Starring no longer relocates a file into a separate 'Starred' section; every
item stays in its own show group and the row's star button just toggles on/off
(filled-yellow vs outline) as the keep-from-cull indicator. Show headers show a
'★N' badge for how many in that group are starred. Drops the leading star-swap
and the now-unused nonPinnedGrouped grouping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-30 01:12:11 -04:00
parent 40188f85a9
commit e532fe14bc

View file

@ -104,23 +104,18 @@ struct OfflineCacheView: View {
}
}
if !pinnedItems.isEmpty {
Section {
ForEach(pinnedItems) { item in
row(for: item)
}
} header: {
sectionHeader("Starred (kept from cull) · \(pinnedItems.count)")
}
}
ForEach(nonPinnedGrouped, id: \.show) { group in
// Items stay in their own show group regardless of star
// state starring just toggles the star on the row (and
// protection from cull), it doesn't relocate the file.
ForEach(groupedFiltered, id: \.show) { group in
Section {
ForEach(group.items) { item in
row(for: item)
}
} header: {
sectionHeader("\(group.show) · \(group.items.count)")
let starred = group.items.filter(isPinned).count
sectionHeader("\(group.show) · \(group.items.count)"
+ (starred > 0 ? " · ★\(starred)" : ""))
}
}
}
@ -187,14 +182,6 @@ struct OfflineCacheView: View {
.sorted { $0.modifiedAt > $1.modifiedAt }
}
private var nonPinnedGrouped: [(show: String, items: [OfflineCachedFile])] {
let pinSet = Set(localPolicy.pinned.map { $0.lowercased() })
let non = filteredItems.filter { !pinSet.contains($0.name.lowercased()) }
let dict = Dictionary(grouping: non, by: \.showDir)
return dict.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
.map { k in (k, dict[k]!.sorted { $0.modifiedAt > $1.modifiedAt }) }
}
private var canPlayAll: Bool { !filteredItems.isEmpty }
private var localDevice: DeviceConfig? {
@ -279,17 +266,10 @@ struct OfflineCacheView: View {
private func row(for item: OfflineCachedFile) -> some View {
HStack(spacing: 10) {
if isPinned(item) {
Image(systemName: "star.fill")
.font(.caption)
.foregroundStyle(.yellow)
.frame(width: 20)
} else {
Image(systemName: "play.rectangle.on.rectangle.fill")
.font(.body)
.foregroundStyle(.tint)
.frame(width: 20)
}
Image(systemName: "play.rectangle.on.rectangle.fill")
.font(.body)
.foregroundStyle(.tint)
.frame(width: 20)
VStack(alignment: .leading, spacing: 1) {
Text(item.name)