Simplify the layout of the onboarding splash screen (#6320)

* Simplify the layout of the onboarding splash screen
* Re-organise OnboardingSplashScreen.
* Fix frame drops for real this time.
This commit is contained in:
Doug
2022-06-22 09:54:21 +01:00
committed by GitHub
parent 4e62819f49
commit 30283509f7
5 changed files with 114 additions and 123 deletions
@@ -29,8 +29,6 @@ struct OnboardingSplashScreen: View {
private var isLeftToRight: Bool { layoutDirection == .leftToRight }
private var pageCount: Int { viewModel.viewState.content.count }
/// The dimensions of the stack with the action buttons and page indicator.
@State private var overlayFrame: CGRect = .zero
/// A timer to automatically animate the pages.
@State private var pageTimer: Timer?
/// The amount of offset to apply when a drag gesture is in progress.
@@ -40,6 +38,61 @@ struct OnboardingSplashScreen: View {
@ObservedObject var viewModel: OnboardingSplashScreenViewModel.Context
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
Spacer()
.frame(height: OnboardingMetrics.spacerHeight(in: geometry))
// The main content of the carousel
HStack(alignment: .top, spacing: 0) {
// Add a hidden page at the start of the carousel duplicating the content of the last page
OnboardingSplashScreenPage(content: viewModel.viewState.content[pageCount - 1])
.frame(width: geometry.size.width)
ForEach(0..<pageCount, id: \.self) { index in
OnboardingSplashScreenPage(content: viewModel.viewState.content[index])
.frame(width: geometry.size.width)
}
}
.offset(x: pageOffset(in: geometry))
Spacer()
OnboardingSplashScreenPageIndicator(pageCount: pageCount,
pageIndex: viewModel.pageIndex)
.frame(width: geometry.size.width)
.padding(.bottom)
Spacer()
buttons
.frame(width: geometry.size.width)
.padding(.bottom, OnboardingMetrics.actionButtonBottomPadding)
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
Spacer()
.frame(height: OnboardingMetrics.spacerHeight(in: geometry))
}
.frame(maxHeight: .infinity)
.background(background.ignoresSafeArea().offset(x: pageOffset(in: geometry)))
.gesture(
DragGesture()
.onChanged(handleDragGestureChange)
.onEnded { handleDragGestureEnded($0, viewSize: geometry.size) }
)
}
.accentColor(theme.colors.accent)
.navigationBarHidden(true)
.onAppear {
startTimer()
}
.onDisappear { stopTimer() }
.track(screen: .welcome)
}
/// The main action buttons.
var buttons: some View {
VStack(spacing: 12) {
@@ -54,70 +107,23 @@ struct OnboardingSplashScreen: View {
.padding(12)
}
}
.padding(.horizontal, 16)
.readableFrame()
}
/// The only part of the UI that isn't inside of the carousel.
var overlay: some View {
VStack(spacing: 50) {
Color.clear
Color.clear
VStack {
OnboardingSplashScreenPageIndicator(pageCount: pageCount,
pageIndex: viewModel.pageIndex)
Spacer()
buttons
.padding(.horizontal, 16)
.frame(maxWidth: OnboardingMetrics.maxContentWidth)
Spacer()
}
.background(ViewFrameReader(frame: $overlayFrame))
@ViewBuilder
/// The view's background, showing a gradient in light mode and a solid colour in dark mode.
var background: some View {
if !theme.isDark {
LinearGradient(gradient: viewModel.viewState.backgroundGradient,
startPoint: .leading,
endPoint: .trailing)
.flipsForRightToLeftLayoutDirection(true)
} else {
theme.colors.background
}
}
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
// The main content of the carousel
HStack(spacing: 0) {
// Add a hidden page at the start of the carousel duplicating the content of the last page
OnboardingSplashScreenPage(content: viewModel.viewState.content[pageCount - 1],
overlayHeight: overlayFrame.height + geometry.safeAreaInsets.bottom)
.frame(width: geometry.size.width)
.tag(-1)
ForEach(0..<pageCount) { index in
OnboardingSplashScreenPage(content: viewModel.viewState.content[index],
overlayHeight: overlayFrame.height + geometry.safeAreaInsets.bottom)
.frame(width: geometry.size.width)
.tag(index)
}
}
.offset(x: (CGFloat(viewModel.pageIndex + 1) * -geometry.size.width) + dragOffset)
.gesture(
DragGesture()
.onChanged(handleDragGestureChange)
.onEnded { handleDragGestureEnded($0, viewSize: geometry.size) }
)
overlay
.frame(width: geometry.size.width)
}
}
.background(theme.colors.background.ignoresSafeArea())
.accentColor(theme.colors.accent)
.navigationBarHidden(true)
.onAppear {
startTimer()
}
.onDisappear { stopTimer() }
.track(screen: .welcome)
}
// MARK: - Animation
/// Starts the animation timer for an automatic carousel effect.
@@ -147,6 +153,11 @@ struct OnboardingSplashScreen: View {
pageTimer.invalidate()
}
/// The offset to apply to the `HStack` of pages.
private func pageOffset(in geometry: GeometryProxy) -> CGFloat {
(CGFloat(viewModel.pageIndex + 1) * -geometry.size.width) + dragOffset
}
// MARK: - Gestures
/// Whether or not a drag gesture is valid or not.