173 lines
7.0 KiB
Swift
173 lines
7.0 KiB
Swift
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
|
||
}
|
||
}
|
||
}
|