import Foundation import UIKit import Combine @MainActor final class AppModel: ObservableObject { enum Phase: Equatable { case boot case link case profiles case main } enum MainTab: String, CaseIterable, Identifiable { case home, movies, series, tv, search, account var id: String { rawValue } var title: String { switch self { case .home: return "Home" case .movies: return "Films" case .series: return "Series" case .tv: return "TV" case .search: return "Zoeken" case .account: return "Account" } } var icon: String { switch self { case .home: return "house.fill" case .movies: return "film" case .series: return "rectangle.stack.fill" case .tv: return "tv.fill" case .search: return "magnifyingglass" case .account: return "person.crop.circle.fill" } } } let store = SessionStore() let api = ApiClient() let favorites = FavoritesStore() @Published var phase: Phase = .boot @Published var tab: MainTab = .home @Published var iptvEnabled = false @Published var canManageProfiles = false @Published var bootError: String? /// > 0 terwijl een speler (VOD/live) open is — verbergt de footer. @Published private(set) var playbackDepth = 0 var isPlaybackActive: Bool { playbackDepth > 0 } func enterPlayback() { playbackDepth += 1 } func leavePlayback() { playbackDepth = max(0, playbackDepth - 1) } private var cancellables = Set() init() { favorites.objectWillChange .receive(on: RunLoop.main) .sink { [weak self] _ in self?.objectWillChange.send() } .store(in: &cancellables) Task { await bootstrap() } } func bootstrap() async { phase = .boot bootError = nil await api.configure(baseUrl: store.apiBase, token: store.refreshToken) guard store.isAuthed else { phase = .link return } do { let me = try await api.clientMe() iptvEnabled = me.iptvEnabled if let profile = me.profile { store.setProfile(id: profile.id, name: profile.name, avatarId: profile.avatarId) favorites.bind(profileId: profile.id) await favorites.refresh(using: api) if let payload = try? await api.listProfiles() { canManageProfiles = payload.canManage == true } else { canManageProfiles = profile.isOwner == true } phase = .main } else { store.setProfile(id: nil, name: nil, avatarId: nil) favorites.bind(profileId: nil) canManageProfiles = false phase = .profiles } } catch { bootError = error.localizedDescription store.setToken(nil) favorites.bind(profileId: nil) await api.configure(baseUrl: store.apiBase, token: nil) phase = .link } } func didLink(token: String) async { store.setToken(token) await api.configure(baseUrl: store.apiBase, token: token) store.setProfile(id: nil, name: nil, avatarId: nil) if let me = try? await api.clientMe() { iptvEnabled = me.iptvEnabled } phase = .profiles } func didSelectProfile(_ profile: ViewerProfile) { store.setProfile(id: profile.id, name: profile.name, avatarId: profile.avatarId) favorites.bind(profileId: profile.id) Task { await favorites.refresh(using: api) if let payload = try? await api.listProfiles() { canManageProfiles = payload.canManage == true } else { canManageProfiles = profile.isOwner == true } } tab = .home phase = .main } func switchProfile() async { try? await api.clearProfile() store.setProfile(id: nil, name: nil, avatarId: nil) favorites.bind(profileId: nil) phase = .profiles } func logout() async { store.setToken(nil) await api.configure(baseUrl: store.apiBase, token: nil) favorites.bind(profileId: nil) iptvEnabled = false phase = .link } static var deviceName: String { UIDevice.current.name.isEmpty ? "iPhone" : UIDevice.current.name } }