// // 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(showsIndicators: false) { 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) } } } }