CasinoGames/CasinoKit/Sources/CasinoKit/Views/WelcomeSheet.swift

208 lines
7.4 KiB
Swift

//
// WelcomeSheet.swift
// CasinoKit
//
// First-launch welcome sheet for casino games.
//
import SwiftUI
/// Welcome sheet shown on first launch of a game.
public struct WelcomeSheet: View {
let gameName: String
let gameEmoji: String
let features: [WelcomeFeature]
let onStartTutorial: () -> Void
let onStartPlaying: () -> Void
@ScaledMetric(relativeTo: .largeTitle) private var titleSize: CGFloat = CasinoDesign.BaseFontSize.xxLarge
@ScaledMetric(relativeTo: .title2) private var featureTitleSize: CGFloat = CasinoDesign.BaseFontSize.subheadline
@ScaledMetric(relativeTo: .body) private var bodySize: CGFloat = CasinoDesign.BaseFontSize.body
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = CasinoDesign.IconSize.large
@ScaledMetric(relativeTo: .body) private var buttonPadding: CGFloat = CasinoDesign.Spacing.medium
/// Creates a welcome sheet with automatic onboarding state management.
///
/// This initializer handles the common pattern of:
/// - "Show Me How" completes welcome and triggers hint display
/// - "Start Playing" skips all hints and completes welcome
///
/// - Parameters:
/// - gameName: The name of the game to display
/// - gameEmoji: An emoji representing the game
/// - features: List of features to highlight
/// - onboarding: The onboarding state to manage (must have hint keys registered)
/// - onDismiss: Called after the sheet is dismissed
/// - onShowHints: Called when user chooses "Show Me How" - use this to trigger tooltip display
public init(
gameName: String,
gameEmoji: String = "🎰",
features: [WelcomeFeature],
onboarding: OnboardingState,
onDismiss: @escaping () -> Void,
onShowHints: @escaping () -> Void
) {
self.gameName = gameName
self.gameEmoji = gameEmoji
self.features = features
self.onStartTutorial = {
onboarding.completeWelcome()
onDismiss()
onShowHints()
}
self.onStartPlaying = {
onboarding.skipOnboarding()
onDismiss()
}
}
/// Creates a welcome sheet with custom callbacks for full control.
public init(
gameName: String,
gameEmoji: String = "🎰",
features: [WelcomeFeature],
onStartTutorial: @escaping () -> Void,
onStartPlaying: @escaping () -> Void
) {
self.gameName = gameName
self.gameEmoji = gameEmoji
self.features = features
self.onStartTutorial = onStartTutorial
self.onStartPlaying = onStartPlaying
}
public var body: some View {
SheetContainerView(
title: String(localized: "Welcome to \(gameName)!", bundle: .module),
content: {
ScrollView {
VStack(spacing: CasinoDesign.Spacing.xLarge) {
// Game icon/emoji
Text(gameEmoji)
.font(.system(size: 60))
.padding(.top, CasinoDesign.Spacing.medium)
// Features list
VStack(spacing: CasinoDesign.Spacing.large) {
ForEach(features) { feature in
FeatureRow(
feature: feature,
iconSize: iconSize,
titleSize: featureTitleSize,
bodySize: bodySize
)
}
}
// Action buttons
VStack(spacing: CasinoDesign.Spacing.medium) {
Button(action: onStartTutorial) {
HStack {
Image(systemName: "play.circle.fill")
Text("Show Me How")
}
.font(.system(size: bodySize, weight: .semibold))
.frame(maxWidth: .infinity)
.padding(buttonPadding)
.background(
RoundedRectangle(cornerRadius: CasinoDesign.CornerRadius.large)
.fill(Color.Sheet.accent)
)
.foregroundStyle(.black)
}
Button(action: onStartPlaying) {
Text("Start Playing")
.font(.system(size: bodySize, weight: .medium))
.foregroundStyle(.white.opacity(CasinoDesign.Opacity.medium))
}
}
.padding(.top, CasinoDesign.Spacing.large)
.padding(.bottom, CasinoDesign.Spacing.xLarge)
}
.padding(.horizontal, CasinoDesign.Spacing.large)
}
},
onDone: onStartPlaying // Done button = Start Playing
)
}
}
// MARK: - Feature Row
private struct FeatureRow: View {
let feature: WelcomeFeature
let iconSize: CGFloat
let titleSize: CGFloat
let bodySize: CGFloat
var body: some View {
HStack(spacing: CasinoDesign.Spacing.medium) {
// Icon
Image(systemName: feature.icon)
.font(.system(size: iconSize))
.foregroundStyle(Color.Sheet.accent)
.frame(width: 40, alignment: .center)
// Text
VStack(alignment: .leading, spacing: CasinoDesign.Spacing.xxSmall) {
Text(feature.title)
.font(.system(size: titleSize, weight: .semibold))
.foregroundStyle(.white)
Text(feature.description)
.font(.system(size: bodySize))
.foregroundStyle(.white.opacity(CasinoDesign.Opacity.strong))
.fixedSize(horizontal: false, vertical: true)
}
Spacer(minLength: 0)
}
}
}
// MARK: - Welcome Feature Model
public struct WelcomeFeature: Identifiable {
public let id = UUID()
let icon: String
let title: String
let description: String
public init(icon: String, title: String, description: String) {
self.icon = icon
self.title = title
self.description = description
}
}
// MARK: - Preview
#Preview {
WelcomeSheet(
gameName: "Casino Game",
gameEmoji: "🎰",
features: [
WelcomeFeature(
icon: "target",
title: "Exciting Gameplay",
description: "Experience the thrill of the casino"
),
WelcomeFeature(
icon: "lightbulb.fill",
title: "Learn Strategy",
description: "Built-in hints show optimal plays"
),
WelcomeFeature(
icon: "dollarsign.circle",
title: "Practice Free",
description: "Start with virtual chips and play risk-free"
)
],
onStartTutorial: {},
onStartPlaying: {}
)
}