diff --git a/Shared/Views/BlurView.swift b/Shared/Components/BlurView.swift similarity index 100% rename from Shared/Views/BlurView.swift rename to Shared/Components/BlurView.swift diff --git a/Shared/Views/Divider.swift b/Shared/Components/Divider.swift similarity index 100% rename from Shared/Views/Divider.swift rename to Shared/Components/Divider.swift diff --git a/Shared/Views/ImageView.swift b/Shared/Components/ImageView.swift similarity index 100% rename from Shared/Views/ImageView.swift rename to Shared/Components/ImageView.swift diff --git a/Shared/Views/InitialFailureView.swift b/Shared/Components/InitialFailureView.swift similarity index 100% rename from Shared/Views/InitialFailureView.swift rename to Shared/Components/InitialFailureView.swift diff --git a/Shared/Views/PlainNavigationLinkButton.swift b/Shared/Components/PlainNavigationLinkButton.swift similarity index 100% rename from Shared/Views/PlainNavigationLinkButton.swift rename to Shared/Components/PlainNavigationLinkButton.swift diff --git a/Shared/Views/PosterIndicators/FavoriteIndicator.swift b/Shared/Components/PosterIndicators/FavoriteIndicator.swift similarity index 100% rename from Shared/Views/PosterIndicators/FavoriteIndicator.swift rename to Shared/Components/PosterIndicators/FavoriteIndicator.swift diff --git a/Shared/Views/PosterIndicators/ProgressIndicator.swift b/Shared/Components/PosterIndicators/ProgressIndicator.swift similarity index 100% rename from Shared/Views/PosterIndicators/ProgressIndicator.swift rename to Shared/Components/PosterIndicators/ProgressIndicator.swift diff --git a/Shared/Views/PosterIndicators/UnwatchedIndicator.swift b/Shared/Components/PosterIndicators/UnwatchedIndicator.swift similarity index 100% rename from Shared/Views/PosterIndicators/UnwatchedIndicator.swift rename to Shared/Components/PosterIndicators/UnwatchedIndicator.swift diff --git a/Shared/Views/PosterIndicators/WatchedIndicator.swift b/Shared/Components/PosterIndicators/WatchedIndicator.swift similarity index 100% rename from Shared/Views/PosterIndicators/WatchedIndicator.swift rename to Shared/Components/PosterIndicators/WatchedIndicator.swift diff --git a/Shared/Views/ProgressBar.swift b/Shared/Components/ProgressBar.swift similarity index 100% rename from Shared/Views/ProgressBar.swift rename to Shared/Components/ProgressBar.swift diff --git a/Shared/Views/RotateContentView.swift b/Shared/Components/RotateContentView.swift similarity index 100% rename from Shared/Views/RotateContentView.swift rename to Shared/Components/RotateContentView.swift diff --git a/Shared/Views/SelectorView.swift b/Shared/Components/SelectorView.swift similarity index 100% rename from Shared/Views/SelectorView.swift rename to Shared/Components/SelectorView.swift diff --git a/Shared/Views/SeparatorHStack.swift b/Shared/Components/SeparatorHStack.swift similarity index 96% rename from Shared/Views/SeparatorHStack.swift rename to Shared/Components/SeparatorHStack.swift index 3658f961..1316f71c 100644 --- a/Shared/Views/SeparatorHStack.swift +++ b/Shared/Components/SeparatorHStack.swift @@ -17,7 +17,8 @@ struct SeparatorHStack: View { var body: some View { _VariadicView.Tree(SeparatorHStackLayout(separator: separator)) { - AnyView(content()) + content() + .eraseToAnyView() } } } diff --git a/Shared/Views/TextPairView.swift b/Shared/Components/TextPairView.swift similarity index 100% rename from Shared/Views/TextPairView.swift rename to Shared/Components/TextPairView.swift diff --git a/Shared/Views/TruncatedTextView.swift b/Shared/Components/TruncatedText.swift similarity index 51% rename from Shared/Views/TruncatedTextView.swift rename to Shared/Components/TruncatedText.swift index 6504ea39..17c5e258 100644 --- a/Shared/Views/TruncatedTextView.swift +++ b/Shared/Components/TruncatedText.swift @@ -9,61 +9,55 @@ import Defaults import SwiftUI -struct TruncatedTextView: View { +struct TruncatedText: View { @Default(.accentColor) private var accentColor @State - private var truncated: Bool = false + private var isTruncated: Bool = false @State private var fullSize: CGFloat = 0 - private var font: Font - private var lineLimit: Int private let text: String private var seeMoreAction: () -> Void - private let seeMoreText = "... \(L10n.seeMore)" + private let seeMoreText = "... See More" var body: some View { ZStack(alignment: .bottomTrailing) { Text(text) - .font(font) - .lineLimit(lineLimit) - .if(truncated) { text in - text.mask { - VStack(spacing: 0) { - Color.black + .inverseMask(alignment: .bottomTrailing) { + VStack { + Spacer() - HStack(spacing: 0) { - Color.black + HStack { + Spacer() - LinearGradient( - stops: [ - .init(color: .black, location: 0), - .init(color: .clear, location: 0.1), - ], - startPoint: .leading, - endPoint: .trailing - ) - .frame(width: seeMoreText.widthOfString(usingFont: font.uiFont) + 15) - } - .frame(height: seeMoreText.heightOfString(usingFont: font.uiFont)) + Text(" " + seeMoreText) + .background { + LinearGradient( + stops: [ + .init(color: .clear, location: 0), + .init(color: .black, location: 0.1), + ], + startPoint: .leading, + endPoint: .trailing + ) + } } } + .visible(isTruncated) } - if truncated { + if isTruncated { #if os(tvOS) Text(seeMoreText) - .font(font) .foregroundColor(accentColor) #else Button { seeMoreAction() } label: { Text(seeMoreText) - .font(font) .foregroundColor(accentColor) } #endif @@ -71,17 +65,15 @@ struct TruncatedTextView: View { } .background { ZStack { - if !truncated { + if !isTruncated { if fullSize != 0 { Text(text) - .font(font) - .lineLimit(lineLimit) .background { - GeometryReader { geo in + GeometryReader { proxy in Color.clear .onAppear { - if fullSize > geo.size.height { - self.truncated = true + if fullSize > proxy.size.height { + self.isTruncated = true } } } @@ -89,14 +81,13 @@ struct TruncatedTextView: View { } Text(text) - .font(font) .lineLimit(10) .fixedSize(horizontal: false, vertical: true) .background { - GeometryReader { geo in + GeometryReader { proxy in Color.clear .onAppear { - self.fullSize = geo.size.height + self.fullSize = proxy.size.height } } } @@ -107,25 +98,15 @@ struct TruncatedTextView: View { } } -extension TruncatedTextView { +extension TruncatedText { - init(text: String) { + init(_ text: String) { self.init( - font: .body, - lineLimit: 1000, text: text, seeMoreAction: {} ) } - func font(_ font: Font) -> Self { - copy(modifying: \.font, with: font) - } - - func lineLimit(_ limit: Int) -> Self { - copy(modifying: \.lineLimit, with: limit) - } - func seeMoreAction(_ action: @escaping () -> Void) -> Self { copy(modifying: \.seeMoreAction, with: action) } diff --git a/Shared/Views/Wrapped View.swift b/Shared/Components/Wrapped View.swift similarity index 100% rename from Shared/Views/Wrapped View.swift rename to Shared/Components/Wrapped View.swift diff --git a/Shared/Coordinators/BasicNavigationCoordinator.swift b/Shared/Coordinators/BasicNavigationCoordinator.swift index fd48bce8..628d7b61 100644 --- a/Shared/Coordinators/BasicNavigationCoordinator.swift +++ b/Shared/Coordinators/BasicNavigationCoordinator.swift @@ -9,6 +9,8 @@ import Stinsen import SwiftUI +// TODO: just have this coordinator wrap the content itself in a NavigationViewCoordinator instead + /// Basic coordinator to wrap a view for the purpose of being wrapped in a NavigationViewCoordinator final class BasicNavigationViewCoordinator: NavigationCoordinatable { diff --git a/Shared/Coordinators/ItemCoordinator.swift b/Shared/Coordinators/ItemCoordinator.swift index 607f4db1..dc1bbc01 100644 --- a/Shared/Coordinators/ItemCoordinator.swift +++ b/Shared/Coordinators/ItemCoordinator.swift @@ -25,11 +25,11 @@ final class ItemCoordinator: NavigationCoordinatable { var library = makeLibrary @Route(.push) var castAndCrew = makeCastAndCrew - @Route(.modal) - var itemOverview = makeItemOverview #if os(iOS) @Route(.modal) + var itemOverview = makeItemOverview + @Route(.modal) var mediaSourceInfo = makeMediaSourceInfo @Route(.modal) var downloadTask = makeDownloadTask @@ -37,10 +37,14 @@ final class ItemCoordinator: NavigationCoordinatable { #if os(tvOS) @Route(.fullScreen) + var itemOverview = makeItemOverview + @Route(.fullScreen) + var mediaSourceInfo = makeMediaSourceInfo + @Route(.fullScreen) var videoPlayer = makeVideoPlayer #endif - let itemDto: BaseItemDto + private let itemDto: BaseItemDto init(item: BaseItemDto) { self.itemDto = item @@ -62,15 +66,17 @@ final class ItemCoordinator: NavigationCoordinatable { CastAndCrewLibraryCoordinator(people: people) } - func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator { - NavigationViewCoordinator(ItemOverviewCoordinator(item: itemDto)) + func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator { + NavigationViewCoordinator(BasicNavigationViewCoordinator { + ItemOverviewView(item: item) + }) + } + + func makeMediaSourceInfo(source: MediaSourceInfo) -> NavigationViewCoordinator { + NavigationViewCoordinator(MediaSourceInfoCoordinator(mediaSourceInfo: source)) } #if os(iOS) - func makeMediaSourceInfo(mediaSourceInfo: MediaSourceInfo) -> NavigationViewCoordinator { - NavigationViewCoordinator(MediaSourceInfoCoordinator(mediaSourceInfo: mediaSourceInfo)) - } - func makeDownloadTask(downloadTask: DownloadTask) -> NavigationViewCoordinator { NavigationViewCoordinator(DownloadTaskCoordinator(downloadTask: downloadTask)) } diff --git a/Shared/Coordinators/ItemOverviewCoordinator.swift b/Shared/Coordinators/ItemOverviewCoordinator.swift deleted file mode 100644 index c73c5df9..00000000 --- a/Shared/Coordinators/ItemOverviewCoordinator.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2023 Jellyfin & Jellyfin Contributors -// - -import JellyfinAPI -import Stinsen -import SwiftUI - -final class ItemOverviewCoordinator: NavigationCoordinatable { - - let stack = NavigationStack(initial: \ItemOverviewCoordinator.start) - - @Root - var start = makeStart - - let item: BaseItemDto - - init(item: BaseItemDto) { - self.item = item - } - - @ViewBuilder - func makeStart() -> some View { - #if os(tvOS) - EmptyView() - #else - ItemOverviewView(item: item) - #endif - } -} diff --git a/Shared/Coordinators/MediaSourceInfoCoordinator.swift b/Shared/Coordinators/MediaSourceInfoCoordinator.swift index d1dbd79c..0ce3eaa8 100644 --- a/Shared/Coordinators/MediaSourceInfoCoordinator.swift +++ b/Shared/Coordinators/MediaSourceInfoCoordinator.swift @@ -16,8 +16,11 @@ final class MediaSourceInfoCoordinator: NavigationCoordinatable { @Root var start = makeStart + + #if os(iOS) @Route(.push) var mediaStreamInfo = makeMediaStreamInfo + #endif private let mediaSourceInfo: MediaSourceInfo @@ -25,13 +28,15 @@ final class MediaSourceInfoCoordinator: NavigationCoordinatable { self.mediaSourceInfo = mediaSourceInfo } + #if os(iOS) @ViewBuilder func makeMediaStreamInfo(mediaStream: MediaStream) -> some View { MediaStreamInfoView(mediaStream: mediaStream) } + #endif @ViewBuilder func makeStart() -> some View { - ItemView.MediaSourceInfoView(mediaSource: mediaSourceInfo) + MediaSourceInfoView(source: mediaSourceInfo) } } diff --git a/Shared/Extensions/Array.swift b/Shared/Extensions/Array.swift index 0e04b66f..0e4fcff7 100644 --- a/Shared/Extensions/Array.swift +++ b/Shared/Extensions/Array.swift @@ -26,6 +26,14 @@ extension Array { self + contents } + func count(where predicate: (Element) throws -> Bool) rethrows -> Int { + try filter(predicate).count + } + + func oneSatisfies(_ predicate: (Element) throws -> Bool) rethrows -> Bool { + try first(where: predicate) != nil + } + func prepending(_ element: Element) -> [Element] { [element] + self } diff --git a/Shared/Extensions/ForEach.swift b/Shared/Extensions/ForEach.swift new file mode 100644 index 00000000..abf374a0 --- /dev/null +++ b/Shared/Extensions/ForEach.swift @@ -0,0 +1,35 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +extension ForEach where Content: View { + + @ViewBuilder + static func `let`( + _ data: Data?, + id: KeyPath, + @ViewBuilder content: @escaping (Data.Element) -> Content + ) -> some View { + if let data { + ForEach(data, id: id, content: content) + } else { + EmptyView() + } + } + + @ViewBuilder + static func `let`(_ data: Data?, @ViewBuilder content: @escaping (Data.Element) -> Content) -> some View where ID == Data.Element.ID, + Data.Element: Identifiable { + if let data { + ForEach(data, content: content) + } else { + EmptyView() + } + } +} diff --git a/Shared/Extensions/JellyfinAPI/BaseItemDto.swift b/Shared/Extensions/JellyfinAPI/BaseItemDto.swift index dafaf0f7..eecf61c7 100644 --- a/Shared/Extensions/JellyfinAPI/BaseItemDto.swift +++ b/Shared/Extensions/JellyfinAPI/BaseItemDto.swift @@ -151,6 +151,15 @@ extension BaseItemDto { return dateFormatter.string(from: premiereDate) } + var hasExternalLinks: Bool { + guard let externalURLs else { return false } + return !externalURLs.isEmpty + } + + var hasRatings: Bool { + [criticRating, communityRating].oneSatisfies { $0 != nil } + } + // MARK: Chapter Images var fullChapterInfo: [ChapterInfo.FullInfo] { diff --git a/Shared/Extensions/ViewExtensions/Modifiers/AttributeStyleModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/AttributeStyleModifier.swift index 44d69b4b..7c8c53b9 100644 --- a/Shared/Extensions/ViewExtensions/Modifiers/AttributeStyleModifier.swift +++ b/Shared/Extensions/ViewExtensions/Modifiers/AttributeStyleModifier.swift @@ -26,11 +26,11 @@ struct AttributeViewModifier: ViewModifier { .background { Color(UIColor.lightGray) .cornerRadius(2) - .inverseMask( + .inverseMask { content .font(.caption.weight(.semibold)) .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) - ) + } } } else { content diff --git a/Shared/Extensions/ViewExtensions/Modifiers/BlurViewModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/BlurViewModifier.swift deleted file mode 100644 index 62baca00..00000000 --- a/Shared/Extensions/ViewExtensions/Modifiers/BlurViewModifier.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2023 Jellyfin & Jellyfin Contributors -// - -import SwiftUI - -struct BlurViewModifier: ViewModifier { - - let style: UIBlurEffect.Style - - func body(content: Content) -> some View { - content - .overlay { - BlurView(style: style) - } - } -} diff --git a/Shared/Extensions/ViewExtensions/Modifiers/VisibilityModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/VisibilityModifier.swift deleted file mode 100644 index 452c7b92..00000000 --- a/Shared/Extensions/ViewExtensions/Modifiers/VisibilityModifier.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2023 Jellyfin & Jellyfin Contributors -// - -import SwiftUI - -public struct VisibilityModifier: ViewModifier { - - @usableFromInline - let isVisible: Bool - - @usableFromInline - init(isVisible: Bool) { - self.isVisible = isVisible - } - - @inlinable - public func body(content: Content) -> some View { - content.opacity(isVisible ? 1 : 0) - } -} diff --git a/Shared/Extensions/ViewExtensions/ViewExtensions.swift b/Shared/Extensions/ViewExtensions/ViewExtensions.swift index 53ba418d..397b6c21 100644 --- a/Shared/Extensions/ViewExtensions/ViewExtensions.swift +++ b/Shared/Extensions/ViewExtensions/ViewExtensions.swift @@ -19,14 +19,14 @@ extension View { AnyView(self) } - func inverseMask(_ mask: M) -> some View { - // exchange foreground and background - let inversed = mask - .foregroundColor(.black) // hide foreground - .background(Color.white) // let the background stand out - .compositingGroup() - .luminanceToAlpha() - return self.mask(inversed) + func inverseMask(alignment: Alignment = .center, _ content: @escaping () -> some View) -> some View { + mask(alignment: alignment) { + content() + .foregroundColor(.black) + .background(.white) + .compositingGroup() + .luminanceToAlpha() + } } // From: https://www.avanderlee.com/swiftui/conditional-view-modifier/ @@ -177,11 +177,12 @@ extension View { @inlinable func visible(_ isVisible: Bool) -> some View { opacity(isVisible ? 1 : 0) -// modifier(VisibilityModifier(isVisible: isVisible)) } func blurred(style: UIBlurEffect.Style = .regular) -> some View { - modifier(BlurViewModifier(style: style)) + overlay { + BlurView(style: style) + } } func accentSymbolRendering(accentColor: Color = Defaults[.accentColor]) -> some View { @@ -217,10 +218,4 @@ extension View { .ignoresSafeArea() } } - - func inBasicNavigationCoordinatable() -> BasicNavigationViewCoordinator { - BasicNavigationViewCoordinator { - self - } - } } diff --git a/Shared/Strings/Strings.swift b/Shared/Strings/Strings.swift index c759c1f1..7d7aa800 100644 --- a/Shared/Strings/Strings.swift +++ b/Shared/Strings/Strings.swift @@ -264,6 +264,8 @@ internal enum L10n { internal static let operatingSystem = L10n.tr("Localizable", "operatingSystem", fallback: "Operating System") /// Orange internal static let orange = L10n.tr("Localizable", "orange", fallback: "Orange") + /// Order + internal static let order = L10n.tr("Localizable", "order", fallback: "Order") /// Other internal static let other = L10n.tr("Localizable", "other", fallback: "Other") /// Other User @@ -338,6 +340,8 @@ internal enum L10n { internal static let randomImage = L10n.tr("Localizable", "randomImage", fallback: "Random Image") /// Rated internal static let rated = L10n.tr("Localizable", "rated", fallback: "Rated") + /// Ratings + internal static let ratings = L10n.tr("Localizable", "ratings", fallback: "Ratings") /// Recently Added internal static let recentlyAdded = L10n.tr("Localizable", "recentlyAdded", fallback: "Recently Added") /// Recommended @@ -448,6 +452,8 @@ internal enum L10n { internal static let smaller = L10n.tr("Localizable", "smaller", fallback: "Smaller") /// Smallest internal static let smallest = L10n.tr("Localizable", "smallest", fallback: "Smallest") + /// Sort + internal static let sort = L10n.tr("Localizable", "sort", fallback: "Sort") /// Sort by internal static let sortBy = L10n.tr("Localizable", "sortBy", fallback: "Sort by") /// Source Code diff --git a/Swiftfin tvOS/Components/PosterButton.swift b/Swiftfin tvOS/Components/PosterButton.swift index ab8ec7eb..d8f4808c 100644 --- a/Swiftfin tvOS/Components/PosterButton.swift +++ b/Swiftfin tvOS/Components/PosterButton.swift @@ -10,6 +10,8 @@ import Defaults import JellyfinAPI import SwiftUI +// TODO: if no context menu defined, don't add context menu + struct PosterButton: View { @FocusState diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/1280x768-back.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/1280x768-back.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/1280x768-back.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/1280x768-back.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/512.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/512.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/512.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/512.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/400x240-back.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/400x240-back.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/400x240-back.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/400x240-back.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Webp.net-resizeimage.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Webp.net-resizeimage.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Webp.net-resizeimage.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Webp.net-resizeimage.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/216.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/216.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/216.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/216.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Webp.net-resizeimage-2.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Webp.net-resizeimage-2.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Webp.net-resizeimage-2.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Webp.net-resizeimage-2.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-1.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-1.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-1.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-1.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-2.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-2.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-2.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-2.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf-1.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf-1.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf-1.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf-1.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-1.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-1.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-1.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-1.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-2.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-2.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-2.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-2.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf-1.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf-1.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf-1.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf-1.png diff --git a/Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf.png b/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf.png similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf.png rename to Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf.png diff --git a/Swiftfin tvOS/Assets.xcassets/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json rename to Swiftfin tvOS/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json diff --git a/Swiftfin tvOS/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg b/Swiftfin tvOS/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg similarity index 100% rename from Swiftfin tvOS/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg rename to Swiftfin tvOS/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg diff --git a/Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json new file mode 100644 index 00000000..3d67d001 --- /dev/null +++ b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "tomato.fresh.svg", + "idiom" : "universal" + } + ] +} diff --git a/Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/tomato.fresh.svg b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/tomato.fresh.svg new file mode 100644 index 00000000..d84fcc47 --- /dev/null +++ b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/tomato.fresh.svg @@ -0,0 +1,108 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from tomato.fresh + Typeset at 100 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json new file mode 100644 index 00000000..4539290b --- /dev/null +++ b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "tomato.rotten.svg", + "idiom" : "universal" + } + ] +} diff --git a/Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/tomato.rotten.svg b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/tomato.rotten.svg new file mode 100644 index 00000000..47e742da --- /dev/null +++ b/Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/tomato.rotten.svg @@ -0,0 +1,97 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from tomato.rotten + Typeset at 100 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swiftfin tvOS/Info.plist b/Swiftfin tvOS/Resources/Info.plist similarity index 100% rename from Swiftfin tvOS/Info.plist rename to Swiftfin tvOS/Resources/Info.plist diff --git a/Swiftfin tvOS/Views/ItemOverviewView.swift b/Swiftfin tvOS/Views/ItemOverviewView.swift new file mode 100644 index 00000000..e0c04eea --- /dev/null +++ b/Swiftfin tvOS/Views/ItemOverviewView.swift @@ -0,0 +1,49 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +struct ItemOverviewView: View { + + let item: BaseItemDto + + @ViewBuilder + private var content: some View { + GeometryReader { proxy in + VStack(alignment: .center) { + + Text(item.displayTitle) + .font(.title) + .frame(maxHeight: proxy.size.height * 0.33) + + VStack(alignment: .leading, spacing: 20) { + if let tagline = item.taglines?.first { + Text(tagline) + .fontWeight(.semibold) + .multilineTextAlignment(.leading) + } + + if let overview = item.overview { + Text(overview) + } + } + } + .padding(.horizontal, 100) + } + } + + var body: some View { + ZStack { + BlurView() + + content + } + .ignoresSafeArea() + } +} diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift index fbe17806..19cc25b7 100644 --- a/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift @@ -16,43 +16,38 @@ extension ItemView { var viewModel: ItemViewModel var body: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 0) { L10n.about.text - .font(.title3) + .font(.title2) .fontWeight(.semibold) + .accessibility(addTraits: [.isHeader]) .padding(.leading, 50) ScrollView(.horizontal) { - HStack(spacing: 30) { - ImageView( - viewModel.item.type == .episode ? viewModel.item.seriesImageSource(.primary, maxWidth: 300) : viewModel.item - .imageSource(.primary, maxWidth: 300) - ) - .failure { - InitialFailureView(viewModel.item.title.initials) - } - .posterStyle(type: .portrait, width: 270) + HStack(alignment: .top, spacing: 30) { + PosterButton(item: viewModel.item, type: .portrait) + .content { + EmptyView() + } + .imageOverlay { + EmptyView() + } + .scaleItem(1.35) - InformationCard( - title: viewModel.item.displayTitle, - content: viewModel.item.overview ?? L10n.noOverviewAvailable - ) + OverviewCard(item: viewModel.item) - if let subtitleStreams = viewModel.playButtonItem?.subtitleStreams, !subtitleStreams.isEmpty { - InformationCard( - title: L10n.subtitles, - content: subtitleStreams.compactMap(\.displayTitle).joined(separator: ", ") - ) + if let mediaSources = viewModel.item.mediaSources { + ForEach(mediaSources) { source in + MediaSourcesCard(subtitle: mediaSources.count > 1 ? source.displayTitle : nil, source: source) + } } - if let audioStreams = viewModel.playButtonItem?.audioStreams, !audioStreams.isEmpty { - InformationCard(title: L10n.audio, content: audioStreams.compactMap(\.displayTitle).joined(separator: ", ")) + if viewModel.item.hasRatings { + RatingsCard(item: viewModel.item) } } - .padding(.horizontal, 50) - .padding(.top) - .padding(.bottom, 100) + .padding(50) } } .focusSection() diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutViewCard.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutViewCard.swift deleted file mode 100644 index f3f22055..00000000 --- a/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutViewCard.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2023 Jellyfin & Jellyfin Contributors -// - -import SwiftUI - -extension ItemView.AboutView { - - struct InformationCard: View { - - @State - private var presentingAlert: Bool = false - - let title: String - let content: String - - var body: some View { - Button { - presentingAlert = true - } label: { - VStack(alignment: .leading) { - title.text - .font(.title3) - .fontWeight(.semibold) - .lineLimit(2) - - Spacer() - .frame(maxWidth: .infinity) - - TruncatedTextView(text: content) - .font(.subheadline) - .lineLimit(4) - } - .padding2() - .frame(width: 700, height: 405) - } - .buttonStyle(.card) - .alert(title, isPresented: $presentingAlert) { - Button { - presentingAlert = false - } label: { - L10n.close.text - } - } message: { - Text(content) - } - } - } -} diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/AboutViewCard.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/AboutViewCard.swift new file mode 100644 index 00000000..eff98115 --- /dev/null +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/AboutViewCard.swift @@ -0,0 +1,67 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +extension ItemView.AboutView { + + struct Card: View { + + private var content: () -> any View + private var onSelect: () -> Void + private let title: String + private let subtitle: String? + + var body: some View { + Button { + onSelect() + } label: { + VStack(alignment: .leading) { + Text(title) + .font(.title3) + .fontWeight(.semibold) + .lineLimit(2) + + if let subtitle { + Text(subtitle) + .font(.subheadline) + } + + Spacer() + .frame(maxWidth: .infinity) + + content() + .eraseToAnyView() + } + .padding2() + .frame(width: 700, height: 405) + } + .buttonStyle(.card) + } + } +} + +extension ItemView.AboutView.Card { + + init(title: String, subtitle: String? = nil) { + self.init( + content: { EmptyView() }, + onSelect: {}, + title: title, + subtitle: subtitle + ) + } + + func content(@ViewBuilder _ content: @escaping () -> any View) -> Self { + copy(modifying: \.content, with: content) + } + + func onSelect(_ action: @escaping () -> Void) -> Self { + copy(modifying: \.onSelect, with: action) + } +} diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/MediaSourcesCard.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/MediaSourcesCard.swift new file mode 100644 index 00000000..48c8e9a3 --- /dev/null +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/MediaSourcesCard.swift @@ -0,0 +1,42 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct MediaSourcesCard: View { + + @EnvironmentObject + private var router: ItemCoordinator.Router + + let subtitle: String? + let source: MediaSourceInfo + + var body: some View { + Card(title: L10n.media, subtitle: subtitle) + .content { + if let mediaStreams = source.mediaStreams { + VStack(alignment: .leading) { + Text(mediaStreams.compactMap(\.displayTitle).prefix(4).joined(separator: "\n")) + .font(.footnote) + + if mediaStreams.count > 4 { + L10n.seeMore.text + .font(.footnote) + } + } + } + } + .onSelect { + router.route(to: \.mediaSourceInfo, source) + } + } + } +} diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/OverviewCard.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/OverviewCard.swift new file mode 100644 index 00000000..a01d5b89 --- /dev/null +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/OverviewCard.swift @@ -0,0 +1,33 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct OverviewCard: View { + + @EnvironmentObject + private var router: ItemCoordinator.Router + + let item: BaseItemDto + + var body: some View { + Card(title: item.displayTitle) + .content { + TruncatedText(item.overview ?? L10n.noOverviewAvailable) + .font(.subheadline) + .lineLimit(4) + } + .onSelect { + router.route(to: \.itemOverview, item) + } + } + } +} diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/RatingsCard.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/RatingsCard.swift new file mode 100644 index 00000000..59393de7 --- /dev/null +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/RatingsCard.swift @@ -0,0 +1,55 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct RatingsCard: View { + + let item: BaseItemDto + + var body: some View { + Card(title: L10n.ratings) + .content { + HStack(alignment: .bottom) { + if let criticRating = item.criticRating { + VStack { + Group { + if criticRating >= 60 { + Image("tomato.fresh") + } else { + Image("tomato.fresh") + } + } + .symbolRenderingMode(.multicolor) + .foregroundStyle(.green, .red) + .font(.largeTitle) + + Text("\(criticRating, specifier: "%.0f")") + .font(.title3) + } + } + + if let communityRating = item.communityRating { + VStack { + Image(systemName: "star.fill") + .symbolRenderingMode(.multicolor) + .foregroundStyle(.yellow) + .font(.largeTitle) + + Text("\(communityRating, specifier: "%.1f")") + .font(.title3) + } + } + } + } + } + } +} diff --git a/Swiftfin tvOS/Views/ItemView/ScrollViews/CinematicScrollView.swift b/Swiftfin tvOS/Views/ItemView/ScrollViews/CinematicScrollView.swift index 5080173a..2bc33770 100644 --- a/Swiftfin tvOS/Views/ItemView/ScrollViews/CinematicScrollView.swift +++ b/Swiftfin tvOS/Views/ItemView/ScrollViews/CinematicScrollView.swift @@ -81,6 +81,14 @@ extension ItemView { } .padding(.bottom) + if let tagline = viewModel.item.taglines?.first { + Text(tagline) + .font(.subheadline) + .fontWeight(.semibold) + .multilineTextAlignment(.leading) + .lineLimit(1) + } + Text(viewModel.item.overview ?? L10n.noOverviewAvailable) .font(.subheadline) .lineLimit(3) diff --git a/Swiftfin tvOS/Views/MediaSourceInfoView.swift b/Swiftfin tvOS/Views/MediaSourceInfoView.swift new file mode 100644 index 00000000..a2f1fd39 --- /dev/null +++ b/Swiftfin tvOS/Views/MediaSourceInfoView.swift @@ -0,0 +1,127 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +struct MediaSourceInfoView: View { + + @FocusState + private var selectedMediaStream: MediaStream? + + @State + private var lastSelectedMediaStream: MediaStream? + + let source: MediaSourceInfo + + @ViewBuilder + private var content: some View { + GeometryReader { proxy in + VStack(alignment: .center) { + + Text(source.displayTitle) + .font(.title) + .frame(maxHeight: proxy.size.height * 0.33) + + HStack { + Form { + if let videoStreams = source.videoStreams, + !videoStreams.isEmpty + { + Section(L10n.video) { + ForEach(videoStreams, id: \.self) { stream in + Button { + Text(stream.displayTitle ?? .emptyDash) + } + .focused($selectedMediaStream, equals: stream) + } + } + } + + if let audioStreams = source.audioStreams, + !audioStreams.isEmpty + { + Section(L10n.audio) { + ForEach(audioStreams, id: \.self) { stream in + Button { + Text(stream.displayTitle ?? .emptyDash) + } + .focused($selectedMediaStream, equals: stream) + } + } + } + + if let subtitleStreams = source.subtitleStreams, + !subtitleStreams.isEmpty + { + Section(L10n.subtitle) { + ForEach(subtitleStreams, id: \.self) { stream in + Button { + Text(stream.displayTitle ?? .emptyDash) + } + .focused($selectedMediaStream, equals: stream) + } + } + } + } + + Form { + if let lastSelectedMediaStream { + Section { + ForEach(lastSelectedMediaStream.metadataProperties) { property in + Button { + TextPairView(property) + } + } + } + + if !lastSelectedMediaStream.colorProperties.isEmpty { + Section(L10n.color) { + ForEach(lastSelectedMediaStream.colorProperties) { property in + Button { + TextPairView(property) + } + } + } + } + + if !lastSelectedMediaStream.deliveryProperties.isEmpty { + Section(L10n.delivery) { + ForEach(lastSelectedMediaStream.deliveryProperties) { property in + Button { + TextPairView(property) + } + } + } + } + } else { + Button { + L10n.none.text + } + } + } + } + .padding2(.horizontal) + } + .frame(maxWidth: .infinity) + } + .onChange(of: selectedMediaStream) { newValue in + guard let newValue else { return } + lastSelectedMediaStream = newValue + } + } + + var body: some View { + ZStack { + BlurView() + + content + } + .ignoresSafeArea() + } +} diff --git a/Swiftfin tvOS/Views/ServerListView.swift b/Swiftfin tvOS/Views/ServerListView.swift index 02198513..7018e682 100644 --- a/Swiftfin tvOS/Views/ServerListView.swift +++ b/Swiftfin tvOS/Views/ServerListView.swift @@ -72,35 +72,63 @@ struct ServerListView: View { } var body: some View { - innerBody - .navigationTitle(L10n.servers) - .if(!viewModel.servers.isEmpty) { view in - view.toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - SFSymbolButton(systemName: "plus.circle.fill") - .onSelect { - router.route(to: \.connectToServer) - } + SplitFormWindowView() + .descriptionView { + VStack { + Image("jellyfin-blob-blue") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 400) + + Button { + router.route(to: \.connectToServer) + } label: { + Button { + router.route(to: \.connectToServer) + } label: { + L10n.connect.text + .bold() + .font(.callout) + .frame(width: 400, height: 75) + .background(Color.jellyfinPurple) + } + .buttonStyle(.card) } } } -// .toolbar { -// ToolbarItem(placement: .navigationBarLeading) { -// SFSymbolButton(systemName: "gearshape.fill") -// .onSelect { -// router.route(to: \.basicAppSettings) -// } + .contentView {} + } + +// var body: some View { +// innerBody +// .navigationTitle(L10n.servers) +// .if(!viewModel.servers.isEmpty) { view in +// view.toolbar { +// ToolbarItem(placement: .navigationBarTrailing) { +// SFSymbolButton(systemName: "plus.circle.fill") +// .onSelect { +// router.route(to: \.connectToServer) +// } +// } // } // } - .alert(item: $longPressedServer) { server in - Alert( - title: Text(server.name), - primaryButton: .destructive(L10n.remove.text, action: { viewModel.remove(server: server) }), - secondaryButton: .cancel() - ) - } - .onAppear { - viewModel.fetchServers() - } - } + //// .toolbar { + //// ToolbarItem(placement: .navigationBarLeading) { + //// SFSymbolButton(systemName: "gearshape.fill") + //// .onSelect { + //// router.route(to: \.basicAppSettings) + //// } + //// } + //// } +// .alert(item: $longPressedServer) { server in +// Alert( +// title: Text(server.name), +// primaryButton: .destructive(L10n.remove.text, action: { viewModel.remove(server: server) }), +// secondaryButton: .cancel() +// ) +// } +// .onAppear { +// viewModel.fetchServers() +// } +// } } diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/ActionButtons/SubtitleButton.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/ActionButtons/SubtitleButton.swift new file mode 100644 index 00000000..62c225dd --- /dev/null +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/ActionButtons/SubtitleButton.swift @@ -0,0 +1,29 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +extension VideoPlayer.Overlay.ActionButtons { + + struct SubtitleButton: View { + + @EnvironmentObject + private var overlayTimer: TimerProxy + @EnvironmentObject + private var videoPlayerManager: VideoPlayerManager + + var body: some View { + SFSymbolButton(systemName: "captions.bubble") + .onSelect { + videoPlayerManager.selectPreviousViewModel() + overlayTimer.start(5) + } + .frame(maxWidth: 30, maxHeight: 30) + } + } +} diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/BarActionButtons.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/BarActionButtons.swift index 308e91d7..023194a2 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/BarActionButtons.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/BarActionButtons.swift @@ -8,6 +8,8 @@ import SwiftUI +// TODO: add subtitles button + extension VideoPlayer.Overlay { struct BarActionButtons: View { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index c1979396..a1f1d268 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -225,6 +225,10 @@ E12186DE2718F1C50010884C /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E12186DD2718F1C50010884C /* Defaults */; }; E122A9132788EAAD0060FA63 /* MediaStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStream.swift */; }; E122A9142788EAAD0060FA63 /* MediaStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStream.swift */; }; + E12376AE2A33D680001F5B44 /* AboutViewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12376AD2A33D680001F5B44 /* AboutViewCard.swift */; }; + E12376B02A33D6AE001F5B44 /* AboutViewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */; }; + E12376B12A33DB33001F5B44 /* MediaSourceInfoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E170D106294D23BA0017224C /* MediaSourceInfoCoordinator.swift */; }; + E12376B32A33DFAC001F5B44 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */; }; E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; }; E129428528F080B500796AC6 /* OnReceiveNotificationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */; }; E129428628F080B500796AC6 /* OnReceiveNotificationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */; }; @@ -404,6 +408,8 @@ E1575EA3293E7B1E001665B1 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3C727164B1E009D4DAF /* UIDevice.swift */; }; E1575EA6293E7D40001665B1 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1575EA5293E7D40001665B1 /* VideoPlayer.swift */; }; E1581E27291EF59800D6C640 /* SplitContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1581E26291EF59800D6C640 /* SplitContentView.swift */; }; + E158C8D12A31947500C527C5 /* MediaSourceInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158C8D02A31947500C527C5 /* MediaSourceInfoView.swift */; }; + E158C8D32A31967600C527C5 /* ForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158C8D22A31967600C527C5 /* ForEach.swift */; }; E168BD10289A4162001A6922 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD08289A4162001A6922 /* HomeView.swift */; }; E168BD11289A4162001A6922 /* HomeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD09289A4162001A6922 /* HomeContentView.swift */; }; E168BD13289A4162001A6922 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD0D289A4162001A6922 /* ContinueWatchingView.swift */; }; @@ -464,6 +470,10 @@ E18A8E8328D60BC400333B9A /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A8E8228D60BC400333B9A /* VideoPlayer.swift */; }; E18A8E8528D60D0000333B9A /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A8E8428D60D0000333B9A /* VideoPlayerCoordinator.swift */; }; E18ACA8B2A14301800BB4F35 /* ScalingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18ACA8A2A14301800BB4F35 /* ScalingButtonStyle.swift */; }; + E18ACA8D2A14773500BB4F35 /* (null) in Sources */ = {isa = PBXBuildFile; }; + E18ACA8F2A15A2CF00BB4F35 /* (null) in Sources */ = {isa = PBXBuildFile; }; + E18ACA922A15A32F00BB4F35 /* (null) in Sources */ = {isa = PBXBuildFile; }; + E18ACA952A15A3E100BB4F35 /* (null) in Sources */ = {isa = PBXBuildFile; }; E18CE0AF28A222240092E7F1 /* PublicUserSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0AE28A222240092E7F1 /* PublicUserSignInView.swift */; }; E18CE0B228A229E70092E7F1 /* UserDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B128A229E70092E7F1 /* UserDto.swift */; }; E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */; }; @@ -548,7 +558,6 @@ E1A1529028FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528F28FD23D600600579 /* PlaybackSettingsCoordinator.swift */; }; E1A1529128FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528F28FD23D600600579 /* PlaybackSettingsCoordinator.swift */; }; E1A16C9D2889AF1E00EA4679 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A16C9C2889AF1E00EA4679 /* AboutView.swift */; }; - E1A16CA1288A7CFD00EA4679 /* AboutViewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A16CA0288A7CFD00EA4679 /* AboutViewCard.swift */; }; E1A2C154279A7D5A005EC829 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A2C153279A7D5A005EC829 /* UIApplication.swift */; }; E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */; }; E1A42E4C28CBD39300A14DCB /* HomeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A42E4B28CBD39300A14DCB /* HomeContentView.swift */; }; @@ -576,7 +585,6 @@ E1B5F7A929577BCE004B26CF /* PulseLogHandler in Frameworks */ = {isa = PBXBuildFile; productRef = E1B5F7A829577BCE004B26CF /* PulseLogHandler */; }; E1B5F7AB29577BCE004B26CF /* PulseUI in Frameworks */ = {isa = PBXBuildFile; productRef = E1B5F7AA29577BCE004B26CF /* PulseUI */; }; E1B5F7AD29577BDD004B26CF /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E1B5F7AC29577BDD004B26CF /* OrderedCollections */; }; - E1B5F7AE29577CC7004B26CF /* VisibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF3182952641300CC0294 /* VisibilityModifier.swift */; }; E1BA6FC529D25DBD007D98DC /* LandscapeItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BA6FC429D25DBD007D98DC /* LandscapeItemElement.swift */; }; E1BDF2E52951475300CC0294 /* VideoPlayerActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF2E42951475300CC0294 /* VideoPlayerActionButton.swift */; }; E1BDF2E62951475300CC0294 /* VideoPlayerActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF2E42951475300CC0294 /* VideoPlayerActionButton.swift */; }; @@ -590,7 +598,6 @@ E1BDF2F929524FDA00CC0294 /* PlayPreviousItemActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF2F829524FDA00CC0294 /* PlayPreviousItemActionButton.swift */; }; E1BDF2FB2952502300CC0294 /* SubtitleActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF2FA2952502300CC0294 /* SubtitleActionButton.swift */; }; E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF31629525F0400CC0294 /* AdvancedActionButton.swift */; }; - E1BDF3192952641300CC0294 /* VisibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BDF3182952641300CC0294 /* VisibilityModifier.swift */; }; E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */; }; E1C812C5277A90B200918266 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C4277A90B200918266 /* URLComponents.swift */; }; E1C8CE5B28FE512400DF5D7B /* CGPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C8CE5A28FE512400DF5D7B /* CGPoint.swift */; }; @@ -647,9 +654,10 @@ E1DA656A28E78B5900592A73 /* SpecialFeaturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA656828E78B5900592A73 /* SpecialFeaturesViewModel.swift */; }; E1DA656C28E78C1700592A73 /* MenuPosterHStackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA656B28E78C1700592A73 /* MenuPosterHStackModel.swift */; }; E1DA656F28E78C9900592A73 /* SeriesEpisodeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA656E28E78C9900592A73 /* SeriesEpisodeSelector.swift */; }; + E1DABAFA2A270E62008AC34A /* OverviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DABAF92A270E62008AC34A /* OverviewCard.swift */; }; + E1DABAFC2A270EE7008AC34A /* MediaSourcesCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */; }; + E1DABAFE2A27B982008AC34A /* RatingsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DABAFD2A27B982008AC34A /* RatingsCard.swift */; }; E1DC9814296DC06200982F06 /* PulseLogHandler in Frameworks */ = {isa = PBXBuildFile; productRef = E1DC9813296DC06200982F06 /* PulseLogHandler */; }; - E1DC9816296DD0FE00982F06 /* BlurViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC9815296DD0FE00982F06 /* BlurViewModifier.swift */; }; - E1DC9817296DD0FE00982F06 /* BlurViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC9815296DD0FE00982F06 /* BlurViewModifier.swift */; }; E1DC9819296DD1CD00982F06 /* CinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC9818296DD1CD00982F06 /* CinematicBackgroundView.swift */; }; E1DC981A296DD1CD00982F06 /* CinematicBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DC9818296DD1CD00982F06 /* CinematicBackgroundView.swift */; }; E1DC981E296DD91900982F06 /* CollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = E1DC981D296DD91900982F06 /* CollectionView */; }; @@ -690,17 +698,18 @@ E1E6C45129B104850064123F /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E6C44F29B104840064123F /* Button.swift */; }; E1E6C45429B1304E0064123F /* ChaptersActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E6C45229B1304E0064123F /* ChaptersActionButton.swift */; }; E1E6C45629B130F50064123F /* ChapterOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E6C45529B130F50064123F /* ChapterOverlay.swift */; }; + E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E750652A33E9B400B2C1EE /* OverviewCard.swift */; }; + E1E750692A33E9B400B2C1EE /* MediaSourcesCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E750662A33E9B400B2C1EE /* MediaSourcesCard.swift */; }; + E1E7506A2A33E9B400B2C1EE /* RatingsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E750672A33E9B400B2C1EE /* RatingsCard.swift */; }; E1E9017B28DAAE4D001B1594 /* RoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E9017A28DAAE4D001B1594 /* RoundedCorner.swift */; }; E1E9017F28DAB15F001B1594 /* BarActionButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E9017E28DAB15F001B1594 /* BarActionButtons.swift */; }; E1E9EFEA28C6B96500CC1F8B /* ServerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */; }; E1E9EFEB28C7EA2C00CC1F8B /* UserDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B128A229E70092E7F1 /* UserDto.swift */; }; E1EA9F6A28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */; }; E1EA9F6B28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */; }; - E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */; }; - E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; + E1EBCB42278BD174009FE6E9 /* TruncatedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */; }; E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; }; - E1EBCB4A278BE443009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; }; - E1EF473A289A0F610034046B /* TruncatedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */; }; + E1EF473A289A0F610034046B /* TruncatedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */; }; E1EF4C412911B783008CC695 /* StreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EF4C402911B783008CC695 /* StreamType.swift */; }; E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FA891B289A302300176FEB /* iPadOSCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA891A289A302300176FEB /* iPadOSCollectionItemView.swift */; }; @@ -936,6 +945,9 @@ E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemContentView.swift; sourceTree = ""; }; E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = ""; }; E122A9122788EAAD0060FA63 /* MediaStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStream.swift; sourceTree = ""; }; + E12376AD2A33D680001F5B44 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = ""; }; + E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = ""; }; + E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewView.swift; sourceTree = ""; }; E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = ""; }; E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnReceiveNotificationModifier.swift; sourceTree = ""; }; E129428728F0831F00796AC6 /* SplitTimestamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTimestamp.swift; sourceTree = ""; }; @@ -1020,6 +1032,8 @@ E15756352936856700976E1F /* VideoPlayerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerType.swift; sourceTree = ""; }; E1575EA5293E7D40001665B1 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = ""; }; E1581E26291EF59800D6C640 /* SplitContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitContentView.swift; sourceTree = ""; }; + E158C8D02A31947500C527C5 /* MediaSourceInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSourceInfoView.swift; sourceTree = ""; }; + E158C8D22A31967600C527C5 /* ForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForEach.swift; sourceTree = ""; }; E168BD08289A4162001A6922 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; E168BD09289A4162001A6922 /* HomeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeContentView.swift; sourceTree = ""; }; E168BD0D289A4162001A6922 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = ""; }; @@ -1126,7 +1140,6 @@ E1A1528C28FD23AC00600579 /* VideoPlayerSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSettingsCoordinator.swift; sourceTree = ""; }; E1A1528F28FD23D600600579 /* PlaybackSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettingsCoordinator.swift; sourceTree = ""; }; E1A16C9C2889AF1E00EA4679 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; - E1A16CA0288A7CFD00EA4679 /* AboutViewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewCard.swift; sourceTree = ""; }; E1A2C153279A7D5A005EC829 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = ""; }; E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicItemSelector.swift; sourceTree = ""; }; E1A42E4B28CBD39300A14DCB /* HomeContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeContentView.swift; sourceTree = ""; }; @@ -1156,7 +1169,6 @@ E1BDF2F829524FDA00CC0294 /* PlayPreviousItemActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayPreviousItemActionButton.swift; sourceTree = ""; }; E1BDF2FA2952502300CC0294 /* SubtitleActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleActionButton.swift; sourceTree = ""; }; E1BDF31629525F0400CC0294 /* AdvancedActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedActionButton.swift; sourceTree = ""; }; - E1BDF3182952641300CC0294 /* VisibilityModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibilityModifier.swift; sourceTree = ""; }; E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackSpeed.swift; sourceTree = ""; }; E1C812C4277A90B200918266 /* URLComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponents.swift; sourceTree = ""; }; E1C8CE5A28FE512400DF5D7B /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = ""; }; @@ -1209,7 +1221,9 @@ E1DA656828E78B5900592A73 /* SpecialFeaturesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialFeaturesViewModel.swift; sourceTree = ""; }; E1DA656B28E78C1700592A73 /* MenuPosterHStackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuPosterHStackModel.swift; sourceTree = ""; }; E1DA656E28E78C9900592A73 /* SeriesEpisodeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeriesEpisodeSelector.swift; sourceTree = ""; }; - E1DC9815296DD0FE00982F06 /* BlurViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurViewModifier.swift; sourceTree = ""; }; + E1DABAF92A270E62008AC34A /* OverviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewCard.swift; sourceTree = ""; }; + E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSourcesCard.swift; sourceTree = ""; }; + E1DABAFD2A27B982008AC34A /* RatingsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingsCard.swift; sourceTree = ""; }; E1DC9818296DD1CD00982F06 /* CinematicBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicBackgroundView.swift; sourceTree = ""; }; E1DC983C296DEB9B00982F06 /* UnwatchedIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnwatchedIndicator.swift; sourceTree = ""; }; E1DC9840296DEBD800982F06 /* WatchedIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchedIndicator.swift; sourceTree = ""; }; @@ -1238,12 +1252,14 @@ E1E6C44F29B104840064123F /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; E1E6C45229B1304E0064123F /* ChaptersActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaptersActionButton.swift; sourceTree = ""; }; E1E6C45529B130F50064123F /* ChapterOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterOverlay.swift; sourceTree = ""; }; + E1E750652A33E9B400B2C1EE /* OverviewCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverviewCard.swift; sourceTree = ""; }; + E1E750662A33E9B400B2C1EE /* MediaSourcesCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaSourcesCard.swift; sourceTree = ""; }; + E1E750672A33E9B400B2C1EE /* RatingsCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingsCard.swift; sourceTree = ""; }; E1E9017A28DAAE4D001B1594 /* RoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorner.swift; sourceTree = ""; }; E1E9017E28DAB15F001B1594 /* BarActionButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarActionButtons.swift; sourceTree = ""; }; E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerButton.swift; sourceTree = ""; }; E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerManager.swift; sourceTree = ""; }; - E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedTextView.swift; sourceTree = ""; }; - E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewCoordinator.swift; sourceTree = ""; }; + E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedText.swift; sourceTree = ""; }; E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewView.swift; sourceTree = ""; }; E1EF4C402911B783008CC695 /* StreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamType.swift; sourceTree = ""; }; E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = ""; }; @@ -1454,10 +1470,9 @@ isa = PBXGroup; children = ( E12186DF2718F2030010884C /* App */, - 535870662669D21700D05A09 /* Assets.xcassets */, 536D3D77267BB9650004248C /* Components */, - 535870702669D21700D05A09 /* Info.plist */, E185920B28CEF23F00326F80 /* Objects */, + E1DABAD62A26E28E008AC34A /* Resources */, E12186E02718F23B0010884C /* Views */, ); path = "Swiftfin tvOS"; @@ -1467,6 +1482,7 @@ isa = PBXGroup; children = ( E1401CA32938123400E8B599 /* AppIcons */, + E1AD105326D96F5A003E4A08 /* Components */, 62C29E9D26D0FE5900C1D2E7 /* Coordinators */, E1FCD08E26C466F3007C8DCF /* Errors */, 621338912660106C00A81A2A /* Extensions */, @@ -1476,7 +1492,6 @@ E1549654296CA2EF00C4EF88 /* Services */, 6286F09F271C0AA500C40ED5 /* Strings */, 532175392671BCED005491E6 /* ViewModels */, - E1AD105326D96F5A003E4A08 /* Views */, ); path = Shared; sourceTree = ""; @@ -1572,12 +1587,10 @@ children = ( E13DD3BB27163C3E009D4DAF /* App */, 62ECA01926FA6D6900E8EBB7 /* AppURLHandler */, - 5377CBF8263B596B003A4E83 /* Assets.xcassets */, 53F866422687A45400DCD1D7 /* Components */, - 5377CC02263B596B003A4E83 /* Info.plist */, - E1DD1127271E7D15005BE12F /* Objects */, - E13D02842788B634000FCB04 /* Swiftfin.entitlements */, E11CEB85289984F5003E74C7 /* Extensions */, + E1DD1127271E7D15005BE12F /* Objects */, + E1DCDE3B2A2D134000FA9C91 /* Resources */, E13DD3D027165886009D4DAF /* Views */, ); path = Swiftfin; @@ -1811,6 +1824,7 @@ E133328729538D8D00EE76AB /* Files.swift */, E15756312935642A00976E1F /* Float.swift */, E11CEB8C28999B4A003E74C7 /* Font.swift */, + E158C8D22A31967600C527C5 /* ForEach.swift */, E1E6C44A29AED2B70064123F /* HorizontalAlignment.swift */, E139CC1E28EC83E400688DE2 /* Int.swift */, E1AD105226D96D5F003E4A08 /* JellyfinAPI */, @@ -1854,7 +1868,6 @@ 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */, 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */, 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */, - E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */, 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */, C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */, C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */, @@ -2023,11 +2036,13 @@ 53ABFDEA2679753200886593 /* ConnectToServerView.swift */, E154967B296CBB1A00C4EF88 /* FontPickerView.swift */, E1A42E4D28CBD3B200A14DCB /* HomeView */, + E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */, E193D54E271942C000900D82 /* ItemView */, 53A83C32268A309300DF3D92 /* LibraryView.swift */, C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */, C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */, C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */, + E158C8D02A31947500C527C5 /* MediaSourceInfoView.swift */, C4E508172703E8190045C9AB /* MediaView.swift */, E1E1643928BAC2EF00323B0A /* SearchView.swift */, E193D54F2719430400900D82 /* ServerDetailView.swift */, @@ -2102,6 +2117,7 @@ C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */, C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */, C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */, + E170D104294D21FA0017224C /* MediaSourceInfoView.swift */, E19F6C5C28F5189300C5197E /* MediaStreamInfoView.swift */, 6213388F265F83A900A81A2A /* MediaView.swift */, E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */, @@ -2200,11 +2216,9 @@ children = ( E18E0202288749200022598C /* AttributeStyleModifier.swift */, E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */, - E1DC9815296DD0FE00982F06 /* BlurViewModifier.swift */, E19E551E2897326C003CE330 /* BottomEdgeGradientModifier.swift */, E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */, E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */, - E1BDF3182952641300CC0294 /* VisibilityModifier.swift */, ); path = Modifiers; sourceTree = ""; @@ -2268,6 +2282,26 @@ path = Components; sourceTree = ""; }; + E18ACA902A15A2D600BB4F35 /* AboutView */ = { + isa = PBXGroup; + children = ( + E18ACA932A15A3D800BB4F35 /* Components */, + E18E01D5288747230022598C /* AboutView.swift */, + ); + path = AboutView; + sourceTree = ""; + }; + E18ACA932A15A3D800BB4F35 /* Components */ = { + isa = PBXGroup; + children = ( + E12376AD2A33D680001F5B44 /* AboutViewCard.swift */, + E1E750662A33E9B400B2C1EE /* MediaSourcesCard.swift */, + E1E750652A33E9B400B2C1EE /* OverviewCard.swift */, + E1E750672A33E9B400B2C1EE /* RatingsCard.swift */, + ); + path = Components; + sourceTree = ""; + }; E18CE0B028A222310092E7F1 /* Components */ = { isa = PBXGroup; children = ( @@ -2384,13 +2418,12 @@ E18E01D4288747230022598C /* Components */ = { isa = PBXGroup; children = ( - E18E01D5288747230022598C /* AboutView.swift */, + E18ACA902A15A2D600BB4F35 /* AboutView */, E18E01D9288747230022598C /* ActionButtonHStack.swift */, E18E01D7288747230022598C /* AttributeHStack.swift */, E17FB55628C1256400311DFE /* CastAndCrewHStack.swift */, E17AC9722955007A003D2BC2 /* DownloadTaskButton.swift */, E17FB55A28C1266400311DFE /* GenresHStack.swift */, - E170D104294D21FA0017224C /* MediaSourceInfoView.swift */, E1D8424E2932F7C400D1041A /* OverviewView.swift */, E18E01D8288747230022598C /* PlayButton.swift */, E1DA656E28E78C9900592A73 /* SeriesEpisodeSelector.swift */, @@ -2442,8 +2475,8 @@ E1A16CA2288A7D0000EA4679 /* AboutView */ = { isa = PBXGroup; children = ( + E1DABAF82A270B00008AC34A /* Components */, E1A16C9C2889AF1E00EA4679 /* AboutView.swift */, - E1A16CA0288A7CFD00EA4679 /* AboutViewCard.swift */, ); path = AboutView; sourceTree = ""; @@ -2484,7 +2517,7 @@ path = JellyfinAPI; sourceTree = ""; }; - E1AD105326D96F5A003E4A08 /* Views */ = { + E1AD105326D96F5A003E4A08 /* Components */ = { isa = PBXGroup; children = ( E18E0203288749200022598C /* BlurView.swift */, @@ -2498,10 +2531,10 @@ E1E1643D28BB074000323B0A /* SelectorView.swift */, E1356E0129A7309D00382563 /* SeparatorHStack.swift */, E1A1528928FD22F600600579 /* TextPairView.swift */, - E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */, + E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */, E1B5784028F8AFCB00D42911 /* Wrapped View.swift */, ); - path = Views; + path = Components; sourceTree = ""; }; E1BDF2E7295148F400CC0294 /* VideoPlayerSettingsView */ = { @@ -2607,6 +2640,26 @@ path = Slider; sourceTree = ""; }; + E1DABAD62A26E28E008AC34A /* Resources */ = { + isa = PBXGroup; + children = ( + 535870662669D21700D05A09 /* Assets.xcassets */, + 535870702669D21700D05A09 /* Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + E1DABAF82A270B00008AC34A /* Components */ = { + isa = PBXGroup; + children = ( + E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */, + E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */, + E1DABAF92A270E62008AC34A /* OverviewCard.swift */, + E1DABAFD2A27B982008AC34A /* RatingsCard.swift */, + ); + path = Components; + sourceTree = ""; + }; E1DC983F296DEBA500982F06 /* PosterIndicators */ = { isa = PBXGroup; children = ( @@ -2618,6 +2671,16 @@ path = PosterIndicators; sourceTree = ""; }; + E1DCDE3B2A2D134000FA9C91 /* Resources */ = { + isa = PBXGroup; + children = ( + 5377CBF8263B596B003A4E83 /* Assets.xcassets */, + 5377CC02263B596B003A4E83 /* Info.plist */, + E13D02842788B634000FCB04 /* Swiftfin.entitlements */, + ); + path = Resources; + sourceTree = ""; + }; E1DD1127271E7D15005BE12F /* Objects */ = { isa = PBXGroup; children = ( @@ -2969,7 +3032,6 @@ E1575E99293E7B1E001665B1 /* UIColor.swift in Sources */, E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */, E1575E96293E7B1E001665B1 /* UIScrollView.swift in Sources */, - E1A16CA1288A7CFD00EA4679 /* AboutViewCard.swift in Sources */, E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */, E1575E6F293E77B5001665B1 /* GestureAction.swift in Sources */, E18A8E7E28D606BE00333B9A /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, @@ -2994,7 +3056,6 @@ E1575E6A293E77B5001665B1 /* RoundedCorner.swift in Sources */, E1575EA1293E7B1E001665B1 /* String.swift in Sources */, E1E6C45429B1304E0064123F /* ChaptersActionButton.swift in Sources */, - E1EBCB4A278BE443009FE6E9 /* ItemOverviewCoordinator.swift in Sources */, E1E6C44229AECCD50064123F /* ActionButtons.swift in Sources */, E1575E78293E77B5001665B1 /* TrailingTimestampType.swift in Sources */, C4BE07772725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */, @@ -3021,12 +3082,15 @@ E12CC1C528D12D9B00678D5D /* SeeAllPosterButton.swift in Sources */, E18A8E8128D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift in Sources */, E1002B652793CEE800E47059 /* ChapterInfo.swift in Sources */, + E12376B32A33DFAC001F5B44 /* ItemOverviewView.swift in Sources */, E111D8FA28D0400900400001 /* PagingLibraryView.swift in Sources */, E1D3043328D175CE00587289 /* StaticLibraryViewModel.swift in Sources */, E1EA9F6B28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */, E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */, E1549661296CA2EF00C4EF88 /* SwiftfinDefaults.swift in Sources */, + E158C8D12A31947500C527C5 /* MediaSourceInfoView.swift in Sources */, E1575E98293E7B1E001665B1 /* UIApplication.swift in Sources */, + E12376B12A33DB33001F5B44 /* MediaSourceInfoCoordinator.swift in Sources */, E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */, E13DD3ED27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, E1C9261C288756BD002A7A66 /* PosterHStack.swift in Sources */, @@ -3044,7 +3108,7 @@ E11895AA289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */, E1575E76293E77B5001665B1 /* VideoPlayerType.swift in Sources */, E17AC96B2954D00E003D2BC2 /* URLResponse.swift in Sources */, - E1EF473A289A0F610034046B /* TruncatedTextView.swift in Sources */, + E1EF473A289A0F610034046B /* TruncatedText.swift in Sources */, E1C926112887565C002A7A66 /* ActionButtonHStack.swift in Sources */, E178859B2780F1F40094FBCF /* tvOSSlider.swift in Sources */, E18E02252887492B0022598C /* PlainNavigationLinkButton.swift in Sources */, @@ -3070,7 +3134,7 @@ E1575E9C293E7B1E001665B1 /* Collection.swift in Sources */, E1C9260F2887565C002A7A66 /* AttributeHStack.swift in Sources */, E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */, - E1B5F7AE29577CC7004B26CF /* VisibilityModifier.swift in Sources */, + E12376B02A33D6AE001F5B44 /* AboutViewCard.swift in Sources */, E12A9EF929499E0100731C3A /* JellyfinClient.swift in Sources */, E148128328C1443D003B8787 /* NameGuidPair.swift in Sources */, E185920828CDAAA200326F80 /* SimilarItemsHStack.swift in Sources */, @@ -3101,6 +3165,7 @@ E18A17F0298C68B700C22F62 /* Overlay.swift in Sources */, E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */, E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */, + E1DABAFC2A270EE7008AC34A /* MediaSourcesCard.swift in Sources */, E193D53D27193F9700900D82 /* UserSignInCoordinator.swift in Sources */, C4BE07862728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */, E1AD104E26D96CE3003E4A08 /* BaseItemDto.swift in Sources */, @@ -3108,7 +3173,6 @@ 62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */, E1575EA2293E7B1E001665B1 /* Color.swift in Sources */, E1575E77293E77B5001665B1 /* MenuPosterHStackModel.swift in Sources */, - E1DC9817296DD0FE00982F06 /* BlurViewModifier.swift in Sources */, E12E30F5296392EC0022FAC9 /* EnumPickerView.swift in Sources */, E1575E72293E77B5001665B1 /* Utilities.swift in Sources */, E1575E84293E7A00001665B1 /* PrimaryAppIcon.swift in Sources */, @@ -3123,6 +3187,7 @@ E1575E88293E7A00001665B1 /* LightAppIcon.swift in Sources */, E1549678296CB22B00C4EF88 /* InlineEnumToggle.swift in Sources */, E193D5432719407E00900D82 /* tvOSMainCoordinator.swift in Sources */, + E1DABAFA2A270E62008AC34A /* OverviewCard.swift in Sources */, E11CEB8928998549003E74C7 /* BottomEdgeGradientModifier.swift in Sources */, E129428628F080B500796AC6 /* OnReceiveNotificationModifier.swift in Sources */, 53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */, @@ -3133,6 +3198,7 @@ E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */, E18A17F2298C68BB00C22F62 /* MainOverlay.swift in Sources */, E1E6C44B29AED2B70064123F /* HorizontalAlignment.swift in Sources */, + E158C8D32A31967600C527C5 /* ForEach.swift in Sources */, E193D549271941CC00900D82 /* UserSignInView.swift in Sources */, 53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */, E148128628C15475003B8787 /* APISortOrder.swift in Sources */, @@ -3216,8 +3282,10 @@ E174121029AE9D94003EF3B5 /* NavigationCoordinatable.swift in Sources */, E154965F296CA2EF00C4EF88 /* DownloadTask.swift in Sources */, E154967E296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift in Sources */, + E1DABAFE2A27B982008AC34A /* RatingsCard.swift in Sources */, E1C9261B288756BD002A7A66 /* DotHStack.swift in Sources */, E104C873296E0D0A00C1C3F9 /* IndicatorSettingsView.swift in Sources */, + E18ACA8D2A14773500BB4F35 /* (null) in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3245,7 +3313,6 @@ E1E9017F28DAB15F001B1594 /* BarActionButtons.swift in Sources */, E17FB55928C125E900311DFE /* StudiosHStack.swift in Sources */, E1C812C5277A90B200918266 /* URLComponents.swift in Sources */, - E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */, E1C925F428875037002A7A66 /* ItemViewType.swift in Sources */, 62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, @@ -3323,7 +3390,7 @@ E104C870296E087200C1C3F9 /* IndicatorSettingsView.swift in Sources */, E12A9EF829499E0100731C3A /* JellyfinClient.swift in Sources */, E1722DB129491C3900CC0239 /* ImageBlurHashes.swift in Sources */, - E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */, + E1EBCB42278BD174009FE6E9 /* TruncatedText.swift in Sources */, 62133890265F83A900A81A2A /* MediaView.swift in Sources */, 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */, E13332942953BAA100EE76AB /* DownloadTaskContentView.swift in Sources */, @@ -3336,6 +3403,7 @@ E18E01DA288747230022598C /* iPadOSEpisodeContentView.swift in Sources */, E1047E2327E5880000CB0D4A /* InitialFailureView.swift in Sources */, E1C8CE5B28FE512400DF5D7B /* CGPoint.swift in Sources */, + E18ACA922A15A32F00BB4F35 /* (null) in Sources */, E1E1E24D28DF8A2E000DF5FD /* PreferenceKeys.swift in Sources */, E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */, E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */, @@ -3359,7 +3427,6 @@ 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */, 62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, E113133428BE988200930F75 /* FilterDrawerHStack.swift in Sources */, - E1BDF3192952641300CC0294 /* VisibilityModifier.swift in Sources */, 5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */, E129428528F080B500796AC6 /* OnReceiveNotificationModifier.swift in Sources */, E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, @@ -3370,7 +3437,6 @@ E139CC1F28EC83E400688DE2 /* Int.swift in Sources */, E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */, E1DA656C28E78C1700592A73 /* MenuPosterHStackModel.swift in Sources */, - E1DC9816296DD0FE00982F06 /* BlurViewModifier.swift in Sources */, E14A08CB28E6831D004FC984 /* VideoPlayerViewModel.swift in Sources */, E1DC9847296DEFF500982F06 /* FavoriteIndicator.swift in Sources */, E1E306CD28EF6E8000537998 /* TimerProxy.swift in Sources */, @@ -3451,9 +3517,11 @@ C4BE07762725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */, E13DD3E927177ED6009D4DAF /* ServerListCoordinator.swift in Sources */, E1CCF12E28ABF989006CAC9E /* PosterType.swift in Sources */, + E1E7506A2A33E9B400B2C1EE /* RatingsCard.swift in Sources */, E1D842912933F87500D1041A /* ItemFields.swift in Sources */, E1BDF2F729524ECD00CC0294 /* PlaybackSpeedActionButton.swift in Sources */, E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */, + E1E750692A33E9B400B2C1EE /* MediaSourcesCard.swift in Sources */, E18295E429CAC6F100F91ED0 /* BasicNavigationCoordinator.swift in Sources */, C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */, E129429328F2845000796AC6 /* SliderType.swift in Sources */, @@ -3471,11 +3539,13 @@ E1401CA92938140700E8B599 /* DarkAppIcon.swift in Sources */, E1A1529028FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */, E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */, + E12376AE2A33D680001F5B44 /* AboutViewCard.swift in Sources */, E1A2C154279A7D5A005EC829 /* UIApplication.swift in Sources */, E1D8428F2933F2D900D1041A /* MediaSourceInfo.swift in Sources */, E1BDF2EC2952290200CC0294 /* AspectFillActionButton.swift in Sources */, E1BDF2F529524E6400CC0294 /* PlayNextItemActionButton.swift in Sources */, E18E01DD288747230022598C /* iPadOSSeriesItemContentView.swift in Sources */, + E18ACA952A15A3E100BB4F35 /* (null) in Sources */, C4E5598928124C10003DECA5 /* LiveTVChannelItemElement.swift in Sources */, E1D5C39B28DF993400CDBEFB /* ThumbSlider.swift in Sources */, E1DC983D296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */, @@ -3493,6 +3563,7 @@ E17FB55728C1256400311DFE /* CastAndCrewHStack.swift in Sources */, 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, E113133828BEADBA00930F75 /* LibraryParent.swift in Sources */, + E18ACA8F2A15A2CF00BB4F35 /* (null) in Sources */, E1401CA72938140300E8B599 /* PrimaryAppIcon.swift in Sources */, E1937A3E288F0D3D00CB80AA /* UIScreen.swift in Sources */, C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */, @@ -3504,6 +3575,7 @@ E187A60229AB28F0008387E6 /* RotateContentView.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */, + E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */, 5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */, E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */, E13DD3C827164B1E009D4DAF /* UIDevice.swift in Sources */, @@ -3703,7 +3775,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; + INFOPLIST_FILE = "Swiftfin tvOS/Resources/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3732,7 +3804,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; + INFOPLIST_FILE = "Swiftfin tvOS/Resources/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3875,7 +3947,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-primary-primary"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; - CODE_SIGN_ENTITLEMENTS = Swiftfin/Swiftfin.entitlements; + CODE_SIGN_ENTITLEMENTS = Swiftfin/Resources/Swiftfin.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 78; @@ -3885,7 +3957,7 @@ ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = Swiftfin/Info.plist; + INFOPLIST_FILE = Swiftfin/Resources/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Swiftfin; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -3912,7 +3984,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-primary-primary"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; - CODE_SIGN_ENTITLEMENTS = Swiftfin/Swiftfin.entitlements; + CODE_SIGN_ENTITLEMENTS = Swiftfin/Resources/Swiftfin.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 78; @@ -3923,7 +3995,7 @@ ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = Swiftfin/Info.plist; + INFOPLIST_FILE = Swiftfin/Resources/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Swiftfin; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; diff --git a/Swiftfin/Components/FilterDrawerHStack/FilterDrawerButton.swift b/Swiftfin/Components/FilterDrawerHStack/FilterDrawerButton.swift index 724717d1..9574aa1f 100644 --- a/Swiftfin/Components/FilterDrawerHStack/FilterDrawerButton.swift +++ b/Swiftfin/Components/FilterDrawerHStack/FilterDrawerButton.swift @@ -68,6 +68,7 @@ extension FilterDrawerHStack { } extension FilterDrawerHStack.FilterDrawerButton { + init(title: String, activated: Bool) { self.init( systemName: nil, diff --git a/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift b/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift index b2a626e8..7810263b 100644 --- a/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift +++ b/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift @@ -12,7 +12,8 @@ import SwiftUI struct FilterDrawerHStack: View { @ObservedObject - var viewModel: FilterViewModel + private var viewModel: FilterViewModel + private var onSelect: (FilterCoordinator.Parameters) -> Void var body: some View { @@ -49,22 +50,20 @@ struct FilterDrawerHStack: View { )) } - // TODO: Localize - FilterDrawerButton(title: "Order", activated: viewModel.currentFilters.sortOrder != [APISortOrder.ascending.filter]) + FilterDrawerButton(title: L10n.order, activated: viewModel.currentFilters.sortOrder != [APISortOrder.ascending.filter]) .onSelect { onSelect(.init( - title: "Order", + title: L10n.order, viewModel: viewModel, filter: \.sortOrder, selectorType: .single )) } - // TODO: Localize - FilterDrawerButton(title: "Sort", activated: viewModel.currentFilters.sortBy != [SortBy.name.filter]) + FilterDrawerButton(title: L10n.sort, activated: viewModel.currentFilters.sortBy != [SortBy.name.filter]) .onSelect { onSelect(.init( - title: "Sort", + title: L10n.sort, viewModel: viewModel, filter: \.sortBy, selectorType: .single @@ -75,9 +74,12 @@ struct FilterDrawerHStack: View { } extension FilterDrawerHStack { + init(viewModel: FilterViewModel) { - self.viewModel = viewModel - self.onSelect = { _ in } + self.init( + viewModel: viewModel, + onSelect: { _ in } + ) } func onSelect(_ action: @escaping (FilterCoordinator.Parameters) -> Void) -> Self { diff --git a/Swiftfin/Components/LibraryItemRow.swift b/Swiftfin/Components/LibraryItemRow.swift index f828f67d..58aa1145 100644 --- a/Swiftfin/Components/LibraryItemRow.swift +++ b/Swiftfin/Components/LibraryItemRow.swift @@ -61,9 +61,12 @@ struct LibraryItemRow: View { } extension LibraryItemRow { + init(item: BaseItemDto) { - self.item = item - self.onSelect = {} + self.init( + item: item, + onSelect: {} + ) } func onSelect(_ action: @escaping () -> Void) -> Self { diff --git a/Swiftfin/Components/PrimaryButton.swift b/Swiftfin/Components/PrimaryButton.swift index 75107ff5..02264571 100644 --- a/Swiftfin/Components/PrimaryButton.swift +++ b/Swiftfin/Components/PrimaryButton.swift @@ -15,16 +15,11 @@ struct PrimaryButton: View { private var accentColor private let title: String - private let action: () -> Void - - init(title: String, _ action: @escaping () -> Void) { - self.title = title - self.action = action - } + private var onSelect: () -> Void var body: some View { Button { - action() + onSelect() } label: { ZStack { Rectangle() @@ -40,3 +35,17 @@ struct PrimaryButton: View { } } } + +extension PrimaryButton { + + init(title: String) { + self.init( + title: title, + onSelect: {} + ) + } + + func onSelect(_ action: @escaping () -> Void) -> Self { + copy(modifying: \.onSelect, with: action) + } +} diff --git a/Swiftfin/Assets.xcassets/AppIcons/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-blue.appiconset/AppIcon-dark-blue.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/AppIcon-dark-blue.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-blue.appiconset/AppIcon-dark-blue.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/AppIcon-dark-blue.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-blue.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-blue.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-green.appiconset/AppIcon-dark-green.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/AppIcon-dark-green.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-green.appiconset/AppIcon-dark-green.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/AppIcon-dark-green.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-green.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-green.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-jellyfin.appiconset/AppIcon-dark-jellyfin.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/AppIcon-dark-jellyfin.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-jellyfin.appiconset/AppIcon-dark-jellyfin.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/AppIcon-dark-jellyfin.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-jellyfin.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-jellyfin.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-orange.appiconset/AppIcon-dark-orange.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/AppIcon-dark-orange.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-orange.appiconset/AppIcon-dark-orange.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/AppIcon-dark-orange.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-orange.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-orange.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-red.appiconset/AppIcon-dark-red.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/AppIcon-dark-red.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-red.appiconset/AppIcon-dark-red.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/AppIcon-dark-red.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-red.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-red.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-yellow.appiconset/AppIcon-dark-yellow.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/AppIcon-dark-yellow.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-yellow.appiconset/AppIcon-dark-yellow.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/AppIcon-dark-yellow.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-yellow.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/AppIcon-dark-yellow.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/blue.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/blue.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/blue.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/blue.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/green.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/green.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/green.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/green.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/jellyfin.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/jellyfin.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/jellyfin.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/jellyfin.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/orange.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/orange.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/orange.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/orange.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/red.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/red.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/red.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/red.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/yellow.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/yellow.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/yellow.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/yellow.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/blue.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/blue.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/blue.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/blue.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/green.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/green.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/green.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/green.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/jellyfin.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/jellyfin.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/jellyfin.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/jellyfin.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/orange.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/orange.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/orange.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/orange.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/red.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/red.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/red.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/red.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/yellow.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/yellow.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/yellow.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/yellow.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/AppIcon-light-blue.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/AppIcon-light-blue.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/AppIcon-light-blue.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/AppIcon-light-blue.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/AppIcon-light-green.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/AppIcon-light-green.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/AppIcon-light-green.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/AppIcon-light-green.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/AppIcon-light-jellyfin.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/AppIcon-light-jellyfin.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/AppIcon-light-jellyfin.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/AppIcon-light-jellyfin.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/AppIcon-light-orange.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/AppIcon-light-orange.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/AppIcon-light-orange.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/AppIcon-light-orange.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/AppIcon-light-red.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/AppIcon-light-red.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/AppIcon-light-red.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/AppIcon-light-red.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/AppIcon-light-yellow.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/AppIcon-light-yellow.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/AppIcon-light-yellow.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/AppIcon-light-yellow.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Primary/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Primary/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Light/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/AppIcon-primary-primary.png b/Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/AppIcon-primary-primary.png similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/AppIcon-primary-primary.png rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/AppIcon-primary-primary.png diff --git a/Swiftfin/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/Contents.json diff --git a/Swiftfin/Assets.xcassets/AppIcons/dark/Contents.json b/Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/AppIcons/dark/Contents.json rename to Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/Contents.json diff --git a/Swiftfin/Assets.xcassets/Contents.json b/Swiftfin/Resources/Assets.xcassets/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/Contents.json rename to Swiftfin/Resources/Assets.xcassets/Contents.json diff --git a/Swiftfin/Assets.xcassets/ShadowColor.colorset/Contents.json b/Swiftfin/Resources/Assets.xcassets/ShadowColor.colorset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/ShadowColor.colorset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/ShadowColor.colorset/Contents.json diff --git a/Swiftfin/Assets.xcassets/TextHighlightColor.colorset/Contents.json b/Swiftfin/Resources/Assets.xcassets/TextHighlightColor.colorset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/TextHighlightColor.colorset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/TextHighlightColor.colorset/Contents.json diff --git a/Swiftfin/Assets.xcassets/git.commit.symbolset/Contents.json b/Swiftfin/Resources/Assets.xcassets/git.commit.symbolset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/git.commit.symbolset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/git.commit.symbolset/Contents.json diff --git a/Swiftfin/Assets.xcassets/git.commit.symbolset/git.commit.svg b/Swiftfin/Resources/Assets.xcassets/git.commit.symbolset/git.commit.svg similarity index 100% rename from Swiftfin/Assets.xcassets/git.commit.symbolset/git.commit.svg rename to Swiftfin/Resources/Assets.xcassets/git.commit.symbolset/git.commit.svg diff --git a/Swiftfin/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json b/Swiftfin/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json diff --git a/Swiftfin/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg b/Swiftfin/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg similarity index 100% rename from Swiftfin/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg rename to Swiftfin/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/jellyfin-blob.svg diff --git a/Swiftfin/Assets.xcassets/logo.github.symbolset/Contents.json b/Swiftfin/Resources/Assets.xcassets/logo.github.symbolset/Contents.json similarity index 100% rename from Swiftfin/Assets.xcassets/logo.github.symbolset/Contents.json rename to Swiftfin/Resources/Assets.xcassets/logo.github.symbolset/Contents.json diff --git a/Swiftfin/Assets.xcassets/logo.github.symbolset/logo.github.svg b/Swiftfin/Resources/Assets.xcassets/logo.github.symbolset/logo.github.svg similarity index 100% rename from Swiftfin/Assets.xcassets/logo.github.symbolset/logo.github.svg rename to Swiftfin/Resources/Assets.xcassets/logo.github.symbolset/logo.github.svg diff --git a/Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json b/Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json new file mode 100644 index 00000000..3d67d001 --- /dev/null +++ b/Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "tomato.fresh.svg", + "idiom" : "universal" + } + ] +} diff --git a/Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/tomato.fresh.svg b/Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/tomato.fresh.svg new file mode 100644 index 00000000..d84fcc47 --- /dev/null +++ b/Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/tomato.fresh.svg @@ -0,0 +1,108 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from tomato.fresh + Typeset at 100 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json b/Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json new file mode 100644 index 00000000..4539290b --- /dev/null +++ b/Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "tomato.rotten.svg", + "idiom" : "universal" + } + ] +} diff --git a/Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/tomato.rotten.svg b/Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/tomato.rotten.svg new file mode 100644 index 00000000..47e742da --- /dev/null +++ b/Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/tomato.rotten.svg @@ -0,0 +1,97 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.4.0 + Requires Xcode 14 or greater + Generated from tomato.rotten + Typeset at 100 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swiftfin/Info.plist b/Swiftfin/Resources/Info.plist similarity index 100% rename from Swiftfin/Info.plist rename to Swiftfin/Resources/Info.plist diff --git a/Swiftfin/Swiftfin.entitlements b/Swiftfin/Resources/Swiftfin.entitlements similarity index 100% rename from Swiftfin/Swiftfin.entitlements rename to Swiftfin/Resources/Swiftfin.entitlements diff --git a/Swiftfin/Views/DownloadTaskView/DownloadTaskContentView.swift b/Swiftfin/Views/DownloadTaskView/DownloadTaskContentView.swift index 6ec864aa..8b02bb82 100644 --- a/Swiftfin/Views/DownloadTaskView/DownloadTaskContentView.swift +++ b/Swiftfin/Views/DownloadTaskView/DownloadTaskContentView.swift @@ -48,11 +48,12 @@ extension DownloadTaskView { // TODO: Break into subview switch downloadTask.state { case .ready, .cancelled: - PrimaryButton(title: "Download") { - downloadManager.download(task: downloadTask) - } - .frame(maxWidth: 300) - .frame(height: 50) + PrimaryButton(title: "Download") + .onSelect { + downloadManager.download(task: downloadTask) + } + .frame(maxWidth: 300) + .frame(height: 50) case let .downloading(progress): HStack { CircularProgressView(progress: progress) @@ -74,27 +75,29 @@ extension DownloadTaskView { .padding(.horizontal) case let .error(error): VStack { - PrimaryButton(title: "Retry") { - downloadManager.download(task: downloadTask) - } - .frame(maxWidth: 300) - .frame(height: 50) + PrimaryButton(title: "Retry") + .onSelect { + downloadManager.download(task: downloadTask) + } + .frame(maxWidth: 300) + .frame(height: 50) Text("Error: \(error.localizedDescription)") .padding(.horizontal) } case .complete: - PrimaryButton(title: "Play") { - if Defaults[.VideoPlayer.videoPlayerType] == .swiftfin { - router.dismissCoordinator { - mainCoordinator.route(to: \.videoPlayer, DownloadVideoPlayerManager(downloadTask: downloadTask)) + PrimaryButton(title: "Play") + .onSelect { + if Defaults[.VideoPlayer.videoPlayerType] == .swiftfin { + router.dismissCoordinator { + mainCoordinator.route(to: \.videoPlayer, DownloadVideoPlayerManager(downloadTask: downloadTask)) + } + } else { + isPresentingVideoPlayerTypeError = true } - } else { - isPresentingVideoPlayerTypeError = true } - } - .frame(maxWidth: 300) - .frame(height: 50) + .frame(maxWidth: 300) + .frame(height: 50) } } diff --git a/Swiftfin/Views/HomeView/HomeErrorView.swift b/Swiftfin/Views/HomeView/HomeErrorView.swift index 10fe8c61..bbdcaf4c 100644 --- a/Swiftfin/Views/HomeView/HomeErrorView.swift +++ b/Swiftfin/Views/HomeView/HomeErrorView.swift @@ -38,11 +38,12 @@ extension HomeView { .frame(minWidth: 50, maxWidth: 240) .multilineTextAlignment(.center) - PrimaryButton(title: L10n.retry) { - viewModel.refresh() - } - .frame(maxWidth: 300) - .frame(height: 50) + PrimaryButton(title: L10n.retry) + .onSelect { + viewModel.refresh() + } + .frame(maxWidth: 300) + .frame(height: 50) } .offset(y: -50) } diff --git a/Swiftfin/Views/ItemOverviewView.swift b/Swiftfin/Views/ItemOverviewView.swift index 87cf14ba..3a480782 100644 --- a/Swiftfin/Views/ItemOverviewView.swift +++ b/Swiftfin/Views/ItemOverviewView.swift @@ -12,7 +12,7 @@ import SwiftUI struct ItemOverviewView: View { @EnvironmentObject - private var router: ItemOverviewCoordinator.Router + private var router: BasicNavigationViewCoordinator.Router let item: BaseItemDto diff --git a/Swiftfin/Views/ItemView/Components/AboutView.swift b/Swiftfin/Views/ItemView/Components/AboutView.swift deleted file mode 100644 index 70125ce5..00000000 --- a/Swiftfin/Views/ItemView/Components/AboutView.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2023 Jellyfin & Jellyfin Contributors -// - -import Defaults -import JellyfinAPI -import SwiftUI - -extension ItemView { - - struct AboutView: View { - - @Default(.accentColor) - private var accentColor - - @EnvironmentObject - private var router: ItemCoordinator.Router - - @ObservedObject - var viewModel: ItemViewModel - - var body: some View { - VStack(alignment: .leading) { - L10n.about.text - .font(.title2) - .fontWeight(.bold) - .accessibility(addTraits: [.isHeader]) - .padding(.horizontal) - .if(UIDevice.isIPad) { view in - view.padding(.horizontal) - } - - ScrollView(.horizontal, showsIndicators: false) { - HStack { - ImageView( - viewModel.item.type == .episode ? viewModel.item.seriesImageSource(.primary, maxWidth: 300) : viewModel - .item.imageSource(.primary, maxWidth: 300) - ) - .posterStyle(type: .portrait, width: 130) - .accessibilityIgnoresInvertColors() - - Card(title: viewModel.item.displayTitle) - .content { - if let overview = viewModel.item.overview { - TruncatedTextView(text: overview) - .lineLimit(4) - .font(.footnote) - .seeMoreAction { - router.route(to: \.itemOverview, viewModel.item) - } - .foregroundColor(.secondary) - } else { - L10n.noOverviewAvailable.text - .font(.footnote) - .foregroundColor(.secondary) - } - } - .onSelect { - router.route(to: \.itemOverview, viewModel.item) - } - - if viewModel.item.type == .episode || - viewModel.item.type == .movie, - let mediaSources = viewModel.item.mediaSources - { - ForEach(mediaSources) { source in - Card(title: L10n.media, subtitle: mediaSources.count > 1 ? source.displayTitle : nil) - .content { - if let mediaStreams = source.mediaStreams { - VStack(alignment: .leading) { - ForEach(mediaStreams.prefix(4), id: \.index) { mediaStream in - Text(mediaStream.displayTitle ?? .emptyDash) - .lineLimit(1) - .font(.footnote) - .foregroundColor(.secondary) - } - - if mediaStreams.count > 4 { - L10n.seeMore.text - .font(.footnote) - .foregroundColor(accentColor) - } - } - } - } - .onSelect { - router.route(to: \.mediaSourceInfo, source) - } - } - } - } - .padding(.horizontal) - .if(UIDevice.isIPad) { view in - view.padding(.horizontal) - } - } - } - } - } -} - -extension ItemView.AboutView { - - struct Card: View { - - private var content: () -> any View - private var onSelect: () -> Void - private let title: String - private let subtitle: String? - - var body: some View { - Button { - onSelect() - } label: { - ZStack(alignment: .leading) { - - Color.secondarySystemFill - .cornerRadius(10) - - VStack(alignment: .leading, spacing: 5) { - Text(title) - .font(.title2) - .fontWeight(.semibold) - .lineLimit(2) - - if let subtitle { - Text(subtitle) - .font(.subheadline) - } - - Spacer() - - content() - .eraseToAnyView() - } - .padding() - } - .frame(width: 330, height: 195) - } - .buttonStyle(.plain) - } - } -} - -extension ItemView.AboutView.Card { - - init(title: String, subtitle: String? = nil) { - self.init( - content: { EmptyView() }, - onSelect: {}, - title: title, - subtitle: subtitle - ) - } - - func content(@ViewBuilder _ content: @escaping () -> any View) -> Self { - copy(modifying: \.content, with: content) - } - - func onSelect(_ action: @escaping () -> Void) -> Self { - copy(modifying: \.onSelect, with: action) - } -} diff --git a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift new file mode 100644 index 00000000..131efd7e --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift @@ -0,0 +1,64 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import Defaults +import JellyfinAPI +import SwiftUI + +extension ItemView { + + struct AboutView: View { + + @Default(.accentColor) + private var accentColor + + @EnvironmentObject + private var router: ItemCoordinator.Router + + @ObservedObject + var viewModel: ItemViewModel + + var body: some View { + VStack(alignment: .leading) { + L10n.about.text + .font(.title2) + .fontWeight(.bold) + .accessibility(addTraits: [.isHeader]) + .padding(.horizontal) + .if(UIDevice.isIPad) { view in + view.padding(.horizontal) + } + + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ImageView( + viewModel.item.type == .episode ? viewModel.item.seriesImageSource(.primary, maxWidth: 300) : viewModel + .item.imageSource(.primary, maxWidth: 300) + ) + .posterStyle(type: .portrait, width: 130) + .accessibilityIgnoresInvertColors() + + OverviewCard(item: viewModel.item) + + if let mediaSources = viewModel.item.mediaSources { + ForEach(mediaSources) { source in + MediaSourcesCard(subtitle: mediaSources.count > 1 ? source.displayTitle : nil, source: source) + } + } + + RatingsCard(item: viewModel.item) + } + .padding(.horizontal) + .if(UIDevice.isIPad) { view in + view.padding(.horizontal) + } + } + } + } + } +} diff --git a/Swiftfin/Views/ItemView/Components/AboutView/Components/AboutViewCard.swift b/Swiftfin/Views/ItemView/Components/AboutView/Components/AboutViewCard.swift new file mode 100644 index 00000000..1d04df98 --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/AboutView/Components/AboutViewCard.swift @@ -0,0 +1,72 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +extension ItemView.AboutView { + + struct Card: View { + + private var content: () -> any View + private var onSelect: () -> Void + private let title: String + private let subtitle: String? + + var body: some View { + Button { + onSelect() + } label: { + ZStack(alignment: .leading) { + + Color.secondarySystemFill + .cornerRadius(10) + + VStack(alignment: .leading, spacing: 5) { + Text(title) + .font(.title2) + .fontWeight(.semibold) + .lineLimit(2) + + if let subtitle { + Text(subtitle) + .font(.subheadline) + } + + Spacer() + + content() + .eraseToAnyView() + } + .padding() + } + .frame(width: 330, height: 195) + } + .buttonStyle(.plain) + } + } +} + +extension ItemView.AboutView.Card { + + init(title: String, subtitle: String? = nil) { + self.init( + content: { EmptyView() }, + onSelect: {}, + title: title, + subtitle: subtitle + ) + } + + func content(@ViewBuilder _ content: @escaping () -> any View) -> Self { + copy(modifying: \.content, with: content) + } + + func onSelect(_ action: @escaping () -> Void) -> Self { + copy(modifying: \.onSelect, with: action) + } +} diff --git a/Swiftfin/Views/ItemView/Components/AboutView/Components/MediaSourcesCard.swift b/Swiftfin/Views/ItemView/Components/AboutView/Components/MediaSourcesCard.swift new file mode 100644 index 00000000..879c7ddb --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/AboutView/Components/MediaSourcesCard.swift @@ -0,0 +1,47 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import Defaults +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct MediaSourcesCard: View { + + @Default(.accentColor) + private var accentColor + + @EnvironmentObject + private var router: ItemCoordinator.Router + + let subtitle: String? + let source: MediaSourceInfo + + var body: some View { + Card(title: L10n.media, subtitle: subtitle) + .content { + if let mediaStreams = source.mediaStreams { + VStack(alignment: .leading) { + Text(mediaStreams.compactMap(\.displayTitle).prefix(4).joined(separator: "\n")) + .font(.footnote) + + if mediaStreams.count > 4 { + L10n.seeMore.text + .font(.footnote) + .foregroundColor(accentColor) + } + } + } + } + .onSelect { + router.route(to: \.mediaSourceInfo, source) + } + } + } +} diff --git a/Swiftfin/Views/ItemView/Components/AboutView/Components/OverviewCard.swift b/Swiftfin/Views/ItemView/Components/AboutView/Components/OverviewCard.swift new file mode 100644 index 00000000..cc17fe6a --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/AboutView/Components/OverviewCard.swift @@ -0,0 +1,42 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct OverviewCard: View { + + @EnvironmentObject + private var router: ItemCoordinator.Router + + let item: BaseItemDto + + var body: some View { + Card(title: item.displayTitle) + .content { + if let overview = item.overview { + TruncatedText(overview) + .seeMoreAction { + router.route(to: \.itemOverview, item) + } + .lineLimit(4) + .font(.footnote) + } else { + L10n.noOverviewAvailable.text + .font(.footnote) + .foregroundColor(.secondary) + } + } + .onSelect { + router.route(to: \.itemOverview, item) + } + } + } +} diff --git a/Swiftfin/Views/ItemView/Components/AboutView/Components/RatingsCard.swift b/Swiftfin/Views/ItemView/Components/AboutView/Components/RatingsCard.swift new file mode 100644 index 00000000..6ae6ec05 --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/AboutView/Components/RatingsCard.swift @@ -0,0 +1,58 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct RatingsCard: View { + + @EnvironmentObject + private var router: ItemCoordinator.Router + + let item: BaseItemDto + + var body: some View { + Card(title: L10n.ratings) + .content { + HStack(alignment: .bottom, spacing: 20) { + if let criticRating = item.criticRating { + VStack { + Group { + if criticRating >= 60 { + Image("tomato.fresh") + .symbolRenderingMode(.multicolor) + .foregroundStyle(.green, .red) + } else { + Image("tomato.rotten") + .symbolRenderingMode(.monochrome) + .foregroundColor(.green) + } + } + .font(.largeTitle) + + Text("\(criticRating, specifier: "%.0f")") + } + } + + if let communityRating = item.communityRating { + VStack { + Image(systemName: "star.fill") + .symbolRenderingMode(.multicolor) + .foregroundStyle(.yellow) + .font(.largeTitle) + + Text("\(communityRating, specifier: "%.1f")") + } + } + } + } + } + } +} diff --git a/Swiftfin/Views/ItemView/Components/MediaSourceInfoView.swift b/Swiftfin/Views/ItemView/Components/MediaSourceInfoView.swift deleted file mode 100644 index 34470007..00000000 --- a/Swiftfin/Views/ItemView/Components/MediaSourceInfoView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Swiftfin is subject to the terms of the Mozilla Public -// License, v2.0. If a copy of the MPL was not distributed with this -// file, you can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2023 Jellyfin & Jellyfin Contributors -// - -import Defaults -import JellyfinAPI -import SwiftUI - -extension ItemView { - - struct MediaSourceInfoView: View { - - @EnvironmentObject - private var router: MediaSourceInfoCoordinator.Router - - let mediaSource: MediaSourceInfo - - var body: some View { - Form { - if let videoStreams = mediaSource.videoStreams, - !videoStreams.isEmpty - { - Section(L10n.video) { - ForEach(videoStreams, id: \.self) { mediaStream in - ChevronButton(title: mediaStream.displayTitle ?? .emptyDash) - .onSelect { - router.route(to: \.mediaStreamInfo, mediaStream) - } - } - } - } - - if let audioStreams = mediaSource.audioStreams, - !audioStreams.isEmpty - { - Section(L10n.audio) { - ForEach(audioStreams, id: \.self) { mediaStream in - ChevronButton(title: mediaStream.displayTitle ?? .emptyDash) - .onSelect { - router.route(to: \.mediaStreamInfo, mediaStream) - } - } - } - } - - if let subtitleStreams = mediaSource.subtitleStreams, - !subtitleStreams.isEmpty - { - Section(L10n.subtitle) { - ForEach(subtitleStreams, id: \.self) { mediaStream in - ChevronButton(title: mediaStream.displayTitle ?? .emptyDash) - .onSelect { - router.route(to: \.mediaStreamInfo, mediaStream) - } - } - } - } - } - .navigationTitle(mediaSource.displayTitle) - .navigationBarTitleDisplayMode(.inline) - .navigationCloseButton { - router.dismissCoordinator() - } - } - } -} diff --git a/Swiftfin/Views/ItemView/Components/OverviewView.swift b/Swiftfin/Views/ItemView/Components/OverviewView.swift index 69ff1cb9..1cda10e6 100644 --- a/Swiftfin/Views/ItemView/Components/OverviewView.swift +++ b/Swiftfin/Views/ItemView/Components/OverviewView.swift @@ -32,7 +32,7 @@ extension ItemView { } if let itemOverview = item.overview { - TruncatedTextView(text: itemOverview) + TruncatedText(itemOverview) .seeMoreAction { router.route(to: \.itemOverview, item) } diff --git a/Swiftfin/Views/MediaSourceInfoView.swift b/Swiftfin/Views/MediaSourceInfoView.swift new file mode 100644 index 00000000..766e45b7 --- /dev/null +++ b/Swiftfin/Views/MediaSourceInfoView.swift @@ -0,0 +1,67 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import Defaults +import JellyfinAPI +import SwiftUI + +struct MediaSourceInfoView: View { + + @EnvironmentObject + private var router: MediaSourceInfoCoordinator.Router + + let source: MediaSourceInfo + + var body: some View { + Form { + if let videoStreams = source.videoStreams, + !videoStreams.isEmpty + { + Section(L10n.video) { + ForEach(videoStreams, id: \.self) { stream in + ChevronButton(title: stream.displayTitle ?? .emptyDash) + .onSelect { + router.route(to: \.mediaStreamInfo, stream) + } + } + } + } + + if let audioStreams = source.audioStreams, + !audioStreams.isEmpty + { + Section(L10n.audio) { + ForEach(audioStreams, id: \.self) { stream in + ChevronButton(title: stream.displayTitle ?? .emptyDash) + .onSelect { + router.route(to: \.mediaStreamInfo, stream) + } + } + } + } + + if let subtitleStreams = source.subtitleStreams, + !subtitleStreams.isEmpty + { + Section(L10n.subtitle) { + ForEach(subtitleStreams, id: \.self) { stream in + ChevronButton(title: stream.displayTitle ?? .emptyDash) + .onSelect { + router.route(to: \.mediaStreamInfo, stream) + } + } + } + } + } + .navigationTitle(source.displayTitle) + .navigationBarTitleDisplayMode(.inline) + .navigationCloseButton { + router.dismissCoordinator() + } + } +} diff --git a/Swiftfin/Views/ServerListView.swift b/Swiftfin/Views/ServerListView.swift index 2ad15984..c719324d 100644 --- a/Swiftfin/Views/ServerListView.swift +++ b/Swiftfin/Views/ServerListView.swift @@ -71,11 +71,12 @@ struct ServerListView: View { .frame(minWidth: 50, maxWidth: 240) .multilineTextAlignment(.center) - PrimaryButton(title: L10n.connect) { - router.route(to: \.connectToServer) - } - .frame(maxWidth: 300) - .frame(height: 50) + PrimaryButton(title: L10n.connect) + .onSelect { + router.route(to: \.connectToServer) + } + .frame(maxWidth: 300) + .frame(height: 50) } } diff --git a/Swiftfin/Views/UserListView.swift b/Swiftfin/Views/UserListView.swift index d06ac8e5..3823dfba 100644 --- a/Swiftfin/Views/UserListView.swift +++ b/Swiftfin/Views/UserListView.swift @@ -23,11 +23,12 @@ struct UserListView: View { .frame(minWidth: 50, maxWidth: 240) .multilineTextAlignment(.center) - PrimaryButton(title: L10n.signIn) { - router.route(to: \.userSignIn, viewModel.server) - } - .frame(maxWidth: 300) - .frame(height: 50) + PrimaryButton(title: L10n.signIn) + .onSelect { + router.route(to: \.userSignIn, viewModel.server) + } + .frame(maxWidth: 300) + .frame(height: 50) } } diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 4fc65f5c..8f0ee68e 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ