import WidgetKit import SwiftUI struct RecentPodcastsEntry: TimelineEntry { let date: Date let podcasts: [WidgetPodcast] static let placeholder = RecentPodcastsEntry(date: .now, podcasts: [ WidgetPodcast(articleUrl: "https://example.com/a", title: "The Future of AI", createdAt: .now), WidgetPodcast(articleUrl: "https://news.com/b", title: "Swift Concurrency Deep Dive", createdAt: .now.addingTimeInterval(-86400)), WidgetPodcast(articleUrl: "https://blog.dev/c", title: "Building Great iOS Apps", createdAt: .now.addingTimeInterval(-172800)), ]) } struct RecentPodcastsProvider: TimelineProvider { func placeholder(in context: Context) -> RecentPodcastsEntry { .placeholder } func getSnapshot(in context: Context, completion: @escaping (RecentPodcastsEntry) -> Void) { let podcasts = WidgetDataStore.loadPodcasts() completion(RecentPodcastsEntry(date: .now, podcasts: Array(podcasts.prefix(3)))) } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { let podcasts = WidgetDataStore.loadPodcasts() let entry = RecentPodcastsEntry(date: .now, podcasts: Array(podcasts.prefix(3))) let next = Calendar.current.date(byAdding: .minute, value: 30, to: .now)! completion(Timeline(entries: [entry], policy: .after(next))) } } struct RecentPodcastsWidgetView: View { let entry: RecentPodcastsEntry var body: some View { VStack(alignment: .leading, spacing: 0) { HStack { Text("Podcasts") .font(.system(size: 12, weight: .semibold)) .foregroundStyle(.secondary) Spacer() Image(systemName: "headphones") .font(.system(size: 11)) .foregroundStyle(.blue) } .padding(.bottom, 8) if entry.podcasts.isEmpty { Spacer() Text("Generate podcasts from your bookmarks to see them here.") .font(.system(size: 13)) .foregroundStyle(.secondary) Spacer() } else { ForEach(Array(entry.podcasts.enumerated()), id: \.element.id) { idx, podcast in if idx > 0 { Divider().padding(.vertical, 5) } Link(destination: .marksPodcast(podcast.articleUrl)) { WidgetPodcastRow(podcast: podcast) } } Spacer(minLength: 0) } } .padding(14) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .containerBackground(.fill.tertiary, for: .widget) } } struct WidgetPodcastRow: View { let podcast: WidgetPodcast var body: some View { HStack(spacing: 8) { RoundedRectangle(cornerRadius: 5) .fill(Color.blue.opacity(0.1)) .frame(width: 26, height: 26) .overlay { Image(systemName: "waveform") .font(.system(size: 10, weight: .medium)) .foregroundStyle(.blue) } VStack(alignment: .leading, spacing: 1) { Text(podcast.title) .font(.system(size: 13, weight: .medium)) .foregroundStyle(.primary) .lineLimit(1) Text(podcast.createdAt, style: .relative) .font(.system(size: 11)) .foregroundStyle(.secondary) } Spacer(minLength: 0) Image(systemName: "play.circle.fill") .font(.system(size: 18)) .foregroundStyle(.blue.opacity(0.8)) } } } struct RecentPodcastsWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration( kind: "com.magicive.marks.RecentPodcasts", provider: RecentPodcastsProvider() ) { entry in RecentPodcastsWidgetView(entry: entry) } .configurationDisplayName("Recent Podcasts") .description("Tap to play recently generated podcasts.") .supportedFamilies([.systemMedium]) } }