Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-12-24 13:43:14 -06:00
parent 21e5d901c7
commit 71b27b671b
5 changed files with 35 additions and 10 deletions

View File

@ -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

View File

@ -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

View File

@ -87,18 +87,25 @@ 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)
// Only dismiss if no newer hint has arrived
if state.hintDisplayID == currentID {
withAnimation(.spring(duration: Design.Animation.springDuration)) {
state.showHintToast = false
}
}
}
}
]
}

View File

@ -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)
// 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
}

View File

@ -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 {