Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
644127ae40
commit
50f569d137
@ -719,6 +719,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Add $%lld more to meet minimum" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add $%lld more to meet minimum"
|
||||
}
|
||||
},
|
||||
"es-MX" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Añade $%lld más para alcanzar el mínimo"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ajoutez %lld$ de plus pour atteindre le minimum"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Clear" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
||||
@ -31,10 +31,44 @@ enum Design {
|
||||
// MARK: - Blackjack-Specific Component Sizes
|
||||
|
||||
enum Size {
|
||||
// Cards - slightly larger than medium for better visibility
|
||||
static let cardWidth: CGFloat = 60
|
||||
// Hand scaling factor (1.5 = 50% larger hands)
|
||||
static let handScale: CGFloat = 1.5
|
||||
|
||||
// Cards - scaled for better visibility
|
||||
static let cardWidth: CGFloat = 60 * handScale // 90pt at 1.5x
|
||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap
|
||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap * handScale // Scaled overlap
|
||||
|
||||
// Player hands container height (accommodates larger cards)
|
||||
static let playerHandsHeight: CGFloat = 180 * handScale // 270pt at 1.5x
|
||||
|
||||
// Hand label font sizes (scaled)
|
||||
static let handLabelFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * handScale
|
||||
static let handNumberFontSize: CGFloat = CasinoDesign.BaseFontSize.small * handScale
|
||||
static let handValueFontSize: CGFloat = CasinoDesign.BaseFontSize.xLarge * handScale
|
||||
|
||||
// Hint font size (scaled to match hands)
|
||||
static let hintFontSize: CGFloat = CasinoDesign.BaseFontSize.small * handScale
|
||||
static let hintIconSize: CGFloat = CasinoDesign.IconSize.medium * handScale
|
||||
static let hintPaddingH: CGFloat = CasinoDesign.Spacing.medium * handScale
|
||||
static let hintPaddingV: CGFloat = CasinoDesign.Spacing.small * handScale
|
||||
|
||||
// Hand icons (scaled)
|
||||
static let handIconSize: CGFloat = CasinoDesign.IconSize.medium * handScale
|
||||
|
||||
// Hi-Lo count badge (scaled)
|
||||
static let countBadgeFontSize: CGFloat = CasinoDesign.BaseFontSize.xxSmall * handScale
|
||||
static let countBadgePaddingH: CGFloat = CasinoDesign.Spacing.xSmall * handScale
|
||||
static let countBadgePaddingV: CGFloat = CasinoDesign.Spacing.xxxSmall * handScale
|
||||
static let countBadgeOffset: CGFloat = CasinoDesign.Spacing.xSmall * handScale
|
||||
|
||||
// Betting zone (chip scales, but zone height stays reasonable)
|
||||
static let bettingChipSize: CGFloat = 36 * handScale // 54pt at 1.5x
|
||||
static let bettingZoneHeightScaled: CGFloat = CasinoDesign.Size.bettingZoneHeight // Keep original height to save space
|
||||
|
||||
// Card count display (scaled)
|
||||
static let cardCountLabelSize: CGFloat = CasinoDesign.BaseFontSize.xSmall * handScale
|
||||
static let cardCountValueSize: CGFloat = CasinoDesign.BaseFontSize.large * handScale
|
||||
|
||||
// Chips - use CasinoDesign values
|
||||
static let chipBadgeSize: CGFloat = CasinoDesign.Size.chipBadge
|
||||
|
||||
@ -14,7 +14,7 @@ struct BettingZoneView: View {
|
||||
let maxBet: Int
|
||||
let onTap: () -> Void
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.BaseFontSize.large
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
|
||||
private var isAtMax: Bool {
|
||||
betAmount >= maxBet
|
||||
@ -33,8 +33,8 @@ struct BettingZoneView: View {
|
||||
|
||||
// Content
|
||||
if betAmount > 0 {
|
||||
// Show chip with amount
|
||||
ChipOnTableView(amount: betAmount, showMax: isAtMax)
|
||||
// Show chip with amount (scaled)
|
||||
ChipOnTableView(amount: betAmount, showMax: isAtMax, size: Design.Size.bettingChipSize)
|
||||
} else {
|
||||
// Empty state
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
@ -44,18 +44,18 @@ struct BettingZoneView: View {
|
||||
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
Text(String(localized: "Min: $\(minBet)"))
|
||||
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
||||
.font(.system(size: Design.Size.handNumberFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.light))
|
||||
|
||||
Text(String(localized: "Max: $\(maxBet.formatted())"))
|
||||
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
||||
.font(.system(size: Design.Size.handNumberFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.light))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: Design.Size.bettingZoneHeight)
|
||||
.frame(height: Design.Size.bettingZoneHeightScaled)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(betAmount > 0 ? "$\(betAmount) bet" + (isAtMax ? ", maximum" : "") : "Place bet")
|
||||
|
||||
@ -39,15 +39,18 @@ struct BlackjackTableView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
// Player hands area
|
||||
PlayerHandsView(
|
||||
hands: state.playerHands,
|
||||
activeHandIndex: state.activeHandIndex,
|
||||
isPlayerTurn: isPlayerTurn,
|
||||
showCardCount: showCardCount,
|
||||
cardWidth: cardWidth,
|
||||
cardSpacing: cardSpacing
|
||||
)
|
||||
// Player hands area - only show when there are cards dealt
|
||||
if state.playerHands.first?.cards.isEmpty == false {
|
||||
PlayerHandsView(
|
||||
hands: state.playerHands,
|
||||
activeHandIndex: state.activeHandIndex,
|
||||
isPlayerTurn: isPlayerTurn,
|
||||
showCardCount: showCardCount,
|
||||
cardWidth: cardWidth,
|
||||
cardSpacing: cardSpacing
|
||||
)
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
// Betting zone (when betting)
|
||||
if state.currentPhase == .betting {
|
||||
|
||||
@ -15,7 +15,7 @@ struct DealerHandView: View {
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.BaseFontSize.medium
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
|
||||
@ -111,18 +111,18 @@ struct GameTableView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
// Chip selector - only interactive during betting phase
|
||||
// During gameplay, show chips as they were (balance + currentBet) but dimmed
|
||||
ChipSelectorView(
|
||||
selectedChip: $selectedChip,
|
||||
balance: state.currentPhase == .betting ? state.balance : (state.balance + state.currentBet),
|
||||
currentBet: state.currentPhase == .betting ? state.currentBet : 0,
|
||||
maxBet: state.settings.maxBet
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.padding(.bottom, Design.Spacing.small)
|
||||
.opacity(state.currentPhase == .betting ? 1.0 : Design.Opacity.medium)
|
||||
.allowsHitTesting(state.currentPhase == .betting) // Disable interaction when not betting
|
||||
// Chip selector - only shown during betting phase
|
||||
if state.currentPhase == .betting {
|
||||
ChipSelectorView(
|
||||
selectedChip: $selectedChip,
|
||||
balance: state.balance,
|
||||
currentBet: state.currentBet,
|
||||
maxBet: state.settings.maxBet
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.padding(.bottom, Design.Spacing.small)
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
ActionButtonsView(state: state)
|
||||
@ -216,24 +216,47 @@ struct ActionButtonsView: View {
|
||||
|
||||
// MARK: - Betting Phase Buttons
|
||||
|
||||
/// Whether the current bet meets the minimum requirement
|
||||
private var isBetBelowMinimum: Bool {
|
||||
state.currentBet > 0 && state.currentBet < state.settings.minBet
|
||||
}
|
||||
|
||||
/// Amount needed to reach minimum bet
|
||||
private var amountNeededForMinimum: Int {
|
||||
state.settings.minBet - state.currentBet
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var bettingButtons: some View {
|
||||
if state.currentBet > 0 {
|
||||
ActionButton(
|
||||
String(localized: "Clear"),
|
||||
icon: "xmark.circle",
|
||||
style: .destructive
|
||||
) {
|
||||
state.clearBet()
|
||||
}
|
||||
|
||||
if state.canDeal {
|
||||
ActionButton(
|
||||
String(localized: "Deal"),
|
||||
icon: "play.fill",
|
||||
style: .primary
|
||||
) {
|
||||
Task { await state.deal() }
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Show hint if bet is below minimum
|
||||
if isBetBelowMinimum {
|
||||
Text(String(localized: "Add $\(amountNeededForMinimum) more to meet minimum"))
|
||||
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
||||
.foregroundStyle(.orange)
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
ActionButton(
|
||||
String(localized: "Clear"),
|
||||
icon: "xmark.circle",
|
||||
style: .destructive
|
||||
) {
|
||||
state.clearBet()
|
||||
}
|
||||
|
||||
// Always show Deal button, but disable if below minimum
|
||||
ActionButton(
|
||||
String(localized: "Deal"),
|
||||
icon: "play.fill",
|
||||
style: .primary
|
||||
) {
|
||||
Task { await state.deal() }
|
||||
}
|
||||
.opacity(state.canDeal ? 1.0 : Design.Opacity.medium)
|
||||
.disabled(!state.canDeal)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -379,11 +402,11 @@ struct CardCountView: View {
|
||||
// Running count
|
||||
VStack(spacing: Design.Spacing.xxSmall) {
|
||||
Text("Running")
|
||||
.font(.system(size: Design.BaseFontSize.xSmall, weight: .medium))
|
||||
.font(.system(size: Design.Size.cardCountLabelSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
|
||||
Text(runningCount >= 0 ? "+\(runningCount)" : "\(runningCount)")
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .bold, design: .monospaced))
|
||||
.font(.system(size: Design.Size.cardCountValueSize, weight: .bold, design: .monospaced))
|
||||
.foregroundStyle(countColor(for: runningCount))
|
||||
}
|
||||
|
||||
@ -394,11 +417,11 @@ struct CardCountView: View {
|
||||
// True count
|
||||
VStack(spacing: Design.Spacing.xxSmall) {
|
||||
Text("True")
|
||||
.font(.system(size: Design.BaseFontSize.xSmall, weight: .medium))
|
||||
.font(.system(size: Design.Size.cardCountLabelSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
|
||||
Text(trueCount >= 0 ? "+\(trueCount, format: .number.precision(.fractionLength(1)))" : "\(trueCount, format: .number.precision(.fractionLength(1)))")
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .bold, design: .monospaced))
|
||||
.font(.system(size: Design.Size.cardCountValueSize, weight: .bold, design: .monospaced))
|
||||
.foregroundStyle(countColor(for: Int(trueCount.rounded())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,15 +14,15 @@ struct HiLoCountBadge: View {
|
||||
|
||||
var body: some View {
|
||||
Text(card.hiLoDisplayText)
|
||||
.font(.system(size: Design.BaseFontSize.xxSmall, weight: .bold, design: .rounded))
|
||||
.font(.system(size: Design.Size.countBadgeFontSize, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(badgeTextColor)
|
||||
.padding(.horizontal, Design.Spacing.xSmall)
|
||||
.padding(.vertical, Design.Spacing.xxxSmall)
|
||||
.padding(.horizontal, Design.Size.countBadgePaddingH)
|
||||
.padding(.vertical, Design.Size.countBadgePaddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(badgeBackgroundColor)
|
||||
)
|
||||
.offset(x: -Design.Spacing.xSmall, y: Design.Spacing.xSmall)
|
||||
.offset(x: -Design.Size.countBadgeOffset, y: Design.Size.countBadgeOffset)
|
||||
}
|
||||
|
||||
private var badgeBackgroundColor: Color {
|
||||
|
||||
@ -17,13 +17,14 @@ struct HintView: View {
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: "lightbulb.fill")
|
||||
.font(.system(size: Design.Size.hintIconSize))
|
||||
.foregroundStyle(.yellow)
|
||||
Text(String(localized: "Hint: \(hint)"))
|
||||
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
||||
.font(.system(size: Design.Size.hintFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.strong))
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.small)
|
||||
.padding(.horizontal, Design.Size.hintPaddingH)
|
||||
.padding(.vertical, Design.Size.hintPaddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.black.opacity(Design.Opacity.light))
|
||||
@ -66,13 +67,14 @@ struct BettingHintView: View {
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: Design.Size.hintIconSize))
|
||||
.foregroundStyle(hintColor)
|
||||
Text(hint)
|
||||
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
||||
.font(.system(size: Design.Size.hintFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.strong))
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.small)
|
||||
.padding(.horizontal, Design.Size.hintPaddingH)
|
||||
.padding(.vertical, Design.Size.hintPaddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.black.opacity(Design.Opacity.light))
|
||||
|
||||
@ -56,7 +56,7 @@ struct PlayerHandsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 180)
|
||||
.frame(height: Design.Size.playerHandsHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,8 +71,8 @@ struct PlayerHandView: View {
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.BaseFontSize.medium
|
||||
@ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.BaseFontSize.small
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
@ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.Size.handNumberFontSize
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
@ -129,7 +129,7 @@ struct PlayerHandView: View {
|
||||
|
||||
if hand.isDoubledDown {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.system(size: handNumberSize))
|
||||
.font(.system(size: Design.Size.handIconSize))
|
||||
.foregroundStyle(.purple)
|
||||
}
|
||||
}
|
||||
@ -139,8 +139,8 @@ struct PlayerHandView: View {
|
||||
Text(result.displayText)
|
||||
.font(.system(size: labelFontSize, weight: .black))
|
||||
.foregroundStyle(result.color)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.padding(.horizontal, Design.Size.hintPaddingH)
|
||||
.padding(.vertical, Design.Size.hintPaddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(result.color.opacity(Design.Opacity.hint))
|
||||
@ -151,6 +151,7 @@ struct PlayerHandView: View {
|
||||
if hand.bet > 0 {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
Image(systemName: "dollarsign.circle.fill")
|
||||
.font(.system(size: Design.Size.handIconSize))
|
||||
.foregroundStyle(.yellow)
|
||||
Text("\(hand.bet * (hand.isDoubledDown ? 2 : 1))")
|
||||
.font(.system(size: handNumberSize, weight: .bold, design: .rounded))
|
||||
|
||||
@ -62,26 +62,29 @@ public struct ChipStackView: View {
|
||||
public struct ChipOnTableView: View {
|
||||
let amount: Int
|
||||
let showMax: Bool
|
||||
let size: CGFloat
|
||||
let theme: any CasinoTheme
|
||||
|
||||
public init(
|
||||
amount: Int,
|
||||
showMax: Bool = false,
|
||||
size: CGFloat = 36,
|
||||
theme: any CasinoTheme = DefaultCasinoTheme()
|
||||
) {
|
||||
self.amount = amount
|
||||
self.showMax = showMax
|
||||
self.size = size
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
// MARK: - Layout Constants
|
||||
// MARK: - Layout Constants (relative to size)
|
||||
|
||||
private let chipSize: CGFloat = 36
|
||||
private let innerRingSize: CGFloat = 26
|
||||
private let gradientEndRadius: CGFloat = 20
|
||||
private let maxBadgeFontSize: CGFloat = 8
|
||||
private let maxBadgeOffsetX: CGFloat = 6
|
||||
private let maxBadgeOffsetY: CGFloat = -4
|
||||
private var chipSize: CGFloat { size }
|
||||
private var innerRingSize: CGFloat { size * 0.72 } // 26/36
|
||||
private var gradientEndRadius: CGFloat { size * 0.56 } // 20/36
|
||||
private var maxBadgeFontSize: CGFloat { size * 0.22 } // 8/36
|
||||
private var maxBadgeOffsetX: CGFloat { size * 0.17 } // 6/36
|
||||
private var maxBadgeOffsetY: CGFloat { size * -0.11 } // -4/36
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
@ -95,8 +98,10 @@ public struct ChipOnTableView: View {
|
||||
amount >= 1000 ? "\(amount / 1000)K" : "\(amount)"
|
||||
}
|
||||
|
||||
/// Text font size scales with chip size
|
||||
private var textFontSize: CGFloat {
|
||||
amount >= 1000 ? CasinoDesign.BaseFontSize.xSmall : CasinoDesign.BaseFontSize.xSmall + 1
|
||||
let baseFontSize = amount >= 1000 ? size * 0.25 : size * 0.30
|
||||
return baseFontSize
|
||||
}
|
||||
|
||||
// MARK: - Accessibility
|
||||
|
||||
Loading…
Reference in New Issue
Block a user