Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
7913cb2d45
commit
2b7b770d3a
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
uuid = "88E73C19-3668-4217-9B1F-56B52ACBA7E0"
|
||||
type = "1"
|
||||
version = "2.0">
|
||||
<Breakpoints>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "B46496BF-3442-4357-9248-38B07C7AB65D"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "Baccarat/Views/GameTableView.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "68"
|
||||
endingLineNumber = "68"
|
||||
landmarkName = "body"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
@ -143,6 +143,7 @@
|
||||
},
|
||||
"BALANCE" : {
|
||||
"comment" : "The label for the user's balance in the top bar.",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
|
||||
@ -290,9 +290,10 @@ struct CardsDisplayArea: View {
|
||||
let bankerIsWinner: Bool
|
||||
let isTie: Bool
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Fixed font sizes for card area
|
||||
// Fixed because the card display has strict layout constraints
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 14
|
||||
private let labelFontSize: CGFloat = 14
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.xxxLarge) {
|
||||
@ -482,36 +483,42 @@ struct TopBarView: View {
|
||||
let onReset: () -> Void
|
||||
let onSettings: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Environment
|
||||
|
||||
@ScaledMetric(relativeTo: .caption2) private var labelFontSize: CGFloat = 9
|
||||
@ScaledMetric(relativeTo: .body) private var currencyFontSize: CGFloat = 14
|
||||
@ScaledMetric(relativeTo: .title3) private var balanceFontSize: CGFloat = 20
|
||||
@ScaledMetric(relativeTo: .caption) private var smallFontSize: CGFloat = 12
|
||||
@ScaledMetric(relativeTo: .body) private var buttonFontSize: CGFloat = 16
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
|
||||
/// Whether the current text size is an accessibility size (very large)
|
||||
private var isAccessibilitySize: Bool {
|
||||
dynamicTypeSize.isAccessibilitySize
|
||||
}
|
||||
|
||||
// MARK: - Fixed font sizes for constrained top bar
|
||||
// These use fixed sizes because the top bar has strict space constraints
|
||||
// and must remain readable at all accessibility settings
|
||||
|
||||
private let labelFontSize: CGFloat = 9
|
||||
private let currencyFontSize: CGFloat = 14
|
||||
private let balanceFontSize: CGFloat = 20
|
||||
private let smallFontSize: CGFloat = 12
|
||||
private let buttonFontSize: CGFloat = 16
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
// Balance display
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text("BALANCE")
|
||||
.font(.system(size: labelFontSize, weight: .medium, design: .rounded))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
.tracking(1)
|
||||
// Balance display - simplified at accessibility sizes
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
Text("$")
|
||||
.font(.system(size: currencyFontSize, weight: .bold))
|
||||
.foregroundStyle(.yellow.opacity(0.8))
|
||||
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
Text("$")
|
||||
.font(.system(size: currencyFontSize, weight: .bold))
|
||||
.foregroundStyle(.yellow.opacity(0.8))
|
||||
|
||||
Text(balance, format: .number)
|
||||
.font(.system(size: balanceFontSize, weight: .black, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
.contentTransition(.numericText())
|
||||
.animation(.spring(duration: Design.Animation.quick), value: balance)
|
||||
}
|
||||
Text(balance, format: .number)
|
||||
.font(.system(size: balanceFontSize, weight: .black, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
.contentTransition(.numericText())
|
||||
.animation(.spring(duration: Design.Animation.quick), value: balance)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.background(
|
||||
Capsule()
|
||||
@ -520,8 +527,8 @@ struct TopBarView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
// Cards remaining indicator (if enabled)
|
||||
if showCardsRemaining {
|
||||
// Cards remaining indicator - hidden at accessibility sizes to save space
|
||||
if showCardsRemaining && !isAccessibilitySize {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
Image(systemName: "rectangle.portrait.on.rectangle.portrait.fill")
|
||||
.font(.system(size: smallFontSize))
|
||||
@ -533,7 +540,7 @@ struct TopBarView: View {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
// Settings button
|
||||
// Settings button (icon only)
|
||||
Button("Settings", systemImage: "gearshape.fill", action: onSettings)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: buttonFontSize))
|
||||
@ -544,14 +551,14 @@ struct TopBarView: View {
|
||||
.fill(Color.black.opacity(Design.Opacity.overlay))
|
||||
)
|
||||
|
||||
// Reset button
|
||||
// Reset button (icon only to save space)
|
||||
Button("Reset", systemImage: "arrow.counterclockwise", action: onReset)
|
||||
.font(.system(size: smallFontSize, weight: .medium))
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: buttonFontSize))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
.padding(.horizontal, Design.Spacing.small)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.padding(Design.Spacing.small)
|
||||
.background(
|
||||
Capsule()
|
||||
Circle()
|
||||
.fill(Color.black.opacity(Design.Opacity.overlay))
|
||||
)
|
||||
}
|
||||
@ -567,65 +574,33 @@ struct ActionButtonsView: View {
|
||||
let onClear: () -> Void
|
||||
let onNewRound: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Environment
|
||||
|
||||
@ScaledMetric(relativeTo: .body) private var clearButtonFontSize: CGFloat = 14
|
||||
@ScaledMetric(relativeTo: .headline) private var primaryButtonFontSize: CGFloat = 16
|
||||
@ScaledMetric(relativeTo: .body) private var statusFontSize: CGFloat = 14
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
|
||||
/// Whether the current text size is an accessibility size (very large)
|
||||
private var isAccessibilitySize: Bool {
|
||||
dynamicTypeSize.isAccessibilitySize
|
||||
}
|
||||
|
||||
// MARK: - Fixed font sizes for action buttons
|
||||
// Fixed because buttons have constrained space and must remain usable
|
||||
|
||||
private let buttonFontSize: CGFloat = 16
|
||||
private let iconSize: CGFloat = 24
|
||||
private let statusFontSize: CGFloat = 14
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
if gameState.currentPhase == .betting {
|
||||
// Clear bets button
|
||||
Button("Clear", systemImage: "xmark.circle", action: onClear)
|
||||
.font(.system(size: clearButtonFontSize, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.Button.destructive)
|
||||
)
|
||||
.opacity(gameState.currentBets.isEmpty ? Design.Opacity.disabled : 1.0)
|
||||
.disabled(gameState.currentBets.isEmpty)
|
||||
// Clear bets button - icon only at accessibility sizes
|
||||
clearButton
|
||||
|
||||
// Deal button
|
||||
Button("Deal", systemImage: "play.fill", action: onDeal)
|
||||
.font(.system(size: primaryButtonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, Design.Spacing.xxxLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
.opacity(gameState.canDeal ? 1.0 : Design.Opacity.disabled)
|
||||
.disabled(!gameState.canDeal)
|
||||
// Deal button - icon only at accessibility sizes
|
||||
dealButton
|
||||
} else if gameState.currentPhase == .roundComplete {
|
||||
// New round button
|
||||
Button("New Round", systemImage: "arrow.right.circle", action: onNewRound)
|
||||
.font(.system(size: primaryButtonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, Design.Spacing.xxxLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
// New round button - icon only at accessibility sizes
|
||||
newRoundButton
|
||||
} else {
|
||||
// Playing indicator
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
@ -635,12 +610,128 @@ struct ActionButtonsView: View {
|
||||
Text("Dealing...")
|
||||
.font(.system(size: statusFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.heavy))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.7)
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var clearButton: some View {
|
||||
if isAccessibilitySize {
|
||||
Button("Clear", systemImage: "xmark.circle", action: onClear)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: iconSize, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(Design.Spacing.medium)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(Color.Button.destructive)
|
||||
)
|
||||
.opacity(gameState.currentBets.isEmpty ? Design.Opacity.disabled : 1.0)
|
||||
.disabled(gameState.currentBets.isEmpty)
|
||||
} else {
|
||||
Button("Clear", systemImage: "xmark.circle", action: onClear)
|
||||
.labelStyle(.titleOnly)
|
||||
.font(.system(size: buttonFontSize, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.Button.destructive)
|
||||
)
|
||||
.opacity(gameState.currentBets.isEmpty ? Design.Opacity.disabled : 1.0)
|
||||
.disabled(gameState.currentBets.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var dealButton: some View {
|
||||
if isAccessibilitySize {
|
||||
Button("Deal", systemImage: "play.fill", action: onDeal)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: iconSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(Design.Spacing.medium)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
.opacity(gameState.canDeal ? 1.0 : Design.Opacity.disabled)
|
||||
.disabled(!gameState.canDeal)
|
||||
} else {
|
||||
Button("Deal", systemImage: "play.fill", action: onDeal)
|
||||
.labelStyle(.titleOnly)
|
||||
.font(.system(size: buttonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, Design.Spacing.xxxLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
.opacity(gameState.canDeal ? 1.0 : Design.Opacity.disabled)
|
||||
.disabled(!gameState.canDeal)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var newRoundButton: some View {
|
||||
if isAccessibilitySize {
|
||||
Button("New Round", systemImage: "arrow.right.circle", action: onNewRound)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: iconSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(Design.Spacing.medium)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
} else {
|
||||
Button("New Round", systemImage: "arrow.right.circle", action: onNewRound)
|
||||
.labelStyle(.titleOnly)
|
||||
.font(.system(size: buttonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, Design.Spacing.xxxLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@ -12,9 +12,10 @@ struct MiniBaccaratTableView: View {
|
||||
@Bindable var gameState: GameState
|
||||
let selectedChip: ChipDenomination
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Fixed Font Sizes
|
||||
// Fixed because the table area has strict layout constraints
|
||||
|
||||
@ScaledMetric(relativeTo: .caption) private var tableLimitsFontSize: CGFloat = Design.BaseFontSize.small
|
||||
private let tableLimitsFontSize: CGFloat = Design.BaseFontSize.small
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
@ -68,6 +69,8 @@ struct MiniBaccaratTableView: View {
|
||||
.font(.system(size: tableLimitsFontSize, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
.tracking(1)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.6)
|
||||
|
||||
ZStack {
|
||||
// Table felt background with arc shape
|
||||
@ -201,10 +204,11 @@ struct TieBettingZone: View {
|
||||
var isAtMax: Bool = false
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Fixed Font Sizes
|
||||
// Fixed because betting zones have strict space constraints
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var titleFontSize: CGFloat = Design.BaseFontSize.medium
|
||||
@ScaledMetric(relativeTo: .caption2) private var subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
private let titleFontSize: CGFloat = Design.BaseFontSize.medium
|
||||
private let subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
@ -243,10 +247,14 @@ struct TieBettingZone: View {
|
||||
Text("TIE")
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(2)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
|
||||
Text("PAYS 8 TO 1")
|
||||
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||
.opacity(Design.Opacity.heavy)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
@ -270,10 +278,11 @@ struct BankerBettingZone: View {
|
||||
var isAtMax: Bool = false
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Fixed Font Sizes
|
||||
// Fixed because betting zones have strict space constraints
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var titleFontSize: CGFloat = Design.BaseFontSize.large
|
||||
@ScaledMetric(relativeTo: .caption2) private var subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
private let titleFontSize: CGFloat = Design.BaseFontSize.large
|
||||
private let subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
@ -328,10 +337,14 @@ struct BankerBettingZone: View {
|
||||
Text("BANKER")
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(3)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
|
||||
Text("PAYS 0.95 TO 1")
|
||||
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||
.opacity(Design.Opacity.heavy)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
@ -355,10 +368,11 @@ struct PlayerBettingZone: View {
|
||||
var isAtMax: Bool = false
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
// MARK: - Fixed Font Sizes
|
||||
// Fixed because betting zones have strict space constraints
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var titleFontSize: CGFloat = Design.BaseFontSize.large
|
||||
@ScaledMetric(relativeTo: .caption2) private var subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
private let titleFontSize: CGFloat = Design.BaseFontSize.large
|
||||
private let subtitleFontSize: CGFloat = Design.BaseFontSize.xSmall
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
@ -413,10 +427,14 @@ struct PlayerBettingZone: View {
|
||||
Text("PLAYER")
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(3)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
|
||||
Text("PAYS 1 TO 1")
|
||||
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||
.opacity(Design.Opacity.heavy)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user