213 lines
7.9 KiB
Swift
213 lines
7.9 KiB
Swift
//
|
|
// ActionButtonsView.swift
|
|
// Baccarat
|
|
//
|
|
// Action buttons for deal, clear, and new round.
|
|
//
|
|
|
|
import SwiftUI
|
|
import CasinoKit
|
|
|
|
/// Action buttons for deal, clear, and new round.
|
|
struct ActionButtonsView: View {
|
|
@Bindable var gameState: GameState
|
|
let onDeal: () -> Void
|
|
let onClear: () -> Void
|
|
let onNewRound: () -> Void
|
|
|
|
// MARK: - Environment
|
|
|
|
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
|
|
|
/// Whether the current text size is an accessibility size (very large)
|
|
private var isAccessibilitySize: Bool {
|
|
dynamicTypeSize.isAccessibilitySize
|
|
}
|
|
|
|
// MARK: - Layout Constants
|
|
|
|
private let buttonFontSize: CGFloat = Design.BaseFontSize.xLarge
|
|
private let iconSize: CGFloat = Design.BaseFontSize.xxLarge + Design.Spacing.xSmall
|
|
private let statusFontSize: CGFloat = Design.BaseFontSize.medium
|
|
private let containerHeight: CGFloat = 70
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
// Fixed height container to prevent layout shifts
|
|
Color.clear
|
|
.frame(height: containerHeight)
|
|
|
|
// Content changes with animation
|
|
Group {
|
|
if gameState.currentPhase == .betting {
|
|
bettingButtons
|
|
} else if gameState.currentPhase == .roundComplete && !gameState.showResultBanner {
|
|
newRoundButton
|
|
} else if !gameState.showResultBanner {
|
|
dealingIndicator
|
|
}
|
|
}
|
|
.animation(.easeInOut(duration: Design.Animation.quick), value: gameState.currentPhase)
|
|
.animation(.easeInOut(duration: Design.Animation.quick), value: gameState.showResultBanner)
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Views
|
|
|
|
private var bettingButtons: some View {
|
|
VStack(spacing: Design.Spacing.small) {
|
|
// Show hint if main bet is below minimum
|
|
if gameState.isMainBetBelowMinimum {
|
|
Text(String(localized: "Add $\(gameState.amountNeededForMinimum) more to meet minimum"))
|
|
.font(.system(size: Design.BaseFontSize.small, weight: .medium))
|
|
.foregroundStyle(.orange)
|
|
.transition(.opacity.combined(with: .scale(scale: 0.8)))
|
|
}
|
|
|
|
HStack(spacing: Design.Spacing.medium) {
|
|
clearButton
|
|
dealButton
|
|
}
|
|
}
|
|
.animation(.easeInOut(duration: Design.Animation.standard), value: gameState.isMainBetBelowMinimum)
|
|
}
|
|
|
|
private var dealingIndicator: some View {
|
|
HStack(spacing: Design.Spacing.xSmall) {
|
|
ProgressView()
|
|
.tint(.white)
|
|
.scaleEffect(0.8)
|
|
Text("Dealing...")
|
|
.font(.system(size: statusFontSize, weight: .medium))
|
|
.foregroundStyle(.white.opacity(Design.Opacity.heavy))
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(Design.MinScaleFactor.relaxed)
|
|
}
|
|
.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 + Design.Spacing.xxSmall)
|
|
.background(
|
|
Circle()
|
|
.fill(Color.CasinoButton.destructive)
|
|
)
|
|
.opacity(gameState.currentBets.isEmpty ? Design.Opacity.disabled : 1.0)
|
|
.disabled(gameState.currentBets.isEmpty)
|
|
} else {
|
|
Button("Clear", systemImage: "xmark.circle", action: onClear)
|
|
.labelStyle(.titleAndIcon)
|
|
.font(.system(size: buttonFontSize, weight: .semibold))
|
|
.foregroundStyle(.white)
|
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
|
.padding(.vertical, Design.Spacing.medium + Design.Spacing.xxSmall)
|
|
.background(
|
|
Capsule()
|
|
.fill(Color.CasinoButton.destructive)
|
|
)
|
|
.opacity(gameState.currentBets.isEmpty ? Design.Opacity.disabled : 1.0)
|
|
.disabled(gameState.currentBets.isEmpty)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var dealButton: some View {
|
|
let buttonBackground = LinearGradient(
|
|
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
|
|
if isAccessibilitySize {
|
|
Button("Deal", systemImage: "play.fill", action: onDeal)
|
|
.labelStyle(.iconOnly)
|
|
.font(.system(size: iconSize, weight: .bold))
|
|
.foregroundStyle(.black)
|
|
.padding(Design.Spacing.medium + Design.Spacing.xxSmall)
|
|
.background(
|
|
Circle()
|
|
.fill(buttonBackground)
|
|
)
|
|
.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(.titleAndIcon)
|
|
.font(.system(size: buttonFontSize, weight: .bold))
|
|
.foregroundStyle(.black)
|
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
|
.padding(.vertical, Design.Spacing.medium + Design.Spacing.xxSmall)
|
|
.background(
|
|
Capsule()
|
|
.fill(buttonBackground)
|
|
)
|
|
.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 {
|
|
let buttonBackground = LinearGradient(
|
|
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
|
|
if isAccessibilitySize {
|
|
Button("New Round", systemImage: "arrow.clockwise", action: onNewRound)
|
|
.labelStyle(.iconOnly)
|
|
.font(.system(size: iconSize, weight: .bold))
|
|
.foregroundStyle(.black)
|
|
.padding(Design.Spacing.medium + Design.Spacing.xxSmall)
|
|
.background(
|
|
Circle()
|
|
.fill(buttonBackground)
|
|
)
|
|
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
|
} else {
|
|
Button("New Round", systemImage: "arrow.clockwise", action: onNewRound)
|
|
.labelStyle(.titleAndIcon)
|
|
.font(.system(size: buttonFontSize, weight: .bold))
|
|
.foregroundStyle(.black)
|
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
|
.padding(.vertical, Design.Spacing.medium + Design.Spacing.xxSmall)
|
|
.background(
|
|
Capsule()
|
|
.fill(buttonBackground)
|
|
)
|
|
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
#Preview("Betting Phase") {
|
|
ZStack {
|
|
TableBackgroundView()
|
|
VStack {
|
|
Spacer()
|
|
ActionButtonsView(
|
|
gameState: GameState(settings: GameSettings()),
|
|
onDeal: {},
|
|
onClear: {},
|
|
onNewRound: {}
|
|
)
|
|
.padding()
|
|
}
|
|
}
|
|
}
|
|
|