import ManaAuthUI import ManaCore import SwiftUI /// Top-Level-Switch: Login vs Haupt-App. Haupt-App ist eine TabBar mit /// drei Tabs (Decks / Entdecken / Account). struct RootView: View { @Environment(AuthClient.self) private var auth @State private var selectedTab: AppTab = .decks @State private var pendingDeepLinkSlug: String? @State private var showCreateDeck = false @State private var showSignUpSheet = false @State private var showForgotSheet = false @State private var resetPasswordToken: String? private let sourceAppUrl = URL(string: "https://cardecky.mana.how/auth/verify")! private let resetUniversalLink = URL(string: "https://cardecky.mana.how/auth/reset")! var body: some View { Group { switch auth.status { case .signedIn: mainTabs .onOpenURL { url in handle(url: url) } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in if let url = activity.webpageURL { handle(url: url) } } case .unknown, .signedOut, .signingIn, .error: authSurface .onOpenURL { url in handle(url: url) } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in if let url = activity.webpageURL { handle(url: url) } } } } .manaBrand(CardsBrand.manaBrand) .task { await auth.ensureSignedIn() } } @ViewBuilder private var authSurface: some View { ManaLoginView( auth: auth, onSignUpTapped: { showSignUpSheet = true }, onForgotTapped: { showForgotSheet = true } ) .sheet(isPresented: $showSignUpSheet) { ManaSignUpView( auth: auth, sourceAppUrl: sourceAppUrl, onDone: { showSignUpSheet = false } ) .manaBrand(CardsBrand.manaBrand) } .sheet(isPresented: $showForgotSheet) { ManaForgotPasswordView( auth: auth, resetUniversalLink: resetUniversalLink, onDone: { showForgotSheet = false } ) .manaBrand(CardsBrand.manaBrand) } .sheet(item: Binding( get: { resetPasswordToken.map(IdentifiedString.init) }, set: { resetPasswordToken = $0?.value } )) { token in ManaResetPasswordView( token: token.value, auth: auth, onDone: { resetPasswordToken = nil } ) .manaBrand(CardsBrand.manaBrand) } } @ViewBuilder private var mainTabs: some View { TabView(selection: $selectedTab) { DeckListView(showCreate: $showCreateDeck) .tabItem { Label("Decks", systemImage: "rectangle.stack") } .tag(AppTab.decks) ExploreView(deepLinkSlug: $pendingDeepLinkSlug) .tabItem { Label("Entdecken", systemImage: "sparkles") } .tag(AppTab.explore) NavigationStack { AccountView() } .tabItem { Label("Account", systemImage: "person.crop.circle") } .tag(AppTab.account) } .decksCreateAccessory(visible: selectedTab == .decks) { showCreateDeck = true } } /// Universal-Link- und URL-Scheme-Handler: /// - `https://cardecky.mana.how/d/` → Explore-Tab + PublicDeckView /// - `https://cardecky.mana.how/auth/reset?token=…` → ManaResetPasswordView /// - `cards://study/` → später (β-6 Notifications) private func handle(url: URL) { Log.app.info("Open URL: \(url.absoluteString, privacy: .public)") guard url.host == "cardecky.mana.how" || url.scheme == "cards" else { return } let parts = url.pathComponents.filter { $0 != "/" } // Auth-Reset-Link aus der Passwort-Vergessen-Email. if parts == ["auth", "reset"], let token = URLComponents(url: url, resolvingAgainstBaseURL: false)? .queryItems? .first(where: { $0.name == "token" })?.value { resetPasswordToken = token return } if parts.count >= 2, parts[0] == "d" { pendingDeepLinkSlug = parts[1] selectedTab = .explore } } } /// Helper für `.sheet(item:)` mit einem String-Value (Reset-Token). private struct IdentifiedString: Identifiable { let value: String var id: String { value } } enum AppTab: Hashable { case decks case explore case account } private extension View { /// iOS 26: floating „Neues Deck"-Pille via `.tabViewBottomAccessory`, /// nur sichtbar wenn der Decks-Tab aktiv ist. iOS 18 fällt auf den /// bestehenden `.bottomBar`-„+"-Toolbar-Button in `DeckListView` zurück. @ViewBuilder func decksCreateAccessory(visible: Bool, onTap: @escaping () -> Void) -> some View { if #available(iOS 26.0, *) { self.tabViewBottomAccessory { if visible { DeckCreateAccessoryPill(action: onTap) } } } else { self } } } @available(iOS 26.0, *) private struct DeckCreateAccessoryPill: View { let action: () -> Void var body: some View { Button(action: action) { Label("Neues Deck", systemImage: "plus") .font(.subheadline.weight(.semibold)) .padding(.horizontal, 14) .padding(.vertical, 8) } .buttonStyle(.borderedProminent) .tint(CardsTheme.primary) .accessibilityLabel("Neues Deck erstellen") } }