196 lines
5.3 KiB
Swift
196 lines
5.3 KiB
Swift
//
|
|
// Swiftfin is subject to the terms of the Mozilla Public
|
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import Combine
|
|
import Foundation
|
|
import JellyfinAPI
|
|
import OrderedCollections
|
|
|
|
class ItemEditorViewModel<ItemType: Equatable>: ViewModel, Stateful, Eventful {
|
|
|
|
// MARK: - Events
|
|
|
|
enum Event: Equatable {
|
|
case updated
|
|
case error(JellyfinAPIError)
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
enum Action: Equatable {
|
|
case add([ItemType])
|
|
case remove([ItemType])
|
|
case update(BaseItemDto)
|
|
}
|
|
|
|
// MARK: BackgroundState
|
|
|
|
enum BackgroundState: Hashable {
|
|
case refreshing
|
|
}
|
|
|
|
// MARK: - State
|
|
|
|
enum State: Hashable {
|
|
case initial
|
|
case error(JellyfinAPIError)
|
|
case updating
|
|
}
|
|
|
|
@Published
|
|
var backgroundStates: OrderedSet<BackgroundState> = []
|
|
|
|
@Published
|
|
var item: BaseItemDto
|
|
|
|
@Published
|
|
var state: State = .initial
|
|
|
|
private var task: AnyCancellable?
|
|
private let eventSubject = PassthroughSubject<Event, Never>()
|
|
|
|
var events: AnyPublisher<Event, Never> {
|
|
eventSubject.receive(on: RunLoop.main).eraseToAnyPublisher()
|
|
}
|
|
|
|
// MARK: - Init
|
|
|
|
init(item: BaseItemDto) {
|
|
self.item = item
|
|
super.init()
|
|
}
|
|
|
|
// MARK: - Respond to Actions
|
|
|
|
func respond(to action: Action) -> State {
|
|
switch action {
|
|
case let .add(items):
|
|
task?.cancel()
|
|
|
|
task = Task { [weak self] in
|
|
guard let self = self else { return }
|
|
do {
|
|
await MainActor.run { self.state = .updating }
|
|
|
|
try await self.addComponents(items)
|
|
|
|
await MainActor.run {
|
|
self.state = .initial
|
|
self.eventSubject.send(.updated)
|
|
}
|
|
} catch {
|
|
let apiError = JellyfinAPIError(error.localizedDescription)
|
|
await MainActor.run {
|
|
self.state = .error(apiError)
|
|
self.eventSubject.send(.error(apiError))
|
|
}
|
|
}
|
|
}.asAnyCancellable()
|
|
|
|
return state
|
|
|
|
case let .remove(items):
|
|
task?.cancel()
|
|
|
|
task = Task { [weak self] in
|
|
guard let self = self else { return }
|
|
do {
|
|
await MainActor.run { self.state = .updating }
|
|
|
|
try await self.removeComponents(items)
|
|
|
|
await MainActor.run {
|
|
self.state = .initial
|
|
self.eventSubject.send(.updated)
|
|
}
|
|
} catch {
|
|
let apiError = JellyfinAPIError(error.localizedDescription)
|
|
await MainActor.run {
|
|
self.state = .error(apiError)
|
|
self.eventSubject.send(.error(apiError))
|
|
}
|
|
}
|
|
}.asAnyCancellable()
|
|
|
|
return state
|
|
|
|
case let .update(newItem):
|
|
task?.cancel()
|
|
|
|
task = Task { [weak self] in
|
|
guard let self = self else { return }
|
|
do {
|
|
await MainActor.run { self.state = .updating }
|
|
|
|
try await self.updateItem(newItem)
|
|
|
|
await MainActor.run {
|
|
self.state = .initial
|
|
self.eventSubject.send(.updated)
|
|
}
|
|
} catch {
|
|
let apiError = JellyfinAPIError(error.localizedDescription)
|
|
await MainActor.run {
|
|
self.state = .error(apiError)
|
|
self.eventSubject.send(.error(apiError))
|
|
}
|
|
}
|
|
}.asAnyCancellable()
|
|
|
|
return state
|
|
}
|
|
}
|
|
|
|
// MARK: - Save Updated Item to Server
|
|
|
|
func updateItem(_ newItem: BaseItemDto, refresh: Bool = false) async throws {
|
|
guard let itemId = item.id else { return }
|
|
|
|
let request = Paths.updateItem(itemID: itemId, newItem)
|
|
_ = try await userSession.client.send(request)
|
|
|
|
if refresh {
|
|
try await refreshItem()
|
|
}
|
|
|
|
await MainActor.run {
|
|
Notifications[.itemMetadataDidChange].post(object: newItem)
|
|
}
|
|
}
|
|
|
|
// MARK: - Refresh Item
|
|
|
|
private func refreshItem() async throws {
|
|
guard let itemId = item.id else { return }
|
|
|
|
await MainActor.run {
|
|
_ = backgroundStates.append(.refreshing)
|
|
}
|
|
|
|
let request = Paths.getItem(userID: userSession.user.id, itemID: itemId)
|
|
let response = try await userSession.client.send(request)
|
|
|
|
await MainActor.run {
|
|
self.item = response.value
|
|
_ = backgroundStates.remove(.refreshing)
|
|
}
|
|
}
|
|
|
|
// MARK: - Add Items (To be overridden)
|
|
|
|
func addComponents(_ items: [ItemType]) async throws {
|
|
fatalError("This method should be overridden in subclasses")
|
|
}
|
|
|
|
// MARK: - Remove Items (To be overridden)
|
|
|
|
func removeComponents(_ items: [ItemType]) async throws {
|
|
fatalError("This method should be overridden in subclasses")
|
|
}
|
|
}
|