SPB Git forge

spb/ka-ios

Public
20commits 1branches 0releases
17.2 MBsize
maindefault branch
29 days agolast push
Swift 100%
5.8 KB · 136 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// OnboardingView.swift — le « waouh » de la première ouverture : animation du3// logo KA (boîte encre qui se redresse, lime qui s'allume), pitch en 3 lignes,4// choix des univers favoris, puis c'est parti. Court, spectaculaire, utile.5// Reduce Motion respecté (l'animation se fige proprement).6import SwiftUI78struct OnboardingView: View {9    @AppStorage("ka.onboarded") private var onboarded = false10    @AppStorage("ka.favUniverses") private var favUniversesRaw = ""11    @Environment(\.accessibilityReduceMotion) private var reduceMotion12    @Environment(\.colorScheme) private var scheme1314    @State private var step = 015    @State private var logoRotation: Double = -1416    @State private var logoScale: CGFloat = 0.617    @State private var glow = false18    @State private var picked: Set<String> = []1920    var body: some View {21        VStack(spacing: 0) {22            Spacer()23            logo24            Spacer()25            if step == 0 { pitch } else { pickUniverses }26            actions27        }28        .padding(24)29        .background {30            ZStack {31                KATheme.paper(scheme)32                KAAurora()33            }34            .ignoresSafeArea()35        }36        .onAppear {37            guard !reduceMotion else { logoRotation = -4; logoScale = 1; glow = true; return }38            withAnimation(.spring(response: 0.9, dampingFraction: 0.55).delay(0.15)) {39                logoRotation = -440                logoScale = 141            }42            withAnimation(.easeInOut(duration: 1.1).delay(0.5)) { glow = true }43        }44    }4546    private var logo: some View {47        VStack(spacing: 18) {48            ZStack {49                RoundedRectangle(cornerRadius: 30, style: .continuous)50                    .fill(KATheme.inkLight)51                    .frame(width: 128, height: 128)52                    .shadow(color: KATheme.lime.opacity(glow ? 0.55 : 0), radius: glow ? 36 : 0)53                Text("KA")54                    .font(.system(size: 56, weight: .bold, design: .rounded))55                    .foregroundStyle(KATheme.lime)56            }57            .rotationEffect(.degrees(logoRotation))58            .scaleEffect(logoScale)59            .accessibilityHidden(true)60            Text("Mille sites. Un seul KA.")61                .font(.system(.title2, design: .rounded).weight(.bold))62                .multilineTextAlignment(.center)63        }64    }6566    private var pitch: some View {67        VStack(alignment: .leading, spacing: 14) {68            pitchRow("magnifyingglass", "Une recherche, tout le Québec", "Logements, propriétés, autos, emplois, restos, sorties — interrogés d'un coup.")69            pitchRow("heart.fill", "Des favoris qui traversent les univers", "Un 4½, une auto et un resto dans une même collection.")70            pitchRow("sparkles", "KA Agent, votre IA d'ici", "Posez une question, il fouille les vraies données de l'écosystème.")71            pitchRow("location.north.fill", "Ka Trajet, le GPS maison", "Itinéraires, conduite 3D et les adresses de l'écosystème sur la carte.")72        }73        .padding(.bottom, 12)74    }7576    private func pitchRow(_ symbol: String, _ title: String, _ sub: String) -> some View {77        HStack(alignment: .top, spacing: 12) {78            Image(systemName: symbol)79                .font(.headline).foregroundStyle(KATheme.green)80                .frame(width: 34, height: 34)81                .background(KATheme.green.opacity(0.12), in: RoundedRectangle(cornerRadius: 9))82            VStack(alignment: .leading, spacing: 2) {83                Text(title).font(.subheadline.weight(.bold))84                Text(sub).font(.caption).foregroundStyle(.secondary)85            }86        }87    }8889    private var pickUniverses: some View {90        VStack(alignment: .leading, spacing: 10) {91            Text("Vos univers du quotidien ?")92                .font(.headline)93            Text("Ils remonteront en tête de votre accueil (modifiable dans Profil).")94                .font(.caption).foregroundStyle(.secondary)95            LazyVGrid(columns: [GridItem(.adaptive(minimum: 105), spacing: 8)], spacing: 8) {96                ForEach(Ecosystem.all) { u in97                    let on = picked.contains(u.id)98                    Button {99                        Haptics.tap()100                        if on { picked.remove(u.id) } else { picked.insert(u.id) }101                    } label: {102                        VStack(spacing: 4) {103                            Image(systemName: u.symbol).font(.subheadline)104                            Text(u.name).font(.caption2.weight(.bold)).lineLimit(1)105                        }106                        .frame(maxWidth: .infinity).padding(.vertical, 10)107                        .background(on ? u.accent : KATheme.surface(scheme), in: RoundedRectangle(cornerRadius: 11, style: .continuous))108                        .foregroundStyle(on ? .white : .primary)109                        .overlay(RoundedRectangle(cornerRadius: 11).strokeBorder(.primary.opacity(0.2), lineWidth: 1))110                    }111                }112            }113        }114        .padding(.bottom, 12)115    }116117    private var actions: some View {118        Button {119            Haptics.success()120            if step == 0 {121                withAnimation(.snappy) { step = 1 }122            } else {123                favUniversesRaw = picked.sorted().joined(separator: ",")124                withAnimation(.easeOut) { onboarded = true }125            }126        } label: {127            Text(step == 0 ? "Continuer" : picked.isEmpty ? "Explorer sans choisir" : "C'est parti 🚀")128                .font(.headline)129                .frame(maxWidth: .infinity).padding(.vertical, 15)130                .background(KATheme.inkLight, in: RoundedRectangle(cornerRadius: 14, style: .continuous))131                .foregroundStyle(KATheme.lime)132        }133        .padding(.top, 4)134    }135}136