247 lines
6.7 KiB
Markdown
247 lines
6.7 KiB
Markdown
# 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:
|
|
|
|
```swift
|
|
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:
|
|
|
|
```swift
|
|
@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:
|
|
|
|
```swift
|
|
public extension View {
|
|
func dynamicTooltip(_ manager: TooltipManager) -> some View
|
|
}
|
|
```
|
|
|
|
## Usage in Games
|
|
|
|
### Before (Verbose)
|
|
|
|
```swift
|
|
// 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)
|
|
|
|
```swift
|
|
// 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:**
|
|
```swift
|
|
@State private var tooltipManager: TooltipManager?
|
|
```
|
|
|
|
2. **Initialize in onAppear:**
|
|
```swift
|
|
.onAppear {
|
|
tooltipManager = TooltipManager(onboarding: gameState.onboarding)
|
|
}
|
|
```
|
|
|
|
3. **Apply view modifier:**
|
|
```swift
|
|
.dynamicTooltip(tooltipManager ?? TooltipManager(onboarding: gameState.onboarding))
|
|
```
|
|
|
|
4. **Show tooltips:**
|
|
```swift
|
|
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.
|
|
|
|
```bash
|
|
# 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
|
|
|