Repository navigation
Expand file tree
/
Copy pathReadmeExamplesTests.swift
More file actions
108 lines (93 loc) · 3.7 KB
/
Copy pathReadmeExamplesTests.swift
File metadata and controls
108 lines (93 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// README.md quotes the interactive button and the networking example below; its other
// examples come from Examples/WidgetKitExample. The steps provider is repeated here so
// its timeline can be tested, and the last example is the one in the Interactive Widgets
// DocC article. ReadmeSyncTests checks that every Swift block in README.md appears in
// this file or in Examples/.
import AppIntents
import Foundation
import SwiftUI
import Testing
import WidgetKit
import WidgetDevelopmentKit
// Same provider as Examples/WidgetKitExample/Shared/Steps.swift
struct StepsEntry: TimelineEntry {
let date: Date
let steps: Int
}
struct StepsProvider: TimelineProvider {
let store = WidgetSnapshotStore(defaults: UserDefaults(suiteName: "group.com.example.widgets") ?? .standard)
func placeholder(in context: Context) -> StepsEntry {
StepsEntry(date: .now, steps: 6_000)
}
func getSnapshot(in context: Context, completion: @escaping (StepsEntry) -> Void) {
completion(placeholder(in: context))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<StepsEntry>) -> Void) {
completion(makeTimeline(now: .now))
}
func makeTimeline(now: Date) -> Timeline<StepsEntry> {
// Written by the app; stale data is better than an empty widget.
let steps = store.staleValue(Int.self, forKey: "steps") ?? 0
let entries = TimelineSchedule
.dates(from: now, every: 15 * 60, count: 4)
.map { StepsEntry(date: $0, steps: steps) }
return TimelineBuilder(entries: entries)
.reloading(.every(minutes: 60), from: now)
.build()
}
}
// Same view and configuration as Examples/WidgetKitExample
struct StepsWidgetView: View {
let entry: StepsEntry
var body: some View {
WidgetProgressRing(progress: Double(entry.steps), total: 10_000, colors: [.green, .mint])
.padding()
.containerBackground(.fill.tertiary, for: .widget)
.widgetURL(WidgetDeepLink(scheme: "steps", host: "today"))
}
}
struct StepsWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "steps", provider: StepsProvider()) { entry in
StepsWidgetView(entry: entry)
}
.configurationDisplayName("Steps")
.supportedFamilies([.systemSmall])
}
}
// README: Interactive button
struct WaterButton: View {
var body: some View {
Button(intent: IncrementIntent(counterId: "water", amount: 250)) {
Label("+250 ml", systemImage: "drop.fill")
}
}
}
// README: Networking
struct RandomQuote: Decodable {
let text: String
}
func loadQuote() async throws -> RandomQuote {
guard let base = URL(string: "https://api.example.com") else { throw URLError(.badURL) }
let endpoint = Endpoint(base: base)
.appendingPath("v1/quotes/random")
.adding(query: "lang", "en")
guard let request = endpoint.request else { throw URLError(.badURL) }
return try await WidgetDataLoader().load(RandomQuote.self, from: request)
}
@Suite("README examples")
struct ReadmeExamplesTests {
@Test func providerBuildsFourEntriesAndHourlyReload() throws {
let now = Date(timeIntervalSince1970: 1_800_000_000)
let timeline = StepsProvider().makeTimeline(now: now)
#expect(timeline.entries.count == 4)
#expect(timeline.entries.last?.date == now.addingTimeInterval(45 * 60))
#expect(timeline.policy == .after(now.addingTimeInterval(60 * 60)))
}
}
// DocC article "Interactive Widgets": register the package's intents
struct AppIntentsRoot: AppIntentsPackage {
static var includedPackages: [any AppIntentsPackage.Type] {
[WidgetDevelopmentKitIntents.self]
}
}