From 71b27b671bc21c74f9967535111d26c02cf260ea Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 24 Dec 2025 13:43:14 -0600 Subject: [PATCH] Signed-off-by: Matt Bruce --- Blackjack/Blackjack/Engine/GameState.swift | 4 ++++ Blackjack/Blackjack/Theme/DesignConstants.swift | 4 ++-- .../Blackjack/Views/Game/GameTableView.swift | 13 ++++++++++--- .../Views/Table/BlackjackTableView.swift | 15 ++++++++++++--- .../Blackjack/Views/Table/DealerHandView.swift | 9 +++++++-- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/Blackjack/Blackjack/Engine/GameState.swift b/Blackjack/Blackjack/Engine/GameState.swift index 099f7d2..a18243b 100644 --- a/Blackjack/Blackjack/Engine/GameState.swift +++ b/Blackjack/Blackjack/Engine/GameState.swift @@ -56,6 +56,10 @@ final class GameState { /// Whether to show the gameplay hint toast. var showHintToast: Bool = false + /// Tracks the current hint display session to prevent race conditions. + /// When a new hint arrives or is shown, increment this so old dismiss tasks become stale. + var hintDisplayID: UUID = UUID() + /// Whether a reshuffle notification should be shown. var showReshuffleNotification: Bool = false diff --git a/Blackjack/Blackjack/Theme/DesignConstants.swift b/Blackjack/Blackjack/Theme/DesignConstants.swift index 93dc652..272940f 100644 --- a/Blackjack/Blackjack/Theme/DesignConstants.swift +++ b/Blackjack/Blackjack/Theme/DesignConstants.swift @@ -20,7 +20,7 @@ enum Design { static let showDebugBorders = false /// Set to true to show debug log statements - static let showDebugLogs = false + static let showDebugLogs = true /// Debug logger - only prints when showDebugLogs is true static func debugLog(_ message: String) { @@ -84,7 +84,7 @@ enum Design { // Result banner static let resultRowAmountWidth: CGFloat = 70 - static let resultRowResultWidth: CGFloat = 150 + static let resultRowResultWidth: CGFloat = 120 // Side bet zones static let sideBetLabelFontSize: CGFloat = 13 diff --git a/Blackjack/Blackjack/Views/Game/GameTableView.swift b/Blackjack/Blackjack/Views/Game/GameTableView.swift index 1fb7f7e..cbb918d 100644 --- a/Blackjack/Blackjack/Views/Game/GameTableView.swift +++ b/Blackjack/Blackjack/Views/Game/GameTableView.swift @@ -87,15 +87,22 @@ struct GameTableView: View { icon: "lightbulb.fill", accessibilityLabel: String(localized: "Show Hint") ) { + // Generate new ID to invalidate any pending dismiss tasks + let currentID = UUID() + state.hintDisplayID = currentID + // Show the toast with animation withAnimation(.spring(duration: Design.Animation.springDuration)) { state.showHintToast = true } - // Auto-dismiss after delay (same as auto-show behavior) + // Auto-dismiss after delay, but only if this is still the active hint session Task { @MainActor in try? await Task.sleep(for: Design.Toast.duration) - withAnimation(.spring(duration: Design.Animation.springDuration)) { - state.showHintToast = false + // Only dismiss if no newer hint has arrived + if state.hintDisplayID == currentID { + withAnimation(.spring(duration: Design.Animation.springDuration)) { + state.showHintToast = false + } } } } diff --git a/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift b/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift index 9381004..d7267c9 100644 --- a/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift +++ b/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift @@ -19,6 +19,7 @@ struct BlackjackTableView: View { @Environment(\.verticalSizeClass) private var verticalSizeClass + // MARK: - Computed from stable screen size private var screenWidth: CGFloat { fullScreenSize.width } @@ -152,18 +153,26 @@ struct BlackjackTableView: View { .onChange(of: state.currentHint) { oldHint, newHint in // Show toast when a new hint appears if let hint = newHint, hint != oldHint { + // Generate new ID to invalidate any pending dismiss tasks + let currentID = UUID() + state.hintDisplayID = currentID + withAnimation(.spring(duration: Design.Animation.springDuration)) { state.showHintToast = true } - // Auto-dismiss after delay + // Auto-dismiss after delay, but only if this is still the active hint session Task { @MainActor in try? await Task.sleep(for: Design.Toast.duration) - withAnimation(.spring(duration: Design.Animation.springDuration)) { - state.showHintToast = false + // Only dismiss if no newer hint has arrived + if state.hintDisplayID == currentID { + withAnimation(.spring(duration: Design.Animation.springDuration)) { + state.showHintToast = false + } } } } else if newHint == nil { // Hide immediately when no hint + state.hintDisplayID = UUID() // Invalidate any pending dismiss tasks withAnimation(.spring(duration: Design.Animation.springDuration)) { state.showHintToast = false } diff --git a/Blackjack/Blackjack/Views/Table/DealerHandView.swift b/Blackjack/Blackjack/Views/Table/DealerHandView.swift index 38566ef..2aca7cc 100644 --- a/Blackjack/Blackjack/Views/Table/DealerHandView.swift +++ b/Blackjack/Blackjack/Views/Table/DealerHandView.swift @@ -16,27 +16,32 @@ struct DealerHandView: View { let cardSpacing: CGFloat @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize + @ScaledMetric(relativeTo: .headline) private var badgeHeight: CGFloat = CasinoDesign.Size.valueBadge var body: some View { VStack(spacing: Design.Spacing.small) { - // Label and value + // Label and value - fixed height prevents vertical layout shift HStack(spacing: Design.Spacing.small) { Text(String(localized: "DEALER")) .font(.system(size: labelFontSize, weight: .bold, design: .rounded)) .foregroundStyle(.white) - // Show value: full value when hole card visible, otherwise just the face-up card's value + // Badge animates in when cards are dealt if !hand.cards.isEmpty { if showHoleCard { // All cards visible - show total hand value ValueBadge(value: hand.value, color: Color.Hand.dealer) + .transition(.scale.combined(with: .opacity)) } else { // Hole card hidden - show only the first (face-up) card's value ValueBadge(value: hand.cards[0].blackjackValue, color: Color.Hand.dealer) + .transition(.scale.combined(with: .opacity)) } } } + .frame(minHeight: badgeHeight) // Reserve consistent height + .animation(.spring(duration: Design.Animation.springDuration), value: hand.cards.isEmpty) // Cards HStack(spacing: hand.cards.isEmpty ? Design.Spacing.small : cardSpacing) { if hand.cards.isEmpty {