Compare commits
No commits in common. "b6ba6264f78e62b7be53106c2d0dde995ad3b013" and "02eecf40ec818128e325b23ffae59884b464fabb" have entirely different histories.
b6ba6264f7
...
02eecf40ec
@ -114,19 +114,6 @@ final class GameState: CasinoGameState {
|
|||||||
var isAnimating: Bool = false
|
var isAnimating: Bool = false
|
||||||
var showResultBanner: Bool = false
|
var showResultBanner: Bool = false
|
||||||
|
|
||||||
// MARK: - Reveal State
|
|
||||||
/// Whether the game is waiting for a user to reveal a card (Tap or Squeeze style).
|
|
||||||
var isWaitingForReveal: Bool = false
|
|
||||||
|
|
||||||
/// The index of the card currently being revealed (0-3 for initial, 4+ for others).
|
|
||||||
var currentRevealIndex: Int = 0
|
|
||||||
|
|
||||||
/// The progress of a squeeze reveal (0.0 to 1.0).
|
|
||||||
var revealProgress: Double = 0.0
|
|
||||||
|
|
||||||
/// Continuation to resume dealing after a manual reveal.
|
|
||||||
private var revealContinuation: CheckedContinuation<Void, Never>?
|
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
var totalBetAmount: Int {
|
var totalBetAmount: Int {
|
||||||
@ -620,45 +607,6 @@ final class GameState: CasinoGameState {
|
|||||||
return allBetTypes.map { betAmount(for: $0) }.min() ?? 0
|
return allBetTypes.map { betAmount(for: $0) }.min() ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func revealCurrentCard() {
|
|
||||||
guard isWaitingForReveal else { return }
|
|
||||||
|
|
||||||
isWaitingForReveal = false
|
|
||||||
revealProgress = 1.0
|
|
||||||
|
|
||||||
let continuation = revealContinuation
|
|
||||||
revealContinuation = nil
|
|
||||||
continuation?.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the progress of a squeeze reveal.
|
|
||||||
func updateRevealProgress(_ progress: Double) {
|
|
||||||
revealProgress = progress
|
|
||||||
if progress >= 1.0 {
|
|
||||||
revealCurrentCard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func waitForReveal(index: Int) async {
|
|
||||||
guard settings.showAnimations else { return }
|
|
||||||
|
|
||||||
let isBottomSlot = index % 2 == 0
|
|
||||||
let isInteractiveStyle = settings.revealStyle == .tap || settings.revealStyle == .squeeze
|
|
||||||
|
|
||||||
if isInteractiveStyle && isBottomSlot {
|
|
||||||
// Wait for user interaction only on Bottom (Home) cards
|
|
||||||
currentRevealIndex = index
|
|
||||||
revealProgress = 0.0
|
|
||||||
isWaitingForReveal = true
|
|
||||||
|
|
||||||
await withCheckedContinuation { continuation in
|
|
||||||
revealContinuation = continuation
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Auto-reveal for all cards in .auto style, and Top (Away) cards in interactive styles
|
|
||||||
try? await Task.sleep(for: flipDelay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// MARK: - Betting Actions
|
// MARK: - Betting Actions
|
||||||
|
|
||||||
/// Places a bet of the specified amount on the given bet type.
|
/// Places a bet of the specified amount on the given bet type.
|
||||||
@ -718,8 +666,6 @@ final class GameState: CasinoGameState {
|
|||||||
func deal() async {
|
func deal() async {
|
||||||
guard canDeal else { return }
|
guard canDeal else { return }
|
||||||
|
|
||||||
let bottomIsPlayer = bettedOnPlayer ?? true
|
|
||||||
|
|
||||||
isAnimating = true
|
isAnimating = true
|
||||||
engine.prepareNewRound()
|
engine.prepareNewRound()
|
||||||
|
|
||||||
@ -739,34 +685,21 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
// Wait for layout animation to complete before dealing cards
|
// Wait for layout animation to complete before dealing cards
|
||||||
if settings.showAnimations {
|
if settings.showAnimations {
|
||||||
// Increased from 1s to 1.25s for better stability on all devices
|
try? await Task.sleep(for: .seconds(1))
|
||||||
try? await Task.sleep(for: .milliseconds(1250))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialCards = engine.dealInitialCards()
|
let initialCards = engine.dealInitialCards()
|
||||||
|
|
||||||
// Check if animations are enabled
|
// Check if animations are enabled
|
||||||
if settings.showAnimations {
|
if settings.showAnimations {
|
||||||
// Brief extra delay before the very first card deal starts
|
// Animate dealing: P1, B1, P2, B2
|
||||||
try? await Task.sleep(for: .milliseconds(250))
|
for (index, card) in initialCards.enumerated() {
|
||||||
|
|
||||||
// Animate dealing: B1 (Bottom 1), T1 (Top 1), B2 (Bottom 2), T2 (Top 2)
|
|
||||||
|
|
||||||
// Map slots to roles: (isPlayerRole, engineCardIndex)
|
|
||||||
// engineCardIndex: 0=P1, 1=B1, 2=P2, 3=B2
|
|
||||||
let dealSequence: [(isPlayer: Bool, cardIndex: Int)] = [
|
|
||||||
(bottomIsPlayer, bottomIsPlayer ? 0 : 1), // B1
|
|
||||||
(!bottomIsPlayer, bottomIsPlayer ? 1 : 0), // T1
|
|
||||||
(bottomIsPlayer, bottomIsPlayer ? 2 : 3), // B2
|
|
||||||
(!bottomIsPlayer, bottomIsPlayer ? 3 : 2) // T2
|
|
||||||
]
|
|
||||||
|
|
||||||
for (stepIndex, step) in dealSequence.enumerated() {
|
|
||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
|
|
||||||
|
// Play card deal sound
|
||||||
sound.playCardDeal()
|
sound.playCardDeal()
|
||||||
|
|
||||||
let card = initialCards[step.cardIndex]
|
if index % 2 == 0 {
|
||||||
if step.isPlayer {
|
|
||||||
visiblePlayerCards.append(card)
|
visiblePlayerCards.append(card)
|
||||||
playerCardsFaceUp.append(false)
|
playerCardsFaceUp.append(false)
|
||||||
} else {
|
} else {
|
||||||
@ -775,29 +708,21 @@ final class GameState: CasinoGameState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reveal according to selected style
|
// Flip cards one by one instead of all at once
|
||||||
switch settings.revealStyle {
|
for i in 0..<playerCardsFaceUp.count {
|
||||||
case .auto, .tap, .squeeze:
|
// Flip player card
|
||||||
// Sequential reveal: B1 -> T1 -> B2 -> T2
|
try? await Task.sleep(for: flipDelay)
|
||||||
for i in 0..<4 {
|
|
||||||
await waitForReveal(index: i)
|
|
||||||
sound.playCardFlip()
|
sound.playCardFlip()
|
||||||
|
playerCardsFaceUp[i] = true
|
||||||
|
|
||||||
let slotIndex = i / 2 // 0 for B1/T1, 1 for B2/T2
|
// Flip banker card
|
||||||
let isBottom = i % 2 == 0
|
try? await Task.sleep(for: flipDelay)
|
||||||
|
sound.playCardFlip()
|
||||||
if (isBottom && bottomIsPlayer) || (!isBottom && !bottomIsPlayer) {
|
bankerCardsFaceUp[i] = true
|
||||||
playerCardsFaceUp[slotIndex] = true
|
|
||||||
} else {
|
|
||||||
bankerCardsFaceUp[slotIndex] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause to let user see initial totals
|
// Pause to let user see initial totals
|
||||||
if settings.revealStyle == .auto {
|
|
||||||
try? await Task.sleep(for: resultDelay)
|
try? await Task.sleep(for: resultDelay)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// No animations - show all cards immediately
|
// No animations - show all cards immediately
|
||||||
for (index, card) in initialCards.enumerated() {
|
for (index, card) in initialCards.enumerated() {
|
||||||
@ -821,89 +746,43 @@ final class GameState: CasinoGameState {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- THIRD CARD HANDLING ---
|
// Player third card
|
||||||
// Slots: index 4 = B3, index 5 = T3
|
currentPhase = .playerThirdCard
|
||||||
let playerThird = engine.drawPlayerThirdCard()
|
if let playerThird = engine.drawPlayerThirdCard() {
|
||||||
let bankerThird = engine.drawBankerThirdCard()
|
|
||||||
|
|
||||||
// Slot B3
|
|
||||||
let b3Card = bottomIsPlayer ? playerThird : bankerThird
|
|
||||||
if let card = b3Card {
|
|
||||||
if settings.showAnimations {
|
if settings.showAnimations {
|
||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
sound.playCardDeal()
|
sound.playCardDeal()
|
||||||
if bottomIsPlayer {
|
visiblePlayerCards.append(playerThird)
|
||||||
visiblePlayerCards.append(card)
|
|
||||||
playerCardsFaceUp.append(false)
|
playerCardsFaceUp.append(false)
|
||||||
|
|
||||||
|
try? await Task.sleep(for: flipDelay) // Using flipDelay for third card too
|
||||||
|
sound.playCardFlip()
|
||||||
|
playerCardsFaceUp[2] = true
|
||||||
|
|
||||||
|
try? await Task.sleep(for: resultDelay)
|
||||||
} else {
|
} else {
|
||||||
visibleBankerCards.append(card)
|
visiblePlayerCards.append(playerThird)
|
||||||
bankerCardsFaceUp.append(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if bottomIsPlayer {
|
|
||||||
visiblePlayerCards.append(card)
|
|
||||||
playerCardsFaceUp.append(true)
|
playerCardsFaceUp.append(true)
|
||||||
} else {
|
|
||||||
visibleBankerCards.append(card)
|
|
||||||
bankerCardsFaceUp.append(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot T3
|
// Banker third card
|
||||||
let t3Card = bottomIsPlayer ? bankerThird : playerThird
|
currentPhase = .bankerThirdCard
|
||||||
if let card = t3Card {
|
if let bankerThird = engine.drawBankerThirdCard() {
|
||||||
if settings.showAnimations {
|
if settings.showAnimations {
|
||||||
try? await Task.sleep(for: dealDelay)
|
try? await Task.sleep(for: dealDelay)
|
||||||
sound.playCardDeal()
|
sound.playCardDeal()
|
||||||
if bottomIsPlayer {
|
visibleBankerCards.append(bankerThird)
|
||||||
visibleBankerCards.append(card)
|
|
||||||
bankerCardsFaceUp.append(false)
|
bankerCardsFaceUp.append(false)
|
||||||
|
|
||||||
|
try? await Task.sleep(for: flipDelay) // Using flipDelay for third card too
|
||||||
|
sound.playCardFlip()
|
||||||
|
bankerCardsFaceUp[2] = true
|
||||||
|
|
||||||
|
try? await Task.sleep(for: resultDelay)
|
||||||
} else {
|
} else {
|
||||||
visiblePlayerCards.append(card)
|
visibleBankerCards.append(bankerThird)
|
||||||
playerCardsFaceUp.append(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if bottomIsPlayer {
|
|
||||||
visibleBankerCards.append(card)
|
|
||||||
bankerCardsFaceUp.append(true)
|
bankerCardsFaceUp.append(true)
|
||||||
} else {
|
|
||||||
visiblePlayerCards.append(card)
|
|
||||||
playerCardsFaceUp.append(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reveal 3rd cards in Slot order: B3 then T3
|
|
||||||
if settings.showAnimations {
|
|
||||||
// Reveal B3 (Reveal Index 4)
|
|
||||||
if b3Card != nil {
|
|
||||||
await waitForReveal(index: 4)
|
|
||||||
sound.playCardFlip()
|
|
||||||
if bottomIsPlayer {
|
|
||||||
playerCardsFaceUp[2] = true
|
|
||||||
} else {
|
|
||||||
bankerCardsFaceUp[2] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.revealStyle == .auto {
|
|
||||||
try? await Task.sleep(for: resultDelay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reveal T3 (Reveal Index 5)
|
|
||||||
if t3Card != nil {
|
|
||||||
await waitForReveal(index: 5)
|
|
||||||
sound.playCardFlip()
|
|
||||||
if bottomIsPlayer {
|
|
||||||
bankerCardsFaceUp[2] = true
|
|
||||||
} else {
|
|
||||||
playerCardsFaceUp[2] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.revealStyle == .auto {
|
|
||||||
try? await Task.sleep(for: resultDelay)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,39 +34,6 @@ enum DeckCount: Int, CaseIterable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The style used to reveal cards.
|
|
||||||
enum RevealStyle: String, CaseIterable, Codable, Identifiable {
|
|
||||||
case auto // Automatically reveal (original behavior)
|
|
||||||
case tap // Tap each card to reveal
|
|
||||||
case squeeze // Tap + Hold / Drag for pressure-based reveal
|
|
||||||
|
|
||||||
var id: String { rawValue } // Keep Identifiable conformance for SwiftUI
|
|
||||||
|
|
||||||
var displayName: String {
|
|
||||||
switch self {
|
|
||||||
case .auto: return String(localized: "Auto Reveal")
|
|
||||||
case .tap: return String(localized: "Tap Reveal")
|
|
||||||
case .squeeze: return String(localized: "Squeeze Reveal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpText: String {
|
|
||||||
switch self {
|
|
||||||
case .auto: return String(localized: "Cards flip automatically")
|
|
||||||
case .tap: return String(localized: "Tap each card to reveal")
|
|
||||||
case .squeeze: return String(localized: "Squeeze card to peek at value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var iconName: String {
|
|
||||||
switch self {
|
|
||||||
case .auto: return "bolt.fill"
|
|
||||||
case .tap: return "hand.tap.fill"
|
|
||||||
case .squeeze: return "hand.point.up.left.and.text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableLimits is now provided by CasinoKit
|
// TableLimits is now provided by CasinoKit
|
||||||
|
|
||||||
/// Observable settings class for Baccarat configuration.
|
/// Observable settings class for Baccarat configuration.
|
||||||
@ -107,9 +74,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
/// Speed of card dealing (uses CasinoDesign.DealingSpeed constants)
|
/// Speed of card dealing (uses CasinoDesign.DealingSpeed constants)
|
||||||
var dealingSpeed: Double = CasinoDesign.DealingSpeed.normal
|
var dealingSpeed: Double = CasinoDesign.DealingSpeed.normal
|
||||||
|
|
||||||
/// The style used to reveal cards.
|
|
||||||
var revealStyle: RevealStyle = .auto
|
|
||||||
|
|
||||||
// MARK: - Display Settings
|
// MARK: - Display Settings
|
||||||
|
|
||||||
/// Whether to show the cards remaining indicator.
|
/// Whether to show the cards remaining indicator.
|
||||||
@ -146,7 +110,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
static let soundEnabled = "settings.soundEnabled"
|
static let soundEnabled = "settings.soundEnabled"
|
||||||
static let hapticsEnabled = "settings.hapticsEnabled"
|
static let hapticsEnabled = "settings.hapticsEnabled"
|
||||||
static let soundVolume = "settings.soundVolume"
|
static let soundVolume = "settings.soundVolume"
|
||||||
static let revealStyle = "settings.revealStyle"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - iCloud
|
// MARK: - iCloud
|
||||||
@ -262,11 +225,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
if let volume = defaults.object(forKey: Keys.soundVolume) as? Float {
|
if let volume = defaults.object(forKey: Keys.soundVolume) as? Float {
|
||||||
self.soundVolume = volume
|
self.soundVolume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
if let rawStyle = defaults.string(forKey: Keys.revealStyle),
|
|
||||||
let style = RevealStyle(rawValue: rawStyle) {
|
|
||||||
self.revealStyle = style
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads settings from iCloud.
|
/// Loads settings from iCloud.
|
||||||
@ -318,11 +276,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
if let volume = store.object(forKey: Keys.soundVolume) as? Double {
|
if let volume = store.object(forKey: Keys.soundVolume) as? Double {
|
||||||
self.soundVolume = Float(volume)
|
self.soundVolume = Float(volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let rawStyle = store.string(forKey: Keys.revealStyle),
|
|
||||||
let style = RevealStyle(rawValue: rawStyle) {
|
|
||||||
self.revealStyle = style
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves settings to UserDefaults and iCloud.
|
/// Saves settings to UserDefaults and iCloud.
|
||||||
@ -340,7 +293,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
defaults.set(soundEnabled, forKey: Keys.soundEnabled)
|
defaults.set(soundEnabled, forKey: Keys.soundEnabled)
|
||||||
defaults.set(hapticsEnabled, forKey: Keys.hapticsEnabled)
|
defaults.set(hapticsEnabled, forKey: Keys.hapticsEnabled)
|
||||||
defaults.set(soundVolume, forKey: Keys.soundVolume)
|
defaults.set(soundVolume, forKey: Keys.soundVolume)
|
||||||
defaults.set(revealStyle.rawValue, forKey: Keys.revealStyle)
|
|
||||||
|
|
||||||
// Also save to iCloud
|
// Also save to iCloud
|
||||||
if iCloudAvailable, let store = iCloudStore {
|
if iCloudAvailable, let store = iCloudStore {
|
||||||
@ -355,7 +307,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
store.set(soundEnabled, forKey: Keys.soundEnabled)
|
store.set(soundEnabled, forKey: Keys.soundEnabled)
|
||||||
store.set(hapticsEnabled, forKey: Keys.hapticsEnabled)
|
store.set(hapticsEnabled, forKey: Keys.hapticsEnabled)
|
||||||
store.set(Double(soundVolume), forKey: Keys.soundVolume)
|
store.set(Double(soundVolume), forKey: Keys.soundVolume)
|
||||||
store.set(revealStyle.rawValue, forKey: Keys.revealStyle)
|
|
||||||
store.synchronize()
|
store.synchronize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -391,7 +342,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
soundEnabled = true
|
soundEnabled = true
|
||||||
hapticsEnabled = true
|
hapticsEnabled = true
|
||||||
soundVolume = 1.0
|
soundVolume = 1.0
|
||||||
revealStyle = .auto
|
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -723,10 +723,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Auto Reveal" : {
|
|
||||||
"comment" : "Name of the \"Auto Reveal\" card reveal style.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Avoid the Tie bet — 14.4% house edge!" : {
|
"Avoid the Tie bet — 14.4% house edge!" : {
|
||||||
"comment" : "Tip for avoiding the Tie bet in baccarat, highlighting its low house edge.",
|
"comment" : "Tip for avoiding the Tie bet in baccarat, highlighting its low house edge.",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -1440,10 +1436,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Cards flip automatically" : {
|
|
||||||
"comment" : "Help text for the \"Auto Reveal\" reveal style.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Change table limits and display options" : {
|
"Change table limits and display options" : {
|
||||||
"comment" : "Welcome screen feature description for customizing settings.",
|
"comment" : "Welcome screen feature description for customizing settings.",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -3673,10 +3665,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"REVEAL STYLE" : {
|
|
||||||
"comment" : "The title of the section in the settings view related to the reveal style.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Road maps show game history and trends" : {
|
"Road maps show game history and trends" : {
|
||||||
"comment" : "Welcome screen feature description for pattern tracking.",
|
"comment" : "Welcome screen feature description for pattern tracking.",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -4230,14 +4218,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Squeeze card to peek at value" : {
|
|
||||||
"comment" : "Help text for the \"Squeeze Reveal\" card reveal style.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Squeeze Reveal" : {
|
|
||||||
"comment" : "Name of the reveal style option that uses pressure-based card peeking.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"STARTING BALANCE" : {
|
"STARTING BALANCE" : {
|
||||||
"comment" : "Section header for starting balance settings.",
|
"comment" : "Section header for starting balance settings.",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -4467,14 +4447,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Tap each card to reveal" : {
|
|
||||||
"comment" : "Help text for the \"Tap Reveal\" reveal style.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Tap Reveal" : {
|
|
||||||
"comment" : "Name of the icon representing the \"Tap Reveal\" card reveal style.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"The hand closest to 9 wins" : {
|
"The hand closest to 9 wins" : {
|
||||||
"comment" : "Welcome screen feature description for betting explanation.",
|
"comment" : "Welcome screen feature description for betting explanation.",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@ -305,32 +305,23 @@ struct GameTableView: View, SherpaDelegate {
|
|||||||
isTie: isTie,
|
isTie: isTie,
|
||||||
showAnimations: settings.showAnimations,
|
showAnimations: settings.showAnimations,
|
||||||
dealingSpeed: settings.dealingSpeed,
|
dealingSpeed: settings.dealingSpeed,
|
||||||
revealStyle: settings.revealStyle,
|
|
||||||
isWaitingForReveal: state.isWaitingForReveal,
|
|
||||||
currentRevealIndex: state.currentRevealIndex,
|
|
||||||
revealProgress: state.revealProgress,
|
|
||||||
bettedOnPlayer: state.bettedOnPlayer,
|
bettedOnPlayer: state.bettedOnPlayer,
|
||||||
isDealing: isDealing,
|
isDealing: isDealing,
|
||||||
screenSize: screenSize,
|
screenSize: screenSize
|
||||||
totalBetAmount: state.totalBetAmount,
|
|
||||||
onReveal: { state.revealCurrentCard() },
|
|
||||||
onUpdateProgress: { state.updateRevealProgress($0) }
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: maxContentWidth)
|
.frame(maxWidth: maxContentWidth)
|
||||||
.padding(.horizontal, isDealing ? 0 : Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
|
|
||||||
// Betting table - completely hidden during dealing
|
// Betting table
|
||||||
if !isDealing {
|
|
||||||
BettingTableView(
|
BettingTableView(
|
||||||
gameState: state,
|
gameState: state,
|
||||||
selectedChip: selectedChip
|
selectedChip: selectedChip
|
||||||
)
|
)
|
||||||
.frame(maxWidth: maxContentWidth)
|
.frame(maxWidth: maxContentWidth)
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.transition(.opacity)
|
|
||||||
.debugBorder(showDebugBorders, color: .blue, label: "BetTable")
|
.debugBorder(showDebugBorders, color: .blue, label: "BetTable")
|
||||||
|
|
||||||
// Betting hint (static, below table, above chips)
|
// Betting hint (static, below table, above chips)
|
||||||
@ -344,7 +335,6 @@ struct GameTableView: View, SherpaDelegate {
|
|||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.debugBorder(showDebugBorders, color: .purple, label: "Hint")
|
.debugBorder(showDebugBorders, color: .purple, label: "Hint")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(minLength: Design.Spacing.xSmall)
|
Spacer(minLength: Design.Spacing.xSmall)
|
||||||
|
|
||||||
@ -418,19 +408,12 @@ struct GameTableView: View, SherpaDelegate {
|
|||||||
isTie: isTie,
|
isTie: isTie,
|
||||||
showAnimations: settings.showAnimations,
|
showAnimations: settings.showAnimations,
|
||||||
dealingSpeed: settings.dealingSpeed,
|
dealingSpeed: settings.dealingSpeed,
|
||||||
revealStyle: settings.revealStyle,
|
|
||||||
isWaitingForReveal: state.isWaitingForReveal,
|
|
||||||
currentRevealIndex: state.currentRevealIndex,
|
|
||||||
revealProgress: state.revealProgress,
|
|
||||||
bettedOnPlayer: state.bettedOnPlayer,
|
bettedOnPlayer: state.bettedOnPlayer,
|
||||||
isDealing: isDealing,
|
isDealing: isDealing,
|
||||||
screenSize: screenSize,
|
screenSize: screenSize
|
||||||
totalBetAmount: state.totalBetAmount,
|
|
||||||
onReveal: { state.revealCurrentCard() },
|
|
||||||
onUpdateProgress: { state.updateRevealProgress($0) }
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||||
.padding(.horizontal, isDealing ? 0 : Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
||||||
|
|
||||||
Spacer(minLength: smallSpacerHeight)
|
Spacer(minLength: smallSpacerHeight)
|
||||||
@ -448,15 +431,13 @@ struct GameTableView: View, SherpaDelegate {
|
|||||||
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer3")
|
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer3")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Betting table - completely hidden during dealing
|
// Betting table
|
||||||
if !isDealing {
|
|
||||||
BettingTableView(
|
BettingTableView(
|
||||||
gameState: state,
|
gameState: state,
|
||||||
selectedChip: selectedChip
|
selectedChip: selectedChip
|
||||||
)
|
)
|
||||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.transition(.opacity)
|
|
||||||
.debugBorder(showDebugBorders, color: .blue, label: "BetTable")
|
.debugBorder(showDebugBorders, color: .blue, label: "BetTable")
|
||||||
|
|
||||||
// Betting hint (static, below table, above chips)
|
// Betting hint (static, below table, above chips)
|
||||||
@ -470,7 +451,6 @@ struct GameTableView: View, SherpaDelegate {
|
|||||||
.padding(.vertical, Design.Spacing.small)
|
.padding(.vertical, Design.Spacing.small)
|
||||||
.debugBorder(showDebugBorders, color: .purple, label: "Hint")
|
.debugBorder(showDebugBorders, color: .purple, label: "Hint")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Chip selector - only shown during betting phase
|
// Chip selector - only shown during betting phase
|
||||||
if state.currentPhase == .betting && !state.showResultBanner {
|
if state.currentPhase == .betting && !state.showResultBanner {
|
||||||
|
|||||||
@ -71,14 +71,6 @@ struct SettingsView: View {
|
|||||||
.background(Color.white.opacity(Design.Opacity.subtle))
|
.background(Color.white.opacity(Design.Opacity.subtle))
|
||||||
|
|
||||||
SpeedPicker(speed: $settings.dealingSpeed, accentColor: accent)
|
SpeedPicker(speed: $settings.dealingSpeed, accentColor: accent)
|
||||||
|
|
||||||
Divider()
|
|
||||||
.background(Color.white.opacity(Design.Opacity.subtle))
|
|
||||||
|
|
||||||
RevealStylePicker(selection: $settings.revealStyle, accentColor: accent)
|
|
||||||
.onChange(of: settings.revealStyle) { _, _ in
|
|
||||||
hasChanges = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@ -447,35 +439,6 @@ struct TableLimitsPicker: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Reveal Style Picker (Baccarat-specific)
|
|
||||||
|
|
||||||
struct RevealStylePicker: View {
|
|
||||||
@Binding var selection: RevealStyle
|
|
||||||
let accentColor: Color
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
|
||||||
Text(String(localized: "REVEAL STYLE"))
|
|
||||||
.font(.system(size: Design.BaseFontSize.small, weight: .bold))
|
|
||||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
|
||||||
.padding(.bottom, Design.Spacing.xxSmall)
|
|
||||||
|
|
||||||
VStack(spacing: Design.Spacing.small) {
|
|
||||||
ForEach(RevealStyle.allCases) { style in
|
|
||||||
SelectableRow(
|
|
||||||
title: style.displayName,
|
|
||||||
subtitle: style.helpText,
|
|
||||||
isSelected: selection == style,
|
|
||||||
accentColor: accentColor,
|
|
||||||
action: { selection = style }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.vertical, Design.Spacing.xSmall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
SettingsView(settings: GameSettings(), gameState: GameState()) { }
|
SettingsView(settings: GameSettings(), gameState: GameState()) { }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,20 +24,12 @@ struct CardsDisplayArea: View {
|
|||||||
let isTie: Bool
|
let isTie: Bool
|
||||||
let showAnimations: Bool
|
let showAnimations: Bool
|
||||||
let dealingSpeed: Double
|
let dealingSpeed: Double
|
||||||
let revealStyle: RevealStyle
|
|
||||||
let isWaitingForReveal: Bool
|
|
||||||
let currentRevealIndex: Int
|
|
||||||
let revealProgress: Double
|
|
||||||
/// Which main bet is placed - nil if no main bet, true if Player, false if Banker.
|
/// Which main bet is placed - nil if no main bet, true if Player, false if Banker.
|
||||||
let bettedOnPlayer: Bool?
|
let bettedOnPlayer: Bool?
|
||||||
/// Whether the game is in dealing/result phase (vertical layout) or betting phase (horizontal).
|
/// Whether the game is in dealing/result phase (vertical layout) or betting phase (horizontal).
|
||||||
let isDealing: Bool
|
let isDealing: Bool
|
||||||
/// Full screen size for calculating card width in dealing mode.
|
/// Full screen size for calculating card width in dealing mode.
|
||||||
let screenSize: CGSize
|
let screenSize: CGSize
|
||||||
/// Total bet amount to display below the bottom hand during dealing.
|
|
||||||
let totalBetAmount: Int
|
|
||||||
let onReveal: () -> Void
|
|
||||||
let onUpdateProgress: (Double) -> Void
|
|
||||||
|
|
||||||
// MARK: - State
|
// MARK: - State
|
||||||
|
|
||||||
@ -103,11 +95,11 @@ struct CardsDisplayArea: View {
|
|||||||
let percentage: CGFloat = isLandscape ? 0.175 : height < 700 ? 0.14 : 0.18
|
let percentage: CGFloat = isLandscape ? 0.175 : height < 700 ? 0.14 : 0.18
|
||||||
let cardWidth = height * percentage
|
let cardWidth = height * percentage
|
||||||
|
|
||||||
// CompactHandView uses: cardWidth = (containerWidth - 2 * spacing) / 3
|
// CompactHandView: cardWidth = containerWidth / divisor
|
||||||
// So: containerWidth = 3 * cardWidth + 2 * spacing
|
let overlapRatio: CGFloat = -0.45
|
||||||
let spacing = Design.Spacing.medium
|
|
||||||
let maxCards: CGFloat = 3
|
let maxCards: CGFloat = 3
|
||||||
return (cardWidth * maxCards) + (2 * spacing)
|
let divisor = 1 + (maxCards - 1) * (1 + overlapRatio)
|
||||||
|
return cardWidth * divisor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current hand section width based on mode
|
/// Current hand section width based on mode
|
||||||
@ -166,9 +158,6 @@ struct CardsDisplayArea: View {
|
|||||||
|
|
||||||
playerHandSection(width: handSectionWidth)
|
playerHandSection(width: handSectionWidth)
|
||||||
.matchedGeometryEffect(id: "player", in: animation)
|
.matchedGeometryEffect(id: "player", in: animation)
|
||||||
|
|
||||||
// Bet amount below the bottom hand
|
|
||||||
betAmountDisplay
|
|
||||||
} else {
|
} else {
|
||||||
playerHandSection(width: handSectionWidth)
|
playerHandSection(width: handSectionWidth)
|
||||||
.matchedGeometryEffect(id: "player", in: animation)
|
.matchedGeometryEffect(id: "player", in: animation)
|
||||||
@ -178,9 +167,6 @@ struct CardsDisplayArea: View {
|
|||||||
|
|
||||||
bankerHandSection(width: handSectionWidth)
|
bankerHandSection(width: handSectionWidth)
|
||||||
.matchedGeometryEffect(id: "banker", in: animation)
|
.matchedGeometryEffect(id: "banker", in: animation)
|
||||||
|
|
||||||
// Bet amount below the bottom hand
|
|
||||||
betAmountDisplay
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -207,39 +193,18 @@ struct CardsDisplayArea: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// Only show background in betting phase (horizontal layout)
|
|
||||||
.background(
|
.background(
|
||||||
Group {
|
|
||||||
if !isDealing {
|
|
||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
|
||||||
.fill(Color.black.opacity(Design.Opacity.quarter))
|
.fill(Color.black.opacity(Design.Opacity.quarter))
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
||||||
.animation(.easeOut(duration: 0.5), value: isDealing)
|
.animation(.spring(duration: 0.6, bounce: 0.2), value: isDealing)
|
||||||
.animation(.easeOut(duration: 0.4), value: playerOnBottom)
|
.animation(.spring(duration: 0.5, bounce: 0.15), value: playerOnBottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Views
|
// MARK: - Private Views
|
||||||
|
|
||||||
/// Bet amount display shown below the bottom hand during dealing.
|
|
||||||
@ViewBuilder
|
|
||||||
private var betAmountDisplay: some View {
|
|
||||||
if totalBetAmount > 0 {
|
|
||||||
HStack(spacing: Design.Spacing.xSmall) {
|
|
||||||
Image(systemName: "dollarsign.circle.fill")
|
|
||||||
.font(.system(size: Design.BaseFontSize.xLarge))
|
|
||||||
.foregroundStyle(.yellow)
|
|
||||||
Text("\(totalBetAmount)")
|
|
||||||
.font(.system(size: Design.BaseFontSize.medium, weight: .bold, design: .rounded))
|
|
||||||
.foregroundStyle(.yellow)
|
|
||||||
}
|
|
||||||
.padding(.top, Design.Spacing.medium)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func playerHandSection(width: CGFloat) -> some View {
|
private func playerHandSection(width: CGFloat) -> some View {
|
||||||
// Calculate value from face-up cards
|
// Calculate value from face-up cards
|
||||||
let visibleValue: Int = {
|
let visibleValue: Int = {
|
||||||
@ -273,19 +238,10 @@ struct CardsDisplayArea: View {
|
|||||||
isWinner: playerIsWinner,
|
isWinner: playerIsWinner,
|
||||||
containerWidth: width,
|
containerWidth: width,
|
||||||
showAnimations: showAnimations,
|
showAnimations: showAnimations,
|
||||||
dealingSpeed: dealingSpeed,
|
dealingSpeed: dealingSpeed
|
||||||
revealStyle: revealStyle,
|
|
||||||
isWaitingForReveal: isWaitingForReveal,
|
|
||||||
currentRevealIndex: currentRevealIndex,
|
|
||||||
revealProgress: revealProgress,
|
|
||||||
isPlayerHand: true,
|
|
||||||
isBottom: playerOnBottom,
|
|
||||||
isDealing: isDealing,
|
|
||||||
onReveal: onReveal,
|
|
||||||
onUpdateProgress: onUpdateProgress
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: isDealing ? .infinity : width)
|
.frame(width: width)
|
||||||
.accessibilityElement(children: .ignore)
|
.accessibilityElement(children: .ignore)
|
||||||
.accessibilityLabel(String(localized: "Player hand"))
|
.accessibilityLabel(String(localized: "Player hand"))
|
||||||
.accessibilityValue(playerHandDescription + (playerIsWinner ? ", " + String(localized: "Winner") : ""))
|
.accessibilityValue(playerHandDescription + (playerIsWinner ? ", " + String(localized: "Winner") : ""))
|
||||||
@ -324,19 +280,10 @@ struct CardsDisplayArea: View {
|
|||||||
isWinner: bankerIsWinner,
|
isWinner: bankerIsWinner,
|
||||||
containerWidth: width,
|
containerWidth: width,
|
||||||
showAnimations: showAnimations,
|
showAnimations: showAnimations,
|
||||||
dealingSpeed: dealingSpeed,
|
dealingSpeed: dealingSpeed
|
||||||
revealStyle: revealStyle,
|
|
||||||
isWaitingForReveal: isWaitingForReveal,
|
|
||||||
currentRevealIndex: currentRevealIndex,
|
|
||||||
revealProgress: revealProgress,
|
|
||||||
isPlayerHand: false,
|
|
||||||
isBottom: !playerOnBottom,
|
|
||||||
isDealing: isDealing,
|
|
||||||
onReveal: onReveal,
|
|
||||||
onUpdateProgress: onUpdateProgress
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: isDealing ? .infinity : width)
|
.frame(width: width)
|
||||||
.accessibilityElement(children: .ignore)
|
.accessibilityElement(children: .ignore)
|
||||||
.accessibilityLabel(String(localized: "Banker hand"))
|
.accessibilityLabel(String(localized: "Banker hand"))
|
||||||
.accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : ""))
|
.accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : ""))
|
||||||
@ -360,16 +307,9 @@ struct CardsDisplayArea: View {
|
|||||||
isTie: false,
|
isTie: false,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
bettedOnPlayer: nil,
|
bettedOnPlayer: nil,
|
||||||
isDealing: false,
|
isDealing: false,
|
||||||
screenSize: CGSize(width: 400, height: 800),
|
screenSize: CGSize(width: 400, height: 800)
|
||||||
totalBetAmount: 0,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,16 +335,9 @@ struct CardsDisplayArea: View {
|
|||||||
isTie: false,
|
isTie: false,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
bettedOnPlayer: true,
|
bettedOnPlayer: true,
|
||||||
isDealing: true,
|
isDealing: true,
|
||||||
screenSize: CGSize(width: 400, height: 800),
|
screenSize: CGSize(width: 400, height: 800)
|
||||||
totalBetAmount: 150,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,16 +365,9 @@ struct CardsDisplayArea: View {
|
|||||||
isTie: false,
|
isTie: false,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
bettedOnPlayer: false,
|
bettedOnPlayer: false,
|
||||||
isDealing: true,
|
isDealing: true,
|
||||||
screenSize: CGSize(width: 400, height: 800),
|
screenSize: CGSize(width: 400, height: 800)
|
||||||
totalBetAmount: 250,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,33 +2,23 @@
|
|||||||
// CompactHandView.swift
|
// CompactHandView.swift
|
||||||
// Baccarat
|
// Baccarat
|
||||||
//
|
//
|
||||||
// A compact view showing cards in a horizontal row.
|
// A compact view showing cards in a horizontal row with overlap.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CasinoKit
|
import CasinoKit
|
||||||
|
|
||||||
/// A compact hand view showing cards in a row.
|
/// A compact hand view showing cards in a row with overlap.
|
||||||
struct CompactHandView: View {
|
struct CompactHandView: View {
|
||||||
let cards: [Card]
|
let cards: [Card]
|
||||||
let cardsFaceUp: [Bool]
|
let cardsFaceUp: [Bool]
|
||||||
let isWinner: Bool
|
let isWinner: Bool
|
||||||
/// Container width passed from parent for sizing
|
/// Container width passed from parent for sizing
|
||||||
let containerWidth: CGFloat
|
let containerWidth: CGFloat
|
||||||
|
/// Whether to show dealing animations
|
||||||
let showAnimations: Bool
|
let showAnimations: Bool
|
||||||
|
/// Speed multiplier for dealing animations
|
||||||
let dealingSpeed: Double
|
let dealingSpeed: Double
|
||||||
let revealStyle: RevealStyle
|
|
||||||
let isWaitingForReveal: Bool
|
|
||||||
let currentRevealIndex: Int
|
|
||||||
let revealProgress: Double
|
|
||||||
/// Whether this is the player hand (used to calculate reveal index)
|
|
||||||
let isPlayerHand: Bool
|
|
||||||
/// Whether this hand is currently displayed at the bottom of the screen (Home hand)
|
|
||||||
let isBottom: Bool
|
|
||||||
/// Whether the game is in dealing phase (vertical layout) vs betting phase (horizontal layout)
|
|
||||||
let isDealing: Bool
|
|
||||||
let onReveal: () -> Void
|
|
||||||
let onUpdateProgress: (Double) -> Void
|
|
||||||
|
|
||||||
// MARK: - Environment
|
// MARK: - Environment
|
||||||
|
|
||||||
@ -36,15 +26,15 @@ struct CompactHandView: View {
|
|||||||
|
|
||||||
// MARK: - Constants
|
// MARK: - Constants
|
||||||
|
|
||||||
|
/// Overlap ratio relative to card width (negative = overlap)
|
||||||
|
private let overlapRatio: CGFloat = -0.45
|
||||||
|
|
||||||
/// Maximum number of cards in baccarat hand
|
/// Maximum number of cards in baccarat hand
|
||||||
private let maxCards: Int = 3
|
private let maxCards: Int = 3
|
||||||
|
|
||||||
/// Placeholder spacing when no cards
|
/// Placeholder spacing when no cards
|
||||||
private let placeholderSpacing: CGFloat = Design.Spacing.small
|
private let placeholderSpacing: CGFloat = Design.Spacing.small
|
||||||
|
|
||||||
/// Spacing between cards
|
|
||||||
private let cardSpacing: CGFloat = Design.Spacing.medium
|
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
/// Whether we're on a large screen (iPad)
|
/// Whether we're on a large screen (iPad)
|
||||||
@ -57,83 +47,29 @@ struct CompactHandView: View {
|
|||||||
isLargeScreen ? 14 : 10
|
isLargeScreen ? 14 : 10
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card width for dealt cards - sized to fit 3 cards with spacing
|
/// Card width calculated from container width
|
||||||
|
/// Formula: containerWidth = cardWidth + (cardWidth + overlap) * 2
|
||||||
|
/// Where overlap = cardWidth * overlapRatio
|
||||||
private var cardWidth: CGFloat {
|
private var cardWidth: CGFloat {
|
||||||
// containerWidth = 3 * cardWidth + 2 * spacing
|
let divisor = 1 + CGFloat(maxCards - 1) * (1 + overlapRatio)
|
||||||
// cardWidth = (containerWidth - 2 * spacing) / 3
|
return containerWidth / divisor
|
||||||
return max(50, (containerWidth - 2 * cardSpacing) / CGFloat(maxCards))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card width for placeholders in horizontal betting view - uses original larger sizing
|
/// Card overlap based on card width
|
||||||
private var bettingPlaceholderWidth: CGFloat {
|
private var cardOverlap: CGFloat {
|
||||||
// Original formula: containerWidth / 2.1 (from overlap ratio of -0.45)
|
cardWidth * overlapRatio
|
||||||
containerWidth / 2.1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Card width to use for placeholders - larger in betting view, regular in dealing view
|
|
||||||
private var placeholderWidth: CGFloat {
|
|
||||||
isDealing ? cardWidth : bettingPlaceholderWidth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card height based on aspect ratio
|
/// Card height based on aspect ratio
|
||||||
private var cardHeight: CGFloat {
|
private var cardHeight: CGFloat {
|
||||||
let width = cards.isEmpty ? placeholderWidth : cardWidth
|
cardWidth * CasinoDesign.Size.cardAspectRatio
|
||||||
return width * CasinoDesign.Size.cardAspectRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The effective spacing for the card stack
|
|
||||||
private var effectiveSpacing: CGFloat {
|
|
||||||
cards.isEmpty ? placeholderSpacing : cardSpacing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
|
||||||
let availableWidth = geometry.size.width
|
|
||||||
|
|
||||||
if isLargeScreen {
|
|
||||||
// iPad: Simple centered layout - no scrolling needed
|
|
||||||
cardsContent
|
cardsContent
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
.frame(width: containerWidth, height: cardHeight)
|
||||||
.frame(width: availableWidth, alignment: .center)
|
|
||||||
} else {
|
|
||||||
// iPhone: Use ScrollView for 3rd card
|
|
||||||
ScrollViewReader { proxy in
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
cardsContent
|
|
||||||
.padding(.horizontal, Design.Spacing.medium)
|
|
||||||
.frame(minWidth: availableWidth, alignment: .center)
|
|
||||||
.id("cards_container")
|
|
||||||
}
|
|
||||||
.scrollDisabled(cards.count < 3)
|
|
||||||
.scrollClipDisabled(true) // Prevent clipping during deal animations
|
|
||||||
.background(Color.clear)
|
|
||||||
.onChange(of: cards.count) { _, newCount in
|
|
||||||
// When 3rd card is dealt, wait for animation then scroll
|
|
||||||
if newCount == 3 {
|
|
||||||
let lastIndex = isBottom ? 4 : 5
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
|
||||||
withAnimation(.spring(duration: 0.5)) {
|
|
||||||
proxy.scrollTo(lastIndex, anchor: .center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: currentRevealIndex) { _, newIndex in
|
|
||||||
// Scroll to the active card during interaction
|
|
||||||
if newIndex >= 4 {
|
|
||||||
withAnimation(.spring(duration: 0.5)) {
|
|
||||||
proxy.scrollTo(newIndex, anchor: .center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(height: cardHeight)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding(.vertical, isWinner ? Design.Spacing.small : 0)
|
|
||||||
.background(winnerBorder)
|
.background(winnerBorder)
|
||||||
.overlay(alignment: .bottom) {
|
.overlay(alignment: .bottom) {
|
||||||
winBadge
|
winBadge
|
||||||
@ -143,38 +79,20 @@ struct CompactHandView: View {
|
|||||||
// MARK: - Private Views
|
// MARK: - Private Views
|
||||||
|
|
||||||
private var cardsContent: some View {
|
private var cardsContent: some View {
|
||||||
HStack(spacing: effectiveSpacing) {
|
HStack(spacing: cards.isEmpty ? placeholderSpacing : cardOverlap) {
|
||||||
if cards.isEmpty {
|
if cards.isEmpty {
|
||||||
// Placeholders - no overlap, just side by side
|
// Placeholders - no overlap, just side by side
|
||||||
ForEach(0..<2, id: \.self) { _ in
|
ForEach(0..<2, id: \.self) { _ in
|
||||||
CardPlaceholderView(width: placeholderWidth)
|
CardPlaceholderView(width: cardWidth)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ForEach(cards.indices, id: \.self) { index in
|
ForEach(cards.indices, id: \.self) { index in
|
||||||
let isFaceUp = index < cardsFaceUp.count ? cardsFaceUp[index] : false
|
let isFaceUp = index < cardsFaceUp.count ? cardsFaceUp[index] : false
|
||||||
|
CardView(
|
||||||
// Reveal Index logic:
|
|
||||||
// Generic Slot-Based: B1=0, T1=1, B2=2, T2=3, B3=4, T3=5
|
|
||||||
let revealIndex: Int = {
|
|
||||||
if index < 2 {
|
|
||||||
return isBottom ? index * 2 : index * 2 + 1
|
|
||||||
} else {
|
|
||||||
return isBottom ? 4 : 5
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
InteractiveCardView(
|
|
||||||
card: cards[index],
|
card: cards[index],
|
||||||
isFaceUp: isFaceUp,
|
isFaceUp: isFaceUp,
|
||||||
cardWidth: cardWidth,
|
cardWidth: cardWidth
|
||||||
revealStyle: revealStyle,
|
|
||||||
isWaiting: isWaitingForReveal && currentRevealIndex == revealIndex,
|
|
||||||
progress: currentRevealIndex == revealIndex ? revealProgress : 0.0,
|
|
||||||
isBottomHand: isBottom,
|
|
||||||
onReveal: onReveal,
|
|
||||||
onUpdateProgress: onUpdateProgress
|
|
||||||
)
|
)
|
||||||
.id(revealIndex)
|
|
||||||
.zIndex(Double(index))
|
.zIndex(Double(index))
|
||||||
.transition(
|
.transition(
|
||||||
showAnimations
|
showAnimations
|
||||||
@ -224,7 +142,7 @@ struct CompactHandView: View {
|
|||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
#Preview("Empty Hand - Betting") {
|
#Preview("Empty Hand") {
|
||||||
ZStack {
|
ZStack {
|
||||||
TableBackgroundView()
|
TableBackgroundView()
|
||||||
CompactHandView(
|
CompactHandView(
|
||||||
@ -233,21 +151,12 @@ struct CompactHandView: View {
|
|||||||
isWinner: false,
|
isWinner: false,
|
||||||
containerWidth: 160,
|
containerWidth: 160,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
isPlayerHand: true,
|
|
||||||
isBottom: true,
|
|
||||||
isDealing: false,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview("Two Cards - Dealing") {
|
#Preview("Two Cards") {
|
||||||
ZStack {
|
ZStack {
|
||||||
TableBackgroundView()
|
TableBackgroundView()
|
||||||
CompactHandView(
|
CompactHandView(
|
||||||
@ -259,16 +168,7 @@ struct CompactHandView: View {
|
|||||||
isWinner: false,
|
isWinner: false,
|
||||||
containerWidth: 160,
|
containerWidth: 160,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
isPlayerHand: true,
|
|
||||||
isBottom: true,
|
|
||||||
isDealing: true,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,16 +186,7 @@ struct CompactHandView: View {
|
|||||||
isWinner: true,
|
isWinner: true,
|
||||||
containerWidth: 160,
|
containerWidth: 160,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
isPlayerHand: true,
|
|
||||||
isBottom: true,
|
|
||||||
isDealing: true,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,16 +203,7 @@ struct CompactHandView: View {
|
|||||||
isWinner: false,
|
isWinner: false,
|
||||||
containerWidth: 160,
|
containerWidth: 160,
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0
|
||||||
revealStyle: .auto,
|
|
||||||
isWaitingForReveal: false,
|
|
||||||
currentRevealIndex: 0,
|
|
||||||
revealProgress: 0,
|
|
||||||
isPlayerHand: true,
|
|
||||||
isBottom: true,
|
|
||||||
isDealing: true,
|
|
||||||
onReveal: {},
|
|
||||||
onUpdateProgress: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,204 +0,0 @@
|
|||||||
//
|
|
||||||
// InteractiveCardView.swift
|
|
||||||
// Baccarat
|
|
||||||
//
|
|
||||||
// A wrapper around card views that adds interaction logic for Tap and Squeeze reveal styles.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
struct InteractiveCardView: View {
|
|
||||||
let card: Card
|
|
||||||
let isFaceUp: Bool
|
|
||||||
let cardWidth: CGFloat
|
|
||||||
let revealStyle: RevealStyle
|
|
||||||
let isWaiting: Bool
|
|
||||||
let progress: Double
|
|
||||||
/// Whether this card is in the bottom (Home) hand - used for glow effect
|
|
||||||
let isBottomHand: Bool
|
|
||||||
let onReveal: () -> 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
|
|
||||||
/// Only show when waiting, face down, bottom hand, not auto mode, AND not currently being touched
|
|
||||||
private var showGlow: Bool {
|
|
||||||
isWaiting && !isFaceUp && isBottomHand && revealStyle != .auto && !isInteracting
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Animation state for the pulsing glow
|
|
||||||
@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 {
|
|
||||||
ZStack {
|
|
||||||
// Glow layer - completely separate from the card, sits underneath
|
|
||||||
GlowBackgroundView(
|
|
||||||
isActive: showGlow,
|
|
||||||
glowPulse: glowPulse,
|
|
||||||
cardWidth: cardWidth
|
|
||||||
)
|
|
||||||
|
|
||||||
// Card layer - completely independent, no glow modifiers
|
|
||||||
cardContent
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
if showGlow {
|
|
||||||
glowPulse = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: showGlow) { _, newValue in
|
|
||||||
glowPulse = newValue
|
|
||||||
}
|
|
||||||
.onChange(of: isWaiting) { _, newValue in
|
|
||||||
// Reset interaction state when this card is no longer waiting
|
|
||||||
if !newValue {
|
|
||||||
isInteracting = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Vertical Flip Card View
|
|
||||||
|
|
||||||
/// 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 glowPulse: Bool
|
|
||||||
let cardWidth: CGFloat
|
|
||||||
|
|
||||||
/// Corner radius matching CasinoKit's CardView
|
|
||||||
private var cornerRadius: CGFloat {
|
|
||||||
cardWidth * 0.08
|
|
||||||
}
|
|
||||||
|
|
||||||
private var cardHeight: CGFloat {
|
|
||||||
cardWidth * CasinoDesign.Size.cardAspectRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
// Outer glow strokes (largest to smallest for layered glow)
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.stroke(Color.orange.opacity(glowPulse ? 0.6 : 0.2), lineWidth: glowPulse ? 20 : 10)
|
|
||||||
.blur(radius: 10)
|
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.stroke(Color.yellow.opacity(glowPulse ? 0.8 : 0.3), lineWidth: glowPulse ? 12 : 6)
|
|
||||||
.blur(radius: 6)
|
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.stroke(Color.yellow.opacity(glowPulse ? 1.0 : 0.5), lineWidth: glowPulse ? 6 : 3)
|
|
||||||
.blur(radius: 3)
|
|
||||||
|
|
||||||
// Bright animated border (sharp, on top)
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.strokeBorder(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [.yellow, .white, .yellow],
|
|
||||||
startPoint: .topLeading,
|
|
||||||
endPoint: .bottomTrailing
|
|
||||||
),
|
|
||||||
lineWidth: glowPulse ? 4 : 3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.frame(width: cardWidth, height: cardHeight)
|
|
||||||
.scaleEffect(isActive && glowPulse ? 1.02 : 1.0)
|
|
||||||
.opacity(isActive ? 1.0 : 0.0)
|
|
||||||
.animation(.easeOut(duration: 0.15), value: isActive)
|
|
||||||
.animation(
|
|
||||||
isActive ? .easeInOut(duration: 0.7).repeatForever(autoreverses: true) : .easeOut(duration: 0.15),
|
|
||||||
value: glowPulse
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
//
|
|
||||||
// PageCurlView.swift
|
|
||||||
// Baccarat
|
|
||||||
//
|
|
||||||
// A SwiftUI wrapper around UIPageViewController to provide native iBooks-style
|
|
||||||
// page curl transitions for card reveals, treated as a single double-sided sheet.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
struct PageCurlView: View {
|
|
||||||
let card: Card
|
|
||||||
let width: CGFloat
|
|
||||||
let onReveal: () -> Void
|
|
||||||
/// Called when user starts interacting with the page curl
|
|
||||||
var onInteractionStarted: (() -> Void)? = nil
|
|
||||||
|
|
||||||
@State private var currentIndex = 0
|
|
||||||
|
|
||||||
private var height: CGFloat {
|
|
||||||
width * CasinoDesign.Size.cardAspectRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
PageCurlRepresentable(
|
|
||||||
currentIndex: $currentIndex,
|
|
||||||
onReveal: onReveal,
|
|
||||||
onInteractionStarted: onInteractionStarted,
|
|
||||||
pages: [
|
|
||||||
// Page 0: Card Back (Top side)
|
|
||||||
AnyView(
|
|
||||||
CardBackView(width: width, height: height)
|
|
||||||
.tag(0)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
),
|
|
||||||
// Page 1: Card Front (Underside/Reveal side)
|
|
||||||
AnyView(
|
|
||||||
CardFrontView(card: card, width: width, height: height)
|
|
||||||
.tag(1)
|
|
||||||
),
|
|
||||||
// Page 2: Empty space behind the card
|
|
||||||
AnyView(
|
|
||||||
Color.clear
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
.tag(2)
|
|
||||||
)
|
|
||||||
])
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
.id("page-curl-\(card.id)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct PageCurlRepresentable: UIViewControllerRepresentable {
|
|
||||||
@Binding var currentIndex: Int
|
|
||||||
let onReveal: () -> Void
|
|
||||||
let onInteractionStarted: (() -> Void)?
|
|
||||||
let pages: [AnyView]
|
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> UIPageViewController {
|
|
||||||
let pageVC = UIPageViewController(
|
|
||||||
transitionStyle: .pageCurl,
|
|
||||||
navigationOrientation: .vertical,
|
|
||||||
options: [UIPageViewController.OptionsKey.spineLocation: UIPageViewController.SpineLocation.min.rawValue]
|
|
||||||
)
|
|
||||||
|
|
||||||
pageVC.isDoubleSided = true
|
|
||||||
pageVC.dataSource = context.coordinator
|
|
||||||
pageVC.delegate = context.coordinator
|
|
||||||
|
|
||||||
let vc = UIHostingController(rootView: pages[0])
|
|
||||||
vc.view.tag = 0
|
|
||||||
vc.view.backgroundColor = .clear
|
|
||||||
pageVC.setViewControllers([vc], direction: .forward, animated: false)
|
|
||||||
|
|
||||||
// Disable internal tap gesture recognizers to ensure purely gestural interaction
|
|
||||||
for gesture in pageVC.gestureRecognizers {
|
|
||||||
if gesture is UITapGestureRecognizer {
|
|
||||||
gesture.isEnabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pageVC
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIViewController(_ pageVC: UIPageViewController, context: Context) { }
|
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
|
||||||
Coordinator(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
|
|
||||||
let parent: PageCurlRepresentable
|
|
||||||
|
|
||||||
init(_ parent: PageCurlRepresentable) {
|
|
||||||
self.parent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
func pageViewController(_ pageVC: UIPageViewController,
|
|
||||||
viewControllerBefore vc: UIViewController) -> UIViewController? {
|
|
||||||
let index = vc.view.tag
|
|
||||||
guard index > 0 else { return nil }
|
|
||||||
|
|
||||||
let prevVC = UIHostingController(rootView: parent.pages[index - 1])
|
|
||||||
prevVC.view.tag = index - 1
|
|
||||||
prevVC.view.backgroundColor = .clear
|
|
||||||
return prevVC
|
|
||||||
}
|
|
||||||
|
|
||||||
func pageViewController(_ pageVC: UIPageViewController,
|
|
||||||
viewControllerAfter vc: UIViewController) -> UIViewController? {
|
|
||||||
let index = vc.view.tag
|
|
||||||
guard index < parent.pages.count - 1 else { return nil }
|
|
||||||
|
|
||||||
let nextVC = UIHostingController(rootView: parent.pages[index + 1])
|
|
||||||
nextVC.view.tag = index + 1
|
|
||||||
nextVC.view.backgroundColor = .clear
|
|
||||||
return nextVC
|
|
||||||
}
|
|
||||||
|
|
||||||
func pageViewController(_ pageVC: UIPageViewController,
|
|
||||||
willTransitionTo pendingViewControllers: [UIViewController]) {
|
|
||||||
// User started interacting with the page curl
|
|
||||||
parent.onInteractionStarted?()
|
|
||||||
}
|
|
||||||
|
|
||||||
func pageViewController(_ pageVC: UIPageViewController,
|
|
||||||
didFinishAnimating finished: Bool,
|
|
||||||
previousViewControllers: [UIViewController],
|
|
||||||
transitionCompleted completed: Bool) {
|
|
||||||
guard completed,
|
|
||||||
let visibleVC = pageVC.viewControllers?.first else { return }
|
|
||||||
|
|
||||||
let newIndex = visibleVC.view.tag
|
|
||||||
parent.currentIndex = newIndex
|
|
||||||
if newIndex >= 1 {
|
|
||||||
parent.onReveal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,191 +0,0 @@
|
|||||||
//
|
|
||||||
// SqueezeCardView.swift
|
|
||||||
// Baccarat
|
|
||||||
//
|
|
||||||
// A premium card view that allows for a gestural "squeeze" or "peel" reveal,
|
|
||||||
// mimicking a real-world page flip from the corners.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
enum SqueezeCorner {
|
|
||||||
case bottomLeft
|
|
||||||
case bottomRight
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SqueezeCardView: View {
|
|
||||||
let card: Card
|
|
||||||
let width: CGFloat
|
|
||||||
let progress: Double // 0.0 to 1.0
|
|
||||||
let corner: SqueezeCorner
|
|
||||||
|
|
||||||
private var height: CGFloat {
|
|
||||||
width * CasinoDesign.Size.cardAspectRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
// 1. The front is always at the bottom
|
|
||||||
CardFrontView(card: card, width: width, height: height)
|
|
||||||
|
|
||||||
// 2. The back is on top, masked by the "unpeeled" area
|
|
||||||
CardBackView(width: width, height: height)
|
|
||||||
.mask(
|
|
||||||
SqueezeMask(progress: progress, corner: corner)
|
|
||||||
.fill(Style.maskFill)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 3. The "Fold" - visual affordance of the peeled corner
|
|
||||||
if progress > 0 && progress < 1.0 {
|
|
||||||
FoldEffect(progress: progress, corner: corner, width: width, height: height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: width * 0.05))
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Style {
|
|
||||||
static let maskFill = Color.black
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mask that represents the part of the card back that is NOT yet peeled.
|
|
||||||
struct SqueezeMask: Shape {
|
|
||||||
var progress: Double
|
|
||||||
var corner: SqueezeCorner
|
|
||||||
|
|
||||||
var animatableData: Double {
|
|
||||||
get { progress }
|
|
||||||
set { progress = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
func path(in rect: CGRect) -> Path {
|
|
||||||
var path = Path()
|
|
||||||
let p = CGFloat(progress)
|
|
||||||
|
|
||||||
// Initial state: Full rectangle
|
|
||||||
path.addRect(rect)
|
|
||||||
|
|
||||||
// Subtraction: The "peeled away" triangle
|
|
||||||
var peelPath = Path()
|
|
||||||
let size = rect.width * p * 2.0 // Scaling factor for the peel area
|
|
||||||
|
|
||||||
switch corner {
|
|
||||||
case .bottomRight:
|
|
||||||
peelPath.move(to: CGPoint(x: rect.width, y: rect.height))
|
|
||||||
peelPath.addLine(to: CGPoint(x: rect.width - size, y: rect.height))
|
|
||||||
peelPath.addLine(to: CGPoint(x: rect.width, y: rect.height - size))
|
|
||||||
peelPath.closeSubpath()
|
|
||||||
case .bottomLeft:
|
|
||||||
peelPath.move(to: CGPoint(x: 0, y: rect.height))
|
|
||||||
peelPath.addLine(to: CGPoint(x: size, y: rect.height))
|
|
||||||
peelPath.addLine(to: CGPoint(x: 0, y: rect.height - size))
|
|
||||||
peelPath.closeSubpath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use a "subtract" logic by drawing the full rect then "erasing" the peel
|
|
||||||
// In SwiftUI Shape, we just return the "remaining" area.
|
|
||||||
// For simplicity, we'll construct the remaining polygon.
|
|
||||||
|
|
||||||
var remainingPath = Path()
|
|
||||||
switch corner {
|
|
||||||
case .bottomRight:
|
|
||||||
remainingPath.move(to: .zero)
|
|
||||||
remainingPath.addLine(to: CGPoint(x: rect.width, y: 0))
|
|
||||||
remainingPath.addLine(to: CGPoint(x: rect.width, y: max(0, rect.height - size)))
|
|
||||||
remainingPath.addLine(to: CGPoint(x: max(0, rect.width - size), y: rect.height))
|
|
||||||
remainingPath.addLine(to: CGPoint(x: 0, y: rect.height))
|
|
||||||
remainingPath.closeSubpath()
|
|
||||||
case .bottomLeft:
|
|
||||||
remainingPath.move(to: .zero)
|
|
||||||
remainingPath.addLine(to: CGPoint(x: rect.width, y: 0))
|
|
||||||
remainingPath.addLine(to: CGPoint(x: rect.width, y: rect.height))
|
|
||||||
remainingPath.addLine(to: CGPoint(x: min(rect.width, size), y: rect.height))
|
|
||||||
remainingPath.addLine(to: CGPoint(x: 0, y: max(0, rect.height - size)))
|
|
||||||
remainingPath.closeSubpath()
|
|
||||||
}
|
|
||||||
|
|
||||||
return remainingPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The visual effect of the folded corner, including shadows and highlight.
|
|
||||||
struct FoldEffect: View {
|
|
||||||
let progress: Double
|
|
||||||
let corner: SqueezeCorner
|
|
||||||
let width: CGFloat
|
|
||||||
let height: CGFloat
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
let size = width * CGFloat(progress) * 2.0
|
|
||||||
let foldWidth = size * 0.5
|
|
||||||
|
|
||||||
ZStack {
|
|
||||||
// Fold shadow cast on the front of the card
|
|
||||||
FoldShadow(size: size, corner: corner)
|
|
||||||
.opacity(0.3 * (1.0 - progress))
|
|
||||||
|
|
||||||
// The fold itself (the "back" of the peel)
|
|
||||||
FoldShape(size: size, corner: corner)
|
|
||||||
.fill(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [.white.opacity(0.8), .gray.opacity(0.3)],
|
|
||||||
startPoint: corner == .bottomRight ? .bottomTrailing : .bottomLeading,
|
|
||||||
endPoint: corner == .bottomRight ? .topLeading : .topTrailing
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
FoldShape(size: size, corner: corner)
|
|
||||||
.stroke(Color.white.opacity(0.5), lineWidth: 0.5)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FoldShape: Shape {
|
|
||||||
let size: CGFloat
|
|
||||||
let corner: SqueezeCorner
|
|
||||||
|
|
||||||
func path(in rect: CGRect) -> Path {
|
|
||||||
var path = Path()
|
|
||||||
// A triangle representing the folded over piece
|
|
||||||
switch corner {
|
|
||||||
case .bottomRight:
|
|
||||||
path.move(to: CGPoint(x: rect.width - size/2, y: rect.height - size/2))
|
|
||||||
path.addLine(to: CGPoint(x: rect.width - size, y: rect.height))
|
|
||||||
path.addLine(to: CGPoint(x: rect.width, y: rect.height - size))
|
|
||||||
path.closeSubpath()
|
|
||||||
case .bottomLeft:
|
|
||||||
path.move(to: CGPoint(x: size/2, y: rect.height - size/2))
|
|
||||||
path.addLine(to: CGPoint(x: size, y: rect.height))
|
|
||||||
path.addLine(to: CGPoint(x: 0, y: rect.height - size))
|
|
||||||
path.closeSubpath()
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FoldShadow: View {
|
|
||||||
let size: CGFloat
|
|
||||||
let corner: SqueezeCorner
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
FoldShape(size: size * 1.1, corner: corner)
|
|
||||||
.fill(Color.black)
|
|
||||||
.blur(radius: 4)
|
|
||||||
.offset(x: corner == .bottomRight ? -2 : 2, y: -2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ZStack {
|
|
||||||
Color.CasinoTable.backgroundDark.ignoresSafeArea()
|
|
||||||
|
|
||||||
VStack(spacing: 60) {
|
|
||||||
SqueezeCardView(card: Card(suit: .spades, rank: .ace), width: 140, progress: 0.1, corner: .bottomRight)
|
|
||||||
SqueezeCardView(card: Card(suit: .hearts, rank: .king), width: 140, progress: 0.3, corner: .bottomRight)
|
|
||||||
SqueezeCardView(card: Card(suit: .diamonds, rank: .seven), width: 140, progress: 0.5, corner: .bottomLeft)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -21,11 +21,6 @@ A feature-rich Baccarat (Punto Banco) app for iOS built with SwiftUI. Experience
|
|||||||
- Animated card dealing with sound effects and haptics
|
- Animated card dealing with sound effects and haptics
|
||||||
- Automatic shoe reshuffling with burn card
|
- Automatic shoe reshuffling with burn card
|
||||||
|
|
||||||
### ⚙️ Settings & Customization
|
|
||||||
- Dealing animations toggle and adjustable dealing speed
|
|
||||||
- Card reveal style: Auto (instant flip), Tap (per-card), Squeeze (peek/drag reveal)
|
|
||||||
- Persistent preferences via iCloud sync
|
|
||||||
|
|
||||||
### 💰 Betting Options
|
### 💰 Betting Options
|
||||||
|
|
||||||
#### Main Bets
|
#### Main Bets
|
||||||
|
|||||||
@ -1,583 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 77;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
|
||||||
EAC0490D2F2173A0007F87EA /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = EAC048F72F21739F007F87EA /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = EAC048FE2F21739F007F87EA;
|
|
||||||
remoteInfo = CardSamples;
|
|
||||||
};
|
|
||||||
EAC049172F2173A0007F87EA /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = EAC048F72F21739F007F87EA /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = EAC048FE2F21739F007F87EA;
|
|
||||||
remoteInfo = CardSamples;
|
|
||||||
};
|
|
||||||
/* End PBXContainerItemProxy section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
EAC048FF2F21739F007F87EA /* CardSamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CardSamples.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
EAC0490C2F2173A0007F87EA /* CardSamplesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CardSamplesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
EAC049162F2173A0007F87EA /* CardSamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CardSamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
|
||||||
EAC049012F21739F007F87EA /* CardSamples */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = CardSamples;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
EAC0490F2F2173A0007F87EA /* CardSamplesTests */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = CardSamplesTests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
EAC049192F2173A0007F87EA /* CardSamplesUITests */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = CardSamplesUITests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
EAC048FC2F21739F007F87EA /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
EAC049092F2173A0007F87EA /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
EAC049132F2173A0007F87EA /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
EAC048F62F21739F007F87EA = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
EAC049012F21739F007F87EA /* CardSamples */,
|
|
||||||
EAC0490F2F2173A0007F87EA /* CardSamplesTests */,
|
|
||||||
EAC049192F2173A0007F87EA /* CardSamplesUITests */,
|
|
||||||
EAC049002F21739F007F87EA /* Products */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
EAC049002F21739F007F87EA /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
EAC048FF2F21739F007F87EA /* CardSamples.app */,
|
|
||||||
EAC0490C2F2173A0007F87EA /* CardSamplesTests.xctest */,
|
|
||||||
EAC049162F2173A0007F87EA /* CardSamplesUITests.xctest */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
EAC048FE2F21739F007F87EA /* CardSamples */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = EAC049202F2173A0007F87EA /* Build configuration list for PBXNativeTarget "CardSamples" */;
|
|
||||||
buildPhases = (
|
|
||||||
EAC048FB2F21739F007F87EA /* Sources */,
|
|
||||||
EAC048FC2F21739F007F87EA /* Frameworks */,
|
|
||||||
EAC048FD2F21739F007F87EA /* Resources */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
fileSystemSynchronizedGroups = (
|
|
||||||
EAC049012F21739F007F87EA /* CardSamples */,
|
|
||||||
);
|
|
||||||
name = CardSamples;
|
|
||||||
packageProductDependencies = (
|
|
||||||
);
|
|
||||||
productName = CardSamples;
|
|
||||||
productReference = EAC048FF2F21739F007F87EA /* CardSamples.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
EAC0490B2F2173A0007F87EA /* CardSamplesTests */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = EAC049232F2173A0007F87EA /* Build configuration list for PBXNativeTarget "CardSamplesTests" */;
|
|
||||||
buildPhases = (
|
|
||||||
EAC049082F2173A0007F87EA /* Sources */,
|
|
||||||
EAC049092F2173A0007F87EA /* Frameworks */,
|
|
||||||
EAC0490A2F2173A0007F87EA /* Resources */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
EAC0490E2F2173A0007F87EA /* PBXTargetDependency */,
|
|
||||||
);
|
|
||||||
fileSystemSynchronizedGroups = (
|
|
||||||
EAC0490F2F2173A0007F87EA /* CardSamplesTests */,
|
|
||||||
);
|
|
||||||
name = CardSamplesTests;
|
|
||||||
packageProductDependencies = (
|
|
||||||
);
|
|
||||||
productName = CardSamplesTests;
|
|
||||||
productReference = EAC0490C2F2173A0007F87EA /* CardSamplesTests.xctest */;
|
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
|
||||||
};
|
|
||||||
EAC049152F2173A0007F87EA /* CardSamplesUITests */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = EAC049262F2173A0007F87EA /* Build configuration list for PBXNativeTarget "CardSamplesUITests" */;
|
|
||||||
buildPhases = (
|
|
||||||
EAC049122F2173A0007F87EA /* Sources */,
|
|
||||||
EAC049132F2173A0007F87EA /* Frameworks */,
|
|
||||||
EAC049142F2173A0007F87EA /* Resources */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
EAC049182F2173A0007F87EA /* PBXTargetDependency */,
|
|
||||||
);
|
|
||||||
fileSystemSynchronizedGroups = (
|
|
||||||
EAC049192F2173A0007F87EA /* CardSamplesUITests */,
|
|
||||||
);
|
|
||||||
name = CardSamplesUITests;
|
|
||||||
packageProductDependencies = (
|
|
||||||
);
|
|
||||||
productName = CardSamplesUITests;
|
|
||||||
productReference = EAC049162F2173A0007F87EA /* CardSamplesUITests.xctest */;
|
|
||||||
productType = "com.apple.product-type.bundle.ui-testing";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
EAC048F72F21739F007F87EA /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
BuildIndependentTargetsInParallel = 1;
|
|
||||||
LastSwiftUpdateCheck = 2620;
|
|
||||||
LastUpgradeCheck = 2620;
|
|
||||||
TargetAttributes = {
|
|
||||||
EAC048FE2F21739F007F87EA = {
|
|
||||||
CreatedOnToolsVersion = 26.2;
|
|
||||||
};
|
|
||||||
EAC0490B2F2173A0007F87EA = {
|
|
||||||
CreatedOnToolsVersion = 26.2;
|
|
||||||
TestTargetID = EAC048FE2F21739F007F87EA;
|
|
||||||
};
|
|
||||||
EAC049152F2173A0007F87EA = {
|
|
||||||
CreatedOnToolsVersion = 26.2;
|
|
||||||
TestTargetID = EAC048FE2F21739F007F87EA;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = EAC048FA2F21739F007F87EA /* Build configuration list for PBXProject "CardSamples" */;
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
Base,
|
|
||||||
);
|
|
||||||
mainGroup = EAC048F62F21739F007F87EA;
|
|
||||||
minimizedProjectReferenceProxies = 1;
|
|
||||||
preferredProjectObjectVersion = 77;
|
|
||||||
productRefGroup = EAC049002F21739F007F87EA /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
EAC048FE2F21739F007F87EA /* CardSamples */,
|
|
||||||
EAC0490B2F2173A0007F87EA /* CardSamplesTests */,
|
|
||||||
EAC049152F2173A0007F87EA /* CardSamplesUITests */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
EAC048FD2F21739F007F87EA /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
EAC0490A2F2173A0007F87EA /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
EAC049142F2173A0007F87EA /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
EAC048FB2F21739F007F87EA /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
EAC049082F2173A0007F87EA /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
EAC049122F2173A0007F87EA /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
|
||||||
EAC0490E2F2173A0007F87EA /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = EAC048FE2F21739F007F87EA /* CardSamples */;
|
|
||||||
targetProxy = EAC0490D2F2173A0007F87EA /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
EAC049182F2173A0007F87EA /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = EAC048FE2F21739F007F87EA /* CardSamples */;
|
|
||||||
targetProxy = EAC049172F2173A0007F87EA /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
/* End PBXTargetDependency section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
EAC0491E2F2173A0007F87EA /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_TESTABILITY = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
EAC0491F2F2173A0007F87EA /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
EAC049212F2173A0007F87EA /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
);
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.CardSamples;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
|
||||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
EAC049222F2173A0007F87EA /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
);
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.CardSamples;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
|
||||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
EAC049242F2173A0007F87EA /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.CardSamplesTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CardSamples.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CardSamples";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
EAC049252F2173A0007F87EA /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.CardSamplesTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CardSamples.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CardSamples";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
EAC049272F2173A0007F87EA /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.CardSamplesUITests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
TEST_TARGET_NAME = CardSamples;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
EAC049282F2173A0007F87EA /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.CardSamplesUITests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
TEST_TARGET_NAME = CardSamples;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
EAC048FA2F21739F007F87EA /* Build configuration list for PBXProject "CardSamples" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
EAC0491E2F2173A0007F87EA /* Debug */,
|
|
||||||
EAC0491F2F2173A0007F87EA /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
EAC049202F2173A0007F87EA /* Build configuration list for PBXNativeTarget "CardSamples" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
EAC049212F2173A0007F87EA /* Debug */,
|
|
||||||
EAC049222F2173A0007F87EA /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
EAC049232F2173A0007F87EA /* Build configuration list for PBXNativeTarget "CardSamplesTests" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
EAC049242F2173A0007F87EA /* Debug */,
|
|
||||||
EAC049252F2173A0007F87EA /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
EAC049262F2173A0007F87EA /* Build configuration list for PBXNativeTarget "CardSamplesUITests" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
EAC049272F2173A0007F87EA /* Debug */,
|
|
||||||
EAC049282F2173A0007F87EA /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
};
|
|
||||||
rootObject = EAC048F72F21739F007F87EA /* Project object */;
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "tinted"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// CardSamplesApp.swift
|
|
||||||
// CardSamples
|
|
||||||
//
|
|
||||||
// Created by Matt Bruce on 1/21/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
@main
|
|
||||||
struct CardSamplesApp: App {
|
|
||||||
var body: some Scene {
|
|
||||||
WindowGroup {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// CardSamples
|
|
||||||
//
|
|
||||||
// Created by Matt Bruce on 1/21/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Image(systemName: "globe")
|
|
||||||
.imageScale(.large)
|
|
||||||
.foregroundStyle(.tint)
|
|
||||||
Text("Hello, world!")
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// CardSamplesTests.swift
|
|
||||||
// CardSamplesTests
|
|
||||||
//
|
|
||||||
// Created by Matt Bruce on 1/21/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Testing
|
|
||||||
@testable import CardSamples
|
|
||||||
|
|
||||||
struct CardSamplesTests {
|
|
||||||
|
|
||||||
@Test func example() async throws {
|
|
||||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
//
|
|
||||||
// CardSamplesUITests.swift
|
|
||||||
// CardSamplesUITests
|
|
||||||
//
|
|
||||||
// Created by Matt Bruce on 1/21/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
final class CardSamplesUITests: XCTestCase {
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
|
||||||
|
|
||||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
|
||||||
continueAfterFailure = false
|
|
||||||
|
|
||||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func testExample() throws {
|
|
||||||
// UI tests must launch the application that they test.
|
|
||||||
let app = XCUIApplication()
|
|
||||||
app.launch()
|
|
||||||
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func testLaunchPerformance() throws {
|
|
||||||
// This measures how long it takes to launch your application.
|
|
||||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
|
||||||
XCUIApplication().launch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// CardSamplesUITestsLaunchTests.swift
|
|
||||||
// CardSamplesUITests
|
|
||||||
//
|
|
||||||
// Created by Matt Bruce on 1/21/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
final class CardSamplesUITestsLaunchTests: XCTestCase {
|
|
||||||
|
|
||||||
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
continueAfterFailure = false
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func testLaunch() throws {
|
|
||||||
let app = XCUIApplication()
|
|
||||||
app.launch()
|
|
||||||
|
|
||||||
// Insert steps here to perform after app launch but before taking a screenshot,
|
|
||||||
// such as logging into a test account or navigating somewhere in the app
|
|
||||||
|
|
||||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
|
||||||
attachment.name = "Launch Screen"
|
|
||||||
attachment.lifetime = .keepAlways
|
|
||||||
add(attachment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
CasinoGames.xcworkspace/contents.xcworkspacedata
generated
3
CasinoGames.xcworkspace/contents.xcworkspacedata
generated
@ -1,9 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
|
||||||
location = "group:CardSamples/CardSamples.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Baccarat/Baccarat.xcodeproj">
|
location = "group:Baccarat/Baccarat.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user