Add home screen with continue watching, next up, recently added, library grid

This commit is contained in:
Brendan Szymanski 2026-06-05 01:28:08 -04:00
parent 0c9938acc2
commit a2ee8d946b
6 changed files with 188 additions and 25 deletions

View file

@ -24,7 +24,7 @@ let package = Package(
),
.target(
name: "LuminateHome",
dependencies: ["LuminateCore", "LuminateLibrary", .product(name: "Adwaita", package: "adwaita-swift")]
dependencies: ["LuminateCore", .product(name: "Adwaita", package: "adwaita-swift")]
),
.target(
name: "LuminateLibrary",

View file

@ -0,0 +1,44 @@
import Adwaita
import LuminateCore
struct HomePosterCell: View {
var item: Components.Schemas.BaseItemDto
var client: JellyfinClient
@State private var imageData: Data?
var view: Body {
VStack {
if let data = imageData {
Picture(data)
.frame(minWidth: 150, maxWidth: 150, minHeight: 225, maxHeight: 225)
} else {
Box()
.frame(minWidth: 150, maxWidth: 150, minHeight: 225, maxHeight: 225)
.style("card")
}
Text(item.Name ?? "")
.style("body")
.halign(.center)
.maxWidth(150)
}
.onAppear {
loadImage()
}
}
private func loadImage() {
guard let tag = item.primaryImageTag,
let itemId = item.Id,
let url = client.imageURL(
itemId: itemId,
imageType: .Primary,
tag: tag,
maxWidth: 300
) else { return }
Task {
let service = ImageService()
imageData = try? await service.loadImage(url: url)
}
}
}

View file

@ -0,0 +1,88 @@
import Adwaita
import LuminateCore
struct HomeView: View {
var app: AdwaitaApp
var window: AdwaitaWindow
var client: JellyfinClient
var userId: String
@State private var resumeItems: [Components.Schemas.BaseItemDto] = []
@State private var nextUpItems: [Components.Schemas.BaseItemDto] = []
@State private var latestItems: [Components.Schemas.BaseItemDto] = []
@State private var libraries: [Components.Schemas.BaseItemDto] = []
@State private var isLoading = true
var view: Body {
ScrollView {
VStack {
if isLoading {
Spinner()
.padding(50)
} else {
if !resumeItems.isEmpty {
MediaRow(
title: "Continue Watching",
items: resumeItems,
client: client
)
.padding(10, .bottom)
}
if !nextUpItems.isEmpty {
MediaRow(
title: "Next Up",
items: nextUpItems,
client: client
)
.padding(10, .bottom)
}
if !latestItems.isEmpty {
MediaRow(
title: "Recently Added",
items: latestItems,
client: client,
onSeeAll: {}
)
.padding(10, .bottom)
}
LibraryGrid(
libraries: libraries,
client: client
)
}
}
}
.onAppear {
loadHomeData()
}
}
private func loadHomeData() {
isLoading = true
Task {
async let resume = client.getItems(
userId: userId,
filters: [.IsResumable],
sortBy: [.DatePlayed],
sortOrder: [.Descending],
limit: 20,
recursive: true
)
async let nextUp = client.getNextUp(userId: userId, limit: 20)
async let latest = client.getLatestMedia(userId: userId, limit: 20)
async let views = client.getUserViews(userId: userId)
do {
let (r, n, l, v) = try await (resume, nextUp, latest, views)
await MainActor.run {
resumeItems = r.Items ?? []
nextUpItems = n.Items ?? []
latestItems = l
libraries = v.Items ?? []
isLoading = false
}
} catch {
await MainActor.run { isLoading = false }
}
}
}
}

View file

@ -0,0 +1,22 @@
import Adwaita
import LuminateCore
struct LibraryGrid: View {
var libraries: [Components.Schemas.BaseItemDto]
var client: JellyfinClient
var view: Body {
VStack {
Text("Libraries")
.style("title-3")
.halign(.start)
.padding(10, .horizontal)
FlowBox {
ForEach(libraries) { library in
HomePosterCell(item: library, client: client)
}
}
}
}
}

View file

@ -1,26 +1,2 @@
import Adwaita
import LuminateCore
import LuminateLibrary
public struct HomeView: View {
public var app: AdwaitaApp
public var window: AdwaitaWindow
public var client: JellyfinClient
public var userId: String
public init(app: AdwaitaApp, window: AdwaitaWindow, client: JellyfinClient, userId: String) {
self.app = app
self.window = window
self.client = client
self.userId = userId
}
public var view: Body {
ItemGrid(
client: client,
userId: userId,
title: "Library"
)
}
}

View file

@ -0,0 +1,33 @@
import Adwaita
import LuminateCore
struct MediaRow: View {
var title: String
var items: [Components.Schemas.BaseItemDto]
var client: JellyfinClient
var onSeeAll: (() -> Void)?
var view: Body {
VStack {
HStack {
Text(title)
.style("title-3")
if onSeeAll != nil {
Button("See All") {
onSeeAll?()
}
.style("flat")
}
}
.padding(10, .horizontal)
ScrollView(.horizontal) {
HStack {
ForEach(items) { item in
HomePosterCell(item: item, client: client)
}
}
}
}
}
}