jellyflood/Swiftfin/Views/ItemView/Components/OffsetScrollView.swift

87 lines
3.0 KiB
Swift

//
// 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<Header: View, Overlay: View, Content: View>: 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)
}
}
}
}