SPB Git forge

spb/ka-ios

Public
20commits 1branches 0releases
17.2 MBsize
maindefault branch
29 days agolast push
Swift 100%
4.7 KB · 126 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// KAApp.swift — point d'entrée : onboarding animé au premier lancement, puis3// la super-app v3 : 6 onglets (dont Ka Trajet, le GPS maison) servis par le4// dock flottant signature (encre + lime) avec KA Agent intégré.5import SwiftUI67@main8struct KAApp: App {9    @AppStorage("ka.onboarded") private var onboarded = false10    @AppStorage("ka.appearance") private var appearance = "clair"11    @StateObject private var favorites = FavoritesStore.shared1213    init() {14        // cache réseau généreux : images et JSON fluides, consultation hors ligne15        URLCache.shared = URLCache(memoryCapacity: 64 * 1024 * 1024,16                                   diskCapacity: 512 * 1024 * 1024)17    }1819    var body: some Scene {20        WindowGroup {21            Group {22                if onboarded {23                    RootView()24                } else {25                    OnboardingView()26                }27            }28            .environmentObject(favorites)29            .environmentObject(RecentsStore.shared)30            .preferredColorScheme(appearance == "clair" ? .light : appearance == "sombre" ? .dark : nil)31        }32    }33}3435// MARK: - Racine : 6 onglets servis par le dock flottant signature3637struct RootView: View {38    @State private var showAgent = false39    // onglet initial pilotable en debug — JAMAIS pendant les tests (les threads40    // MapLibre de la carte feraient une course avec le exit() de XCTest)41    @State private var tab: Int42    // onglets déjà visités : montés une fois, gardés vivants (état préservé,43    // cartes MapLibre non réinitialisées) — les autres restent paresseux44    @State private var loaded: Set<Int>45    @StateObject private var recents = RecentsStore.shared46    @StateObject private var trajet = TrajetModel()4748    init() {49        let initial = ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil50            ? 0 : UserDefaults.standard.integer(forKey: "ka.debug.tab")51        _tab = State(initialValue: initial)52        _loaded = State(initialValue: [initial])53    }5455    /// Le dock s'efface dans les modes immersifs de Trajet56    /// (conduite et Découvrir plein écran)57    private var dockVisible: Bool { !trajet.navigating && !trajet.discovering }58    @Environment(\.colorScheme) private var scheme5960    var body: some View {61        // le dock occupe SON espace, sous le contenu : rien ne passe jamais62        // dessous, sur aucune page — c'est une vraie barre, pas un flotteur63        VStack(spacing: 0) {64            ZStack {65                pane(0) { HomeView() }66                pane(1) { SearchView() }67                pane(2) { UniversesView() }68                pane(3) { MapTab() }69                pane(4) { TrajetView().environmentObject(trajet) }70                pane(5) { MoreTab() }71            }72            if dockVisible {73                KADock(tab: $tab, alertCount: recents.alertCount) { showAgent = true }74                    .padding(.top, 8)75                    .padding(.bottom, 2)76                    .transition(.move(edge: .bottom).combined(with: .opacity))77            }78        }79        .background(KATheme.paper(scheme).ignoresSafeArea())80        .tint(KATheme.green)81        .animation(.spring(response: 0.4, dampingFraction: 0.8), value: trajet.navigating)82        .animation(.spring(response: 0.4, dampingFraction: 0.8), value: trajet.discovering)83        .onChange(of: tab) { _, t in loaded.insert(t) }84        // navigation demandée par une vue (ex. pilule de recherche de l'accueil)85        .onReceive(NotificationCenter.default.publisher(for: .kaSwitchTab)) { note in86            if let i = note.object as? Int, (0..<6).contains(i) {87                withAnimation(.spring(response: 0.35, dampingFraction: 0.8)) { tab = i }88            }89        }90        .task { await recents.refreshAlerts() }91        .sheet(isPresented: $showAgent) {92            AgentChatView()93                .presentationDetents([.large])94        }95    }9697    @ViewBuilder98    private func pane(_ i: Int, @ViewBuilder content: () -> some View) -> some View {99        if loaded.contains(i) {100            content()101                .opacity(tab == i ? 1 : 0)102                .allowsHitTesting(tab == i)103                .accessibilityHidden(tab != i)104        }105    }106}107108extension Notification.Name {109    /// Demande de changement d'onglet (object = index Int)110    static let kaSwitchTab = Notification.Name("ka.switch.tab")111}112113/// Onglet Carte : la carte unifiée directement (pas de fermeture — c'est un onglet)114struct MapTab: View {115    var body: some View {116        UnifiedMapView(embedded: true)117    }118}119120/// Onglet Profil — inclut Favoris, Historique et Recherches sauvegardées.121struct MoreTab: View {122    var body: some View {123        ProfileView()124    }125}126