cards-native/Sources/App/RootView.swift
Till JS 6805bd78c7 feat(decks): iOS-26 tabViewBottomAccessory für „Neues Deck"-Pille
Ersetzt den bottomBar-„+"-Button auf iOS 26 durch eine schwebende
Liquid-Glass-Pille via `.tabViewBottomAccessory`, nur sichtbar wenn
der Decks-Tab aktiv ist. iOS 18-Geräte behalten den bestehenden
bottomBar-Button (gated via `if #unavailable(iOS 26.0)`).

`showCreate` wandert als Binding von DeckListView nach RootView,
damit das Accessory den Sheet triggern kann.

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

106 lines
3.4 KiB
Swift

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
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:
LoginView()
}
}
.task {
await auth.ensureSignedIn()
}
}
@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/<slug>` Explore-Tab + PublicDeckView
/// - `cards://study/<deckId>` später (β-6 Notifications)
private func handle(url: URL) {
Log.app.info("Open URL: \(url.absoluteString, privacy: .public)")
if url.host == "cardecky.mana.how" || url.scheme == "cards" {
let parts = url.pathComponents.filter { $0 != "/" }
if parts.count >= 2, parts[0] == "d" {
pendingDeepLinkSlug = parts[1]
selectedTab = .explore
}
}
}
}
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")
}
}