Stateful - Set cleanup and `final` classes (#1465)

* cleanup

* Update Stateful.swift
This commit is contained in:
Ethan Pippin 2025-03-30 01:05:14 -04:00 committed by GitHub
parent 16efcbefdc
commit 4a63b52b17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 110 additions and 139 deletions

View File

@ -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<BackgroundState> { get set }
var backgroundStates: Set<BackgroundState> { 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<Never> {
var backgroundStates: Set<Never> {
get {
assertionFailure("Attempted to access `backgroundStates` when there are none")
return []

View File

@ -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

View File

@ -36,11 +36,11 @@ final class ActiveSessionsViewModel: ViewModel, Stateful {
}
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@Published
final var sessions: OrderedDictionary<String, BindingBox<SessionInfo?>> = [:]
var sessions: OrderedDictionary<String, BindingBox<SessionInfo?>> = [:]
@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 {

View File

@ -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<Event, Never> = .init()

View File

@ -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<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@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 {

View File

@ -47,11 +47,11 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful {
// MARK: Published Values
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@Published
final var devices: [DeviceInfo] = []
var devices: [DeviceInfo] = []
@Published
final var state: State = .initial
var state: State = .initial
var events: AnyPublisher<Event, Never> {
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 {

View File

@ -48,9 +48,9 @@ final class ServerTaskObserver: ViewModel, Stateful, Eventful, Identifiable {
// MARK: Published Values
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@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 {

View File

@ -41,11 +41,11 @@ final class ServerTasksViewModel: ViewModel, Stateful {
}
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@Published
final var state: State = .initial
var state: State = .initial
@Published
final var tasks: OrderedDictionary<String, [ServerTaskObserver]> = [:]
var tasks: OrderedDictionary<String, [ServerTaskObserver]> = [:]
private var getTasksCancellable: AnyCancellable?

View File

@ -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<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@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)

View File

@ -50,13 +50,13 @@ final class ServerUsersViewModel: ViewModel, Eventful, Stateful, Identifiable {
// MARK: Published Values
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@Published
final var users: IdentifiedArrayOf<UserDto> = []
var users: IdentifiedArrayOf<UserDto> = []
@Published
final var state: State = .initial
var state: State = .initial
var events: AnyPublisher<Event, Never> {
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 {

View File

@ -48,7 +48,7 @@ final class ConnectToServerViewModel: ViewModel, Eventful, Stateful {
}
@Published
var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
// no longer-found servers are not cleared, but not an issue
@Published

View File

@ -46,7 +46,7 @@ final class FilterViewModel: ViewModel, Stateful {
/// ViewModel Background State(s)
@Published
var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
/// 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)
}
}

View File

@ -45,9 +45,7 @@ final class HomeViewModel: ViewModel, Stateful {
var resumeItems: OrderedSet<BaseItemDto> = []
@Published
var backgroundStates: OrderedSet<BackgroundState> = []
@Published
var lastAction: Action? = nil
var backgroundStates: Set<BackgroundState> = []
@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 {

View File

@ -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

View File

@ -12,7 +12,7 @@ import Get
import JellyfinAPI
import OrderedCollections
class IdentifyItemViewModel: ViewModel, Stateful, Eventful {
final class IdentifyItemViewModel: ViewModel, Stateful, Eventful {
// MARK: - Events

View File

@ -10,7 +10,7 @@ import Combine
import Foundation
import JellyfinAPI
class GenreEditorViewModel: ItemEditorViewModel<String> {
final class GenreEditorViewModel: ItemEditorViewModel<String> {
// MARK: - Populate the Trie

View File

@ -50,7 +50,7 @@ class ItemEditorViewModel<Element: Equatable>: ViewModel, Stateful, Eventful {
}
@Published
var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@Published
var item: BaseItemDto
@Published
@ -60,7 +60,7 @@ class ItemEditorViewModel<Element: Equatable>: ViewModel, Stateful, Eventful {
@Published
var state: State = .initial
final var trie = Trie<String, Element>()
var trie = Trie<String, Element>()
private var loadTask: AnyCancellable?
private var updateTask: AnyCancellable?
@ -69,7 +69,7 @@ class ItemEditorViewModel<Element: Equatable>: ViewModel, Stateful, Eventful {
private let eventSubject = PassthroughSubject<Event, Never>()
var events: AnyPublisher<Event, Never> {
final var events: AnyPublisher<Event, Never> {
eventSubject.receive(on: RunLoop.main).eraseToAnyPublisher()
}
@ -112,7 +112,7 @@ class ItemEditorViewModel<Element: Equatable>: 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<Element: Equatable>: 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<Element: Equatable>: 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)

View File

@ -10,7 +10,7 @@ import Combine
import Foundation
import JellyfinAPI
class PeopleEditorViewModel: ItemEditorViewModel<BaseItemPerson> {
final class PeopleEditorViewModel: ItemEditorViewModel<BaseItemPerson> {
// MARK: - Populate the Trie

View File

@ -10,7 +10,7 @@ import Combine
import Foundation
import JellyfinAPI
class StudioEditorViewModel: ItemEditorViewModel<NameGuidPair> {
final class StudioEditorViewModel: ItemEditorViewModel<NameGuidPair> {
// MARK: - Populate the Trie

View File

@ -10,7 +10,7 @@ import Combine
import Foundation
import JellyfinAPI
class TagEditorViewModel: ItemEditorViewModel<String> {
final class TagEditorViewModel: ItemEditorViewModel<String> {
// MARK: - Populate the Trie

View File

@ -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<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
private var task: AnyCancellable?
private let eventSubject = PassthroughSubject<Event, Never>()
@ -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(

View File

@ -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

View File

@ -9,7 +9,7 @@
import Foundation
import JellyfinAPI
class RemoteImageInfoViewModel: PagingLibraryViewModel<RemoteImageInfo> {
final class RemoteImageInfoViewModel: PagingLibraryViewModel<RemoteImageInfo> {
// Image providers come from the paging call
@Published

View File

@ -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 {

View File

@ -74,11 +74,9 @@ class ItemViewModel: ViewModel, Stateful {
private(set) var specialFeatures: [BaseItemDto] = []
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@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 }

View File

@ -92,27 +92,25 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
}
@Published
final var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
/// - Keys: the `hashValue` of the `Element.ID`
@Published
final var elements: IdentifiedArray<Int, Element>
var elements: IdentifiedArray<Int, Element>
@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<Event, Never> {
final var events: AnyPublisher<Event, Never> {
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<Event, Never> = .init()
private let isStatic: Bool
@ -282,7 +280,7 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
guard hasNextPage else { return state }
backgroundStates.append(.gettingNextPage)
backgroundStates.insert(.gettingNextPage)
pagingTask = Task { [weak self] in
do {

View File

@ -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?

View File

@ -36,9 +36,10 @@ final class MediaViewModel: ViewModel, Stateful {
var mediaItems: OrderedSet<MediaType> = []
@Published
final var state: State = .initial
var backgroundStates: Set<BackgroundState> = []
@Published
final var lastAction: Action? = nil
var state: State = .initial
func respond(to action: Action) -> State {
switch action {

View File

@ -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?

View File

@ -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?

View File

@ -33,8 +33,6 @@ final class QuickConnectAuthorizeViewModel: ViewModel, Eventful, Stateful {
case initial
}
@Published
var lastAction: Action? = nil
@Published
var state: State = .initial

View File

@ -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<String, Never> = .init("")

View File

@ -14,7 +14,7 @@ import JellyfinAPI
import KeychainSwift
import OrderedCollections
class SelectUserViewModel: ViewModel, Eventful, Stateful {
final class SelectUserViewModel: ViewModel, Eventful, Stateful {
// MARK: Event

View File

@ -11,7 +11,7 @@ import Factory
import Foundation
import JellyfinAPI
class ServerCheckViewModel: ViewModel, Stateful {
final class ServerCheckViewModel: ViewModel, Stateful {
enum Action: Equatable {
case checkServer

View File

@ -10,7 +10,7 @@ import CoreStore
import Foundation
import JellyfinAPI
class ServerConnectionViewModel: ViewModel {
final class ServerConnectionViewModel: ViewModel {
@Published
var server: ServerState

View File

@ -26,9 +26,7 @@ final class ServerLogsViewModel: ViewModel, Stateful {
@Published
private(set) var logs: OrderedSet<LogFile> = []
@Published
final var state: State = .initial
@Published
final var lastAction: Action?
var state: State = .initial
func respond(to action: Action) -> State {
switch action {

View File

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

View File

@ -12,7 +12,7 @@ import JellyfinAPI
import Nuke
import UIKit
class UserProfileImageViewModel: ViewModel, Eventful, Stateful {
final class UserProfileImageViewModel: ViewModel, Eventful, Stateful {
// MARK: - Action

View File

@ -58,7 +58,7 @@ final class UserSignInViewModel: ViewModel, Eventful, Stateful {
}
@Published
var backgroundStates: OrderedSet<BackgroundState> = []
var backgroundStates: Set<BackgroundState> = []
@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()

View File

@ -9,7 +9,7 @@
import Foundation
import JellyfinAPI
class DownloadVideoPlayerManager: VideoPlayerManager {
final class DownloadVideoPlayerManager: VideoPlayerManager {
init(downloadTask: DownloadTask) {
super.init()

View File

@ -9,7 +9,7 @@
import Foundation
import JellyfinAPI
class OnlineVideoPlayerManager: VideoPlayerManager {
final class OnlineVideoPlayerManager: VideoPlayerManager {
init(item: BaseItemDto, mediaSource: MediaSourceInfo) {
super.init()

View File

@ -14,7 +14,7 @@ import JellyfinAPI
import UIKit
import VLCUI
class VideoPlayerViewModel: ViewModel {
final class VideoPlayerViewModel: ViewModel {
let playbackURL: URL
let item: BaseItemDto