// // 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 CollectionVGrid import Defaults import Factory import JellyfinAPI import SwiftUI struct XtreamView: View { @Router private var router @StateObject private var viewModel = XtreamViewModel() private var layout: CollectionVGridLayout { if UIDevice.isTV { .columns(4, insets: .init(50), itemSpacing: 50, lineSpacing: 50) } else if UIDevice.isPad { .minWidth(200) } else { .columns(2) } } @ViewBuilder private var content: some View { CollectionVGrid( uniqueElements: viewModel.xtreamChannels, layout: layout ) { channel in MediaItem(viewModel: viewModel, type: .collectionFolder(channel)) { namespace in let channelViewModel = ItemLibraryViewModel( parent: channel, filters: .default ) router.route(to: .library(viewModel: channelViewModel), in: namespace) } } } @ViewBuilder private func errorView(with error: some Error) -> some View { ErrorView(error: error) .onRetry { viewModel.send(.refresh) } } var body: some View { ZStack { Color.clear switch viewModel.state { case .content: content case let .error(error): errorView(with: error) case .initial: content case .refreshing: ProgressView() } } .animation(.linear(duration: 0.1), value: viewModel.state) .ignoresSafeArea() .navigationTitle("Xtream") .onFirstAppear { viewModel.send(.refresh) } .if(UIDevice.isTV) { view in view.toolbar(.hidden, for: .navigationBar) } } }