mana-swift-ui/Sources/ManaThemeUI/ManaAppearanceSection.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

103 lines
4.3 KiB
Swift

import ManaTokens
import SwiftUI
/// Vereinheitlichte **„Darstellung"-Sektion** für den Konto-/Einstellungen-Tab
/// — das Pendant zu ``ManaAuthUI/ManaAboutSection`` (Über) und
/// ``ManaAuthUI/ManaFeedbackUI/ManaFeedbackCard`` (Feedback). Bündelt den
/// Hell/Dunkel-Wähler (``ManaAppearancePicker``) und die Theme-Galerie
/// (``ManaThemeGallery``) in *einer* `Section` mit Header + Footer.
///
/// Vorher baute jede App diesen Block von Hand — `Section("Darstellung")` mit
/// den beiden Pickern und einem jeweils leicht anders formulierten Footer
/// („Thema bestimmt die Farbwelt, „Automatisch" folgt dem System …"). Die
/// Einzelteile waren schon geteilt, der **Section-Rahmen + Footer** nicht.
/// Diese Komponente macht Aufbau, Reihenfolge und Wortlaut über alle Apps
/// gleich.
///
/// **Persistenz bleibt App-Sache.** Das Paket schreibt keine `UserDefaults`:
/// die App hält Appearance-Modus *und* aktive Theme-Variant (typisch je ein
/// `@AppStorage` unter app-eigenem Key) und reicht zwei `Binding`s herein. Die
/// App wendet die Wahl auch an (`.preferredColorScheme(appearance.colorScheme)`
/// + `.manaTheme(theme)` an höherer Stelle) — die Sektion *rendert* nur.
///
/// ```swift
/// @AppStorage("myapp.appearance") private var appearanceRaw = ManaAppearanceMode.system.rawValue
/// @AppStorage("myapp.theme") private var themeRaw = ManaTheme.mana.rawValue
///
/// ManaAppearanceSection(
/// appearance: Binding(
/// get: { ManaAppearanceMode(rawValue: appearanceRaw) ?? .system },
/// set: { appearanceRaw = $0.rawValue }
/// ),
/// theme: Binding(
/// get: { ManaTheme(rawValue: themeRaw) ?? .mana },
/// set: { themeRaw = $0.rawValue }
/// )
/// )
/// ```
public struct ManaAppearanceSection: View {
/// Standard-Footer, ökosystemweit gleich. Über den `footer:`-Init-Parameter
/// überschreibbar, `nil` blendet ihn aus.
public static let defaultFooter =
"Das Thema bestimmt die Farbwelt, „Automatisch“ folgt dem System (Hell/Dunkel) — deine Wahl gilt sofort in der ganzen App."
@Binding private var appearance: ManaAppearanceMode
@Binding private var theme: ManaTheme
private let themes: [ManaTheme]
private let unlocked: Set<ManaTheme>
private let onLockedTap: ((ManaTheme) -> Void)?
private let header: String
private let footer: String?
/// - Parameters:
/// - appearance: app-gehaltener Hell/Dunkel/Automatik-Modus.
/// - theme: app-gehaltene aktive Theme-Variant.
/// - themes: anzubietende Variants (Default: alle). Verein-Doktrin:
/// jede App bietet alle an.
/// - unlocked: freigeschaltete Variants für den Theme-Pass (Default:
/// alle frei). Nicht enthaltene erscheinen gesperrt.
/// - onLockedTap: optionaler Analytics-Hook der Galerie (feuert beim
/// „<App> öffnen"-Button).
/// - header: Section-Titel. Default „Darstellung".
/// - footer: erklärender Fließtext. Default ``defaultFooter``; `nil`
/// blendet ihn aus.
public init(
appearance: Binding<ManaAppearanceMode>,
theme: Binding<ManaTheme>,
themes: [ManaTheme] = ManaTheme.allCases,
unlocked: Set<ManaTheme> = Set(ManaTheme.allCases),
onLockedTap: ((ManaTheme) -> Void)? = nil,
header: String = "Darstellung",
footer: String? = ManaAppearanceSection.defaultFooter
) {
_appearance = appearance
_theme = theme
self.themes = themes
self.unlocked = unlocked
self.onLockedTap = onLockedTap
self.header = header
self.footer = footer
}
public var body: some View {
Section {
ManaAppearancePicker(selection: $appearance)
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
// Galerie randlos: ihr seitliches Innen-Padding zentriert die aktive
// Variant selbst — eigene Row-Insets würden doppelt ziehen.
ManaThemeGallery(
selection: $theme,
themes: themes,
unlocked: unlocked,
onLockedTap: onLockedTap
)
.listRowInsets(EdgeInsets(top: 4, leading: 0, bottom: 12, trailing: 0))
} header: {
Text(header)
} footer: {
if let footer {
Text(footer)
}
}
}
}