Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
b08acb9012
commit
109b2416fc
@ -20,33 +20,36 @@ struct InteractiveCardView: View {
|
|||||||
let onReveal: () -> Void
|
let onReveal: () -> Void
|
||||||
let onUpdateProgress: (Double) -> Void
|
let onUpdateProgress: (Double) -> Void
|
||||||
|
|
||||||
|
/// Whether user is currently interacting with the card (peeling)
|
||||||
|
@State private var isInteracting = false
|
||||||
|
|
||||||
/// Whether to show the glowing animation around this card
|
/// Whether to show the glowing animation around this card
|
||||||
|
/// Only show when waiting, face down, bottom hand, not auto mode, AND not currently being touched
|
||||||
private var showGlow: Bool {
|
private var showGlow: Bool {
|
||||||
isWaiting && !isFaceUp && isBottomHand && revealStyle != .auto
|
isWaiting && !isFaceUp && isBottomHand && revealStyle != .auto && !isInteracting
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Animation state for the pulsing glow
|
/// Animation state for the pulsing glow
|
||||||
@State private var glowPulse = false
|
@State private var glowPulse = false
|
||||||
|
|
||||||
|
/// Tracks if we just completed a squeeze reveal (to skip flip animation)
|
||||||
|
@State private var squeezeJustCompleted = false
|
||||||
|
|
||||||
|
private var cardHeight: CGFloat {
|
||||||
|
cardWidth * CasinoDesign.Size.cardAspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if revealStyle == .squeeze && !isFaceUp && isWaiting {
|
// Glow layer - completely separate from the card, sits underneath
|
||||||
PageCurlView(
|
GlowBackgroundView(
|
||||||
card: card,
|
isActive: showGlow,
|
||||||
width: cardWidth,
|
glowPulse: glowPulse,
|
||||||
onReveal: onReveal
|
cardWidth: cardWidth
|
||||||
)
|
)
|
||||||
.modifier(GlowModifier(isActive: showGlow, glowPulse: glowPulse, cardWidth: cardWidth))
|
|
||||||
} else {
|
// Card layer - completely independent, no glow modifiers
|
||||||
// Standard CardView (handles its own flip animation)
|
cardContent
|
||||||
CardView(card: card, isFaceUp: isFaceUp, cardWidth: cardWidth)
|
|
||||||
.modifier(GlowModifier(isActive: showGlow, glowPulse: glowPulse, cardWidth: cardWidth))
|
|
||||||
.onTapGesture {
|
|
||||||
if !isFaceUp && isWaiting && revealStyle == .tap {
|
|
||||||
onReveal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if showGlow {
|
if showGlow {
|
||||||
@ -56,13 +59,100 @@ struct InteractiveCardView: View {
|
|||||||
.onChange(of: showGlow) { _, newValue in
|
.onChange(of: showGlow) { _, newValue in
|
||||||
glowPulse = newValue
|
glowPulse = newValue
|
||||||
}
|
}
|
||||||
|
.onChange(of: isWaiting) { _, newValue in
|
||||||
|
// Reset interaction state when this card is no longer waiting
|
||||||
|
if !newValue {
|
||||||
|
isInteracting = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Glow Modifier
|
/// The card content - completely isolated from glow state
|
||||||
|
@ViewBuilder
|
||||||
|
private var cardContent: some View {
|
||||||
|
if revealStyle == .squeeze && !isFaceUp && isWaiting {
|
||||||
|
PageCurlView(
|
||||||
|
card: card,
|
||||||
|
width: cardWidth,
|
||||||
|
onReveal: {
|
||||||
|
// Mark that squeeze just completed to skip flip animation
|
||||||
|
squeezeJustCompleted = true
|
||||||
|
onReveal()
|
||||||
|
// Reset after a short delay
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
|
||||||
|
squeezeJustCompleted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// Detect touch to hide glow without interfering with page curl gesture
|
||||||
|
.simultaneousGesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { _ in
|
||||||
|
if !isInteracting {
|
||||||
|
isInteracting = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if squeezeJustCompleted && isFaceUp {
|
||||||
|
// After squeeze reveal: animate vertical flip to continue upward motion
|
||||||
|
VerticalFlipCardView(card: card, cardWidth: cardWidth)
|
||||||
|
} else {
|
||||||
|
// Standard CardView with flip animation
|
||||||
|
CardView(card: card, isFaceUp: isFaceUp, cardWidth: cardWidth)
|
||||||
|
.onTapGesture {
|
||||||
|
if !isFaceUp && isWaiting && revealStyle == .tap {
|
||||||
|
// Hide glow immediately before flip starts
|
||||||
|
isInteracting = true
|
||||||
|
onReveal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A view modifier that adds a pulsing glow effect around a card.
|
// MARK: - Vertical Flip Card View
|
||||||
private struct GlowModifier: ViewModifier {
|
|
||||||
|
/// A card view that animates a vertical flip (X-axis rotation) to reveal the card face.
|
||||||
|
/// Used after the page curl peel completes to smoothly continue the upward flip motion.
|
||||||
|
private struct VerticalFlipCardView: View {
|
||||||
|
let card: Card
|
||||||
|
let cardWidth: CGFloat
|
||||||
|
|
||||||
|
/// Rotation angle - starts slightly tilted and settles to flat
|
||||||
|
/// The page curl ends with the card nearly revealed, so we just do a small settling animation
|
||||||
|
@State private var rotationAngle: Double = -15
|
||||||
|
|
||||||
|
/// Opacity for smooth fade-in
|
||||||
|
@State private var opacity: Double = 0.8
|
||||||
|
|
||||||
|
private var cardHeight: CGFloat {
|
||||||
|
cardWidth * CasinoDesign.Size.cardAspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
CardFrontView(card: card, width: cardWidth, height: cardHeight)
|
||||||
|
.rotation3DEffect(
|
||||||
|
.degrees(rotationAngle),
|
||||||
|
axis: (x: 1, y: 0, z: 0),
|
||||||
|
anchor: .bottom,
|
||||||
|
perspective: 0.4
|
||||||
|
)
|
||||||
|
.opacity(opacity)
|
||||||
|
.onAppear {
|
||||||
|
// Smooth settling animation
|
||||||
|
withAnimation(.easeOut(duration: 0.2)) {
|
||||||
|
rotationAngle = 0
|
||||||
|
opacity = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Glow Background View
|
||||||
|
|
||||||
|
/// A standalone glow view that sits underneath the card.
|
||||||
|
/// Completely separate from the card view to avoid any state changes affecting the card.
|
||||||
|
private struct GlowBackgroundView: View {
|
||||||
let isActive: Bool
|
let isActive: Bool
|
||||||
let glowPulse: Bool
|
let glowPulse: Bool
|
||||||
let cardWidth: CGFloat
|
let cardWidth: CGFloat
|
||||||
@ -72,11 +162,11 @@ private struct GlowModifier: ViewModifier {
|
|||||||
cardWidth * 0.08
|
cardWidth * 0.08
|
||||||
}
|
}
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
private var cardHeight: CGFloat {
|
||||||
if isActive {
|
cardWidth * CasinoDesign.Size.cardAspectRatio
|
||||||
content
|
}
|
||||||
.overlay {
|
|
||||||
// Glow effect using multiple stroke layers
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Outer glow strokes (largest to smallest for layered glow)
|
// Outer glow strokes (largest to smallest for layered glow)
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
@ -102,14 +192,13 @@ private struct GlowModifier: ViewModifier {
|
|||||||
lineWidth: glowPulse ? 4 : 3
|
lineWidth: glowPulse ? 4 : 3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
.frame(width: cardWidth, height: cardHeight)
|
||||||
.scaleEffect(glowPulse ? 1.02 : 1.0)
|
.scaleEffect(isActive && glowPulse ? 1.02 : 1.0)
|
||||||
|
.opacity(isActive ? 1.0 : 0.0)
|
||||||
|
.animation(.easeOut(duration: 0.15), value: isActive)
|
||||||
.animation(
|
.animation(
|
||||||
.easeInOut(duration: 0.7).repeatForever(autoreverses: true),
|
isActive ? .easeInOut(duration: 0.7).repeatForever(autoreverses: true) : .easeOut(duration: 0.15),
|
||||||
value: glowPulse
|
value: glowPulse
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ struct PageCurlView: View {
|
|||||||
let card: Card
|
let card: Card
|
||||||
let width: CGFloat
|
let width: CGFloat
|
||||||
let onReveal: () -> Void
|
let onReveal: () -> Void
|
||||||
|
/// Called when user starts interacting with the page curl
|
||||||
|
var onInteractionStarted: (() -> Void)? = nil
|
||||||
|
|
||||||
@State private var currentIndex = 0
|
@State private var currentIndex = 0
|
||||||
|
|
||||||
@ -22,7 +24,11 @@ struct PageCurlView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
PageCurlRepresentable(currentIndex: $currentIndex, onReveal: onReveal, pages: [
|
PageCurlRepresentable(
|
||||||
|
currentIndex: $currentIndex,
|
||||||
|
onReveal: onReveal,
|
||||||
|
onInteractionStarted: onInteractionStarted,
|
||||||
|
pages: [
|
||||||
// Page 0: Card Back (Top side)
|
// Page 0: Card Back (Top side)
|
||||||
AnyView(
|
AnyView(
|
||||||
CardBackView(width: width, height: height)
|
CardBackView(width: width, height: height)
|
||||||
@ -49,6 +55,7 @@ struct PageCurlView: View {
|
|||||||
private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
||||||
@Binding var currentIndex: Int
|
@Binding var currentIndex: Int
|
||||||
let onReveal: () -> Void
|
let onReveal: () -> Void
|
||||||
|
let onInteractionStarted: (() -> Void)?
|
||||||
let pages: [AnyView]
|
let pages: [AnyView]
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> UIPageViewController {
|
func makeUIViewController(context: Context) -> UIPageViewController {
|
||||||
@ -112,6 +119,12 @@ private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
|||||||
return nextVC
|
return nextVC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pageViewController(_ pageVC: UIPageViewController,
|
||||||
|
willTransitionTo pendingViewControllers: [UIViewController]) {
|
||||||
|
// User started interacting with the page curl
|
||||||
|
parent.onInteractionStarted?()
|
||||||
|
}
|
||||||
|
|
||||||
func pageViewController(_ pageVC: UIPageViewController,
|
func pageViewController(_ pageVC: UIPageViewController,
|
||||||
didFinishAnimating finished: Bool,
|
didFinishAnimating finished: Bool,
|
||||||
previousViewControllers: [UIViewController],
|
previousViewControllers: [UIViewController],
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user