90 lines
3.2 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|