stremio/apps/ios/VMCTV/Data/AppModel.swift
Jos Vooges | STH ac951617cb Add Live Events across API/admin/Android TV, mobile ClearKey/DASH fixes, and tvOS app.
Wire FAAB schedule-events into master-api and admin IPTV settings, add Android TV Events UI, fix mobile ClearKey/stream playback (DASH + bump 1.0.23), and scaffold the Apple TV (VMCTV) client with docs/scripts.
2026-09-18 23:34:42 +02:00

163 lines
5 KiB
Swift

import Foundation
import UIKit
import Combine
@MainActor
final class AppModel: ObservableObject {
enum Phase: Equatable {
case boot
case server
case auth
case password
case link
case profiles
case main
}
enum MainTab: String, CaseIterable, Identifiable {
case home, watchlist, movies, series, tv, search, settings
var id: String { rawValue }
var title: String {
switch self {
case .home: return "Home"
case .watchlist: return "Watchlist"
case .movies: return "Films"
case .series: return "Series"
case .tv: return "TV"
case .search: return "Zoeken"
case .settings: return "Instellingen"
}
}
var icon: String {
switch self {
case .home: return "house.fill"
case .watchlist: return "bookmark.fill"
case .movies: return "film"
case .series: return "rectangle.stack.fill"
case .tv: return "tv.fill"
case .search: return "magnifyingglass"
case .settings: return "gearshape.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?
@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<AnyCancellable>()
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 = .server
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,
showAvatar: profile.showsAvatar,
avatarUrl: profile.avatarUrl ?? ""
)
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 = .server
}
}
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,
showAvatar: profile.showsAvatar,
avatarUrl: profile.avatarUrl ?? ""
)
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 = .server
}
static var deviceName: String {
UIDevice.current.name.isEmpty ? "Apple TV" : UIDevice.current.name
}
}