updated privacy

Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
Matt Bruce 2026-01-20 15:37:59 -06:00
parent ade1eb342a
commit 63b583ebe2
12 changed files with 235 additions and 16 deletions

View File

@ -29,12 +29,11 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
EACONFIG005000000000000 /* Baccarat/Configuration/Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Baccarat/Configuration/Debug.xcconfig; sourceTree = SOURCE_ROOT; };
EACONFIG006000000000000 /* Baccarat/Configuration/Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Baccarat/Configuration/Release.xcconfig; sourceTree = SOURCE_ROOT; };
EAD890B72EF1E9CE006DBA80 /* Baccarat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Baccarat.app; sourceTree = BUILT_PRODUCTS_DIR; }; EAD890B72EF1E9CE006DBA80 /* Baccarat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Baccarat.app; sourceTree = BUILT_PRODUCTS_DIR; };
EAD890C42EF1E9CF006DBA80 /* BaccaratTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; EAD890C42EF1E9CF006DBA80 /* BaccaratTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
EAD890CE2EF1E9CF006DBA80 /* BaccaratUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; EAD890CE2EF1E9CF006DBA80 /* BaccaratUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BaccaratUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
EACONFIG004000000000000 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Baccarat/Configuration/Base.xcconfig; sourceTree = SOURCE_ROOT; };
EACONFIG005000000000000 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Baccarat/Configuration/Debug.xcconfig; sourceTree = SOURCE_ROOT; };
EACONFIG006000000000000 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Baccarat/Configuration/Release.xcconfig; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
@ -82,6 +81,15 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
EA39BCA72F1D35690073D1E1 /* Recovered References */ = {
isa = PBXGroup;
children = (
EACONFIG005000000000000 /* Baccarat/Configuration/Debug.xcconfig */,
EACONFIG006000000000000 /* Baccarat/Configuration/Release.xcconfig */,
);
name = "Recovered References";
sourceTree = "<group>";
};
EA5AD1FF2EF34B660040CB90 /* Frameworks */ = { EA5AD1FF2EF34B660040CB90 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -97,6 +105,7 @@
EAD890D12EF1E9CF006DBA80 /* BaccaratUITests */, EAD890D12EF1E9CF006DBA80 /* BaccaratUITests */,
EA5AD1FF2EF34B660040CB90 /* Frameworks */, EA5AD1FF2EF34B660040CB90 /* Frameworks */,
EAD890B82EF1E9CE006DBA80 /* Products */, EAD890B82EF1E9CE006DBA80 /* Products */,
EA39BCA72F1D35690073D1E1 /* Recovered References */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -296,7 +305,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
EAD890D62EF1E9CF006DBA80 /* Debug */ = { EAD890D62EF1E9CF006DBA80 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = EACONFIG005000000000000 /* Debug.xcconfig */; baseConfigurationReference = EACONFIG005000000000000 /* Baccarat/Configuration/Debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@ -363,7 +372,7 @@
}; };
EAD890D72EF1E9CF006DBA80 /* Release */ = { EAD890D72EF1E9CF006DBA80 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = EACONFIG006000000000000 /* Release.xcconfig */; baseConfigurationReference = EACONFIG006000000000000 /* Baccarat/Configuration/Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@ -445,7 +454,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.7; MARKETING_VERSION = 1.9;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -483,7 +492,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.7; MARKETING_VERSION = 1.9;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -391,8 +391,8 @@ struct SettingsView: View {
} }
.sheet(isPresented: $showPrivacyPolicy) { .sheet(isPresented: $showPrivacyPolicy) {
PrivacyPolicyView( PrivacyPolicyView(
developerName: "Your Name", // TODO: Replace with your name/company developerName: "TopDog Labs",
contactEmail: "your@email.com" // TODO: Replace with your email contactEmail: "info@topdoglabs.com"
) )
} }
} }

View File

@ -433,7 +433,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.7; MARKETING_VERSION = 1.9;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@ -466,7 +466,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.7; MARKETING_VERSION = 1.9;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;

View File

@ -20,7 +20,7 @@ enum Design {
static let showDebugBorders = false static let showDebugBorders = false
/// Set to true to show debug log statements /// Set to true to show debug log statements
static let showDebugLogs = true static let showDebugLogs = false
/// Debug logger - only prints when showDebugLogs is true /// Debug logger - only prints when showDebugLogs is true
static func debugLog(_ message: String) { static func debugLog(_ message: String) {

View File

@ -484,8 +484,8 @@ struct SettingsView: View {
} }
.sheet(isPresented: $showPrivacyPolicy) { .sheet(isPresented: $showPrivacyPolicy) {
PrivacyPolicyView( PrivacyPolicyView(
developerName: "Your Name", // TODO: Replace with your name/company developerName: "TopDog Labs",
contactEmail: "your@email.com" // TODO: Replace with your email contactEmail: "info@topdoglabs.com"
) )
} }
} }

View File

@ -615,6 +615,34 @@ public protocol PersistableGameData: Codable, Sendable {
- 📢 **Change Notifications** - Callbacks when data changes from other devices - 📢 **Change Notifications** - Callbacks when data changes from other devices
- 🔒 **Privacy** - Uses Apple ID, no Game Center required - 🔒 **Privacy** - Uses Apple ID, no Game Center required
### 📈 Analytics
**AnalyticsService** - Lightweight event tracking with a configurable endpoint.
```swift
let analytics: AnalyticsTracking = AnalyticsService()
await analytics.track(
AnalyticsEvent(
name: "round_started",
properties: [
"game": .string("blackjack"),
"tableLimit": .int(25)
]
)
)
```
**Configure an endpoint later:**
```swift
await analytics.updateEndpoint(URL(string: "https://example.com/analytics"))
```
**No-op tracker for disabled analytics:**
```swift
let analytics: AnalyticsTracking = NoOpAnalyticsTracker()
```
### 🎨 Design System ### 🎨 Design System
**CasinoDesign** - Shared design constants. **CasinoDesign** - Shared design constants.
@ -805,6 +833,15 @@ CasinoKit/
│ │ └── SessionViews.swift # Session UI components │ │ └── SessionViews.swift # Session UI components
│ ├── Audio/ │ ├── Audio/
│ │ └── SoundManager.swift │ │ └── SoundManager.swift
│ ├── Analytics/
│ │ ├── Models/
│ │ │ ├── AnalyticsEvent.swift
│ │ │ └── AnalyticsValue.swift
│ │ ├── Protocols/
│ │ │ └── AnalyticsTracking.swift
│ │ └── Services/
│ │ ├── AnalyticsService.swift
│ │ └── NoOpAnalyticsTracker.swift
│ ├── Storage/ │ ├── Storage/
│ │ └── CloudSyncManager.swift # iCloud persistence │ │ └── CloudSyncManager.swift # iCloud persistence
│ ├── Theme/ │ ├── Theme/
@ -886,4 +923,3 @@ private var fontSize: CGFloat = CasinoDesign.BaseFontSize.body
## License ## License
This package is for personal use in your casino game projects. This package is for personal use in your casino game projects.

View File

@ -0,0 +1,22 @@
//
// AnalyticsEvent.swift
// CasinoKit
//
import Foundation
public struct AnalyticsEvent: Codable, Equatable, Sendable {
public let name: String
public let timestamp: Date
public let properties: [String: AnalyticsValue]
public init(
name: String,
timestamp: Date = Date(),
properties: [String: AnalyticsValue] = [:]
) {
self.name = name
self.timestamp = timestamp
self.properties = properties
}
}

View File

@ -0,0 +1,58 @@
//
// AnalyticsValue.swift
// CasinoKit
//
import Foundation
public enum AnalyticsValue: Codable, Equatable, Sendable {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
private enum CodingKeys: String, CodingKey {
case type
case value
}
private enum ValueType: String, Codable {
case string
case int
case double
case bool
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ValueType.self, forKey: .type)
switch type {
case .string:
self = .string(try container.decode(String.self, forKey: .value))
case .int:
self = .int(try container.decode(Int.self, forKey: .value))
case .double:
self = .double(try container.decode(Double.self, forKey: .value))
case .bool:
self = .bool(try container.decode(Bool.self, forKey: .value))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .string(value):
try container.encode(ValueType.string, forKey: .type)
try container.encode(value, forKey: .value)
case let .int(value):
try container.encode(ValueType.int, forKey: .type)
try container.encode(value, forKey: .value)
case let .double(value):
try container.encode(ValueType.double, forKey: .type)
try container.encode(value, forKey: .value)
case let .bool(value):
try container.encode(ValueType.bool, forKey: .type)
try container.encode(value, forKey: .value)
}
}
}

View File

@ -0,0 +1,11 @@
//
// AnalyticsTracking.swift
// CasinoKit
//
import Foundation
public protocol AnalyticsTracking: Sendable {
func track(_ event: AnalyticsEvent) async
func updateEndpoint(_ endpoint: URL?) async
}

View File

@ -0,0 +1,63 @@
//
// AnalyticsService.swift
// CasinoKit
//
import Foundation
public actor AnalyticsService: AnalyticsTracking {
private var endpoint: URL?
public init(endpoint: URL? = nil) {
self.endpoint = endpoint
}
public func updateEndpoint(_ endpoint: URL?) async {
self.endpoint = endpoint
}
public func track(_ event: AnalyticsEvent) async {
guard let endpoint else { return }
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let payload = AnalyticsPayload(
appIdentifier: AppContext.bundleIdentifier,
appVersion: AppContext.appVersion,
buildNumber: AppContext.buildNumber,
event: event
)
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
request.httpBody = try encoder.encode(payload)
_ = try await URLSession.shared.data(for: request)
} catch {
return
}
}
}
private enum AppContext {
static var bundleIdentifier: String {
Bundle.main.bundleIdentifier ?? "unknown.bundle"
}
static var appVersion: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0"
}
static var buildNumber: String {
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "0"
}
}
private struct AnalyticsPayload: Codable {
let appIdentifier: String
let appVersion: String
let buildNumber: String
let event: AnalyticsEvent
}

View File

@ -0,0 +1,14 @@
//
// NoOpAnalyticsTracker.swift
// CasinoKit
//
import Foundation
public struct NoOpAnalyticsTracker: AnalyticsTracking {
public init() {}
public func track(_ event: AnalyticsEvent) async {}
public func updateEndpoint(_ endpoint: URL?) async {}
}

View File

@ -106,6 +106,13 @@
// - CloudSyncManager // - CloudSyncManager
// - PersistableGameData (protocol) // - PersistableGameData (protocol)
// MARK: - Analytics
// - AnalyticsTracking (protocol)
// - AnalyticsEvent
// - AnalyticsValue
// - AnalyticsService
// - NoOpAnalyticsTracker
// MARK: - Sessions // MARK: - Sessions
// - GameSession<Stats> (generic session with game-specific stats) // - GameSession<Stats> (generic session with game-specific stats)
// - GameSpecificStats (protocol for game-specific statistics) // - GameSpecificStats (protocol for game-specific statistics)
@ -137,4 +144,3 @@
// MARK: - Debug // MARK: - Debug
// - debugBorder(_:color:label:) View modifier // - debugBorder(_:color:label:) View modifier