SPB Git forge

spb/ka-macos

Public
3commits 1branches 0releases
6.1 MBsize
maindefault branch
1 mo agolast push
Swift 98.3% Shell 1.7%
8.8 KB · 207 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// HomeView.swift — l'Accueil : le pouls de l'écosystème (compteurs live),3// collections éditoriales (requêtes RÉELLES vers les API — aucun contenu4// inventé), grille des 12 univers, reprise de l'historique récent.5import SwiftUI67struct HomeView: View {8    @EnvironmentObject private var pulse: EcosystemPulse9    @EnvironmentObject private var state: AppState10    @EnvironmentObject private var recents: RecentsStore11    @Environment(\.colorScheme) private var scheme1213    private var totalAll: Int {14        // le pouls global = somme des compteurs connus (sans les pages indexées ni le parc Vrai-Prix, hors offre "consommable")15        Ecosystem.all16            .filter { !["trouve-ka", "vrai-prix", "api-ka"].contains($0.id) }17            .compactMap { pulse.totals[$0.id] }18            .reduce(0, +)19    }2021    var body: some View {22        ScrollView {23            VStack(alignment: .leading, spacing: 22) {24                header25                if recents.alertCount > 0 { alertBanner }26                editorial27                universes28                if !recents.viewed.isEmpty { recentStrip }29                footer30            }31            .padding(20)32            .frame(maxWidth: 1300)33            .frame(maxWidth: .infinity)34        }35        .background(KATheme.paper(scheme))36    }3738    // MARK: en-tête3940    private var header: some View {41        VStack(alignment: .leading, spacing: 8) {42            GroupeKAMark(size: 30)43            Text("Le Québec entier, lu à la source.")44                .font(KAFont.display(22))45            HStack(spacing: 8) {46                if totalAll > 0 {47                    Text("\(totalAll.fr) éléments en direct")48                        .font(KAFont.mono(11))49                        .padding(.horizontal, 10).padding(.vertical, 5)50                        .background(KATheme.green.opacity(0.12), in: Capsule())51                        .foregroundStyle(KATheme.green)52                        .contentTransition(.numericText())53                }54                Text("\(pulse.servicesUp)/\(pulse.health.count) services en ligne")55                    .font(KAFont.mono(11))56                    .padding(.horizontal, 10).padding(.vertical, 5)57                    .background((pulse.servicesUp == pulse.health.count ? KATheme.green : .orange).opacity(0.12), in: Capsule())58                    .foregroundStyle(pulse.servicesUp == pulse.health.count ? KATheme.green : .orange)59                Spacer()60                Button {61                    state.openUniversalSearch()62                } label: {63                    Label("Recherche universelle", systemImage: "magnifyingglass")64                        .font(.caption.weight(.bold))65                        .padding(.horizontal, 12).padding(.vertical, 7)66                        .background(KATheme.inkLight, in: Capsule())67                        .foregroundStyle(KATheme.lime)68                }69                .buttonStyle(.plain)70                .help("⌘K — une barre pour tous les univers")71            }72        }73    }7475    private var alertBanner: some View {76        Button {77            state.selection = .alerts78        } label: {79            HStack(spacing: 10) {80                Image(systemName: "bell.badge.fill").foregroundStyle(.red)81                Text("**+\(recents.alertCount) nouveauté\(recents.alertCount > 1 ? "s" : "")** dans vos recherches sauvegardées")82                    .font(.subheadline)83                Spacer()84                Image(systemName: "chevron.right").font(.caption).foregroundStyle(.secondary)85            }86            .padding(13)87            .frame(maxWidth: .infinity, alignment: .leading)88            .kaCard(accent: .red)89        }90        .buttonStyle(KAPressStyle())91    }9293    // MARK: collections éditoriales (requêtes réelles)9495    private var editorial: some View {96        VStack(alignment: .leading, spacing: 10) {97            Text("En ce moment").font(.title3.weight(.bold))98            LazyVGrid(columns: [GridItem(.adaptive(minimum: 270), spacing: 12)], spacing: 12) {99                ForEach(Editorial.collections) { c in100                    let u = Ecosystem.universe(c.universeID)101                    Button {102                        state.openUniverse(c.universeID, query: c.query ?? "", params: c.params)103                    } label: {104                        HStack(spacing: 12) {105                            Image(systemName: c.symbol)106                                .font(.title3.weight(.semibold))107                                .foregroundStyle(u?.accent ?? .gray)108                                .frame(width: 42, height: 42)109                                .background((u?.accent ?? .gray).opacity(0.13),110                                            in: RoundedRectangle(cornerRadius: 11, style: .continuous))111                            VStack(alignment: .leading, spacing: 2) {112                                Text(c.title).font(.subheadline.weight(.bold))113                                Text(c.subtitle).font(.caption).foregroundStyle(KATheme.ink2(scheme))114                                    .lineLimit(2)115                            }116                            Spacer()117                            Image(systemName: "chevron.right").font(.caption).foregroundStyle(.tertiary)118                        }119                        .padding(12)120                        .frame(maxWidth: .infinity, alignment: .leading)121                        .kaCard(accent: u?.accent)122                    }123                    .buttonStyle(KAPressStyle())124                }125            }126        }127    }128129    // MARK: grille des univers130131    private var universes: some View {132        VStack(alignment: .leading, spacing: 10) {133            Text("Les univers").font(.title3.weight(.bold))134            LazyVGrid(columns: [GridItem(.adaptive(minimum: 215), spacing: 13)], spacing: 13) {135                ForEach(Ecosystem.all) { u in136                    Button {137                        state.selection = .universe(u.id)138                    } label: {139                        VStack(alignment: .leading, spacing: 9) {140                            HStack {141                                KALogo(id: u.id, size: 38,142                                       fallbackSymbol: u.symbol, fallbackAccent: u.accent)143                                Spacer()144                                Image(systemName: "chevron.right").font(.caption).foregroundStyle(.tertiary)145                            }146                            KAWordmark(universe: u, size: 16)147                            Text(u.tagline)148                                .font(.caption).foregroundStyle(KATheme.ink2(scheme))149                                .lineLimit(2, reservesSpace: true)150                                .multilineTextAlignment(.leading)151                            if let total = pulse.totals[u.id] {152                                Text("\(total.fr) \(u.unit)")153                                    .font(KAFont.mono(10))154                                    .foregroundStyle(u.accent)155                                    .contentTransition(.numericText())156                            } else {157                                Text("—").font(KAFont.mono(10, bold: false))158                                    .foregroundStyle(.tertiary)159                            }160                        }161                        .padding(13)162                        .frame(maxWidth: .infinity, alignment: .leading)163                        .kaCard(accent: u.accent)164                    }165                    .buttonStyle(KAPressStyle())166                    .accessibilityLabel("\(u.name) — \(u.tagline)")167                }168            }169        }170    }171172    // MARK: reprise (derniers consultés)173174    private var recentStrip: some View {175        VStack(alignment: .leading, spacing: 10) {176            HStack {177                Text("Reprendre où vous étiez").font(.title3.weight(.bold))178                Spacer()179                Button("Tout l'historique") { state.selection = .history }180                    .buttonStyle(.plain)181                    .font(.caption.weight(.semibold))182                    .foregroundStyle(KATheme.green)183            }184            ScrollView(.horizontal, showsIndicators: false) {185                HStack(spacing: 11) {186                    ForEach(recents.viewed.prefix(8)) { item in187                        Button {188                            state.openUniverse(item.universeID, query: item.title)189                        } label: {190                            KAItemCard(item: item)191                                .frame(width: 230)192                        }193                        .buttonStyle(KAPressStyle())194                    }195                }196                .padding(.vertical, 2)197            }198        }199    }200201    private var footer: some View {202        Text(Ecosystem.disclaimer)203            .font(.caption2).foregroundStyle(.tertiary)204            .padding(.top, 4)205    }206}207