Files
WhiteNights_iOS/WhiteNights/Widgets/SightViewModel.swift
15lu.akari 30d97f420e fix video
2025-08-27 00:19:05 +03:00

196 lines
8.0 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()
@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: - Загрузка медиа
// 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 {
// Пытаемся загрузить видео-превью
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)
// Пробуем создать AVPlayer из данных
let localFile = try saveMediaToFile(data: data, fileExtension: "mp4")
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)
self.downloadProgress = nil
player.play()
await player.seek(to: .zero)
} else {
self.mediaState = .error
}
} else {
self.mediaState = .error
}
} else if let previewMediaId = sight.preview_media {
// Если нет видео-превью, пытаемся загрузить изображение
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)
// Пробуем создать UIImage из данных
if let image = UIImage(data: data) {
let localFile = try saveMediaToFile(data: data, fileExtension: "jpeg")
self.mediaState = .image(localFile)
self.downloadProgress = nil
} 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 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")
self.mediaState = .image(localFile)
self.downloadProgress = nil
} 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)
}
}