mana-swift-ui/Sources/ManaTransparencyUI/ManaTransparencyView.swift
Till JS 1d14c4c5c5 ManaTransparencyView: aufklappbar (DisclosureGroup), standardmäßig zu
swift build grün.

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

90 lines
3.2 KiB
Swift

import SwiftUI
/// „Danke & Transparenz" — der geteilte native Renderer: dankt den Projekten,
/// auf denen eine App aufbaut, und legt ihren Stack offen (Frameworks,
/// Bibliotheken, Schriften, selbst-gehostete Dienste, bewusste externe
/// Ausnahmen). Ökosystem-Konvention, siehe mana/docs/TRANSPARENZ.md.
///
/// Rein präsentational: Daten via Init (aus der generierten JSON, s.
/// ``ManaTransparencyData``), System-Styling (app-agnostisch). Üblicherweise
/// als letzter Abschnitt der Einstellungen/Über.
@MainActor
public struct ManaTransparencyView: View {
private let intro: String
private let entries: [ManaCredit]
@Environment(\.openURL) private var openURL
/// Feste Reihenfolge + deutsche Überschriften der Sektionen.
private static let sections: [(key: String, title: String)] = [
("upstream", "Aufbauend auf"),
("framework", "Frameworks"),
("library", "Bibliotheken"),
("font", "Schriften"),
("infra", "Dienste (selbst gehostet)"),
("saas-exception", "Bewusst externe Dienste"),
]
public init(intro: String, entries: [ManaCredit]) {
self.intro = intro
self.entries = entries
}
public init(_ data: ManaTransparencyData) {
self.init(intro: data.intro, entries: data.entries)
}
/// Standardmäßig zugeklappt — die Fläche soll die Einstellungen nicht zumüllen.
@State private var expanded = false
public var body: some View {
DisclosureGroup(isExpanded: $expanded) {
VStack(alignment: .leading, spacing: 16) {
Text(intro)
.font(.footnote)
.foregroundStyle(.secondary)
ForEach(Self.sections, id: \.key) { section in
let items = entries.filter { $0.category == section.key }
if !items.isEmpty {
VStack(alignment: .leading, spacing: 8) {
Text(section.title).font(.headline)
ForEach(items) { entry in
row(entry)
}
}
}
}
}
.padding(.top, 8)
} label: {
Text("Danke & Transparenz")
.font(.headline)
.foregroundStyle(.tint)
}
}
@ViewBuilder
private func row(_ entry: ManaCredit) -> some View {
let body = VStack(alignment: .leading, spacing: 2) {
HStack(spacing: 6) {
Text(entry.name).font(.body).fontWeight(.medium)
if let license = entry.license {
Text(license).font(.caption2).foregroundStyle(.secondary)
}
}
if let detail = entry.description ?? entry.why {
Text(detail).font(.caption).foregroundStyle(.secondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
if let urlString = entry.url, let url = URL(string: urlString) {
Button { openURL(url) } label: { body }
.buttonStyle(.plain)
} else {
body
}
}
}