Files
WhiteNights_iOS/WhiteNights/Widgets/WeatherView.swift
2025-08-24 14:44:50 +03:00

258 lines
10 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
private let WEATHER_STATUS_MAP: [String: String] = [
"Rain": "дождливо",
"Clouds": "облачно",
"Clear": "солнечно",
"Thunderstorm": "гроза",
"Snow": "снег",
"Drizzle": "мелкий дождь",
"Fog": "туман"
]
struct FormattedWeather {
let temperature: Int
let status: String
let precipitation: Int?
let windSpeed: Double?
let dayOfWeek: String?
}
struct WeatherView: View {
@State private var todayWeather: FormattedWeather?
@State private var forecast: [FormattedWeather] = []
var body: some View {
HStack(alignment: .center, spacing: 4) {
if let today = todayWeather {
// ЛЕВЫЙ СТОЛБЕЦ: Иконка, температура и статус
VStack(alignment: .leading, spacing: 2) {
Image(getWeatherIconName(for: today.status))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
.padding(.bottom, 5)
Text("\(today.temperature)°")
.font(.system(size: 30, weight: .bold))
.foregroundColor(.white)
Text(today.status)
.font(.system(size: 12))
.foregroundColor(.white)
}
// ПРАВЫЙ СТОЛБЕЦ: Прогноз с иконками
VStack(alignment: .leading, spacing: 10) {
// Прогноз на 3 дня
ForEach(forecast.prefix(3).indices, id: \.self) { index in
let day = forecast[index]
HStack(spacing: 5) {
Image(getWeatherIconName(for: day.status))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 14, height: 14)
if let dayName = day.dayOfWeek {
Text(dayName)
.font(.system(size: 12))
.foregroundColor(.white)
}
Text("\(day.temperature)°")
.font(.system(size: 12))
.foregroundColor(.white)
.fontWeight(.bold)
}
}
// Влажность и скорость ветра
if let precipitation = today.precipitation {
HStack(spacing: 5) {
Image("det_humidity")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 14, height: 14)
Text("\(precipitation)%")
.font(.system(size: 12))
.foregroundColor(.white)
}
}
if let windSpeed = today.windSpeed {
HStack(spacing: 5) {
Image("det_wind_speed")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 14, height: 14)
Text("\(Int(windSpeed)) м/с")
.font(.system(size: 12))
.foregroundColor(.white)
}
}
}
} else {
Text("Загрузка погоды...")
.foregroundColor(.white)
.padding()
}
}
.padding(.horizontal, 10)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fit)
.background(
ZStack {
Color(hex: 0x806C59)
LinearGradient(
stops: [
.init(color: .white.opacity(0.0), location: 0.0871),
.init(color: .white.opacity(0.16), location: 0.6969)
],
startPoint: .bottomLeading,
endPoint: .topTrailing
)
}
)
.cornerRadius(25)
.shadow(color: Color.black.opacity(0.10), radius: 8, x: 0, y: 2)
.task {
await fetchAndFormatWeather()
}
}
private func fetchAndFormatWeather() async {
let lat = 59.938784
let lng = 30.314997
guard let url = URL(string: "https://white-nights.krbl.ru/services/weather") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let parameters: [String: Any] = [
"coordinates": ["latitude": lat, "longitude": lng]
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
let (data, _) = try await URLSession.shared.data(for: request)
// 💡 Отладка: Печатаем полученные данные для проверки
// print("Received Data (String): \(String(data: data, encoding: .utf8) ?? "N/A")")
let weatherResponse = try JSONDecoder().decode(WeatherResponse.self, from: data)
formatWeatherData(data: weatherResponse)
} catch {
print("Ошибка загрузки погоды: \(error)")
}
}
private func formatWeatherData(data: WeatherResponse) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) // UTC
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Добавление POSIX-локали для надежного парсинга
var calendar = Calendar.current
calendar.timeZone = TimeZone(secondsFromGMT: 0)! // UTC
// 1. Фильтруем все записи, которые соответствуют 12:00 UTC
let middayForecast = data.forecast.filter { item in
guard let date = dateFormatter.date(from: item.date) else {
// 💡 Отладка: если здесь вы увидите сообщения, значит, формат даты API не совпадает с форматом dateFormatter
// print("Парсинг даты провален для: \(item.date)")
return false
}
return calendar.component(.hour, from: date) == 12
}
// 💡 Отладка: Проверяем, сколько записей найдено
// print("Найдено записей на 12:00 UTC: \(middayForecast.count)")
// Сегодня
if let today = data.currentWeather {
self.todayWeather = FormattedWeather(
temperature: Int(today.temperatureCelsius.rounded()),
status: WEATHER_STATUS_MAP[today.description] ?? today.description,
precipitation: today.humidity,
windSpeed: today.windSpeed,
dayOfWeek: nil
)
}
// 🚀 ИСПРАВЛЕНИЕ: Прогноз на 3 дня. Берем первые 3 доступные записи с 12:00.
var formattedForecast: [FormattedWeather] = []
// Перебираем первые 3 записи с 12:00 UTC
for item in middayForecast.prefix(3) {
guard let date = dateFormatter.date(from: item.date) else { continue }
let averageTemp = (item.minTemperatureCelsius + item.maxTemperatureCelsius) / 2
let dayOfWeekString = getDayOfWeek(from: date)
// 💡 Отладка: Печатаем найденный прогноз
// print("Прогноз найден: \(dayOfWeekString) - \(Int(averageTemp.rounded()))°")
formattedForecast.append(FormattedWeather(
temperature: Int(averageTemp.rounded()),
status: WEATHER_STATUS_MAP[item.description] ?? item.description,
precipitation: item.humidity,
windSpeed: item.windSpeed,
dayOfWeek: dayOfWeekString
))
}
// Если данных меньше 3, заполняем оставшиеся места нулями (N/A)
let daysToFill = 3 - formattedForecast.count
if daysToFill > 0 {
let baseDate = Date()
for i in 1...daysToFill {
guard let nextDayForNA = Calendar.current.date(byAdding: .day, value: formattedForecast.count + i, to: baseDate) else { continue }
let dayOfWeekString = getDayOfWeek(from: nextDayForNA)
formattedForecast.append(FormattedWeather(
temperature: 0,
status: "N/A",
precipitation: nil,
windSpeed: nil,
dayOfWeek: dayOfWeekString
))
}
}
self.forecast = formattedForecast
}
private func getDayOfWeek(from date: Date) -> String {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "E"
return formatter.string(from: date).capitalized
}
private func getWeatherIconName(for status: String) -> String {
let normalizedStatus = status.lowercased()
switch normalizedStatus {
case "солнечно":
return "cond_sunny"
case "облачно":
return "cond_cloudy"
case "дождливо", "мелкий дождь":
return "cond_rainy"
case "снег":
return "cond_snowy"
case "гроза":
return "cond_thunder"
case "туман":
return "det_humidity"
default:
return "cond_sunny"
}
}
}