172 lines
6.5 KiB
Swift
172 lines
6.5 KiB
Swift
//
|
|
// ResultBannerView.swift
|
|
// Baccarat
|
|
//
|
|
// Animated result banner showing the winner and winnings.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
/// An animated banner showing the round result.
|
|
struct ResultBannerView: View {
|
|
let result: GameResult
|
|
let winnings: Int
|
|
|
|
@State private var showBanner = false
|
|
@State private var showText = false
|
|
@State private var showWinnings = false
|
|
|
|
// MARK: - Scaled Font Sizes (Dynamic Type)
|
|
|
|
@ScaledMetric(relativeTo: .largeTitle) private var resultFontSize: CGFloat = Design.BaseFontSize.largeTitle
|
|
@ScaledMetric(relativeTo: .title2) private var winningsFontSize: CGFloat = 28
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
// Background overlay
|
|
Color.black.opacity(showBanner ? Design.Opacity.medium : 0)
|
|
.ignoresSafeArea()
|
|
.animation(.easeIn(duration: Design.Animation.fadeInDuration), value: showBanner)
|
|
|
|
// Banner
|
|
VStack(spacing: Design.Spacing.xLarge) {
|
|
// Result text
|
|
Text(result.displayText)
|
|
.font(.system(size: resultFontSize, weight: .black, design: .rounded))
|
|
.foregroundStyle(
|
|
LinearGradient(
|
|
colors: [.white, result.color],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
)
|
|
.shadow(color: result.color.opacity(Design.Opacity.heavy), radius: Design.Shadow.radiusLarge)
|
|
.scaleEffect(showText ? Design.Scale.normal : Design.Scale.shrunk)
|
|
.opacity(showText ? Design.Scale.normal : 0)
|
|
|
|
// Winnings display
|
|
if winnings != 0 {
|
|
HStack(spacing: Design.Spacing.small) {
|
|
if winnings > 0 {
|
|
Image(systemName: "plus.circle.fill")
|
|
.foregroundStyle(.green)
|
|
Text("\(winnings)")
|
|
.foregroundStyle(.green)
|
|
} else {
|
|
Image(systemName: "minus.circle.fill")
|
|
.foregroundStyle(.red)
|
|
Text("\(abs(winnings))")
|
|
.foregroundStyle(.red)
|
|
}
|
|
}
|
|
.font(.system(size: winningsFontSize, weight: .bold, design: .rounded))
|
|
.scaleEffect(showWinnings ? Design.Scale.normal : Design.Scale.shrunk)
|
|
.opacity(showWinnings ? Design.Scale.normal : 0)
|
|
}
|
|
}
|
|
.padding(Design.Spacing.xxxLarge + Design.Spacing.small)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge + Design.Spacing.xSmall)
|
|
.fill(
|
|
LinearGradient(
|
|
colors: [
|
|
Color(white: 0.15),
|
|
Color(white: 0.08)
|
|
],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge + Design.Spacing.xSmall)
|
|
.strokeBorder(
|
|
LinearGradient(
|
|
colors: [
|
|
result.color.opacity(Design.Opacity.heavy),
|
|
result.color.opacity(Design.Opacity.light)
|
|
],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
),
|
|
lineWidth: Design.LineWidth.thick
|
|
)
|
|
)
|
|
)
|
|
.shadow(color: result.color.opacity(Design.Opacity.light), radius: Design.Shadow.radiusXXLarge)
|
|
.scaleEffect(showBanner ? Design.Scale.normal : Design.Scale.slightShrink)
|
|
.opacity(showBanner ? Design.Scale.normal : 0)
|
|
}
|
|
.onAppear {
|
|
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) {
|
|
showBanner = true
|
|
}
|
|
|
|
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(Design.Animation.staggerDelay1)) {
|
|
showText = true
|
|
}
|
|
|
|
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(Design.Animation.staggerDelay2)) {
|
|
showWinnings = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Confetti particle for celebrations.
|
|
struct ConfettiPiece: View {
|
|
let color: Color
|
|
@State private var position: CGPoint = .zero
|
|
@State private var rotation: Double = 0
|
|
@State private var opacity: Double = 1
|
|
|
|
private let confettiWidth: CGFloat = 8
|
|
private let confettiHeight: CGFloat = 12
|
|
|
|
var body: some View {
|
|
Rectangle()
|
|
.fill(color)
|
|
.frame(width: confettiWidth, height: confettiHeight)
|
|
.rotationEffect(.degrees(rotation))
|
|
.position(position)
|
|
.opacity(opacity)
|
|
.onAppear {
|
|
let screenWidth = 400.0
|
|
let startX = Double.random(in: 0...screenWidth)
|
|
position = CGPoint(x: startX, y: -20)
|
|
|
|
withAnimation(.easeIn(duration: Double.random(in: 2...4))) {
|
|
position = CGPoint(
|
|
x: startX + Double.random(in: -100...100),
|
|
y: 800
|
|
)
|
|
rotation = Double.random(in: 360...1080)
|
|
opacity = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A confetti celebration overlay.
|
|
struct ConfettiView: View {
|
|
let colors: [Color] = [.red, .blue, .green, .yellow, .orange, .purple, .pink]
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
ForEach(0..<50, id: \.self) { _ in
|
|
ConfettiPiece(color: colors.randomElement() ?? .yellow)
|
|
}
|
|
}
|
|
.allowsHitTesting(false)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ZStack {
|
|
Color.Table.preview
|
|
.ignoresSafeArea()
|
|
|
|
ResultBannerView(result: .playerWins, winnings: 500)
|
|
}
|
|
}
|
|
|