mana-swift-ui/Sources/ManaAuthUI/Brand/ManaBrandConfig.swift
Till JS 0a2cb349b4 v0.1.0 — initialer Sprint, vollständige Auth-Reise als SwiftUI
Phase 2 aus dem Native-Auth-Vollausbau-Plan (Option A, siehe
../mana/docs/MANA_SWIFT.md). Entstanden weil drei Apps fast-
byte-identische LoginView.swift hatten und Sign-Up/Forgot-PW
komplett fehlten.

ManaAuthUI-Library mit:
- ManaBrandConfig — App-injizierte Theme-Werte (forest für Cards/
  Manaspur, mana-default für Memoro), Environment-Key, View-Modifier
- Base-Components: ManaAuthScaffold, ManaPrimaryButton, ManaTextField,
  ManaSecureField + .manaEmailField()-Modifier
- ManaLoginView + LoginViewModel — Email/PW-Login, schaltet bei
  AuthError.emailNotVerified automatisch auf ManaEmailVerifyGateView
- ManaSignUpView + SignUpViewModel — Email/Name/PW + awaiting-
  Verification-Hinweis-Screen
- ManaEmailVerifyGateView + ViewModel — Resend-Verification
- ManaForgotPasswordView + ViewModel — Reset-Mail anfordern (immer
  generischer Hinweis, User-Enumeration-Schutz)
- ManaResetPasswordView + ViewModel — neues PW mit Token aus
  Universal-Link
- ManaChangeEmailView, ManaChangePasswordView, ManaDeleteAccountView
  + internal ViewModels — Account-Bausteine
- ManaDeleteAccountView ist zweistufig (Bestätigungs-Wort tippen
  + Passwort) → App-Store-Guideline 5.1.1(v) Pflicht-Surface

26/26 ViewModel-Tests grün via per-test-ID URLProtocol-Routing
(löst Parallel-Pollution zwischen .serialized Suites).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:22:42 +02:00

162 lines
5 KiB
Swift

import SwiftUI
/// App-injizierte Brand- und Theme-Werte für die Auth-Reise.
///
/// `ManaAuthUI` weiß nichts über Cards, Manaspur oder Memoro die
/// konsumierende App liefert ihren App-Namen, ihren Tagline und ihren
/// Farbsatz. Cards/Manaspur fahren heute den `forest`-Theme, Memoro
/// den `mana`-Default. Beide werden hier als Werte übergeben.
///
/// **Migration zu ManaTokens-Theme-Variants:** Sobald ManaTokens
/// (mana-swift-core) mehrere Variants liefert (`mana`, `forest`, ),
/// kann eine App einfach `.forest` statt eines manuellen
/// `ManaBrandConfig` schicken Convenience-Initializer folgen dann.
/// Heute (v0.1.0) ist alles explizit, bewusst.
public struct ManaBrandConfig: Sendable {
/// Anzeige-Name der App. Wird groß auf Login/SignUp gezeigt.
public let appName: String
/// Untertitel unter dem App-Namen. Optional wenn nil, wird kein
/// Tagline gerendert.
public let tagline: String?
/// Optionales SF-Symbol, das zentral über dem App-Namen erscheint.
/// Z.B. `"rectangle.stack.fill"` für Cardecky, `"map.fill"` für
/// Manaspur. Wenn nil, wird kein Icon gerendert.
public let logoSymbol: String?
// MARK: - Theme-Farben
/// Seiten-Hintergrund.
public let background: Color
/// Standard-Text auf Background.
public let foreground: Color
/// Card, Panel, Modal, Eingabefeld.
public let surface: Color
/// Sekundär-Text, Placeholder.
public let mutedForeground: Color
/// Rahmen, Trennlinien.
public let border: Color
/// Brand-Akzent (Primary-Button, Logo-Tint).
public let primary: Color
/// Text auf Primary-Background.
public let primaryForeground: Color
/// Fehler-Text und Destruktiv-Buttons.
public let error: Color
/// Success-Text (z.B. "Email verschickt").
public let success: Color
public init(
appName: String,
tagline: String? = nil,
logoSymbol: String? = nil,
background: Color,
foreground: Color,
surface: Color,
mutedForeground: Color,
border: Color,
primary: Color,
primaryForeground: Color,
error: Color,
success: Color
) {
self.appName = appName
self.tagline = tagline
self.logoSymbol = logoSymbol
self.background = background
self.foreground = foreground
self.surface = surface
self.mutedForeground = mutedForeground
self.border = border
self.primary = primary
self.primaryForeground = primaryForeground
self.error = error
self.success = success
}
}
public extension ManaBrandConfig {
/// SwiftUI-System-Default Plattform-System-Colors, geeignet für
/// Previews und Apps ohne eigenes Brand-Theme (heute: Memoro).
/// Folgt automatisch dem Light/Dark-Mode des Systems.
static let systemDefault = ManaBrandConfig(
appName: "mana",
tagline: nil,
logoSymbol: nil,
background: PlatformPalette.background,
foreground: .primary,
surface: PlatformPalette.surface,
mutedForeground: .secondary,
border: PlatformPalette.border,
primary: .accentColor,
primaryForeground: .white,
error: .red,
success: .green
)
}
/// Plattform-Brücke für die wenigen System-Colors, die zwischen iOS
/// und macOS unterschiedliche Namen haben. Bewusst privat Apps
/// arbeiten mit `Color`-Werten, nicht mit dieser Helper-Schicht.
private enum PlatformPalette {
static var background: Color {
#if canImport(UIKit)
Color(uiColor: .systemBackground)
#elseif canImport(AppKit)
Color(nsColor: .windowBackgroundColor)
#else
Color.white
#endif
}
static var surface: Color {
#if canImport(UIKit)
Color(uiColor: .secondarySystemBackground)
#elseif canImport(AppKit)
Color(nsColor: .underPageBackgroundColor)
#else
Color.gray.opacity(0.1)
#endif
}
static var border: Color {
#if canImport(UIKit)
Color(uiColor: .separator)
#elseif canImport(AppKit)
Color(nsColor: .separatorColor)
#else
Color.gray.opacity(0.3)
#endif
}
}
/// Environment-Key für die Brand-Config. Apps setzen den am Root-View
/// einmal; alle `ManaAuthUI`-Views lesen automatisch über
/// `@Environment(\.manaBrand)`.
public struct ManaBrandConfigKey: EnvironmentKey {
public static let defaultValue: ManaBrandConfig = .systemDefault
}
public extension EnvironmentValues {
/// Brand-/Theme-Werte für `ManaAuthUI`-Views. Vom App-Root via
/// `.environment(\.manaBrand, brandConfig)` gesetzt.
var manaBrand: ManaBrandConfig {
get { self[ManaBrandConfigKey.self] }
set { self[ManaBrandConfigKey.self] = newValue }
}
}
public extension View {
/// Convenience-Modifier semantisch `.environment(\.manaBrand, ...)`.
func manaBrand(_ config: ManaBrandConfig) -> some View {
environment(\.manaBrand, config)
}
}