diff --git a/Sources/Luminate/Luminate.swift b/Sources/Luminate/Luminate.swift index f5a1c35..0cb66a8 100644 --- a/Sources/Luminate/Luminate.swift +++ b/Sources/Luminate/Luminate.swift @@ -52,7 +52,7 @@ struct ContentView: View { item: item, client: client, userId: userId, - mediaSourceId: item.Id ?? "", + mediaSourceId: item.id ?? "", playSessionId: "", streamURL: URL(string: "https://example.com/stream")!, onClose: { activePlayerItem = nil } diff --git a/Sources/Luminate/ServerSetupView.swift b/Sources/Luminate/ServerSetupView.swift index 319cae3..962e88c 100644 --- a/Sources/Luminate/ServerSetupView.swift +++ b/Sources/Luminate/ServerSetupView.swift @@ -61,7 +61,7 @@ public struct ServerSetupView: View { do { let client = JellyfinClient(serverURL: url) let result = try await client.authenticate(username: username, password: password) - let userId = result.User?.value1.Id ?? "" + let userId = result.user?.value1.id ?? "" isLoading = false onLogin(client, userId) diff --git a/Sources/LuminateCore/BaseItemDto+Display.swift b/Sources/LuminateCore/BaseItemDto+Display.swift index 4f912a7..bd27831 100644 --- a/Sources/LuminateCore/BaseItemDto+Display.swift +++ b/Sources/LuminateCore/BaseItemDto+Display.swift @@ -1,16 +1,12 @@ import Foundation -extension Components.Schemas.BaseItemPerson: Identifiable { - public var id: String { Name ?? UUID().uuidString } -} +extension Components.Schemas.BaseItemPerson: Identifiable { } -extension Components.Schemas.BaseItemDto: Identifiable { - public var id: String { Id ?? "" } -} +extension Components.Schemas.BaseItemDto: Identifiable { } public extension Components.Schemas.SearchHint { var runtimeString: String { - guard let ticks = RunTimeTicks else { return "" } + guard let ticks = runTimeTicks else { return "" } let totalSeconds = Int(ticks / 10_000_000) let hours = totalSeconds / 3600 let minutes = (totalSeconds % 3600) / 60 @@ -20,14 +16,8 @@ public extension Components.Schemas.SearchHint { } public extension Components.Schemas.BaseItemDto { - var isMovie: Bool { _Type?.value1 == .Movie } - var isSeries: Bool { _Type?.value1 == .Series } - var isEpisode: Bool { _Type?.value1 == .Episode } - var isSeason: Bool { _Type?.value1 == .Season } - var isFolder: Bool { IsFolder ?? false } - var runtimeString: String { - guard let ticks = RunTimeTicks else { return "" } + guard let ticks = runTimeTicks else { return "" } let totalSeconds = Int(ticks / 10_000_000) let hours = totalSeconds / 3600 let minutes = (totalSeconds % 3600) / 60 @@ -36,15 +26,15 @@ public extension Components.Schemas.BaseItemDto { } var yearString: String { - guard let year = ProductionYear else { return "" } + guard let year = productionYear else { return "" } return "\(year)" } var primaryImageTag: String? { - ImageTags?.additionalProperties["Primary"] + imageTags?.additionalProperties["Primary"] } var backdropImageTag: String? { - BackdropImageTags?.first + backdropImageTags?.first } } diff --git a/Sources/LuminateCore/InjectionValues.swift b/Sources/LuminateCore/InjectionValues.swift new file mode 100644 index 0000000..dc5d933 --- /dev/null +++ b/Sources/LuminateCore/InjectionValues.swift @@ -0,0 +1,12 @@ +import Foundation + +public struct InjectionValues { + + public var client: JellyfinClient? + public var userId: String? + public var imageService: ImageService? + public var webSocketClient: WebSocketClient? + + public init() {} + +} diff --git a/Sources/LuminateCore/JellyfinClient.swift b/Sources/LuminateCore/JellyfinClient.swift index 83918b6..169d969 100644 --- a/Sources/LuminateCore/JellyfinClient.swift +++ b/Sources/LuminateCore/JellyfinClient.swift @@ -95,29 +95,29 @@ public actor JellyfinClient { } public func authenticate(username: String, password: String) async throws -> Components.Schemas.AuthenticationResult { - let response = try await client.AuthenticateUserByName(Operations.AuthenticateUserByName.Input( - body: .json(.init(value1: .init(Username: username, Pw: password))) + let response = try await client.authenticateUserByName(Operations.AuthenticateUserByName.Input( + body: .json(.init(value1: .init(username: username, pw: password))) )) switch response { case .ok(let ok): switch ok.body { case .json(let result): - guard let accessToken = result.AccessToken else { throw JellyfinError.invalidResponse } + guard let accessToken = result.accessToken else { throw JellyfinError.invalidResponse } token = accessToken client = makeClient(token: accessToken) - userId = result.User?.value1.Id + userId = result.user?.value1.id return result - case .application_json_profile__quot_camelcase_quot_(let result): - guard let accessToken = result.AccessToken else { throw JellyfinError.invalidResponse } + case .applicationJsonProfile_Quot_camelcase_quot_(let result): + guard let accessToken = result.accessToken else { throw JellyfinError.invalidResponse } token = accessToken client = makeClient(token: accessToken) - userId = result.User?.value1.Id + userId = result.user?.value1.id return result - case .application_json_profile__quot_pascalcase_quot_(let result): - guard let accessToken = result.AccessToken else { throw JellyfinError.invalidResponse } + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): + guard let accessToken = result.accessToken else { throw JellyfinError.invalidResponse } token = accessToken client = makeClient(token: accessToken) - userId = result.User?.value1.Id + userId = result.user?.value1.id return result } case .serviceUnavailable: @@ -154,15 +154,15 @@ public actor JellyfinClient { query.limit = limit query.recursive = recursive query.isFavorite = isFavorite - let response = try await client.GetItems(Operations.GetItems.Input(query: query)) + let response = try await client.getItems(Operations.GetItems.Input(query: query)) switch response { case .ok(let ok): switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -178,7 +178,7 @@ public actor JellyfinClient { public func getItem(itemId: String, userId: String? = nil) async throws -> Components.Schemas.BaseItemDto { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.GetItem(Operations.GetItem.Input( + let response = try await client.getItem(Operations.GetItem.Input( path: .init(itemId: itemId), query: .init(userId: userId) )) @@ -187,9 +187,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -205,7 +205,7 @@ public actor JellyfinClient { public func getUserViews(userId: String) async throws -> Components.Schemas.BaseItemDtoQueryResult { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.GetUserViews(Operations.GetUserViews.Input( + let response = try await client.getUserViews(Operations.GetUserViews.Input( query: .init(userId: userId) )) switch response { @@ -213,9 +213,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -248,15 +248,15 @@ public actor JellyfinClient { query.parentId = parentId query.enableResumable = enableResumable query.enableRewatching = enableRewatching - let response = try await client.GetNextUp(Operations.GetNextUp.Input(query: query)) + let response = try await client.getNextUp(Operations.GetNextUp.Input(query: query)) switch response { case .ok(let ok): switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -288,7 +288,7 @@ public actor JellyfinClient { query.imageTypeLimit = imageTypeLimit query.enableImageTypes = enableImageTypes query.enableUserData = enableUserData - let response = try await client.GetSeasons(Operations.GetSeasons.Input( + let response = try await client.getSeasons(Operations.GetSeasons.Input( path: .init(seriesId: seriesId), query: query )) @@ -297,9 +297,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .notFound: @@ -339,7 +339,7 @@ public actor JellyfinClient { query.imageTypeLimit = imageTypeLimit query.enableImageTypes = enableImageTypes query.enableUserData = enableUserData - let response = try await client.GetEpisodes(Operations.GetEpisodes.Input( + let response = try await client.getEpisodes(Operations.GetEpisodes.Input( path: .init(seriesId: seriesId), query: query )) @@ -348,9 +348,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .notFound: @@ -381,15 +381,15 @@ public actor JellyfinClient { query.limit = limit query.includeItemTypes = includeItemTypes query.parentId = parentId - let response = try await client.GetSearchHints(Operations.GetSearchHints.Input(query: query)) + let response = try await client.getSearchHints(Operations.GetSearchHints.Input(query: query)) switch response { case .ok(let ok): switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -405,7 +405,7 @@ public actor JellyfinClient { public func markPlayedItem(itemId: String, userId: String, datePlayed: Date? = nil) async throws -> Components.Schemas.UserItemDataDto { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.MarkPlayedItem(Operations.MarkPlayedItem.Input( + let response = try await client.markPlayedItem(Operations.MarkPlayedItem.Input( path: .init(itemId: itemId), query: .init(userId: userId, datePlayed: datePlayed) )) @@ -414,9 +414,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .notFound: @@ -434,7 +434,7 @@ public actor JellyfinClient { public func markUnplayedItem(itemId: String, userId: String) async throws -> Components.Schemas.UserItemDataDto { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.MarkUnplayedItem(Operations.MarkUnplayedItem.Input( + let response = try await client.markUnplayedItem(Operations.MarkUnplayedItem.Input( path: .init(itemId: itemId), query: .init(userId: userId) )) @@ -443,9 +443,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .notFound: @@ -463,7 +463,7 @@ public actor JellyfinClient { public func markFavoriteItem(itemId: String, userId: String) async throws -> Components.Schemas.UserItemDataDto { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.MarkFavoriteItem(Operations.MarkFavoriteItem.Input( + let response = try await client.markFavoriteItem(Operations.MarkFavoriteItem.Input( path: .init(itemId: itemId), query: .init(userId: userId) )) @@ -472,9 +472,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -490,7 +490,7 @@ public actor JellyfinClient { public func unmarkFavoriteItem(itemId: String, userId: String) async throws -> Components.Schemas.UserItemDataDto { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.UnmarkFavoriteItem(Operations.UnmarkFavoriteItem.Input( + let response = try await client.unmarkFavoriteItem(Operations.UnmarkFavoriteItem.Input( path: .init(itemId: itemId), query: .init(userId: userId) )) @@ -499,9 +499,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: @@ -517,7 +517,7 @@ public actor JellyfinClient { public func getPlaybackInfo(itemId: String, userId: String) async throws -> Components.Schemas.PlaybackInfoResponse { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.GetPlaybackInfo(Operations.GetPlaybackInfo.Input( + let response = try await client.getPlaybackInfo(Operations.GetPlaybackInfo.Input( path: .init(itemId: itemId), query: .init(userId: userId) )) @@ -526,9 +526,9 @@ public actor JellyfinClient { switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .notFound: @@ -546,7 +546,7 @@ public actor JellyfinClient { public func reportPlaybackStart(info: Components.Schemas.PlaybackStartInfo) async throws { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.ReportPlaybackStart(Operations.ReportPlaybackStart.Input( + let response = try await client.reportPlaybackStart(Operations.ReportPlaybackStart.Input( body: .json(.init(value1: info)) )) switch response { @@ -565,7 +565,7 @@ public actor JellyfinClient { public func reportPlaybackProgress(info: Components.Schemas.PlaybackProgressInfo) async throws { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.ReportPlaybackProgress(Operations.ReportPlaybackProgress.Input( + let response = try await client.reportPlaybackProgress(Operations.ReportPlaybackProgress.Input( body: .json(.init(value1: info)) )) switch response { @@ -584,7 +584,7 @@ public actor JellyfinClient { public func reportPlaybackStopped(info: Components.Schemas.PlaybackStopInfo) async throws { guard token != nil else { throw JellyfinError.notAuthenticated } - let response = try await client.ReportPlaybackStopped(Operations.ReportPlaybackStopped.Input( + let response = try await client.reportPlaybackStopped(Operations.ReportPlaybackStopped.Input( body: .json(.init(value1: info)) )) switch response { @@ -624,15 +624,15 @@ public actor JellyfinClient { query.enableImageTypes = enableImageTypes query.enableUserData = enableUserData query.groupItems = groupItems - let response = try await client.GetLatestMedia(Operations.GetLatestMedia.Input(query: query)) + let response = try await client.getLatestMedia(Operations.GetLatestMedia.Input(query: query)) switch response { case .ok(let ok): switch ok.body { case .json(let result): return result - case .application_json_profile__quot_camelcase_quot_(let result): + case .applicationJsonProfile_Quot_camelcase_quot_(let result): return result - case .application_json_profile__quot_pascalcase_quot_(let result): + case .applicationJsonProfile_Quot_pascalcase_quot_(let result): return result } case .unauthorized: diff --git a/Sources/LuminateCore/openapi-generator-config.yaml b/Sources/LuminateCore/openapi-generator-config.yaml index 736542d..8e60f91 100644 --- a/Sources/LuminateCore/openapi-generator-config.yaml +++ b/Sources/LuminateCore/openapi-generator-config.yaml @@ -2,3 +2,4 @@ generate: - types - client accessModifier: public +namingStrategy: idiomatic diff --git a/Sources/LuminateHome/AnyView+Overflow.swift b/Sources/LuminateHome/AnyView+Overflow.swift new file mode 100644 index 0000000..3283bd0 --- /dev/null +++ b/Sources/LuminateHome/AnyView+Overflow.swift @@ -0,0 +1,40 @@ +// +// AnyView+Overflow.swift +// Luminate +// +// Created by Brendan Szymanski on 6/8/26. +// + +import Adwaita +import CAdw + +/// The overflow behavior for a widget. +public enum Overflow: Int { + + /// Content is not clipped. + case visible + /// Content is clipped to the widget's bounds. + case hidden + + /// Get the GtkOverflow value. + public var cValue: GtkOverflow { + switch self { + case .visible: + CAdw.GTK_OVERFLOW_VISIBLE + case .hidden: + CAdw.GTK_OVERFLOW_HIDDEN + } + } + +} + +extension AnyView { + + /// Set the overflow behavior of the widget. + public func overflow(_ overflow: Overflow) -> AnyView { + wrapModifier(properties: [overflow]) { storage in + gtk_widget_set_overflow(storage.opaquePointer?.cast(), overflow.cValue) + } + } + +} diff --git a/Sources/LuminateHome/HomePosterCell.swift b/Sources/LuminateHome/HomePosterCell.swift index 7e86b9c..82742c5 100644 --- a/Sources/LuminateHome/HomePosterCell.swift +++ b/Sources/LuminateHome/HomePosterCell.swift @@ -12,34 +12,47 @@ struct HomePosterCell: View { VStack { if let data = imageData { Picture() + .contentFit(.cover) .data(data) - .frame(minWidth: 150, minHeight: 225) -.frame(maxWidth: 150) -.frame(maxHeight: 225) + .frame(minWidth: 200, minHeight: 300) + .frame(maxWidth: 200) + .frame(maxHeight: 300) } else { Box(spacing: 0) {} - .frame(minWidth: 150, minHeight: 225) -.frame(maxWidth: 150) -.frame(maxHeight: 225) - .style("card") + .frame(minWidth: 200, minHeight: 300) + .frame(maxWidth: 200) + .frame(maxHeight: 300) + .card() } - Text(item.Name ?? "") - .style("body") - .halign(.center) - .frame(maxWidth: 150) + VStack(spacing: 0) { + Text(item.name ?? "") + .ellipsize() + .heading() + .halign(.center) + .frame(maxWidth: 200) + Text(item.yearString) + .ellipsize() + .caption() + .dimLabel() + .halign(.center) + .frame(maxWidth: 200) + } + .padding(4) } .onAppear { loadImage() } + .overflow(.hidden) + .card() } private func loadImage() { guard let tag = item.primaryImageTag, - let itemId = item.Id else { return } + let itemId = item.id else { return } Task { guard let url = await client.imageURL( itemId: itemId, - imageType: .Primary, + imageType: .primary, tag: tag, maxWidth: 300 ) else { return } diff --git a/Sources/LuminateHome/HomeView.swift b/Sources/LuminateHome/HomeView.swift index ccbeb4f..14fe690 100644 --- a/Sources/LuminateHome/HomeView.swift +++ b/Sources/LuminateHome/HomeView.swift @@ -12,6 +12,9 @@ public struct HomeView: View { @State private var latestItems: [Components.Schemas.BaseItemDto] = [] @State private var libraries: [Components.Schemas.BaseItemDto] = [] @State private var isLoading = true + @State private var isLoadingData = false + + @Environment("client") var apiClient: JellyfinClient? public init( app: AdwaitaApp, @@ -27,56 +30,67 @@ public struct HomeView: View { public 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) + Clamp() + .maximumSize(1550) + .tighteningThreshold(550) + .child { + VStack { + if isLoading { + + Spinner() + .halign(.center) + .valign(.center) + .frame(minWidth: 64) + .frame(maxWidth: 64) + } else { + if !resumeItems.isEmpty { + MediaRow( + title: "Continue Watching", + items: resumeItems, + client: client + ) + .padding(16, .bottom) + } + if !nextUpItems.isEmpty { + MediaRow( + title: "Next Up", + items: nextUpItems, + client: client + ) + .padding(16, .bottom) + } + if !latestItems.isEmpty { + MediaRow( + title: "Recently Added", + items: latestItems, + client: client, + onSeeAll: {} + ) + .padding(16, .bottom) + } + LibraryGrid( + libraries: libraries, + client: client + ) + } } - 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 - ) + .padding(8, .horizontal) } - } } + .hscrollbarPolicy(.never) + .propagateNaturalHeight() .onAppear { loadHomeData() } } private func loadHomeData() { - isLoading = true Task { async let resume = client.getItems( userId: userId, - filters: [.IsResumable], - sortBy: [.DatePlayed], - sortOrder: [.Descending], + filters: [.isResumable], + sortBy: [.datePlayed], + sortOrder: [.descending], limit: 20, recursive: true ) @@ -85,16 +99,15 @@ public struct HomeView: View { 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 - } + resumeItems = r.items ?? [] + nextUpItems = n.items ?? [] + latestItems = l + libraries = v.items ?? [] + isLoading = false } catch { - await MainActor.run { isLoading = false } + isLoading = false } + isLoadingData = false } } } diff --git a/Sources/LuminateHome/LuminateHome.swift b/Sources/LuminateHome/LuminateHome.swift deleted file mode 100644 index 11fdee6..0000000 --- a/Sources/LuminateHome/LuminateHome.swift +++ /dev/null @@ -1,2 +0,0 @@ -import Adwaita -import LuminateCore diff --git a/Sources/LuminateHome/MediaRow.swift b/Sources/LuminateHome/MediaRow.swift index e6b3012..3f56bda 100644 --- a/Sources/LuminateHome/MediaRow.swift +++ b/Sources/LuminateHome/MediaRow.swift @@ -10,7 +10,7 @@ struct MediaRow: View { var onSeeAll: (() -> Void)? var view: Body { - VStack { + VStack(spacing: 8) { HStack { Text(title) .style("title-3") @@ -21,14 +21,15 @@ struct MediaRow: View { .style("flat") } } - .padding(10, .horizontal) ScrollView { - HStack { - ForEach(items) { item in - HomePosterCell(item: item, client: client) - } - } - } + ForEach(items, horizontal: true) { item in + HomePosterCell(item: item, client: client) + .padding(16, .trailing) + } + } + .vscrollbarPolicy(.never) + .hscrollbarPolicy(.external) } } } + diff --git a/Sources/LuminateLibrary/Components/PersonCell.swift b/Sources/LuminateLibrary/Components/PersonCell.swift index d50c9a1..7dc5dbd 100644 --- a/Sources/LuminateLibrary/Components/PersonCell.swift +++ b/Sources/LuminateLibrary/Components/PersonCell.swift @@ -6,9 +6,9 @@ struct PersonCell: View { var view: Body { VStack { Avatar(showInitials: false, size: 60) - Text(person.Name ?? "") + Text(person.name ?? "") .style("caption") - if let role = person.Role { + if let role = person.role { Text(role) .style("caption") .dimLabel() diff --git a/Sources/LuminateLibrary/EpisodeList.swift b/Sources/LuminateLibrary/EpisodeList.swift index 51c6da8..d930d4a 100644 --- a/Sources/LuminateLibrary/EpisodeList.swift +++ b/Sources/LuminateLibrary/EpisodeList.swift @@ -31,7 +31,7 @@ struct EpisodeList: View { userId: userId, seasonId: seasonId ) - await MainActor.run { episodes = result?.Items ?? [] } + await MainActor.run { episodes = result?.items ?? [] } } } } @@ -59,7 +59,7 @@ struct EpisodeRow: View { .style("card") } VStack { - Text("\(episode.IndexNumber ?? 0). \(episode.Name ?? "")") + Text("\(episode.indexNumber ?? 0). \(episode.name ?? "")") .style("body") .halign(.start) Text(episode.runtimeString) @@ -79,10 +79,10 @@ struct EpisodeRow: View { } private func loadImage() { - guard let tag = episode.primaryImageTag, let itemId = episode.Id else { return } + guard let tag = episode.primaryImageTag, let itemId = episode.id else { return } Task { guard let url = await client.imageURL( - itemId: itemId, imageType: .Primary, tag: tag, maxWidth: 200 + itemId: itemId, imageType: .primary, tag: tag, maxWidth: 200 ) else { return } let service = ImageService() imageData = try? await service.loadImage(url: url) diff --git a/Sources/LuminateLibrary/ItemGrid.swift b/Sources/LuminateLibrary/ItemGrid.swift index d5e3215..da629a9 100644 --- a/Sources/LuminateLibrary/ItemGrid.swift +++ b/Sources/LuminateLibrary/ItemGrid.swift @@ -57,15 +57,15 @@ public struct ItemGrid: View { userId: userId, parentId: parentId, includeItemTypes: includeItemTypes, - fields: [.Overview, .Genres, .People, .MediaSources], - sortBy: [.SortName], - sortOrder: [.Ascending], + fields: [.overview, .genres, .people, .mediaSources], + sortBy: [.sortName], + sortOrder: [.ascending], startIndex: 0, limit: pageSize, recursive: true ) await MainActor.run { - items = result.Items ?? [] + items = result.items ?? [] isLoading = false } } catch { diff --git a/Sources/LuminateLibrary/MovieDetailView.swift b/Sources/LuminateLibrary/MovieDetailView.swift index 11e5145..43a679a 100644 --- a/Sources/LuminateLibrary/MovieDetailView.swift +++ b/Sources/LuminateLibrary/MovieDetailView.swift @@ -16,8 +16,8 @@ struct MovieDetailView: View { 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) + _isFavorite = .init(wrappedValue: item.userData?.value1.isFavorite ?? false) + _isPlayed = .init(wrappedValue: item.userData?.value1.played ?? false) } var view: Body { @@ -35,15 +35,15 @@ struct MovieDetailView: View { .frame(minWidth: 200) .frame(maxWidth: 200) VStack { - Text(item.Name ?? "") + Text(item.name ?? "") .style("title-1") .halign(.start) HStack { - if let year = item.ProductionYear { + if let year = item.productionYear { Text("\(year)") } Text(item.runtimeString) - if let rating = item.CommunityRating { + if let rating = item.communityRating { RatingBadge(rating: Double(rating)) } } @@ -62,7 +62,7 @@ struct MovieDetailView: View { .style("flat") } .padding(10, .vertical) - if let overview = item.Overview { + if let overview = item.overview { Text(overview) .style("body") .halign(.start) @@ -71,7 +71,7 @@ struct MovieDetailView: View { .hexpand(true) } .padding() - if let people = item.People, !people.isEmpty { + if let people = item.people, !people.isEmpty { VStack { Text("Cast") .style("title-3") @@ -107,10 +107,10 @@ struct MovieDetailView: View { } private func loadBackdrop() { - guard let tag = item.backdropImageTag, let itemId = item.Id else { return } + 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 + itemId: itemId, imageType: .backdrop, tag: tag, maxWidth: 1920 ) else { return } let service = ImageService() backdropData = try? await service.loadImage(url: url) @@ -121,22 +121,22 @@ struct MovieDetailView: View { Task { let result = try? await client.getItems( userId: userId, - includeItemTypes: [.Movie], - fields: [.Overview, .Genres, .MediaSources], - sortBy: [.SortName], + includeItemTypes: [.movie], + fields: [.overview, .genres, .mediaSources], + sortBy: [.sortName], limit: 10, recursive: true ) - await MainActor.run { similarItems = result?.Items ?? [] } + await MainActor.run { similarItems = result?.items ?? [] } } } private func toggleFavorite() { Task { if isFavorite { - try? await client.unmarkFavoriteItem(itemId: item.Id ?? "", userId: userId) + try? await client.unmarkFavoriteItem(itemId: item.id ?? "", userId: userId) } else { - try? await client.markFavoriteItem(itemId: item.Id ?? "", userId: userId) + try? await client.markFavoriteItem(itemId: item.id ?? "", userId: userId) } await MainActor.run { isFavorite.toggle() } } @@ -145,9 +145,9 @@ struct MovieDetailView: View { private func togglePlayed() { Task { if isPlayed { - try? await client.markUnplayedItem(itemId: item.Id ?? "", userId: userId) + try? await client.markUnplayedItem(itemId: item.id ?? "", userId: userId) } else { - try? await client.markPlayedItem(itemId: item.Id ?? "", userId: userId) + try? await client.markPlayedItem(itemId: item.id ?? "", userId: userId) } await MainActor.run { isPlayed.toggle() } } diff --git a/Sources/LuminateLibrary/PosterCell.swift b/Sources/LuminateLibrary/PosterCell.swift index 5262e1b..9aaa888 100644 --- a/Sources/LuminateLibrary/PosterCell.swift +++ b/Sources/LuminateLibrary/PosterCell.swift @@ -21,7 +21,7 @@ struct PosterCell: View { .frame(maxHeight: 225) .style("card") } - Text(item.Name ?? "") + Text(item.name ?? "") .style("body") .halign(.center) .frame(maxWidth: 150) @@ -33,11 +33,11 @@ struct PosterCell: View { private func loadImage() { guard let tag = item.primaryImageTag, - let itemId = item.Id else { return } + let itemId = item.id else { return } Task { let url = await client.imageURL( itemId: itemId, - imageType: .Primary, + imageType: .primary, tag: tag, maxWidth: 300 ) diff --git a/Sources/LuminateLibrary/SearchView.swift b/Sources/LuminateLibrary/SearchView.swift index 8ebe1ba..281e071 100644 --- a/Sources/LuminateLibrary/SearchView.swift +++ b/Sources/LuminateLibrary/SearchView.swift @@ -44,7 +44,7 @@ struct SearchView: View { limit: 50 ) await MainActor.run { - results = result?.SearchHints ?? [] + results = result?.searchHints ?? [] isSearching = false } } @@ -52,7 +52,7 @@ struct SearchView: View { } extension Components.Schemas.SearchHint: Identifiable { - public var id: String { Id ?? ItemId ?? String(describing: self) } + public var id: String { id ?? itemId ?? String(describing: self) } } struct SearchResultRow: View { @@ -78,15 +78,15 @@ struct SearchResultRow: View { .style("card") } VStack { - Text(hint.Name ?? "") + Text(hint.name ?? "") .style("body") .halign(.start) - if let type = hint._Type?.value1 { + if let type = hint._type?.value1 { Text("\(type)") .style("caption") .halign(.start) } - if let year = hint.ProductionYear { + if let year = hint.productionYear { Text("\(year)") .style("caption") .halign(.start) @@ -104,11 +104,11 @@ struct SearchResultRow: View { } private func loadImage() { - guard let tag = hint.PrimaryImageTag, - let itemId = hint.Id ?? hint.ItemId else { return } + guard let tag = hint.primaryImageTag, + let itemId = hint.id ?? hint.itemId else { return } Task { guard let url = await client.imageURL( - itemId: itemId, imageType: .Primary, tag: tag, maxWidth: 160 + itemId: itemId, imageType: .primary, tag: tag, maxWidth: 160 ) else { return } let service = ImageService() imageData = try? await service.loadImage(url: url) diff --git a/Sources/LuminateLibrary/TVShowView.swift b/Sources/LuminateLibrary/TVShowView.swift index bf0be24..a4bd5c0 100644 --- a/Sources/LuminateLibrary/TVShowView.swift +++ b/Sources/LuminateLibrary/TVShowView.swift @@ -26,15 +26,15 @@ struct TVShowView: View { .frame(minWidth: 200) .frame(maxWidth: 200) VStack { - Text(item.Name ?? "") + Text(item.name ?? "") .style("title-1") .halign(.start) - if let status = item.Status { + if let status = item.status { Text(status) .style("caption") .halign(.start) } - if let overview = item.Overview { + if let overview = item.overview { Text(overview) .style("body") .halign(.start) @@ -51,10 +51,10 @@ struct TVShowView: View { .halign(.start) HStack { ForEach(seasons) { season in - Button(season.Name ?? "?") { - selectedSeasonId = season.Id + Button(season.name ?? "?") { + selectedSeasonId = season.id } - .style(selectedSeasonId == season.Id ? "suggested-action" : "flat") + .style(selectedSeasonId == season.id ? "suggested-action" : "flat") } } } @@ -62,7 +62,7 @@ struct TVShowView: View { } if let seasonId = selectedSeasonId { EpisodeList( - seriesId: item.Id ?? "", + seriesId: item.id ?? "", seasonId: seasonId, client: client, userId: userId @@ -77,10 +77,10 @@ struct TVShowView: View { } private func loadBackdrop() { - guard let tag = item.backdropImageTag, let itemId = item.Id else { return } + 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 + itemId: itemId, imageType: .backdrop, tag: tag, maxWidth: 1920 ) else { return } let service = ImageService() backdropData = try? await service.loadImage(url: url) @@ -89,10 +89,10 @@ struct TVShowView: View { private func loadSeasons() { Task { - let result = try? await client.getSeasons(seriesId: item.Id ?? "", userId: userId) + let result = try? await client.getSeasons(seriesId: item.id ?? "", userId: userId) await MainActor.run { - seasons = result?.Items ?? [] - selectedSeasonId = seasons.first?.Id + seasons = result?.items ?? [] + selectedSeasonId = seasons.first?.id } } } diff --git a/Sources/LuminatePlayer/PlayerView.swift b/Sources/LuminatePlayer/PlayerView.swift index 87d36c5..5d3ef0b 100644 --- a/Sources/LuminatePlayer/PlayerView.swift +++ b/Sources/LuminatePlayer/PlayerView.swift @@ -66,9 +66,9 @@ public struct PlayerView: View { Task { try? await client.reportPlaybackStart( info: .init( - ItemId: item.Id, - MediaSourceId: mediaSourceId, - PlaySessionId: playSessionId + itemId: item.id, + mediaSourceId: mediaSourceId, + playSessionId: playSessionId ) ) } @@ -78,10 +78,10 @@ public struct PlayerView: View { Task { try? await client.reportPlaybackStopped( info: .init( - ItemId: item.Id, - MediaSourceId: mediaSourceId, - PositionTicks: Int64(position * 10_000_000), - PlaySessionId: playSessionId + itemId: item.id, + mediaSourceId: mediaSourceId, + positionTicks: Int64(position * 10_000_000), + playSessionId: playSessionId ) ) } diff --git a/dev.bscubed.Luminate.json b/dev.bscubed.Luminate.json index a84e7f8..465f768 100644 --- a/dev.bscubed.Luminate.json +++ b/dev.bscubed.Luminate.json @@ -1,7 +1,7 @@ { "app-id": "dev.bscubed.Luminate", "runtime": "org.gnome.Platform", - "runtime-version": "49", + "runtime-version": "50", "sdk": "org.gnome.Sdk", "sdk-extensions": [ "org.freedesktop.Sdk.Extension.swift6"