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

This commit is contained in:
Matt Bruce 2025-12-22 13:37:52 -06:00
parent 78f7cb1544
commit cac0af4ab3
3 changed files with 216 additions and 97 deletions

View File

@ -34,22 +34,9 @@
EAD890CE2EF1E9CF006DBA80 /* BaccaratUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; EAD890CE2EF1E9CF006DBA80 /* BaccaratUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
EAD891062EF1F51E006DBA80 /* Exceptions for "Baccarat" folder in "Baccarat" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Agents.md,
);
target = EAD890B62EF1E9CE006DBA80 /* Baccarat */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
EAD890B92EF1E9CE006DBA80 /* Baccarat */ = { EAD890B92EF1E9CE006DBA80 /* Baccarat */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
EAD891062EF1F51E006DBA80 /* Exceptions for "Baccarat" folder in "Baccarat" target */,
);
path = Baccarat; path = Baccarat;
sourceTree = "<group>"; sourceTree = "<group>";
}; };

View File

@ -29,15 +29,50 @@ enum Design {
// MARK: - Baccarat-Specific Component Sizes // MARK: - Baccarat-Specific Component Sizes
enum Size { enum Size {
// Cards - use CasinoDesign values // MARK: - Hand Scaling
/// Hand scaling factor for cards and related elements.
/// 1.0 = original size, 1.5 = 50% larger, 2.0 = double size.
/// Adjust this value to change card sizes across the app.
static let handScale: CGFloat = 1.5
/// Scale multiplier for small screens (iPhone SE, etc).
/// Applied instead of handScale on screens narrower than smallScreenThreshold.
static let smallScreenScale: CGFloat = 1.2
/// Screen width threshold for small screen detection (iPhone SE is 375pt)
static let smallScreenThreshold: CGFloat = 390
/// Additional scale multiplier for large screens (iPad).
/// Applied on top of handScale when on regular size class.
static let largeScreenMultiplier: CGFloat = 1.2
// Cards - base values from CasinoDesign
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
static let cardWidthMedium: CGFloat = CasinoDesign.Size.cardWidthMedium static let cardWidthMedium: CGFloat = CasinoDesign.Size.cardWidthMedium
static let cardWidthLarge: CGFloat = CasinoDesign.Size.cardWidthLarge static let cardWidthLarge: CGFloat = CasinoDesign.Size.cardWidthLarge
static let cardAspectRatio: CGFloat = CasinoDesign.Size.cardAspectRatio static let cardAspectRatio: CGFloat = CasinoDesign.Size.cardAspectRatio
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap
// Baccarat table cards (smaller for compact layout) /// Base card width before scaling (for reference)
static let cardWidthTable: CGFloat = 45 private static let cardWidthTableBase: CGFloat = 45
/// Card overlap scaled with hand size (standard iPhone)
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap * handScale
/// Card overlap for small screens
static let cardOverlapSmall: CGFloat = CasinoDesign.Size.cardOverlap * smallScreenScale
// Baccarat table cards - scaled for better visibility (standard iPhone)
static let cardWidthTable: CGFloat = cardWidthTableBase * handScale
/// Card width for small screens (iPhone SE, etc)
static let cardWidthTableSmall: CGFloat = cardWidthTableBase * smallScreenScale
/// Card width for large screens (iPad) - applies additional multiplier
static let cardWidthTableLarge: CGFloat = cardWidthTableBase * handScale * largeScreenMultiplier
/// Card overlap for large screens
static let cardOverlapLarge: CGFloat = CasinoDesign.Size.cardOverlap * handScale * largeScreenMultiplier
// Chips - use CasinoDesign values // Chips - use CasinoDesign values
static let chipSmall: CGFloat = CasinoDesign.Size.chipSmall static let chipSmall: CGFloat = CasinoDesign.Size.chipSmall

View File

@ -52,6 +52,7 @@ struct GameTableView: View {
} }
var body: some View { var body: some View {
GeometryReader { geometry in
ZStack { ZStack {
// Table background // Table background
TableBackgroundView() TableBackgroundView()
@ -81,7 +82,8 @@ struct GameTableView: View {
bankerValue: state.bankerHandValue, bankerValue: state.bankerHandValue,
playerIsWinner: playerIsWinner, playerIsWinner: playerIsWinner,
bankerIsWinner: bankerIsWinner, bankerIsWinner: bankerIsWinner,
isTie: isTie isTie: isTie,
screenWidth: geometry.size.width
) )
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity) .frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
@ -167,6 +169,7 @@ struct GameTableView: View {
.transition(.opacity) .transition(.opacity)
} }
} }
}
.onAppear { .onAppear {
if gameState == nil { if gameState == nil {
gameState = GameState(settings: settings) gameState = GameState(settings: settings)
@ -347,11 +350,27 @@ struct CardsDisplayArea: View {
let playerIsWinner: Bool let playerIsWinner: Bool
let bankerIsWinner: Bool let bankerIsWinner: Bool
let isTie: Bool let isTie: Bool
/// Screen width for responsive card sizing
var screenWidth: CGFloat = 400
// MARK: - Fixed font sizes for card area // MARK: - Environment
// Fixed because the card display has strict layout constraints
private let labelFontSize: CGFloat = 14 @Environment(\.horizontalSizeClass) private var horizontalSizeClass
// MARK: - Scaled font sizes for card area
// Scales with hand size for proportional appearance
/// Whether we're on a large screen (iPad)
private var isLargeScreen: Bool {
horizontalSizeClass == .regular
}
/// Label font size - only scales on iPad to avoid clipping on small iPhones
private var labelFontSize: CGFloat {
let baseSize: CGFloat = 14
// Only apply scaling on large screens; keep original size on iPhone
return isLargeScreen ? baseSize * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseSize
}
// MARK: - Accessibility // MARK: - Accessibility
@ -387,8 +406,25 @@ struct CardsDisplayArea: View {
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue) return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
} }
/// Minimum height for label row - only scales on iPad
private var labelRowMinHeight: CGFloat {
let baseHeight: CGFloat = 30
// Only apply scaling on large screens; keep original size on iPhone
return isLargeScreen ? baseHeight * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseHeight
}
/// Spacing between PLAYER and BANKER hands - reduced on smaller screens
private var handsSpacing: CGFloat {
isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.medium
}
/// Horizontal padding inside the container - reduced on smaller screens
private var containerPaddingH: CGFloat {
isLargeScreen ? Design.Spacing.xLarge : Design.Spacing.small
}
var body: some View { var body: some View {
HStack(spacing: Design.Spacing.xxxLarge) { HStack(spacing: handsSpacing) {
// Player side // Player side
VStack(spacing: Design.Spacing.small) { VStack(spacing: Design.Spacing.small) {
// Label with value // Label with value
@ -401,13 +437,14 @@ struct CardsDisplayArea: View {
ValueBadge(value: playerValue, color: .blue) ValueBadge(value: playerValue, color: .blue)
} }
} }
.frame(minHeight: 30) .frame(minHeight: labelRowMinHeight)
// Cards // Cards
CompactHandView( CompactHandView(
cards: playerCards, cards: playerCards,
cardsFaceUp: playerCardsFaceUp, cardsFaceUp: playerCardsFaceUp,
isWinner: playerIsWinner isWinner: playerIsWinner,
screenWidth: screenWidth
) )
} }
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
@ -426,28 +463,29 @@ struct CardsDisplayArea: View {
ValueBadge(value: bankerValue, color: .red) ValueBadge(value: bankerValue, color: .red)
} }
} }
.frame(minHeight: 30) .frame(minHeight: labelRowMinHeight)
// Cards // Cards
CompactHandView( CompactHandView(
cards: bankerCards, cards: bankerCards,
cardsFaceUp: bankerCardsFaceUp, cardsFaceUp: bankerCardsFaceUp,
isWinner: bankerIsWinner isWinner: bankerIsWinner,
screenWidth: screenWidth
) )
} }
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel(String(localized: "Banker hand")) .accessibilityLabel(String(localized: "Banker hand"))
.accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : "")) .accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : ""))
} }
.padding(.top, Design.Spacing.large) .padding(.top, Design.Spacing.medium)
.padding(.bottom, Design.Spacing.xLarge) .padding(.bottom, Design.Spacing.large)
.padding(.horizontal, Design.Spacing.xLarge) .padding(.horizontal, containerPaddingH)
.background( .background(
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge) RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
.fill(Color.black.opacity(Design.Opacity.quarter)) .fill(Color.black.opacity(Design.Opacity.quarter))
.accessibilityHidden(true) .accessibilityHidden(true)
) )
.padding(.horizontal) .padding(.horizontal, Design.Spacing.small)
} }
} }
@ -456,17 +494,59 @@ struct CompactHandView: View {
let cards: [Card] let cards: [Card]
let cardsFaceUp: [Bool] let cardsFaceUp: [Bool]
let isWinner: Bool let isWinner: Bool
/// Screen width passed from parent for responsive sizing
var screenWidth: CGFloat = 400
// MARK: - Scaled Font Sizes (Dynamic Type) // MARK: - Environment
@ScaledMetric(relativeTo: .caption) private var winBadgeFontSize: CGFloat = 10 @Environment(\.horizontalSizeClass) private var horizontalSizeClass
// MARK: - Layout Constants // MARK: - Layout Constants
// Fixed size: cards have strict visual constraints // Responsive sizing based on device
/// Whether we're on a large screen (iPad)
private var isLargeScreen: Bool {
horizontalSizeClass == .regular
}
/// Whether we're on a small screen (iPhone SE, etc)
private var isSmallScreen: Bool {
!isLargeScreen && screenWidth < Design.Size.smallScreenThreshold
}
/// WIN badge font size - only scales on iPad
private var winBadgeFontSize: CGFloat {
let baseSize: CGFloat = 10
return isLargeScreen ? baseSize * Design.Size.handScale * Design.Size.largeScreenMultiplier : baseSize
}
/// Card width - responsive based on screen size
private var cardWidth: CGFloat {
if isLargeScreen {
return Design.Size.cardWidthTableLarge
} else if isSmallScreen {
return Design.Size.cardWidthTableSmall
} else {
return Design.Size.cardWidthTable
}
}
/// Card height based on aspect ratio
private var cardHeight: CGFloat {
cardWidth * Design.Size.cardAspectRatio
}
/// Card overlap - scaled with card size
private var cardOverlap: CGFloat {
if isLargeScreen {
return Design.Size.cardOverlapLarge
} else if isSmallScreen {
return Design.Size.cardOverlapSmall
} else {
return Design.Size.cardOverlap
}
}
private let cardWidth: CGFloat = Design.Size.cardWidthTable
private let cardHeight: CGFloat = Design.Size.cardWidthTable * Design.Size.cardAspectRatio
private let cardOverlap: CGFloat = Design.Size.cardOverlap
private let placeholderSpacing: CGFloat = Design.Spacing.small private let placeholderSpacing: CGFloat = Design.Spacing.small
/// Fixed container width to prevent resizing during deal /// Fixed container width to prevent resizing during deal
@ -538,10 +618,27 @@ struct ValueBadge: View {
let value: Int let value: Int
let color: Color let color: Color
// MARK: - Environment
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
// MARK: - Scaled Font Sizes (Dynamic Type) // MARK: - Scaled Font Sizes (Dynamic Type)
@ScaledMetric(relativeTo: .headline) private var valueFontSize: CGFloat = 15 /// Whether we're on a large screen (iPad)
@ScaledMetric(relativeTo: .headline) private var badgeSize: CGFloat = 26 private var isLargeScreen: Bool {
horizontalSizeClass == .regular
}
/// Scale factor for badge sizing - only applies on iPad to avoid clipping on iPhone
private var scale: CGFloat {
isLargeScreen ? Design.Size.handScale * Design.Size.largeScreenMultiplier : 1.0
}
@ScaledMetric(relativeTo: .headline) private var baseValueFontSize: CGFloat = 15
@ScaledMetric(relativeTo: .headline) private var baseBadgeSize: CGFloat = 26
private var valueFontSize: CGFloat { baseValueFontSize * scale }
private var badgeSize: CGFloat { baseBadgeSize * scale }
var body: some View { var body: some View {
Text("\(value)") Text("\(value)")