-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
45893e3
commit e32c882
Showing
23 changed files
with
343 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// | ||
// WeatherProvider.swift | ||
// SwiftyForecast | ||
// | ||
// Created by Pawel Milek on 12/8/23. | ||
// Copyright © 2023 Pawel Milek. All rights reserved. | ||
// | ||
|
||
import WidgetKit | ||
import SwiftUI | ||
import CoreLocation | ||
|
||
struct WeatherProvider: TimelineProvider { | ||
private let locationManager = WidgetLocationManager() | ||
private let dataSource: WeatherProviderDataSource | ||
|
||
init(dataSource: WeatherProviderDataSource = WeatherProviderDataSource()) { | ||
self.dataSource = dataSource | ||
} | ||
|
||
func placeholder(in context: Context) -> WeatherEntry { | ||
WeatherEntry.sampleTimeline.first! | ||
} | ||
|
||
func getSnapshot(in context: Context, completion: @escaping (WeatherEntry) -> Void) { | ||
Task { | ||
let result = await loadWeatherDataForCurrentLocation() | ||
let now = Date.now | ||
|
||
let entry = WeatherEntry( | ||
date: now, | ||
locationName: result.name, | ||
icon: result.icon, | ||
description: result.description, | ||
temperature: result.temperature, | ||
temperatureMaxMin: result.temperatureMaxMin | ||
) | ||
|
||
completion(entry) | ||
} | ||
} | ||
|
||
func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) { | ||
Task { | ||
let result = await loadWeatherDataForCurrentLocation() | ||
let now = Date.now | ||
|
||
let entry = WeatherEntry( | ||
date: now, | ||
locationName: result.name, | ||
icon: result.icon, | ||
description: result.description, | ||
temperature: result.temperature, | ||
temperatureMaxMin: result.temperatureMaxMin | ||
) | ||
|
||
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 45, to: now)! | ||
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate)) | ||
completion(timeline) | ||
} | ||
} | ||
|
||
private func loadWeatherDataForCurrentLocation() async -> WeatherProviderDataSource.EntryData { | ||
let location = await locationManager.startUpdatingLocation() | ||
let result = await dataSource.loadEntryData(for: location) | ||
return result | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
SwiftyForecast Widget/Provider/WeatherProviderDataSource.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// WeatherProviderDataSource.swift | ||
// SwiftyForecastWidgetExtension | ||
// | ||
// Created by Pawel Milek on 12/13/23. | ||
// Copyright © 2023 Pawel Milek. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
import CoreLocation | ||
|
||
struct WeatherProviderDataSource { | ||
struct EntryData { | ||
let name: String | ||
let icon: Image | ||
let description: String | ||
let temperature: String | ||
let temperatureMaxMin: String | ||
} | ||
|
||
private let service: WeatherServiceProtocol | ||
private let temperatureRenderer: TemperatureRenderer | ||
|
||
init( | ||
service: WeatherServiceProtocol = WeatherService(), | ||
temperatureRenderer: TemperatureRenderer = TemperatureRenderer() | ||
) { | ||
self.service = service | ||
self.temperatureRenderer = temperatureRenderer | ||
} | ||
|
||
func loadEntryData(for location: CLLocation) async -> EntryData { | ||
let placemark = await fetchPlacemark(at: location) | ||
let name = placemark.locality ?? InvalidReference.undefined | ||
let coordinate = placemark.location?.coordinate ?? location.coordinate | ||
let model = await fetchCurrentWeather(coordinate: coordinate) | ||
let icon = await fetchIcon(with: model.icon) | ||
|
||
let readyForDisplayTemperature = temperatureRenderer.render(model.temperatureValue) | ||
|
||
let result = EntryData( | ||
name: name, | ||
icon: icon, | ||
description: model.description, | ||
temperature: readyForDisplayTemperature.current, | ||
temperatureMaxMin: readyForDisplayTemperature.maxMin | ||
) | ||
|
||
return result | ||
} | ||
|
||
private func fetchPlacemark(at location: CLLocation) async -> CLPlacemark { | ||
do { | ||
let placemark = try await Geocoder.fetchPlacemark(at: location) | ||
return placemark | ||
} catch { | ||
fatalError(error.localizedDescription) | ||
} | ||
} | ||
|
||
private func fetchCurrentWeather(coordinate: CLLocationCoordinate2D) async -> CurrentWeatherModel { | ||
do { | ||
let response = try await service.fetchCurrent( | ||
latitude: coordinate.latitude, | ||
longitude: coordinate.longitude | ||
) | ||
let model = ResponseParser.parse(current: response) | ||
return model | ||
} catch { | ||
fatalError(error.localizedDescription) | ||
} | ||
} | ||
|
||
private func fetchIcon(with symbol: String) async -> Image { | ||
do { | ||
let result = try await service.fetchLargeIcon(symbol: symbol) | ||
let image = Image(uiImage: result) | ||
return image | ||
} catch { | ||
fatalError(error.localizedDescription) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.