| struct ShowRowView: View {
let entry: SpotEntry
var showBell: Bool = true
@State private var cachedImage: UIImage?
/// "EEE, MMM d · h:mm a" e.g. "Sat, Mar 15 · 8:00 PM"
private static let rowDateFormatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "EEE, MMM d · h:mm a"
return f
}()
/// Teal matching the Upcoming pin/button color
private let showTeal = Color(red: 0/255, green: 150/255, blue: 136/255)
var body: some View {
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 3) {
// Show name + optional bell
HStack(spacing: 5) {
Text(verbatim: entry.playName)
.font(.system(size: 15, weight: .medium))
.foregroundColor(.primary)
if showBell && entry.remindersEnabled {
Image(systemName: "bell.fill")
.font(.system(size: 10))
.foregroundColor(.orange)
}
}
// Date — only for committed (non-consider) entries
if !entry.consider {
Text(Self.rowDateFormatter.string(from: entry.dateTime))
.font(.system(size: 13))
.foregroundColor(
entry.dateTime > Date() ? showTeal : Color.primary.opacity(0.65)
)
}
// Venue (stored in cuisine field)
if !entry.cuisine.isEmpty {
Text(entry.cuisine)
.font(.system(size: 13))
.foregroundColor(Color.primary.opacity(0.65))
}
// Row / Seat (stored in neighborhood field)
if !entry.neighborhood.isEmpty {
Text(entry.neighborhood)
.font(.system(size: 12))
.foregroundColor(Color.primary.opacity(0.5))
}
}
Spacer()
VStack(spacing: 3) {
// Star rating (post-show)
if entry.starRating > 0 {
HStack(spacing: 2) {
ForEach(1...entry.starRating, id: \.self) { _ in
Image(systemName: "star.fill")
.font(.system(size: 6))
.foregroundColor(.yellow)
}
}
.frame(width: 50)
}
// Thumbnail
if let uiImage = cachedImage {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 50, height: 77)
.clipped()
.cornerRadius(4)
} else {
Rectangle()
.fill(Color.gray.opacity(0.3))
.frame(width: 50, height: 77)
.cornerRadius(4)
}
}
}
// Load downsampled thumbnail (50-pt × 3× = 150 px)
.task(id: entry.id) {
if let filename = entry.imageFilename, !filename.isEmpty {
let image = await EntryStore.loadThumbnail(filename: filename, maxPixelSize: 150)
await MainActor.run { cachedImage = image }
} else if let data = entry.imageData {
let image = await Task.detached(priority: .userInitiated) {
let resized = EntryStore.resizedImageData(data, maxDimension: 150)
guard let source = CGImageSourceCreateWithData(resized as CFData, nil),
let cg = CGImageSourceCreateImageAtIndex(source, 0, nil)
else { return UIImage?.none }
return UIImage(cgImage: cg)
}.value
await MainActor.run { cachedImage = image }
}
}
// Drop thumbnail when row scrolls off to avoid holding many UIImages in memory
.onDisappear { cachedImage = nil }
.onReceive(NotificationCenter.default.publisher(
for: UIApplication.didReceiveMemoryWarningNotification)) { _ in
cachedImage = nil
}
}
} | `ShowRowView` struct | Defines the `ShowRowView` struct. Conforms to View. |