147 lines
4.9 KiB
Swift
147 lines
4.9 KiB
Swift
import SwiftUI
|
|
import Bedrock
|
|
|
|
/// The welcome screen shown as the first step of the setup wizard.
|
|
struct WelcomeStepView: View {
|
|
let onContinue: () -> Void
|
|
|
|
@State private var animateRings = false
|
|
@State private var animateText = false
|
|
@State private var animateButton = false
|
|
|
|
var body: some View {
|
|
VStack(spacing: Design.Spacing.xxLarge) {
|
|
Spacer()
|
|
|
|
// Animated rings visual
|
|
animatedRingsView
|
|
.frame(width: 200, height: 200)
|
|
.opacity(animateRings ? 1 : 0)
|
|
.scaleEffect(animateRings ? 1 : 0.8)
|
|
|
|
VStack(spacing: Design.Spacing.medium) {
|
|
Text(String(localized: "Welcome to Rituals"))
|
|
.typography(.heroBold)
|
|
.foregroundStyle(AppTextColors.primary)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text(String(localized: "Build lasting habits through focused, time-bound journeys"))
|
|
.typography(.body)
|
|
.foregroundStyle(AppTextColors.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
|
}
|
|
.opacity(animateText ? 1 : 0)
|
|
.offset(y: animateText ? 0 : 20)
|
|
|
|
Spacer()
|
|
|
|
// Get Started button
|
|
Button(action: onContinue) {
|
|
Text(String(localized: "Get Started"))
|
|
.typography(.heading)
|
|
.foregroundStyle(AppTextColors.inverse)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: AppMetrics.Size.buttonHeight)
|
|
.background(AppAccent.primary)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
}
|
|
.accessibilityIdentifier("onboarding.getStarted")
|
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
|
.opacity(animateButton ? 1 : 0)
|
|
.offset(y: animateButton ? 0 : 20)
|
|
|
|
Spacer()
|
|
.frame(height: Design.Spacing.xxLarge)
|
|
}
|
|
.onAppear {
|
|
startAnimations()
|
|
}
|
|
}
|
|
|
|
// MARK: - Animated Rings
|
|
|
|
private var animatedRingsView: some View {
|
|
ZStack {
|
|
// Outer ring
|
|
Circle()
|
|
.stroke(
|
|
AppAccent.primary.opacity(0.3),
|
|
style: StrokeStyle(lineWidth: 8, lineCap: .round)
|
|
)
|
|
.frame(width: 180, height: 180)
|
|
.rotationEffect(.degrees(animateRings ? 360 : 0))
|
|
.animation(
|
|
.linear(duration: 20).repeatForever(autoreverses: false),
|
|
value: animateRings
|
|
)
|
|
|
|
// Middle ring with arc
|
|
Circle()
|
|
.trim(from: 0, to: 0.7)
|
|
.stroke(
|
|
AppAccent.primary.opacity(0.6),
|
|
style: StrokeStyle(lineWidth: 10, lineCap: .round)
|
|
)
|
|
.frame(width: 140, height: 140)
|
|
.rotationEffect(.degrees(animateRings ? -360 : 0))
|
|
.animation(
|
|
.linear(duration: 15).repeatForever(autoreverses: false),
|
|
value: animateRings
|
|
)
|
|
|
|
// Inner ring with arc
|
|
Circle()
|
|
.trim(from: 0, to: 0.5)
|
|
.stroke(
|
|
AppAccent.primary,
|
|
style: StrokeStyle(lineWidth: 12, lineCap: .round)
|
|
)
|
|
.frame(width: 100, height: 100)
|
|
.rotationEffect(.degrees(animateRings ? 360 : 0))
|
|
.animation(
|
|
.linear(duration: 10).repeatForever(autoreverses: false),
|
|
value: animateRings
|
|
)
|
|
|
|
// Center icon
|
|
SymbolIcon("sparkles", size: .card, color: AppAccent.primary)
|
|
.scaleEffect(animateRings ? 1.1 : 1.0)
|
|
.animation(
|
|
.easeInOut(duration: 1.5).repeatForever(autoreverses: true),
|
|
value: animateRings
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Animations
|
|
|
|
private func startAnimations() {
|
|
// Stagger the animations
|
|
withAnimation(.easeOut(duration: 0.6)) {
|
|
animateRings = true
|
|
}
|
|
|
|
withAnimation(.easeOut(duration: 0.6).delay(0.3)) {
|
|
animateText = true
|
|
}
|
|
|
|
withAnimation(.easeOut(duration: 0.6).delay(0.6)) {
|
|
animateButton = true
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ZStack {
|
|
LinearGradient(
|
|
colors: [AppSurface.primary, AppSurface.secondary],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
.ignoresSafeArea()
|
|
|
|
WelcomeStepView(onContinue: {})
|
|
}
|
|
}
|