diff --git a/Shared/Objects/Stateful.swift b/Shared/Objects/Stateful.swift index fda22269..889415bf 100644 --- a/Shared/Objects/Stateful.swift +++ b/Shared/Objects/Stateful.swift @@ -6,17 +6,11 @@ // Copyright (c) 2025 Jellyfin & Jellyfin Contributors // -import Foundation -import OrderedCollections - // TODO: documentation // TODO: find a better way to handle backgroundStates on action/state transitions // so that conformers don't have to manually insert/remove them -// TODO: better/official way for subclasses of conformers to perform actions during -// parent class actions // TODO: official way for a cleaner `respond` method so it doesn't have all Task // construction and get bloated -// TODO: move backgroundStates to just a `Set` protocol Stateful: AnyObject { @@ -27,9 +21,8 @@ protocol Stateful: AnyObject { /// Background states that the conformer can be in. /// Usually used to indicate background events that shouldn't /// set the conformer to a primary state. - var backgroundStates: OrderedSet { get set } + var backgroundStates: Set { get set } - var lastAction: Action? { get set } var state: State { get set } /// Respond to a sent action and return the new state @@ -44,21 +37,15 @@ protocol Stateful: AnyObject { extension Stateful { - var lastAction: Action? { - get { nil } - set {} - } - @MainActor func send(_ action: Action) { state = respond(to: action) - lastAction = action } } extension Stateful where BackgroundState == Never { - var backgroundStates: OrderedSet { + var backgroundStates: Set { get { assertionFailure("Attempted to access `backgroundStates` when there are none") return [] diff --git a/Shared/ViewModels/AdminDashboard/APIKeysViewModel.swift b/Shared/ViewModels/AdminDashboard/APIKeysViewModel.swift index ee4f304e..464a9413 100644 --- a/Shared/ViewModels/AdminDashboard/APIKeysViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/APIKeysViewModel.swift @@ -34,9 +34,9 @@ final class APIKeysViewModel: ViewModel, Stateful { // MARK: Published Variables @Published - final var apiKeys: [AuthenticationInfo] = [] + var apiKeys: [AuthenticationInfo] = [] @Published - final var state: State = .initial + var state: State = .initial // MARK: Action Responses diff --git a/Shared/ViewModels/AdminDashboard/ActiveSessionsViewModel.swift b/Shared/ViewModels/AdminDashboard/ActiveSessionsViewModel.swift index 00c93040..45a1054f 100644 --- a/Shared/ViewModels/AdminDashboard/ActiveSessionsViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/ActiveSessionsViewModel.swift @@ -36,11 +36,11 @@ final class ActiveSessionsViewModel: ViewModel, Stateful { } @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published - final var sessions: OrderedDictionary> = [:] + var sessions: OrderedDictionary> = [:] @Published - final var state: State = .initial + var state: State = .initial private let activeWithinSeconds: Int = 960 private var sessionTask: AnyCancellable? @@ -52,7 +52,7 @@ final class ActiveSessionsViewModel: ViewModel, Stateful { sessionTask = Task { [weak self] in await MainActor.run { - let _ = self?.backgroundStates.append(.gettingSessions) + let _ = self?.backgroundStates.insert(.gettingSessions) } do { diff --git a/Shared/ViewModels/AdminDashboard/AddServerUserViewModel.swift b/Shared/ViewModels/AdminDashboard/AddServerUserViewModel.swift index 79ce618e..4435861c 100644 --- a/Shared/ViewModels/AdminDashboard/AddServerUserViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/AddServerUserViewModel.swift @@ -45,7 +45,7 @@ final class AddServerUserViewModel: ViewModel, Eventful, Stateful, Identifiable } @Published - final var state: State = .initial + var state: State = .initial private var userTask: AnyCancellable? private var eventSubject: PassthroughSubject = .init() diff --git a/Shared/ViewModels/AdminDashboard/DeviceDetailViewModel.swift b/Shared/ViewModels/AdminDashboard/DeviceDetailViewModel.swift index ac4f35a0..c5fad5d9 100644 --- a/Shared/ViewModels/AdminDashboard/DeviceDetailViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/DeviceDetailViewModel.swift @@ -11,7 +11,7 @@ import Foundation import JellyfinAPI import OrderedCollections -class DeviceDetailViewModel: ViewModel, Stateful, Eventful { +final class DeviceDetailViewModel: ViewModel, Stateful, Eventful { enum Event { case error(JellyfinAPIError) @@ -31,7 +31,7 @@ class DeviceDetailViewModel: ViewModel, Stateful, Eventful { } @Published - var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published var state: State = .initial @@ -57,7 +57,7 @@ class DeviceDetailViewModel: ViewModel, Stateful, Eventful { Task { await MainActor.run { - _ = backgroundStates.append(.updating) + _ = backgroundStates.insert(.updating) } do { diff --git a/Shared/ViewModels/AdminDashboard/DevicesViewModel.swift b/Shared/ViewModels/AdminDashboard/DevicesViewModel.swift index 3917b997..decb00e0 100644 --- a/Shared/ViewModels/AdminDashboard/DevicesViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/DevicesViewModel.swift @@ -47,11 +47,11 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful { // MARK: Published Values @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published - final var devices: [DeviceInfo] = [] + var devices: [DeviceInfo] = [] @Published - final var state: State = .initial + var state: State = .initial var events: AnyPublisher { eventSubject @@ -69,7 +69,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful { case .refresh: deviceTask?.cancel() - backgroundStates.append(.refreshing) + backgroundStates.insert(.refreshing) deviceTask = Task { [weak self] in do { @@ -98,7 +98,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful { case let .delete(ids): deviceTask?.cancel() - backgroundStates.append(.deleting) + backgroundStates.insert(.deleting) deviceTask = Task { [weak self] in do { diff --git a/Shared/ViewModels/AdminDashboard/ServerTaskObserver.swift b/Shared/ViewModels/AdminDashboard/ServerTaskObserver.swift index 3205ca85..04e29771 100644 --- a/Shared/ViewModels/AdminDashboard/ServerTaskObserver.swift +++ b/Shared/ViewModels/AdminDashboard/ServerTaskObserver.swift @@ -48,9 +48,9 @@ final class ServerTaskObserver: ViewModel, Stateful, Eventful, Identifiable { // MARK: Published Values @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published - final var state: State = .initial + var state: State = .initial @Published private(set) var task: TaskInfo @@ -138,7 +138,7 @@ final class ServerTaskObserver: ViewModel, Stateful, Eventful, Identifiable { .appending(trigger) await MainActor.run { - _ = self.backgroundStates.append(.updatingTriggers) + _ = self.backgroundStates.insert(.updatingTriggers) } do { @@ -165,7 +165,7 @@ final class ServerTaskObserver: ViewModel, Stateful, Eventful, Identifiable { updatedTriggers.removeAll { $0 == trigger } await MainActor.run { - _ = self.backgroundStates.append(.updatingTriggers) + _ = self.backgroundStates.insert(.updatingTriggers) } do { diff --git a/Shared/ViewModels/AdminDashboard/ServerTasksViewModel.swift b/Shared/ViewModels/AdminDashboard/ServerTasksViewModel.swift index 5f2fb8f4..b4c4ad0a 100644 --- a/Shared/ViewModels/AdminDashboard/ServerTasksViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/ServerTasksViewModel.swift @@ -41,11 +41,11 @@ final class ServerTasksViewModel: ViewModel, Stateful { } @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published - final var state: State = .initial + var state: State = .initial @Published - final var tasks: OrderedDictionary = [:] + var tasks: OrderedDictionary = [:] private var getTasksCancellable: AnyCancellable? diff --git a/Shared/ViewModels/AdminDashboard/ServerUserAdminViewModel.swift b/Shared/ViewModels/AdminDashboard/ServerUserAdminViewModel.swift index 3d90edc2..d1be757a 100644 --- a/Shared/ViewModels/AdminDashboard/ServerUserAdminViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/ServerUserAdminViewModel.swift @@ -49,9 +49,9 @@ final class ServerUserAdminViewModel: ViewModel, Eventful, Stateful, Identifiabl // MARK: - Published Values @Published - final var state: State = .initial + var state: State = .initial @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published private(set) var user: UserDto @@ -99,7 +99,7 @@ final class ServerUserAdminViewModel: ViewModel, Eventful, Stateful, Identifiabl userTaskCancellable = Task { do { await MainActor.run { - _ = backgroundStates.append(.refreshing) + _ = backgroundStates.insert(.refreshing) } try await loadDetails() @@ -126,7 +126,7 @@ final class ServerUserAdminViewModel: ViewModel, Eventful, Stateful, Identifiabl userTaskCancellable = Task { do { await MainActor.run { - _ = backgroundStates.append(.refreshing) + _ = backgroundStates.insert(.refreshing) } try await loadLibraries(isHidden: isHidden) @@ -153,7 +153,7 @@ final class ServerUserAdminViewModel: ViewModel, Eventful, Stateful, Identifiabl userTaskCancellable = Task { do { await MainActor.run { - _ = backgroundStates.append(.updating) + _ = backgroundStates.insert(.updating) } try await updatePolicy(policy: policy) @@ -181,7 +181,7 @@ final class ServerUserAdminViewModel: ViewModel, Eventful, Stateful, Identifiabl userTaskCancellable = Task { do { await MainActor.run { - _ = backgroundStates.append(.updating) + _ = backgroundStates.insert(.updating) } try await updateConfiguration(configuration: configuration) @@ -209,7 +209,7 @@ final class ServerUserAdminViewModel: ViewModel, Eventful, Stateful, Identifiabl userTaskCancellable = Task { do { await MainActor.run { - _ = backgroundStates.append(.updating) + _ = backgroundStates.insert(.updating) } try await updateUsername(username: username) diff --git a/Shared/ViewModels/AdminDashboard/ServerUsersViewModel.swift b/Shared/ViewModels/AdminDashboard/ServerUsersViewModel.swift index bc193b5f..d63e1bb5 100644 --- a/Shared/ViewModels/AdminDashboard/ServerUsersViewModel.swift +++ b/Shared/ViewModels/AdminDashboard/ServerUsersViewModel.swift @@ -50,13 +50,13 @@ final class ServerUsersViewModel: ViewModel, Eventful, Stateful, Identifiable { // MARK: Published Values @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published - final var users: IdentifiedArrayOf = [] + var users: IdentifiedArrayOf = [] @Published - final var state: State = .initial + var state: State = .initial var events: AnyPublisher { eventSubject @@ -88,7 +88,7 @@ final class ServerUsersViewModel: ViewModel, Eventful, Stateful, Identifiable { switch action { case let .refreshUser(userID): userTask?.cancel() - backgroundStates.append(.gettingUsers) + backgroundStates.insert(.gettingUsers) userTask = Task { do { @@ -114,7 +114,7 @@ final class ServerUsersViewModel: ViewModel, Eventful, Stateful, Identifiable { case let .getUsers(isHidden, isDisabled): userTask?.cancel() - backgroundStates.append(.gettingUsers) + backgroundStates.insert(.gettingUsers) userTask = Task { do { @@ -140,7 +140,7 @@ final class ServerUsersViewModel: ViewModel, Eventful, Stateful, Identifiable { case let .deleteUsers(ids): userTask?.cancel() - backgroundStates.append(.deletingUsers) + backgroundStates.insert(.deletingUsers) userTask = Task { do { @@ -167,7 +167,7 @@ final class ServerUsersViewModel: ViewModel, Eventful, Stateful, Identifiable { case let .appendUser(user): userTask?.cancel() - backgroundStates.append(.appendingUsers) + backgroundStates.insert(.appendingUsers) userTask = Task { do { diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 8b2f1db0..59f3cd51 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -48,7 +48,7 @@ final class ConnectToServerViewModel: ViewModel, Eventful, Stateful { } @Published - var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] // no longer-found servers are not cleared, but not an issue @Published diff --git a/Shared/ViewModels/FilterViewModel.swift b/Shared/ViewModels/FilterViewModel.swift index 94211b6f..3ffc4d78 100644 --- a/Shared/ViewModels/FilterViewModel.swift +++ b/Shared/ViewModels/FilterViewModel.swift @@ -46,7 +46,7 @@ final class FilterViewModel: ViewModel, Stateful { /// ViewModel Background State(s) @Published - var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] /// ViewModel State @Published @@ -89,13 +89,13 @@ final class FilterViewModel: ViewModel, Stateful { queryFiltersTask = Task { do { await MainActor.run { - _ = self.backgroundStates.append(.gettingQueryFilters) + _ = self.backgroundStates.insert(.gettingQueryFilters) } try await setQueryFilters() } catch { await MainActor.run { - _ = self.backgroundStates.append(.failedToGetQueryFilters) + _ = self.backgroundStates.insert(.failedToGetQueryFilters) } } diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 008c0b75..b7bd97f2 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -45,9 +45,7 @@ final class HomeViewModel: ViewModel, Stateful { var resumeItems: OrderedSet = [] @Published - var backgroundStates: OrderedSet = [] - @Published - var lastAction: Action? = nil + var backgroundStates: Set = [] @Published var state: State = .initial @@ -83,7 +81,7 @@ final class HomeViewModel: ViewModel, Stateful { case .backgroundRefresh: backgroundRefreshTask?.cancel() - backgroundStates.append(.refresh) + backgroundStates.insert(.refresh) backgroundRefreshTask = Task { [weak self] in do { diff --git a/Shared/ViewModels/ItemAdministration/DeleteItemViewModel.swift b/Shared/ViewModels/ItemAdministration/DeleteItemViewModel.swift index 1ffa5d44..7451f432 100644 --- a/Shared/ViewModels/ItemAdministration/DeleteItemViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/DeleteItemViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import JellyfinAPI -class DeleteItemViewModel: ViewModel, Stateful, Eventful { +final class DeleteItemViewModel: ViewModel, Stateful, Eventful { // MARK: - Events @@ -33,7 +33,7 @@ class DeleteItemViewModel: ViewModel, Stateful, Eventful { } @Published - final var state: State = .initial + var state: State = .initial // MARK: - Published Item diff --git a/Shared/ViewModels/ItemAdministration/IdentifyItemViewModel.swift b/Shared/ViewModels/ItemAdministration/IdentifyItemViewModel.swift index 9be23a4b..9308daf8 100644 --- a/Shared/ViewModels/ItemAdministration/IdentifyItemViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/IdentifyItemViewModel.swift @@ -12,7 +12,7 @@ import Get import JellyfinAPI import OrderedCollections -class IdentifyItemViewModel: ViewModel, Stateful, Eventful { +final class IdentifyItemViewModel: ViewModel, Stateful, Eventful { // MARK: - Events diff --git a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/GenreEditorViewModel.swift b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/GenreEditorViewModel.swift index c4848ab3..629444c3 100644 --- a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/GenreEditorViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/GenreEditorViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import JellyfinAPI -class GenreEditorViewModel: ItemEditorViewModel { +final class GenreEditorViewModel: ItemEditorViewModel { // MARK: - Populate the Trie diff --git a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/ItemEditorViewModel.swift b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/ItemEditorViewModel.swift index 64c47eab..2396f848 100644 --- a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/ItemEditorViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/ItemEditorViewModel.swift @@ -50,7 +50,7 @@ class ItemEditorViewModel: ViewModel, Stateful, Eventful { } @Published - var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published var item: BaseItemDto @Published @@ -60,7 +60,7 @@ class ItemEditorViewModel: ViewModel, Stateful, Eventful { @Published var state: State = .initial - final var trie = Trie() + var trie = Trie() private var loadTask: AnyCancellable? private var updateTask: AnyCancellable? @@ -69,7 +69,7 @@ class ItemEditorViewModel: ViewModel, Stateful, Eventful { private let eventSubject = PassthroughSubject() - var events: AnyPublisher { + final var events: AnyPublisher { eventSubject.receive(on: RunLoop.main).eraseToAnyPublisher() } @@ -112,7 +112,7 @@ class ItemEditorViewModel: ViewModel, Stateful, Eventful { await MainActor.run { self.matches = [] self.state = .initial - _ = self.backgroundStates.append(.loading) + _ = self.backgroundStates.insert(.loading) } let allElements = try await self.fetchElements() @@ -178,7 +178,7 @@ class ItemEditorViewModel: ViewModel, Stateful, Eventful { do { await MainActor.run { - _ = self.backgroundStates.append(.searching) + _ = self.backgroundStates.insert(.searching) } let results = try await self.searchElements(searchTerm) @@ -247,7 +247,7 @@ class ItemEditorViewModel: ViewModel, Stateful, Eventful { guard let itemId = item.id else { return } await MainActor.run { - _ = self.backgroundStates.append(.refreshing) + _ = self.backgroundStates.insert(.refreshing) } let request = Paths.getItem(userID: userSession.user.id, itemID: itemId) diff --git a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/PeopleEditorViewModel.swift b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/PeopleEditorViewModel.swift index fd8a1ead..33c2e16e 100644 --- a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/PeopleEditorViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/PeopleEditorViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import JellyfinAPI -class PeopleEditorViewModel: ItemEditorViewModel { +final class PeopleEditorViewModel: ItemEditorViewModel { // MARK: - Populate the Trie diff --git a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/StudioEditorViewModel.swift b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/StudioEditorViewModel.swift index 129696a5..12fccf4e 100644 --- a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/StudioEditorViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/StudioEditorViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import JellyfinAPI -class StudioEditorViewModel: ItemEditorViewModel { +final class StudioEditorViewModel: ItemEditorViewModel { // MARK: - Populate the Trie diff --git a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/TagEditorViewModel.swift b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/TagEditorViewModel.swift index 8403963d..7fdd73c5 100644 --- a/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/TagEditorViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/ItemEditorViewModel/TagEditorViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import JellyfinAPI -class TagEditorViewModel: ItemEditorViewModel { +final class TagEditorViewModel: ItemEditorViewModel { // MARK: - Populate the Trie diff --git a/Shared/ViewModels/ItemAdministration/ItemImagesViewModel.swift b/Shared/ViewModels/ItemAdministration/ItemImagesViewModel.swift index a133b30f..329d3ec7 100644 --- a/Shared/ViewModels/ItemAdministration/ItemImagesViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/ItemImagesViewModel.swift @@ -12,7 +12,7 @@ import JellyfinAPI import OrderedCollections import SwiftUI -class ItemImagesViewModel: ViewModel, Stateful, Eventful { +final class ItemImagesViewModel: ViewModel, Stateful, Eventful { enum Event: Equatable { case updated @@ -50,7 +50,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { @Published var state: State = .initial @Published - var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] private var task: AnyCancellable? private let eventSubject = PassthroughSubject() @@ -85,7 +85,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { guard let self else { return } do { await MainActor.run { - _ = self.backgroundStates.append(.updating) + _ = self.backgroundStates.insert(.updating) self.images.removeAll() } @@ -114,7 +114,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { guard let self = self else { return } do { await MainActor.run { - _ = self.backgroundStates.append(.updating) + _ = self.backgroundStates.insert(.updating) } try await self.setImage(remoteImageInfo) @@ -144,7 +144,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { guard let self = self else { return } do { await MainActor.run { - _ = self.backgroundStates.append(.updating) + _ = self.backgroundStates.insert(.updating) } try await self.uploadPhoto(image, type: type) @@ -174,7 +174,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { guard let self = self else { return } do { await MainActor.run { - _ = self.backgroundStates.append(.updating) + _ = self.backgroundStates.insert(.updating) } try await self.uploadFile(url, type: type) @@ -204,7 +204,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { guard let self = self else { return } do { await MainActor.run { - _ = self.backgroundStates.append(.updating) + _ = self.backgroundStates.insert(.updating) } try await deleteImage(imageInfo) @@ -384,7 +384,7 @@ class ItemImagesViewModel: ViewModel, Stateful, Eventful { guard let itemID = item.id else { return } await MainActor.run { - _ = backgroundStates.append(.updating) + _ = backgroundStates.insert(.updating) } let request = Paths.getItem( diff --git a/Shared/ViewModels/ItemAdministration/RefreshMetadataViewModel.swift b/Shared/ViewModels/ItemAdministration/RefreshMetadataViewModel.swift index 04844c1a..71407782 100644 --- a/Shared/ViewModels/ItemAdministration/RefreshMetadataViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/RefreshMetadataViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import JellyfinAPI -class RefreshMetadataViewModel: ViewModel, Stateful, Eventful { +final class RefreshMetadataViewModel: ViewModel, Stateful, Eventful { // MARK: - Events @@ -37,7 +37,7 @@ class RefreshMetadataViewModel: ViewModel, Stateful, Eventful { } @Published - final var state: State = .initial + var state: State = .initial // MARK: - Published Items diff --git a/Shared/ViewModels/ItemAdministration/RemoteImageInfoViewModel.swift b/Shared/ViewModels/ItemAdministration/RemoteImageInfoViewModel.swift index 22e380a5..eb9a4265 100644 --- a/Shared/ViewModels/ItemAdministration/RemoteImageInfoViewModel.swift +++ b/Shared/ViewModels/ItemAdministration/RemoteImageInfoViewModel.swift @@ -9,7 +9,7 @@ import Foundation import JellyfinAPI -class RemoteImageInfoViewModel: PagingLibraryViewModel { +final class RemoteImageInfoViewModel: PagingLibraryViewModel { // Image providers come from the paging call @Published diff --git a/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift b/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift index c3384427..544dcf06 100644 --- a/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/EpisodeItemViewModel.swift @@ -18,27 +18,24 @@ final class EpisodeItemViewModel: ItemViewModel { private var seriesItemTask: AnyCancellable? - override init(item: BaseItemDto) { - super.init(item: item) + override func respond(to action: ItemViewModel.Action) -> ItemViewModel.State { - $lastAction - .sink { [weak self] action in - guard let self else { return } + switch action { + case .refresh: + seriesItemTask?.cancel() - if action == .refresh { - seriesItemTask?.cancel() + seriesItemTask = Task { + let seriesItem = try await self.getSeriesItem() - seriesItemTask = Task { - let seriesItem = try await self.getSeriesItem() - - await MainActor.run { - self.seriesItem = seriesItem - } - } - .asAnyCancellable() + await MainActor.run { + self.seriesItem = seriesItem } } - .store(in: &cancellables) + .asAnyCancellable() + default: break + } + + return super.respond(to: action) } private func getSeriesItem() async throws -> BaseItemDto { diff --git a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift index ada0c58f..c29abb51 100644 --- a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift @@ -74,11 +74,9 @@ class ItemViewModel: ViewModel, Stateful { private(set) var specialFeatures: [BaseItemDto] = [] @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published - final var lastAction: Action? = nil - @Published - final var state: State = .initial + var state: State = .initial // tasks @@ -121,7 +119,7 @@ class ItemViewModel: ViewModel, Stateful { switch action { case .backgroundRefresh: - backgroundStates.append(.refresh) + backgroundStates.insert(.refresh) Task { [weak self] in guard let self else { return } @@ -212,7 +210,7 @@ class ItemViewModel: ViewModel, Stateful { return .refreshing case let .replace(newItem): - backgroundStates.append(.refresh) + backgroundStates.insert(.refresh) Task { [weak self] in guard let self else { return } diff --git a/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift index a41f5f36..f0686e51 100644 --- a/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift @@ -92,27 +92,25 @@ class PagingLibraryViewModel: ViewModel, Eventful, Stateful { } @Published - final var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] /// - Keys: the `hashValue` of the `Element.ID` @Published - final var elements: IdentifiedArray + var elements: IdentifiedArray @Published - final var state: State = .initial - @Published - final var lastAction: Action? = nil + var state: State = .initial final let filterViewModel: FilterViewModel? final let parent: (any LibraryParent)? - var events: AnyPublisher { + final var events: AnyPublisher { eventSubject .receive(on: RunLoop.main) .eraseToAnyPublisher() } let pageSize: Int - private(set) final var currentPage = 0 - private(set) final var hasNextPage = true + private(set) var currentPage = 0 + private(set) var hasNextPage = true private let eventSubject: PassthroughSubject = .init() private let isStatic: Bool @@ -282,7 +280,7 @@ class PagingLibraryViewModel: ViewModel, Eventful, Stateful { guard hasNextPage else { return state } - backgroundStates.append(.gettingNextPage) + backgroundStates.insert(.gettingNextPage) pagingTask = Task { [weak self] in do { diff --git a/Shared/ViewModels/LiveVideoPlayerManager.swift b/Shared/ViewModels/LiveVideoPlayerManager.swift index 5a0fce2d..74ec3dc0 100644 --- a/Shared/ViewModels/LiveVideoPlayerManager.swift +++ b/Shared/ViewModels/LiveVideoPlayerManager.swift @@ -13,7 +13,7 @@ import JellyfinAPI // with the channel retrieving method below and is mainly just for reference // for how I should probably handle getting the channels of programs elsewhere. -class LiveVideoPlayerManager: VideoPlayerManager { +final class LiveVideoPlayerManager: VideoPlayerManager { @Published var program: ChannelProgram? diff --git a/Shared/ViewModels/MediaViewModel/MediaViewModel.swift b/Shared/ViewModels/MediaViewModel/MediaViewModel.swift index 903d0469..e9780ad4 100644 --- a/Shared/ViewModels/MediaViewModel/MediaViewModel.swift +++ b/Shared/ViewModels/MediaViewModel/MediaViewModel.swift @@ -36,9 +36,10 @@ final class MediaViewModel: ViewModel, Stateful { var mediaItems: OrderedSet = [] @Published - final var state: State = .initial + var backgroundStates: Set = [] + @Published - final var lastAction: Action? = nil + var state: State = .initial func respond(to action: Action) -> State { switch action { diff --git a/Shared/ViewModels/ParentalRatingsViewModel.swift b/Shared/ViewModels/ParentalRatingsViewModel.swift index 0c4e9b01..7b5bedc4 100644 --- a/Shared/ViewModels/ParentalRatingsViewModel.swift +++ b/Shared/ViewModels/ParentalRatingsViewModel.swift @@ -31,7 +31,7 @@ final class ParentalRatingsViewModel: ViewModel, Stateful { private(set) var parentalRatings: [ParentalRating] = [] @Published - final var state: State = .initial + var state: State = .initial private var currentRefreshTask: AnyCancellable? diff --git a/Shared/ViewModels/ProgramsViewModel.swift b/Shared/ViewModels/ProgramsViewModel.swift index 64e661e0..708c9e3b 100644 --- a/Shared/ViewModels/ProgramsViewModel.swift +++ b/Shared/ViewModels/ProgramsViewModel.swift @@ -51,9 +51,7 @@ final class ProgramsViewModel: ViewModel, Stateful { private(set) var sports: [BaseItemDto] = [] @Published - final var lastAction: Action? = nil - @Published - final var state: State = .initial + var state: State = .initial private var currentRefreshTask: AnyCancellable? diff --git a/Shared/ViewModels/QuickConnectAuthorizeViewModel.swift b/Shared/ViewModels/QuickConnectAuthorizeViewModel.swift index 9228842f..c3a6f42e 100644 --- a/Shared/ViewModels/QuickConnectAuthorizeViewModel.swift +++ b/Shared/ViewModels/QuickConnectAuthorizeViewModel.swift @@ -33,8 +33,6 @@ final class QuickConnectAuthorizeViewModel: ViewModel, Eventful, Stateful { case initial } - @Published - var lastAction: Action? = nil @Published var state: State = .initial diff --git a/Shared/ViewModels/SearchViewModel.swift b/Shared/ViewModels/SearchViewModel.swift index 6114def1..d3efa6e9 100644 --- a/Shared/ViewModels/SearchViewModel.swift +++ b/Shared/ViewModels/SearchViewModel.swift @@ -49,9 +49,7 @@ final class SearchViewModel: ViewModel, Stateful { private(set) var suggestions: [BaseItemDto] = [] @Published - final var state: State = .initial - @Published - final var lastAction: Action? = nil + var state: State = .initial private var searchTask: AnyCancellable? private var searchQuery: CurrentValueSubject = .init("") diff --git a/Shared/ViewModels/SelectUserViewModel.swift b/Shared/ViewModels/SelectUserViewModel.swift index 6fc80332..39429f32 100644 --- a/Shared/ViewModels/SelectUserViewModel.swift +++ b/Shared/ViewModels/SelectUserViewModel.swift @@ -14,7 +14,7 @@ import JellyfinAPI import KeychainSwift import OrderedCollections -class SelectUserViewModel: ViewModel, Eventful, Stateful { +final class SelectUserViewModel: ViewModel, Eventful, Stateful { // MARK: Event diff --git a/Shared/ViewModels/ServerCheckViewModel.swift b/Shared/ViewModels/ServerCheckViewModel.swift index 047e5e0c..df613669 100644 --- a/Shared/ViewModels/ServerCheckViewModel.swift +++ b/Shared/ViewModels/ServerCheckViewModel.swift @@ -11,7 +11,7 @@ import Factory import Foundation import JellyfinAPI -class ServerCheckViewModel: ViewModel, Stateful { +final class ServerCheckViewModel: ViewModel, Stateful { enum Action: Equatable { case checkServer diff --git a/Shared/ViewModels/ServerConnectionViewModel.swift b/Shared/ViewModels/ServerConnectionViewModel.swift index 53977928..27c119dd 100644 --- a/Shared/ViewModels/ServerConnectionViewModel.swift +++ b/Shared/ViewModels/ServerConnectionViewModel.swift @@ -10,7 +10,7 @@ import CoreStore import Foundation import JellyfinAPI -class ServerConnectionViewModel: ViewModel { +final class ServerConnectionViewModel: ViewModel { @Published var server: ServerState diff --git a/Shared/ViewModels/ServerLogsViewModel.swift b/Shared/ViewModels/ServerLogsViewModel.swift index e63f837f..387c0cd1 100644 --- a/Shared/ViewModels/ServerLogsViewModel.swift +++ b/Shared/ViewModels/ServerLogsViewModel.swift @@ -26,9 +26,7 @@ final class ServerLogsViewModel: ViewModel, Stateful { @Published private(set) var logs: OrderedSet = [] @Published - final var state: State = .initial - @Published - final var lastAction: Action? + var state: State = .initial func respond(to action: Action) -> State { switch action { diff --git a/Shared/ViewModels/UserLocalSecurityViewModel.swift b/Shared/ViewModels/UserLocalSecurityViewModel.swift index 42202757..91248012 100644 --- a/Shared/ViewModels/UserLocalSecurityViewModel.swift +++ b/Shared/ViewModels/UserLocalSecurityViewModel.swift @@ -10,7 +10,7 @@ import Combine import Foundation import KeychainSwift -class UserLocalSecurityViewModel: ViewModel, Eventful { +final class UserLocalSecurityViewModel: ViewModel, Eventful { enum Event: Hashable { case error(JellyfinAPIError) diff --git a/Shared/ViewModels/UserProfileImageViewModel.swift b/Shared/ViewModels/UserProfileImageViewModel.swift index bcdff63e..649b0acf 100644 --- a/Shared/ViewModels/UserProfileImageViewModel.swift +++ b/Shared/ViewModels/UserProfileImageViewModel.swift @@ -12,7 +12,7 @@ import JellyfinAPI import Nuke import UIKit -class UserProfileImageViewModel: ViewModel, Eventful, Stateful { +final class UserProfileImageViewModel: ViewModel, Eventful, Stateful { // MARK: - Action diff --git a/Shared/ViewModels/UserSignInViewModel.swift b/Shared/ViewModels/UserSignInViewModel.swift index 61903be2..a8478678 100644 --- a/Shared/ViewModels/UserSignInViewModel.swift +++ b/Shared/ViewModels/UserSignInViewModel.swift @@ -58,7 +58,7 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful { } @Published - var backgroundStates: OrderedSet = [] + var backgroundStates: Set = [] @Published var isQuickConnectEnabled = false @Published @@ -106,7 +106,7 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful { do { await MainActor.run { - let _ = self?.backgroundStates.append(.gettingPublicData) + let _ = self?.backgroundStates.insert(.gettingPublicData) } let isQuickConnectEnabled = try await self?.retrieveQuickConnectEnabled() diff --git a/Shared/ViewModels/VideoPlayerManager/DownloadVideoPlayerManager.swift b/Shared/ViewModels/VideoPlayerManager/DownloadVideoPlayerManager.swift index d1fdd758..3662a100 100644 --- a/Shared/ViewModels/VideoPlayerManager/DownloadVideoPlayerManager.swift +++ b/Shared/ViewModels/VideoPlayerManager/DownloadVideoPlayerManager.swift @@ -9,7 +9,7 @@ import Foundation import JellyfinAPI -class DownloadVideoPlayerManager: VideoPlayerManager { +final class DownloadVideoPlayerManager: VideoPlayerManager { init(downloadTask: DownloadTask) { super.init() diff --git a/Shared/ViewModels/VideoPlayerManager/OnlineVideoPlayerManager.swift b/Shared/ViewModels/VideoPlayerManager/OnlineVideoPlayerManager.swift index 89c3f22f..813e74cc 100644 --- a/Shared/ViewModels/VideoPlayerManager/OnlineVideoPlayerManager.swift +++ b/Shared/ViewModels/VideoPlayerManager/OnlineVideoPlayerManager.swift @@ -9,7 +9,7 @@ import Foundation import JellyfinAPI -class OnlineVideoPlayerManager: VideoPlayerManager { +final class OnlineVideoPlayerManager: VideoPlayerManager { init(item: BaseItemDto, mediaSource: MediaSourceInfo) { super.init() diff --git a/Shared/ViewModels/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel.swift index de45be5a..1d109984 100644 --- a/Shared/ViewModels/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel.swift @@ -14,7 +14,7 @@ import JellyfinAPI import UIKit import VLCUI -class VideoPlayerViewModel: ViewModel { +final class VideoPlayerViewModel: ViewModel { let playbackURL: URL let item: BaseItemDto