import SwiftUI import AVKit import NukeUI struct SightView: View { @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 and reload on sight change .task(id: appState.sightId) { guard let currentSightId = appState.sightId else { return } viewModel.setLanguage(appState.selectedLanguage) await viewModel.loadInitialData(sightId: currentSightId) } // MARK: - Reload on language change .onChange(of: appState.selectedLanguage) { newLang in guard let currentSightId = appState.sightId else { return } viewModel.setLanguage(newLang) Task { await viewModel.loadInitialData(sightId: currentSightId) } } .blockStyle(cornerRadius: 25) } // MARK: - Медиа @ViewBuilder private var mediaSection: some View { Group { switch viewModel.mediaState { case .loading: ZStack { Color.gray.opacity(0.3) if let progress = viewModel.downloadProgress { VStack { ProgressView(value: progress) .progressViewStyle(.linear) .tint(.white) Text("\(Int(progress * 100))%") .foregroundColor(.white) .font(.caption) } .padding() } else { ProgressView() .progressViewStyle(.circular) .tint(.white) } } .frame(maxWidth: .infinity, minHeight: 200) .cornerRadius(24, corners: [.topLeft, .topRight]) .clipped() case .image(let url): if let uiImage = UIImage(contentsOfFile: url.path) { Image(uiImage: uiImage) .resizable() .scaledToFit() .cornerRadius(24, corners: [.topLeft, .topRight]) .clipped() } else { Image(systemName: "photo") .resizable() .scaledToFit() .foregroundColor(.gray) .frame(maxWidth: .infinity, minHeight: 200) .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 } } }