From 6805bd78c7130078a5b91dbc4bc273d03fbb6338 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 13 May 2026 19:08:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(decks):=20iOS-26=20tabViewBottomAccessory?= =?UTF-8?q?=20f=C3=BCr=20=E2=80=9ENeues=20Deck"-Pille?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- Sources/App/RootView.swift | 41 ++++++++++++++++++++++- Sources/Features/Decks/DeckListView.swift | 26 +++++++++----- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/Sources/App/RootView.swift b/Sources/App/RootView.swift index 225085a..3c960a9 100644 --- a/Sources/App/RootView.swift +++ b/Sources/App/RootView.swift @@ -7,6 +7,7 @@ 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 { @@ -29,7 +30,7 @@ struct RootView: View { @ViewBuilder private var mainTabs: some View { TabView(selection: $selectedTab) { - DeckListView() + DeckListView(showCreate: $showCreateDeck) .tabItem { Label("Decks", systemImage: "rectangle.stack") } .tag(AppTab.decks) @@ -43,6 +44,9 @@ struct RootView: View { .tabItem { Label("Account", systemImage: "person.crop.circle") } .tag(AppTab.account) } + .decksCreateAccessory(visible: selectedTab == .decks) { + showCreateDeck = true + } } /// Universal-Link- und URL-Scheme-Handler: @@ -65,3 +69,38 @@ enum AppTab: Hashable { 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") + } +} diff --git a/Sources/Features/Decks/DeckListView.swift b/Sources/Features/Decks/DeckListView.swift index f224839..3cdd9f0 100644 --- a/Sources/Features/Decks/DeckListView.swift +++ b/Sources/Features/Decks/DeckListView.swift @@ -18,9 +18,10 @@ struct DeckListView: View { @Environment(\.modelContext) private var context @Query(sort: \CachedDeck.updatedAt, order: .reverse) private var decks: [CachedDeck] + @Binding var showCreate: Bool + @State private var store: DeckListStore? @State private var showAccount = false - @State private var showCreate = false @State private var pendingShares: [PendingShare] = [] @State private var path = NavigationPath() @@ -235,7 +236,7 @@ struct DeckListView: View { Label("Noch keine Decks", systemImage: "rectangle.stack") .foregroundStyle(CardsTheme.foreground) } description: { - Text("Tippe oben auf »+«, um dein erstes Deck zu erstellen, oder browse den Marketplace im Entdecken-Tab.") + Text("Tippe unten auf »+«, um dein erstes Deck zu erstellen, oder browse den Marketplace im Entdecken-Tab.") .foregroundStyle(CardsTheme.mutedForeground) } } @@ -245,14 +246,21 @@ struct DeckListView: View { @ToolbarContentBuilder private var toolbar: some ToolbarContent { - ToolbarItem(placement: .topBarLeading) { - Button { - showCreate = true - } label: { - Image(systemName: "plus.circle") - .foregroundStyle(CardsTheme.primary) + // Auf iOS 26 übernimmt das `.tabViewBottomAccessory` aus RootView die + // „Neues Deck"-Pille. Doppelten „+"-Button im Liquid-Glass-Layout + // vermeiden — bottomBar-Button nur auf iOS < 26 zeigen. + if #unavailable(iOS 26.0) { + ToolbarItemGroup(placement: .bottomBar) { + Button { + showCreate = true + } label: { + Label("Deck hinzufügen", systemImage: "plus") + .labelStyle(.iconOnly) + .foregroundStyle(CardsTheme.primary) + } + .accessibilityLabel("Deck hinzufügen") + Spacer() } - .accessibilityLabel("Deck hinzufügen") } ToolbarItem(placement: .topBarTrailing) { Button {