From aa94601409acaddc9f111dc0528d1daefee992e9 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 13 May 2026 17:28:11 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Cardecky-Web-Design=20=E2=80=94=20F?= =?UTF-8?q?an-Stack-Tiles=20+=20CardSurface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI-Refactor angelehnt an cards/apps/web. Drei Killer-Patterns übernommen: 1. CardSurface (Sources/Core/Theme/CardSurface.swift) - Drei Sizes md/lg/hero mit identischem Border-Radius 14pt, 1pt Border, layered Shadows je nach Elevation - Aspect-Ratio 5:7 für md/hero, 12:16.8 für lg - Optional Color-Accent-Stripe links (6pt, deck.color) 2. DeckStackTile (Sources/Features/Decks/DeckStackTile.swift) - Spielkarten-Stack-Visual: 3 gestaffelt-rotierte Hintergrund-Layer hinter der CardSurface - Layer-Offsets + Tilts deterministisch aus Deck-ID gehasht (gleiches Deck = gleiche Asymmetrie) - Inhalt: Category-Icon oben rechts, Titel + Description zentriert, Counts unten als Pill für dueCount 3. RatingBar mit Good-Emphasis (Features/Study/RatingBar.swift) - "Good" als full primary background (hero action) - again/hard/easy mit subtle border-tint + opacity-08-Background - Keyboard-Shortcut im Button-Label als kbd-Style-Pill DeckListView komplett umgebaut: - Horizontale ScrollView mit scrollTransition + viewAligned-Snap - Zwei Sektionen: "Eigene Decks" und "Abonniert" - Inbox-Banner als highlight (primary opacity 0.08 mit border) - Pending-Share-Banner mit warning-Tint - Section-Headers mit Icon + Title + Count StudySessionView.cardSurface nutzt jetzt CardSurface(.hero, .raised). Build 6 → 7. Drei native Targets bauen, 35 Tests grün. Co-Authored-By: Claude Opus 4.7 (1M context) --- Sources/Core/Theme/CardSurface.swift | 108 ++++++++ Sources/Features/Decks/DeckListView.swift | 230 ++++++++---------- Sources/Features/Decks/DeckStackTile.swift | 131 ++++++++++ Sources/Features/Study/RatingBar.swift | 60 +++-- Sources/Features/Study/StudySessionView.swift | 23 +- project.yml | 6 +- 6 files changed, 396 insertions(+), 162 deletions(-) create mode 100644 Sources/Core/Theme/CardSurface.swift create mode 100644 Sources/Features/Decks/DeckStackTile.swift diff --git a/Sources/Core/Theme/CardSurface.swift b/Sources/Core/Theme/CardSurface.swift new file mode 100644 index 0000000..f19f3e3 --- /dev/null +++ b/Sources/Core/Theme/CardSurface.swift @@ -0,0 +1,108 @@ +import SwiftUI + +/// Wiederverwendbare Karten-Hülle in drei Größen — entspricht den +/// Web-`CardSurface.svelte`-Varianten. Konsistenter Border-Radius (14pt), +/// gleicher Border-Stil, gleiche Shadow-Behandlung über alle Größen, +/// optional ein linker Color-Accent-Streifen. +/// +/// Spec aus `cards/apps/web/src/lib/components/CardSurface.svelte`: +/// - Alle Größen Border-Radius 0.875rem (14pt) +/// - Border 1px hsl(--color-border) +/// - Background hsl(--color-surface) +/// - Aspect-Ratio 5/7 für `.md` und `.hero`, fix für `.lg` +struct CardSurface: View { + enum Size: Sendable { + case md // Deck-Tile in der Liste (max-width 18rem) + case lg // Fan-Detail (12rem x 16.8rem) + case hero // Study-Lernkarte (max-width 24rem) + } + + enum Elevation: Sendable { + case flat // Subtle shadow + case standard // Default Karten-Shadow + case raised // Study-Hero + } + + let size: Size + let elevation: Elevation + let colorAccentHex: String? + let content: () -> Content + + init( + size: Size = .md, + elevation: Elevation = .standard, + colorAccentHex: String? = nil, + @ViewBuilder content: @escaping () -> Content + ) { + self.size = size + self.elevation = elevation + self.colorAccentHex = colorAccentHex + self.content = content + } + + var body: some View { + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(CardsTheme.surface) + .overlay( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .stroke(CardsTheme.border, lineWidth: 1) + ) + + if let colorAccentHex { + Color.swatchFromHex(colorAccentHex) + .frame(width: 6) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: 14, + bottomLeadingRadius: 14, + bottomTrailingRadius: 0, + topTrailingRadius: 0, + style: .continuous + ) + ) + } + + content() + .padding(EdgeInsets(top: 16, leading: 22, bottom: 18, trailing: 16)) + } + .frame(maxWidth: maxWidth) + .aspectRatio(aspectRatio, contentMode: .fit) + .shadow(color: shadowColor, radius: shadowRadius, x: 0, y: shadowY) + } + + private var maxWidth: CGFloat? { + switch size { + case .md: 288 // 18rem + case .lg: 192 // 12rem + case .hero: 384 // 24rem + } + } + + private var aspectRatio: CGFloat? { + switch size { + case .md, .hero: 5.0 / 7.0 + case .lg: 12.0 / 16.8 + } + } + + private var shadowColor: Color { + CardsTheme.foreground.opacity(elevation == .raised ? 0.18 : 0.08) + } + + private var shadowRadius: CGFloat { + switch elevation { + case .flat: 3 + case .standard: 8 + case .raised: 18 + } + } + + private var shadowY: CGFloat { + switch elevation { + case .flat: 1 + case .standard: 4 + case .raised: 12 + } + } +} diff --git a/Sources/Features/Decks/DeckListView.swift b/Sources/Features/Decks/DeckListView.swift index fff8437..8d8fb42 100644 --- a/Sources/Features/Decks/DeckListView.swift +++ b/Sources/Features/Decks/DeckListView.swift @@ -2,8 +2,9 @@ import ManaCore import SwiftData import SwiftUI -/// β-1 Hauptbildschirm: Liste aller Decks mit Card- und Due-Counts. -/// Web-Vorbild: `cards/apps/web/src/routes/decks/+page.svelte`. +/// Decks-Hauptbildschirm im Cardecky-Look: horizontale Scroll-Reihen +/// mit Fan-Stack-Karten-Tiles. Web-Vorbild: +/// `cards/apps/web/src/routes/decks/+page.svelte`. struct DeckListView: View { @Environment(AuthClient.self) private var auth @Environment(\.modelContext) private var context @@ -69,25 +70,107 @@ struct DeckListView: View { if decks.isEmpty { emptyState } else { - List { - pendingShareSection - inboxBannerSection - ownDecksSection + ScrollView { + VStack(alignment: .leading, spacing: 24) { + pendingShareSection + inboxBanner + deckSection(title: "Eigene Decks", icon: "rectangle.stack", decks: ownDecks) + if !subscribedDecks.isEmpty { + deckSection(title: "Abonniert", icon: "globe", decks: subscribedDecks) + } + } + .padding(.vertical, 12) } - .listStyle(.plain) - .scrollContentBackground(.hidden) + } + } + + private var ownDecks: [CachedDeck] { + decks.filter { !$0.isFromMarketplace } + } + + private var subscribedDecks: [CachedDeck] { + decks.filter { $0.isFromMarketplace } + } + + @ViewBuilder + private func deckSection(title: String, icon: String, decks: [CachedDeck]) -> some View { + if !decks.isEmpty { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 6) { + Image(systemName: icon) + .foregroundStyle(CardsTheme.primary) + Text(title) + .font(.title3.weight(.semibold)) + .foregroundStyle(CardsTheme.foreground) + Text("\(decks.count)") + .font(.subheadline) + .foregroundStyle(CardsTheme.mutedForeground) + } + .padding(.horizontal, 20) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: 16) { + ForEach(decks) { deck in + NavigationLink(value: deck.id) { + DeckStackTile(deck: deck) + .frame(width: 240) + } + .buttonStyle(.plain) + .scrollTransition(.animated) { content, phase in + content + .scaleEffect(phase.isIdentity ? 1 : 0.92) + .opacity(phase.isIdentity ? 1 : 0.7) + } + } + } + .padding(.horizontal, 20) + .padding(.bottom, 12) + .scrollTargetLayout() + } + .scrollTargetBehavior(.viewAligned) + } + } + } + + @ViewBuilder + private var inboxBanner: some View { + if let inbox = decks.first(where: { $0.isFromMarketplace && $0.dueCount > 0 }) { + HStack(spacing: 12) { + Image(systemName: "tray.full.fill") + .font(.title3) + .foregroundStyle(CardsTheme.primary) + VStack(alignment: .leading, spacing: 2) { + Text("Inbox") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(CardsTheme.foreground) + Text("\(inbox.dueCount) fällige Karten aus abonnierten Decks") + .font(.caption) + .foregroundStyle(CardsTheme.mutedForeground) + } + Spacer() + Image(systemName: "chevron.right") + .font(.footnote) + .foregroundStyle(CardsTheme.mutedForeground) + } + .padding(14) + .background(CardsTheme.primary.opacity(0.08), in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(CardsTheme.primary.opacity(0.18), lineWidth: 1) + ) + .padding(.horizontal, 20) } } @ViewBuilder private var pendingShareSection: some View { if !pendingShares.isEmpty { - Section { + VStack(alignment: .leading, spacing: 8) { ForEach(pendingShares) { share in NavigationLink(value: PendingShareRoute(share: share)) { HStack(spacing: 12) { Image(systemName: "square.and.arrow.down") - .foregroundStyle(CardsTheme.primary) + .foregroundStyle(CardsTheme.warning) VStack(alignment: .leading, spacing: 2) { Text("Aus Teilen-Menü") .font(.subheadline.weight(.semibold)) @@ -98,16 +181,17 @@ struct DeckListView: View { .lineLimit(2) } Spacer() + Image(systemName: "chevron.right") + .font(.footnote) + .foregroundStyle(CardsTheme.mutedForeground) } - .padding() - .background(CardsTheme.warning.opacity(0.12), in: RoundedRectangle(cornerRadius: 10)) + .padding(14) + .background(CardsTheme.warning.opacity(0.12), in: RoundedRectangle(cornerRadius: 12, style: .continuous)) } .buttonStyle(.plain) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) } } + .padding(.horizontal, 20) } } @@ -131,7 +215,7 @@ struct DeckListView: View { Label("Noch keine Decks", systemImage: "rectangle.stack") .foregroundStyle(CardsTheme.foreground) } description: { - Text("Erstelle dein erstes Deck auf cardecky.mana.how oder ziehe nach unten zum Aktualisieren.") + Text("Tippe oben auf »+«, um dein erstes Deck zu erstellen, oder browse den Marketplace im Entdecken-Tab.") .foregroundStyle(CardsTheme.mutedForeground) } } @@ -139,45 +223,6 @@ struct DeckListView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } - @ViewBuilder - private var inboxBannerSection: some View { - if let inbox = decks.first(where: { $0.isFromMarketplace && $0.dueCount > 0 }) { - Section { - HStack(spacing: 12) { - Image(systemName: "tray.full.fill") - .foregroundStyle(CardsTheme.primary) - VStack(alignment: .leading, spacing: 2) { - Text("Inbox") - .font(.subheadline.weight(.semibold)) - .foregroundStyle(CardsTheme.foreground) - Text("\(inbox.dueCount) fällige Karten aus abonnierten Decks") - .font(.caption) - .foregroundStyle(CardsTheme.mutedForeground) - } - Spacer() - } - .padding() - .background(CardsTheme.surface, in: RoundedRectangle(cornerRadius: 10)) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - } - } - } - - private var ownDecksSection: some View { - Section { - ForEach(decks) { deck in - NavigationLink(value: deck.id) { - DeckRow(deck: deck) - } - .buttonStyle(.plain) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) - } - } - } - @ToolbarContentBuilder private var toolbar: some ToolbarContent { ToolbarItem(placement: .topBarLeading) { @@ -205,78 +250,3 @@ struct DeckListView: View { return "person.crop.circle.badge.exclamationmark" } } - -/// Einzelne Deck-Zeile in der Liste. -struct DeckRow: View { - let deck: CachedDeck - - var body: some View { - HStack(spacing: 12) { - // Farbiger Streifen aus deck.color (Hex), default forest-primary - RoundedRectangle(cornerRadius: 3) - .fill(deckColor) - .frame(width: 4) - - VStack(alignment: .leading, spacing: 4) { - HStack { - Text(deck.name) - .font(.headline) - .foregroundStyle(CardsTheme.foreground) - if deck.isFromMarketplace { - Image(systemName: "globe") - .font(.caption) - .foregroundStyle(CardsTheme.mutedForeground) - } - } - - if let category = deck.category { - Text(category.label) - .font(.caption) - .foregroundStyle(CardsTheme.mutedForeground) - } - - HStack(spacing: 12) { - Label("\(deck.cardCount)", systemImage: "rectangle.stack") - .font(.caption) - .foregroundStyle(CardsTheme.mutedForeground) - if deck.dueCount > 0 { - Label("\(deck.dueCount) fällig", systemImage: "clock.badge.exclamationmark") - .font(.caption.weight(.semibold)) - .foregroundStyle(CardsTheme.primary) - } - } - } - Spacer() - Image(systemName: "chevron.right") - .font(.footnote) - .foregroundStyle(CardsTheme.mutedForeground) - } - .padding(.vertical, 12) - .padding(.horizontal, 12) - .background(CardsTheme.surface, in: RoundedRectangle(cornerRadius: 10)) - } - - private var deckColor: Color { - guard let hex = deck.color, let rgb = parseHex(hex) else { - return CardsTheme.primary - } - return Color.manaHexLocal(rgb) - } - - private func parseHex(_ hex: String) -> UInt32? { - var trimmed = hex.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.hasPrefix("#") { trimmed = String(trimmed.dropFirst()) } - return UInt32(trimmed, radix: 16) - } -} - -private extension Color { - /// Lokales Hex-Helper analog zu `ManaTokens.Color.manaHex`. Hier - /// dupliziert, weil DeckRow nicht von ManaTokens abhängen muss. - static func manaHexLocal(_ rgb: UInt32) -> Color { - let r = Double((rgb >> 16) & 0xFF) / 255.0 - let g = Double((rgb >> 8) & 0xFF) / 255.0 - let b = Double(rgb & 0xFF) / 255.0 - return Color(red: r, green: g, blue: b) - } -} diff --git a/Sources/Features/Decks/DeckStackTile.swift b/Sources/Features/Decks/DeckStackTile.swift new file mode 100644 index 0000000..8b041d0 --- /dev/null +++ b/Sources/Features/Decks/DeckStackTile.swift @@ -0,0 +1,131 @@ +import SwiftUI + +/// Spiel-Karten-Stack-Visual mit drei gestaffelt-rotierten Hintergrund- +/// Layern hinter einer `CardSurface`. Web-Vorbild: +/// `cards/apps/web/src/lib/components/DeckStack.svelte`. +/// +/// Die Layer-Offsets + Tilts sind deterministisch aus der Deck-ID +/// gehasht — gleiches Deck zeigt immer gleiche Asymmetrie. +struct DeckStackTile: View { + let deck: CachedDeck + + var body: some View { + ZStack { + // Drei Hintergrund-Layer (von hinten nach vorne) + ForEach(Array(layers.enumerated()), id: \.offset) { _, layer in + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(CardsTheme.surface) + .overlay( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .stroke(CardsTheme.border, lineWidth: 1) + ) + .opacity(layer.opacity) + .rotationEffect(.degrees(layer.tilt)) + .offset(x: layer.dx, y: layer.dy) + .shadow(color: CardsTheme.foreground.opacity(0.05), radius: 2, y: 1) + } + + CardSurface(size: .md, elevation: .standard, colorAccentHex: deck.color) { + cardContent + } + } + .aspectRatio(5.0 / 7.0, contentMode: .fit) + .frame(maxWidth: 280) + } + + private var cardContent: some View { + VStack(alignment: .leading, spacing: 6) { + HStack(alignment: .top) { + Spacer() + Image(systemName: deck.category?.systemImageName ?? "rectangle.stack") + .font(.title3) + .foregroundStyle(CardsTheme.mutedForeground.opacity(0.85)) + } + + Spacer(minLength: 0) + + VStack(alignment: .leading, spacing: 6) { + Text(deck.name) + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(CardsTheme.foreground) + .lineLimit(3) + + if let description = deck.deckDescription, !description.isEmpty { + Text(description) + .font(.caption) + .foregroundStyle(CardsTheme.mutedForeground) + .lineLimit(2) + } + } + + Spacer(minLength: 0) + + HStack(spacing: 8) { + Label("\(deck.cardCount)", systemImage: "rectangle.stack") + .font(.caption2) + .foregroundStyle(CardsTheme.mutedForeground) + if deck.dueCount > 0 { + Text("\(deck.dueCount) fällig") + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background(CardsTheme.primary.opacity(0.15), in: Capsule()) + .foregroundStyle(CardsTheme.primary) + } + Spacer() + if deck.isFromMarketplace { + Image(systemName: "globe") + .font(.caption2) + .foregroundStyle(CardsTheme.mutedForeground) + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } + + /// Deterministische Stack-Layer aus Deck-ID gehasht. + private var layers: [StackLayer] { + var hash = UInt64(0) + for byte in deck.id.utf8 { + hash = hash &* 31 &+ UInt64(byte) + } + return (0 ..< 3).map { index in + let seed = hash &+ UInt64(index) &* 17 + let tiltRaw = Double((seed >> 8) & 0xFF) / 255.0 - 0.5 + let xRaw = Double((seed >> 16) & 0xFF) / 255.0 - 0.5 + let yRaw = Double((seed >> 24) & 0xFF) / 255.0 - 0.5 + let depth = Double(index + 1) + return StackLayer( + tilt: tiltRaw * 4.0, + dx: xRaw * 6.0, + dy: depth * 3.0 + yRaw * 2.0, + opacity: 0.7 - depth * 0.18 + ) + } + } +} + +private struct StackLayer { + let tilt: Double + let dx: Double + let dy: Double + let opacity: Double +} + +private extension DeckCategory { + var systemImageName: String { + switch self { + case .language: "character.book.closed" + case .medicine: "cross.case" + case .science: "atom" + case .math: "function" + case .history: "scroll" + case .law: "scale.3d" + case .technology: "cpu" + case .arts: "paintbrush" + case .music: "music.note" + case .sport: "figure.run" + case .other: "rectangle.stack" + } + } +} diff --git a/Sources/Features/Study/RatingBar.swift b/Sources/Features/Study/RatingBar.swift index 51d8181..5c3d85b 100644 --- a/Sources/Features/Study/RatingBar.swift +++ b/Sources/Features/Study/RatingBar.swift @@ -4,8 +4,9 @@ import SwiftUI import UIKit #endif -/// Vier Rating-Buttons unten am Bildschirm. Tap → onRate(rating) -/// plus Haptic-Feedback. +/// Vier Rating-Buttons mit emphasis auf "Good" (full-width primary). +/// Web-Vorbild: `cards/apps/web/src/routes/study/[deckId]/+page.svelte` +/// — `.grade.again/.hard/.good/.easy`-Klassen. struct RatingBar: View { let onRate: (Rating) -> Void @@ -16,17 +17,24 @@ struct RatingBar: View { triggerHaptic(for: rating) onRate(rating) } label: { - VStack(spacing: 2) { + HStack(spacing: 6) { Text(rating.label) .font(.subheadline.weight(.semibold)) Text(rating.shortcut) - .font(.caption2) - .foregroundStyle(.secondary) + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 5) + .padding(.vertical, 1) + .background(kbdBackground(for: rating), in: RoundedRectangle(cornerRadius: 4)) + .foregroundStyle(kbdForeground(for: rating)) } .frame(maxWidth: .infinity) .padding(.vertical, 14) - .background(background(for: rating), in: RoundedRectangle(cornerRadius: 10)) + .background(background(for: rating), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) .foregroundStyle(foreground(for: rating)) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .stroke(borderColor(for: rating), lineWidth: rating == .good ? 0 : 1) + ) } .buttonStyle(.plain) } @@ -34,12 +42,14 @@ struct RatingBar: View { .padding(.horizontal, 16) } + /// `good` ist die Hero-Action (primary full background) — analog + /// zum Web-Default-Klick. Andere bekommen subtle tinted borders. private func background(for rating: Rating) -> Color { switch rating { - case .again: CardsTheme.error.opacity(0.12) - case .hard: CardsTheme.warning.opacity(0.12) - case .good: CardsTheme.primary.opacity(0.12) - case .easy: CardsTheme.success.opacity(0.12) + case .again: CardsTheme.error.opacity(0.06) + case .hard: CardsTheme.warning.opacity(0.06) + case .good: CardsTheme.primary + case .easy: CardsTheme.success.opacity(0.06) } } @@ -47,17 +57,37 @@ struct RatingBar: View { switch rating { case .again: CardsTheme.error case .hard: CardsTheme.warning - case .good: CardsTheme.primary + case .good: CardsTheme.primaryForeground case .easy: CardsTheme.success } } + private func borderColor(for rating: Rating) -> Color { + switch rating { + case .again: CardsTheme.error.opacity(0.4) + case .hard: CardsTheme.warning.opacity(0.4) + case .good: .clear + case .easy: CardsTheme.success.opacity(0.4) + } + } + + private func kbdBackground(for rating: Rating) -> Color { + rating == .good + ? CardsTheme.primaryForeground.opacity(0.18) + : CardsTheme.muted + } + + private func kbdForeground(for rating: Rating) -> Color { + rating == .good + ? CardsTheme.primaryForeground.opacity(0.85) + : CardsTheme.mutedForeground + } + private func triggerHaptic(for rating: Rating) { #if canImport(UIKit) - let generator = UIImpactFeedbackGenerator( - style: rating == .easy ? .heavy : .medium - ) - generator.impactOccurred() + let style: UIImpactFeedbackGenerator.FeedbackStyle = + rating == .easy ? .heavy : .medium + UIImpactFeedbackGenerator(style: style).impactOccurred() #endif } } diff --git a/Sources/Features/Study/StudySessionView.swift b/Sources/Features/Study/StudySessionView.swift index 43d801f..822134f 100644 --- a/Sources/Features/Study/StudySessionView.swift +++ b/Sources/Features/Study/StudySessionView.swift @@ -101,21 +101,16 @@ struct StudySessionView: View { } private func cardSurface(due: DueReview, isFlipped: Bool) -> some View { - RoundedRectangle(cornerRadius: 16) - .fill(CardsTheme.surface) - .overlay( - CardRenderer( - card: due.card, - subIndex: due.review.subIndex, - isFlipped: isFlipped - ) + CardSurface(size: .hero, elevation: .raised) { + CardRenderer( + card: due.card, + subIndex: due.review.subIndex, + isFlipped: isFlipped ) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(CardsTheme.border, lineWidth: 1) - ) - .padding(.horizontal, 16) - .padding(.top, 12) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .padding(.horizontal, 16) + .padding(.top, 12) } private func finishedView(session: StudySession) -> some View { diff --git a/project.yml b/project.yml index 57253ca..971834e 100644 --- a/project.yml +++ b/project.yml @@ -55,7 +55,7 @@ targets: path: Sources/Resources/Info.plist properties: CFBundleShortVersionString: "0.1.0" - CFBundleVersion: "6" + CFBundleVersion: "7" CFBundleDevelopmentRegion: de CFBundleDisplayName: Cardecky LSApplicationCategoryType: "public.app-category.education" @@ -111,7 +111,7 @@ targets: properties: CFBundleDisplayName: Als Karte speichern CFBundleShortVersionString: "0.1.0" - CFBundleVersion: "6" + CFBundleVersion: "7" NSExtension: NSExtensionPointIdentifier: com.apple.share-services NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController @@ -144,7 +144,7 @@ targets: properties: CFBundleDisplayName: Cardecky Widget CFBundleShortVersionString: "0.1.0" - CFBundleVersion: "6" + CFBundleVersion: "7" NSExtension: NSExtensionPointIdentifier: com.apple.widgetkit-extension entitlements: