-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathContentView.swift
More file actions
132 lines (112 loc) · 4.12 KB
/
ContentView.swift
File metadata and controls
132 lines (112 loc) · 4.12 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//
// 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 }
await reload()
}
.refreshable {
guard !Globals.isPreview else { return }
await WeatherAPI.map.refresh()
await reload()
}
.errorAlert($error)
// Handle internet connection recovery by refreshing the data; this is also called at app startup
.onReceive(NotificationCenter.default.publisher(for: .networkDidChangeStatus)) { output in
if let connected = output.object as? Bool, connected {
print("Connection is back...")
Task {
await WeatherAPI.map.refresh()
await reload()
}
}
}
// Purge memory caches on memory warnings from the OS
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didReceiveMemoryWarningNotification)) { _ in
Task {
await MuxRepository.clearMemory()
ImageCache.clear()
}
}
}
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 {
do {
// 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
}
}
catch {
self.error = error
}
}
}
#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)))
]
)
}