CasinoGames/Baccarat/Views/SettingsView.swift

385 lines
14 KiB
Swift

//
// SettingsView.swift
// Baccarat
//
// Settings screen for game customization.
//
import SwiftUI
/// The settings screen for customizing game options.
struct SettingsView: View {
@Bindable var settings: GameSettings
@Environment(\.dismiss) private var dismiss
let onApplyChanges: () -> Void
@State private var hasChanges = false
var body: some View {
NavigationStack {
ZStack {
// Background
Color(red: 0.08, green: 0.12, blue: 0.08)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 24) {
// Table Limits Section (First!)
SettingsSection(title: "TABLE LIMITS", icon: "banknote") {
TableLimitsPicker(selection: $settings.tableLimits)
.onChange(of: settings.tableLimits) { _, _ in
hasChanges = true
}
}
// Deck Settings Section
SettingsSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait") {
DeckCountPicker(selection: $settings.deckCount)
.onChange(of: settings.deckCount) { _, _ in
hasChanges = true
}
}
// Starting Balance Section
SettingsSection(title: "STARTING BALANCE", icon: "dollarsign.circle") {
BalancePicker(balance: $settings.startingBalance)
.onChange(of: settings.startingBalance) { _, _ in
hasChanges = true
}
}
// Display Settings Section
SettingsSection(title: "DISPLAY", icon: "eye") {
SettingsToggle(
title: "Show Cards Remaining",
subtitle: "Display deck counter at top",
isOn: $settings.showCardsRemaining
)
Divider()
.background(Color.white.opacity(0.1))
SettingsToggle(
title: "Show History",
subtitle: "Display result road map",
isOn: $settings.showHistory
)
}
// Animation Settings Section
SettingsSection(title: "ANIMATIONS", icon: "sparkles") {
SettingsToggle(
title: "Card Animations",
subtitle: "Animate dealing and flipping",
isOn: $settings.showAnimations
)
if settings.showAnimations {
Divider()
.background(Color.white.opacity(0.1))
SpeedPicker(speed: $settings.dealingSpeed)
}
}
// Reset Button
Button {
settings.resetToDefaults()
hasChanges = true
} label: {
HStack {
Image(systemName: "arrow.counterclockwise")
Text("Reset to Defaults")
}
.font(.system(size: 14, weight: .medium))
.foregroundStyle(.red.opacity(0.8))
.padding()
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.red.opacity(0.1))
)
}
.padding(.horizontal)
.padding(.top, 8)
}
.padding(.vertical)
}
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(Color(red: 0.08, green: 0.12, blue: 0.08), for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") {
settings.load() // Revert changes
dismiss()
}
.foregroundStyle(.white.opacity(0.7))
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
settings.save()
if hasChanges {
onApplyChanges()
}
dismiss()
}
.bold()
.foregroundStyle(.yellow)
}
}
}
}
}
/// A settings section with a title and content.
struct SettingsSection<Content: View>: View {
let title: String
let icon: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading, spacing: 12) {
// Header
HStack(spacing: 8) {
Image(systemName: icon)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.yellow.opacity(0.8))
Text(title)
.font(.system(size: 12, weight: .bold, design: .rounded))
.tracking(1)
.foregroundStyle(.white.opacity(0.6))
}
.padding(.horizontal, 4)
// Content card
VStack(spacing: 0) {
content
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.white.opacity(0.05))
)
}
.padding(.horizontal)
}
}
/// Deck count picker with visual options.
struct DeckCountPicker: View {
@Binding var selection: DeckCount
var body: some View {
VStack(spacing: 12) {
ForEach(DeckCount.allCases) { count in
Button {
selection = count
} label: {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(count.displayName)
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white)
Text(count.description)
.font(.system(size: 12))
.foregroundStyle(.white.opacity(0.5))
}
Spacer()
if selection == count {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22))
.foregroundStyle(.yellow)
} else {
Circle()
.strokeBorder(Color.white.opacity(0.3), lineWidth: 2)
.frame(width: 22, height: 22)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(selection == count ? Color.yellow.opacity(0.1) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(
selection == count ? Color.yellow.opacity(0.5) : Color.white.opacity(0.1),
lineWidth: 1
)
)
}
.buttonStyle(.plain)
}
}
}
}
/// Starting balance picker.
struct BalancePicker: View {
@Binding var balance: Int
private let options = [1_000, 5_000, 10_000, 25_000, 50_000, 100_000]
var body: some View {
LazyVGrid(columns: [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
], spacing: 10) {
ForEach(options, id: \.self) { amount in
Button {
balance = amount
} label: {
Text("$\(amount / 1000)K")
.font(.system(size: 14, weight: .bold))
.foregroundStyle(balance == amount ? .black : .white)
.padding(.vertical, 12)
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(balance == amount ? Color.yellow : Color.white.opacity(0.1))
)
}
.buttonStyle(.plain)
}
}
}
}
/// A toggle setting row.
struct SettingsToggle: View {
let title: String
let subtitle: String
@Binding var isOn: Bool
var body: some View {
Toggle(isOn: $isOn) {
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.system(size: 15, weight: .medium))
.foregroundStyle(.white)
Text(subtitle)
.font(.system(size: 12))
.foregroundStyle(.white.opacity(0.5))
}
}
.tint(.yellow)
}
}
/// Animation speed picker.
struct SpeedPicker: View {
@Binding var speed: Double
private let options: [(String, Double)] = [
("Fast", 0.5),
("Normal", 1.0),
("Slow", 2.0)
]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Dealing Speed")
.font(.system(size: 15, weight: .medium))
.foregroundStyle(.white)
HStack(spacing: 8) {
ForEach(options, id: \.1) { option in
Button {
speed = option.1
} label: {
Text(option.0)
.font(.system(size: 13, weight: .medium))
.foregroundStyle(speed == option.1 ? .black : .white.opacity(0.7))
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
.background(
Capsule()
.fill(speed == option.1 ? Color.yellow : Color.white.opacity(0.1))
)
}
.buttonStyle(.plain)
}
}
}
}
}
/// Table limits picker for min/max bets.
struct TableLimitsPicker: View {
@Binding var selection: TableLimits
var body: some View {
VStack(spacing: 10) {
ForEach(TableLimits.allCases) { limit in
Button {
selection = limit
} label: {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(limit.displayName)
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white)
Text(limit.detailedDescription)
.font(.system(size: 12))
.foregroundStyle(.white.opacity(0.5))
}
Spacer()
// Limits badge
Text(limit.description)
.font(.system(size: 12, weight: .bold, design: .rounded))
.foregroundStyle(selection == limit ? .black : .yellow)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(
Capsule()
.fill(selection == limit ? Color.yellow : Color.yellow.opacity(0.2))
)
if selection == limit {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 20))
.foregroundStyle(.yellow)
} else {
Circle()
.strokeBorder(Color.white.opacity(0.3), lineWidth: 2)
.frame(width: 20, height: 20)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(selection == limit ? Color.yellow.opacity(0.1) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(
selection == limit ? Color.yellow.opacity(0.5) : Color.white.opacity(0.1),
lineWidth: 1
)
)
}
.buttonStyle(.plain)
}
}
}
}
#Preview {
SettingsView(settings: GameSettings()) { }
}