Initial commit

This commit is contained in:
15lu.akari
2025-08-24 14:44:50 +03:00
parent dca1ae410b
commit 5a583d9415
50 changed files with 2019 additions and 17 deletions

View File

@ -0,0 +1,257 @@
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"
}
}
}