209 lines
8.6 KiB
Swift
209 lines
8.6 KiB
Swift
import Foundation
|
||
import AVKit
|
||
import Combine
|
||
import SwiftUI
|
||
|
||
// MARK: - ViewModel
|
||
@MainActor
|
||
class SightViewModel: ObservableObject {
|
||
private let fileDownloader = FileDownloader()
|
||
|
||
private var mediaCache: [String: URL] = [:]
|
||
|
||
@Published var sightName: String = "Загрузка..."
|
||
@Published var allArticles: [Article] = []
|
||
@Published var selectedArticle: Article?
|
||
@Published var articleHeading: String = ""
|
||
@Published var articleBody: String = ""
|
||
@Published var mediaState: MediaState = .loading
|
||
@Published var downloadProgress: Double? = nil // прогресс загрузки
|
||
|
||
private var sightModel: SightModel?
|
||
private var selectedLanguage: String = "ru" // по умолчанию
|
||
|
||
enum MediaState {
|
||
case loading
|
||
case image(URL)
|
||
case video(AVPlayer)
|
||
case error
|
||
}
|
||
|
||
func setLanguage(_ language: String) {
|
||
self.selectedLanguage = language
|
||
}
|
||
|
||
func loadInitialData(sightId: Int) async {
|
||
// Вот это исправление. Сбрасываем выбранную статью перед загрузкой новых данных.
|
||
self.selectedArticle = nil
|
||
|
||
do {
|
||
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)
|
||
|
||
self.sightModel = fetchedSightModel
|
||
self.sightName = fetchedSightModel.name
|
||
|
||
let reviewArticle = Article(id: -1, body: "", heading: "Обзор", isReviewArticle: true)
|
||
self.allArticles = [reviewArticle] + fetchedArticles
|
||
|
||
selectArticle(reviewArticle)
|
||
|
||
} catch {
|
||
print("Ошибка начальной загрузки данных: \(error)")
|
||
self.mediaState = .error
|
||
}
|
||
}
|
||
|
||
func selectArticle(_ article: Article) {
|
||
guard selectedArticle != article else { return }
|
||
|
||
self.selectedArticle = article
|
||
self.articleHeading = article.heading
|
||
self.articleBody = article.body
|
||
|
||
Task {
|
||
await updateMedia(for: article)
|
||
}
|
||
}
|
||
|
||
// MARK: - Загрузка медиа
|
||
@MainActor
|
||
private func updateMedia(for article: Article) async {
|
||
self.mediaState = .loading
|
||
self.downloadProgress = nil
|
||
|
||
do {
|
||
if article.isReviewArticle ?? false {
|
||
guard let sight = sightModel else { return }
|
||
|
||
if let videoPreviewId = sight.video_preview {
|
||
if let cachedURL = mediaCache[videoPreviewId] {
|
||
let player = AVPlayer(url: cachedURL)
|
||
self.mediaState = .video(player)
|
||
player.play()
|
||
await player.seek(to: .zero)
|
||
return
|
||
}
|
||
|
||
let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(videoPreviewId)/download?lang=\(selectedLanguage)")!
|
||
let (data, _) = try await URLSession.shared.data(from: url)
|
||
let localFile = try saveMediaToFile(data: data, fileExtension: "mp4")
|
||
mediaCache[videoPreviewId] = localFile
|
||
|
||
let player = AVPlayer(url: localFile)
|
||
let asset = player.currentItem?.asset
|
||
if let asset = asset {
|
||
try await asset.load(.isPlayable)
|
||
if asset.isPlayable {
|
||
self.mediaState = .video(player)
|
||
player.play()
|
||
await player.seek(to: .zero)
|
||
} else {
|
||
self.mediaState = .error
|
||
}
|
||
} else {
|
||
self.mediaState = .error
|
||
}
|
||
|
||
} else if let previewMediaId = sight.preview_media {
|
||
if let cachedURL = mediaCache[previewMediaId] {
|
||
self.mediaState = .image(cachedURL)
|
||
return
|
||
}
|
||
|
||
let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(previewMediaId)/download?lang=\(selectedLanguage)")!
|
||
let (data, _) = try await URLSession.shared.data(from: url)
|
||
if let image = UIImage(data: data) {
|
||
let localFile = try saveMediaToFile(data: data, fileExtension: "jpeg")
|
||
mediaCache[previewMediaId] = localFile
|
||
self.mediaState = .image(localFile)
|
||
} else {
|
||
self.mediaState = .error
|
||
}
|
||
} else {
|
||
self.mediaState = .error
|
||
}
|
||
|
||
} else {
|
||
// Загрузка медиа для статьи
|
||
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 mediaId = "\(firstMedia.id)"
|
||
if let cachedURL = mediaCache[mediaId] {
|
||
self.mediaState = .image(cachedURL)
|
||
return
|
||
}
|
||
|
||
if let url = URL(string: "https://white-nights.krbl.ru/services/content/media/\(firstMedia.id)/download?lang=\(selectedLanguage)") {
|
||
let (data, _) = try await URLSession.shared.data(from: url)
|
||
if let image = UIImage(data: data) {
|
||
let localFile = try saveMediaToFile(data: data, fileExtension: "jpeg")
|
||
mediaCache[mediaId] = localFile
|
||
self.mediaState = .image(localFile)
|
||
} else {
|
||
self.mediaState = .error
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch {
|
||
print("Ошибка загрузки файла: \(error)")
|
||
self.mediaState = .error
|
||
self.downloadProgress = nil
|
||
}
|
||
}
|
||
|
||
|
||
private func saveMediaToFile(data: Data, fileExtension: String) throws -> URL {
|
||
let destinationURL = FileManager.default.temporaryDirectory
|
||
.appendingPathComponent(UUID().uuidString)
|
||
.appendingPathExtension(fileExtension)
|
||
|
||
try data.write(to: destinationURL)
|
||
return destinationURL
|
||
}
|
||
|
||
// MARK: - Загрузка файла с прогрессом
|
||
private func downloadFile(from url: URL) async throws -> URL {
|
||
let (tempLocalUrl, response) = try await URLSession.shared.download(from: url)
|
||
|
||
guard let httpResponse = response as? HTTPURLResponse,
|
||
(200...299).contains(httpResponse.statusCode) else {
|
||
throw URLError(.badServerResponse)
|
||
}
|
||
|
||
// Пытаемся угадать расширение
|
||
let suggestedName = response.suggestedFilename ?? UUID().uuidString
|
||
let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(suggestedName)
|
||
|
||
// Удаляем файл, если уже есть
|
||
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
||
try FileManager.default.removeItem(at: destinationURL)
|
||
}
|
||
|
||
try FileManager.default.moveItem(at: tempLocalUrl, to: destinationURL)
|
||
return destinationURL
|
||
}
|
||
|
||
// MARK: - JSON загрузчик
|
||
private func fetchJSON<T: Decodable>(from urlString: String, type: T.Type) async throws -> T {
|
||
guard let url = URL(string: urlString) else {
|
||
throw URLError(.badURL)
|
||
}
|
||
let (data, _) = try await URLSession.shared.data(from: url)
|
||
return try JSONDecoder().decode(T.self, from: data)
|
||
}
|
||
}
|