Files
honeyDueKMP/iosApp/iosApp/Onboarding/OnboardingWelcomeView.swift
Trey t b05e52521f Apply Warm Organic design system to all iOS views
- Full-screen views: Added WarmGradientBackground() to CompleteTaskView,
  ContractorDetailView, DocumentDetailView, DocumentFormView,
  FeatureComparisonView, TaskTemplatesBrowserView, ManageUsersView,
  ContractorPickerView

- Onboarding: Redesigned all 8 screens with organic styling including
  animated hero sections, gradient buttons, decorative blobs

- Components: Updated ErrorView, EmptyStateView, EmptyResidencesView,
  EmptyTasksView, TaskSuggestionsView, StatView, SummaryStatView,
  CompletionCardView, DynamicTaskColumnView with organic styling

- Applied consistent patterns: OrganicSpacing, naturalShadow modifier,
  RoundedRectangle with .continuous style, rounded font designs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 09:05:47 -06:00

211 lines
8.6 KiB
Swift

import SwiftUI
/// Screen 1: Welcome screen with Start Fresh / Join Existing options
struct OnboardingWelcomeView: View {
var onStartFresh: () -> Void
var onJoinExisting: () -> Void
var onLogin: () -> Void
@State private var showingLoginSheet = false
@State private var isAnimating = false
@State private var iconScale: CGFloat = 0.8
@State private var iconOpacity: Double = 0
var body: some View {
ZStack {
WarmGradientBackground()
// Decorative blobs
GeometryReader { geo in
OrganicBlobShape(variation: 0)
.fill(
RadialGradient(
colors: [
Color.appPrimary.opacity(0.08),
Color.appPrimary.opacity(0.02),
Color.clear
],
center: .center,
startRadius: 0,
endRadius: geo.size.width * 0.4
)
)
.frame(width: geo.size.width * 0.7, height: geo.size.height * 0.4)
.offset(x: -geo.size.width * 0.2, y: geo.size.height * 0.1)
.blur(radius: 30)
OrganicBlobShape(variation: 1)
.fill(
RadialGradient(
colors: [
Color.appAccent.opacity(0.06),
Color.appAccent.opacity(0.01),
Color.clear
],
center: .center,
startRadius: 0,
endRadius: geo.size.width * 0.3
)
)
.frame(width: geo.size.width * 0.5, height: geo.size.height * 0.3)
.offset(x: geo.size.width * 0.6, y: geo.size.height * 0.65)
.blur(radius: 25)
}
VStack(spacing: 0) {
Spacer()
// Hero section
VStack(spacing: OrganicSpacing.comfortable) {
// Animated icon with glow
ZStack {
// Outer pulsing glow
Circle()
.fill(
RadialGradient(
colors: [
Color.appPrimary.opacity(0.2),
Color.appPrimary.opacity(0.05),
Color.clear
],
center: .center,
startRadius: 0,
endRadius: 100
)
)
.frame(width: 200, height: 200)
.scaleEffect(isAnimating ? 1.1 : 1.0)
.animation(
Animation.easeInOut(duration: 2.5).repeatForever(autoreverses: true),
value: isAnimating
)
// App icon
Image("icon")
.resizable()
.scaledToFit()
.frame(width: 120, height: 120)
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
.naturalShadow(.pronounced)
.scaleEffect(iconScale)
.opacity(iconOpacity)
}
// Welcome text
VStack(spacing: 10) {
Text("Welcome to Casera")
.font(.system(size: 32, weight: .bold, design: .rounded))
.foregroundColor(Color.appTextPrimary)
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.welcomeTitle)
Text("Your home maintenance companion")
.font(.system(size: 17, weight: .medium))
.foregroundColor(Color.appTextSecondary)
.multilineTextAlignment(.center)
}
}
Spacer()
// Action buttons
VStack(spacing: 14) {
// Primary CTA - Start Fresh
Button(action: onStartFresh) {
HStack(spacing: 12) {
Image("icon")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
Text("Start Fresh")
.font(.system(size: 17, weight: .semibold))
}
.frame(maxWidth: .infinity)
.frame(height: 56)
.foregroundColor(Color.appTextOnPrimary)
.background(
LinearGradient(
colors: [Color.appPrimary, Color.appPrimary.opacity(0.85)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.naturalShadow(.medium)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.startFreshButton)
// Secondary CTA - Join Existing
Button(action: onJoinExisting) {
HStack(spacing: 12) {
Image(systemName: "person.2.fill")
.font(.system(size: 18, weight: .medium))
Text("I have a code to join")
.font(.system(size: 17, weight: .medium))
}
.frame(maxWidth: .infinity)
.frame(height: 56)
.foregroundColor(Color.appPrimary)
.background(Color.appPrimary.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(Color.appPrimary.opacity(0.2), lineWidth: 1)
)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.joinExistingButton)
// Returning user login
Button(action: {
showingLoginSheet = true
}) {
Text("Already have an account? Log in")
.font(.system(size: 15, weight: .medium))
.foregroundColor(Color.appTextSecondary)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.loginButton)
.padding(.top, 8)
}
.padding(.horizontal, OrganicSpacing.comfortable)
.padding(.bottom, OrganicSpacing.airy)
// Floating leaves decoration
HStack(spacing: 50) {
FloatingLeaf(delay: 0, size: 16, color: Color.appPrimary)
FloatingLeaf(delay: 0.5, size: 12, color: Color.appAccent)
FloatingLeaf(delay: 1.0, size: 18, color: Color.appPrimary)
}
.opacity(0.5)
.padding(.bottom, 20)
}
}
.sheet(isPresented: $showingLoginSheet) {
LoginView(onLoginSuccess: {
showingLoginSheet = false
onLogin()
})
}
.onAppear {
isAnimating = true
withAnimation(.spring(response: 0.8, dampingFraction: 0.7)) {
iconScale = 1.0
iconOpacity = 1.0
}
}
}
}
// MARK: - Content-only version (no navigation bar)
/// Content-only version for use in coordinator
typealias OnboardingWelcomeContent = OnboardingWelcomeView
// MARK: - Preview
#Preview {
OnboardingWelcomeView(
onStartFresh: {},
onJoinExisting: {},
onLogin: {}
)
}