CasinoGames/TOOLTIP_REFACTORING.md

6.7 KiB

Tooltip System Refactoring

Overview

The tooltip management system has been refactored from game-specific implementations to a generic, reusable system in CasinoKit.

Problem

The original implementation had several issues:

  • Verbose: Each game had duplicate TooltipConfig structs and complex state management
  • Not scalable: Adding new tooltips required significant boilerplate
  • Repetitive logic: Similar code duplicated across Blackjack and Baccarat
  • Difficult to maintain: Changes required updates in multiple places

Solution

Created a centralized TooltipManager class in CasinoKit that:

  • Manages tooltip state generically
  • Automatically handles dismiss tracking
  • Ensures only one tooltip shows at a time
  • Integrates seamlessly with OnboardingState

New Components

1. TooltipConfig (CasinoKit)

A simple struct defining tooltip properties:

public struct TooltipConfig: Equatable {
    public let key: String
    public let message: String
    public let icon: String
    public let position: ContextualTooltip.TooltipPosition
}

2. TooltipManager (CasinoKit)

An @Observable class that manages tooltip lifecycle:

@Observable
@MainActor
public final class TooltipManager {
    private let onboarding: OnboardingState
    public var currentTooltip: TooltipConfig?
    
    public func show(
        key: String,
        message: String,
        icon: String = "lightbulb.fill",
        position: ContextualTooltip.TooltipPosition = .bottom,
        delay: TimeInterval = 1.0
    )
    
    public func dismiss()
}

Key Features:

  • Automatic dismiss tracking via didSet on currentTooltip
  • Delayed presentation with cancellation if hint already shown
  • Respects OnboardingState.shouldShowHint() checks
  • Single source of truth for current tooltip

3. dynamicTooltip View Modifier (CasinoKit)

A convenient view modifier that displays tooltips:

public extension View {
    func dynamicTooltip(_ manager: TooltipManager) -> some View
}

Usage in Games

Before (Verbose)

// State variables for each tooltip
@State private var showBettingHint = false
@State private var showDealHint = false
@State private var showActionsHint = false

// Separate struct definition
private struct TooltipConfig {
    let key: String
    let message: String
    let icon: String
    let position: ContextualTooltip.TooltipPosition
}

// Complex state with tracking
@State private var currentTooltip: TooltipConfig? {
    didSet {
        if let oldValue = oldValue, oldValue.key != currentTooltip?.key {
            state.onboarding.markHintShown(oldValue.key)
        }
    }
}

// Generic helper with lots of parameters
private func showTooltip(key: String, message: String, icon: String, position: ContextualTooltip.TooltipPosition, delay: TimeInterval) {
    Task { @MainActor in
        try? await Task.sleep(for: .seconds(delay))
        guard state.onboarding.shouldShowHint(key) else { return }
        withAnimation(.spring(duration: Design.Animation.springDuration)) {
            currentTooltip = TooltipConfig(key: key, message: message, icon: icon, position: position)
        }
    }
}

// Complex overlay in view body
.overlay {
    if let tooltip = currentTooltip {
        VStack {
            // ... positioning logic
            ContextualTooltip(
                message: tooltip.message,
                icon: tooltip.icon,
                position: tooltip.position,
                onDismiss: {
                    state.onboarding.markHintShown(tooltip.key)
                    withAnimation(.spring(duration: Design.Animation.springDuration)) {
                        currentTooltip = nil
                    }
                }
            )
            // ... more positioning
        }
    }
}

After (Clean)

// Single state variable
@State private var tooltipManager: TooltipManager?

// Initialize in onAppear
.onAppear {
    tooltipManager = TooltipManager(onboarding: state.onboarding)
}

// Simple show calls
private func showBettingHint() {
    tooltipManager?.show(
        key: "bettingZone",
        message: "Select a chip and tap here to bet",
        icon: "hand.tap.fill",
        position: .bottom,
        delay: 1.0
    )
}

// One-line view modifier
.dynamicTooltip(tooltipManager ?? TooltipManager(onboarding: state.onboarding))

Benefits

  1. Reduced code: ~70 lines of boilerplate eliminated per game
  2. Better scalability: Adding new tooltips is now trivial
  3. Single source of truth: All tooltip logic in CasinoKit
  4. Easier maintenance: Changes only need to be made in one place
  5. Reusable: Any new game can use TooltipManager with minimal setup

Files Changed

CasinoKit

  • Created: Sources/CasinoKit/Models/TooltipManager.swift
  • Updated: Sources/CasinoKit/Exports.swift (added TooltipManager export)
  • Updated: README.md (added TooltipManager documentation)

Blackjack

  • Updated: Views/Game/GameTableView.swift
    • Removed local TooltipConfig struct
    • Removed currentTooltip @State with didSet
    • Removed generic showTooltip() helper
    • Added @State private var tooltipManager: TooltipManager?
    • Simplified tooltip show methods
    • Replaced complex overlay with .dynamicTooltip() modifier

Baccarat

  • Updated: Views/Game/GameTableView.swift
    • Same changes as Blackjack

Migration Guide

To use TooltipManager in a new game:

  1. Add state variable:

    @State private var tooltipManager: TooltipManager?
    
  2. Initialize in onAppear:

    .onAppear {
        tooltipManager = TooltipManager(onboarding: gameState.onboarding)
    }
    
  3. Apply view modifier:

    .dynamicTooltip(tooltipManager ?? TooltipManager(onboarding: gameState.onboarding))
    
  4. Show tooltips:

    tooltipManager?.show(
        key: "uniqueKey",
        message: "Your hint text",
        icon: "lightbulb.fill",
        position: .bottom,
        delay: 1.0
    )
    

Testing

Both Blackjack and Baccarat have been built and tested successfully with the new system.

# Blackjack
xcodebuild -workspace CasinoGames.xcworkspace -scheme Blackjack \
  -configuration Debug -destination 'platform=iOS Simulator,id=...' build
# ✅ BUILD SUCCEEDED

# Baccarat
xcodebuild -workspace CasinoGames.xcworkspace -scheme Baccarat \
  -configuration Debug -destination 'platform=iOS Simulator,id=...' build
# ✅ BUILD SUCCEEDED

Future Enhancements

Potential improvements:

  • Tooltip queue: Allow queueing multiple tooltips
  • Positioning hints: Auto-detect best position based on screen space
  • Animation customization: Per-tooltip animation styles
  • Analytics: Track which tooltips are most helpful