Files
WhiteNights_iOS/WhiteNights/Widgets/SightViewModel.swift
15lu.akari 49737209bd last-update
2025-08-29 19:43:57 +03:00

209 lines
8.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}