diff --git a/Baccarat/Baccarat/Engine/GameState.swift b/Baccarat/Baccarat/Engine/GameState.swift index 5977277..98dfd64 100644 --- a/Baccarat/Baccarat/Engine/GameState.swift +++ b/Baccarat/Baccarat/Engine/GameState.swift @@ -114,6 +114,19 @@ final class GameState: CasinoGameState { var isAnimating: 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? + // MARK: - Computed Properties var totalBetAmount: Int { @@ -607,6 +620,41 @@ final class GameState: CasinoGameState { 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 } + + if settings.revealStyle == .tap || settings.revealStyle == .squeeze { + currentRevealIndex = index + revealProgress = 0.0 + isWaitingForReveal = true + + await withCheckedContinuation { continuation in + revealContinuation = continuation + } + } else { + // Auto Styles + try? await Task.sleep(for: flipDelay) + } + } // MARK: - Betting Actions /// Places a bet of the specified amount on the given bet type. @@ -685,13 +733,17 @@ final class GameState: CasinoGameState { // Wait for layout animation to complete before dealing cards if settings.showAnimations { - try? await Task.sleep(for: .seconds(1)) + // Increased from 1s to 1.25s for better stability on all devices + try? await Task.sleep(for: .milliseconds(1250)) } let initialCards = engine.dealInitialCards() // Check if animations are enabled if settings.showAnimations { + // Brief extra delay before the very first card deal starts + try? await Task.sleep(for: .milliseconds(250)) + // Animate dealing: P1, B1, P2, B2 for (index, card) in initialCards.enumerated() { try? await Task.sleep(for: dealDelay) @@ -708,21 +760,52 @@ final class GameState: CasinoGameState { } } - // Flip cards one by one instead of all at once - for i in 0.. Void + let onUpdateProgress: (Double) -> Void // MARK: - State @@ -195,7 +201,7 @@ struct CardsDisplayArea: View { ) .background( RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge) - .fill(Color.black.opacity(Design.Opacity.quarter)) + .fill(Color.black.opacity(isWaitingForReveal ? 0.05 : Design.Opacity.quarter)) .accessibilityHidden(true) ) .debugBorder(showDebugBorders, color: .mint, label: "HandsContainer") @@ -238,10 +244,17 @@ struct CardsDisplayArea: View { isWinner: playerIsWinner, containerWidth: width, showAnimations: showAnimations, - dealingSpeed: dealingSpeed + dealingSpeed: dealingSpeed, + revealStyle: revealStyle, + isWaitingForReveal: isWaitingForReveal, + currentRevealIndex: currentRevealIndex, + revealProgress: revealProgress, + isPlayerHand: true, + onReveal: onReveal, + onUpdateProgress: onUpdateProgress ) } - .frame(width: width) + .frame(maxWidth: isDealing ? .infinity : width) .accessibilityElement(children: .ignore) .accessibilityLabel(String(localized: "Player hand")) .accessibilityValue(playerHandDescription + (playerIsWinner ? ", " + String(localized: "Winner") : "")) @@ -280,10 +293,17 @@ struct CardsDisplayArea: View { isWinner: bankerIsWinner, containerWidth: width, showAnimations: showAnimations, - dealingSpeed: dealingSpeed + dealingSpeed: dealingSpeed, + revealStyle: revealStyle, + isWaitingForReveal: isWaitingForReveal, + currentRevealIndex: currentRevealIndex, + revealProgress: revealProgress, + isPlayerHand: false, + onReveal: onReveal, + onUpdateProgress: onUpdateProgress ) } - .frame(width: width) + .frame(maxWidth: isDealing ? .infinity : width) .accessibilityElement(children: .ignore) .accessibilityLabel(String(localized: "Banker hand")) .accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : "")) @@ -307,9 +327,15 @@ struct CardsDisplayArea: View { isTie: false, showAnimations: true, dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, bettedOnPlayer: nil, isDealing: false, - screenSize: CGSize(width: 400, height: 800) + screenSize: CGSize(width: 400, height: 800), + onReveal: {}, + onUpdateProgress: { _ in } ) } } @@ -335,9 +361,15 @@ struct CardsDisplayArea: View { isTie: false, showAnimations: true, dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, bettedOnPlayer: true, isDealing: true, - screenSize: CGSize(width: 400, height: 800) + screenSize: CGSize(width: 400, height: 800), + onReveal: {}, + onUpdateProgress: { _ in } ) } } @@ -365,9 +397,15 @@ struct CardsDisplayArea: View { isTie: false, showAnimations: true, dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, bettedOnPlayer: false, isDealing: true, - screenSize: CGSize(width: 400, height: 800) + screenSize: CGSize(width: 400, height: 800), + onReveal: {}, + onUpdateProgress: { _ in } ) } } diff --git a/Baccarat/Baccarat/Views/Table/CompactHandView.swift b/Baccarat/Baccarat/Views/Table/CompactHandView.swift index 5c2ccb6..e5dd4d4 100644 --- a/Baccarat/Baccarat/Views/Table/CompactHandView.swift +++ b/Baccarat/Baccarat/Views/Table/CompactHandView.swift @@ -15,10 +15,16 @@ struct CompactHandView: View { let isWinner: Bool /// Container width passed from parent for sizing let containerWidth: CGFloat - /// Whether to show dealing animations let showAnimations: Bool - /// Speed multiplier for dealing animations 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 + let onReveal: () -> Void + let onUpdateProgress: (Double) -> Void // MARK: - Environment @@ -34,6 +40,9 @@ struct CompactHandView: View { /// Placeholder spacing when no cards private let placeholderSpacing: CGFloat = Design.Spacing.small + + /// Spacing for interactive layout + private let spacedGap: CGFloat = Design.Spacing.medium // MARK: - Computed Properties @@ -51,7 +60,9 @@ struct CompactHandView: View { /// Formula: containerWidth = cardWidth + (cardWidth + overlap) * 2 /// Where overlap = cardWidth * overlapRatio private var cardWidth: CGFloat { - let divisor = 1 + CGFloat(maxCards - 1) * (1 + overlapRatio) + // Use a fixed overlap ratio for sizing to keep cards large + let baseOverlapRatio: CGFloat = -0.45 + let divisor = 1 + CGFloat(maxCards - 1) * (1 + baseOverlapRatio) return containerWidth / divisor } @@ -64,22 +75,87 @@ struct CompactHandView: View { private var cardHeight: CGFloat { cardWidth * CasinoDesign.Size.cardAspectRatio } + + /// Whether to use a spaced layout for interaction + private var useSpacedLayout: Bool { + revealStyle == .tap || revealStyle == .squeeze + } + + /// The effective spacing for the card stack + private var effectiveSpacing: CGFloat { + if cards.isEmpty { + return placeholderSpacing + } else if useSpacedLayout { + return spacedGap + } else { + return cardOverlap + } + } + + /// Total width required for the spaced layout + private var totalSpacedWidth: CGFloat { + let count = CGFloat(max(cards.count, 2)) + return (count * cardWidth) + ((count - 1) * effectiveSpacing) + } // MARK: - Body var body: some View { - cardsContent - .frame(width: containerWidth, height: cardHeight) - .background(winnerBorder) - .overlay(alignment: .bottom) { - winBadge + GeometryReader { geometry in + let availableWidth = geometry.size.width + + Group { + if useSpacedLayout { + // Always use ScrollView in interactive modes for stable view hierarchy + 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 = isPlayerHand ? 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) + } + } + } + } + } else { + // Regular centered layout for non-interactive modes + cardsContent + .frame(maxWidth: .infinity, alignment: .center) + } } + } + .frame(height: cardHeight) + .frame(maxWidth: .infinity) + .background(winnerBorder) + .overlay(alignment: .bottom) { + winBadge + } } // MARK: - Private Views private var cardsContent: some View { - HStack(spacing: cards.isEmpty ? placeholderSpacing : cardOverlap) { + HStack(spacing: effectiveSpacing) { if cards.isEmpty { // Placeholders - no overlap, just side by side ForEach(0..<2, id: \.self) { _ in @@ -88,11 +164,28 @@ struct CompactHandView: View { } else { ForEach(cards.indices, id: \.self) { index in let isFaceUp = index < cardsFaceUp.count ? cardsFaceUp[index] : false - CardView( + + // Reveal Index logic: + // P1=0, B1=1, P2=2, B2=3, P3=4, B3=5 + let revealIndex: Int = { + if index < 2 { + return isPlayerHand ? index * 2 : index * 2 + 1 + } else { + return isPlayerHand ? 4 : 5 + } + }() + + InteractiveCardView( card: cards[index], isFaceUp: isFaceUp, - cardWidth: cardWidth + cardWidth: cardWidth, + revealStyle: revealStyle, + isWaiting: isWaitingForReveal && currentRevealIndex == revealIndex, + progress: currentRevealIndex == revealIndex ? revealProgress : 0.0, + onReveal: onReveal, + onUpdateProgress: onUpdateProgress ) + .id(revealIndex) .zIndex(Double(index)) .transition( showAnimations @@ -151,7 +244,14 @@ struct CompactHandView: View { isWinner: false, containerWidth: 160, showAnimations: true, - dealingSpeed: 1.0 + dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, + isPlayerHand: true, + onReveal: {}, + onUpdateProgress: { _ in } ) } } @@ -168,7 +268,14 @@ struct CompactHandView: View { isWinner: false, containerWidth: 160, showAnimations: true, - dealingSpeed: 1.0 + dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, + isPlayerHand: true, + onReveal: {}, + onUpdateProgress: { _ in } ) } } @@ -186,7 +293,14 @@ struct CompactHandView: View { isWinner: true, containerWidth: 160, showAnimations: true, - dealingSpeed: 1.0 + dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, + isPlayerHand: true, + onReveal: {}, + onUpdateProgress: { _ in } ) } } @@ -203,7 +317,14 @@ struct CompactHandView: View { isWinner: false, containerWidth: 160, showAnimations: true, - dealingSpeed: 1.0 + dealingSpeed: 1.0, + revealStyle: .auto, + isWaitingForReveal: false, + currentRevealIndex: 0, + revealProgress: 0, + isPlayerHand: true, + onReveal: {}, + onUpdateProgress: { _ in } ) } } diff --git a/Baccarat/Baccarat/Views/Table/InteractiveCardView.swift b/Baccarat/Baccarat/Views/Table/InteractiveCardView.swift new file mode 100644 index 0000000..33b6a7e --- /dev/null +++ b/Baccarat/Baccarat/Views/Table/InteractiveCardView.swift @@ -0,0 +1,79 @@ +// +// 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 + let onReveal: () -> Void + let onUpdateProgress: (Double) -> Void + + var body: some View { + ZStack { + if revealStyle == .squeeze && !isFaceUp && isWaiting { + // Specialized squeeze view + SqueezeCardView(card: card, width: cardWidth, progress: progress) + .contentShape(Rectangle()) + .gesture( + DragGesture(minimumDistance: 0) + .onChanged { value in + let dx = abs(value.translation.width) + let dy = abs(value.translation.height) + let distance = sqrt(dx*dx + dy*dy) + let maxDistance = cardWidth * 1.0 + let newProgress = min(1.0, max(progress, Double(distance / maxDistance))) + onUpdateProgress(newProgress) + } + ) + .overlay { + if progress < 0.05 { + revealInstructionOverlay(text: String(localized: "PEEL")) + } + } + } else { + // Standard CardView (handles its own flip animation) + CardView(card: card, isFaceUp: isFaceUp, cardWidth: cardWidth) + .overlay { + if !isFaceUp && isWaiting && revealStyle == .tap { + revealInstructionOverlay(text: String(localized: "TAP")) + } + } + .onTapGesture { + if !isFaceUp && isWaiting && revealStyle == .tap { + onReveal() + } + } + } + } + } + + private func revealInstructionOverlay(text: String) -> some View { + Text(text) + .font(.system(size: Design.BaseFontSize.small, weight: .black, design: .rounded)) + .foregroundStyle(.white) + .padding(.horizontal, Design.Spacing.small) + .padding(.vertical, Design.Spacing.xxSmall) + .background( + Capsule() + .fill(Color.black.opacity(Design.Opacity.heavy)) + .overlay( + Capsule() + .strokeBorder(.white.opacity(Design.Opacity.medium), lineWidth: 1) + ) + ) + .scaleEffect(isWaiting ? 1.0 : 0.8) + .opacity(isWaiting ? 1.0 : 0) + .animation(.interactiveSpring().repeatForever(), value: isWaiting) + .allowsHitTesting(false) + } +} diff --git a/Baccarat/Baccarat/Views/Table/SqueezeCardView.swift b/Baccarat/Baccarat/Views/Table/SqueezeCardView.swift new file mode 100644 index 0000000..5346f01 --- /dev/null +++ b/Baccarat/Baccarat/Views/Table/SqueezeCardView.swift @@ -0,0 +1,76 @@ +// +// SqueezeCardView.swift +// Baccarat +// +// A premium card view that allows for a gestural "squeeze" or "peel" reveal. +// + +import SwiftUI +import CasinoKit + +struct SqueezeCardView: View { + let card: Card + let width: CGFloat + let progress: Double // 0.0 (Back) to 1.0 (Front) + + private var height: CGFloat { + width * CasinoDesign.Size.cardAspectRatio + } + + var body: some View { + ZStack { + // The front is always underneath + CardFrontView(card: card, width: width, height: height) + + // The back is on top and masked away as progress increases + CardBackView(width: width, height: height) + .mask( + SqueezeMask(progress: progress) + .frame(width: width, height: height) + ) + .shadow( + color: .black.opacity(Design.Opacity.medium * (1.0 - progress)), + radius: 2, + x: 2, + y: 2 + ) + } + .frame(width: width, height: height) + } +} + +/// A mask that peels away relative to progress. +struct SqueezeMask: Shape { + var progress: Double + + var animatableData: Double { + get { progress } + set { progress = newValue } + } + + func path(in rect: CGRect) -> Path { + var path = Path() + + // We want the mask (the card back) to shrink towards the top-left + // as progress (the reveal) increases. + // A simple diagonal peel: + let p = CGFloat(1.0 - progress) + + path.move(to: .zero) + path.addLine(to: CGPoint(x: rect.width * p * 1.5, y: 0)) + path.addLine(to: CGPoint(x: 0, y: rect.height * p * 1.5)) + path.closeSubpath() + + return path + } +} + +#Preview { + VStack(spacing: 40) { + SqueezeCardView(card: Card(suit: .spades, rank: .ace), width: 150, progress: 0.2) + SqueezeCardView(card: Card(suit: .hearts, rank: .king), width: 150, progress: 0.5) + SqueezeCardView(card: Card(suit: .diamonds, rank: .seven), width: 150, progress: 0.8) + } + .padding() + .background(Color.CasinoTable.backgroundDark) +} diff --git a/CardSamples/CardSamples.xcodeproj/project.pbxproj b/CardSamples/CardSamples.xcodeproj/project.pbxproj new file mode 100644 index 0000000..bec7f64 --- /dev/null +++ b/CardSamples/CardSamples.xcodeproj/project.pbxproj @@ -0,0 +1,583 @@ +// !$*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 = ""; + }; + EAC0490F2F2173A0007F87EA /* CardSamplesTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = CardSamplesTests; + sourceTree = ""; + }; + EAC049192F2173A0007F87EA /* CardSamplesUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = CardSamplesUITests; + sourceTree = ""; + }; +/* 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 = ""; + }; + EAC049002F21739F007F87EA /* Products */ = { + isa = PBXGroup; + children = ( + EAC048FF2F21739F007F87EA /* CardSamples.app */, + EAC0490C2F2173A0007F87EA /* CardSamplesTests.xctest */, + EAC049162F2173A0007F87EA /* CardSamplesUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* 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 */; +} diff --git a/CardSamples/CardSamples/Assets.xcassets/AccentColor.colorset/Contents.json b/CardSamples/CardSamples/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/CardSamples/CardSamples/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CardSamples/CardSamples/Assets.xcassets/AppIcon.appiconset/Contents.json b/CardSamples/CardSamples/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/CardSamples/CardSamples/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "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 + } +} diff --git a/CardSamples/CardSamples/Assets.xcassets/Contents.json b/CardSamples/CardSamples/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/CardSamples/CardSamples/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CardSamples/CardSamples/CardSamplesApp.swift b/CardSamples/CardSamples/CardSamplesApp.swift new file mode 100644 index 0000000..06a584a --- /dev/null +++ b/CardSamples/CardSamples/CardSamplesApp.swift @@ -0,0 +1,17 @@ +// +// CardSamplesApp.swift +// CardSamples +// +// Created by Matt Bruce on 1/21/26. +// + +import SwiftUI + +@main +struct CardSamplesApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/CardSamples/CardSamples/ContentView.swift b/CardSamples/CardSamples/ContentView.swift new file mode 100644 index 0000000..f3bc6da --- /dev/null +++ b/CardSamples/CardSamples/ContentView.swift @@ -0,0 +1,24 @@ +// +// 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() +} diff --git a/CardSamples/CardSamplesTests/CardSamplesTests.swift b/CardSamples/CardSamplesTests/CardSamplesTests.swift new file mode 100644 index 0000000..ca446db --- /dev/null +++ b/CardSamples/CardSamplesTests/CardSamplesTests.swift @@ -0,0 +1,17 @@ +// +// 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. + } + +} diff --git a/CardSamples/CardSamplesUITests/CardSamplesUITests.swift b/CardSamples/CardSamplesUITests/CardSamplesUITests.swift new file mode 100644 index 0000000..3348806 --- /dev/null +++ b/CardSamples/CardSamplesUITests/CardSamplesUITests.swift @@ -0,0 +1,41 @@ +// +// 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() + } + } +} diff --git a/CardSamples/CardSamplesUITests/CardSamplesUITestsLaunchTests.swift b/CardSamples/CardSamplesUITests/CardSamplesUITestsLaunchTests.swift new file mode 100644 index 0000000..ac03ad0 --- /dev/null +++ b/CardSamples/CardSamplesUITests/CardSamplesUITestsLaunchTests.swift @@ -0,0 +1,33 @@ +// +// 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) + } +} diff --git a/CasinoGames.xcworkspace/contents.xcworkspacedata b/CasinoGames.xcworkspace/contents.xcworkspacedata index 28d8c10..04cbe45 100644 --- a/CasinoGames.xcworkspace/contents.xcworkspacedata +++ b/CasinoGames.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + +