diff --git a/Shared/Components/SelectorView.swift b/Shared/Components/SelectorView.swift index e802eab4..18633007 100644 --- a/Shared/Components/SelectorView.swift +++ b/Shared/Components/SelectorView.swift @@ -6,6 +6,7 @@ // Copyright (c) 2024 Jellyfin & Jellyfin Contributors // +import Defaults import SwiftUI // TODO: Label generic not really necessary if just restricting to `Text` @@ -18,7 +19,7 @@ enum SelectorType { struct SelectorView: View { - @Environment(\.accentColor) + @Default(.accentColor) private var accentColor @Binding diff --git a/Shared/Components/TruncatedText.swift b/Shared/Components/TruncatedText.swift index f71ab2d0..1ed51519 100644 --- a/Shared/Components/TruncatedText.swift +++ b/Shared/Components/TruncatedText.swift @@ -19,7 +19,7 @@ struct TruncatedText: View { case view } - @Environment(\.accentColor) + @Default(.accentColor) private var accentColor @State diff --git a/Shared/Extensions/EdgeInsets.swift b/Shared/Extensions/EdgeInsets.swift index eada39ac..0c88fc2f 100644 --- a/Shared/Extensions/EdgeInsets.swift +++ b/Shared/Extensions/EdgeInsets.swift @@ -36,6 +36,10 @@ extension EdgeInsets { } static let zero: EdgeInsets = .init() + + var vertical: CGFloat { + top + bottom + } } extension NSDirectionalEdgeInsets { diff --git a/Shared/Extensions/EnvironmentValue/EnvironmentValue+Keys.swift b/Shared/Extensions/EnvironmentValue/EnvironmentValue+Keys.swift index 013dd14f..1cb36545 100644 --- a/Shared/Extensions/EnvironmentValue/EnvironmentValue+Keys.swift +++ b/Shared/Extensions/EnvironmentValue/EnvironmentValue+Keys.swift @@ -11,10 +11,6 @@ import SwiftUI extension EnvironmentValues { - struct AccentColorKey: EnvironmentKey { - static let defaultValue: Color = Defaults[.accentColor] - } - struct AudioOffsetKey: EnvironmentKey { static let defaultValue: Binding = .constant(0) } diff --git a/Shared/Extensions/EnvironmentValue/EnvironmentValue+Values.swift b/Shared/Extensions/EnvironmentValue/EnvironmentValue+Values.swift index 3a337fd8..a9425cc5 100644 --- a/Shared/Extensions/EnvironmentValue/EnvironmentValue+Values.swift +++ b/Shared/Extensions/EnvironmentValue/EnvironmentValue+Values.swift @@ -10,11 +10,6 @@ import SwiftUI extension EnvironmentValues { - var accentColor: Color { - get { self[AccentColorKey.self] } - set { self[AccentColorKey.self] = newValue } - } - var audioOffset: Binding { get { self[AudioOffsetKey.self] } set { self[AudioOffsetKey.self] = newValue } diff --git a/Shared/Extensions/ViewExtensions/Modifiers/PaletteOverlayRenderingModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/PaletteOverlayRenderingModifier.swift index 1d55378c..2d654f20 100644 --- a/Shared/Extensions/ViewExtensions/Modifiers/PaletteOverlayRenderingModifier.swift +++ b/Shared/Extensions/ViewExtensions/Modifiers/PaletteOverlayRenderingModifier.swift @@ -6,11 +6,12 @@ // Copyright (c) 2024 Jellyfin & Jellyfin Contributors // +import Defaults import SwiftUI struct PaletteOverlayRenderingModifier: ViewModifier { - @Environment(\.accentColor) + @Default(.accentColor) private var accentColor let color: Color? diff --git a/Shared/Extensions/ViewExtensions/Modifiers/ScrollViewOffsetModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/ScrollViewOffsetModifier.swift index 74f1a2c2..87946e45 100644 --- a/Shared/Extensions/ViewExtensions/Modifiers/ScrollViewOffsetModifier.swift +++ b/Shared/Extensions/ViewExtensions/Modifiers/ScrollViewOffsetModifier.swift @@ -6,34 +6,34 @@ // Copyright (c) 2024 Jellyfin & Jellyfin Contributors // -import Introspect import SwiftUI +import SwiftUIIntrospect struct ScrollViewOffsetModifier: ViewModifier { - @Binding - var scrollViewOffset: CGFloat - - private let scrollViewDelegate: ScrollViewDelegate? + @StateObject + private var scrollViewDelegate: ScrollViewDelegate init(scrollViewOffset: Binding) { - self._scrollViewOffset = scrollViewOffset - self.scrollViewDelegate = ScrollViewDelegate() - self.scrollViewDelegate?.parent = self + self._scrollViewDelegate = StateObject(wrappedValue: ScrollViewDelegate(scrollViewOffset: scrollViewOffset)) } func body(content: Content) -> some View { - content.introspectScrollView { scrollView in + content.introspect(.scrollView, on: .iOS(.v15), .iOS(.v16), .iOS(.v17)) { scrollView in scrollView.delegate = scrollViewDelegate } } - private class ScrollViewDelegate: NSObject, UIScrollViewDelegate { + private class ScrollViewDelegate: NSObject, ObservableObject, UIScrollViewDelegate { - var parent: ScrollViewOffsetModifier? + let scrollViewOffset: Binding + + init(scrollViewOffset: Binding) { + self.scrollViewOffset = scrollViewOffset + } func scrollViewDidScroll(_ scrollView: UIScrollView) { - parent?._scrollViewOffset.wrappedValue = scrollView.contentOffset.y + scrollViewOffset.wrappedValue = scrollView.contentOffset.y } } } diff --git a/Shared/Extensions/ViewExtensions/PreferenceKeys.swift b/Shared/Extensions/ViewExtensions/PreferenceKeys.swift index 4ee7c95b..5b8b0b59 100644 --- a/Shared/Extensions/ViewExtensions/PreferenceKeys.swift +++ b/Shared/Extensions/ViewExtensions/PreferenceKeys.swift @@ -13,12 +13,18 @@ struct FramePreferenceKey: PreferenceKey { static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} } +struct GeometryPrefenceKey: PreferenceKey { + + static var defaultValue: Value = Value(size: .zero, safeAreaInsets: .init(top: 0, leading: 0, bottom: 0, trailing: 0)) + static func reduce(value: inout Value, nextValue: () -> Value) {} + + struct Value: Equatable { + let size: CGSize + let safeAreaInsets: EdgeInsets + } +} + struct LocationPreferenceKey: PreferenceKey { static var defaultValue: CGPoint = .zero static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {} } - -struct SizePreferenceKey: PreferenceKey { - static var defaultValue: CGSize = .zero - static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} -} diff --git a/Shared/Extensions/ViewExtensions/ViewExtensions.swift b/Shared/Extensions/ViewExtensions/ViewExtensions.swift index 5e78f9fe..f6ad2546 100644 --- a/Shared/Extensions/ViewExtensions/ViewExtensions.swift +++ b/Shared/Extensions/ViewExtensions/ViewExtensions.swift @@ -183,14 +183,25 @@ extension View { // TODO: have width/height tracked binding - func onSizeChanged(_ onChange: @escaping (CGSize) -> Void) -> some View { + func onSizeChanged(perform action: @escaping (CGSize) -> Void) -> some View { + onSizeChanged { size, _ in + action(size) + } + } + + func onSizeChanged(perform action: @escaping (CGSize, EdgeInsets) -> Void) -> some View { background { GeometryReader { reader in Color.clear - .preference(key: SizePreferenceKey.self, value: reader.size) + .preference( + key: GeometryPrefenceKey.self, + value: GeometryPrefenceKey.Value(size: reader.size, safeAreaInsets: reader.safeAreaInsets) + ) } } - .onPreferenceChange(SizePreferenceKey.self, perform: onChange) + .onPreferenceChange(GeometryPrefenceKey.self) { value in + action(value.size, value.safeAreaInsets) + } } // TODO: probably rename since this doesn't set the size but tracks it diff --git a/Swiftfin/Objects/ScalingButtonStyle.swift b/Shared/Objects/ScalingButtonStyle.swift similarity index 100% rename from Swiftfin/Objects/ScalingButtonStyle.swift rename to Shared/Objects/ScalingButtonStyle.swift diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 43a523bc..1a5c4d16 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -199,8 +199,8 @@ E11245B128D919CD00D8A977 /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11245B028D919CD00D8A977 /* Overlay.swift */; }; E11245B428D97D5D00D8A977 /* BottomBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11245B328D97D5D00D8A977 /* BottomBarView.swift */; }; E11245B728D97ED200D8A977 /* TopBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11245B628D97ED200D8A977 /* TopBarView.swift */; }; - E113132B28BDB4B500930F75 /* NavBarDrawerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */; }; - E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */; }; + E113132B28BDB4B500930F75 /* NavigationBarDrawerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132A28BDB4B500930F75 /* NavigationBarDrawerView.swift */; }; + E113132F28BDB66A00930F75 /* NavigationBarDrawerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132E28BDB66A00930F75 /* NavigationBarDrawerModifier.swift */; }; E113133228BDC72000930F75 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133128BDC72000930F75 /* FilterView.swift */; }; E113133428BE988200930F75 /* NavigationBarFilterDrawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133328BE988200930F75 /* NavigationBarFilterDrawer.swift */; }; E113133628BE98AA00930F75 /* FilterDrawerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133528BE98AA00930F75 /* FilterDrawerButton.swift */; }; @@ -230,8 +230,8 @@ E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; }; E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */; }; E11895AA289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */; }; - E11895AC289383EE0042947B /* NavBarOffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895AB289383EE0042947B /* NavBarOffsetModifier.swift */; }; - E11895AF2893840F0042947B /* NavBarOffsetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895AE2893840F0042947B /* NavBarOffsetView.swift */; }; + E11895AC289383EE0042947B /* NavigationBarOffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895AB289383EE0042947B /* NavigationBarOffsetModifier.swift */; }; + E11895AF2893840F0042947B /* NavigationBarOffsetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895AE2893840F0042947B /* NavigationBarOffsetView.swift */; }; E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */; }; E11895B42893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */; }; E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; @@ -243,7 +243,7 @@ E11BDF972B865F550045C54A /* ItemTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11BDF962B865F550045C54A /* ItemTag.swift */; }; E11BDF982B865F550045C54A /* ItemTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11BDF962B865F550045C54A /* ItemTag.swift */; }; E11CEB8928998549003E74C7 /* BottomEdgeGradientModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19E551E2897326C003CE330 /* BottomEdgeGradientModifier.swift */; }; - E11CEB8B28998552003E74C7 /* iOSViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8A28998552003E74C7 /* iOSViewExtensions.swift */; }; + E11CEB8B28998552003E74C7 /* View-iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8A28998552003E74C7 /* View-iOS.swift */; }; E11CEB8D28999B4A003E74C7 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8C28999B4A003E74C7 /* Font.swift */; }; E11CEB9128999D84003E74C7 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB8F28999D84003E74C7 /* EpisodeItemView.swift */; }; E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */; }; @@ -590,7 +590,7 @@ E1A3E4C72BB74E50005C59F8 /* EpisodeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4C62BB74E50005C59F8 /* EpisodeCard.swift */; }; E1A3E4C92BB74EA3005C59F8 /* LoadingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4C82BB74EA3005C59F8 /* LoadingCard.swift */; }; E1A3E4CB2BB74EFD005C59F8 /* EpisodeHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4CA2BB74EFD005C59F8 /* EpisodeHStack.swift */; }; - E1A3E4CD2BB7D8C8005C59F8 /* iOSLabelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4CC2BB7D8C8005C59F8 /* iOSLabelExtensions.swift */; }; + E1A3E4CD2BB7D8C8005C59F8 /* Label-iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4CC2BB7D8C8005C59F8 /* Label-iOS.swift */; }; E1A3E4CF2BB7E02B005C59F8 /* DelayedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4CE2BB7E02B005C59F8 /* DelayedProgressView.swift */; }; E1A3E4D12BB7F5BF005C59F8 /* ErrorCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A3E4D02BB7F5BF005C59F8 /* ErrorCard.swift */; }; E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */; }; @@ -618,6 +618,8 @@ 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 */; }; + E1B90C6A2BBE68D5007027C8 /* OffsetScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B90C692BBE68D5007027C8 /* OffsetScrollView.swift */; }; + E1B90C8A2BC475E7007027C8 /* ScalingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18ACA8A2A14301800BB4F35 /* ScalingButtonStyle.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 */; }; @@ -964,8 +966,8 @@ E11245B028D919CD00D8A977 /* Overlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overlay.swift; sourceTree = ""; }; E11245B328D97D5D00D8A977 /* BottomBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomBarView.swift; sourceTree = ""; }; E11245B628D97ED200D8A977 /* TopBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBarView.swift; sourceTree = ""; }; - E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarDrawerView.swift; sourceTree = ""; }; - E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarDrawerModifier.swift; sourceTree = ""; }; + E113132A28BDB4B500930F75 /* NavigationBarDrawerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarDrawerView.swift; sourceTree = ""; }; + E113132E28BDB66A00930F75 /* NavigationBarDrawerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarDrawerModifier.swift; sourceTree = ""; }; E113133128BDC72000930F75 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = ""; }; E113133328BE988200930F75 /* NavigationBarFilterDrawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarFilterDrawer.swift; sourceTree = ""; }; E113133528BE98AA00930F75 /* FilterDrawerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterDrawerButton.swift; sourceTree = ""; }; @@ -980,14 +982,14 @@ E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectView.swift; sourceTree = ""; }; E118959C289312020042947B /* BaseItemPerson+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemPerson+Poster.swift"; sourceTree = ""; }; E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetModifier.swift; sourceTree = ""; }; - E11895AB289383EE0042947B /* NavBarOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarOffsetModifier.swift; sourceTree = ""; }; - E11895AE2893840F0042947B /* NavBarOffsetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarOffsetView.swift; sourceTree = ""; }; + E11895AB289383EE0042947B /* NavigationBarOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarOffsetModifier.swift; sourceTree = ""; }; + E11895AE2893840F0042947B /* NavigationBarOffsetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarOffsetView.swift; sourceTree = ""; }; E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundParallaxHeaderModifier.swift; sourceTree = ""; }; E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = ""; }; E11BDF762B8513B40045C54A /* ItemGenre.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemGenre.swift; sourceTree = ""; }; E11BDF792B85529D0045C54A /* SupportedCaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedCaseIterable.swift; sourceTree = ""; }; E11BDF962B865F550045C54A /* ItemTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTag.swift; sourceTree = ""; }; - E11CEB8A28998552003E74C7 /* iOSViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSViewExtensions.swift; sourceTree = ""; }; + E11CEB8A28998552003E74C7 /* View-iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View-iOS.swift"; sourceTree = ""; }; E11CEB8C28999B4A003E74C7 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; E11CEB8F28999D84003E74C7 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = ""; }; E11CEB9328999D9E003E74C7 /* EpisodeItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemContentView.swift; sourceTree = ""; }; @@ -1185,7 +1187,7 @@ E1A3E4C62BB74E50005C59F8 /* EpisodeCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCard.swift; sourceTree = ""; }; E1A3E4C82BB74EA3005C59F8 /* LoadingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCard.swift; sourceTree = ""; }; E1A3E4CA2BB74EFD005C59F8 /* EpisodeHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeHStack.swift; sourceTree = ""; }; - E1A3E4CC2BB7D8C8005C59F8 /* iOSLabelExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSLabelExtensions.swift; sourceTree = ""; }; + E1A3E4CC2BB7D8C8005C59F8 /* Label-iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Label-iOS.swift"; sourceTree = ""; }; E1A3E4CE2BB7E02B005C59F8 /* DelayedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelayedProgressView.swift; sourceTree = ""; }; E1A3E4D02BB7F5BF005C59F8 /* ErrorCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorCard.swift; sourceTree = ""; }; E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicItemSelector.swift; sourceTree = ""; }; @@ -1203,6 +1205,7 @@ E1B490462967E2E500D3EDCE /* CoreStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreStore.swift; sourceTree = ""; }; E1B5784028F8AFCB00D42911 /* WrappedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedView.swift; sourceTree = ""; }; E1B5861129E32EEF00E45D6E /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; + E1B90C692BBE68D5007027C8 /* OffsetScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetScrollView.swift; sourceTree = ""; }; E1BA6FC429D25DBD007D98DC /* LandscapeItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapeItemElement.swift; sourceTree = ""; }; E1BDF2E42951475300CC0294 /* VideoPlayerActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerActionButton.swift; sourceTree = ""; }; E1BDF2E82951490400CC0294 /* ActionButtonSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonSelectorView.swift; sourceTree = ""; }; @@ -1581,6 +1584,7 @@ E1CCF12D28ABF989006CAC9E /* PosterType.swift */, E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */, E1E9017A28DAAE4D001B1594 /* RoundedCorner.swift */, + E18ACA8A2A14301800BB4F35 /* ScalingButtonStyle.swift */, E129429228F2845000796AC6 /* SliderType.swift */, E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */, E11042742B8013DF00821020 /* Stateful.swift */, @@ -1649,7 +1653,6 @@ isa = PBXGroup; children = ( E13DD3BB27163C3E009D4DAF /* App */, - 62ECA01926FA6D6900E8EBB7 /* AppURLHandler */, 53F866422687A45400DCD1D7 /* Components */, E11CEB85289984F5003E74C7 /* Extensions */, E1DD1127271E7D15005BE12F /* Objects */, @@ -1941,15 +1944,6 @@ path = Coordinators; sourceTree = ""; }; - 62ECA01926FA6D6900E8EBB7 /* AppURLHandler */ = { - isa = PBXGroup; - children = ( - 6220D0CB26D640C400B8E046 /* AppURLHandler.swift */, - 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */, - ); - path = AppURLHandler; - sourceTree = ""; - }; AE8C3157265D6F5E008AA076 /* Resources */ = { isa = PBXGroup; children = ( @@ -2015,13 +2009,13 @@ path = Components; sourceTree = ""; }; - E113133028BDB6D600930F75 /* NavBarDrawerButtons */ = { + E113133028BDB6D600930F75 /* NavigationBarDrawerButtons */ = { isa = PBXGroup; children = ( - E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */, - E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */, + E113132E28BDB66A00930F75 /* NavigationBarDrawerModifier.swift */, + E113132A28BDB4B500930F75 /* NavigationBarDrawerView.swift */, ); - path = NavBarDrawerButtons; + path = NavigationBarDrawerButtons; sourceTree = ""; }; E1153D972BBA3E5300424D36 /* Components */ = { @@ -2065,19 +2059,19 @@ path = ViewExtensions; sourceTree = ""; }; - E11895B12893842D0042947B /* NavBarOffset */ = { + E11895B12893842D0042947B /* NavigationBarOffset */ = { isa = PBXGroup; children = ( - E11895AB289383EE0042947B /* NavBarOffsetModifier.swift */, - E11895AE2893840F0042947B /* NavBarOffsetView.swift */, + E11895AB289383EE0042947B /* NavigationBarOffsetModifier.swift */, + E11895AE2893840F0042947B /* NavigationBarOffsetView.swift */, ); - path = NavBarOffset; + path = NavigationBarOffset; sourceTree = ""; }; E11CEB85289984F5003E74C7 /* Extensions */ = { isa = PBXGroup; children = ( - E1A3E4CC2BB7D8C8005C59F8 /* iOSLabelExtensions.swift */, + E1A3E4CC2BB7D8C8005C59F8 /* Label-iOS.swift */, E11CEB8828998522003E74C7 /* View */, ); path = Extensions; @@ -2086,10 +2080,8 @@ E11CEB8828998522003E74C7 /* View */ = { isa = PBXGroup; children = ( - E11CEB8A28998552003E74C7 /* iOSViewExtensions.swift */, - E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */, - E113133028BDB6D600930F75 /* NavBarDrawerButtons */, - E11895B12893842D0042947B /* NavBarOffset */, + E1B90C892BC4563D007027C8 /* Modifiers */, + E11CEB8A28998552003E74C7 /* View-iOS.swift */, ); path = View; sourceTree = ""; @@ -2553,6 +2545,7 @@ E17AC9722955007A003D2BC2 /* DownloadTaskButton.swift */, E172D3AF2BACA54A007B4647 /* EpisodeSelector */, E17FB55A28C1266400311DFE /* GenresHStack.swift */, + E1B90C692BBE68D5007027C8 /* OffsetScrollView.swift */, E1D8424E2932F7C400D1041A /* OverviewView.swift */, E18E01D8288747230022598C /* PlayButton.swift */, E17FB55428C1250B00311DFE /* SimilarItemsHStack.swift */, @@ -2663,6 +2656,16 @@ path = Components; sourceTree = ""; }; + E1B90C892BC4563D007027C8 /* Modifiers */ = { + isa = PBXGroup; + children = ( + E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */, + E113133028BDB6D600930F75 /* NavigationBarDrawerButtons */, + E11895B12893842D0042947B /* NavigationBarOffset */, + ); + path = Modifiers; + sourceTree = ""; + }; E1BDF2E7295148F400CC0294 /* VideoPlayerSettingsView */ = { isa = PBXGroup; children = ( @@ -2832,7 +2835,8 @@ E1DD1127271E7D15005BE12F /* Objects */ = { isa = PBXGroup; children = ( - E18ACA8A2A14301800BB4F35 /* ScalingButtonStyle.swift */, + 6220D0CB26D640C400B8E046 /* AppURLHandler.swift */, + 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */, ); path = Objects; sourceTree = ""; @@ -3556,6 +3560,7 @@ E14EDECD2B8FB709000F00A4 /* ItemYear.swift in Sources */, E154965F296CA2EF00C4EF88 /* DownloadTask.swift in Sources */, E154967E296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift in Sources */, + E1B90C8A2BC475E7007027C8 /* ScalingButtonStyle.swift in Sources */, E1DABAFE2A27B982008AC34A /* RatingsCard.swift in Sources */, E1C9261B288756BD002A7A66 /* DotHStack.swift in Sources */, E104C873296E0D0A00C1C3F9 /* IndicatorSettingsView.swift in Sources */, @@ -3578,6 +3583,7 @@ E1A1528828FD229500600579 /* ChevronButton.swift in Sources */, E1B490472967E2E500D3EDCE /* CoreStore.swift in Sources */, 6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */, + E1B90C6A2BBE68D5007027C8 /* OffsetScrollView.swift in Sources */, E18E01DB288747230022598C /* iPadOSEpisodeItemView.swift in Sources */, E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */, 621338932660107500A81A2A /* String.swift in Sources */, @@ -3603,7 +3609,7 @@ E1721FAA28FB7CAC00762992 /* CompactTimeStamp.swift in Sources */, 62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */, E12CC1B128D1008F00678D5D /* NextUpView.swift in Sources */, - E11895AF2893840F0042947B /* NavBarOffsetView.swift in Sources */, + E11895AF2893840F0042947B /* NavigationBarOffsetView.swift in Sources */, E18E0208288749200022598C /* BlurView.swift in Sources */, E18E01E7288747230022598C /* CollectionItemContentView.swift in Sources */, E1E1643F28BB075C00323B0A /* SelectorView.swift in Sources */, @@ -3630,7 +3636,7 @@ 6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */, E18A8E8528D60D0000333B9A /* VideoPlayerCoordinator.swift in Sources */, E19E551F2897326C003CE330 /* BottomEdgeGradientModifier.swift in Sources */, - E1A3E4CD2BB7D8C8005C59F8 /* iOSLabelExtensions.swift in Sources */, + E1A3E4CD2BB7D8C8005C59F8 /* Label-iOS.swift in Sources */, E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, E12CC1BE28D11F4500678D5D /* RecentlyAddedView.swift in Sources */, E17AC96A2954D00E003D2BC2 /* URLResponse.swift in Sources */, @@ -3657,7 +3663,7 @@ E12CC1BB28D11E1000678D5D /* RecentlyAddedViewModel.swift in Sources */, E17FB55228C119D400311DFE /* Displayable.swift in Sources */, E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */, - E113132B28BDB4B500930F75 /* NavBarDrawerView.swift in Sources */, + E113132B28BDB4B500930F75 /* NavigationBarDrawerView.swift in Sources */, E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */, E1559A76294D960C00C1FFBC /* MainOverlay.swift in Sources */, C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */, @@ -3771,7 +3777,7 @@ E170D105294D21FA0017224C /* MediaSourceInfoView.swift in Sources */, E1D37F4B2B9CEA5C00343D2B /* ImageSource.swift in Sources */, E1CAF6622BA363840087D991 /* UIHostingController.swift in Sources */, - E11895AC289383EE0042947B /* NavBarOffsetModifier.swift in Sources */, + E11895AC289383EE0042947B /* NavigationBarOffsetModifier.swift in Sources */, E1CD13EF28EF364100CB46CA /* DetectOrientationModifier.swift in Sources */, E157563029355B7900976E1F /* UpdateView.swift in Sources */, E1D8424F2932F7C400D1041A /* OverviewView.swift in Sources */, @@ -3804,7 +3810,7 @@ E1E7506A2A33E9B400B2C1EE /* RatingsCard.swift in Sources */, E1D842912933F87500D1041A /* ItemFields.swift in Sources */, E1BDF2F729524ECD00CC0294 /* PlaybackSpeedActionButton.swift in Sources */, - E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */, + E113132F28BDB66A00930F75 /* NavigationBarDrawerModifier.swift in Sources */, E1E750692A33E9B400B2C1EE /* MediaSourcesCard.swift in Sources */, E1DE2B4C2B98389E00F6715F /* PaletteOverlayRenderingModifier.swift in Sources */, E18295E429CAC6F100F91ED0 /* BasicNavigationCoordinator.swift in Sources */, @@ -3819,7 +3825,7 @@ E1549662296CA2EF00C4EF88 /* NewSessionManager.swift in Sources */, E15756362936856700976E1F /* VideoPlayerType.swift in Sources */, E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */, - E11CEB8B28998552003E74C7 /* iOSViewExtensions.swift in Sources */, + E11CEB8B28998552003E74C7 /* View-iOS.swift in Sources */, E1401CA92938140700E8B599 /* DarkAppIcon.swift in Sources */, E1A1529028FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */, E11042752B8013DF00821020 /* Stateful.swift in Sources */, @@ -4252,7 +4258,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 78; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -4268,7 +4274,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_CFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = pip.jellyfin.swiftfin; + PRODUCT_BUNDLE_IDENTIFIER = org.jellyfin.swiftfin; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTS_MACCATALYST = NO; @@ -4292,7 +4298,7 @@ CURRENT_PROJECT_VERSION = 78; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -4308,7 +4314,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_CFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = pip.jellyfin.swiftfin; + PRODUCT_BUNDLE_IDENTIFIER = org.jellyfin.swiftfin; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTS_MACCATALYST = NO; diff --git a/Swiftfin/Components/NavigationBarFilterDrawer/FilterDrawerButton.swift b/Swiftfin/Components/NavigationBarFilterDrawer/FilterDrawerButton.swift index 9de39821..71e5167a 100644 --- a/Swiftfin/Components/NavigationBarFilterDrawer/FilterDrawerButton.swift +++ b/Swiftfin/Components/NavigationBarFilterDrawer/FilterDrawerButton.swift @@ -13,7 +13,7 @@ extension NavigationBarFilterDrawer { struct FilterDrawerButton: View { - @Environment(\.accentColor) + @Default(.accentColor) private var accentColor private let systemName: String? diff --git a/Swiftfin/Components/PillHStack.swift b/Swiftfin/Components/PillHStack.swift index c9bbfc53..88cae310 100644 --- a/Swiftfin/Components/PillHStack.swift +++ b/Swiftfin/Components/PillHStack.swift @@ -15,7 +15,7 @@ struct PillHStack: View { private var onSelect: (Item) -> Void var body: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 10) { Text(title) .font(.title2) .fontWeight(.semibold) diff --git a/Swiftfin/Extensions/iOSLabelExtensions.swift b/Swiftfin/Extensions/Label-iOS.swift similarity index 100% rename from Swiftfin/Extensions/iOSLabelExtensions.swift rename to Swiftfin/Extensions/Label-iOS.swift diff --git a/Swiftfin/Extensions/View/DetectOrientationModifier.swift b/Swiftfin/Extensions/View/Modifiers/DetectOrientationModifier.swift similarity index 100% rename from Swiftfin/Extensions/View/DetectOrientationModifier.swift rename to Swiftfin/Extensions/View/Modifiers/DetectOrientationModifier.swift diff --git a/Swiftfin/Extensions/View/NavBarDrawerButtons/NavBarDrawerModifier.swift b/Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerModifier.swift similarity index 85% rename from Swiftfin/Extensions/View/NavBarDrawerButtons/NavBarDrawerModifier.swift rename to Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerModifier.swift index 382e514d..52b1186d 100644 --- a/Swiftfin/Extensions/View/NavBarDrawerButtons/NavBarDrawerModifier.swift +++ b/Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerModifier.swift @@ -8,7 +8,7 @@ import SwiftUI -struct NavBarDrawerModifier: ViewModifier { +struct NavigationBarDrawerModifier: ViewModifier { private let drawer: () -> Drawer @@ -17,7 +17,7 @@ struct NavBarDrawerModifier: ViewModifier { } func body(content: Content) -> some View { - NavBarDrawerView { + NavigationBarDrawerView { drawer() .ignoresSafeArea() } content: { diff --git a/Swiftfin/Extensions/View/NavBarDrawerButtons/NavBarDrawerView.swift b/Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerView.swift similarity index 53% rename from Swiftfin/Extensions/View/NavBarDrawerButtons/NavBarDrawerView.swift rename to Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerView.swift index 3bfedec4..32606faf 100644 --- a/Swiftfin/Extensions/View/NavBarDrawerButtons/NavBarDrawerView.swift +++ b/Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerView.swift @@ -8,61 +8,55 @@ import SwiftUI -struct NavBarDrawerView: UIViewControllerRepresentable { +struct NavigationBarDrawerView: UIViewControllerRepresentable { - private let buttons: () -> any View - private let content: () -> any View + private let buttons: () -> Drawer + private let content: () -> Content init( - @ViewBuilder buttons: @escaping () -> any View, - @ViewBuilder content: @escaping () -> any View + @ViewBuilder buttons: @escaping () -> Drawer, + @ViewBuilder content: @escaping () -> Content ) { self.buttons = buttons self.content = content } - func makeUIViewController(context: Context) -> UINavBarDrawerHostingController { - UINavBarDrawerHostingController(buttons: buttons, content: content) + func makeUIViewController(context: Context) -> UINavigationBarDrawerHostingController { + UINavigationBarDrawerHostingController(buttons: buttons, content: content) } - func updateUIViewController(_ uiViewController: UINavBarDrawerHostingController, context: Context) {} + func updateUIViewController(_ uiViewController: UINavigationBarDrawerHostingController, context: Context) {} } -class UINavBarDrawerHostingController: UIViewController { +class UINavigationBarDrawerHostingController: UIHostingController { - private let buttons: () -> any View - private let content: () -> any View + private let drawer: () -> Drawer + private let content: () -> Content // TODO: see if we can get the height instead from the view passed in private let drawerHeight: CGFloat = 36 - private lazy var navBarBlurView: UIVisualEffectView = { + private lazy var blurView: UIVisualEffectView = { let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterial)) blurView.translatesAutoresizingMaskIntoConstraints = false return blurView }() - private lazy var contentView: UIHostingController = { - let contentView = UIHostingController(rootView: content().eraseToAnyView()) - contentView.view.translatesAutoresizingMaskIntoConstraints = false - contentView.view.backgroundColor = nil - return contentView - }() - - private lazy var drawerButtonsView: UIHostingController = { - let drawerButtonsView = UIHostingController(rootView: buttons().eraseToAnyView()) + private lazy var drawerButtonsView: UIHostingController = { + let drawerButtonsView = UIHostingController(rootView: drawer()) drawerButtonsView.view.translatesAutoresizingMaskIntoConstraints = false drawerButtonsView.view.backgroundColor = nil return drawerButtonsView }() init( - buttons: @escaping () -> any View, - content: @escaping () -> any View + buttons: @escaping () -> Drawer, + content: @escaping () -> Content ) { - self.buttons = buttons + self.drawer = buttons self.content = content - super.init(nibName: nil, bundle: nil) + + super.init(rootView: content()) } @available(*, unavailable) @@ -75,11 +69,7 @@ class UINavBarDrawerHostingController: UIViewController { view.backgroundColor = nil - addChild(contentView) - view.addSubview(contentView.view) - contentView.didMove(toParent: self) - - view.addSubview(navBarBlurView) + view.addSubview(blurView) addChild(drawerButtonsView) view.addSubview(drawerButtonsView.view) @@ -91,17 +81,12 @@ class UINavBarDrawerHostingController: UIViewController { drawerButtonsView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), drawerButtonsView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) + NSLayoutConstraint.activate([ - navBarBlurView.topAnchor.constraint(equalTo: view.topAnchor), - navBarBlurView.bottomAnchor.constraint(equalTo: drawerButtonsView.view.bottomAnchor), - navBarBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navBarBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - ]) - NSLayoutConstraint.activate([ - contentView.view.topAnchor.constraint(equalTo: view.topAnchor), - contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - contentView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - contentView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + blurView.topAnchor.constraint(equalTo: view.topAnchor), + blurView.bottomAnchor.constraint(equalTo: drawerButtonsView.view.bottomAnchor), + blurView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + blurView.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) } diff --git a/Swiftfin/Extensions/View/NavBarOffset/NavBarOffsetModifier.swift b/Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetModifier.swift similarity index 71% rename from Swiftfin/Extensions/View/NavBarOffset/NavBarOffsetModifier.swift rename to Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetModifier.swift index 370f7a06..5795cea9 100644 --- a/Swiftfin/Extensions/View/NavBarOffset/NavBarOffsetModifier.swift +++ b/Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetModifier.swift @@ -8,7 +8,7 @@ import SwiftUI -struct NavBarOffsetModifier: ViewModifier { +struct NavigationBarOffsetModifier: ViewModifier { @Binding var scrollViewOffset: CGFloat @@ -17,7 +17,11 @@ struct NavBarOffsetModifier: ViewModifier { let end: CGFloat func body(content: Content) -> some View { - NavBarOffsetView(scrollViewOffset: $scrollViewOffset, start: start, end: end) { + NavigationBarOffsetView( + scrollViewOffset: $scrollViewOffset, + start: start, + end: end + ) { content } .ignoresSafeArea() diff --git a/Swiftfin/Extensions/View/NavBarOffset/NavBarOffsetView.swift b/Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetView.swift similarity index 64% rename from Swiftfin/Extensions/View/NavBarOffset/NavBarOffsetView.swift rename to Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetView.swift index 3b91d918..da079794 100644 --- a/Swiftfin/Extensions/View/NavBarOffset/NavBarOffsetView.swift +++ b/Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetView.swift @@ -8,7 +8,7 @@ import SwiftUI -struct NavBarOffsetView: UIViewControllerRepresentable { +struct NavigationBarOffsetView: UIViewControllerRepresentable { @Binding private var scrollViewOffset: CGFloat @@ -29,20 +29,20 @@ struct NavBarOffsetView: UIViewControllerRepresentable { self.content = content } - func makeUIViewController(context: Context) -> UINavBarOffsetHostingController { - UINavBarOffsetHostingController(rootView: content()) + func makeUIViewController(context: Context) -> UINavigationBarOffsetHostingController { + UINavigationBarOffsetHostingController(rootView: content()) } - func updateUIViewController(_ uiViewController: UINavBarOffsetHostingController, context: Context) { + func updateUIViewController(_ uiViewController: UINavigationBarOffsetHostingController, context: Context) { uiViewController.scrollViewDidScroll(scrollViewOffset, start: start, end: end) } } -class UINavBarOffsetHostingController: UIHostingController { +class UINavigationBarOffsetHostingController: UIHostingController { - private var lastScrollViewOffset: CGFloat = 0 + private var lastAlpha: CGFloat = 0 - private lazy var navBarBlurView: UIVisualEffectView = { + private lazy var blurView: UIVisualEffectView = { let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterial)) blurView.translatesAutoresizingMaskIntoConstraints = false return blurView @@ -53,38 +53,41 @@ class UINavBarOffsetHostingController: UIHostingController, start: CGFloat, end: CGFloat) -> some View { - modifier(NavBarOffsetModifier(scrollViewOffset: scrollViewOffset, start: start, end: end)) + modifier(NavigationBarOffsetModifier(scrollViewOffset: scrollViewOffset, start: start, end: end)) } func navigationBarDrawer(@ViewBuilder _ drawer: @escaping () -> Drawer) -> some View { - modifier(NavBarDrawerModifier(drawer: drawer)) + modifier(NavigationBarDrawerModifier(drawer: drawer)) } @ViewBuilder diff --git a/Swiftfin/AppURLHandler/AppURLHandler.swift b/Swiftfin/Objects/AppURLHandler.swift similarity index 100% rename from Swiftfin/AppURLHandler/AppURLHandler.swift rename to Swiftfin/Objects/AppURLHandler.swift diff --git a/Swiftfin/AppURLHandler/DeepLink.swift b/Swiftfin/Objects/DeepLink.swift similarity index 100% rename from Swiftfin/AppURLHandler/DeepLink.swift rename to Swiftfin/Objects/DeepLink.swift diff --git a/Swiftfin/Views/FilterView.swift b/Swiftfin/Views/FilterView.swift index fa510c73..04bc186f 100644 --- a/Swiftfin/Views/FilterView.swift +++ b/Swiftfin/Views/FilterView.swift @@ -11,6 +11,8 @@ import SwiftUI // Note: Keep all of the ItemFilterCollection/ItemFilter/AnyItemFilter KeyPath wackiness in this file +// TODO: multiple filter types? +// - for sort order and sort by combined struct FilterView: View { @Binding diff --git a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift index 872741b7..aa6a1647 100644 --- a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift +++ b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift @@ -29,10 +29,7 @@ extension ItemView { .font(.title2) .fontWeight(.bold) .accessibility(addTraits: [.isHeader]) - .padding(.horizontal) - .if(UIDevice.isPad) { view in - view.padding(.horizontal) - } + .edgePadding(.horizontal) ScrollView(.horizontal, showsIndicators: false) { HStack { @@ -41,6 +38,7 @@ extension ItemView { .item.imageSource(.primary, maxWidth: 300) ) .posterStyle(.portrait) + .posterShadow() .frame(width: 130) .accessibilityIgnoresInvertColors() @@ -54,10 +52,7 @@ extension ItemView { RatingsCard(item: viewModel.item) } - .padding(.horizontal) - .if(UIDevice.isPad) { view in - view.padding(.horizontal) - } + .edgePadding(.horizontal) } } } diff --git a/Swiftfin/Views/ItemView/Components/OffsetScrollView.swift b/Swiftfin/Views/ItemView/Components/OffsetScrollView.swift new file mode 100644 index 00000000..1110e16f --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/OffsetScrollView.swift @@ -0,0 +1,86 @@ +// +// 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) 2024 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +// TODO: given height or height ratio options + +// The fading values just "feel right" and is the same for iOS and iPadOS. +// Adjust if necessary or if a more concrete design comes along. + +extension ItemView { + + struct OffsetScrollView: View { + + @State + private var scrollViewOffset: CGFloat = 0 + @State + private var size: CGSize = .zero + @State + private var safeAreaInsets: EdgeInsets = .zero + + private let header: () -> Header + private let overlay: () -> Overlay + private let content: () -> Content + private let heightRatio: CGFloat + + init( + headerHeight: CGFloat = 0, + @ViewBuilder header: @escaping () -> Header, + @ViewBuilder overlay: @escaping () -> Overlay, + @ViewBuilder content: @escaping () -> Content + ) { + self.header = header + self.overlay = overlay + self.content = content + self.heightRatio = headerHeight + } + + private var headerOpacity: CGFloat { + let start = (size.height + safeAreaInsets.vertical) * heightRatio - safeAreaInsets.top - 90 + let end = (size.height + safeAreaInsets.vertical) * heightRatio - safeAreaInsets.top - 40 + let diff = end - start + let opacity = clamp((scrollViewOffset - start) / diff, min: 0, max: 1) + return opacity + } + + var body: some View { + ScrollView { + VStack(spacing: 0) { + overlay() + .frame(height: (size.height + safeAreaInsets.vertical) * heightRatio) + .overlay { + Color.systemBackground + .opacity(headerOpacity) + } + + content() + } + } + .edgesIgnoringSafeArea(.top) + .onSizeChanged { size, safeAreaInsets in + self.size = size + self.safeAreaInsets = safeAreaInsets + } + .scrollViewOffset($scrollViewOffset) + .navigationBarOffset( + $scrollViewOffset, + start: (size.height + safeAreaInsets.vertical) * heightRatio - safeAreaInsets.top - 45, + end: (size.height + safeAreaInsets.vertical) * heightRatio - safeAreaInsets.top - 5 + ) + .backgroundParallaxHeader( + $scrollViewOffset, + height: (size.height + safeAreaInsets.vertical) * heightRatio, + multiplier: 0.3 + ) { + header() + .frame(height: (size.height + safeAreaInsets.vertical) * heightRatio) + } + } + } +} diff --git a/Swiftfin/Views/ItemView/Components/OverviewView.swift b/Swiftfin/Views/ItemView/Components/OverviewView.swift index c7980a29..aed7e679 100644 --- a/Swiftfin/Views/ItemView/Components/OverviewView.swift +++ b/Swiftfin/Views/ItemView/Components/OverviewView.swift @@ -28,9 +28,7 @@ extension ItemView { .font(.body) .fontWeight(.semibold) .multilineTextAlignment(.leading) - .ifLet(taglineLineLimit) { view, lineLimit in - view.lineLimit(lineLimit) - } + .lineLimit(taglineLineLimit) } if let itemOverview = item.overview { @@ -40,9 +38,7 @@ extension ItemView { } .seeMoreType(.view) .font(.footnote) - .ifLet(overviewLineLimit) { view, lineLimit in - view.lineLimit(lineLimit) - } + .lineLimit(overviewLineLimit) } } } diff --git a/Swiftfin/Views/ItemView/Components/PlayButton.swift b/Swiftfin/Views/ItemView/Components/PlayButton.swift index ed740d68..a86858b5 100644 --- a/Swiftfin/Views/ItemView/Components/PlayButton.swift +++ b/Swiftfin/Views/ItemView/Components/PlayButton.swift @@ -10,6 +10,8 @@ import Defaults import Factory import SwiftUI +// TODO: fix play from beginning + extension ItemView { struct PlayButton: View { diff --git a/Swiftfin/Views/ItemView/Components/SpecialFeatureHStack.swift b/Swiftfin/Views/ItemView/Components/SpecialFeatureHStack.swift index 3ff752ad..fb463814 100644 --- a/Swiftfin/Views/ItemView/Components/SpecialFeatureHStack.swift +++ b/Swiftfin/Views/ItemView/Components/SpecialFeatureHStack.swift @@ -23,7 +23,7 @@ extension ItemView { PosterHStack( title: L10n.specialFeatures, type: .landscape, - items: .constant(OrderedSet(items)) + items: items ) .onSelect { item in guard let mediaSource = item.mediaSources?.first else { return } diff --git a/Swiftfin/Views/ItemView/ItemView.swift b/Swiftfin/Views/ItemView/ItemView.swift index e232f4bc..bf2ea59e 100644 --- a/Swiftfin/Views/ItemView/ItemView.swift +++ b/Swiftfin/Views/ItemView/ItemView.swift @@ -11,6 +11,10 @@ import JellyfinAPI import SwiftUI import WidgetKit +// TODO: try to make views simpler so there isn't one per media type, but per view type +// - basic (episodes, collection) vs more fancy (rest) +// - think about future for other media types + struct ItemView: View { @StateObject diff --git a/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemContentView.swift b/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemContentView.swift index e818e58d..720a22d4 100644 --- a/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemContentView.swift +++ b/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemContentView.swift @@ -22,6 +22,35 @@ extension CollectionItemView { var body: some View { VStack(alignment: .leading, spacing: 20) { + VStack(alignment: .center) { + ImageView(viewModel.item.imageSource(.backdrop, maxWidth: 600)) + .posterStyle(.landscape, contentMode: .fill) + .frame(maxHeight: 300) + .posterShadow() + .edgePadding(.horizontal) + + Text(viewModel.item.displayTitle) + .font(.title2) + .fontWeight(.bold) + .multilineTextAlignment(.center) + .lineLimit(2) + .padding(.horizontal) + + ItemView.ActionButtonHStack(viewModel: viewModel) + .font(.title) + .frame(maxWidth: 300) + .foregroundStyle(.primary) + } + + // MARK: Overview + + ItemView.OverviewView(item: viewModel.item) + .overviewLineLimit(4) + .taglineLineLimit(2) + .padding(.horizontal) + + RowDivider() + // MARK: Genres if let genres = viewModel.item.itemGenres, genres.isNotEmpty { diff --git a/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemView.swift b/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemView.swift index 9325d018..142a2970 100644 --- a/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemView.swift +++ b/Swiftfin/Views/ItemView/iOS/CollectionItemView/CollectionItemView.swift @@ -12,26 +12,13 @@ import SwiftUI struct CollectionItemView: View { - @Default(.Customization.itemViewType) - private var itemViewType - @ObservedObject var viewModel: CollectionItemViewModel var body: some View { - switch itemViewType { - case .compactPoster: - ItemView.CompactPosterScrollView(viewModel: viewModel) { - ContentView(viewModel: viewModel) - } - case .compactLogo: - ItemView.CompactLogoScrollView(viewModel: viewModel) { - ContentView(viewModel: viewModel) - } - case .cinematic: - ItemView.CinematicScrollView(viewModel: viewModel) { - ContentView(viewModel: viewModel) - } + ScrollView { + ContentView(viewModel: viewModel) + .edgePadding(.bottom) } } } diff --git a/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift b/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift index d094eb54..5d7cf1be 100644 --- a/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift +++ b/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift @@ -39,6 +39,8 @@ extension EpisodeItemView { .overviewLineLimit(4) .padding(.horizontal) + RowDivider() + // MARK: Genres if let genres = viewModel.item.itemGenres, genres.isNotEmpty { diff --git a/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemView.swift b/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemView.swift index ae931fe2..784e89fe 100644 --- a/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemView.swift +++ b/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemView.swift @@ -11,25 +11,13 @@ import SwiftUI struct EpisodeItemView: View { - @EnvironmentObject - private var router: ItemCoordinator.Router - @ObservedObject var viewModel: EpisodeItemViewModel - @State - private var scrollViewOffset: CGFloat = 0 - var body: some View { - ScrollView(showsIndicators: false) { + ScrollView { ContentView(viewModel: viewModel) .edgePadding(.bottom) } - .scrollViewOffset($scrollViewOffset) - .navigationBarOffset( - $scrollViewOffset, - start: 0, - end: 10 - ) } } diff --git a/Swiftfin/Views/ItemView/iOS/ScrollViews/CinematicScrollView.swift b/Swiftfin/Views/ItemView/iOS/ScrollViews/CinematicScrollView.swift index 578ad84c..e8a62cc3 100644 --- a/Swiftfin/Views/ItemView/iOS/ScrollViews/CinematicScrollView.swift +++ b/Swiftfin/Views/ItemView/iOS/ScrollViews/CinematicScrollView.swift @@ -15,7 +15,7 @@ extension ItemView { struct CinematicScrollView: View { @Default(.Customization.CinematicItemViewType.usePrimaryImage) - private var cinematicItemViewTypeUsePrimaryImage + private var usePrimaryImage @EnvironmentObject private var router: ItemCoordinator.Router @@ -41,7 +41,7 @@ extension ItemView { @ViewBuilder private var headerView: some View { ImageView(viewModel.item.imageSource( - cinematicItemViewTypeUsePrimaryImage ? .primary : .backdrop, + usePrimaryImage ? .primary : .backdrop, maxWidth: UIScreen.main.bounds.width )) .aspectRatio(contentMode: .fill) @@ -60,59 +60,33 @@ extension ItemView { } var body: some View { - ScrollView(showsIndicators: false) { - VStack(spacing: 0) { - - VStack(spacing: 0) { - Spacer() - - OverlayView(viewModel: viewModel) - .padding(.horizontal) - .padding(.bottom) - .background { - BlurView(style: .systemThinMaterialDark) - .mask { - LinearGradient( - stops: [ - .init(color: .white.opacity(0), location: 0), - .init(color: .white, location: 0.3), - .init(color: .white, location: 1), - ], - startPoint: .top, - endPoint: .bottom - ) - } - } - .overlay { - Color.systemBackground - .opacity(topOpacity) - } - } - .frame(height: UIScreen.main.bounds.height * 0.8) - - content() - .padding(.vertical) - .background(Color.systemBackground) - } - } - .edgesIgnoringSafeArea(.top) - .scrollViewOffset($scrollViewOffset) - .navigationBarOffset( - $scrollViewOffset, - start: UIScreen.main.bounds.height * 0.66, - end: UIScreen.main.bounds.height * 0.66 + 50 - ) - .backgroundParallaxHeader( - $scrollViewOffset, - height: UIScreen.main.bounds.height * 0.8, - multiplier: 0.3 - ) { + OffsetScrollView(headerHeight: 0.75) { headerView - } - .topBarTrailing { - if viewModel.state == .refreshing { - ProgressView() + } overlay: { + VStack { + Spacer() + + OverlayView(viewModel: viewModel) + .edgePadding(.horizontal) + .padding(.bottom) + .background { + BlurView(style: .systemThinMaterialDark) + .mask { + LinearGradient( + stops: [ + .init(color: .white.opacity(0), location: 0), + .init(color: .white, location: 0.3), + .init(color: .white, location: 1), + ], + startPoint: .top, + endPoint: .bottom + ) + } + } } + } content: { + content() + .edgePadding(.vertical) } } } @@ -132,10 +106,9 @@ extension ItemView.CinematicScrollView { var body: some View { VStack(alignment: .leading, spacing: 10) { - VStack(alignment: .center, spacing: 10) { if !cinematicItemViewTypeUsePrimaryImage { - ImageView(viewModel.item.imageURL(.logo, maxWidth: UIScreen.main.bounds.width)) + ImageView(viewModel.item.imageURL(.logo, maxHeight: 100)) .placeholder { EmptyView() } @@ -147,11 +120,7 @@ extension ItemView.CinematicScrollView { .foregroundColor(.white) } .aspectRatio(contentMode: .fit) - .frame(height: 100) - .frame(maxWidth: .infinity) - } else { - Spacer() - .frame(height: 50) + .frame(height: 100, alignment: .bottom) } DotHStack { @@ -183,7 +152,7 @@ extension ItemView.CinematicScrollView { .frame(maxWidth: .infinity) ItemView.OverviewView(item: viewModel.item) - .overviewLineLimit(4) + .overviewLineLimit(3) .taglineLineLimit(2) .foregroundColor(.white) diff --git a/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactLogoScrollView.swift b/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactLogoScrollView.swift index 4a2239c6..318333f1 100644 --- a/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactLogoScrollView.swift +++ b/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactLogoScrollView.swift @@ -26,14 +26,6 @@ extension ItemView { let content: () -> Content - private var topOpacity: CGFloat { - let start = UIScreen.main.bounds.height * 0.25 - let end = UIScreen.main.bounds.height * 0.42 - 50 - let diff = end - start - let opacity = clamp((scrollViewOffset - start) / diff, min: 0, max: 1) - return opacity - } - @ViewBuilder private var headerView: some View { ImageView(viewModel.item.imageSource(.backdrop, maxHeight: UIScreen.main.bounds.height * 0.35)) @@ -53,57 +45,42 @@ extension ItemView { } var body: some View { - ScrollView(showsIndicators: false) { - VStack(alignment: .leading, spacing: 0) { + OffsetScrollView(headerHeight: 0.5) { + headerView + } overlay: { + VStack { + Spacer() - VStack { - Spacer() - - OverlayView(viewModel: viewModel) - .padding(.horizontal) - .padding(.bottom) - .background { - BlurView(style: .systemThinMaterialDark) - .mask { - LinearGradient( - stops: [ - .init(color: .clear, location: 0), - .init(color: .black, location: 0.3), - ], - startPoint: .top, - endPoint: .bottom - ) - } - } - .overlay { - Color.systemBackground - .opacity(topOpacity) - } - } - .frame(height: UIScreen.main.bounds.height * 0.5) + OverlayView(viewModel: viewModel) + .padding(.horizontal) + .padding(.bottom) + .background { + BlurView(style: .systemThinMaterialDark) + .mask { + LinearGradient( + stops: [ + .init(color: .clear, location: 0), + .init(color: .black, location: 0.3), + ], + startPoint: .top, + endPoint: .bottom + ) + } + } + } + } content: { + VStack(alignment: .leading, spacing: 10) { ItemView.OverviewView(item: viewModel.item) .overviewLineLimit(4) .taglineLineLimit(2) - .edgePadding() + .padding(.horizontal) + + RowDivider() content() - .edgePadding(.bottom) } - } - .edgesIgnoringSafeArea(.top) - .scrollViewOffset($scrollViewOffset) - .navigationBarOffset( - $scrollViewOffset, - start: UIScreen.main.bounds.height * 0.42 - 50, - end: UIScreen.main.bounds.height * 0.42 - ) - .backgroundParallaxHeader( - $scrollViewOffset, - height: UIScreen.main.bounds.height * 0.5, - multiplier: 0.3 - ) { - headerView + .edgePadding(.vertical) } } } diff --git a/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactPortraitScrollView.swift b/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactPortraitScrollView.swift index f33345cd..1cc0df8a 100644 --- a/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactPortraitScrollView.swift +++ b/Swiftfin/Views/ItemView/iOS/ScrollViews/CompactPortraitScrollView.swift @@ -53,64 +53,43 @@ extension ItemView { } var body: some View { - ScrollView(showsIndicators: false) { - VStack(alignment: .leading, spacing: 0) { + OffsetScrollView(headerHeight: 0.45) { + headerView + } overlay: { + VStack { + Spacer() - VStack { - Spacer() - - OverlayView(viewModel: viewModel, scrollViewOffset: $scrollViewOffset) - .padding(.horizontal) - .padding(.bottom) - .background { - BlurView(style: .systemThinMaterialDark) - .mask { - LinearGradient( - stops: [ - .init(color: .white.opacity(0), location: 0.2), - .init(color: .white.opacity(0.5), location: 0.3), - .init(color: .white, location: 0.55), - ], - startPoint: .top, - endPoint: .bottom - ) - } - } - .overlay { - Color.systemBackground - .opacity(topOpacity) - } - } - .frame(height: UIScreen.main.bounds.height * 0.45) + OverlayView(viewModel: viewModel) + .edgePadding(.horizontal) + .edgePadding(.bottom) + .background { + BlurView(style: .systemThinMaterialDark) + .mask { + LinearGradient( + stops: [ + .init(color: .white.opacity(0), location: 0.2), + .init(color: .white.opacity(0.5), location: 0.3), + .init(color: .white, location: 0.55), + ], + startPoint: .top, + endPoint: .bottom + ) + } + } + } + } content: { + VStack(alignment: .leading, spacing: 10) { ItemView.OverviewView(item: viewModel.item) .overviewLineLimit(4) .taglineLineLimit(2) - .padding(.top) .padding(.horizontal) + RowDivider() + content() - .padding(.vertical) - } - } - .edgesIgnoringSafeArea(.top) - .scrollViewOffset($scrollViewOffset) - .navigationBarOffset( - $scrollViewOffset, - start: UIScreen.main.bounds.height * 0.28, - end: UIScreen.main.bounds.height * 0.28 + 50 - ) - .backgroundParallaxHeader( - $scrollViewOffset, - height: UIScreen.main.bounds.height * 0.45, - multiplier: 0.8 - ) { - headerView - } - .topBarTrailing { - if viewModel.state == .refreshing { - ProgressView() } + .edgePadding(.vertical) } } } @@ -126,22 +105,15 @@ extension ItemView.CompactPosterScrollView { @ObservedObject var viewModel: ItemViewModel - @Binding - var scrollViewOffset: CGFloat - @ViewBuilder private var rightShelfView: some View { VStack(alignment: .leading) { - // MARK: Name - Text(viewModel.item.displayTitle) .font(.title2) .fontWeight(.semibold) .foregroundColor(.white) - // MARK: Details - DotHStack { if viewModel.item.isUnaired { if let premiereDateLabel = viewModel.item.airDateLabel { @@ -169,8 +141,6 @@ extension ItemView.CompactPosterScrollView { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .bottom, spacing: 12) { - // MARK: Portrait Image - ImageView(viewModel.item.imageSource(.primary, maxWidth: 130)) .failure { SystemImageContentView(systemName: viewModel.item.typeSystemImage) @@ -183,8 +153,6 @@ extension ItemView.CompactPosterScrollView { .padding(.bottom) } - // MARK: Play - HStack(alignment: .center) { ItemView.PlayButton(viewModel: viewModel) diff --git a/Swiftfin/Views/ItemView/iPadOS/ScrollViews/iPadOSCinematicScrollView.swift b/Swiftfin/Views/ItemView/iPadOS/ScrollViews/iPadOSCinematicScrollView.swift index c182a7fb..06b04084 100644 --- a/Swiftfin/Views/ItemView/iPadOS/ScrollViews/iPadOSCinematicScrollView.swift +++ b/Swiftfin/Views/ItemView/iPadOS/ScrollViews/iPadOSCinematicScrollView.swift @@ -8,31 +8,25 @@ import SwiftUI +// TODO: remove rest occurrences of `UIDevice.main` sizings +// TODO: overlay spacing between overview and play button should be dynamic +// - smaller spacing on smaller widths (iPad Mini, portrait) + +// landscape vs portrait ratios just "feel right". Adjust if necessary +// or if a concrete design comes along. + extension ItemView { struct iPadOSCinematicScrollView: View { - @EnvironmentObject - private var router: ItemCoordinator.Router - @ObservedObject var viewModel: ItemViewModel @State private var globalSize: CGSize = .zero - @State - private var scrollViewOffset: CGFloat = 0 let content: () -> Content - private var topOpacity: CGFloat { - let start = globalSize.isLandscape ? globalSize.height * 0.45 : globalSize.height * 0.25 - let end = globalSize.isLandscape ? globalSize.height * 0.65 : globalSize.height * 0.30 - let diff = end - start - let opacity = clamp((scrollViewOffset - start) / diff, min: 0, max: 1) - return opacity - } - @ViewBuilder private var headerView: some View { Group { @@ -43,56 +37,36 @@ extension ItemView { } } .aspectRatio(contentMode: .fill) - .frame(height: globalSize.isLandscape ? globalSize.height * 0.8 : globalSize.height * 0.4) } var body: some View { - ScrollView(showsIndicators: false) { - VStack(spacing: 0) { - VStack(spacing: 0) { - Spacer() - - OverlayView(viewModel: viewModel) - .edgePadding() - } - .frame(height: globalSize.isLandscape ? globalSize.height * 0.8 : globalSize.height * 0.4) - .background { - BlurView(style: .systemThinMaterialDark) - .mask { - LinearGradient( - stops: [ - .init(color: .clear, location: 0.4), - .init(color: .white, location: 0.8), - ], - startPoint: .top, - endPoint: .bottom - ) - } - } - .overlay { - Color.systemBackground - .opacity(topOpacity) - } - - content() - .padding(.vertical) - .background(Color.systemBackground) - } - } - .edgesIgnoringSafeArea(.top) - .edgesIgnoringSafeArea(.horizontal) - .scrollViewOffset($scrollViewOffset) - .navigationBarOffset( - $scrollViewOffset, - start: globalSize.isLandscape ? globalSize.height * 0.65 : globalSize.height * 0.30, - end: globalSize.isLandscape ? globalSize.height * 0.65 + 50 : globalSize.height * 0.30 + 50 - ) - .backgroundParallaxHeader( - $scrollViewOffset, - height: globalSize.isLandscape ? globalSize.height * 0.8 : globalSize.height * 0.4, - multiplier: 0.3 + OffsetScrollView( + headerHeight: globalSize.isLandscape ? 0.75 : 0.6 ) { headerView + } overlay: { + VStack(spacing: 0) { + Spacer() + + OverlayView(viewModel: viewModel) + .edgePadding() + } + .background { + BlurView(style: .systemThinMaterialDark) + .mask { + LinearGradient( + stops: [ + .init(color: .clear, location: 0.4), + .init(color: .white, location: 0.8), + ], + startPoint: .top, + endPoint: .bottom + ) + } + } + } content: { + content() + .edgePadding(.vertical) } .size($globalSize) } @@ -135,7 +109,7 @@ extension ItemView.iPadOSCinematicScrollView { ItemView.OverviewView(item: viewModel.item) .overviewLineLimit(3) - .taglineLineLimit(1) + .taglineLineLimit(2) .foregroundColor(.white) HStack(spacing: 30) { @@ -162,15 +136,22 @@ extension ItemView.iPadOSCinematicScrollView { Spacer() - VStack(spacing: 10) { - ItemView.PlayButton(viewModel: viewModel) - .frame(height: 50) + // TODO: remove when/if collections have a different view - ItemView.ActionButtonHStack(viewModel: viewModel) - .font(.title) - .foregroundColor(.white) + if !(viewModel is CollectionItemViewModel) { + VStack(spacing: 10) { + ItemView.PlayButton(viewModel: viewModel) + .frame(height: 50) + + ItemView.ActionButtonHStack(viewModel: viewModel) + .font(.title) + .foregroundColor(.white) + } + .frame(width: 250) + } else { + Color.clear + .frame(width: 250) } - .frame(width: 250) } } }