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: RecentsStore1314 private var favIDs: Set<String> {15 Set(favUniversesRaw.split(separator: ",").map(String.init))16 }1718 var body: some View {19 NavigationStack {20 List {21 Section {22 if let p = kaid.profile {23 KAIDCard(profile: p)24 .listRowInsets(EdgeInsets())25 .listRowBackground(Color.clear)26 Link(destination: Ecosystem.hubURL.appendingPathComponent("/compte")) {27 Label("Gérer mon profil sur groupe-ka.com", systemImage: "person.text.rectangle")28 }29 Button(role: .destructive) { kaid.logout() } label: {30 Label("Se déconnecter", systemImage: "rectangle.portrait.and.arrow.right")31 }32 } else {33 HStack(spacing: 14) {34 ZStack {35 RoundedRectangle(cornerRadius: 14, style: .continuous)36 .fill(KATheme.inkLight)37 .frame(width: 56, height: 56)38 Text("KA").font(.system(size: 22, weight: .bold, design: .rounded))39 .foregroundStyle(KATheme.lime)40 .rotationEffect(.degrees(-4))41 }42 VStack(alignment: .leading, spacing: 3) {43 Text("Votre KA ID").font(.headline)44 Text("Un seul compte pour les 13 plateformes de l'écosystème.")45 .font(.caption).foregroundStyle(.secondary)46 }47 }48 Button {49 kaid.login()50 } label: {51 HStack {52 Label("Se connecter avec KA ID", systemImage: "person.crop.circle.badge.checkmark")53 Spacer()54 if kaid.busy { ProgressView() }55 }56 }57 .disabled(kaid.busy)58 Link(destination: Ecosystem.signupURL) {59 Label("Créer un compte sur groupe-ka.com", systemImage: "arrow.up.right.square")60 }61 if let e = kaid.lastError {62 Text(e).font(.caption).foregroundStyle(.red)63 }64 }65 }6667 Section("Mes contenus") {68 NavigationLink { FavoritesView() } label: {69 Label("Favoris & collections", systemImage: "heart.fill")70 .badge(favorites.allItems.count)71 }72 NavigationLink { HistoryView() } label: {73 Label("Historique consulté", systemImage: "clock.arrow.circlepath")74 .badge(recents.viewed.count)75 }76 NavigationLink { SavedSearchesView() } label: {77 Label("Recherches sauvegardées & alertes", systemImage: "bell.badge")78 .badge(recents.alertCount > 0 ? "\(recents.alertCount) nouv." : "")79 }80 }8182 Section {83 ForEach(Ecosystem.all) { u in84 Button {85 var ids = favIDs86 if ids.contains(u.id) { ids.remove(u.id) } else { ids.insert(u.id) }87 favUniversesRaw = ids.sorted().joined(separator: ",")88 Haptics.tap()89 } label: {90 HStack {91 Image(systemName: u.symbol).foregroundStyle(u.accent).frame(width: 26)92 Text(u.name).foregroundStyle(.primary)93 Spacer()94 if favIDs.contains(u.id) {95 Image(systemName: "star.fill").foregroundStyle(.yellow)96 }97 }98 }99 }100 } header: {101 Text("Mes univers favoris")102 } footer: {103 Text("Vos univers favoris remontent en tête de l'accueil.")104 }105106 Section("Apparence") {107 Picker("Thème", selection: $appearance) {108 Text("Automatique").tag("auto")109 Text("Clair").tag("clair")110 Text("Sombre").tag("sombre")111 }112 }113114 Section("Joindre le Groupe KA") {115 ForEach(Ecosystem.contacts, id: \.email) { c in116 Link(destination: URL(string: "mailto:\(c.email)")!) {117 VStack(alignment: .leading, spacing: 2) {118 Text(c.email).font(.subheadline.weight(.semibold))119 Text(c.role).font(.caption).foregroundStyle(.secondary)120 }121 }122 }123 Link(destination: Ecosystem.hubURL) {124 Label("groupe-ka.com — le portail", systemImage: "globe")125 }126 Link(destination: Ecosystem.statusURL) {127 Label("État des services en direct", systemImage: "waveform.path.ecg")128 }129 }130131 Section("Légal") {132 ForEach(Ecosystem.legal, id: \.path) { l in133 Link(destination: Ecosystem.hubURL.appendingPathComponent(l.path)) {134 Text(l.label)135 }136 }137 Text(Ecosystem.disclaimer)138 .font(.caption2).foregroundStyle(.secondary)139 }140141 Section("À propos") {142 LabeledContent("Version", value: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0")143 LabeledContent("Écosystème", value: "\(Ecosystem.all.count) univers")144 Text("KA — tout l'écosystème Groupe-KA dans votre poche. Fait au Québec. 💚")145 .font(.caption).foregroundStyle(.secondary)146 }147 }148 .navigationTitle("Profil")149 .scrollContentBackground(.hidden)150 .background(KATheme.paper(scheme))151 }152 }153}154155156// MARK: - Historique consulté157158struct HistoryView: View {159 @EnvironmentObject private var recents: RecentsStore160 @Environment(\.colorScheme) private var scheme161 var body: some View {162 ScrollView {163 LazyVStack(spacing: 10) {164 if recents.viewed.isEmpty {165 KAEmptyState(symbol: "clock", title: "Rien de consulté encore",166 message: "Les fiches que vous ouvrez apparaîtront ici.")167 }168 ForEach(recents.viewed) { item in169 NavigationLink(value: item) { KAItemRow(item: item) }170 .buttonStyle(KAPressStyle())171 }172 }173 .padding(14)174 }175 .background(KATheme.paper(scheme))176 .navigationTitle("Historique")177 .navigationDestination(for: KAItem.self) { ItemDetailView(item: $0) }178 .toolbar {179 if !recents.viewed.isEmpty {180 ToolbarItem(placement: .topBarTrailing) {181 Button("Effacer") { recents.clearHistory() }182 }183 }184 }185 }186}187188// MARK: - Recherches sauvegardées & alertes189190struct SavedSearchesView: View {191 @EnvironmentObject private var recents: RecentsStore192 @Environment(\.colorScheme) private var scheme193 var body: some View {194 List {195 if recents.savedSearches.isEmpty {196 KAEmptyState(symbol: "bell.slash", title: "Aucune recherche sauvegardée",197 message: "Dans Recherche, touchez 🔔 pour suivre une recherche : KA recomptera les résultats et affichera les nouveautés.")198 .listRowBackground(Color.clear)199 }200 ForEach($recents.savedSearches) { $s in201 VStack(alignment: .leading, spacing: 6) {202 HStack {203 Text(s.name).font(.headline)204 if s.newCount > 0 {205 Text("+\(s.newCount) nouveautés")206 .font(.system(size: 10, design: .monospaced).weight(.bold))207 .padding(.horizontal, 7).padding(.vertical, 3)208 .background(.red, in: Capsule()).foregroundStyle(.white)209 }210 Spacer()211 Toggle("", isOn: $s.alertsOn).labelsHidden()212 .accessibilityLabel("Alerte pour \(s.name)")213 }214 HStack(spacing: 8) {215 if let u = Ecosystem.universe(s.universeID) { KAChip(text: u.wordmark, accent: u.accent) }216 Text("« \(s.query) »").font(.caption).foregroundStyle(.secondary)217 Spacer()218 if let total = s.lastTotal {219 Text("\(total + s.newCount) résultats")220 .font(.system(.caption2, design: .monospaced))221 .foregroundStyle(.tertiary)222 }223 }224 if s.newCount > 0 {225 Button("Marquer comme vues") { recents.markSeen(s.id) }226 .font(.caption.weight(.semibold))227 }228 }229 .swipeActions {230 Button(role: .destructive) { recents.remove(s.id) } label: {231 Label("Supprimer", systemImage: "trash")232 }233 }234 }235 }236 .navigationTitle("Recherches & alertes")237 .refreshable { await recents.refreshAlerts() }238 .scrollContentBackground(.hidden)239 .background(KATheme.paper(scheme))240 }241}242