SPB Git forge

spb/ka-ios

Public
20commits 1branches 0releases
17.2 MBsize
maindefault branch
28 days agolast push
Swift 100%
5.2 KB · 112 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// KAWidgets.swift — widget « Le pouls » : les compteurs EN DIRECT de3// l'écosystème Groupe-KA sur l'écran d'accueil (petit : 1 chiffre-choc ;4// moyen : 3 univers ; grand : 6 univers). Rafraîchi ~toutes les 30 min.5import WidgetKit6import SwiftUI78struct PulseEntry: TimelineEntry {9    let date: Date10    let counts: [(name: String, unit: String, value: Int, hex: String)]11}1213struct PulseProvider: TimelineProvider {14    static let sources: [(name: String, unit: String, url: String, keys: [String], hex: String)] = [15        ("Lou·Ka", "logements à louer", "https://www.lou-ka.com/api/stats", ["total"], "#ff6a00"),16        ("Immo·Ka", "propriétés à vendre", "https://www.immo-ka.com/api/stats", ["total"], "#e23744"),17        ("Job·Ka", "offres d'emploi", "https://www.job-ka.com/api/stats", ["total"], "#0c8599"),18        ("Sorti·Ka", "événements", "https://www.sorti-ka.com/api/stats", ["total_active", "total"], "#d6336c"),19        ("Auto·Ka", "véhicules", "https://www.auto-ka.com/api/stats", ["total"], "#ff5a2a"),20        ("Resto·Ka", "restaurants", "https://www.resto-ka.com/api/stats", ["restaurants", "total"], "#f08c00"),21    ]2223    static let placeholderCounts: [(String, String, Int, String)] = [24        ("Lou·Ka", "logements à louer", 33900, "#ff6a00"),25        ("Immo·Ka", "propriétés à vendre", 67000, "#e23744"),26        ("Job·Ka", "offres d'emploi", 4400, "#0c8599"),27        ("Sorti·Ka", "événements", 15300, "#d6336c"),28        ("Auto·Ka", "véhicules", 17000, "#ff5a2a"),29        ("Resto·Ka", "restaurants", 14000, "#f08c00"),30    ]3132    func placeholder(in context: Context) -> PulseEntry {33        PulseEntry(date: .now, counts: Self.placeholderCounts)34    }3536    func getSnapshot(in context: Context, completion: @escaping (PulseEntry) -> Void) {37        completion(placeholder(in: context))38    }3940    func getTimeline(in context: Context, completion: @escaping (Timeline<PulseEntry>) -> Void) {41        Task {42            var counts: [(String, String, Int, String)] = []43            for s in Self.sources {44                if let url = URL(string: s.url),45                   let (data, _) = try? await URLSession.shared.data(from: url),46                   let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {47                    for k in s.keys {48                        if let n = obj[k] as? Int { counts.append((s.name, s.unit, n, s.hex)); break }49                        if let n = obj[k] as? Double { counts.append((s.name, s.unit, Int(n), s.hex)); break }50                    }51                }52            }53            let entry = PulseEntry(date: .now, counts: counts.isEmpty ? Self.placeholderCounts : counts)54            completion(Timeline(entries: [entry], policy: .after(.now.addingTimeInterval(1800))))55        }56    }57}5859struct PulseWidgetView: View {60    var entry: PulseEntry61    @Environment(\.widgetFamily) private var family6263    private func hexColor(_ h: String) -> Color {64        let s = h.dropFirst(); let v = UInt64(s, radix: 16) ?? 065        return Color(.sRGB, red: Double((v >> 16) & 0xFF) / 255,66                     green: Double((v >> 8) & 0xFF) / 255, blue: Double(v & 0xFF) / 255)67    }6869    var body: some View {70        VStack(alignment: .leading, spacing: family == .systemSmall ? 4 : 8) {71            HStack(spacing: 4) {72                Text("Groupe").font(.system(.caption, design: .rounded).weight(.bold))73                Text("KA").font(.system(.caption2, design: .rounded).weight(.bold))74                    .foregroundStyle(Color(.sRGB, red: 0.85, green: 0.95, blue: 0.42))75                    .padding(.horizontal, 5).padding(.vertical, 1)76                    .background(.black, in: RoundedRectangle(cornerRadius: 5))77                Spacer()78                Text("en direct").font(.system(size: 8, design: .monospaced)).foregroundStyle(.secondary)79            }80            let rows = Array(entry.counts.prefix(family == .systemSmall ? 1 : family == .systemMedium ? 3 : 6))81            ForEach(rows, id: \.name) { c in82                VStack(alignment: .leading, spacing: 0) {83                    Text(c.value.formatted(.number.locale(Locale(identifier: "fr_CA"))))84                        .font(.system(family == .systemSmall ? .title2 : .headline, design: .rounded).weight(.bold))85                        .foregroundStyle(hexColor(c.hex))86                        .minimumScaleFactor(0.6).lineLimit(1)87                    Text("\(c.name) · \(c.unit)")88                        .font(.system(size: 9)).foregroundStyle(.secondary).lineLimit(1)89                }90            }91            Spacer(minLength: 0)92        }93        .containerBackground(for: .widget) { Color(.systemBackground) }94    }95}9697@main98struct KAWidgets: WidgetBundle {99    var body: some Widget { PulseWidget() }100}101102struct PulseWidget: Widget {103    var body: some WidgetConfiguration {104        StaticConfiguration(kind: "KAPulse", provider: PulseProvider()) { entry in105            PulseWidgetView(entry: entry)106        }107        .configurationDisplayName("Le pouls de l'écosystème")108        .description("Les compteurs en direct du Groupe KA : logements, propriétés, emplois, sorties…")109        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])110    }111}112