Skip to content

Weather API support #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Config.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974

OPENWEATHERMAP_APP_ID =
WEATHER_API_KEY=
142 changes: 33 additions & 109 deletions DatWeatherDoe.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/ashleymills/Reachability.swift",
"state" : {
"revision" : "c01bbdf2d633cf049ae1ed1a68a2020a8bda32e2",
"version" : "5.1.0"
"revision" : "7cbd73f46a7dfaeca079e18df7324c6de6d1834a",
"version" : "5.2.3"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git",
"state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
"version" : "0.34.1"
"revision" : "fd4df99170f5e9d7cf9aa8312aa8506e0e7a44e7",
"version" : "0.35.0"
}
},
{
Expand All @@ -51,26 +51,26 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
"revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
"version" : "510.0.2"
}
},
{
"identity" : "swiftformat",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nicklockwood/SwiftFormat",
"state" : {
"revision" : "ab238886b8b50f8b678b251f3c28c0c887305407",
"version" : "0.53.8"
"revision" : "dd989a46d0c6f15c016484bab8afe5e7a67a4022",
"version" : "0.54.0"
}
},
{
"identity" : "swiftlint",
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/SwiftLint",
"state" : {
"revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee",
"version" : "0.54.0"
"revision" : "b515723b16eba33f15c4677ee65f3fef2ce8c255",
"version" : "0.55.1"
}
},
{
Expand Down
27 changes: 27 additions & 0 deletions DatWeatherDoe/API/Response/ForecastData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// ForecastData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//

import Foundation

struct Forecast: Decodable {
let dayDataArr: [ForecastDayData]

private enum CodingKeys: String, CodingKey {
case dayDataArr = "forecastday"
}
}

struct ForecastDayData: Decodable {
let temp: ForecastTemperatureData
let astro: SunriseSunsetData

private enum CodingKeys: String, CodingKey {
case temp = "day"
case astro
}
}
23 changes: 23 additions & 0 deletions DatWeatherDoe/API/Response/ForecastTemperatureData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// ForecastTemperatureData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//

import Foundation

struct ForecastTemperatureData: Decodable {
let maxTempC: Double
let maxTempF: Double
let minTempC: Double
let minTempF: Double

private enum CodingKeys: String, CodingKey {
case maxTempC = "maxtemp_c"
case maxTempF = "maxtemp_f"
case minTempC = "mintemp_c"
case minTempF = "mintemp_f"
}
}
24 changes: 24 additions & 0 deletions DatWeatherDoe/API/Response/SunriseSunsetData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// SunriseSunsetData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/22/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//

import Foundation

struct SunriseSunsetData: Decodable {
let isDay: Int
let sunrise: String
let sunset: String

private enum CodingKeys: String, CodingKey {
case isDay = "is_sun_up"
case sunrise, sunset
}

var isDayBool: Bool {
isDay > 0
}
}
23 changes: 23 additions & 0 deletions DatWeatherDoe/API/Response/TemperatureData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// TemperatureData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//

import Foundation

struct TemperatureData: Decodable {
let tempCelsius: Double
let feelsLikeTempCelsius: Double
let tempFahrenheit: Double
let feelsLikeTempFahrenheit: Double

private enum CodingKeys: String, CodingKey {
case tempCelsius = "temp_c"
case feelsLikeTempCelsius = "feelslike_c"
case tempFahrenheit = "temp_f"
case feelsLikeTempFahrenheit = "feelslike_f"
}
}
93 changes: 40 additions & 53 deletions DatWeatherDoe/API/Response/WeatherAPIResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,77 +9,64 @@
import Foundation

struct WeatherAPIResponse: Decodable {
let cityId: Int
let locationName: String
let temperatureData: TemperatureData
let weatherConditionCode: Int
let humidity: Int
let location: String
let weatherId: Int
let sunrise: TimeInterval
let sunset: TimeInterval
let windData: WindData
let forecastDayData: ForecastDayData

struct TemperatureData: Decodable {
let temperature: Double
let feelsLikeTemperature: Double
let minTemperature: Double
let maxTemperature: Double

// swiftlint:disable:next nesting
private enum CodingKeys: String, CodingKey {
case temperature = "temp"
case feelsLikeTemperature = "feels_like"
case minTemperature = "temp_min"
case maxTemperature = "temp_max"
}
private enum RootKeys: String, CodingKey {
case location, current, forecast
}

struct WindData: Decodable {
let speed: Double
let degrees: Int

// swiftlint:disable:next nesting
private enum CodingKeys: String, CodingKey {
case speed
case degrees = "deg"
}
private enum LocationKeys: String, CodingKey {
case name
}

private enum RootKeys: String, CodingKey {
case cityId = "id"
case main, weather, humidity, name, sys, wind
private enum CurrentKeys: String, CodingKey {
case condition, humidity
}

private enum MainKeys: String, CodingKey {
case humidity
private enum WeatherConditionKeys: String, CodingKey {
case code
}

private enum SysKeys: String, CodingKey {
case sunrise, sunset
private enum ForecastKeys: String, CodingKey {
case forecastDay = "forecastday"
}

private enum WeatherKeys: String, CodingKey {
case id
private enum ForecastDayKeys: String, CodingKey {
case day, astro
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)

cityId = try container.decode(Int.self, forKey: .cityId)
temperatureData = try container.decode(TemperatureData.self, forKey: .main)

let mainContainer = try container.nestedContainer(keyedBy: MainKeys.self, forKey: .main)
humidity = try mainContainer.decode(Int.self, forKey: .humidity)

location = try container.decode(String.self, forKey: .name)

var weatherContainer = try container.nestedUnkeyedContainer(forKey: .weather)
let weatherChildContainer = try weatherContainer.nestedContainer(keyedBy: WeatherKeys.self)
weatherId = try weatherChildContainer.decode(Int.self, forKey: .id)

let sysContainer = try container.nestedContainer(keyedBy: SysKeys.self, forKey: .sys)
sunrise = try sysContainer.decode(TimeInterval.self, forKey: .sunrise)
sunset = try sysContainer.decode(TimeInterval.self, forKey: .sunset)

windData = try container.decode(WindData.self, forKey: .wind)
let locationContainer = try container.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
locationName = try locationContainer.decode(String.self, forKey: .name)
temperatureData = try container.decode(TemperatureData.self, forKey: .current)

let currentContainer = try container.nestedContainer(keyedBy: CurrentKeys.self, forKey: .current)
let weatherConditionContainer = try currentContainer.nestedContainer(
keyedBy: WeatherConditionKeys.self,
forKey: .condition
)
weatherConditionCode = try weatherConditionContainer.decode(Int.self, forKey: .code)

windData = try container.decode(WindData.self, forKey: .current)

humidity = try currentContainer.decode(Int.self, forKey: .humidity)

let forecast = try container.decode(Forecast.self, forKey: .forecast)
if let dayData = forecast.dayDataArr.first {
forecastDayData = dayData
} else {
throw DecodingError.dataCorruptedError(
forKey: .forecast,
in: container,
debugDescription: "Missing forecast day data"
)
}
}
}
23 changes: 23 additions & 0 deletions DatWeatherDoe/API/Response/WindData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// WindData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//

import Foundation

struct WindData: Decodable {
let speedMph: Double
let speedKph: Double
let degrees: Int
let direction: String

private enum CodingKeys: String, CodingKey {
case speedMph = "wind_mph"
case speedKph = "wind_kph"
case degrees = "wind_degree"
case direction = "wind_dir"
}
}
6 changes: 0 additions & 6 deletions DatWeatherDoe/API/WeatherError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ enum WeatherError: LocalizedError {
case unableToConstructUrl
case locationError
case latLongIncorrect
case zipCodeIncorrect
case cityIncorrect
case networkError

var errorDescription: String? {
Expand All @@ -24,10 +22,6 @@ enum WeatherError: LocalizedError {
return NSLocalizedString("❗️Location", comment: "Location error when fetching weather")
case .latLongIncorrect:
return NSLocalizedString("❗️Lat/Long", comment: "Lat/Long error when fetching weather")
case .zipCodeIncorrect:
return NSLocalizedString("❗️Zipcode", comment: "Zip Code error when fetching weather")
case .cityIncorrect:
return NSLocalizedString("❗️City", comment: "City error when fetching weather")
case .networkError:
return "🖧"
}
Expand Down
4 changes: 1 addition & 3 deletions DatWeatherDoe/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
viewModel = WeatherViewModel(
locationFetcher: SystemLocationFetcher(logger: logger),
weatherFactory: WeatherRepositoryFactory(
appId: WeatherAppIDParser().parse(),
appId: APIKeyParser().parse(),
networkClient: NetworkClient(),
logger: logger
),
Expand Down Expand Up @@ -90,8 +90,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@objc private func terminate() { NSApp.terminate(self) }

private func updateWeather(with weatherData: WeatherData) {
viewModel.updateCity(with: weatherData.response.cityId)

let measurementUnit = MeasurementUnit(rawValue: configManager.measurementUnit) ?? .imperial
menuBarManager.updateMenuBarWith(
weatherData: weatherData,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// WeatherAppIDParser.swift
// APIKeyParser.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/11/22.
Expand All @@ -8,11 +8,11 @@

import Foundation

final class WeatherAppIDParser {
final class APIKeyParser {
func parse() -> String {
guard let appId = Bundle.main.infoDictionary?["OPENWEATHERMAP_APP_ID"] as? String else {
guard let apiKey = Bundle.main.infoDictionary?["WEATHER_API_KEY"] as? String else {
fatalError("Unable to find OPENWEATHERMAP_APP_ID in `Config.xcconfig`")
}
return appId
return apiKey
}
}
Loading
Loading