// // MovieDetailView.swift // LuminateUI // // Created by Brendan Szymanski on 6/5/26. // @preconcurrency import Adwaita import Foundation import LuminateCore struct MovieDetailView: View { var item: Components.Schemas.BaseItemDto var client: JellyfinClient var userId: String @State private nonisolated(unsafe) var isFavorite: Bool @State private nonisolated(unsafe) var isPlayed: Bool @State private nonisolated(unsafe) var similarItems: [Components.Schemas.BaseItemDto] = [] @State private nonisolated(unsafe) var backdropData: Data? init(item: Components.Schemas.BaseItemDto, client: JellyfinClient, userId: String) { self.item = item self.client = client self.userId = userId _isFavorite = .init(wrappedValue: item.userData?.value1.isFavorite ?? false) _isPlayed = .init(wrappedValue: item.userData?.value1.played ?? false) } var view: Body { ScrollView { VStack { if let data = backdropData { Picture() .data(data) .frame(minHeight: 300) .frame(maxHeight: 300) .hexpand(true) } HStack { PosterCell(item: item, client: client) .frame(minWidth: 200) .frame(maxWidth: 200) VStack { Text(item.name ?? "") .title1() .halign(.start) HStack { if let year = item.productionYear { Text("\(year)") } Text(item.runtimeString) if let rating = item.communityRating { RatingBadge(rating: Double(rating)) } } .halign(.start) HStack { Button("Play", icon: .default(icon: .mediaPlaybackStart)) { } .suggested() Button(icon: .default(icon: .bookmarkNew)) { toggleFavorite() } Button(isPlayed ? "Mark Unplayed" : "Mark Played") { togglePlayed() } .flat() } .padding(10, .vertical) if let overview = item.overview { Text(overview) .body() .halign(.start) } } .hexpand(true) } .padding() if let people = item.people, !people.isEmpty { VStack { Text("Cast") .title3() .halign(.start) FlowBox(people) { person in PersonCell(person: person) } } .padding() } if !similarItems.isEmpty { VStack { Text("Similar") .title3() .halign(.start) ScrollView { HStack { ForEach(similarItems) { sim in PosterCell(item: sim, client: client) } } } .hscrollbarPolicy(.automatic) } .padding() } } } .onAppear { loadBackdrop() loadSimilar() } } private func loadBackdrop() { guard let tag = item.backdropImageTag, let itemId = item.id else { return } Task { [self] in guard let url = await client.imageURL( itemId: itemId, imageType: .backdrop, tag: tag, maxWidth: 1920 ) else { return } let service = ImageService() backdropData = try? await service.loadImage(url: url) } } private func loadSimilar() { Task { [self] in let result = try? await client.getItems( userId: userId, includeItemTypes: [.movie], fields: [.overview, .genres, .mediaSources], sortBy: [.sortName], limit: 10, recursive: true ) similarItems = result?.items ?? [] } } private func toggleFavorite() { Task { [self] in if isFavorite { try? await client.unmarkFavoriteItem(itemId: item.id ?? "", userId: userId) } else { try? await client.markFavoriteItem(itemId: item.id ?? "", userId: userId) } isFavorite.toggle() } } private func togglePlayed() { Task { [self] in if isPlayed { try? await client.markUnplayedItem(itemId: item.id ?? "", userId: userId) } else { try? await client.markPlayedItem(itemId: item.id ?? "", userId: userId) } isPlayed.toggle() } } }