1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// ProfileView.swift — profil : KA ID (compte unique de l'écosystème), univers3// favoris, apparence, contacts complets du Groupe KA, pages légales, à propos.4import SwiftUI56struct ProfileView: View {7 @AppStorage("ka.favUniverses") private var favUniversesRaw = ""8 @AppStorage("ka.appearance") private var appearance = "clair"9 @Environment(\.colorScheme) private var scheme10 @StateObject private var kaid = KAIDManager.shared11 @EnvironmentObject private var favorites: FavoritesStore12 @EnvironmentObject private var recents: RecentsStore13 @State private var showHubSite = false1415 private var favIDs: Set<String> {16 Set(favUniversesRaw.split(separator: ",").map(String.init))17 }1819 var body: some View {20 NavigationStack {21 List {22 Section {23 if let p = kaid.profile {24 KAIDCard(profile: p)25 .listRowInsets(EdgeInsets())26 .listRowBackground(Color.clear)27 Link(destination: Ecosystem.hubURL.appendingPathComponent("/compte")) {28 Label("Gérer mon profil sur groupe-ka.com", systemImage: "person.text.rectangle")29 }30 Button(role: .destructive) { kaid.logout() } label: {31 Label("Se déconnecter", systemImage: "rectangle.portrait.and.arrow.right")32 }33 } else {34 HStack(spacing: 14) {35 ZStack {36 RoundedRectangle(cornerRadius: 14, style: .continuous)37 .fill(KATheme.inkLight)38 .frame(width: 56, height: 56)39 Text("KA").font(.system(size: 22, weight: .bold, design: .rounded))40 .foregroundStyle(KATheme.lime)41 .rotationEffect(.degrees(-4))42 }43 VStack(alignment: .leading, spacing: 3) {44 Text("Votre KA ID").font(.headline)45 Text("Un seul compte pour les 13 plateformes de l'écosystème.")46 .font(.caption).foregroundStyle(.secondary)47 }48 }49 Button {50 kaid.login()51 } label: {52 HStack {53 Label("Se connecter avec KA ID", systemImage: "person.crop.circle.badge.checkmark")54 Spacer()55 if kaid.busy { ProgressView() }56 }57 }58 .disabled(kaid.busy)59 Link(destination: Ecosystem.signupURL) {60 Label("Créer un compte sur groupe-ka.com", systemImage: "arrow.up.right.square")61 }62 if let e = kaid.lastError {63 Text(e).font(.caption).foregroundStyle(.red)64 }65 }66 }6768 Section("Mes contenus") {69 NavigationLink { FavoritesView() } label: {70 Label("Favoris & collections", systemImage: "heart.fill")71 .badge(favorites.allItems.count)72 }73 NavigationLink { HistoryView() } label: {74 Label("Historique consulté", systemImage: "clock.arrow.circlepath")75 .badge(recents.viewed.count)76 }77 NavigationLink { SavedSearchesView() } label: {78 Label("Recherches sauvegardées & alertes", systemImage: "bell.badge")79 .badge(recents.alertCount > 0 ? "\(recents.alertCount) nouv." : "")80 }81 }8283 Section {84 ForEach(Ecosystem.all) { u in85 Button {86 var ids = favIDs87 if ids.contains(u.id) { ids.remove(u.id) } else { ids.insert(u.id) }88 favUniversesRaw = ids.sorted().joined(separator: ",")89 Haptics.tap()90 } label: {91 HStack {92 KALogo(universe: u, size: 26)93 Text(u.name).foregroundStyle(.primary)94 Spacer()95 if favIDs.contains(u.id) {96 Image(systemName: "star.fill").foregroundStyle(.yellow)97 }98 }99 }100 }101 } header: {102 Text("Mes univers favoris")103 } footer: {104 Text("Vos univers favoris remontent en tête de l'accueil.")105 }106107 Section("Apparence") {108 Picker("Thème", selection: $appearance) {109 Text("Automatique").tag("auto")110 Text("Clair").tag("clair")111 Text("Sombre").tag("sombre")112 }113 }114115 Section("Joindre le Groupe KA") {116 ForEach(Ecosystem.contacts, id: \.email) { c in117 Link(destination: URL(string: "mailto:\(c.email)")!) {118 VStack(alignment: .leading, spacing: 2) {119 Text(c.email).font(.subheadline.weight(.semibold))120 Text(c.role).font(.caption).foregroundStyle(.secondary)121 }122 }123 }124 Button { showHubSite = true } label: {125 HStack(spacing: 10) {126 KALogo(assetID: "groupe-ka", size: 22)127 Text("groupe-ka.com — le portail")128 }129 }130 Link(destination: Ecosystem.statusURL) {131 Label("État des services en direct", systemImage: "waveform.path.ecg")132 }133 }134135 Section("Légal") {136 ForEach(Ecosystem.legal, id: \.path) { l in137 Link(destination: Ecosystem.hubURL.appendingPathComponent(l.path)) {138 Text(l.label)139 }140 }141 Text(Ecosystem.disclaimer)142 .font(.caption2).foregroundStyle(.secondary)143 }144145 Section("À propos") {146 LabeledContent("Version", value: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0")147 LabeledContent("Écosystème", value: "\(Ecosystem.all.count) univers")148 Text("KA — tout l'écosystème Groupe-KA dans votre poche. Fait au Québec. 💚")149 .font(.caption).foregroundStyle(.secondary)150 }151 }152 .navigationTitle("Profil")153 .scrollContentBackground(.hidden)154 .background(KATheme.paper(scheme))155 .fullScreenCover(isPresented: $showHubSite) {156 SiteBrowserCover(title: "Groupe KA", accent: KATheme.green,157 logoID: "groupe-ka", url: Ecosystem.hubURL)158 }159 }160 }161}162163164// MARK: - Historique consulté165166struct HistoryView: View {167 @EnvironmentObject private var recents: RecentsStore168 @Environment(\.colorScheme) private var scheme169 var body: some View {170 ScrollView {171 LazyVStack(spacing: 10) {172 if recents.viewed.isEmpty {173 KAEmptyState(symbol: "clock", title: "Rien de consulté encore",174 message: "Les fiches que vous ouvrez apparaîtront ici.")175 }176 ForEach(recents.viewed) { item in177 NavigationLink(value: item) { KAItemRow(item: item) }178 .buttonStyle(KAPressStyle())179 }180 }181 .padding(14)182 }183 .background(KATheme.paper(scheme))184 .navigationTitle("Historique")185 .navigationDestination(for: KAItem.self) { ItemDetailView(item: $0) }186 .toolbar {187 if !recents.viewed.isEmpty {188 ToolbarItem(placement: .topBarTrailing) {189 Button("Effacer") { recents.clearHistory() }190 }191 }192 }193 }194}195196// MARK: - Recherches sauvegardées & alertes197198struct SavedSearchesView: View {199 @EnvironmentObject private var recents: RecentsStore200 @Environment(\.colorScheme) private var scheme201 var body: some View {202 List {203 if recents.savedSearches.isEmpty {204 KAEmptyState(symbol: "bell.slash", title: "Aucune recherche sauvegardée",205 message: "Dans Recherche, touchez 🔔 pour suivre une recherche : KA recomptera les résultats et affichera les nouveautés.")206 .listRowBackground(Color.clear)207 }208 ForEach($recents.savedSearches) { $s in209 VStack(alignment: .leading, spacing: 6) {210 HStack {211 Text(s.name).font(.headline)212 if s.newCount > 0 {213 Text("+\(s.newCount) nouveautés")214 .font(.system(size: 10, design: .monospaced).weight(.bold))215 .padding(.horizontal, 7).padding(.vertical, 3)216 .background(.red, in: Capsule()).foregroundStyle(.white)217 }218 Spacer()219 Toggle("", isOn: $s.alertsOn).labelsHidden()220 .accessibilityLabel("Alerte pour \(s.name)")221 }222 HStack(spacing: 8) {223 if let u = Ecosystem.universe(s.universeID) { KAChip(text: u.wordmark, accent: u.accent) }224 Text("« \(s.query) »").font(.caption).foregroundStyle(.secondary)225 Spacer()226 if let total = s.lastTotal {227 Text("\(total + s.newCount) résultats")228 .font(.system(.caption2, design: .monospaced))229 .foregroundStyle(.tertiary)230 }231 }232 if s.newCount > 0 {233 Button("Marquer comme vues") { recents.markSeen(s.id) }234 .font(.caption.weight(.semibold))235 }236 }237 .swipeActions {238 Button(role: .destructive) { recents.remove(s.id) } label: {239 Label("Supprimer", systemImage: "trash")240 }241 }242 }243 }244 .navigationTitle("Recherches & alertes")245 .refreshable { await recents.refreshAlerts() }246 .scrollContentBackground(.hidden)247 .background(KATheme.paper(scheme))248 }249}250