import Foundation import Adwaita import LuminateCore struct MovieDetailView: View { var item: Components.Schemas.BaseItemDto var client: JellyfinClient var userId: String @State private var isFavorite: Bool @State private var isPlayed: Bool @State private var similarItems: [Components.Schemas.BaseItemDto] = [] @State private 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 ?? "") .style("title-1") .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)) { } .style("suggested-action") Button(icon: .default(icon: .bookmarkNew)) { toggleFavorite() } Button(isPlayed ? "Mark Unplayed" : "Mark Played") { togglePlayed() } .style("flat") } .padding(10, .vertical) if let overview = item.Overview { Text(overview) .style("body") .halign(.start) } } .hexpand(true) } .padding() if let people = item.People, !people.isEmpty { VStack { Text("Cast") .style("title-3") .halign(.start) FlowBox(people) { person in PersonCell(person: person) } } .padding() } if !similarItems.isEmpty { VStack { Text("Similar") .style("title-3") .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 { 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 { let result = try? await client.getItems( userId: userId, includeItemTypes: [.Movie], fields: [.Overview, .Genres, .MediaSources], sortBy: [.SortName], limit: 10, recursive: true ) await MainActor.run { similarItems = result?.Items ?? [] } } } private func toggleFavorite() { Task { if isFavorite { try? await client.unmarkFavoriteItem(itemId: item.Id ?? "", userId: userId) } else { try? await client.markFavoriteItem(itemId: item.Id ?? "", userId: userId) } await MainActor.run { isFavorite.toggle() } } } private func togglePlayed() { Task { if isPlayed { try? await client.markUnplayedItem(itemId: item.Id ?? "", userId: userId) } else { try? await client.markPlayedItem(itemId: item.Id ?? "", userId: userId) } await MainActor.run { isPlayed.toggle() } } } }