Skip to content
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
37 changes: 35 additions & 2 deletions Sources/VoidBar/Services/WeatherStore.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import Foundation
import Combine

struct HourlyWeather: Codable, Identifiable {
let id = UUID()
let time: Date
let temperature: Double
let condition: Int
}

struct WeatherData: Codable {
let temperature: Double
let condition: Int
let locationName: String?
let hourly: [HourlyWeather]
}

@MainActor
Expand Down Expand Up @@ -46,7 +54,7 @@ final class WeatherStore: ObservableObject {
let ipResponse = try JSONDecoder().decode(IPResponse.self, from: locationData)

// Get weather from Open-Meteo
let weatherUrlString = "https://api.open-meteo.com/v1/forecast?latitude=\(ipResponse.latitude)&longitude=\(ipResponse.longitude)&current_weather=true"
let weatherUrlString = "https://api.open-meteo.com/v1/forecast?latitude=\(ipResponse.latitude)&longitude=\(ipResponse.longitude)&current_weather=true&hourly=temperature_2m,weathercode&timezone=auto"
guard let weatherUrl = URL(string: weatherUrlString) else { return }

let (weatherJsonData, _) = try await URLSession.shared.data(from: weatherUrl)
Expand All @@ -56,14 +64,39 @@ final class WeatherStore: ObservableObject {
let temperature: Double
let weathercode: Int
}
struct HourlyData: Decodable {
let time: [String]
let temperature_2m: [Double]
let weathercode: [Int]
}
let current_weather: CurrentWeather
let hourly: HourlyData?
}
let meteoResponse = try JSONDecoder().decode(MeteoResponse.self, from: weatherJsonData)

var hourlyForecast: [HourlyWeather] = []
if let hourly = meteoResponse.hourly {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withDashSeparatorInDate, .withColonSeparatorInTime, .withColonSeparatorInTimeZone]
// Open-Meteo timezone=auto returns format like "2023-08-07T12:00" without Z.
// We will use a simpler formatter.
let simpleFormatter = DateFormatter()
simpleFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm"

let now = Date()
for i in 0..<min(hourly.time.count, hourly.temperature_2m.count, hourly.weathercode.count) {
if let date = simpleFormatter.date(from: hourly.time[i]), date >= now.addingTimeInterval(-3600) {
hourlyForecast.append(HourlyWeather(time: date, temperature: hourly.temperature_2m[i], condition: hourly.weathercode[i]))
if hourlyForecast.count >= 24 { break } // Only next 24 hours
}
}
}

self.weather = WeatherData(
temperature: meteoResponse.current_weather.temperature,
condition: meteoResponse.current_weather.weathercode,
locationName: ipResponse.city
locationName: ipResponse.city,
hourly: hourlyForecast
)
self.error = nil
} catch {
Expand Down
27 changes: 27 additions & 0 deletions Sources/VoidBar/UI/WeatherPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ struct WeatherPane: View {
.font(.system(size: 11, weight: .medium))
.foregroundStyle(Theme.secondary)
}

if !weather.hourly.isEmpty {
Spacer(minLength: 8)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(weather.hourly) { hour in
hourlyItem(hour)
}
}
.padding(.horizontal, 16)
}
.frame(height: 60)
}
} else {
ProgressView()
.controlSize(.small)
Expand All @@ -45,4 +58,18 @@ struct WeatherPane: View {
default: return "cloud.fill"
}
}

private func hourlyItem(_ hour: HourlyWeather) -> some View {
VStack(spacing: 4) {
Text(hour.time, format: .dateTime.hour())
.font(.system(size: 10, weight: .medium))
.foregroundStyle(Theme.secondary)
Image(systemName: icon(for: hour.condition))
.font(.system(size: 14))
.foregroundStyle(.white)
Text(String(format: "%.0f°", hour.temperature))
.font(.system(size: 11, weight: .semibold))
.foregroundStyle(.white)
}
}
}
Loading