import SwiftUI import AVKit import NukeUI 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) .font(.title2) .fontWeight(.bold) .foregroundColor(.white) .frame(maxWidth: .infinity, alignment: viewModel.selectedArticle?.isReviewArticle == true ? .center : .leading) .multilineTextAlignment(viewModel.selectedArticle?.isReviewArticle == true ? .center : .leading) // Тело статьи ScrollView { if viewModel.selectedArticle?.isReviewArticle == true { VStack { Text(viewModel.articleBody) .font(.system(size: 13)) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: .infinity) } } else { Text(viewModel.articleBody) .font(.system(size: 13)) .foregroundColor(.white) .frame(maxWidth: .infinity, alignment: .leading) } } // Список статей GeometryReader { geometry in ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 10) { Spacer(minLength: 0) ForEach(viewModel.allArticles) { article in Text(localizedHeading(article)) .font(.system(size: 12)) .lineLimit(1) .padding(.vertical, 6) .padding(.horizontal, 6) .frame(minWidth: 70) .foregroundColor(.white) .overlay( Rectangle() .frame(height: 2) .foregroundColor(viewModel.selectedArticle == article ? Color.white : Color.clear), alignment: .bottom ) .onTapGesture { viewModel.selectArticle(article) } } Spacer(minLength: 0) } .frame(minWidth: geometry.size.width) } .scrollIndicators(.hidden) } .frame(height: 34) .frame(maxWidth: .infinity) } .padding(.horizontal, 8) .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 { switch viewModel.mediaState { case .loading: ZStack { Color.gray.opacity(0.3) ProgressView() .progressViewStyle(.circular) .tint(.white) } .frame(maxWidth: .infinity) .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() } else { 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() .aspectRatio(contentMode: .fit) .foregroundColor(.gray) .frame(maxWidth: .infinity, minHeight: 200) .cornerRadius(24, corners: [.topLeft, .topRight]) .clipped() } } .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 } } }