NewsBlur/clients/ios/Widget Extension/WidgetExtension.swift

154 lines
5 KiB
Swift
Raw Normal View History

//
// WidgetExtension.swift
// WidgetExtension
//
// Created by David Sinclair on 2021-08-05.
// Copyright © 2021 NewsBlur. All rights reserved.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
let cache = WidgetCache()
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), cache: cache)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), cache: cache)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
cache.loadCachedStories()
cache.load {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
#if DEBUG
let units = Calendar.Component.minute
#else
let units = Calendar.Component.hour
#endif
let entryDate = Calendar.current.date(byAdding: units, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, cache: cache)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let cache: WidgetCache
}
struct WidgetEntryView : View {
var entry: Provider.Entry
@Environment(\.colorScheme) var colorScheme
@Environment(\.widgetFamily) private var family
var isCompact: Bool {
family != .systemLarge
}
var body: some View {
ZStack {
Color("WidgetBackground")
.ignoresSafeArea()
if let error = entry.cache.error {
Text(message(for: error))
.font(.headline)
.foregroundColor(.secondary)
} else {
VStack(alignment: .leading, spacing: 0, content: {
ForEach(entry.cache.stories(count: isCompact ? 2 : 3)) { story in
WidgetStoryView(cache: entry.cache, story: story)
Divider()
}
})
}
}
}
func message(for error: WidgetCacheError) -> String {
switch error {
case .notLoggedIn:
return "Please log in to NewsBlur"
case .loading:
return "Tap to set up in NewsBlur"
case .noFeeds:
return "Please choose sites to show"
case .noStories:
return "No stories for selected sites"
}
}
}
@main
struct WidgetExtension: Widget {
let kind: String = "Latest"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetEntryView(entry: entry)
}
.configurationDisplayName("NewsBlur")
.description("The latest stories from NewsBlur.")
.supportedFamilies([.systemMedium, .systemLarge])
}
}
struct WidgetExtension_Previews: PreviewProvider {
static let cache: WidgetCache = {
let cache = WidgetCache()
cache.loadCachedStories()
// cache.error = WidgetCacheError.loading
let sample1 = "sample"
let sample2 = "another"
if cache.feeds.isEmpty {
cache.feeds.append(Feed(sample: sample1, title: "Sample Feed"))
cache.feeds.append(Feed(sample: sample2, title: "Another One"))
}
if cache.stories.isEmpty {
cache.stories.append(Story(sample: "This is an example story", feed: sample1))
cache.stories.append(Story(sample: "A second sample", feed: sample1))
cache.stories.append(Story(sample: "But for a real test, we need one with a very long title, to make sure that displays sensibly", feed: sample2))
cache.stories.append(Story(sample: "How about another sample, for good measure?", feed: sample2))
}
return cache
}()
static var previews: some View {
WidgetEntryView(entry: SimpleEntry(date: Date(), cache: cache))
.previewContext(WidgetPreviewContext(family: .systemMedium))
.colorScheme(.light)
WidgetEntryView(entry: SimpleEntry(date: Date(), cache: cache))
.previewContext(WidgetPreviewContext(family: .systemMedium))
.colorScheme(.dark)
WidgetEntryView(entry: SimpleEntry(date: Date(), cache: cache))
.previewContext(WidgetPreviewContext(family: .systemLarge))
.colorScheme(.light)
WidgetEntryView(entry: SimpleEntry(date: Date(), cache: cache))
.previewContext(WidgetPreviewContext(family: .systemLarge))
.colorScheme(.dark)
}
}