From 63b583ebe24137d8a4381b13cc4907f1969c5989 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 20 Jan 2026 15:37:59 -0600 Subject: [PATCH] updated privacy Signed-off-by: Matt Bruce --- Baccarat/Baccarat.xcodeproj/project.pbxproj | 23 ++++--- .../Baccarat/Views/Sheets/SettingsView.swift | 4 +- Blackjack/Blackjack.xcodeproj/project.pbxproj | 4 +- .../Blackjack/Theme/DesignConstants.swift | 2 +- .../Blackjack/Views/Sheets/SettingsView.swift | 4 +- CasinoKit/README.md | 38 ++++++++++- .../Analytics/Models/AnalyticsEvent.swift | 22 +++++++ .../Analytics/Models/AnalyticsValue.swift | 58 +++++++++++++++++ .../Protocols/AnalyticsTracking.swift | 11 ++++ .../Analytics/Services/AnalyticsService.swift | 63 +++++++++++++++++++ .../Services/NoOpAnalyticsTracker.swift | 14 +++++ CasinoKit/Sources/CasinoKit/Exports.swift | 8 ++- 12 files changed, 235 insertions(+), 16 deletions(-) create mode 100644 CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsEvent.swift create mode 100644 CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsValue.swift create mode 100644 CasinoKit/Sources/CasinoKit/Analytics/Protocols/AnalyticsTracking.swift create mode 100644 CasinoKit/Sources/CasinoKit/Analytics/Services/AnalyticsService.swift create mode 100644 CasinoKit/Sources/CasinoKit/Analytics/Services/NoOpAnalyticsTracker.swift diff --git a/Baccarat/Baccarat.xcodeproj/project.pbxproj b/Baccarat/Baccarat.xcodeproj/project.pbxproj index 3cfa486..a9c0430 100644 --- a/Baccarat/Baccarat.xcodeproj/project.pbxproj +++ b/Baccarat/Baccarat.xcodeproj/project.pbxproj @@ -29,12 +29,11 @@ /* End PBXContainerItemProxy 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; }; 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; }; - 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 */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -82,6 +81,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + EA39BCA72F1D35690073D1E1 /* Recovered References */ = { + isa = PBXGroup; + children = ( + EACONFIG005000000000000 /* Baccarat/Configuration/Debug.xcconfig */, + EACONFIG006000000000000 /* Baccarat/Configuration/Release.xcconfig */, + ); + name = "Recovered References"; + sourceTree = ""; + }; EA5AD1FF2EF34B660040CB90 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -97,6 +105,7 @@ EAD890D12EF1E9CF006DBA80 /* BaccaratUITests */, EA5AD1FF2EF34B660040CB90 /* Frameworks */, EAD890B82EF1E9CE006DBA80 /* Products */, + EA39BCA72F1D35690073D1E1 /* Recovered References */, ); sourceTree = ""; }; @@ -296,7 +305,7 @@ /* Begin XCBuildConfiguration section */ EAD890D62EF1E9CF006DBA80 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EACONFIG005000000000000 /* Debug.xcconfig */; + baseConfigurationReference = EACONFIG005000000000000 /* Baccarat/Configuration/Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -363,7 +372,7 @@ }; EAD890D72EF1E9CF006DBA80 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EACONFIG006000000000000 /* Release.xcconfig */; + baseConfigurationReference = EACONFIG006000000000000 /* Baccarat/Configuration/Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -445,7 +454,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7; + MARKETING_VERSION = 1.9; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -483,7 +492,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7; + MARKETING_VERSION = 1.9; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Baccarat/Baccarat/Views/Sheets/SettingsView.swift b/Baccarat/Baccarat/Views/Sheets/SettingsView.swift index 4281ea3..dafc9f3 100644 --- a/Baccarat/Baccarat/Views/Sheets/SettingsView.swift +++ b/Baccarat/Baccarat/Views/Sheets/SettingsView.swift @@ -391,8 +391,8 @@ struct SettingsView: View { } .sheet(isPresented: $showPrivacyPolicy) { PrivacyPolicyView( - developerName: "Your Name", // TODO: Replace with your name/company - contactEmail: "your@email.com" // TODO: Replace with your email + developerName: "TopDog Labs", + contactEmail: "info@topdoglabs.com" ) } } diff --git a/Blackjack/Blackjack.xcodeproj/project.pbxproj b/Blackjack/Blackjack.xcodeproj/project.pbxproj index e7b8cc1..ef3450e 100644 --- a/Blackjack/Blackjack.xcodeproj/project.pbxproj +++ b/Blackjack/Blackjack.xcodeproj/project.pbxproj @@ -433,7 +433,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7; + MARKETING_VERSION = 1.9; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -466,7 +466,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7; + MARKETING_VERSION = 1.9; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; diff --git a/Blackjack/Blackjack/Theme/DesignConstants.swift b/Blackjack/Blackjack/Theme/DesignConstants.swift index 99c5b01..3dea750 100644 --- a/Blackjack/Blackjack/Theme/DesignConstants.swift +++ b/Blackjack/Blackjack/Theme/DesignConstants.swift @@ -20,7 +20,7 @@ enum Design { static let showDebugBorders = false /// Set to true to show debug log statements - static let showDebugLogs = true + static let showDebugLogs = false /// Debug logger - only prints when showDebugLogs is true static func debugLog(_ message: String) { diff --git a/Blackjack/Blackjack/Views/Sheets/SettingsView.swift b/Blackjack/Blackjack/Views/Sheets/SettingsView.swift index 0a0c120..c66aba8 100644 --- a/Blackjack/Blackjack/Views/Sheets/SettingsView.swift +++ b/Blackjack/Blackjack/Views/Sheets/SettingsView.swift @@ -484,8 +484,8 @@ struct SettingsView: View { } .sheet(isPresented: $showPrivacyPolicy) { PrivacyPolicyView( - developerName: "Your Name", // TODO: Replace with your name/company - contactEmail: "your@email.com" // TODO: Replace with your email + developerName: "TopDog Labs", + contactEmail: "info@topdoglabs.com" ) } } diff --git a/CasinoKit/README.md b/CasinoKit/README.md index f7e4578..22d86ef 100644 --- a/CasinoKit/README.md +++ b/CasinoKit/README.md @@ -615,6 +615,34 @@ public protocol PersistableGameData: Codable, Sendable { - 📢 **Change Notifications** - Callbacks when data changes from other devices - 🔒 **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 **CasinoDesign** - Shared design constants. @@ -805,6 +833,15 @@ CasinoKit/ │ │ └── SessionViews.swift # Session UI components │ ├── Audio/ │ │ └── SoundManager.swift +│ ├── Analytics/ +│ │ ├── Models/ +│ │ │ ├── AnalyticsEvent.swift +│ │ │ └── AnalyticsValue.swift +│ │ ├── Protocols/ +│ │ │ └── AnalyticsTracking.swift +│ │ └── Services/ +│ │ ├── AnalyticsService.swift +│ │ └── NoOpAnalyticsTracker.swift │ ├── Storage/ │ │ └── CloudSyncManager.swift # iCloud persistence │ ├── Theme/ @@ -886,4 +923,3 @@ private var fontSize: CGFloat = CasinoDesign.BaseFontSize.body ## License This package is for personal use in your casino game projects. - diff --git a/CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsEvent.swift b/CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsEvent.swift new file mode 100644 index 0000000..f746741 --- /dev/null +++ b/CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsEvent.swift @@ -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 + } +} diff --git a/CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsValue.swift b/CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsValue.swift new file mode 100644 index 0000000..c5f2790 --- /dev/null +++ b/CasinoKit/Sources/CasinoKit/Analytics/Models/AnalyticsValue.swift @@ -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) + } + } +} diff --git a/CasinoKit/Sources/CasinoKit/Analytics/Protocols/AnalyticsTracking.swift b/CasinoKit/Sources/CasinoKit/Analytics/Protocols/AnalyticsTracking.swift new file mode 100644 index 0000000..3d9f400 --- /dev/null +++ b/CasinoKit/Sources/CasinoKit/Analytics/Protocols/AnalyticsTracking.swift @@ -0,0 +1,11 @@ +// +// AnalyticsTracking.swift +// CasinoKit +// + +import Foundation + +public protocol AnalyticsTracking: Sendable { + func track(_ event: AnalyticsEvent) async + func updateEndpoint(_ endpoint: URL?) async +} diff --git a/CasinoKit/Sources/CasinoKit/Analytics/Services/AnalyticsService.swift b/CasinoKit/Sources/CasinoKit/Analytics/Services/AnalyticsService.swift new file mode 100644 index 0000000..8135ee4 --- /dev/null +++ b/CasinoKit/Sources/CasinoKit/Analytics/Services/AnalyticsService.swift @@ -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 +} diff --git a/CasinoKit/Sources/CasinoKit/Analytics/Services/NoOpAnalyticsTracker.swift b/CasinoKit/Sources/CasinoKit/Analytics/Services/NoOpAnalyticsTracker.swift new file mode 100644 index 0000000..a7bf57e --- /dev/null +++ b/CasinoKit/Sources/CasinoKit/Analytics/Services/NoOpAnalyticsTracker.swift @@ -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 {} +} diff --git a/CasinoKit/Sources/CasinoKit/Exports.swift b/CasinoKit/Sources/CasinoKit/Exports.swift index 9a5e84d..88b546f 100644 --- a/CasinoKit/Sources/CasinoKit/Exports.swift +++ b/CasinoKit/Sources/CasinoKit/Exports.swift @@ -106,6 +106,13 @@ // - CloudSyncManager // - PersistableGameData (protocol) +// MARK: - Analytics +// - AnalyticsTracking (protocol) +// - AnalyticsEvent +// - AnalyticsValue +// - AnalyticsService +// - NoOpAnalyticsTracker + // MARK: - Sessions // - GameSession (generic session with game-specific stats) // - GameSpecificStats (protocol for game-specific statistics) @@ -137,4 +144,3 @@ // MARK: - Debug // - debugBorder(_:color:label:) View modifier -