feat: update bundle ID config, CloudKit container, and add landing page

- Update CloudKit container ID to iCloud.com.88oakapps.SportsTime across all services
- Update IAP product IDs to match new bundle ID (com.88oakapps.SportsTime)
- Add app landing page with light, welcoming design matching app aesthetic
- Update entitlements and project configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-01 22:47:55 -06:00
parent dbb0099776
commit 61c4e39807
21 changed files with 1250 additions and 36 deletions

View File

@@ -27,7 +27,8 @@
"Bash(npx tsc:*)", "Bash(npx tsc:*)",
"Bash(timeout 10 npx remotion:*)", "Bash(timeout 10 npx remotion:*)",
"Bash(npx remotion:*)", "Bash(npx remotion:*)",
"Bash(xcrun xcresulttool:*)" "Bash(xcrun xcresulttool:*)",
"Bash(sips:*)"
] ]
} }
} }

View File

@@ -134,7 +134,7 @@ TripCreationView → TripCreationViewModel → PlanningRequest
- ViewModels use `@Observable` (not ObservableObject) - ViewModels use `@Observable` (not ObservableObject)
- All planning engine components are `actor` types for thread safety - All planning engine components are `actor` types for thread safety
- Domain models are pure Codable structs; SwiftData models wrap them via encoded `Data` fields - Domain models are pure Codable structs; SwiftData models wrap them via encoded `Data` fields
- CloudKit container ID: `iCloud.com.sportstime.app` - CloudKit container ID: `iCloud.com.88oakapps.SportsTime`
- `PDFGenerator` and `ExportService` are `@MainActor final class` (not actors) because they access MainActor-isolated UI properties and use UIKit drawing - `PDFGenerator` and `ExportService` are `@MainActor final class` (not actors) because they access MainActor-isolated UI properties and use UIKit drawing
### Themed Background System ### Themed Background System

View File

@@ -304,10 +304,10 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTime.entitlements; CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTimeDebug.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = V3PF3M6B6U; DEVELOPMENT_TEAM = QND55P4443;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SportsTime/Info.plist; INFOPLIST_FILE = SportsTime/Info.plist;
@@ -322,7 +322,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTime"; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTime.Debug;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -342,7 +342,7 @@
CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTime.entitlements; CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTime.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = V3PF3M6B6U; DEVELOPMENT_TEAM = QND55P4443;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SportsTime/Info.plist; INFOPLIST_FILE = SportsTime/Info.plist;
@@ -357,7 +357,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTime"; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTime;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -500,7 +500,7 @@
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.2; IPHONEOS_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTimeTests"; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTimeTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO; STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -542,7 +542,7 @@
DEVELOPMENT_TEAM = V3PF3M6B6U; DEVELOPMENT_TEAM = V3PF3M6B6U;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTimeUITests"; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTimeUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO; STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;

View File

@@ -93,7 +93,7 @@
"locale" : "en_US" "locale" : "en_US"
} }
], ],
"productID" : "com.sportstime.pro.monthly", "productID" : "com.88oakapps.SportsTime.pro.monthly",
"recurringSubscriptionPeriod" : "P1M", "recurringSubscriptionPeriod" : "P1M",
"referenceName" : "Pro Monthly", "referenceName" : "Pro Monthly",
"subscriptionGroupID" : "21514523", "subscriptionGroupID" : "21514523",
@@ -127,7 +127,7 @@
"locale" : "en_US" "locale" : "en_US"
} }
], ],
"productID" : "com.sportstime.pro.annual", "productID" : "com.88oakapps.SportsTime.pro.annual2",
"recurringSubscriptionPeriod" : "P1Y", "recurringSubscriptionPeriod" : "P1Y",
"referenceName" : "Pro Annual", "referenceName" : "Pro Annual",
"subscriptionGroupID" : "21514523", "subscriptionGroupID" : "21514523",

View File

@@ -18,10 +18,10 @@ final class BackgroundSyncManager {
// MARK: - Task Identifiers // MARK: - Task Identifiers
/// Background app refresh task - runs periodically (system decides frequency) /// Background app refresh task - runs periodically (system decides frequency)
static let refreshTaskIdentifier = "com.sportstime.app.refresh" static let refreshTaskIdentifier = "com.88oakapps.SportsTime.refresh"
/// Background processing task - runs during optimal conditions (plugged in, overnight) /// Background processing task - runs during optimal conditions (plugged in, overnight)
static let processingTaskIdentifier = "com.sportstime.app.db-cleanup" static let processingTaskIdentifier = "com.88oakapps.SportsTime.db-cleanup"
// MARK: - Singleton // MARK: - Singleton
@@ -31,7 +31,7 @@ final class BackgroundSyncManager {
private var modelContainer: ModelContainer? private var modelContainer: ModelContainer?
private var currentCancellationToken: BackgroundTaskCancellationToken? private var currentCancellationToken: BackgroundTaskCancellationToken?
private let logger = Logger(subsystem: "com.sportstime.app", category: "BackgroundSyncManager") private let logger = Logger(subsystem: "com.88oakapps.SportsTime", category: "BackgroundSyncManager")
private init() {} private init() {}
@@ -332,16 +332,16 @@ final class BackgroundSyncManager {
#if DEBUG #if DEBUG
extension BackgroundSyncManager { extension BackgroundSyncManager {
/// Manually trigger refresh task for testing. /// Manually trigger refresh task for testing.
/// Run in debugger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.sportstime.app.refresh"]` /// Run in debugger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.88oakapps.SportsTime.refresh"]`
func debugTriggerRefresh() { func debugTriggerRefresh() {
print("Debug: Use lldb command to trigger background refresh:") print("Debug: Use lldb command to trigger background refresh:")
print("e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@\"com.sportstime.app.refresh\"]") print("e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@\"com.88oakapps.SportsTime.refresh\"]")
} }
/// Manually trigger processing task for testing. /// Manually trigger processing task for testing.
func debugTriggerProcessing() { func debugTriggerProcessing() {
print("Debug: Use lldb command to trigger background processing:") print("Debug: Use lldb command to trigger background processing:")
print("e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@\"com.sportstime.app.db-cleanup\"]") print("e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@\"com.88oakapps.SportsTime.db-cleanup\"]")
} }
} }
#endif #endif

View File

@@ -69,7 +69,7 @@ actor CloudKitService {
private let recordsPerPage = 400 private let recordsPerPage = 400
private init() { private init() {
self.container = CKContainer(identifier: "iCloud.com.sportstime.app") self.container = CKContainer(identifier: "iCloud.com.88oakapps.SportsTime")
self.publicDatabase = container.publicCloudDatabase self.publicDatabase = container.publicCloudDatabase
} }

View File

@@ -5,7 +5,7 @@ import CloudKit
actor ItineraryItemService { actor ItineraryItemService {
static let shared = ItineraryItemService() static let shared = ItineraryItemService()
private let container = CKContainer(identifier: "iCloud.com.sportstime.app") private let container = CKContainer(identifier: "iCloud.com.88oakapps.SportsTime")
private var database: CKDatabase { container.privateCloudDatabase } private var database: CKDatabase { container.privateCloudDatabase }
private let recordType = "ItineraryItem" private let recordType = "ItineraryItem"

View File

@@ -17,8 +17,8 @@ final class NetworkMonitor {
// MARK: - Properties // MARK: - Properties
private let monitor = NWPathMonitor() private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "com.sportstime.networkmonitor") private let queue = DispatchQueue(label: "com.88oakapps.SportsTime.networkmonitor")
private let logger = Logger(subsystem: "com.sportstime.app", category: "NetworkMonitor") private let logger = Logger(subsystem: "com.88oakapps.SportsTime", category: "NetworkMonitor")
/// Current network status /// Current network status
private(set) var isConnected: Bool = false private(set) var isConnected: Bool = false

View File

@@ -52,7 +52,7 @@ actor PollService {
private var pollSubscriptionID: CKSubscription.ID? private var pollSubscriptionID: CKSubscription.ID?
private init() { private init() {
self.container = CKContainer(identifier: "iCloud.com.sportstime.app") self.container = CKContainer(identifier: "iCloud.com.88oakapps.SportsTime")
self.publicDatabase = container.publicCloudDatabase self.publicDatabase = container.publicCloudDatabase
} }

View File

@@ -13,7 +13,7 @@ final class SyncLogger {
private let fileURL: URL private let fileURL: URL
private let maxLines = 500 private let maxLines = 500
private let queue = DispatchQueue(label: "com.sportstime.synclogger") private let queue = DispatchQueue(label: "com.88oakapps.SportsTime.synclogger")
private init() { private init() {
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

View File

@@ -63,7 +63,7 @@ final class VisitPhotoService {
init(modelContext: ModelContext) { init(modelContext: ModelContext) {
self.modelContext = modelContext self.modelContext = modelContext
self.container = CKContainer(identifier: "iCloud.com.sportstime.app") self.container = CKContainer(identifier: "iCloud.com.88oakapps.SportsTime")
self.privateDatabase = container.privateCloudDatabase self.privateDatabase = container.privateCloudDatabase
} }

View File

@@ -18,8 +18,8 @@ final class StoreManager {
// MARK: - Constants // MARK: - Constants
static let proProductIDs: Set<String> = [ static let proProductIDs: Set<String> = [
"com.sportstime.pro.monthly", "com.88oakapps.SportsTime.pro.monthly",
"com.sportstime.pro.annual" "com.88oakapps.SportsTime.pro.annual2"
] ]
static let freeTripLimit = 1 static let freeTripLimit = 1
@@ -59,11 +59,11 @@ final class StoreManager {
} }
var monthlyProduct: Product? { var monthlyProduct: Product? {
products.first { $0.id == "com.sportstime.pro.monthly" } products.first { $0.id == "com.88oakapps.SportsTime.pro.monthly" }
} }
var annualProduct: Product? { var annualProduct: Product? {
products.first { $0.id == "com.sportstime.pro.annual" } products.first { $0.id == "com.88oakapps.SportsTime.pro.annual2" }
} }
// MARK: - Initialization // MARK: - Initialization

View File

@@ -37,7 +37,7 @@ final class ShareService {
) )
// Open Instagram Stories // Open Instagram Stories
let urlString = "instagram-stories://share?source_application=com.sportstime.app" let urlString = "instagram-stories://share?source_application=com.88oakapps.SportsTime"
if let url = URL(string: urlString) { if let url = URL(string: urlString) {
UIApplication.shared.open(url) UIApplication.shared.open(url)
return true return true

View File

@@ -7,7 +7,7 @@ import Foundation
import SwiftUI import SwiftUI
import os.log import os.log
private let logger = Logger(subsystem: "com.sportstime.app", category: "ScheduleViewModel") private let logger = Logger(subsystem: "com.88oakapps.SportsTime", category: "ScheduleViewModel")
@MainActor @MainActor
@Observable @Observable

View File

@@ -2,16 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.88oakapps.SportsTime.refresh</string>
<string>com.88oakapps.SportsTime.db-cleanup</string>
</array>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>remote-notification</string> <string>remote-notification</string>
<string>fetch</string> <string>fetch</string>
<string>processing</string> <string>processing</string>
</array> </array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.sportstime.app.refresh</string>
<string>com.sportstime.app.db-cleanup</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@@ -6,7 +6,7 @@
<string>development</string> <string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key> <key>com.apple.developer.icloud-container-identifiers</key>
<array> <array>
<string>iCloud.com.sportstime.app</string> <string>iCloud.com.88oakapps.SportsTime</string>
</array> </array>
<key>com.apple.developer.icloud-services</key> <key>com.apple.developer.icloud-services</key>
<array> <array>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oakapps.SportsTime.Debug</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
</dict>
</plist>

BIN
icon.pxd

Binary file not shown.

1197
landing_page/index.html Normal file

File diff suppressed because it is too large Load Diff

BIN
screens/flow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
screens/phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB