// // 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) 2025 Jellyfin & Jellyfin Contributors // import Combine import CoreStore import Foundation import JellyfinAPI import KeychainSwift import OrderedCollections final class SelectUserViewModel: ViewModel, Eventful, Stateful { // MARK: Event enum Event { case error(JellyfinAPIError) case signedIn(UserState) } // MARK: Action enum Action: Equatable { case deleteUsers(Set) case getServers case signIn(UserState, pin: String) } // MARK: State enum State: Hashable { case content } @Published private(set) var servers: OrderedDictionary = [:] @Published var state: State = .content var events: AnyPublisher { eventSubject .eraseToAnyPublisher() } private var eventSubject: PassthroughSubject = .init() @MainActor func respond(to action: Action) -> State { switch action { case let .deleteUsers(users): do { for user in users { try delete(user: user) } send(.getServers) } catch { eventSubject.send(.error(.init(error.localizedDescription))) } case .getServers: do { servers = try getServers() .zipped(map: getUsers) .reduce(into: OrderedDictionary()) { partialResult, pair in partialResult[pair.0] = pair.1 } return .content } catch { eventSubject.send(.error(.init(error.localizedDescription))) } case let .signIn(user, pin): if user.accessPolicy == .requirePin, let storedPin = keychain.get("\(user.id)-pin") { if pin != storedPin { eventSubject.send(.error(.init(L10n.incorrectPinForUser(user.username)))) return .content } } eventSubject.send(.signedIn(user)) } return .content } private func getServers() throws -> [ServerState] { try SwiftfinStore .dataStack .fetchAll(From()) .map(\.state) .sorted(using: \.name) } private func getUsers(for server: ServerState) throws -> [UserState] { guard let storedServer = try? dataStack.fetchOne(From().where(\.$id == server.id)) else { throw JellyfinAPIError("Unable to find server for users") } return storedServer.users .map(\.state) } private func delete(user: UserState) throws { try user.delete() } }