- 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>
103 lines
4.3 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|
|
}
|