big update
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import SwiftUI
|
||||
import SDWebImageSwiftUI
|
||||
|
||||
// MARK: - String Extension
|
||||
private extension String {
|
||||
var trimmedNonEmpty: String? {
|
||||
let t = trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@ -9,37 +10,52 @@ private extension String {
|
||||
}
|
||||
|
||||
// MARK: - ViewModel
|
||||
@MainActor
|
||||
final class StopsViewModel: ObservableObject {
|
||||
@Published var stops: [StopDetail] = []
|
||||
@Published var stops: [Stop] = []
|
||||
@Published var isLoading: Bool = false
|
||||
@Published var selectedStopId: Int?
|
||||
@Published var selectedStopDetail: StopDetail?
|
||||
|
||||
func fetchStops(for routeId: Int) {
|
||||
guard let url = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeId)/station") else { return }
|
||||
private var detailCache: [Int: StopDetail] = [:]
|
||||
|
||||
// MARK: - Fetch Stops With Transfers
|
||||
func fetchStops(routeId: Int, language: String = "ru") {
|
||||
guard let url = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeId)/station?lang=\(language)") else { return }
|
||||
isLoading = true
|
||||
|
||||
Task {
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
DispatchQueue.main.async { self.isLoading = false }
|
||||
guard let data = data, error == nil else { return }
|
||||
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
let stops = try decoder.decode([StopDetail].self, from: data)
|
||||
self.stops = stops
|
||||
|
||||
let stopDetails = try decoder.decode([StopDetail].self, from: data)
|
||||
|
||||
// Раздаем детали по остановкам
|
||||
var stops: [Stop] = []
|
||||
var cache: [Int: StopDetail] = [:]
|
||||
for detail in stopDetails {
|
||||
cache[detail.id] = detail
|
||||
stops.append(Stop(id: detail.id, name: detail.name))
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.stops = stops
|
||||
self.detailCache = cache
|
||||
}
|
||||
} catch {
|
||||
print("Ошибка загрузки остановок:", error)
|
||||
self.stops = []
|
||||
print("Parse stops error:", error)
|
||||
}
|
||||
self.isLoading = false
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func toggleStop(id: Int) {
|
||||
withAnimation(.easeInOut(duration: 0.25)) {
|
||||
if selectedStopId == id {
|
||||
selectedStopId = nil
|
||||
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
|
||||
if selectedStopDetail?.id == id {
|
||||
selectedStopDetail = nil
|
||||
} else {
|
||||
selectedStopId = id
|
||||
selectedStopDetail = detailCache[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,12 +79,19 @@ struct BottomMenu: View {
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
ZStack {
|
||||
if isPresented {
|
||||
Color.black.opacity(0.4)
|
||||
.ignoresSafeArea()
|
||||
.onTapGesture { isPresented = false }
|
||||
.transition(.opacity)
|
||||
}
|
||||
VisualEffectBlur(blurStyle: .systemUltraThinMaterialDark)
|
||||
.ignoresSafeArea()
|
||||
.mask(
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: Color.black.opacity(0), location: 0),
|
||||
.init(color: Color.black.opacity(1), location: 0.25),
|
||||
.init(color: Color.black.opacity(1), location: 1)
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
).onTapGesture { isPresented = false }
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
@ -80,101 +103,19 @@ struct BottomMenu: View {
|
||||
.padding(.top, 8)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
menuButton(title: "Достопримечательности", tab: .sights)
|
||||
menuButton(title: "Остановки", tab: .stops)
|
||||
menuButton(title: appState.selectedLanguage == "ru" ? "Достопримечательности" : appState.selectedLanguage == "zh" ? "景点" : "Sights", tab: .sights)
|
||||
menuButton(title: appState.selectedLanguage == "ru" ? "Остановки" : appState.selectedLanguage == "zh" ? "车站" : "Stops", tab: .stops)
|
||||
|
||||
if selectedTab == .sights {
|
||||
// --- Достопримечательности ---
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 20) {
|
||||
ForEach(appState.sights) { sight in
|
||||
VStack(spacing: 8) {
|
||||
SightThumbnail(
|
||||
url: URL(string: "https://white-nights.krbl.ru/services/content/media/\(sight.thumbnail)/download"),
|
||||
size: 80
|
||||
)
|
||||
.onTapGesture {
|
||||
appState.sightId = sight.id
|
||||
isPresented = false
|
||||
}
|
||||
|
||||
Text(sight.name)
|
||||
.font(.footnote)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(2)
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
}
|
||||
sightsView
|
||||
} else {
|
||||
// --- Остановки + пересадки ---
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
if stopsVM.isLoading {
|
||||
ProgressView("Загрузка остановок...")
|
||||
.padding()
|
||||
} else {
|
||||
ForEach(stopsVM.stops) { stop in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Button {
|
||||
stopsVM.toggleStop(id: stop.id)
|
||||
} label: {
|
||||
Text(stop.name)
|
||||
.font(.subheadline) // меньше размер
|
||||
.frame(maxWidth: .infinity, alignment: .leading) // левое выравнивание
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 20)
|
||||
.foregroundColor(.white)
|
||||
.transaction { $0.animation = nil }
|
||||
}
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(.white.opacity(0.3)),
|
||||
alignment: .bottom
|
||||
)
|
||||
|
||||
if stopsVM.selectedStopId == stop.id {
|
||||
transfersView(for: stop)
|
||||
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if stopsVM.stops.isEmpty,
|
||||
let routeId = appState.selectedRoute?.id {
|
||||
stopsVM.fetchStops(for: routeId)
|
||||
}
|
||||
}
|
||||
stopsView
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 2)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
Image("GAT_Icon")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 30)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 2) {
|
||||
Image("ru_lang_icon").resizable().scaledToFit().frame(width: 24, height: 24)
|
||||
Image("zh_lang_icon").resizable().scaledToFit().frame(width: 24, height: 24)
|
||||
Image("en_lang_icon").resizable().scaledToFit().frame(width: 24, height: 24)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 26)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 32)
|
||||
menuFooter
|
||||
}
|
||||
.frame(height: geo.size.height * 0.8)
|
||||
.frame(maxWidth: .infinity)
|
||||
@ -184,14 +125,12 @@ struct BottomMenu: View {
|
||||
.shadow(radius: 10)
|
||||
)
|
||||
.offset(y: isPresented ? dragOffset : geo.size.height)
|
||||
.animation(.spring(response: 0.35, dampingFraction: 0.9), value: isPresented)
|
||||
.animation(.spring(response: 0.35, dampingFraction: 0.9), value: dragOffset)
|
||||
.animation(.spring(response: 0.4, dampingFraction: 0.85), value: isPresented)
|
||||
.animation(.spring(response: 0.4, dampingFraction: 0.85), value: dragOffset)
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onChanged { value in
|
||||
if value.translation.height > 0 {
|
||||
dragOffset = value.translation.height
|
||||
}
|
||||
if value.translation.height > 0 { dragOffset = value.translation.height }
|
||||
}
|
||||
.onEnded { value in
|
||||
if value.translation.height > 100 { isPresented = false }
|
||||
@ -209,8 +148,9 @@ struct BottomMenu: View {
|
||||
private func menuButton(title: String, tab: Tab) -> some View {
|
||||
Button { selectedTab = tab } label: {
|
||||
Text(title)
|
||||
.font(.system(size: 14))
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 47)
|
||||
.frame(height: 40)
|
||||
.foregroundColor(.white)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
@ -220,6 +160,146 @@ struct BottomMenu: View {
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
// MARK: - Sights View
|
||||
@ViewBuilder
|
||||
private var sightsView: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 20) {
|
||||
ForEach(appState.sights) { sight in
|
||||
VStack(spacing: 8) {
|
||||
SightThumbnail(
|
||||
url: URL(string: "https://white-nights.krbl.ru/services/content/media/\(sight.thumbnail)/download?lang=\(appState.selectedLanguage)"),
|
||||
size: 80
|
||||
)
|
||||
.onTapGesture {
|
||||
appState.sightId = sight.id
|
||||
isPresented = false
|
||||
}
|
||||
|
||||
Text(sight.name)
|
||||
.font(.footnote)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
}.mask(
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: Color.black.opacity(0), location: 0),
|
||||
.init(color: Color.black, location: 0.05),
|
||||
.init(color: Color.black, location: 0.95),
|
||||
.init(color: Color.black.opacity(0), location: 1)
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Stops View
|
||||
@ViewBuilder
|
||||
private var stopsView: some View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 10) {
|
||||
if stopsVM.isLoading {
|
||||
ProgressView(appState.selectedLanguage == "ru" ? "Загрузка остановок..." : appState.selectedLanguage == "zh" ? "加载车站..." : "Loading stops...")
|
||||
.padding()
|
||||
} else {
|
||||
ForEach(stopsVM.stops) { stop in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Button {
|
||||
stopsVM.toggleStop(id: stop.id)
|
||||
} label: {
|
||||
Text(stop.name)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(.white.opacity(0.3)),
|
||||
alignment: .bottom
|
||||
)
|
||||
|
||||
if stopsVM.selectedStopDetail?.id == stop.id,
|
||||
let detail = stopsVM.selectedStopDetail {
|
||||
transfersView(for: detail)
|
||||
.transition(.opacity.combined(with: .scale(scale: 0.95, anchor: .top)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.mask(
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: Color.black.opacity(0), location: 0),
|
||||
.init(color: Color.black, location: 0.05),
|
||||
.init(color: Color.black, location: 0.95),
|
||||
.init(color: Color.black.opacity(0), location: 1)
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.onAppear {
|
||||
if stopsVM.stops.isEmpty, let routeId = appState.selectedRoute?.id {
|
||||
stopsVM.fetchStops(routeId: routeId, language: appState.selectedLanguage)
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.25),
|
||||
value: stopsVM.selectedStopDetail?.id)
|
||||
}
|
||||
|
||||
// MARK: - Footer
|
||||
@ViewBuilder
|
||||
private var menuFooter: some View {
|
||||
HStack(spacing: 16) {
|
||||
Image("GAT_Icon")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 30)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 4) {
|
||||
languageButton(imageName: "ru_lang_icon", code: "ru")
|
||||
languageButton(imageName: "zh_lang_icon", code: "zh")
|
||||
languageButton(imageName: "en_lang_icon", code: "en")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 26)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func languageButton(imageName: String, code: String) -> some View {
|
||||
Button {
|
||||
appState.selectedLanguage = code
|
||||
// Перезагрузка остановок при смене языка
|
||||
if let routeId = appState.selectedRoute?.id {
|
||||
stopsVM.fetchStops(routeId: routeId, language: code)
|
||||
}
|
||||
} label: {
|
||||
Image(imageName)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 24, height: 24)
|
||||
.brightness(appState.selectedLanguage == code ? 0.5 : 0) // осветление выбранного языка
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
// MARK: - Transfers View
|
||||
@ViewBuilder
|
||||
private func transfersView(for stop: StopDetail) -> some View {
|
||||
@ -240,31 +320,30 @@ struct BottomMenu: View {
|
||||
}
|
||||
|
||||
if items.isEmpty {
|
||||
Text("Нет пересадок")
|
||||
.font(.caption) // меньше размер
|
||||
Text(appState.selectedLanguage == "ru" ? "Нет пересадок" : appState.selectedLanguage == "zh" ? "没有换乘" : "No transfers")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.padding(.leading, 20)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.vertical, 4)
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 6) { // меньше расстояние
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
ForEach(items, id: \.0) { icon, text in
|
||||
HStack(spacing: 8) {
|
||||
HStack(spacing: 6) {
|
||||
Image(icon)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 18, height: 18)
|
||||
Text(text)
|
||||
.font(.caption) // меньше размер
|
||||
.font(.caption2)
|
||||
.foregroundColor(.white)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity, alignment: .leading) // левое выравнивание
|
||||
}
|
||||
.padding(.leading, 20)
|
||||
.padding(.vertical, 4)
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
.padding(.top, 6)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import SwiftUI
|
||||
|
||||
struct RouteSelectionView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var routes: [Route] = []
|
||||
@State private var isLoading = true
|
||||
@ -9,12 +10,17 @@ struct RouteSelectionView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
if isLoading {
|
||||
ProgressView("Загрузка маршрутов...")
|
||||
.padding()
|
||||
ProgressView(
|
||||
appState.selectedLanguage == "ru" ? "Загрузка маршрутов..." :
|
||||
appState.selectedLanguage == "zh" ? "正在加载路线..." :
|
||||
"Loading routes..."
|
||||
)
|
||||
.padding()
|
||||
} else {
|
||||
List(routes, id: \.id) { route in
|
||||
Button(action: {
|
||||
appState.selectedRoute = route
|
||||
dismiss()
|
||||
}) {
|
||||
HStack {
|
||||
Text("\(route.routeNumber)")
|
||||
@ -26,15 +32,21 @@ struct RouteSelectionView: View {
|
||||
.listStyle(PlainListStyle())
|
||||
}
|
||||
}
|
||||
.navigationTitle("Выберите маршрут")
|
||||
.navigationTitle(
|
||||
appState.selectedLanguage == "ru" ? "Выберите маршрут" :
|
||||
appState.selectedLanguage == "zh" ? "选择路线" :
|
||||
"Select route"
|
||||
)
|
||||
.onAppear {
|
||||
Task {
|
||||
await fetchRoutes()
|
||||
}
|
||||
}
|
||||
.onChange(of: appState.selectedLanguage) { _ in
|
||||
// просто перерисовываем view, navigationTitle автоматически обновится
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fetch Routes
|
||||
private func fetchRoutes() async {
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
|
@ -5,7 +5,7 @@ struct RouteView: View {
|
||||
|
||||
@State private var firstStationName: String = "Загрузка..."
|
||||
@State private var lastStationName: String = "Загрузка..."
|
||||
@State private var engStationsName: String = "Загрузка..."
|
||||
@State private var stationsRangeName: String = "Загрузка..."
|
||||
|
||||
private var topBackgroundColor = Color(hex: 0xFCD500)
|
||||
|
||||
@ -42,7 +42,7 @@ struct RouteView: View {
|
||||
foregroundColor: .white
|
||||
)
|
||||
MarqueeText(
|
||||
text: engStationsName,
|
||||
text: stationsRangeName,
|
||||
font: .caption,
|
||||
foregroundColor: .white.opacity(0.5)
|
||||
)
|
||||
@ -76,33 +76,72 @@ struct RouteView: View {
|
||||
await fetchStations(forRoute: routeID)
|
||||
}
|
||||
}
|
||||
.onChange(of: appState.selectedLanguage) { _ in
|
||||
if let routeID = appState.selectedRoute?.id {
|
||||
Task {
|
||||
await fetchStations(forRoute: routeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fetch Stations
|
||||
private func fetchStations(forRoute routeID: Int) async {
|
||||
firstStationName = "Загрузка..."
|
||||
lastStationName = "Загрузка..."
|
||||
engStationsName = "Loading..."
|
||||
|
||||
guard let url = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeID)/station"),
|
||||
let urlEng = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeID)/station?lang=en") else { return }
|
||||
// текст загрузки
|
||||
switch appState.selectedLanguage {
|
||||
case "ru":
|
||||
firstStationName = "Загрузка..."
|
||||
lastStationName = "Загрузка..."
|
||||
stationsRangeName = "Loading..."
|
||||
case "zh":
|
||||
firstStationName = "正在加载..."
|
||||
lastStationName = "正在加载..."
|
||||
stationsRangeName = "正在加载..."
|
||||
default:
|
||||
firstStationName = "Loading..."
|
||||
lastStationName = "Loading..."
|
||||
stationsRangeName = "Loading..."
|
||||
}
|
||||
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
let (dataEn, _) = try await URLSession.shared.data(from: urlEng)
|
||||
// Загружаем станции на русском для диапазона
|
||||
guard let urlRu = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeID)/station?lang=ru") else { return }
|
||||
let (dataRu, _) = try await URLSession.shared.data(from: urlRu)
|
||||
let stationsRu = try JSONDecoder().decode([Station].self, from: dataRu)
|
||||
|
||||
let stations = try JSONDecoder().decode([Station].self, from: data)
|
||||
// Загружаем станции на английском для диапазона
|
||||
guard let urlEn = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeID)/station?lang=en") else { return }
|
||||
let (dataEn, _) = try await URLSession.shared.data(from: urlEn)
|
||||
let stationsEn = try JSONDecoder().decode([Station].self, from: dataEn)
|
||||
|
||||
if let firstStation = stations.first { firstStationName = firstStation.name }
|
||||
if let lastStation = stations.last { lastStationName = lastStation.name }
|
||||
// Загружаем станции на языке интерфейса для отображения first/last
|
||||
let langCode = appState.selectedLanguage
|
||||
guard let urlCurrent = URL(string: "https://white-nights.krbl.ru/services/content/route/\(routeID)/station?lang=\(langCode)") else { return }
|
||||
let (dataCurrent, _) = try await URLSession.shared.data(from: urlCurrent)
|
||||
let stationsCurrent = try JSONDecoder().decode([Station].self, from: dataCurrent)
|
||||
|
||||
let firstStationEn = stationsEn.first?.name ?? "Loading..."
|
||||
let lastStationEn = stationsEn.last?.name ?? "Loading..."
|
||||
engStationsName = "\(firstStationEn) - \(lastStationEn)"
|
||||
// Устанавливаем first и last на текущем языке интерфейса
|
||||
if let first = stationsCurrent.first { firstStationName = first.name }
|
||||
if let last = stationsCurrent.last { lastStationName = last.name }
|
||||
|
||||
// Диапазон станций
|
||||
if appState.selectedLanguage == "ru" {
|
||||
// для русского языка диапазон на английском
|
||||
let firstEn = stationsEn.first?.name ?? ""
|
||||
let lastEn = stationsEn.last?.name ?? ""
|
||||
stationsRangeName = "\(firstEn) - \(lastEn)"
|
||||
} else {
|
||||
// для всех остальных языков диапазон на русском
|
||||
let firstRu = stationsRu.first?.name ?? ""
|
||||
let lastRu = stationsRu.last?.name ?? ""
|
||||
stationsRangeName = "\(firstRu) - \(lastRu)"
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("Ошибка загрузки станций: \(error)")
|
||||
firstStationName = appState.selectedLanguage == "ru" ? "Ошибка загрузки" : appState.selectedLanguage == "zh" ? "加载失败" : "Failed to load"
|
||||
lastStationName = appState.selectedLanguage == "ru" ? "Ошибка загрузки" : appState.selectedLanguage == "zh" ? "加载失败" : "Failed to load"
|
||||
stationsRangeName = appState.selectedLanguage == "ru" ? "Ошибка загрузки" : appState.selectedLanguage == "zh" ? "加载失败" : "Failed to load"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,28 @@
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import NukeUI
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - SightView
|
||||
|
||||
struct SightView: View {
|
||||
let sightId: Int
|
||||
@StateObject private var viewModel = SightViewModel()
|
||||
@EnvironmentObject private var appState: AppState
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
mediaSection
|
||||
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
// Заголовок статьи
|
||||
Text(viewModel.selectedArticle?.isReviewArticle == true ? viewModel.sightName : viewModel.articleHeading)
|
||||
Text(viewModel.selectedArticle?.isReviewArticle == true
|
||||
? viewModel.sightName
|
||||
: viewModel.articleHeading)
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity, alignment: viewModel.selectedArticle?.isReviewArticle == true ? .center : .leading)
|
||||
|
||||
.frame(maxWidth: .infinity,
|
||||
alignment: viewModel.selectedArticle?.isReviewArticle == true ? .center : .leading)
|
||||
.multilineTextAlignment(viewModel.selectedArticle?.isReviewArticle == true ? .center : .leading)
|
||||
|
||||
// Тело статьи
|
||||
ScrollView {
|
||||
if viewModel.selectedArticle?.isReviewArticle == true {
|
||||
@ -38,16 +39,14 @@ struct SightView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
// Список статей (кнопки навигации) - ФИНАЛЬНОЕ ИСПРАВЛЕНИЕ ЦЕНТРИРОВАНИЯ
|
||||
|
||||
// Список статей
|
||||
GeometryReader { geometry in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 6) {
|
||||
// Spacers для центрирования
|
||||
HStack(spacing: 10) {
|
||||
Spacer(minLength: 0)
|
||||
|
||||
ForEach(viewModel.allArticles) { article in
|
||||
Text(article.heading)
|
||||
Text(localizedHeading(article))
|
||||
.font(.system(size: 12))
|
||||
.lineLimit(1)
|
||||
.padding(.vertical, 6)
|
||||
@ -64,28 +63,34 @@ struct SightView: View {
|
||||
viewModel.selectArticle(article)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
// Принудительно задаем ширину HStack как ширину GeometryReader
|
||||
.frame(minWidth: geometry.size.width)
|
||||
}
|
||||
.scrollIndicators(.hidden) // Скрываем полосу прокрутки
|
||||
.scrollIndicators(.hidden)
|
||||
}
|
||||
// Задаем высоту для GeometryReader
|
||||
.frame(height: 34)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
// MARK: - Initial load
|
||||
.task(id: sightId) {
|
||||
viewModel.setLanguage(appState.selectedLanguage)
|
||||
await viewModel.loadInitialData(sightId: sightId)
|
||||
}
|
||||
// MARK: - Reload on language change
|
||||
.onChange(of: appState.selectedLanguage) { newLang in
|
||||
viewModel.setLanguage(newLang)
|
||||
Task {
|
||||
await viewModel.loadInitialData(sightId: sightId)
|
||||
}
|
||||
}
|
||||
.blockStyle(cornerRadius: 25)
|
||||
}
|
||||
|
||||
// Медиа-секция
|
||||
// MARK: - Медиа
|
||||
@ViewBuilder
|
||||
private var mediaSection: some View {
|
||||
Group {
|
||||
@ -98,29 +103,29 @@ struct SightView: View {
|
||||
.tint(.white)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.aspectRatio(16/9, contentMode: .fit)
|
||||
.frame(height: 160)
|
||||
.cornerRadius(24, corners: [.topLeft, .topRight])
|
||||
.clipped()
|
||||
|
||||
case .image(let url):
|
||||
LazyImage(url: url) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
image.resizable().scaledToFit()
|
||||
} else {
|
||||
ProgressView()
|
||||
ZStack {
|
||||
Color.gray.opacity(0.3)
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.tint(.white)
|
||||
}
|
||||
}
|
||||
}
|
||||
.cornerRadius(24, corners: [.topLeft, .topRight])
|
||||
.clipped()
|
||||
|
||||
case .video(let player):
|
||||
VideoPlayer(player: player)
|
||||
.aspectRatio(16/9, contentMode: .fit)
|
||||
.cornerRadius(24, corners: [.topLeft, .topRight])
|
||||
.clipped()
|
||||
|
||||
case .error:
|
||||
Image(systemName: "photo")
|
||||
.resizable()
|
||||
@ -134,4 +139,18 @@ struct SightView: View {
|
||||
.padding(4)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
// MARK: - Локализованные заголовки статей
|
||||
private func localizedHeading(_ article: Article) -> String {
|
||||
if article.isReviewArticle == true {
|
||||
switch appState.selectedLanguage {
|
||||
case "ru": return "Обзор"
|
||||
case "en": return "Review"
|
||||
case "zh": return "奥布佐尔"
|
||||
default: return "Обзор"
|
||||
}
|
||||
} else {
|
||||
return article.heading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Foundation
|
||||
import AVKit
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
class SightViewModel: ObservableObject {
|
||||
@Published var sightName: String = "Загрузка..."
|
||||
@ -10,8 +9,9 @@ class SightViewModel: ObservableObject {
|
||||
@Published var articleHeading: String = ""
|
||||
@Published var articleBody: String = ""
|
||||
@Published var mediaState: MediaState = .loading
|
||||
|
||||
|
||||
private var sightModel: SightModel?
|
||||
private var selectedLanguage: String = "ru" // по умолчанию
|
||||
|
||||
enum MediaState {
|
||||
case loading
|
||||
@ -20,10 +20,20 @@ class SightViewModel: ObservableObject {
|
||||
case error
|
||||
}
|
||||
|
||||
func setLanguage(_ language: String) {
|
||||
self.selectedLanguage = language
|
||||
}
|
||||
|
||||
func loadInitialData(sightId: Int) async {
|
||||
do {
|
||||
async let sightModelTask = fetchJSON(from: "https://white-nights.krbl.ru/services/content/sight/\(sightId)", type: SightModel.self)
|
||||
async let articlesTask = fetchJSON(from: "https://white-nights.krbl.ru/services/content/sight/\(sightId)/article", type: [Article].self)
|
||||
async let sightModelTask = fetchJSON(
|
||||
from: "https://white-nights.krbl.ru/services/content/sight/\(sightId)?lang=\(selectedLanguage)",
|
||||
type: SightModel.self
|
||||
)
|
||||
async let articlesTask = fetchJSON(
|
||||
from: "https://white-nights.krbl.ru/services/content/sight/\(sightId)/article?lang=\(selectedLanguage)",
|
||||
type: [Article].self
|
||||
)
|
||||
|
||||
let (fetchedSightModel, fetchedArticles) = try await (sightModelTask, articlesTask)
|
||||
|
||||
@ -63,20 +73,23 @@ class SightViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
if let videoPreviewId = sight.video_preview,
|
||||
let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(videoPreviewId)/download") {
|
||||
let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(videoPreviewId)/download?lang=\(selectedLanguage)") {
|
||||
let player = AVPlayer(url: url)
|
||||
player.play()
|
||||
self.mediaState = .video(player)
|
||||
} else if let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(sight.preview_media)/download") {
|
||||
} else if let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(sight.preview_media)/download?lang=\(selectedLanguage)") {
|
||||
self.mediaState = .image(url)
|
||||
} else {
|
||||
self.mediaState = .error
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
let mediaItems = try await fetchJSON(from: "https://white-nights.krbl.ru/services/content/article/\(article.id)/media", type: [ArticleMedia].self)
|
||||
let mediaItems = try await fetchJSON(
|
||||
from: "https://white-nights.krbl.ru/services/content/article/\(article.id)/media?lang=\(selectedLanguage)",
|
||||
type: [ArticleMedia].self
|
||||
)
|
||||
if let firstMedia = mediaItems.first,
|
||||
let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(firstMedia.id)/download") {
|
||||
let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(firstMedia.id)/download?lang=\(selectedLanguage)") {
|
||||
self.mediaState = .image(url)
|
||||
} else {
|
||||
self.mediaState = .error
|
||||
|
@ -7,5 +7,7 @@ struct VisualEffectBlur: UIViewRepresentable {
|
||||
UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIVisualEffectView, context: Context) { }
|
||||
func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
|
||||
uiView.effect = UIBlurEffect(style: blurStyle)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user