big update

This commit is contained in:
15lu.akari
2025-08-26 23:37:39 +03:00
parent 6b88875bee
commit a87a3d12ab
34 changed files with 803 additions and 282 deletions

View File

@ -1,6 +1,6 @@
import SwiftUI
private let WEATHER_STATUS_MAP: [String: String] = [
private let WEATHER_STATUS_MAP_RU: [String: String] = [
"Rain": "дождливо",
"Clouds": "облачно",
"Clear": "солнечно",
@ -10,52 +10,81 @@ private let WEATHER_STATUS_MAP: [String: String] = [
"Fog": "туман"
]
private let WEATHER_STATUS_MAP_ZH: [String: String] = [
"Rain": "下雨",
"Clouds": "多云",
"Clear": "晴朗",
"Thunderstorm": "雷雨",
"Snow": "下雪",
"Drizzle": "毛毛雨",
"Fog": ""
]
private let WEATHER_STATUS_MAP_EN: [String: String] = [
"Rain": "Rain",
"Clouds": "Cloudy",
"Clear": "Sunny",
"Thunderstorm": "Thunderstorm",
"Snow": "Snow",
"Drizzle": "Drizzle",
"Fog": "Fog"
]
struct FormattedWeather {
let temperature: Int
let status: String
let statusCode: String
let precipitation: Int?
let windSpeed: Double?
let dayOfWeek: String?
var dayOfWeek: String?
let originalDate: Date? // добавлено для пересчета дня недели
func localizedStatus(language: String) -> String {
switch language {
case "ru": return WEATHER_STATUS_MAP_RU[statusCode] ?? statusCode
case "zh": return WEATHER_STATUS_MAP_ZH[statusCode] ?? statusCode
default: return WEATHER_STATUS_MAP_EN[statusCode] ?? statusCode
}
}
}
struct WeatherView: View {
@EnvironmentObject var appState: AppState
@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))
VStack(alignment: .center, spacing: 2) {
Image(getWeatherIconName(for: today.localizedStatus(language: appState.selectedLanguage)))
.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)
Text(today.localizedStatus(language: appState.selectedLanguage))
.font(.system(size: 12))
.foregroundColor(.white)
.multilineTextAlignment(.center)
}
// ПРАВЫЙ СТОЛБЕЦ: Прогноз с иконками
VStack(alignment: .leading, spacing: 10) {
// Прогноз на 3 дня
VStack(alignment: .leading, spacing: 6) {
ForEach(forecast.prefix(3).indices, id: \.self) { index in
let day = forecast[index]
HStack(spacing: 5) {
Image(getWeatherIconName(for: day.status))
HStack(spacing: 4) {
Image(getWeatherIconName(for: day.localizedStatus(language: appState.selectedLanguage)))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 14, height: 14)
if let dayName = day.dayOfWeek {
Text(dayName)
.font(.system(size: 12))
if let date = day.originalDate {
Text(getDayOfWeek(from: date))
.font(.system(size: 10))
.foregroundColor(.white)
}
@ -66,9 +95,12 @@ struct WeatherView: View {
}
}
// Влажность и скорость ветра
Divider()
.background(Color.white.opacity(0.8))
.padding(.vertical, 1)
if let precipitation = today.precipitation {
HStack(spacing: 5) {
HStack(spacing: 4) {
Image("det_humidity")
.resizable()
.aspectRatio(contentMode: .fit)
@ -78,20 +110,25 @@ struct WeatherView: View {
.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)) м/с")
Text(appState.selectedLanguage == "ru" ? "\(Int(windSpeed)) м/с" :
appState.selectedLanguage == "zh" ? "\(Int(windSpeed)) 米/秒" :
"\(Int(windSpeed)) m/s")
.font(.system(size: 12))
.foregroundColor(.white)
}
}
}
} else {
Text("Загрузка погоды...")
Text(appState.selectedLanguage == "ru" ? "Загрузка погоды..." :
appState.selectedLanguage == "zh" ? "正在加载天气..." :
"Loading weather...")
.foregroundColor(.white)
.padding()
}
@ -117,13 +154,16 @@ struct WeatherView: View {
.task {
await fetchAndFormatWeather()
}
.onChange(of: appState.selectedLanguage) { _ in
// при смене языка день недели пересчитывается динамически в View
}
}
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 }
guard let url = URL(string: "https://white-nights.krbl.ru/services/weather?lang=\(appState.selectedLanguage)") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
@ -136,14 +176,8 @@ struct WeatherView: View {
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)")
}
@ -152,103 +186,85 @@ struct WeatherView: View {
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-локали для надежного парсинга
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
var calendar = Calendar.current
calendar.timeZone = TimeZone(secondsFromGMT: 0)! // UTC
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
// 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
}
guard let date = dateFormatter.date(from: item.date) else { return false }
return calendar.component(.hour, from: date) == 12
}
// 💡 Отладка: Проверяем, сколько записей найдено
// print("Найдено записей на 12:00 UTC: \(middayForecast.count)")
// Сегодня
if let today = data.currentWeather {
self.todayWeather = FormattedWeather(
todayWeather = FormattedWeather(
temperature: Int(today.temperatureCelsius.rounded()),
status: WEATHER_STATUS_MAP[today.description] ?? today.description,
statusCode: today.description,
precipitation: today.humidity,
windSpeed: today.windSpeed,
dayOfWeek: nil
dayOfWeek: nil,
originalDate: 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,
statusCode: item.description,
precipitation: item.humidity,
windSpeed: item.windSpeed,
dayOfWeek: dayOfWeekString
dayOfWeek: getDayOfWeek(from: date),
originalDate: date
))
}
// Если данных меньше 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)
guard let nextDay = Calendar.current.date(byAdding: .day, value: formattedForecast.count + i, to: baseDate) else { continue }
formattedForecast.append(FormattedWeather(
temperature: 0,
status: "N/A",
statusCode: "N/A",
precipitation: nil,
windSpeed: nil,
dayOfWeek: dayOfWeekString
dayOfWeek: getDayOfWeek(from: nextDay),
originalDate: nextDay
))
}
}
self.forecast = formattedForecast
}
private func getDayOfWeek(from date: Date) -> String {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.locale = appState.selectedLanguage == "ru" ? Locale(identifier: "ru_RU") :
appState.selectedLanguage == "zh" ? Locale(identifier: "zh_CN") :
Locale(identifier: "en_US")
formatter.dateFormat = "E"
return formatter.string(from: date).capitalized
}
private func getWeatherIconName(for status: String) -> String {
let normalizedStatus = status.lowercased()
switch normalizedStatus {
case "солнечно":
case "солнечно", "晴朗", "sunny":
return "cond_sunny"
case "облачно":
case "облачно", "多云", "cloudy":
return "cond_cloudy"
case "дождливо", "мелкий дождь":
case "дождливо", "мелкий дождь", "下雨", "毛毛雨", "rainy", "drizzle":
return "cond_rainy"
case "снег":
case "снег", "下雪", "snowy":
return "cond_snowy"
case "гроза":
case "гроза", "雷雨", "thunderstorm":
return "cond_thunder"
case "туман":
case "туман", "", "fog":
return "det_humidity"
default:
return "cond_sunny"