CasinoGames/Blackjack/Views/SettingsView.swift

226 lines
9.1 KiB
Swift

//
// SettingsView.swift
// Blackjack
//
// Game settings and rule configuration.
//
import SwiftUI
import CasinoKit
struct SettingsView: View {
@Bindable var settings: GameSettings
let gameState: GameState?
@Environment(\.dismiss) private var dismiss
var body: some View {
SheetContainerView(
title: String(localized: "Settings"),
content: {
// Game Style
SheetSection(title: String(localized: "GAME STYLE"), icon: "suit.club.fill") {
GameStylePicker(selection: $settings.gameStyle)
}
// Deck Settings
SheetSection(title: String(localized: "DECK SETTINGS"), icon: "rectangle.portrait.on.rectangle.portrait") {
DeckCountPicker(selection: $settings.deckCount)
}
// Table Limits
SheetSection(title: String(localized: "TABLE LIMITS"), icon: "banknote") {
TableLimitsPicker(selection: $settings.tableLimits)
}
// Rule Options (for custom style)
if settings.gameStyle == .custom {
SheetSection(title: String(localized: "RULES"), icon: "list.bullet.clipboard") {
VStack(spacing: Design.Spacing.small) {
SettingsToggle(
title: String(localized: "Dealer Hits Soft 17"),
subtitle: String(localized: "H17 rule, increases house edge"),
isOn: $settings.dealerHitsSoft17
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: String(localized: "Double After Split"),
subtitle: String(localized: "Allow doubling on split hands"),
isOn: $settings.doubleAfterSplit
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: String(localized: "Re-split Aces"),
subtitle: String(localized: "Allow splitting aces again"),
isOn: $settings.resplitAces
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: String(localized: "Late Surrender"),
subtitle: String(localized: "Surrender after dealer checks for blackjack"),
isOn: $settings.lateSurrender
)
}
}
}
// Display
SheetSection(title: String(localized: "DISPLAY"), icon: "eye") {
VStack(spacing: Design.Spacing.small) {
SettingsToggle(
title: String(localized: "Show Animations"),
subtitle: String(localized: "Card dealing animations"),
isOn: $settings.showAnimations
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: String(localized: "Show Hints"),
subtitle: String(localized: "Basic strategy suggestions"),
isOn: $settings.showHints
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: String(localized: "Cards Remaining"),
subtitle: String(localized: "Show cards left in shoe"),
isOn: $settings.showCardsRemaining
)
Divider().background(Color.white.opacity(Design.Opacity.hint))
SpeedPicker(speed: $settings.dealingSpeed)
}
}
// Sound & Haptics
SheetSection(title: String(localized: "SOUND & HAPTICS"), icon: "speaker.wave.2") {
VStack(spacing: Design.Spacing.small) {
SettingsToggle(
title: String(localized: "Sound Effects"),
subtitle: String(localized: "Chips, cards, and results"),
isOn: $settings.soundEnabled
)
.onChange(of: settings.soundEnabled) { _, newValue in
SoundManager.shared.soundEnabled = newValue
}
Divider().background(Color.white.opacity(Design.Opacity.hint))
SettingsToggle(
title: String(localized: "Haptic Feedback"),
subtitle: String(localized: "Vibration on actions"),
isOn: $settings.hapticsEnabled
)
.onChange(of: settings.hapticsEnabled) { _, newValue in
SoundManager.shared.hapticsEnabled = newValue
}
Divider().background(Color.white.opacity(Design.Opacity.hint))
VolumePicker(volume: $settings.soundVolume)
.onChange(of: settings.soundVolume) { _, newValue in
SoundManager.shared.volume = newValue
}
}
}
// Starting Balance
SheetSection(title: String(localized: "NEW GAME"), icon: "dollarsign.circle") {
BalancePicker(balance: $settings.startingBalance)
}
// Version info
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
Text(String(localized: "Version \(version) (\(build))"))
.font(.system(size: Design.BaseFontSize.small))
.foregroundStyle(.white.opacity(Design.Opacity.light))
.frame(maxWidth: .infinity)
.padding(.top, Design.Spacing.large)
}
},
onCancel: nil,
onDone: {
settings.save()
dismiss()
},
doneButtonText: String(localized: "Done")
)
}
}
// MARK: - Game Style Picker
struct GameStylePicker: View {
@Binding var selection: BlackjackStyle
var body: some View {
VStack(spacing: Design.Spacing.small) {
ForEach(BlackjackStyle.allCases) { style in
SelectableRow(
title: style.displayName,
subtitle: style.description,
isSelected: selection == style,
accentColor: Color.Settings.accent,
action: { selection = style }
)
}
}
}
}
// MARK: - Deck Count Picker
struct DeckCountPicker: View {
@Binding var selection: DeckCount
var body: some View {
VStack(spacing: Design.Spacing.medium) {
ForEach(DeckCount.allCases) { count in
SelectableRow(
title: count.displayName,
subtitle: count.description,
isSelected: selection == count,
accentColor: Color.Settings.accent,
action: { selection = count }
)
}
}
}
}
// MARK: - Table Limits Picker
struct TableLimitsPicker: View {
@Binding var selection: TableLimits
var body: some View {
VStack(spacing: Design.Spacing.small) {
ForEach(TableLimits.allCases) { limit in
SelectableRow(
title: limit.displayName,
subtitle: limit.detailedDescription,
isSelected: selection == limit,
accentColor: Color.Settings.accent,
badge: { BadgePill(text: limit.description, isSelected: selection == limit, accentColor: Color.Settings.accent) },
action: { selection = limit }
)
}
}
}
}
#Preview {
SettingsView(settings: GameSettings(), gameState: nil)
}