SPB Git forge

spb/ka-ios

Public
20commits 1branches 0releases
17.2 MBsize
maindefault branch
28 days agolast push
Swift 100%
7.5 KB · 202 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// SiteBrowserView.swift — les sites du Groupe KA DANS l'app : navigateur3// WKWebView intégré aux couleurs de l'univers (progression, retour/avancer,4// recharger, partager, ouvrir dans Safari). Utilisé en plein écran depuis5// chaque univers et inline pour les univers sans liste native (hub).6import SwiftUI7import WebKit89// MARK: - Modèle observable du WKWebView1011final class SiteWebModel: NSObject, ObservableObject, WKNavigationDelegate {12    let webView: WKWebView13    @Published var progress: Double = 014    @Published var loading = false15    @Published var canGoBack = false16    @Published var canGoForward = false17    @Published var currentURL: URL1819    private var observers: [NSKeyValueObservation] = []2021    init(start: URL) {22        currentURL = start23        let config = WKWebViewConfiguration()24        config.allowsInlineMediaPlayback = true25        config.defaultWebpagePreferences.allowsContentJavaScript = true26        webView = WKWebView(frame: .zero, configuration: config)27        super.init()28        webView.navigationDelegate = self29        webView.allowsBackForwardNavigationGestures = true30        webView.isOpaque = false31        observers = [32            webView.observe(\.estimatedProgress) { [weak self] w, _ in33                DispatchQueue.main.async { self?.progress = w.estimatedProgress }34            },35            webView.observe(\.isLoading) { [weak self] w, _ in36                DispatchQueue.main.async { self?.loading = w.isLoading }37            },38            webView.observe(\.canGoBack) { [weak self] w, _ in39                DispatchQueue.main.async { self?.canGoBack = w.canGoBack }40            },41            webView.observe(\.canGoForward) { [weak self] w, _ in42                DispatchQueue.main.async { self?.canGoForward = w.canGoForward }43            },44            webView.observe(\.url) { [weak self] w, _ in45                DispatchQueue.main.async { if let u = w.url { self?.currentURL = u } }46            },47        ]48        webView.load(URLRequest(url: start))49    }5051    // http(s) reste dans la vue ; tel:/mailto:/itms… → le système52    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,53                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {54        if let u = navigationAction.request.url,55           let scheme = u.scheme?.lowercased(),56           !["http", "https", "about", "blob", "data"].contains(scheme) {57            UIApplication.shared.open(u)58            decisionHandler(.cancel)59            return60        }61        decisionHandler(.allow)62    }6364    // les target="_blank" s'ouvrent dans la même vue65    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,66                 for navigationAction: WKNavigationAction,67                 windowFeatures: WKWindowFeatures) -> WKWebView? {68        if let u = navigationAction.request.url { webView.load(URLRequest(url: u)) }69        return nil70    }71}7273private struct WebViewContainer: UIViewRepresentable {74    let webView: WKWebView75    func makeUIView(context: Context) -> WKWebView { webView }76    func updateUIView(_ uiView: WKWebView, context: Context) {}77}7879// MARK: - Écran navigateur (embarquable : web + progression + barre de contrôle)8081struct SiteBrowserScreen: View {82    let accent: Color83    @ObservedObject var model: SiteWebModel84    @Environment(\.colorScheme) private var scheme8586    var body: some View {87        VStack(spacing: 0) {88            WebViewContainer(webView: model.webView)89                .overlay(alignment: .top) {90                    if model.loading {91                        ProgressView(value: max(model.progress, 0.06))92                            .progressViewStyle(.linear)93                            .tint(accent)94                    }95                }96            controls97        }98        .background(KATheme.paper(scheme))99    }100101    private var controls: some View {102        HStack(spacing: 26) {103            Button { Haptics.tap(); model.webView.goBack() } label: {104                Image(systemName: "chevron.backward")105            }106            .disabled(!model.canGoBack)107            .accessibilityLabel("Page précédente")108            Button { Haptics.tap(); model.webView.goForward() } label: {109                Image(systemName: "chevron.forward")110            }111            .disabled(!model.canGoForward)112            .accessibilityLabel("Page suivante")113            Spacer()114            Text(model.currentURL.host()?.replacingOccurrences(of: "www.", with: "") ?? "")115                .font(.system(.caption2, design: .monospaced).weight(.bold))116                .foregroundStyle(.secondary)117                .lineLimit(1)118            Spacer()119            ShareLink(item: model.currentURL) { Image(systemName: "square.and.arrow.up") }120                .accessibilityLabel("Partager la page")121            Button {122                Haptics.tap()123                if model.loading { model.webView.stopLoading() } else { model.webView.reload() }124            } label: {125                Image(systemName: model.loading ? "xmark" : "arrow.clockwise")126            }127            .accessibilityLabel(model.loading ? "Arrêter" : "Recharger")128            Link(destination: model.currentURL) { Image(systemName: "safari") }129                .accessibilityLabel("Ouvrir dans Safari")130        }131        .font(.body.weight(.semibold))132        .tint(accent)133        .padding(.horizontal, 18)134        .padding(.vertical, 10)135        .background(.bar)136    }137}138139// MARK: - Site embarqué inline (univers sans liste native, ex. le hub)140141struct EmbeddedSite: View {142    let universe: Universe143    @StateObject private var model: SiteWebModel144145    init(universe: Universe) {146        self.universe = universe147        _model = StateObject(wrappedValue: SiteWebModel(start: universe.baseURL))148    }149150    var body: some View {151        SiteBrowserScreen(accent: universe.accent, model: model)152    }153}154155// MARK: - Plein écran (couverture) : titre + logo + fermer156157struct SiteBrowserCover: View {158    let title: String159    let accent: Color160    let logoID: String161    @StateObject private var model: SiteWebModel162    @Environment(\.dismiss) private var dismiss163164    init(universe: Universe, path: String? = nil) {165        title = universe.name166        accent = universe.accent167        logoID = universe.id168        let url = path.map { universe.baseURL.appendingPathComponent($0) } ?? universe.baseURL169        _model = StateObject(wrappedValue: SiteWebModel(start: url))170    }171172    init(title: String, accent: Color, logoID: String, url: URL) {173        self.title = title174        self.accent = accent175        self.logoID = logoID176        _model = StateObject(wrappedValue: SiteWebModel(start: url))177    }178179    var body: some View {180        NavigationStack {181            SiteBrowserScreen(accent: accent, model: model)182                .toolbar {183                    ToolbarItem(placement: .topBarLeading) {184                        Button { dismiss() } label: {185                            Image(systemName: "xmark")186                        }187                        .accessibilityLabel("Fermer le site")188                    }189                    ToolbarItem(placement: .principal) {190                        HStack(spacing: 8) {191                            KALogo(assetID: logoID, size: 24)192                            Text(title).font(.headline)193                        }194                    }195                }196                .navigationBarTitleDisplayMode(.inline)197                .toolbarBackground(.visible, for: .navigationBar)198        }199        .tint(accent)200    }201}202