Compare commits
2 commits
0e90f4b1c1
...
710ede6acd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
710ede6acd | ||
|
|
6805bd78c7 |
3 changed files with 60 additions and 13 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ actor CardsAPI {
|
|||
func fetchMedia(id: String) async throws -> Data {
|
||||
let (data, http) = try await transport.request(path: "/api/v1/media/\(id)")
|
||||
guard (200 ..< 300).contains(http.statusCode) else {
|
||||
throw AuthError.serverError(status: http.statusCode, message: "media fetch failed")
|
||||
throw AuthError.serverError(status: http.statusCode, code: nil, message: "media fetch failed")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
|
@ -178,7 +178,7 @@ actor CardsAPI {
|
|||
/// `DELETE /api/v1/media/:id` — Soft-Forget. (Endpoint heute nicht
|
||||
/// implementiert serverseitig; Stub bleibt für späteren Use.)
|
||||
func deleteMedia(id _: String) async throws {
|
||||
throw AuthError.serverError(status: 501, message: "media delete not implemented on server")
|
||||
throw AuthError.serverError(status: 501, code: nil, message: "media delete not implemented on server")
|
||||
}
|
||||
|
||||
// MARK: - Deck-Mutations
|
||||
|
|
@ -326,7 +326,7 @@ actor CardsAPI {
|
|||
private func ensureOK(_ http: HTTPURLResponse, data: Data) throws {
|
||||
guard (200 ..< 300).contains(http.statusCode) else {
|
||||
let message = (try? JSONDecoder().decode(CardsServerError.self, from: data))?.error
|
||||
throw AuthError.serverError(status: http.statusCode, message: message)
|
||||
throw AuthError.serverError(status: http.statusCode, code: nil, message: message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue