SPB Git forge

spb/ka-ios

Public
20commits 1branches 0releases
17.2 MBsize
maindefault branch
28 days agolast push
Swift 100%
6.9 KB · 164 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// ForYouSection.swift — le feed « Pour vous » de l'accueil : un mélange3// CLASSÉ (pas chronologique) des univers que l'utilisateur suit vraiment,4// en grandes cartes immersives pleine largeur, chaque carte expliquant5// honnêtement pourquoi elle est là. Apparaît seulement quand le profil a6// assez de signal — jamais de fausse personnalisation à la première ouverture.7import SwiftUI89struct ForYouSection: View {10    let entries: [ForYouEntry]11    @Environment(\.colorScheme) private var scheme1213    var body: some View {14        VStack(alignment: .leading, spacing: 12) {15            HStack(spacing: 8) {16                Image(systemName: "sparkles")17                    .font(.subheadline.weight(.bold))18                    .foregroundStyle(KATheme.lime)19                    .frame(width: 28, height: 28)20                    .background(KATheme.inkLight, in: RoundedRectangle(cornerRadius: 9, style: .continuous))21                VStack(alignment: .leading, spacing: 0) {22                    Text("Pour vous").font(KAFont.display(22))23                        .foregroundStyle(KATheme.ink(scheme))24                    Text("Selon vos visites — tout reste sur votre appareil")25                        .font(.caption2).foregroundStyle(.secondary)26                }27                Spacer()28            }29            .accessibilityElement(children: .combine)30            .accessibilityAddTraits(.isHeader)3132            ForEach(entries) { entry in33                NavigationLink(value: entry.item) {34                    ForYouCard(entry: entry)35                }36                .buttonStyle(KAPressStyle())37                .kaScrollPop(axis: .vertical)38            }39        }40    }41}4243/// Grande carte immersive : photo pleine largeur, raison en badge accent,44/// titre display sur scrim. Sans photo : rangée éditoriale avec pastille.45struct ForYouCard: View {46    let entry: ForYouEntry47    @Environment(\.colorScheme) private var scheme4849    private var universe: Universe? { Ecosystem.universe(entry.item.universeID) }50    private var accent: Color { universe?.accent ?? KATheme.green }5152    var body: some View {53        Group {54            if entry.item.imageURL != nil {55                immersive56            } else {57                row58            }59        }60        .accessibilityElement(children: .combine)61        .accessibilityLabel("\(entry.reason). \(entry.item.title)\(entry.item.priceLabel.map { ", \($0)" } ?? "")")62    }6364    private var immersive: some View {65        ZStack(alignment: .bottomLeading) {66            // l'image vit en OVERLAY d'un cadre fixe : sa largeur idéale67            // (panoramas!) ne se propage jamais à la mise en page68            Color.clear69                .frame(height: 200)70                .frame(maxWidth: .infinity)71                .overlay(KAImage(url: entry.item.imageURL, accent: accent,72                                 symbol: universe?.symbol ?? "sparkles"))73                .clipped()74            LinearGradient(colors: [.clear, .black.opacity(0.10), .black.opacity(0.82)],75                           startPoint: .top, endPoint: .bottom)76            VStack(alignment: .leading, spacing: 6) {77                HStack(spacing: 5) {78                    Image(systemName: "sparkle").font(.system(size: 8, weight: .bold))79                    Text(entry.reason.uppercased())80                        .font(KAFont.mono(8))81                        .kerning(0.8)82                        .lineLimit(1)83                        .minimumScaleFactor(0.8)84                }85                .padding(.horizontal, 9).padding(.vertical, 5)86                .background(accent, in: Capsule())87                .foregroundStyle(.white)88                Text(entry.item.title)89                    .font(KAFont.display(19))90                    .foregroundStyle(.white)91                    .lineLimit(2)92                    .multilineTextAlignment(.leading)93                HStack(spacing: 8) {94                    if let p = entry.item.priceLabel {95                        Text(p)96                            .font(.system(.subheadline, design: .rounded, weight: .heavy))97                            .foregroundStyle(KATheme.lime)98                    }99                    if let c = entry.item.city {100                        Text(c).font(.caption).foregroundStyle(.white.opacity(0.78))101                    }102                    Spacer(minLength: 0)103                    if let u = universe {104                        Text(u.wordmark)105                            .font(KAFont.mono(9))106                            .foregroundStyle(.white)107                            .padding(.horizontal, 8).padding(.vertical, 4)108                            .background(.white.opacity(0.18), in: Capsule())109                    }110                }111            }112            .padding(14)113        }114        .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))115        .overlay(RoundedRectangle(cornerRadius: 18, style: .continuous)116            .strokeBorder(KATheme.ink(scheme).opacity(scheme == .dark ? 0.35 : 0.85), lineWidth: 1.5))117        .background(RoundedRectangle(cornerRadius: 18, style: .continuous)118            .fill(accent.opacity(scheme == .dark ? 0.35 : 1))119            .offset(x: 6, y: 6))120    }121122    private var row: some View {123        HStack(alignment: .top, spacing: 12) {124            RoundedRectangle(cornerRadius: 8, style: .continuous)125                .fill(accent)126                .frame(width: 5)127                .padding(.vertical, 2)128            VStack(alignment: .leading, spacing: 4) {129                HStack(spacing: 5) {130                    Image(systemName: "sparkle").font(.system(size: 8, weight: .bold))131                    Text(entry.reason.uppercased())132                        .font(KAFont.mono(8))133                        .kerning(0.6)134                        .lineLimit(1)135                        .minimumScaleFactor(0.85)136                }137                .foregroundStyle(accent)138                Text(entry.item.title)139                    .font(.subheadline.weight(.bold))140                    .foregroundStyle(KATheme.ink(scheme))141                    .lineLimit(2)142                    .multilineTextAlignment(.leading)143                HStack(spacing: 8) {144                    if let p = entry.item.priceLabel {145                        Text(p)146                            .font(.system(.caption, design: .rounded).weight(.bold))147                            .foregroundStyle(accent)148                            .lineLimit(1)149                    }150                    if let c = entry.item.city {151                        Text(c).font(.caption2).foregroundStyle(.tertiary).lineLimit(1)152                    }153                    Spacer(minLength: 0)154                    if let u = universe {155                        KAChip(text: u.wordmark, accent: u.accent)156                    }157                }158            }159        }160        .padding(12)161        .kaCard(accent: accent)162    }163}164