-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathContentView.swift
118 lines (100 loc) · 3.42 KB
/
ContentView.swift
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
109
110
111
112
113
114
115
116
117
118
//
// ContentView.swift
// AsyncMuxDemo
//
// Created by Hovik Melikyan on 07/01/2023.
//
import SwiftUI
import AsyncMux
// TODO: a function to add a custom location by city name
private let backgroundURL = URL(string: "https://images.unsplash.com/photo-1513051265668-0ebab31671ae")!
struct ContentView: View {
@State var placeNames = WeatherAPI.defaultPlaceNames
@State var weather: [String: WeatherItem] = [:]
@State private var error: Error?
var body: some View {
listView()
.background(backgroundImageView())
.preferredColorScheme(.dark)
.task {
guard !Globals.isPreview else { return }
do {
try await reload()
}
catch {
self.error = error
}
}
.refreshable {
guard !Globals.isPreview else { return }
do {
await WeatherAPI.map.refresh()
try await reload()
}
catch {
self.error = error
}
}
.errorAlert($error)
}
private func listView() -> some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
ForEach(placeNames, id: \.self) { placeName in
HStack {
Text(placeName)
Spacer()
if let item = weather[placeName] {
Text(item.weather.map { "\(Int(round($0.currentWeather.temperature)))ºC" } ?? "-")
}
}
.padding(.vertical)
Divider()
}
}
}
.padding()
.font(.title2)
}
private func backgroundImageView() -> some View {
RemoteImage(url: backgroundURL) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
} placeholder: { error in
if error != nil {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 28))
.opacity(0.3)
}
else {
ProgressView()
}
}
}
@MainActor
private func reload() async throws {
// Create an array of actions for the zipper
let actions = placeNames.map { name in
{ @Sendable in
try await WeatherAPI.map.request(key: name)
}
}
// Execute the array of actions in parallel, get the results in an array and convert them to a dictionary to be used in the UI
weather = try await Zip(actions: actions)
.result
.reduce(into: [String: WeatherItem]()) {
$0[$1.name] = $1
}
}
}
#Preview {
ContentView(
placeNames: ["London, UK", "Paris, FR"],
weather: [
"London, UK": .init(name: "London, UK", place: .init(city: "London", countryCode: "GB", lat: 51.51, lon: -0.13), weather: .init(currentWeather: .init(temperature: 8.1, weathercode: 2))),
"Paris, FR": .init(name: "Paris, FR", place: .init(city: "Paris", countryCode: "FR", lat: 48.84, lon: 2.36), weather: .init(currentWeather: .init(temperature: 10.2, weathercode: 3)))
]
)
}