Signed-off-by: Matt Bruce <matt.bruce1@toyota.com>

This commit is contained in:
Matt Bruce 2025-12-23 22:15:47 -06:00
parent 6b17039918
commit 8e82c8ab2b
4 changed files with 49 additions and 18 deletions

View File

@ -17,7 +17,7 @@ enum Design {
// MARK: - Debug // MARK: - Debug
/// Set to true to show layout debug borders on views /// Set to true to show layout debug borders on views
static let showDebugBorders = false static let showDebugBorders = true
// MARK: - Shared Constants (from CasinoKit) // MARK: - Shared Constants (from CasinoKit)
@ -71,6 +71,33 @@ enum Design {
// Table // Table
static let tableHeight: CGFloat = 280 static let tableHeight: CGFloat = 280
// Result banner
static let resultRowAmountWidth: CGFloat = 70
static let resultRowResultWidth: CGFloat = 150
// Side bet zones
static let sideBetLabelFontSize: CGFloat = 13
static let sideBetPayoutFontSize: CGFloat = 11
// Side bet toast notifications
static let sideBetToastTitleFontSize: CGFloat = 11
static let sideBetToastResultFontSize: CGFloat = 12
static let sideBetToastAmountFontSize: CGFloat = 14
}
// MARK: - Blackjack-Specific Delays
enum Delay {
/// Delay before playing game over sound
static let gameOverSound: Double = 1.0
}
// MARK: - Blackjack-Specific Animation
enum AnimationExtra {
/// Bounce for side bet toast animations
static let toastBounce: Double = 0.4
} }
} }

View File

@ -31,9 +31,9 @@ struct PlayerHandsView: View {
// Display hands in reverse order (right to left play order) // Display hands in reverse order (right to left play order)
// Visual order: Hand 3, Hand 2, Hand 1 (left to right) // Visual order: Hand 3, Hand 2, Hand 1 (left to right)
// Play order: Hand 1 played first (rightmost), then Hand 2, etc. // Play order: Hand 1 played first (rightmost), then Hand 2, etc.
ForEach(hands.indices.reversed(), id: \.self) { index in ForEach(Array(hands.enumerated()).reversed(), id: \.element.id) { index, hand in
PlayerHandView( PlayerHandView(
hand: hands[index], hand: hand,
isActive: index == activeHandIndex && isPlayerTurn, isActive: index == activeHandIndex && isPlayerTurn,
showCardCount: showCardCount, showCardCount: showCardCount,
// Hand numbers: rightmost (index 0) is Hand 1, played first // Hand numbers: rightmost (index 0) is Hand 1, played first
@ -41,35 +41,39 @@ struct PlayerHandsView: View {
cardWidth: cardWidth, cardWidth: cardWidth,
cardSpacing: cardSpacing cardSpacing: cardSpacing
) )
.id(index) .id(hand.id)
.transition(.scale.combined(with: .opacity))
} }
} }
.animation(.spring(duration: Design.Animation.springDuration), value: hands.count)
.padding(.horizontal, Design.Spacing.xxLarge) // More padding for scrolling .padding(.horizontal, Design.Spacing.xxLarge) // More padding for scrolling
} }
.scrollClipDisabled() .scrollClipDisabled()
.scrollBounceBehavior(.always) // Always allow bouncing for better scroll feel .scrollBounceBehavior(.always) // Always allow bouncing for better scroll feel
.defaultScrollAnchor(.center) // Center the content by default .defaultScrollAnchor(.center) // Center the content by default
.onChange(of: activeHandIndex) { _, newIndex in .onChange(of: activeHandIndex) { _, newIndex in
scrollToHand(proxy: proxy, index: newIndex) scrollToActiveHand(proxy: proxy)
} }
.onChange(of: totalCardCount) { _, _ in .onChange(of: totalCardCount) { _, _ in
// Scroll to active hand when cards are added (hit) // Scroll to active hand when cards are added (hit)
scrollToHand(proxy: proxy, index: activeHandIndex) scrollToActiveHand(proxy: proxy)
} }
.onChange(of: hands.count) { _, _ in .onChange(of: hands.count) { _, _ in
// Scroll to active hand when split occurs // Scroll to active hand when split occurs
scrollToHand(proxy: proxy, index: activeHandIndex) scrollToActiveHand(proxy: proxy)
} }
.onAppear { .onAppear {
scrollToHand(proxy: proxy, index: activeHandIndex) scrollToActiveHand(proxy: proxy)
} }
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
private func scrollToHand(proxy: ScrollViewProxy, index: Int) { private func scrollToActiveHand(proxy: ScrollViewProxy) {
guard activeHandIndex < hands.count else { return }
let activeHandId = hands[activeHandIndex].id
withAnimation(.easeInOut(duration: Design.Animation.quick)) { withAnimation(.easeInOut(duration: Design.Animation.quick)) {
proxy.scrollTo(index, anchor: .center) proxy.scrollTo(activeHandId, anchor: .center)
} }
} }
} }

View File

@ -18,9 +18,9 @@ struct SideBetToastView: View {
@State private var isShowing = false @State private var isShowing = false
@ScaledMetric(relativeTo: .caption) private var titleFontSize: CGFloat = 10 @ScaledMetric(relativeTo: .caption) private var titleFontSize: CGFloat = Design.Size.sideBetToastTitleFontSize
@ScaledMetric(relativeTo: .caption2) private var resultFontSize: CGFloat = 11 @ScaledMetric(relativeTo: .callout) private var resultFontSize: CGFloat = Design.Size.sideBetToastResultFontSize
@ScaledMetric(relativeTo: .caption2) private var amountFontSize: CGFloat = 13 @ScaledMetric(relativeTo: .body) private var amountFontSize: CGFloat = Design.Size.sideBetToastAmountFontSize
private var backgroundColor: Color { private var backgroundColor: Color {
isWin ? Color.green.opacity(Design.Opacity.heavy) : Color.red.opacity(Design.Opacity.heavy) isWin ? Color.green.opacity(Design.Opacity.heavy) : Color.red.opacity(Design.Opacity.heavy)
@ -68,14 +68,14 @@ struct SideBetToastView: View {
) )
) )
.shadow(color: borderColor.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium) .shadow(color: borderColor.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium)
.scaleEffect(isShowing ? 1.0 : 0.5) .scaleEffect(isShowing ? Design.Scale.normal : Design.Scale.shrunk)
.opacity(isShowing ? 1.0 : 0) .opacity(isShowing ? 1.0 : 0)
.offset(x: isShowing ? 0 : (showOnLeft ? -Design.Spacing.toastSlide : Design.Spacing.toastSlide)) .offset(x: isShowing ? 0 : (showOnLeft ? -Design.Spacing.toastSlide : Design.Spacing.toastSlide))
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
.accessibilityLabel("\(title): \(result)") .accessibilityLabel("\(title): \(result)")
.accessibilityValue(amountText) .accessibilityValue(amountText)
.onAppear { .onAppear {
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: 0.4).delay(showOnLeft ? 0 : Design.Animation.staggerDelay1)) { withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.AnimationExtra.toastBounce).delay(showOnLeft ? 0 : Design.Animation.staggerDelay1)) {
isShowing = true isShowing = true
} }
} }

View File

@ -16,8 +16,8 @@ struct SideBetZoneView: View {
let isAtMax: Bool let isAtMax: Bool
let onTap: () -> Void let onTap: () -> Void
@ScaledMetric(relativeTo: .caption) private var labelFontSize: CGFloat = 11 @ScaledMetric(relativeTo: .callout) private var labelFontSize: CGFloat = Design.Size.sideBetLabelFontSize
@ScaledMetric(relativeTo: .caption2) private var payoutFontSize: CGFloat = 9 @ScaledMetric(relativeTo: .caption) private var payoutFontSize: CGFloat = Design.Size.sideBetPayoutFontSize
private var backgroundColor: Color { private var backgroundColor: Color {
switch betType { switch betType {
@ -49,7 +49,7 @@ struct SideBetZoneView: View {
RoundedRectangle(cornerRadius: Design.CornerRadius.medium) RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.strokeBorder( .strokeBorder(
Color.white.opacity(Design.Opacity.hint), Color.white.opacity(Design.Opacity.hint),
lineWidth: Design.LineWidth.thin lineWidth: Design.LineWidth.medium
) )
) )