mana-swift-ui/Sources/ManaAuthUI/Account/ManaAboutSection.swift
Till JS bc0cb0e4e4 Weitere Konto-Sektionen vereinheitlichen: ManaAppearanceSection + ManaSafariLink
- ManaThemeUI: ManaAppearanceSection — geteilter „Darstellung"-Block
  (ManaAppearancePicker + ManaThemeGallery + einheitlicher Footer) als eine
  Section. Bindings + Theme-Pass (unlocked/onLockedTap) werden durchgereicht;
  Persistenz bleibt App-Sache. Ersetzt den pro App handgebauten Section-Rahmen.
- ManaAuthUI: ManaSafariLink — öffnet URLs in-App (iOS SFSafariViewController,
  macOS System-Browser-Fallback) für rechtliche/redaktionelle Seiten.
- ManaAuthUI: ManaAboutSection.Link.inApp — neuer additiver Parameter; true
  öffnet den Link via ManaSafariLink. Eine Sektion deckt App-Info + Rechtliches
  ab, statt einer separaten Legal-Sektion pro App.

iOS-Build (Seepuls, Release) verifiziert beide neuen Komponenten. Kein
API-Bruch (alles additiv). CHANGELOG aktualisiert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:37:42 +02:00

100 lines
3.9 KiB
Swift

import SwiftUI
/// Vereinheitlichte **„Über"-Sektion** für den Konto-/Einstellungen-Tab —
/// das Pendant zur ``ManaSignInCTA`` (Login) und ``ManaFeedbackUI/ManaFeedbackCard``
/// (Feedback): App-Name, Version + Build (automatisch aus dem Bundle) und eine
/// konfigurierbare Liste externer Links (Homepage, Datenschutz, Vereins-Landing).
///
/// Vorher baute jede App diese Sektion von Hand via `LabeledContent` +
/// `Bundle.main.infoDictionary` — uneinheitlich (comicello: App/Version/Build +
/// 3 Links; wordeck: nur Server-/Auth-Hosts; memoro: gar keine). Diese
/// Komponente macht Platzierung + Optik über alle Apps gleich.
///
/// Wird typischerweise nicht von Hand platziert, sondern von ``ManaAccountView``
/// an **fester Position** (ganz unten, nach den App-Sektionen) gerendert, wenn
/// beim Aufruf eine ``Config`` übergeben wird. Für Stand-alone-Einsatz (eigener
/// Settings-Screen) direkt in eine `Form` legen — die View *ist* eine `Section`:
///
/// ```swift
/// Form {
/// ManaAboutSection(.init(
/// appName: "Seepuls",
/// links: [
/// .init("seepuls.com", URL(string: "https://seepuls.com")!),
/// .init("Datenschutz", URL(string: "https://verein.mana.how/datenschutz")!),
/// ]
/// ))
/// }
/// ```
public struct ManaAboutSection: View {
/// Was die „Über"-Sektion zeigt. `Sendable`, damit sie als
/// ``ManaAccountView``-Init-Parameter durchgereicht werden kann.
public struct Config: Sendable {
public var appName: String
public var links: [Link]
/// - Parameters:
/// - appName: Anzeigename der App (z.B. „Seepuls").
/// - links: externe Links (Homepage, Datenschutz, Vereins-Landing …).
public init(appName: String, links: [Link] = []) {
self.appName = appName
self.links = links
}
}
/// Ein benannter Link in der „Über"-Sektion. Standardmäßig öffnet er extern
/// (System-Browser); mit `inApp: true` bleibt er **in der App**
/// (iOS: `SFSafariViewController`, macOS: System-Browser-Fallback) — für
/// rechtliche/redaktionelle Seiten (Impressum, Datenschutz, Lizenz), die man
/// nicht in einer separaten App öffnen will.
public struct Link: Identifiable, Sendable {
public var id: String {
title
}
public let title: String
public let url: URL
public let inApp: Bool
public init(_ title: String, _ url: URL, inApp: Bool = false) {
self.title = title
self.url = url
self.inApp = inApp
}
}
private let config: Config
private let version: String
private let build: String
/// - Parameters:
/// - config: App-Name + Links.
/// - bundle: Quelle für `CFBundleShortVersionString` (Version) und
/// `CFBundleVersion` (Build). Default `.main`; injizierbar für Previews/Tests.
public init(_ config: Config, bundle: Bundle = .main) {
self.config = config
version = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "—"
build = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "—"
}
public var body: some View {
Section("Über") {
LabeledContent("App", value: config.appName)
LabeledContent("Version", value: versionText)
ForEach(config.links) { link in
if link.inApp {
ManaSafariLink(link.title, link.url)
} else {
// SwiftUI.Link explizit — der verschachtelte Typ `Link` würde
// sonst den gleichnamigen SwiftUI-View überschatten.
SwiftUI.Link(link.title, destination: link.url)
}
}
}
}
/// „1.4.2 (87)" wenn Build vorhanden, sonst nur die Version.
private var versionText: String {
build == "—" ? version : "\(version) (\(build))"
}
}