mana-swift-ui/Sources/ManaOnboardingUI/View+ManaOnboarding.swift
Till JS efe424aa37 ManaOnboardingUI: Apple-„Neue Funktionen"-Sheet-Optik, keine Permission-Abfrage
Redesign nach Feedback. Statt Schritt-Karussell jetzt Apples
What's-New-Sheet-Stil: pro Seite Titel + Zeilen-Liste (Icon+Titel+Text)
+ EIN Button unten, Indikator oben (nicht beim Button).

- ManaOnboardingStep → ManaOnboardingPage (Titel/Vorspann/Row-Liste/
  Fußnote). Voll Sendable, keine Action-Closures mehr.
- Doktrin: fragt NIE Berechtigungen ab — die bleiben just-in-time.
  Onboarding informiert nur (kein Extrahieren auf Vorrat).
- Präsentation als .sheet (.presentationDetents([.large]) + Griff) statt
  fullScreenCover → nicht mehr eingezwängt; per Wischen = gesehen.
- Cross-Fade statt Swipe, reduced-motion-aware. 7 Tests (Seiten-Modell).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 19:19:07 +02:00

69 lines
2.4 KiB
Swift

import SwiftUI
public extension View {
/// Zeigt den geteilten Einstiegs-Flow **einmalig beim ersten Start**
/// als `.sheet` über dieser View (Apple-„Neue Funktionen“-Optik) und
/// merkt sich per `@AppStorage`, dass er durch ist.
///
/// - Parameters:
/// - storageKey: Eindeutiger, **versionierter** Schlüssel, z.B.
/// `"ev.mana.stetig.onboarding.v1"`. Die `vN`-Endung erlaubt es,
/// den Flow später bei größeren Inhaltsänderungen erneut zu zeigen
/// (neuer Key = wieder ungesehen).
/// - pages: Die app-gelieferten Seiten (siehe ``ManaOnboardingPage``).
/// - onFinish: Optionaler Callback, wenn der Flow durchlaufen wurde.
///
/// Local-First: Das Sheet ist per Wischen schließbar — wer es wegwischt,
/// hat es „gesehen“ und wird nicht erneut belästigt. Erzwingt nie Login
/// und fragt nie Berechtigungen ab.
func manaOnboarding(
storageKey: String,
pages: [ManaOnboardingPage],
onFinish: (() -> Void)? = nil
) -> some View {
modifier(ManaOnboardingGate(
storageKey: storageKey,
pages: pages,
onFinish: onFinish
))
}
}
/// Hält den „schon gesehen?“-Zustand und präsentiert den Flow als Sheet.
private struct ManaOnboardingGate: ViewModifier {
let storageKey: String
let pages: [ManaOnboardingPage]
let onFinish: (() -> Void)?
@AppStorage private var hasCompleted: Bool
init(storageKey: String, pages: [ManaOnboardingPage], onFinish: (() -> Void)?) {
self.storageKey = storageKey
self.pages = pages
self.onFinish = onFinish
_hasCompleted = AppStorage(wrappedValue: false, storageKey)
}
/// Binding, das beim Schließen (Button **oder** Wischen) `hasCompleted`
/// setzt. Wir präsentieren, solange `!hasCompleted`.
private var presented: Binding<Bool> {
Binding(
get: { !hasCompleted },
set: { stillPresented in if !stillPresented { hasCompleted = true } }
)
}
func body(content: Content) -> some View {
content.sheet(isPresented: presented) {
ManaOnboardingView(pages: pages) {
hasCompleted = true
onFinish?()
}
.presentationDetents([.large])
.presentationDragIndicator(.visible)
#if os(macOS)
.frame(minWidth: 460, minHeight: 600)
#endif
}
}
}