SPB Git

spb/ka-ios

Public
10.9 KB · 242 lines swift
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
Files · ce73f14a9dd1fe80ae09bf638fcba97f1a74e08a
Menu

Theme