import AppIntents import SwiftUI import WidgetKit /// The home screen and lock screen widget (#428): All Subscriptions' unread count, and the episode /// you were last on with a button to carry on or pause. It draws what the app last wrote to their /// shared App Group (`Snapshot`); the app asks for a redraw when that changes. @main struct IPXWidgets: WidgetBundle { var body: some Widget { IPXWidget() } } struct IPXWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "net.sdf1.ipx.widget", provider: Provider()) { entry in WidgetView(snap: entry.snap) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("iPX") .description("What is unread, and the episode you were listening to.") .supportedFamilies([.systemSmall, .systemMedium, .accessoryCircular, .accessoryRectangular]) } } struct Entry: TimelineEntry { let date: Date let snap: Snapshot } struct Provider: TimelineProvider { private static let sample = Snapshot(unread: 12, title: "An episode", feedTitle: "A show", position: 600, duration: 1800) func placeholder(in context: Context) -> Entry { Entry(date: .now, snap: Self.sample) } func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) { completion(Entry(date: .now, snap: context.isPreview ? Self.sample : Snapshot.load())) } /// One entry, kept until the app says something changed: the widget has nothing of its own /// to look at. func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { completion(Timeline(entries: [Entry(date: .now, snap: Snapshot.load())], policy: .never)) } } struct WidgetView: View { @Environment(\.widgetFamily) private var family let snap: Snapshot var body: some View { switch family { case .accessoryCircular: VStack(spacing: 0) { Text(count).font(.title3.weight(.semibold)).minimumScaleFactor(0.5) Text("unread").font(.caption2) } case .accessoryRectangular: VStack(alignment: .leading) { Text("\(count) unread").font(.headline) if let title = snap.title { Text(title).font(.caption).lineLimit(2) } } case .systemMedium: HStack(spacing: 16) { unread Divider() episode } default: VStack(alignment: .leading) { unread Spacer(minLength: 4) if let title = snap.title { Text(title).font(.caption).lineLimit(2).foregroundStyle(.secondary) } } .frame(maxWidth: .infinity, alignment: .leading) } } private var count: String { guard let n = snap.unread else { return "–" } return n > 999 ? "999+" : "\(n)" } private var unread: some View { VStack(alignment: .leading, spacing: 0) { Text(count).font(.system(size: 40, weight: .semibold, design: .rounded)) .minimumScaleFactor(0.5).lineLimit(1) Text("unread").font(.caption).foregroundStyle(.secondary) } } @ViewBuilder private var episode: some View { if let title = snap.title { VStack(alignment: .leading, spacing: 6) { Text(snap.feedTitle ?? "").font(.caption2).foregroundStyle(.secondary).lineLimit(1) Text(title).font(.subheadline.weight(.semibold)).lineLimit(2) if let p = snap.progress { ProgressView(value: p).tint(.orange) } Button(intent: PlayPauseIntent()) { Label(snap.playing ? "Pause" : "Resume", systemImage: snap.playing ? "pause.fill" : "play.fill") .font(.caption.weight(.semibold)) } .tint(.orange) } .frame(maxWidth: .infinity, alignment: .leading) } else { Text("Nothing playing").font(.subheadline).foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .leading) } } }