Compare commits

..

3 Commits

Author SHA1 Message Date
6ec98d8bcd updated launch screen, remove this if apple doesn't like it.
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2025-12-31 14:50:52 -06:00
0c5f27e33e Signed-off-by: Matt Bruce <mbrucedogs@gmail.com> 2025-12-31 14:45:41 -06:00
1ecbbbf9ee Signed-off-by: Matt Bruce <mbrucedogs@gmail.com> 2025-12-31 14:20:36 -06:00
29 changed files with 648 additions and 738 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "AppIcon-1024.png", "filename" : "AppIcon.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"

View File

@ -5,16 +5,15 @@
// Created by Matt Bruce on 12/16/25. // Created by Matt Bruce on 12/16/25.
// //
import SwiftUI import SwiftUI
import CasinoKit
@main @main
struct BaccaratApp: App { struct BaccaratApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
// #if DEBUG AppLaunchView(config: .baccarat) {
// IconGeneratorView() ContentView()
// #else }
ContentView() // your real app root
// #endif
} }
} }
} }

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/> <device id="retina6_12" orientation="portrait" appearance="dark"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -14,31 +13,8 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="♠️ ♥️" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iconsLabel">
<rect key="frame" x="146.66666666666666" y="376" width="100" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="iconHeight"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="40"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="BACCARAT" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="titleLabel">
<rect key="frame" x="97" y="434" width="199" height="41"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
<color key="textColor" red="1" green="0.80000000000000004" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/> <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" red="0.058823529411764705" green="0.12156862745098039" blue="0.2196078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.05" green="0.12" blue="0.22" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="iconsLabel" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="iconsCenterX"/>
<constraint firstItem="iconsLabel" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" constant="-50" id="iconsCenterY"/>
<constraint firstItem="titleLabel" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="titleCenterX"/>
<constraint firstItem="titleLabel" firstAttribute="top" secondItem="iconsLabel" secondAttribute="bottom" constant="8" id="titleTopToIcons"/>
</constraints>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>

View File

@ -117,6 +117,7 @@
}, },
"%lld." : { "%lld." : {
"comment" : "A numbered list item with a callout number and accompanying text. The first argument is the number of the item. The second argument is the text of the item.", "comment" : "A numbered list item with a callout number and accompanying text. The first argument is the number of the item. The second argument is the text of the item.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -140,6 +141,7 @@
}, },
"%lldpx" : { "%lldpx" : {
"comment" : "A text label displaying the size of the app icon. The argument is the size of the icon in pixels.", "comment" : "A text label displaying the size of the app icon. The argument is the size of the icon in pixels.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -186,6 +188,7 @@
}, },
"• Add to Assets.xcassets/AppIcon" : { "• Add to Assets.xcassets/AppIcon" : {
"comment" : "A step in the process of exporting app icons.", "comment" : "A step in the process of exporting app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -208,6 +211,7 @@
} }
}, },
"• Call IconRenderer.renderAppIcon(config: .baccarat)" : { "• Call IconRenderer.renderAppIcon(config: .baccarat)" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -230,6 +234,7 @@
} }
}, },
"• Run the preview in Xcode" : { "• Run the preview in Xcode" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -253,6 +258,7 @@
}, },
"• Save the resulting UIImage to files" : { "• Save the resulting UIImage to files" : {
"comment" : "A step in the process of exporting app icons.", "comment" : "A step in the process of exporting app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -276,6 +282,7 @@
}, },
"• Screenshot the 1024px icon" : { "• Screenshot the 1024px icon" : {
"comment" : "A step in the process of exporting app icons, describing how to take a screenshot of a 1024px icon.", "comment" : "A step in the process of exporting app icons, describing how to take a screenshot of a 1024px icon.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -299,6 +306,7 @@
}, },
"• Use an online tool to generate all sizes" : { "• Use an online tool to generate all sizes" : {
"comment" : "A bullet point in the \"How to Export Icons\" section, describing how to use an online tool to generate all sizes for an app icon.", "comment" : "A bullet point in the \"How to Export Icons\" section, describing how to use an online tool to generate all sizes for an app icon.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -528,6 +536,7 @@
}, },
"After generating:" : { "After generating:" : {
"comment" : "A heading for the instructions section of the icon generator view.", "comment" : "A heading for the instructions section of the icon generator view.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -551,6 +560,7 @@
}, },
"All Sizes" : { "All Sizes" : {
"comment" : "A heading that describes the various sizes of the app icon.", "comment" : "A heading that describes the various sizes of the app icon.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -597,6 +607,7 @@
}, },
"Alternative: Use an online tool" : { "Alternative: Use an online tool" : {
"comment" : "A section header that suggests using an online tool to generate app icon sizes.", "comment" : "A section header that suggests using an online tool to generate app icon sizes.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -666,6 +677,7 @@
}, },
"App Icon" : { "App Icon" : {
"comment" : "A label displayed above the preview of the app icon.", "comment" : "A label displayed above the preview of the app icon.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -689,6 +701,7 @@
}, },
"App Icon Preview" : { "App Icon Preview" : {
"comment" : "A header describing the preview of the app icon.", "comment" : "A header describing the preview of the app icon.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -1240,6 +1253,7 @@
}, },
"Blackjack" : { "Blackjack" : {
"comment" : "The name of a blackjack game.", "comment" : "The name of a blackjack game.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -1994,6 +2008,7 @@
}, },
"Generate & Save Icons" : { "Generate & Save Icons" : {
"comment" : "A button label that triggers the generation of app icons.", "comment" : "A button label that triggers the generation of app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2017,6 +2032,7 @@
}, },
"Generated Icons:" : { "Generated Icons:" : {
"comment" : "A label displayed above the list of generated icon filenames.", "comment" : "A label displayed above the list of generated icon filenames.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2040,6 +2056,7 @@
}, },
"Generating..." : { "Generating..." : {
"comment" : "A text that appears while generating icons.", "comment" : "A text that appears while generating icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2247,6 +2264,7 @@
}, },
"How to Export Icons" : { "How to Export Icons" : {
"comment" : "A section header explaining how to export app icons.", "comment" : "A section header explaining how to export app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2337,6 +2355,7 @@
}, },
"Icon" : { "Icon" : {
"comment" : "The title for the tab that displays the app icon preview.", "comment" : "The title for the tab that displays the app icon preview.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2360,6 +2379,7 @@
}, },
"Icon Generator" : { "Icon Generator" : {
"comment" : "The title of the Icon Generator view.", "comment" : "The title of the Icon Generator view.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2520,6 +2540,7 @@
}, },
"Launch" : { "Launch" : {
"comment" : "A tab label for the launch screen preview.", "comment" : "A tab label for the launch screen preview.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2929,6 +2950,7 @@
}, },
"Option 1: Screenshot from Preview" : { "Option 1: Screenshot from Preview" : {
"comment" : "A description of one method for exporting app icons.", "comment" : "A description of one method for exporting app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2952,6 +2974,7 @@
}, },
"Option 2: Use IconRenderer in Code" : { "Option 2: Use IconRenderer in Code" : {
"comment" : "A description of how to use the `IconRenderer` in code to generate app icons.", "comment" : "A description of how to use the `IconRenderer` in code to generate app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2975,6 +2998,7 @@
}, },
"Other Game Icons" : { "Other Game Icons" : {
"comment" : "A label displayed above a section of the BrandingPreviewView", "comment" : "A label displayed above a section of the BrandingPreviewView",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2998,6 +3022,7 @@
}, },
"Others" : { "Others" : {
"comment" : "The tab label for the section that previews branding assets for other games.", "comment" : "The tab label for the section that previews branding assets for other games.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -3411,6 +3436,7 @@
}, },
"Poker" : { "Poker" : {
"comment" : "The name of a poker game.", "comment" : "The name of a poker game.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -3640,6 +3666,7 @@
}, },
"Roulette" : { "Roulette" : {
"comment" : "The name of a roulette game.", "comment" : "The name of a roulette game.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -4326,6 +4353,7 @@
}, },
"These show how the same pattern works for other games" : { "These show how the same pattern works for other games" : {
"comment" : "A description below the section of the view that previews icons for other games.", "comment" : "A description below the section of the view that previews icons for other games.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -4691,6 +4719,7 @@
}, },
"Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically." : { "Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically." : {
"comment" : "A description of an alternative method for generating app icons.", "comment" : "A description of an alternative method for generating app icons.",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {

View File

@ -1,40 +0,0 @@
//
// BrandingConfig+Baccarat.swift
// Baccarat
//
// Baccarat-specific branding configurations for AppIconView and LaunchScreenView.
//
import SwiftUI
import CasinoKit
extension AppIconConfig {
/// Baccarat game icon configuration.
static let baccarat = AppIconConfig(
title: "BACCARAT",
iconSymbol: "suit.spade.fill"
)
}
extension LaunchScreenConfig {
/// Baccarat game launch screen configuration.
static let baccarat = LaunchScreenConfig(
title: "BACCARAT",
tagline: "The Classic Casino Card Game",
iconSymbols: ["suit.spade.fill", "suit.heart.fill"]
)
}
// MARK: - For Development Preview Comparison
extension AppIconConfig {
/// Blackjack config for side-by-side comparison in dev previews.
static let blackjack = AppIconConfig(
title: "BLACKJACK",
subtitle: "21",
iconSymbol: "suit.club.fill",
primaryColor: Color(red: 0.05, green: 0.35, blue: 0.15),
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
)
}

View File

@ -0,0 +1,30 @@
//
// BrandingConfig.swift
// Baccarat
//
// App-specific branding configurations for icons and launch screens.
//
import SwiftUI
import CasinoKit
// MARK: - App Icon Configuration
extension AppIconConfig {
/// Baccarat app icon configuration.
static let baccarat = AppIconConfig(
title: "BACCARAT",
iconSymbol: "suit.spade.fill"
)
}
// MARK: - Launch Screen Configuration
extension LaunchScreenConfig {
/// Baccarat launch screen configuration.
static let baccarat = LaunchScreenConfig(
title: "BACCARAT",
tagline: "The Classic Casino Card Game",
iconSymbols: ["suit.spade.fill", "suit.heart.fill"]
)
}

View File

@ -1,130 +0,0 @@
//
// BrandingPreviewView.swift
// Baccarat
//
// Development view for previewing and exporting app icons and launch screens.
// Access this during development to generate icon assets.
//
import SwiftUI
import CasinoKit
/// Preview view for app branding assets.
/// Use this during development to preview and export icons.
struct BrandingPreviewView: View {
var body: some View {
TabView {
// App Icon Preview
ScrollView {
VStack(spacing: 32) {
Text("App Icon")
.font(.largeTitle.bold())
AppIconView(config: .baccarat, size: 300)
.clipShape(.rect(cornerRadius: 300 * 0.22))
.shadow(radius: 20)
Text("All Sizes")
.font(.title2.bold())
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
VStack {
AppIconView(config: .baccarat, size: CGFloat(size))
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
Text("\(size)px")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
instructionsSection
}
.padding()
}
.tabItem {
Label("Icon", systemImage: "app.fill")
}
// Launch Screen Preview
LaunchScreenView(config: .baccarat)
.tabItem {
Label("Launch", systemImage: "rectangle.portrait.fill")
}
// Other Games Preview
ScrollView {
VStack(spacing: 32) {
Text("Other Game Icons")
.font(.largeTitle.bold())
HStack(spacing: 20) {
VStack {
AppIconView(config: .blackjack, size: 150)
.clipShape(.rect(cornerRadius: 150 * 0.22))
Text("Blackjack")
.font(.caption)
}
VStack {
AppIconView(config: .poker, size: 150)
.clipShape(.rect(cornerRadius: 150 * 0.22))
Text("Poker")
.font(.caption)
}
VStack {
AppIconView(config: .roulette, size: 150)
.clipShape(.rect(cornerRadius: 150 * 0.22))
Text("Roulette")
.font(.caption)
}
}
Text("These show how the same pattern works for other games")
.font(.callout)
.foregroundStyle(.secondary)
}
.padding()
}
.tabItem {
Label("Others", systemImage: "square.grid.2x2")
}
}
}
private var instructionsSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("How to Export Icons")
.font(.headline)
VStack(alignment: .leading, spacing: 8) {
Text("Option 1: Screenshot from Preview")
.font(.subheadline.bold())
Text("• Run the preview in Xcode")
Text("• Screenshot the 1024px icon")
Text("• Use an online tool to generate all sizes")
}
VStack(alignment: .leading, spacing: 8) {
Text("Option 2: Use IconRenderer in Code")
.font(.subheadline.bold())
Text("• Call IconRenderer.renderAppIcon(config: .baccarat)")
Text("• Save the resulting UIImage to files")
Text("• Add to Assets.xcassets/AppIcon")
}
}
.font(.callout)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
}
}
#Preview {
BrandingPreviewView()
}

View File

@ -350,6 +350,19 @@ struct SettingsView: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.top, Design.Spacing.xSmall) .padding(.top, Design.Spacing.xSmall)
//
// DEBUG SECTION - Only visible in DEBUG builds
//
#if DEBUG
SheetSection(title: "DEBUG", icon: "ant.fill") {
BrandingDebugRows(
iconConfig: .baccarat,
launchConfig: .baccarat,
appName: "Baccarat"
)
}
#endif
// 11. App Version // 11. App Version
Text(appVersionString) Text(appVersionString)
.font(.system(size: Design.BaseFontSize.callout)) .font(.system(size: Design.BaseFontSize.callout))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "AppIcon-1024.png", "filename" : "AppIcon.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"

View File

@ -18,12 +18,9 @@ struct BlackjackApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
// #if DEBUG AppLaunchView(config: .blackjack) {
// IconGeneratorView() ContentView()
// #else }
ContentView() // your real app root
// #endif
} }
} }
} }

View File

@ -1,49 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24127" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/> <device id="retina6_12" orientation="portrait" appearance="dark"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E"> <scene sceneID="EHf-IW-A2E">
<objects> <objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController"> <viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="🃏 ♠️" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iconsLabel">
<rect key="frame" x="147.33333333333334" y="351" width="98.666666666666657" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="iconHeight"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="40"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="BLACKJACK" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="titleLabel">
<rect key="frame" x="81" y="409" width="215" height="41"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
<color key="textColor" red="1" green="0.84313725490196079" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/> <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" red="0.050980392156862744" green="0.34901960784313724" blue="0.14901960784313725" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.05" green="0.35" blue="0.15" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="iconsLabel" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="iconsCenterX"/>
<constraint firstItem="iconsLabel" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" constant="-50" id="iconsCenterY"/>
<constraint firstItem="titleLabel" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="titleCenterX"/>
<constraint firstItem="titleLabel" firstAttribute="top" secondItem="iconsLabel" secondAttribute="bottom" constant="8" id="titleTopToIcons"/>
</constraints>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="52.671755725190835" y="374.64788732394368"/> <point key="canvasLocation" x="53" y="375"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@ -119,6 +119,7 @@
}, },
"%lld." : { "%lld." : {
"comment" : "A numbered list item with a callout number and accompanying text. The first argument is the number of the item. The second argument is the text of the item.", "comment" : "A numbered list item with a callout number and accompanying text. The first argument is the number of the item. The second argument is the text of the item.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -143,6 +144,7 @@
}, },
"%lldpx" : { "%lldpx" : {
"comment" : "A text label displaying the size of the app icon. The argument is the size of the icon in pixels.", "comment" : "A text label displaying the size of the app icon. The argument is the size of the icon in pixels.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -191,6 +193,7 @@
}, },
"• Add to Assets.xcassets/AppIcon" : { "• Add to Assets.xcassets/AppIcon" : {
"comment" : "A bullet point describing how to add an app icon to Xcode's asset catalog.", "comment" : "A bullet point describing how to add an app icon to Xcode's asset catalog.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -215,6 +218,7 @@
}, },
"• Call IconRenderer.renderAppIcon(config: .blackjack)" : { "• Call IconRenderer.renderAppIcon(config: .blackjack)" : {
"comment" : "A bullet point in the \"Option 2: Use IconRenderer in Code\" section of the BrandingPreviewView.", "comment" : "A bullet point in the \"Option 2: Use IconRenderer in Code\" section of the BrandingPreviewView.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -238,6 +242,7 @@
} }
}, },
"• Run the preview in Xcode" : { "• Run the preview in Xcode" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -261,6 +266,7 @@
}, },
"• Save the resulting UIImage to files" : { "• Save the resulting UIImage to files" : {
"comment" : "A step in the process of exporting app icons.", "comment" : "A step in the process of exporting app icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -284,6 +290,7 @@
} }
}, },
"• Screenshot the 1024px icon" : { "• Screenshot the 1024px icon" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -307,6 +314,7 @@
}, },
"• Use an online tool to generate all sizes" : { "• Use an online tool to generate all sizes" : {
"comment" : "A step in the process of exporting app icons.", "comment" : "A step in the process of exporting app icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -828,6 +836,7 @@
}, },
"After generating:" : { "After generating:" : {
"comment" : "A heading for instructions on how to use the IconGeneratorView.", "comment" : "A heading for instructions on how to use the IconGeneratorView.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -852,6 +861,7 @@
}, },
"All Sizes" : { "All Sizes" : {
"comment" : "A heading that describes the various sizes of the app icon.", "comment" : "A heading that describes the various sizes of the app icon.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -943,6 +953,7 @@
}, },
"Alternative: Use an online tool" : { "Alternative: Use an online tool" : {
"comment" : "A section header that suggests using an online tool to generate app icons.", "comment" : "A section header that suggests using an online tool to generate app icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1013,6 +1024,7 @@
}, },
"App Icon" : { "App Icon" : {
"comment" : "A label displayed above the preview of the app icon.", "comment" : "A label displayed above the preview of the app icon.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1037,6 +1049,7 @@
}, },
"App Icon Preview" : { "App Icon Preview" : {
"comment" : "A title for the preview of the app icon.", "comment" : "A title for the preview of the app icon.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1105,6 +1118,7 @@
}, },
"Baccarat" : { "Baccarat" : {
"comment" : "The name of a casino game.", "comment" : "The name of a casino game.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -2385,9 +2399,6 @@
} }
} }
} }
},
"Deal Splittable Pair (8s)" : {
}, },
"DEALER" : { "DEALER" : {
"localizations" : { "localizations" : {
@ -3589,6 +3600,7 @@
}, },
"Generate & Save Icons" : { "Generate & Save Icons" : {
"comment" : "A button label that triggers icon generation and saving.", "comment" : "A button label that triggers icon generation and saving.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -3613,6 +3625,7 @@
}, },
"Generated Icons:" : { "Generated Icons:" : {
"comment" : "A label describing the list of icons that have been successfully generated.", "comment" : "A label describing the list of icons that have been successfully generated.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -3637,6 +3650,7 @@
}, },
"Generating..." : { "Generating..." : {
"comment" : "A label indicating that the app is currently generating icons.", "comment" : "A label indicating that the app is currently generating icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -3955,6 +3969,7 @@
}, },
"How to Export Icons" : { "How to Export Icons" : {
"comment" : "A section header explaining how to export app icons.", "comment" : "A section header explaining how to export app icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -4045,6 +4060,7 @@
}, },
"Icon" : { "Icon" : {
"comment" : "The label for the tab item representing the app icon preview.", "comment" : "The label for the tab item representing the app icon preview.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -4069,6 +4085,7 @@
}, },
"Icon Generator" : { "Icon Generator" : {
"comment" : "The title of the Icon Generator view.", "comment" : "The title of the Icon Generator view.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -4434,6 +4451,7 @@
}, },
"Launch" : { "Launch" : {
"comment" : "A tab in the BrandingPreviewView that links to the launch screen preview.", "comment" : "A tab in the BrandingPreviewView that links to the launch screen preview.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -5093,6 +5111,7 @@
}, },
"Option 1: Screenshot from Preview" : { "Option 1: Screenshot from Preview" : {
"comment" : "A description of one method for exporting app icons.", "comment" : "A description of one method for exporting app icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -5117,6 +5136,7 @@
}, },
"Option 2: Use IconRenderer in Code" : { "Option 2: Use IconRenderer in Code" : {
"comment" : "A subheading within the instructions section of the BrandingPreviewView.", "comment" : "A subheading within the instructions section of the BrandingPreviewView.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -5163,6 +5183,7 @@
}, },
"Other Game Icons" : { "Other Game Icons" : {
"comment" : "A label displayed above a section of the BrandingPreviewView that shows icons for other games.", "comment" : "A label displayed above a section of the BrandingPreviewView that shows icons for other games.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -5187,6 +5208,7 @@
}, },
"Others" : { "Others" : {
"comment" : "A tab label for the section displaying icons for other games.", "comment" : "A tab label for the section displaying icons for other games.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -5464,6 +5486,7 @@
} }
}, },
"Poker" : { "Poker" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -5781,6 +5804,7 @@
}, },
"Roulette" : { "Roulette" : {
"comment" : "The name of a roulette card.", "comment" : "The name of a roulette card.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -7195,6 +7219,7 @@
}, },
"These show how the same pattern works for other games" : { "These show how the same pattern works for other games" : {
"comment" : "A description below the section of the view that previews icons for other games.", "comment" : "A description below the section of the view that previews icons for other games.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -7471,6 +7496,7 @@
}, },
"Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically." : { "Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically." : {
"comment" : "A description of an alternative method for generating app icons.", "comment" : "A description of an alternative method for generating app icons.",
"extractionState" : "stale",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
"localizations" : { "localizations" : {
"en" : { "en" : {

View File

@ -1,15 +1,17 @@
// //
// BrandingConfig+Blackjack.swift // BrandingConfig.swift
// Blackjack // Blackjack
// //
// Blackjack-specific branding configurations for AppIconView and LaunchScreenView. // App-specific branding configurations for icons and launch screens.
// //
import SwiftUI import SwiftUI
import CasinoKit import CasinoKit
// MARK: - App Icon Configuration
extension AppIconConfig { extension AppIconConfig {
/// Blackjack game icon configuration. /// Blackjack app icon configuration.
static let blackjack = AppIconConfig( static let blackjack = AppIconConfig(
title: "BLACKJACK", title: "BLACKJACK",
subtitle: "21", subtitle: "21",
@ -19,8 +21,10 @@ extension AppIconConfig {
) )
} }
// MARK: - Launch Screen Configuration
extension LaunchScreenConfig { extension LaunchScreenConfig {
/// Blackjack game launch screen configuration. /// Blackjack launch screen configuration.
static let blackjack = LaunchScreenConfig( static let blackjack = LaunchScreenConfig(
title: "BLACKJACK", title: "BLACKJACK",
subtitle: "21", subtitle: "21",
@ -30,14 +34,3 @@ extension LaunchScreenConfig {
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1) secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
) )
} }
// MARK: - For Development Preview Comparison
extension AppIconConfig {
/// Baccarat config for side-by-side comparison in dev previews.
static let baccarat = AppIconConfig(
title: "BACCARAT",
iconSymbol: "suit.spade.fill"
)
}

View File

@ -1,130 +0,0 @@
//
// BrandingPreviewView.swift
// Blackjack
//
// Development view for previewing and exporting app icons and launch screens.
// Access this during development to generate icon assets.
//
import SwiftUI
import CasinoKit
/// Preview view for app branding assets.
/// Use this during development to preview and export icons.
struct BrandingPreviewView: View {
var body: some View {
TabView {
// App Icon Preview
ScrollView {
VStack(spacing: Design.Spacing.xxxLarge) {
Text("App Icon")
.font(.largeTitle.bold())
AppIconView(config: .blackjack, size: 300)
.clipShape(.rect(cornerRadius: 300 * 0.22))
.shadow(radius: Design.Shadow.radiusXLarge)
Text("All Sizes")
.font(.title2.bold())
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: Design.Spacing.xLarge) {
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
VStack {
AppIconView(config: .blackjack, size: CGFloat(size))
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
Text("\(size)px")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
instructionsSection
}
.padding()
}
.tabItem {
Label("Icon", systemImage: "app.fill")
}
// Launch Screen Preview
LaunchScreenView(config: .blackjack)
.tabItem {
Label("Launch", systemImage: "rectangle.portrait.fill")
}
// Other Games Preview
ScrollView {
VStack(spacing: Design.Spacing.xxxLarge) {
Text("Other Game Icons")
.font(.largeTitle.bold())
HStack(spacing: Design.Spacing.xLarge) {
VStack {
AppIconView(config: .baccarat, size: 150)
.clipShape(.rect(cornerRadius: 150 * 0.22))
Text("Baccarat")
.font(.caption)
}
VStack {
AppIconView(config: .poker, size: 150)
.clipShape(.rect(cornerRadius: 150 * 0.22))
Text("Poker")
.font(.caption)
}
VStack {
AppIconView(config: .roulette, size: 150)
.clipShape(.rect(cornerRadius: 150 * 0.22))
Text("Roulette")
.font(.caption)
}
}
Text("These show how the same pattern works for other games")
.font(.callout)
.foregroundStyle(.secondary)
}
.padding()
}
.tabItem {
Label("Others", systemImage: "square.grid.2x2")
}
}
}
private var instructionsSection: some View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text("How to Export Icons")
.font(.headline)
VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text("Option 1: Screenshot from Preview")
.font(.subheadline.bold())
Text("• Run the preview in Xcode")
Text("• Screenshot the 1024px icon")
Text("• Use an online tool to generate all sizes")
}
VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text("Option 2: Use IconRenderer in Code")
.font(.subheadline.bold())
Text("• Call IconRenderer.renderAppIcon(config: .blackjack)")
Text("• Save the resulting UIImage to files")
Text("• Add to Assets.xcassets/AppIcon")
}
}
.font(.callout)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(Design.Opacity.subtle))
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
}
}
#Preview {
BrandingPreviewView()
}

View File

@ -1,196 +0,0 @@
//
// IconGeneratorView.swift
// Blackjack
//
// Development tool to generate and export app icon images.
// Run this view, tap the button, then find the icons in the Files app.
//
import SwiftUI
import CasinoKit
/// A development view that generates and saves app icon images.
/// After running, find the icons in Files app On My iPhone Blackjack
struct IconGeneratorView: View {
@State private var status: String = "Tap the button to generate icons"
@State private var isGenerating = false
@State private var generatedIcons: [GeneratedIcon] = []
// Development view: hardcoded sizes acceptable
private let previewSize: CGFloat = 200
private let iconCornerRadiusRatio: CGFloat = 0.22
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: Design.Spacing.xxLarge) {
// Preview
AppIconView(config: .blackjack, size: previewSize)
.clipShape(.rect(cornerRadius: previewSize * iconCornerRadiusRatio))
.shadow(radius: 10)
Text("App Icon Preview")
.font(.headline)
// Generate button
Button {
Task {
await generateIcons()
}
} label: {
HStack {
if isGenerating {
ProgressView()
.tint(.white)
}
Text(isGenerating ? "Generating..." : "Generate & Save Icons")
}
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(isGenerating ? Color.gray : Color.blue)
.clipShape(.rect(cornerRadius: 12))
}
.disabled(isGenerating)
.padding(.horizontal)
// Status
Text(status)
.font(.callout)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal)
// Generated icons
if !generatedIcons.isEmpty {
VStack(alignment: .leading, spacing: 12) {
Text("Generated Icons:")
.font(.headline)
ForEach(generatedIcons) { icon in
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
Text(icon.filename)
.font(.caption.monospaced())
Spacer()
Text("\(Int(icon.size))px")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.padding()
.background(Color.green.opacity(Design.Opacity.subtle))
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
.padding(.horizontal)
}
// Instructions
instructionsSection
}
.padding(.vertical)
}
.navigationTitle("Icon Generator")
}
}
private var instructionsSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("After generating:")
.font(.headline)
VStack(alignment: .leading, spacing: Design.Spacing.small) {
instructionRow(number: 1, text: "Open Files app on your device/simulator")
instructionRow(number: 2, text: "Navigate to: On My iPhone → Blackjack")
instructionRow(number: 3, text: "Find the AppIcon-1024.png file")
instructionRow(number: 4, text: "AirDrop or share to your Mac")
instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon")
}
Divider()
Text("Alternative: Use an online tool")
.font(.subheadline.bold())
Text("Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically.")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
.background(Color.gray.opacity(Design.Opacity.subtle))
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
.padding(.horizontal)
}
private func instructionRow(number: Int, text: String) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(number).")
.font(.callout.bold())
.foregroundStyle(.blue)
Text(text)
.font(.callout)
}
}
@MainActor
private func generateIcons() async {
isGenerating = true
generatedIcons = []
status = "Generating icons..."
let sizes: [(CGFloat, String)] = [
(1024, "AppIcon-1024"),
(180, "AppIcon-180"),
(120, "AppIcon-120"),
(87, "AppIcon-87"),
(80, "AppIcon-80"),
(60, "AppIcon-60"),
(40, "AppIcon-40")
]
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
for (size, name) in sizes {
// Render the icon
let view = AppIconView(config: .blackjack, size: size)
let renderer = ImageRenderer(content: view)
renderer.scale = 1.0
if let uiImage = renderer.uiImage,
let data = uiImage.pngData() {
let filename = "\(name).png"
let fileURL = documentsPath.appending(path: filename)
do {
try data.write(to: fileURL)
generatedIcons.append(GeneratedIcon(filename: filename, size: size))
} catch {
status = "Error saving \(filename): \(error.localizedDescription)"
}
}
// Small delay for UI feedback
try? await Task.sleep(for: .milliseconds(100))
}
if generatedIcons.count == sizes.count {
status = "✅ All icons saved to Documents folder!\nOpen Files app to find them."
} else {
status = "⚠️ Some icons failed to generate"
}
isGenerating = false
}
}
struct GeneratedIcon: Identifiable {
let id = UUID()
let filename: String
let size: CGFloat
}
#Preview {
IconGeneratorView()
}

View File

@ -435,28 +435,27 @@ struct SettingsView: View {
// calls the corresponding debug function in GameState. // calls the corresponding debug function in GameState.
// //
#if DEBUG #if DEBUG
if let state = gameState { SheetSection(title: "DEBUG", icon: "ant.fill") {
SheetSection(title: "DEBUG", icon: "ant.fill") { // Game-specific debug actions
if let state = gameState {
// Split Testing - deals a pair of 8s // Split Testing - deals a pair of 8s
Button { DebugActionRow(
title: "Deal Splittable Pair (8s)",
icon: "rectangle.split.2x1"
) {
triggerDebugDeal(state: state) triggerDebugDeal(state: state)
} label: {
HStack {
Text("Deal Splittable Pair (8s)")
.font(.system(size: Design.BaseFontSize.body, weight: .medium))
.foregroundStyle(.orange)
Spacer()
Image(systemName: "rectangle.split.2x1")
.font(.system(size: Design.BaseFontSize.large))
.foregroundStyle(.orange)
}
.frame(minHeight: CasinoDesign.Size.actionRowMinHeight)
} }
// Add new debug buttons here: Divider()
// Divider().background(Color.orange.opacity(Design.Opacity.hint)) .background(Color.orange.opacity(Design.Opacity.hint))
// Button { triggerDebugBlackjack(state: state) } label: { ... }
} }
// Branding tools (from CasinoKit)
BrandingDebugRows(
iconConfig: .blackjack,
launchConfig: .blackjack,
appName: "Blackjack"
)
} }
#endif #endif

View File

@ -72,12 +72,17 @@
// - SelectableRow (card-like selectable picker row) // - SelectableRow (card-like selectable picker row)
// - SelectionIndicator (checkmark circle) // - SelectionIndicator (checkmark circle)
// - BadgePill (capsule badge for values) // - BadgePill (capsule badge for values)
// - DebugNavigationRow (debug row with navigation)
// - DebugActionRow (debug row with action)
// - BrandingDebugRows (icon generator + branding preview rows)
// MARK: - Branding // MARK: - Branding
// - AppIconView, AppIconConfig // - AppIconView, AppIconConfig
// - LaunchScreenView, LaunchScreenConfig, StaticLaunchScreenView // - LaunchScreenView, LaunchScreenConfig, StaticLaunchScreenView
// - IconRenderer, IconExportView // - IconRenderer, IconExportView
// - IconGeneratorView, GeneratedIconInfo (development tool for exporting icons)
// - BrandingPreviewView (development tool for previewing all branding assets)
// MARK: - Theme // MARK: - Theme
// - CasinoTheme (protocol) // - CasinoTheme (protocol)

View File

@ -93,6 +93,10 @@
} }
} }
}, },
"%lld." : {
"comment" : "A numbered list item with a callout number and accompanying text. The first argument is the number of the item. The second argument is the text describing the item.",
"isCommentAutoGenerated" : true
},
"%lld%%" : { "%lld%%" : {
"comment" : "A text displaying the current volume percentage. The argument is a value between 0.0 (no volume) and 1.0 (full volume).", "comment" : "A text displaying the current volume percentage. The argument is a value between 0.0 (no volume) and 1.0 (full volume).",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@ -141,6 +145,10 @@
} }
} }
}, },
"%lldpx" : {
"comment" : "A text label displaying the size of an app icon. The argument is the size of the icon in pixels.",
"isCommentAutoGenerated" : true
},
"•" : { "•" : {
"comment" : "A bullet point indicator.", "comment" : "A bullet point indicator.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@ -316,6 +324,10 @@
} }
} }
}, },
"1024 × 1024px" : {
"comment" : "A description of the size of the app icon.",
"isCommentAutoGenerated" : true
},
"Ace" : { "Ace" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -365,6 +377,10 @@
} }
} }
}, },
"After generating:" : {
"comment" : "A heading describing the steps to take after generating app icons.",
"isCommentAutoGenerated" : true
},
"All game data is stored:" : { "All game data is stored:" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -387,6 +403,10 @@
} }
} }
}, },
"App Icon" : {
"comment" : "The title of the section displaying the app icon preview.",
"isCommentAutoGenerated" : true
},
"App Icon Preview" : { "App Icon Preview" : {
"comment" : "A title for the preview section of the icon export view.", "comment" : "A title for the preview section of the icon export view.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@ -1099,6 +1119,14 @@
} }
} }
}, },
"Generate & Save Icon" : {
"comment" : "A button label that triggers icon generation and saving.",
"isCommentAutoGenerated" : true
},
"Generating..." : {
"comment" : "A button label indicating that the app is currently generating icons.",
"isCommentAutoGenerated" : true
},
"Global" : { "Global" : {
"comment" : "Title for the \"Global\" tab in the statistics view.", "comment" : "Title for the \"Global\" tab in the statistics view.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
@ -1202,6 +1230,14 @@
} }
} }
}, },
"Icon" : {
"comment" : "The label for the tab that displays the app icon preview.",
"isCommentAutoGenerated" : true
},
"Icon Generator" : {
"comment" : "The title of the icon generator view.",
"isCommentAutoGenerated" : true
},
"If you choose to enable iCloud sync:" : { "If you choose to enable iCloud sync:" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1400,6 +1436,10 @@
} }
} }
}, },
"Launch" : {
"comment" : "A tab label for the launch screen preview.",
"isCommentAutoGenerated" : true
},
"Locally on your device using iOS standard storage" : { "Locally on your device using iOS standard storage" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1578,6 +1618,10 @@
}, },
"No Session History" : { "No Session History" : {
},
"Note: iOS uses a single 1024px icon" : {
"comment" : "A note explaining that iOS uses a single 1024px icon.",
"isCommentAutoGenerated" : true
}, },
"Our apps do not integrate with third-party services that collect user data. We do not share any information with third parties." : { "Our apps do not integrate with third-party services that collect user data. We do not share any information with third parties." : {
"localizations" : { "localizations" : {
@ -2106,6 +2150,10 @@
} }
} }
}, },
"To Export" : {
"comment" : "A section header explaining how to export branding assets.",
"isCommentAutoGenerated" : true
},
"TOTAL" : { "TOTAL" : {
"comment" : "A label displayed alongside the total winnings in the result banner.", "comment" : "A label displayed alongside the total winnings in the result banner.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
@ -2148,6 +2196,10 @@
} }
} }
}, },
"Use the Icon Generator in Settings → DEBUG to save the 1024px icon to the Files app, then add it to Xcode's Assets.xcassets/AppIcon." : {
"comment" : "Instructions for exporting an app icon.",
"isCommentAutoGenerated" : true
},
"VIP" : { "VIP" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -2344,6 +2396,10 @@
"comment" : "A label for the worst session amount in the statistics view.", "comment" : "A label for the worst session amount in the statistics view.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Xcode automatically generates all required sizes from the 1024px source." : {
"comment" : "A footnote explaining that Xcode generates all app icon sizes from the 1024px source image.",
"isCommentAutoGenerated" : true
},
"You can disable iCloud sync at any time in the app settings" : { "You can disable iCloud sync at any time in the app settings" : {
"comment" : "Text in the Privacy Policy View explaining how to disable iCloud sync in the app settings.", "comment" : "Text in the Privacy Policy View explaining how to disable iCloud sync in the app settings.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,

View File

@ -33,27 +33,21 @@ public struct AppIconConfig: Sendable {
self.accentColor = accentColor self.accentColor = accentColor
} }
// MARK: - Example Preset Configurations // MARK: - Example Configuration (for previews only)
// Game-specific presets should be defined in the respective apps as extensions.
/// Poker game icon configuration (example preset). /// Example configuration for CasinoKit previews.
public static let poker = AppIconConfig( /// Apps should define their own configs in `BrandingConfig.swift`.
title: "POKER", public static let example = AppIconConfig(
iconSymbol: "suit.diamond.fill", title: "CASINO",
accentColor: .red iconSymbol: "suit.diamond.fill"
)
/// Roulette game icon configuration (example preset).
public static let roulette = AppIconConfig(
title: "ROULETTE",
iconSymbol: "circle.grid.3x3.fill",
primaryColor: Color(red: 0.4, green: 0.1, blue: 0.1),
secondaryColor: Color(red: 0.25, green: 0.05, blue: 0.05)
) )
} }
/// A customizable app icon view for casino games. /// A customizable app icon view for casino games.
/// Render this view to create your app icon assets. /// Render this view to create your app icon assets.
///
/// **Important**: This view generates a full-bleed square icon. iOS applies its own
/// superellipse mask, so decorative borders are inset to avoid clipping at the edges.
public struct AppIconView: View { public struct AppIconView: View {
let config: AppIconConfig let config: AppIconConfig
let size: CGFloat let size: CGFloat
@ -63,16 +57,33 @@ public struct AppIconView: View {
self.size = size self.size = size
} }
private var cornerRadius: CGFloat { size * 0.22 } // Size calculations
private var iconSize: CGFloat { size * 0.35 } private var iconSize: CGFloat { size * 0.35 }
private var titleSize: CGFloat { size * 0.12 }
private var subtitleSize: CGFloat { size * 0.25 } private var subtitleSize: CGFloat { size * 0.25 }
private var borderWidth: CGFloat { size * 0.02 }
/// Dynamic title size based on text length.
/// Shorter titles get larger fonts, longer titles shrink to fit within the border.
private var titleSize: CGFloat {
let baseSize = size * 0.12
let length = config.title.count
// Scale factor: full size for 6 chars, progressively smaller for longer
let scaleFactor: CGFloat = switch length {
case ...6: 1.0 // "CASINO", "POKER"
case 7: 0.95 //
case 8: 0.85 // "BACCARAT"
case 9: 0.75 // "BLACKJACK"
default: 0.65 // Very long titles
}
return baseSize * scaleFactor
}
public var body: some View { public var body: some View {
ZStack { ZStack {
// Background gradient // Background gradient - full bleed, no rounded corners
RoundedRectangle(cornerRadius: cornerRadius) // iOS will apply its own superellipse mask
Rectangle()
.fill( .fill(
LinearGradient( LinearGradient(
colors: [config.primaryColor, config.secondaryColor], colors: [config.primaryColor, config.secondaryColor],
@ -85,21 +96,6 @@ public struct AppIconView: View {
DiamondPatternOverlay(size: size) DiamondPatternOverlay(size: size)
.opacity(0.08) .opacity(0.08)
// Gold border
RoundedRectangle(cornerRadius: cornerRadius)
.strokeBorder(
LinearGradient(
colors: [
config.accentColor,
config.accentColor.opacity(0.6),
config.accentColor
],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: borderWidth
)
// Content // Content
VStack(spacing: size * 0.03) { VStack(spacing: size * 0.03) {
// Icon symbol // Icon symbol
@ -179,24 +175,56 @@ private struct DiamondPatternOverlay: View {
} }
} }
// MARK: - Preview // MARK: - Previews
#Preview("Poker Icon") { #Preview("App Icon") {
AppIconView(config: .poker, size: 512) AppIconView(config: .example, size: 512)
.clipShape(.rect(cornerRadius: 512 * 0.22))
.padding() .padding()
.background(Color.gray) .background(Color.gray)
} }
#Preview("Roulette Icon") { #Preview("Title Scaling") {
AppIconView(config: .roulette, size: 512) let shortTitle = AppIconConfig(title: "POKER", iconSymbol: "suit.diamond.fill")
.padding() let mediumTitle = AppIconConfig(title: "BACCARAT", iconSymbol: "suit.spade.fill")
.background(Color.gray) let longTitle = AppIconConfig(
} title: "BLACKJACK",
subtitle: "21",
iconSymbol: "suit.club.fill",
primaryColor: Color(red: 0.05, green: 0.35, blue: 0.15),
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
)
#Preview("All Icons") { return HStack(spacing: 20) {
HStack(spacing: 20) { VStack {
AppIconView(config: .poker, size: 200) AppIconView(config: shortTitle, size: 180)
AppIconView(config: .roulette, size: 200) .clipShape(.rect(cornerRadius: 180 * 0.22))
Text("6 chars").font(.caption)
}
VStack {
AppIconView(config: mediumTitle, size: 180)
.clipShape(.rect(cornerRadius: 180 * 0.22))
Text("8 chars").font(.caption)
}
VStack {
AppIconView(config: longTitle, size: 180)
.clipShape(.rect(cornerRadius: 180 * 0.22))
Text("9 chars").font(.caption)
}
}
.padding()
.background(Color.gray)
}
#Preview("All Sizes") {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
VStack {
AppIconView(config: .example, size: CGFloat(size))
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
Text("\(size)px").font(.caption)
}
}
} }
.padding() .padding()
.background(Color.gray) .background(Color.gray)

View File

@ -0,0 +1,64 @@
//
// AppLaunchView.swift
// CasinoKit
//
// A wrapper view that shows an animated launch screen before transitioning
// to the main app content. Use this to create seamless animated launches.
//
import SwiftUI
/// A wrapper that shows an animated launch screen before the main content.
///
/// Usage:
/// ```swift
/// AppLaunchView(config: .baccarat) {
/// ContentView()
/// }
/// ```
public struct AppLaunchView<Content: View>: View {
let config: LaunchScreenConfig
let content: () -> Content
@State private var showLaunchScreen = true
/// Creates a launch wrapper.
/// - Parameters:
/// - config: The launch screen configuration.
/// - content: The main app content to show after the launch animation.
public init(
config: LaunchScreenConfig,
@ViewBuilder content: @escaping () -> Content
) {
self.config = config
self.content = content
}
public var body: some View {
ZStack {
// Main content (always rendered underneath)
content()
// Launch screen overlay
if showLaunchScreen {
LaunchScreenView(config: config)
.transition(.opacity)
.zIndex(1)
}
}
.task {
// Wait for launch animation to complete, then fade out
try? await Task.sleep(for: .seconds(2.0))
withAnimation(.easeOut(duration: 0.5)) {
showLaunchScreen = false
}
}
}
}
#Preview {
AppLaunchView(config: .example) {
Text("Main App Content")
.font(.largeTitle)
}
}

View File

@ -0,0 +1,91 @@
//
// BrandingPreviewView.swift
// CasinoKit
//
// Development view for previewing and exporting app icons and launch screens.
// Access this during development to generate icon assets.
//
import SwiftUI
/// Preview view for app branding assets.
/// Use this during development to preview icons and launch screens.
public struct BrandingPreviewView: View {
let iconConfig: AppIconConfig
let launchConfig: LaunchScreenConfig
let appName: String
// Development view: fixed sizes acceptable
private let largePreviewSize: CGFloat = 300
private let iconCornerRadiusRatio: CGFloat = 0.22
/// Creates a branding preview view.
/// - Parameters:
/// - iconConfig: The app icon configuration for this game.
/// - launchConfig: The launch screen configuration for this game.
/// - appName: The app name for display purposes.
public init(
iconConfig: AppIconConfig,
launchConfig: LaunchScreenConfig,
appName: String
) {
self.iconConfig = iconConfig
self.launchConfig = launchConfig
self.appName = appName
}
public var body: some View {
TabView {
// App Icon Preview
ScrollView {
VStack(spacing: 32) {
Text("App Icon")
.font(.largeTitle.bold())
AppIconView(config: iconConfig, size: largePreviewSize)
.clipShape(.rect(cornerRadius: largePreviewSize * iconCornerRadiusRatio))
.shadow(radius: 20)
Text("1024 × 1024px")
.font(.caption)
.foregroundStyle(.secondary)
instructionsSection
}
.padding()
}
.tabItem {
Label("Icon", systemImage: "app.fill")
}
// Launch Screen Preview
LaunchScreenView(config: launchConfig)
.tabItem {
Label("Launch", systemImage: "rectangle.portrait.fill")
}
}
}
private var instructionsSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("To Export")
.font(.headline)
Text("Use the Icon Generator in Settings → DEBUG to save the 1024px icon to the Files app, then add it to Xcode's Assets.xcassets/AppIcon.")
.font(.callout)
}
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
}
}
#Preview {
BrandingPreviewView(
iconConfig: .example,
launchConfig: .example,
appName: "Casino"
)
}

View File

@ -1,28 +1,43 @@
// //
// IconGeneratorView.swift // IconGeneratorView.swift
// Baccarat // CasinoKit
// //
// Development tool to generate and export app icon images. // Development tool to generate and export app icon images.
// Run this view, tap the button, then find the icons in the Files app. // Run this view, tap the button, then find the icons in the Files app.
// //
import SwiftUI import SwiftUI
import CasinoKit
/// A development view that generates and saves app icon images. /// A development view that generates and saves app icon images.
/// After running, find the icons in Files app On My iPhone Baccarat /// After running, find the icons in Files app On My iPhone [App Name]
struct IconGeneratorView: View { public struct IconGeneratorView: View {
@State private var status: String = "Tap the button to generate icons" let config: AppIconConfig
@State private var isGenerating = false let appName: String
@State private var generatedIcons: [GeneratedIcon] = []
var body: some View { @State private var status: String = "Tap the button to generate the icon"
@State private var isGenerating = false
@State private var generatedIcon: GeneratedIconInfo?
// Development view: fixed sizes acceptable
private let previewSize: CGFloat = 200
private let iconCornerRadiusRatio: CGFloat = 0.22
/// Creates a new icon generator view.
/// - Parameters:
/// - config: The app icon configuration to use for rendering.
/// - appName: The app name for display in instructions (e.g., "Blackjack", "Baccarat").
public init(config: AppIconConfig, appName: String) {
self.config = config
self.appName = appName
}
public var body: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {
VStack(spacing: 24) { VStack(spacing: 24) {
// Preview // Preview
AppIconView(config: .baccarat, size: 200) AppIconView(config: config, size: previewSize)
.clipShape(.rect(cornerRadius: 200 * 0.22)) .clipShape(.rect(cornerRadius: previewSize * iconCornerRadiusRatio))
.shadow(radius: 10) .shadow(radius: 10)
Text("App Icon Preview") Text("App Icon Preview")
@ -31,7 +46,7 @@ struct IconGeneratorView: View {
// Generate button // Generate button
Button { Button {
Task { Task {
await generateIcons() await generateIcon()
} }
} label: { } label: {
HStack { HStack {
@ -39,7 +54,7 @@ struct IconGeneratorView: View {
ProgressView() ProgressView()
.tint(.white) .tint(.white)
} }
Text(isGenerating ? "Generating..." : "Generate & Save Icons") Text(isGenerating ? "Generating..." : "Generate & Save Icon")
} }
.font(.headline) .font(.headline)
.foregroundStyle(.white) .foregroundStyle(.white)
@ -58,24 +73,17 @@ struct IconGeneratorView: View {
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal) .padding(.horizontal)
// Generated icons // Generated icon confirmation
if !generatedIcons.isEmpty { if let icon = generatedIcon {
VStack(alignment: .leading, spacing: 12) { HStack {
Text("Generated Icons:") Image(systemName: "checkmark.circle.fill")
.font(.headline) .foregroundStyle(.green)
Text(icon.filename)
ForEach(generatedIcons) { icon in .font(.callout.monospaced())
HStack { Spacer()
Image(systemName: "checkmark.circle.fill") Text("\(Int(icon.size))px")
.foregroundStyle(.green) .font(.callout)
Text(icon.filename) .foregroundStyle(.secondary)
.font(.caption.monospaced())
Spacer()
Text("\(Int(icon.size))px")
.font(.caption)
.foregroundStyle(.secondary)
}
}
} }
.padding() .padding()
.background(Color.green.opacity(0.1)) .background(Color.green.opacity(0.1))
@ -99,17 +107,17 @@ struct IconGeneratorView: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
instructionRow(number: 1, text: "Open Files app on your device/simulator") instructionRow(number: 1, text: "Open Files app on your device/simulator")
instructionRow(number: 2, text: "Navigate to: On My iPhone → Baccarat") instructionRow(number: 2, text: "Navigate to: On My iPhone → \(appName)")
instructionRow(number: 3, text: "Find the AppIcon-1024.png file") instructionRow(number: 3, text: "Find AppIcon.png (1024×1024)")
instructionRow(number: 4, text: "AirDrop or share to your Mac") instructionRow(number: 4, text: "AirDrop or share to your Mac")
instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon") instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon")
} }
Divider() Divider()
Text("Alternative: Use an online tool") Text("Note: iOS uses a single 1024px icon")
.font(.subheadline.bold()) .font(.subheadline.bold())
Text("Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically.") Text("Xcode automatically generates all required sizes from the 1024px source.")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
@ -130,63 +138,50 @@ struct IconGeneratorView: View {
} }
@MainActor @MainActor
private func generateIcons() async { private func generateIcon() async {
isGenerating = true isGenerating = true
generatedIcons = [] generatedIcon = nil
status = "Generating icons..." status = "Generating icon..."
let sizes: [(CGFloat, String)] = [
(1024, "AppIcon-1024"),
(180, "AppIcon-180"),
(120, "AppIcon-120"),
(87, "AppIcon-87"),
(80, "AppIcon-80"),
(60, "AppIcon-60"),
(40, "AppIcon-40")
]
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
for (size, name) in sizes { // Render the 1024px icon (the only size needed for modern iOS)
// Render the icon let view = AppIconView(config: config, size: 1024)
let view = AppIconView(config: .baccarat, size: size) let renderer = ImageRenderer(content: view)
let renderer = ImageRenderer(content: view) renderer.scale = 1.0
renderer.scale = 1.0
if let uiImage = renderer.uiImage, if let uiImage = renderer.uiImage,
let data = uiImage.pngData() { let data = uiImage.pngData() {
let filename = "\(name).png" let filename = "AppIcon.png"
let fileURL = documentsPath.appending(path: filename) let fileURL = documentsPath.appending(path: filename)
do { do {
try data.write(to: fileURL) try data.write(to: fileURL)
generatedIcons.append(GeneratedIcon(filename: filename, size: size)) generatedIcon = GeneratedIconInfo(filename: filename, size: 1024)
} catch { status = "✅ Icon saved to Documents folder!\nOpen Files app to find it."
status = "Error saving \(filename): \(error.localizedDescription)" } catch {
} status = "Error saving icon: \(error.localizedDescription)"
} }
// Small delay for UI feedback
try? await Task.sleep(for: .milliseconds(100))
}
if generatedIcons.count == sizes.count {
status = "✅ All icons saved to Documents folder!\nOpen Files app to find them."
} else { } else {
status = "⚠️ Some icons failed to generate" status = "⚠️ Failed to render icon"
} }
isGenerating = false isGenerating = false
} }
} }
struct GeneratedIcon: Identifiable { /// Information about a generated icon file.
let id = UUID() public struct GeneratedIconInfo: Identifiable, Sendable {
let filename: String public let id = UUID()
let size: CGFloat public let filename: String
public let size: CGFloat
public init(filename: String, size: CGFloat) {
self.filename = filename
self.size = size
}
} }
#Preview { #Preview {
IconGeneratorView() IconGeneratorView(config: .example, appName: "Casino")
} }

View File

@ -134,6 +134,6 @@ public struct IconExportView: View {
} }
#Preview("Icon Export") { #Preview("Icon Export") {
IconExportView(config: .poker) IconExportView(config: .example)
} }

View File

@ -38,15 +38,14 @@ public struct LaunchScreenConfig: Sendable {
self.showLoadingIndicator = showLoadingIndicator self.showLoadingIndicator = showLoadingIndicator
} }
// MARK: - Example Preset Configurations // MARK: - Example Configuration (for previews only)
// Game-specific presets should be defined in the respective apps as extensions.
/// Poker game launch screen configuration (example preset). /// Example configuration for CasinoKit previews.
public static let poker = LaunchScreenConfig( /// Apps should define their own configs in `BrandingConfig.swift`.
title: "POKER", public static let example = LaunchScreenConfig(
tagline: "Texas Hold'em", title: "CASINO",
iconSymbols: ["suit.diamond.fill", "suit.club.fill"], tagline: "Your Game Tagline",
accentColor: .red iconSymbols: ["suit.diamond.fill", "suit.heart.fill"]
) )
} }
@ -321,13 +320,13 @@ public struct StaticLaunchScreenView: View {
} }
} }
// MARK: - Preview // MARK: - Previews
#Preview("Poker Launch") { #Preview("Launch Screen") {
LaunchScreenView(config: .poker) LaunchScreenView(config: .example)
} }
#Preview("Static Launch") { #Preview("Static Launch") {
StaticLaunchScreenView(config: .poker) StaticLaunchScreenView(config: .example)
} }

View File

@ -0,0 +1,130 @@
//
// DebugSection.swift
// CasinoKit
//
// Reusable debug section for settings views.
// Only visible in DEBUG builds.
//
import SwiftUI
/// A debug row that navigates to a destination view.
public struct DebugNavigationRow<Destination: View>: View {
let title: String
let icon: String
let destination: () -> Destination
public init(
title: String,
icon: String,
@ViewBuilder destination: @escaping () -> Destination
) {
self.title = title
self.icon = icon
self.destination = destination
}
public var body: some View {
NavigationLink {
destination()
} label: {
HStack {
Text(title)
.font(.system(size: CasinoDesign.BaseFontSize.body, weight: .medium))
.foregroundStyle(.orange)
Spacer()
Image(systemName: icon)
.font(.system(size: CasinoDesign.BaseFontSize.large))
.foregroundStyle(.orange)
}
.frame(minHeight: CasinoDesign.Size.actionRowMinHeight)
}
}
}
/// A debug row that performs an action.
public struct DebugActionRow: View {
let title: String
let icon: String
let action: () -> Void
public init(
title: String,
icon: String,
action: @escaping () -> Void
) {
self.title = title
self.icon = icon
self.action = action
}
public var body: some View {
Button(action: action) {
HStack {
Text(title)
.font(.system(size: CasinoDesign.BaseFontSize.body, weight: .medium))
.foregroundStyle(.orange)
Spacer()
Image(systemName: icon)
.font(.system(size: CasinoDesign.BaseFontSize.large))
.foregroundStyle(.orange)
}
.frame(minHeight: CasinoDesign.Size.actionRowMinHeight)
}
}
}
/// Branding debug rows for icon generation and preview.
/// Use these in your app's debug section.
public struct BrandingDebugRows: View {
let iconConfig: AppIconConfig
let launchConfig: LaunchScreenConfig
let appName: String
public init(
iconConfig: AppIconConfig,
launchConfig: LaunchScreenConfig,
appName: String
) {
self.iconConfig = iconConfig
self.launchConfig = launchConfig
self.appName = appName
}
public var body: some View {
DebugNavigationRow(
title: "Icon Generator",
icon: "app.badge.fill"
) {
IconGeneratorView(config: iconConfig, appName: appName)
}
Divider()
.background(Color.orange.opacity(0.3))
DebugNavigationRow(
title: "Branding Preview",
icon: "paintpalette.fill"
) {
BrandingPreviewView(
iconConfig: iconConfig,
launchConfig: launchConfig,
appName: appName
)
}
}
}
#Preview {
NavigationStack {
List {
Section("Debug") {
BrandingDebugRows(
iconConfig: .example,
launchConfig: .example,
appName: "Casino"
)
}
}
}
}