From e532fe14bc2164397983acf6058b8457ec5526e2 Mon Sep 17 00:00:00 2001 From: Natalie Date: Tue, 30 Jun 2026 01:12:11 -0400 Subject: [PATCH] =?UTF-8?q?feat(offline):=20=E2=AD=90=20star=20in=20place?= =?UTF-8?q?=20=E2=80=94=20keep=20files=20in=20their=20show=20group?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Sources/TVAnarchy/OfflineCacheView.swift | 42 +++++++----------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/Sources/TVAnarchy/OfflineCacheView.swift b/Sources/TVAnarchy/OfflineCacheView.swift index fb19aa2..49a0099 100644 --- a/Sources/TVAnarchy/OfflineCacheView.swift +++ b/Sources/TVAnarchy/OfflineCacheView.swift @@ -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)