// // HomeView.swift // // Copyright 2026 Brendan Szymanski // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // SPDX-License-Identifier: GPL-3.0-or-later // import Adwaita import LuminateCore import LuminateDI import LuminateUI public struct HomeView: View { nonisolated public init(navigation: Binding>) { _navigation = navigation } @Injected(\.client) var client @Injected(\.userId) var userId @Binding var navigation: NavigationStack @State private var resumeItems: [BaseItemDto] = [] @State private var nextUpItems: [BaseItemDto] = [] @State private var latestItems: [BaseItemDto] = [] @State private var libraries: [BaseItemDto] = [] @State private var isLoading = true @State private var isLoadingData = false public var view: Body { ScrollView { Clamp() .maximumSize(1550) .tighteningThreshold(550) .child { VStack { if isLoading { Spinner() .halign(.center) .valign(.center) .frame(minWidth: 64) .frame(maxWidth: 64) } else { ItemGrid( items: libraries, type: .library, navigation: $navigation ) .padding(32, .bottom) if !resumeItems.isEmpty { let title = "Continue Watching" MediaRow( title: title, items: resumeItems, navigation: $navigation, onSeeAll: { navigation.push(.items(title: title, items: resumeItems)) } ) .padding(32, .bottom) } if !nextUpItems.isEmpty { let title = "Next Up" MediaRow( title: title, items: nextUpItems, navigation: $navigation, onSeeAll: { navigation.push(.items(title: title, items: nextUpItems)) } ) .padding(32, .bottom) } if !latestItems.isEmpty { let title = "Recently Added" MediaRow( title: title, items: latestItems, navigation: $navigation, onSeeAll: { navigation.push(.items(title: title, items: latestItems)) } ) .padding(32, .bottom) } } } .padding(8, .horizontal) .padding(32, .vertical) } } .hscrollbarPolicy(.never) .propagateNaturalHeight() .onAppear { loadHomeData() } } private func loadHomeData() { Task { async let resume = client.getItems( userId: userId, fields: [.primaryImageAspectRatio], filters: [.isResumable], sortBy: [.datePlayed], sortOrder: [.descending], limit: 20, recursive: true ) async let nextUp = client.getNextUp( userId: userId, limit: 20, fields: [.primaryImageAspectRatio]) async let latest = client.getLatestMedia( userId: userId, fields: [.primaryImageAspectRatio], limit: 20) async let views = client.getUserViews(userId: userId) do { let (resume, nextUp, latest, views) = try await (resume, nextUp, latest, views) resumeItems = resume.items ?? [] nextUpItems = nextUp.items ?? [] latestItems = latest libraries = views.items ?? [] isLoading = false } catch { isLoading = false } isLoadingData = false } } }