v2.0.0 — logos officiels des 13 marques + sites web intégrés (WKWebView)
- Logos ka-ui (512 px) embarqués en ressources SwiftPM (Bundle.module), helper KALogo avec repli symbole SF : sidebar, accueil, héros d'explorateur, pouls du MenuBarExtra. - SiteView : navigateur WKWebView intégré (retour/avancer, recharger, domaine courant, partager, ouvrir dans le navigateur, progression teintée accent) ; tel:/mailto: vers le système ; target=_blank gardé dans la vue. - Bascule « Données | Site » dans la barre d'outils de chaque univers ; entrée sidebar + menu Univers « groupe-ka.com » (hub intégré). - package-app.sh copie le bundle de ressources SPM ; version 2.0.0 (2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23 changed files +295 −43
modified
Package.swift
+1 −0
@@ -11,6 +11,7 @@ let package = Package( | ||
| 11 | 11 | .executableTarget( |
| 12 | 12 | name: "KA", |
| 13 | 13 | path: "Sources/KA", |
| 14 | + resources: [.copy("Resources/Logos")], | |
| 14 | 15 | linkerSettings: [.linkedFramework("Carbon")] |
| 15 | 16 | ) |
| 16 | 17 | ] |
modified
Sources/KA/App/KAApp.swift
+15 −0
@@ -12,6 +12,8 @@ enum SidebarSelection: Hashable { | ||
| 12 | 12 | case home, search, map, agent, status |
| 13 | 13 | case favorites, history, alerts |
| 14 | 14 | case universe(String) |
| 15 | + /// Le site du hub groupe-ka.com, intégré (WKWebView). | |
| 16 | + case hub | |
| 15 | 17 | } |
| 16 | 18 | |
| 17 | 19 | @MainActor |
@@ -178,6 +180,8 @@ struct KAApp: App { | ||
| 178 | 180 | ForEach(Array(Ecosystem.all.enumerated()), id: \.element.id) { i, u in |
| 179 | 181 | UniverseCommand(universe: u, index: i) |
| 180 | 182 | } |
| 183 | + Divider() | |
| 184 | + HubCommand() | |
| 181 | 185 | } |
| 182 | 186 | } |
| 183 | 187 | |
@@ -255,6 +259,17 @@ private struct SearchCommands: View { | ||
| 255 | 259 | } |
| 256 | 260 | } |
| 257 | 261 | |
| 262 | +/// Le hub groupe-ka.com, intégré dans l'app (menu Univers). | |
| 263 | +private struct HubCommand: View { | |
| 264 | + @Environment(\.openWindow) private var openWindow | |
| 265 | + var body: some View { | |
| 266 | + Button("groupe-ka.com — le hub") { | |
| 267 | + focusMainWindow(openWindow) | |
| 268 | + AppState.shared.selection = .hub | |
| 269 | + } | |
| 270 | + } | |
| 271 | +} | |
| 272 | + | |
| 258 | 273 | /// Menu « Univers » : ⌘1..⌘9 pour les 9 premiers. |
| 259 | 274 | private struct UniverseCommand: View { |
| 260 | 275 | let universe: Universe |
modified
Sources/KA/DesignMac/Theme.swift
+41 −0
@@ -95,6 +95,47 @@ struct GroupeKAMark: View { | ||
| 95 | 95 | } |
| 96 | 96 | } |
| 97 | 97 | |
| 98 | +// MARK: - Logo officiel d'une marque (PNG ka-ui embarqué, repli symbole SF) | |
| 99 | + | |
| 100 | +/// Vrai logo de la marque (tuile encre + « Ka » accent, rendu depuis ka-ui). | |
| 101 | +/// `id` = id d'univers (« lou-ka »…) ou « groupe-ka » pour le hub. | |
| 102 | +struct KALogo: View { | |
| 103 | + let id: String | |
| 104 | + var size: CGFloat = 20 | |
| 105 | + /// Repli si le PNG manque (ex. id inconnu) — symbole SF teinté accent. | |
| 106 | + var fallbackSymbol: String = "square.grid.2x2" | |
| 107 | + var fallbackAccent: Color = .gray | |
| 108 | + | |
| 109 | + private static var cache: [String: NSImage] = [:] | |
| 110 | + | |
| 111 | + static func image(_ id: String) -> NSImage? { | |
| 112 | + if let hit = cache[id] { return hit } | |
| 113 | + guard let url = Bundle.module.url(forResource: "logo-\(id)", withExtension: "png", | |
| 114 | + subdirectory: "Logos"), | |
| 115 | + let img = NSImage(contentsOf: url) else { return nil } | |
| 116 | + cache[id] = img | |
| 117 | + return img | |
| 118 | + } | |
| 119 | + | |
| 120 | + var body: some View { | |
| 121 | + if let img = Self.image(id) { | |
| 122 | + Image(nsImage: img) | |
| 123 | + .resizable() | |
| 124 | + .interpolation(.high) | |
| 125 | + .aspectRatio(contentMode: .fit) | |
| 126 | + .frame(width: size, height: size) | |
| 127 | + .clipShape(RoundedRectangle(cornerRadius: size * 0.22, style: .continuous)) | |
| 128 | + .accessibilityHidden(true) | |
| 129 | + } else { | |
| 130 | + Image(systemName: fallbackSymbol) | |
| 131 | + .font(.system(size: size * 0.6, weight: .semibold)) | |
| 132 | + .foregroundStyle(fallbackAccent) | |
| 133 | + .frame(width: size, height: size) | |
| 134 | + .accessibilityHidden(true) | |
| 135 | + } | |
| 136 | + } | |
| 137 | +} | |
| 138 | + | |
| 98 | 139 | // MARK: - Puce mono (klabel) |
| 99 | 140 | |
| 100 | 141 | struct KAChip: View { |
modified
Sources/KA/Features/HomeView.swift
+2 −5
@@ -138,11 +138,8 @@ struct HomeView: View { | ||
| 138 | 138 | } label: { |
| 139 | 139 | VStack(alignment: .leading, spacing: 9) { |
| 140 | 140 | HStack { |
| 141 | − Image(systemName: u.symbol) | |
| 142 | − .font(.title3.weight(.semibold)) | |
| 143 | − .foregroundStyle(u.accent) | |
| 144 | − .frame(width: 38, height: 38) | |
| 145 | − .background(u.accent.opacity(0.14), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) | |
| 141 | + KALogo(id: u.id, size: 38, | |
| 142 | + fallbackSymbol: u.symbol, fallbackAccent: u.accent) | |
| 146 | 143 | Spacer() |
| 147 | 144 | Image(systemName: "chevron.right").font(.caption).foregroundStyle(.tertiary) |
| 148 | 145 | } |
modified
Sources/KA/Features/MainWindow.swift
+12 −4
@@ -43,10 +43,8 @@ struct MainWindowView: View { | ||
| 43 | 43 | Section("Univers") { |
| 44 | 44 | ForEach(Ecosystem.all) { u in |
| 45 | 45 | HStack(spacing: 8) { |
| 46 | − Image(systemName: u.symbol) | |
| 47 | − .font(.caption.weight(.semibold)) | |
| 48 | − .foregroundStyle(u.accent) | |
| 49 | − .frame(width: 18) | |
| 46 | + KALogo(id: u.id, size: 18, | |
| 47 | + fallbackSymbol: u.symbol, fallbackAccent: u.accent) | |
| 50 | 48 | KAWordmark(universe: u, size: 12) |
| 51 | 49 | Spacer(minLength: 4) |
| 52 | 50 | if let n = pulse.totals[u.id] { |
@@ -58,6 +56,13 @@ struct MainWindowView: View { | ||
| 58 | 56 | } |
| 59 | 57 | .tag(SidebarSelection.universe(u.id)) |
| 60 | 58 | } |
| 59 | + HStack(spacing: 8) { | |
| 60 | + KALogo(id: "groupe-ka", size: 18) | |
| 61 | + Text("groupe-ka.com") | |
| 62 | + .font(.system(size: 12, weight: .bold, design: .rounded)) | |
| 63 | + } | |
| 64 | + .tag(SidebarSelection.hub) | |
| 65 | + .help("Le hub Groupe KA — le site, intégré dans l'app") | |
| 61 | 66 | } |
| 62 | 67 | |
| 63 | 68 | Section("Bibliothèque") { |
@@ -113,6 +118,9 @@ struct MainWindowView: View { | ||
| 113 | 118 | case .favorites: FavoritesView() |
| 114 | 119 | case .history: HistoryView() |
| 115 | 120 | case .alerts: AlertsView() |
| 121 | + case .hub: | |
| 122 | + SiteView(url: Ecosystem.hubURL, title: "Groupe KA", | |
| 123 | + accent: KATheme.green, logoID: "groupe-ka") | |
| 116 | 124 | case .universe(let id): |
| 117 | 125 | if let u = Ecosystem.universe(id) { |
| 118 | 126 | UniverseExplorerView(universe: u) |
modified
Sources/KA/Features/MenuBarView.swift
+2 −4
@@ -73,10 +73,8 @@ struct MenuBarView: View { | ||
| 73 | 73 | focusMainWindow(openWindow) |
| 74 | 74 | } label: { |
| 75 | 75 | HStack(spacing: 6) { |
| 76 | − Image(systemName: u.symbol) | |
| 77 | − .font(.caption2.weight(.semibold)) | |
| 78 | − .foregroundStyle(u.accent) | |
| 79 | − .frame(width: 14) | |
| 76 | + KALogo(id: u.id, size: 14, | |
| 77 | + fallbackSymbol: u.symbol, fallbackAccent: u.accent) | |
| 80 | 78 | KAWordmark(universe: u, size: 10) |
| 81 | 79 | Spacer(minLength: 2) |
| 82 | 80 | Text(pulse.totals[u.id].map { Double($0).compact } ?? "—") |
added
Sources/KA/Features/SiteView.swift
+184 −0
@@ -0,0 +1,184 @@ | ||
| 1 | +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +// SiteView.swift — le SITE WEB d'une plateforme Ka, intégré dans l'app : | |
| 3 | +// WKWebView plein volet avec chrome minimal (retour/avancer, recharger, | |
| 4 | +// domaine courant, partager, ouvrir dans le navigateur) et barre de | |
| 5 | +// progression teintée à l'accent de l'univers. Les liens externes non | |
| 6 | +// web (tel:, mailto:…) partent vers le système. | |
| 7 | +import SwiftUI | |
| 8 | +import WebKit | |
| 9 | + | |
| 10 | +// MARK: - Modèle observable du WKWebView (progression, historique, titre) | |
| 11 | + | |
| 12 | +@MainActor | |
| 13 | +final class SiteWebModel: NSObject, ObservableObject, WKNavigationDelegate, WKUIDelegate { | |
| 14 | + let webView: WKWebView | |
| 15 | + @Published var progress: Double = 0 | |
| 16 | + @Published var isLoading = false | |
| 17 | + @Published var canGoBack = false | |
| 18 | + @Published var canGoForward = false | |
| 19 | + @Published var currentURL: URL? | |
| 20 | + | |
| 21 | + private var observers: [NSKeyValueObservation] = [] | |
| 22 | + | |
| 23 | + init(url: URL) { | |
| 24 | + let config = WKWebViewConfiguration() | |
| 25 | + config.defaultWebpagePreferences.allowsContentJavaScript = true | |
| 26 | + webView = WKWebView(frame: .zero, configuration: config) | |
| 27 | + webView.allowsBackForwardNavigationGestures = true | |
| 28 | + webView.allowsMagnification = true | |
| 29 | + super.init() | |
| 30 | + webView.navigationDelegate = self | |
| 31 | + webView.uiDelegate = self | |
| 32 | + observers = [ | |
| 33 | + webView.observe(\.estimatedProgress) { [weak self] wv, _ in | |
| 34 | + Task { @MainActor in self?.progress = wv.estimatedProgress } | |
| 35 | + }, | |
| 36 | + webView.observe(\.isLoading) { [weak self] wv, _ in | |
| 37 | + Task { @MainActor in self?.isLoading = wv.isLoading } | |
| 38 | + }, | |
| 39 | + webView.observe(\.canGoBack) { [weak self] wv, _ in | |
| 40 | + Task { @MainActor in self?.canGoBack = wv.canGoBack } | |
| 41 | + }, | |
| 42 | + webView.observe(\.canGoForward) { [weak self] wv, _ in | |
| 43 | + Task { @MainActor in self?.canGoForward = wv.canGoForward } | |
| 44 | + }, | |
| 45 | + webView.observe(\.url) { [weak self] wv, _ in | |
| 46 | + Task { @MainActor in self?.currentURL = wv.url } | |
| 47 | + }, | |
| 48 | + ] | |
| 49 | + currentURL = url | |
| 50 | + webView.load(URLRequest(url: url)) | |
| 51 | + } | |
| 52 | + | |
| 53 | + // Les schémas non web (tel:, mailto:, facetime:…) partent vers le système. | |
| 54 | + nonisolated func webView(_ webView: WKWebView, | |
| 55 | + decidePolicyFor navigationAction: WKNavigationAction, | |
| 56 | + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { | |
| 57 | + guard let url = navigationAction.request.url else { return decisionHandler(.allow) } | |
| 58 | + if let scheme = url.scheme?.lowercased(), scheme != "http", scheme != "https", scheme != "about" { | |
| 59 | + NSWorkspace.shared.open(url) | |
| 60 | + return decisionHandler(.cancel) | |
| 61 | + } | |
| 62 | + decisionHandler(.allow) | |
| 63 | + } | |
| 64 | + | |
| 65 | + // target="_blank" : on reste dans la même vue plutôt que d'ouvrir une fenêtre. | |
| 66 | + nonisolated func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, | |
| 67 | + for navigationAction: WKNavigationAction, | |
| 68 | + windowFeatures: WKWindowFeatures) -> WKWebView? { | |
| 69 | + if let url = navigationAction.request.url { | |
| 70 | + Task { @MainActor in webView.load(URLRequest(url: url)) } | |
| 71 | + } | |
| 72 | + return nil | |
| 73 | + } | |
| 74 | +} | |
| 75 | + | |
| 76 | +private struct SiteWebViewRepresentable: NSViewRepresentable { | |
| 77 | + let model: SiteWebModel | |
| 78 | + func makeNSView(context: Context) -> WKWebView { model.webView } | |
| 79 | + func updateNSView(_ nsView: WKWebView, context: Context) {} | |
| 80 | +} | |
| 81 | + | |
| 82 | +// MARK: - Le navigateur intégré (chrome + progression + web) | |
| 83 | + | |
| 84 | +struct SiteView: View { | |
| 85 | + let url: URL | |
| 86 | + var title: String | |
| 87 | + var accent: Color | |
| 88 | + var logoID: String? | |
| 89 | + @StateObject private var model: SiteWebModel | |
| 90 | + @Environment(\.colorScheme) private var scheme | |
| 91 | + | |
| 92 | + init(url: URL, title: String, accent: Color, logoID: String? = nil) { | |
| 93 | + self.url = url | |
| 94 | + self.title = title | |
| 95 | + self.accent = accent | |
| 96 | + self.logoID = logoID | |
| 97 | + _model = StateObject(wrappedValue: SiteWebModel(url: url)) | |
| 98 | + } | |
| 99 | + | |
| 100 | + init(universe: Universe) { | |
| 101 | + self.init(url: universe.baseURL, title: universe.name, | |
| 102 | + accent: universe.accent, logoID: universe.id) | |
| 103 | + } | |
| 104 | + | |
| 105 | + var body: some View { | |
| 106 | + VStack(spacing: 0) { | |
| 107 | + chrome | |
| 108 | + ZStack(alignment: .top) { | |
| 109 | + SiteWebViewRepresentable(model: model) | |
| 110 | + if model.isLoading { | |
| 111 | + GeometryReader { geo in | |
| 112 | + Rectangle() | |
| 113 | + .fill(accent) | |
| 114 | + .frame(width: geo.size.width * max(model.progress, 0.06), height: 2.5) | |
| 115 | + .animation(.easeOut(duration: 0.25), value: model.progress) | |
| 116 | + } | |
| 117 | + .frame(height: 2.5) | |
| 118 | + .accessibilityHidden(true) | |
| 119 | + } | |
| 120 | + } | |
| 121 | + } | |
| 122 | + .background(KATheme.paper(scheme)) | |
| 123 | + } | |
| 124 | + | |
| 125 | + private var chrome: some View { | |
| 126 | + HStack(spacing: 10) { | |
| 127 | + if let logoID { | |
| 128 | + KALogo(id: logoID, size: 20, fallbackAccent: accent) | |
| 129 | + } | |
| 130 | + Button { model.webView.goBack() } label: { | |
| 131 | + Image(systemName: "chevron.left") | |
| 132 | + } | |
| 133 | + .buttonStyle(.plain) | |
| 134 | + .disabled(!model.canGoBack) | |
| 135 | + .help("Page précédente") | |
| 136 | + Button { model.webView.goForward() } label: { | |
| 137 | + Image(systemName: "chevron.right") | |
| 138 | + } | |
| 139 | + .buttonStyle(.plain) | |
| 140 | + .disabled(!model.canGoForward) | |
| 141 | + .help("Page suivante") | |
| 142 | + Button { | |
| 143 | + if model.isLoading { model.webView.stopLoading() } | |
| 144 | + else { model.webView.reload() } | |
| 145 | + } label: { | |
| 146 | + Image(systemName: model.isLoading ? "xmark" : "arrow.clockwise") | |
| 147 | + } | |
| 148 | + .buttonStyle(.plain) | |
| 149 | + .help(model.isLoading ? "Arrêter" : "Recharger") | |
| 150 | + | |
| 151 | + // Domaine courant, façon champ d'adresse (lecture seule) | |
| 152 | + HStack(spacing: 6) { | |
| 153 | + Image(systemName: "lock.fill").font(.caption2).foregroundStyle(.secondary) | |
| 154 | + Text(model.currentURL?.host() ?? url.host() ?? "") | |
| 155 | + .font(.system(.caption, design: .monospaced).weight(.semibold)) | |
| 156 | + .lineLimit(1) | |
| 157 | + .truncationMode(.middle) | |
| 158 | + } | |
| 159 | + .padding(.horizontal, 10).padding(.vertical, 5) | |
| 160 | + .frame(maxWidth: .infinity) | |
| 161 | + .background(KATheme.surface(scheme), in: Capsule()) | |
| 162 | + .overlay(Capsule().strokeBorder(KATheme.ink(scheme).opacity(0.35), lineWidth: 1)) | |
| 163 | + .accessibilityLabel("Adresse : \(model.currentURL?.absoluteString ?? url.absoluteString)") | |
| 164 | + | |
| 165 | + if let current = model.currentURL { | |
| 166 | + ShareLink(item: current, subject: Text(title)) { | |
| 167 | + Image(systemName: "square.and.arrow.up") | |
| 168 | + } | |
| 169 | + .buttonStyle(.plain) | |
| 170 | + .help("Partager la page") | |
| 171 | + } | |
| 172 | + Button { | |
| 173 | + NSWorkspace.shared.open(model.currentURL ?? url) | |
| 174 | + } label: { | |
| 175 | + Image(systemName: "safari") | |
| 176 | + } | |
| 177 | + .buttonStyle(.plain) | |
| 178 | + .help("Ouvrir dans le navigateur") | |
| 179 | + } | |
| 180 | + .font(.body) | |
| 181 | + .padding(.horizontal, 12).padding(.vertical, 8) | |
| 182 | + .background(KATheme.paper(scheme)) | |
| 183 | + } | |
| 184 | +} | |
modified
Sources/KA/Features/UniverseExplorerView.swift
+33 −27
@@ -22,44 +22,45 @@ struct UniverseExplorerView: View { | ||
| 22 | 22 | @State private var errorText: String? |
| 23 | 23 | @State private var asGrid = true |
| 24 | 24 | @State private var savedFlash = false |
| 25 | + /// Bascule Données (explorateur natif) | Site (le vrai site web, intégré). | |
| 26 | + enum Mode: String, CaseIterable { case donnees = "Données", site = "Site" } | |
| 27 | + @State private var mode: Mode | |
| 28 | + | |
| 29 | + init(universe: Universe) { | |
| 30 | + self.universe = universe | |
| 31 | + // Les univers sans liste native s'ouvrent directement sur leur site. | |
| 32 | + let nativeless = universe.listPath == nil || universe.map == nil | |
| 33 | + let hasCustom = universe.id == "vrai-prix" || universe.id == "api-ka" | |
| 34 | + _mode = State(initialValue: nativeless && !hasCustom ? .site : .donnees) | |
| 35 | + } | |
| 25 | 36 | |
| 26 | 37 | var body: some View { |
| 27 | 38 | Group { |
| 28 | − if universe.id == "vrai-prix" { | |
| 39 | + if mode == .site { | |
| 40 | + SiteView(universe: universe) | |
| 41 | + } else if universe.id == "vrai-prix" { | |
| 29 | 42 | VraiPrixExplorer(universe: universe) |
| 30 | 43 | } else if universe.id == "api-ka" { |
| 31 | 44 | ApiPlaygroundView(universe: universe) |
| 32 | 45 | } else if universe.listPath == nil || universe.map == nil { |
| 33 | − webUniverse | |
| 46 | + SiteView(universe: universe) | |
| 34 | 47 | } else { |
| 35 | 48 | explorer |
| 36 | 49 | } |
| 37 | 50 | } |
| 38 | − } | |
| 39 | − | |
| 40 | − // MARK: univers sans liste native → renvoi au site | |
| 41 | − | |
| 42 | − private var webUniverse: some View { | |
| 43 | − VStack(spacing: 18) { | |
| 44 | − Image(systemName: universe.symbol).font(.system(size: 52)).foregroundStyle(universe.accent) | |
| 45 | − KAWordmark(universe: universe, size: 30) | |
| 46 | − Text(universe.tagline).font(.title3).foregroundStyle(.secondary) | |
| 47 | − .multilineTextAlignment(.center) | |
| 48 | − if let n = pulse.totals[universe.id] { | |
| 49 | − Text("\(n.fr) \(universe.unit)") | |
| 50 | − .font(.system(.subheadline, design: .monospaced).weight(.bold)) | |
| 51 | − .foregroundStyle(universe.accent) | |
| 52 | − } | |
| 53 | − Link(destination: universe.baseURL) { | |
| 54 | − Label("Ouvrir \(universe.name)", systemImage: "arrow.up.right.square") | |
| 55 | − .font(.headline).padding(.horizontal, 22).padding(.vertical, 12) | |
| 56 | − .background(universe.accent, in: Capsule()) | |
| 57 | − .foregroundStyle(.white) | |
| 51 | + .toolbar { | |
| 52 | + ToolbarItem(placement: .principal) { | |
| 53 | + Picker("Présentation de \(universe.name)", selection: $mode) { | |
| 54 | + ForEach(Mode.allCases, id: \.self) { m in | |
| 55 | + Text(m.rawValue).tag(m) | |
| 56 | + } | |
| 57 | + } | |
| 58 | + .pickerStyle(.segmented) | |
| 59 | + .labelsHidden() | |
| 60 | + .frame(width: 170) | |
| 61 | + .help("Données = explorateur natif ; Site = le site web \(universe.domain), intégré") | |
| 58 | 62 | } |
| 59 | − .buttonStyle(.plain) | |
| 60 | 63 | } |
| 61 | − .padding(30) | |
| 62 | − .frame(maxWidth: .infinity, maxHeight: .infinity) | |
| 63 | 64 | } |
| 64 | 65 | |
| 65 | 66 | // MARK: explorateur grille/liste + fiche |
@@ -101,6 +102,9 @@ struct UniverseExplorerView: View { | ||
| 101 | 102 | private var hero: some View { |
| 102 | 103 | VStack(alignment: .leading, spacing: 10) { |
| 103 | 104 | HStack(alignment: .firstTextBaseline, spacing: 12) { |
| 105 | + KALogo(id: universe.id, size: 26, | |
| 106 | + fallbackSymbol: universe.symbol, fallbackAccent: universe.accent) | |
| 107 | + .alignmentGuide(.firstTextBaseline) { $0[VerticalAlignment.center] + 8 } | |
| 104 | 108 | KAWordmark(universe: universe, size: 24) |
| 105 | 109 | Text(universe.tagline) |
| 106 | 110 | .font(.system(.subheadline, design: .rounded).weight(.bold)) |
@@ -121,11 +125,13 @@ struct UniverseExplorerView: View { | ||
| 121 | 125 | .pickerStyle(.segmented) |
| 122 | 126 | .labelsHidden() |
| 123 | 127 | .frame(width: 90) |
| 124 | − Link(destination: universe.baseURL) { | |
| 125 | − Label("Ouvrir le site", systemImage: "safari") | |
| 128 | + Button { mode = .site } label: { | |
| 129 | + Label("Voir le site", systemImage: "safari") | |
| 126 | 130 | .font(.caption.weight(.semibold)) |
| 127 | 131 | } |
| 132 | + .buttonStyle(.plain) | |
| 128 | 133 | .foregroundStyle(universe.accent) |
| 134 | + .help("Ouvrir \(universe.domain) dans l'app") | |
| 129 | 135 | } |
| 130 | 136 | HStack(spacing: 8) { |
| 131 | 137 | KASearchField(prompt: "Chercher dans \(universe.name)…", text: $query) { |
added
Sources/KA/Resources/Logos/logo-api-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-auto-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-crea-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-fabri-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-food-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-groupe-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-immo-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-job-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-lou-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-resto-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-sorti-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-trouve-ka.png
+0 −0
Binary file not shown.
added
Sources/KA/Resources/Logos/logo-vrai-prix.png
+0 −0
Binary file not shown.
modified
scripts/notarize.sh
+1 −1
@@ -9,7 +9,7 @@ cd "$(dirname "$0")/.." | ||
| 9 | 9 | IDENTITY="Developer ID Application: Simon-Pierre Boucher (3YM54G49SN)" |
| 10 | 10 | KEYCHAIN_PROFILE="MacLustr-Notarize" |
| 11 | 11 | APP_DIR="dist/KA.app" |
| 12 | −DMG_NAME="dist/KA-macos-1.0.0.dmg" # publié en téléchargement sur groupe-ka.com | |
| 12 | +DMG_NAME="dist/KA-macos-2.0.0.dmg" # publié en téléchargement sur groupe-ka.com | |
| 13 | 13 | |
| 14 | 14 | ./scripts/package-app.sh release |
| 15 | 15 | |
modified
scripts/package-app.sh
+4 −2
@@ -17,6 +17,8 @@ mkdir -p "$APP_DIR/Contents/MacOS" "$APP_DIR/Contents/Resources" | ||
| 17 | 17 | cp "$BIN" "$APP_DIR/Contents/MacOS/KA" |
| 18 | 18 | if [ ! -f Assets/AppIcon.icns ]; then ./scripts/generate-icon.sh; fi |
| 19 | 19 | cp Assets/AppIcon.icns "$APP_DIR/Contents/Resources/AppIcon.icns" |
| 20 | +# Bundle de ressources SwiftPM (logos des 13 marques) — requis par Bundle.module | |
| 21 | +cp -R ".build/$CONFIG/KA_KA.bundle" "$APP_DIR/Contents/Resources/" | |
| 20 | 22 | |
| 21 | 23 | cat > "$APP_DIR/Contents/Info.plist" <<'PLIST' |
| 22 | 24 | <?xml version="1.0" encoding="UTF-8"?> |
@@ -27,8 +29,8 @@ cat > "$APP_DIR/Contents/Info.plist" <<'PLIST' | ||
| 27 | 29 | <key>CFBundleDisplayName</key><string>KA</string> |
| 28 | 30 | <key>CFBundleIdentifier</key><string>com.groupeka.ka.mac</string> |
| 29 | 31 | <key>CFBundleExecutable</key><string>KA</string> |
| 30 | − <key>CFBundleVersion</key><string>1</string> | |
| 31 | − <key>CFBundleShortVersionString</key><string>1.0.0</string> | |
| 32 | + <key>CFBundleVersion</key><string>2</string> | |
| 33 | + <key>CFBundleShortVersionString</key><string>2.0.0</string> | |
| 32 | 34 | <key>CFBundlePackageType</key><string>APPL</string> |
| 33 | 35 | <key>LSMinimumSystemVersion</key><string>14.0</string> |
| 34 | 36 | <key>LSApplicationCategoryType</key><string>public.app-category.lifestyle</string> |
| 35 | 37 | |