diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 81ca791..d388989 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -27,7 +27,8 @@ "Bash(npx tsc:*)", "Bash(timeout 10 npx remotion:*)", "Bash(npx remotion:*)", - "Bash(xcrun xcresulttool:*)" + "Bash(xcrun xcresulttool:*)", + "Bash(sips:*)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index b8236b9..3ae4fba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,7 +134,7 @@ TripCreationView β†’ TripCreationViewModel β†’ PlanningRequest - ViewModels use `@Observable` (not ObservableObject) - All planning engine components are `actor` types for thread safety - 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 ### Themed Background System diff --git a/SportsTime.xcodeproj/project.pbxproj b/SportsTime.xcodeproj/project.pbxproj index 698993f..0f2a80c 100644 --- a/SportsTime.xcodeproj/project.pbxproj +++ b/SportsTime.xcodeproj/project.pbxproj @@ -304,10 +304,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTime.entitlements; + CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTimeDebug.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = V3PF3M6B6U; + DEVELOPMENT_TEAM = QND55P4443; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SportsTime/Info.plist; @@ -322,7 +322,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTime"; + PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTime.Debug; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; @@ -342,7 +342,7 @@ CODE_SIGN_ENTITLEMENTS = SportsTime/SportsTime.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = V3PF3M6B6U; + DEVELOPMENT_TEAM = QND55P4443; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SportsTime/Info.plist; @@ -357,7 +357,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTime"; + PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTime; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; @@ -500,7 +500,7 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 26.2; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTimeTests"; + PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTimeTests; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; SWIFT_APPROACHABLE_CONCURRENCY = YES; @@ -542,7 +542,7 @@ DEVELOPMENT_TEAM = V3PF3M6B6U; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.SportsTimeUITests"; + PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.SportsTimeUITests; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; SWIFT_APPROACHABLE_CONCURRENCY = YES; diff --git a/SportsTime/Configuration.storekit b/SportsTime/Configuration.storekit index 6f99614..adfa6cf 100644 --- a/SportsTime/Configuration.storekit +++ b/SportsTime/Configuration.storekit @@ -93,7 +93,7 @@ "locale" : "en_US" } ], - "productID" : "com.sportstime.pro.monthly", + "productID" : "com.88oakapps.SportsTime.pro.monthly", "recurringSubscriptionPeriod" : "P1M", "referenceName" : "Pro Monthly", "subscriptionGroupID" : "21514523", @@ -127,7 +127,7 @@ "locale" : "en_US" } ], - "productID" : "com.sportstime.pro.annual", + "productID" : "com.88oakapps.SportsTime.pro.annual2", "recurringSubscriptionPeriod" : "P1Y", "referenceName" : "Pro Annual", "subscriptionGroupID" : "21514523", diff --git a/SportsTime/Core/Services/BackgroundSyncManager.swift b/SportsTime/Core/Services/BackgroundSyncManager.swift index 3451249..45b5d81 100644 --- a/SportsTime/Core/Services/BackgroundSyncManager.swift +++ b/SportsTime/Core/Services/BackgroundSyncManager.swift @@ -18,10 +18,10 @@ final class BackgroundSyncManager { // MARK: - Task Identifiers /// 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) - static let processingTaskIdentifier = "com.sportstime.app.db-cleanup" + static let processingTaskIdentifier = "com.88oakapps.SportsTime.db-cleanup" // MARK: - Singleton @@ -31,7 +31,7 @@ final class BackgroundSyncManager { private var modelContainer: ModelContainer? 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() {} @@ -332,16 +332,16 @@ final class BackgroundSyncManager { #if DEBUG extension BackgroundSyncManager { /// 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() { 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. func debugTriggerProcessing() { 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 diff --git a/SportsTime/Core/Services/CloudKitService.swift b/SportsTime/Core/Services/CloudKitService.swift index 5c9a523..326db06 100644 --- a/SportsTime/Core/Services/CloudKitService.swift +++ b/SportsTime/Core/Services/CloudKitService.swift @@ -69,7 +69,7 @@ actor CloudKitService { private let recordsPerPage = 400 private init() { - self.container = CKContainer(identifier: "iCloud.com.sportstime.app") + self.container = CKContainer(identifier: "iCloud.com.88oakapps.SportsTime") self.publicDatabase = container.publicCloudDatabase } diff --git a/SportsTime/Core/Services/ItineraryItemService.swift b/SportsTime/Core/Services/ItineraryItemService.swift index 5f5a6b6..f59cff0 100644 --- a/SportsTime/Core/Services/ItineraryItemService.swift +++ b/SportsTime/Core/Services/ItineraryItemService.swift @@ -5,7 +5,7 @@ import CloudKit actor 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 let recordType = "ItineraryItem" diff --git a/SportsTime/Core/Services/NetworkMonitor.swift b/SportsTime/Core/Services/NetworkMonitor.swift index 4f9aa2e..fb0ae47 100644 --- a/SportsTime/Core/Services/NetworkMonitor.swift +++ b/SportsTime/Core/Services/NetworkMonitor.swift @@ -17,8 +17,8 @@ final class NetworkMonitor { // MARK: - Properties private let monitor = NWPathMonitor() - private let queue = DispatchQueue(label: "com.sportstime.networkmonitor") - private let logger = Logger(subsystem: "com.sportstime.app", category: "NetworkMonitor") + private let queue = DispatchQueue(label: "com.88oakapps.SportsTime.networkmonitor") + private let logger = Logger(subsystem: "com.88oakapps.SportsTime", category: "NetworkMonitor") /// Current network status private(set) var isConnected: Bool = false diff --git a/SportsTime/Core/Services/PollService.swift b/SportsTime/Core/Services/PollService.swift index 19d864a..b569c3d 100644 --- a/SportsTime/Core/Services/PollService.swift +++ b/SportsTime/Core/Services/PollService.swift @@ -52,7 +52,7 @@ actor PollService { private var pollSubscriptionID: CKSubscription.ID? private init() { - self.container = CKContainer(identifier: "iCloud.com.sportstime.app") + self.container = CKContainer(identifier: "iCloud.com.88oakapps.SportsTime") self.publicDatabase = container.publicCloudDatabase } diff --git a/SportsTime/Core/Services/SyncLogger.swift b/SportsTime/Core/Services/SyncLogger.swift index 118801f..1787129 100644 --- a/SportsTime/Core/Services/SyncLogger.swift +++ b/SportsTime/Core/Services/SyncLogger.swift @@ -13,7 +13,7 @@ final class SyncLogger { private let fileURL: URL private let maxLines = 500 - private let queue = DispatchQueue(label: "com.sportstime.synclogger") + private let queue = DispatchQueue(label: "com.88oakapps.SportsTime.synclogger") private init() { let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! diff --git a/SportsTime/Core/Services/VisitPhotoService.swift b/SportsTime/Core/Services/VisitPhotoService.swift index 5d61e0a..dd40a5f 100644 --- a/SportsTime/Core/Services/VisitPhotoService.swift +++ b/SportsTime/Core/Services/VisitPhotoService.swift @@ -63,7 +63,7 @@ final class VisitPhotoService { init(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 } diff --git a/SportsTime/Core/Store/StoreManager.swift b/SportsTime/Core/Store/StoreManager.swift index 20a2792..68fe1d2 100644 --- a/SportsTime/Core/Store/StoreManager.swift +++ b/SportsTime/Core/Store/StoreManager.swift @@ -18,8 +18,8 @@ final class StoreManager { // MARK: - Constants static let proProductIDs: Set = [ - "com.sportstime.pro.monthly", - "com.sportstime.pro.annual" + "com.88oakapps.SportsTime.pro.monthly", + "com.88oakapps.SportsTime.pro.annual2" ] static let freeTripLimit = 1 @@ -59,11 +59,11 @@ final class StoreManager { } var monthlyProduct: Product? { - products.first { $0.id == "com.sportstime.pro.monthly" } + products.first { $0.id == "com.88oakapps.SportsTime.pro.monthly" } } var annualProduct: Product? { - products.first { $0.id == "com.sportstime.pro.annual" } + products.first { $0.id == "com.88oakapps.SportsTime.pro.annual2" } } // MARK: - Initialization diff --git a/SportsTime/Export/Sharing/ShareService.swift b/SportsTime/Export/Sharing/ShareService.swift index d9ed938..e3326df 100644 --- a/SportsTime/Export/Sharing/ShareService.swift +++ b/SportsTime/Export/Sharing/ShareService.swift @@ -37,7 +37,7 @@ final class ShareService { ) // 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) { UIApplication.shared.open(url) return true diff --git a/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift b/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift index e1ca1e7..fc4f128 100644 --- a/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift +++ b/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift @@ -7,7 +7,7 @@ import Foundation import SwiftUI import os.log -private let logger = Logger(subsystem: "com.sportstime.app", category: "ScheduleViewModel") +private let logger = Logger(subsystem: "com.88oakapps.SportsTime", category: "ScheduleViewModel") @MainActor @Observable diff --git a/SportsTime/Info.plist b/SportsTime/Info.plist index 065f278..a9b85ea 100644 --- a/SportsTime/Info.plist +++ b/SportsTime/Info.plist @@ -2,16 +2,16 @@ + BGTaskSchedulerPermittedIdentifiers + + com.88oakapps.SportsTime.refresh + com.88oakapps.SportsTime.db-cleanup + UIBackgroundModes remote-notification fetch processing - BGTaskSchedulerPermittedIdentifiers - - com.sportstime.app.refresh - com.sportstime.app.db-cleanup - diff --git a/SportsTime/SportsTime.entitlements b/SportsTime/SportsTime.entitlements index 8606a3d..f2bb18f 100644 --- a/SportsTime/SportsTime.entitlements +++ b/SportsTime/SportsTime.entitlements @@ -6,7 +6,7 @@ development com.apple.developer.icloud-container-identifiers - iCloud.com.sportstime.app + iCloud.com.88oakapps.SportsTime com.apple.developer.icloud-services diff --git a/SportsTime/SportsTimeDebug.entitlements b/SportsTime/SportsTimeDebug.entitlements new file mode 100644 index 0000000..e38d0f0 --- /dev/null +++ b/SportsTime/SportsTimeDebug.entitlements @@ -0,0 +1,16 @@ + + + + + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.com.88oakapps.SportsTime.Debug + + com.apple.developer.icloud-services + + CloudKit + + + diff --git a/icon.pxd b/icon.pxd index 180843d..6f97815 100644 Binary files a/icon.pxd and b/icon.pxd differ diff --git a/landing_page/index.html b/landing_page/index.html new file mode 100644 index 0000000..f38b15b --- /dev/null +++ b/landing_page/index.html @@ -0,0 +1,1197 @@ + + + + + + SportsTime: Road Trip Planner - Plan Epic Multi-Sport Stadium Adventures + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ ✨ NEW Plan Multi-Sport Stadium Adventures +
+

+ Adventure Awaits.
+ Plan Your Ultimate Sports Road Trip. +

+

+ Visit stadiums, catch games, and create unforgettable memories. + The easiest way to plan multi-sport road trips across MLB, NBA, NHL & NFL. +

+ +
+ +
+
+
+
+
+
+

Adventure Awaits

+

Plan your ultimate sports road trip. Visit stadiums, catch games, and create unforgettable memories.

+ +
+ +
Featured Trips
+
+
+ East +
Columbus β†’ Columbus
+
1 game Β· 1 city
+
+
+ East +
Newark β†’ Newark
+
2 games Β· 1 city
+
+
+ +
Planning Tips
+
+
+
πŸ”„
+
+ Reschedule alerts + Enable notifications +
+
+
+
πŸŽ“
+
+ College town energy + Fun atmospheres +
+
+
+
+
+
+ +
+
πŸ—ΊοΈ
+
+ Smart Routes
+ Optimized for you +
+
+ +
+
🎟️
+
+ 6,600+ Games
+ All seasons +
+
+
+
+
+ + +
+
+
+
120+
+
Stadiums
+
+
+
4
+
Major Sports
+
+
+
6,600+
+
Games Tracked
+
+
+
∞
+
Adventures
+
+
+
+ + +
+
+
+

Everything You Need to Plan the Perfect Trip

+

From route optimization to group coordination, we've got you covered.

+
+ +
+
+
πŸ—ΊοΈ
+

Smart Route Planning

+

Our algorithm finds the most efficient route between stadiums, saving you time and miles on your adventure.

+
+ +
+
πŸ“‹
+

Bucket List Tracking

+

Track every stadium you've visited across all leagues. Earn achievements and share your progress with friends.

+
+ +
+
πŸ‘₯
+

Group Coordination

+

Planning with friends? Create polls to vote on destinations and games. Everyone stays in sync.

+
+ +
+
πŸ“„
+

PDF Export

+

Generate beautiful PDF itineraries with maps, game details, and local attractions for offline access.

+
+
+
+
+ + +
+
+
+

All Your Favorite Sports

+

Plan trips across America's biggest professional sports leagues.

+
+ +
+
+
⚾
+

MLB

+

30 Ballparks

+
+
+
πŸ€
+

NBA

+

30 Arenas

+
+
+
πŸ’
+

NHL

+

32 Rinks

+
+
+
🏈
+

NFL

+

30 Stadiums

+
+
+
+
+ + +
+
+
+
+

Ready to Start Your Adventure?

+

Download SportsTime and start planning your next epic stadium road trip today.

+ + + + +
+ Download on the + App Store +
+
+
+
+ + + + + + + diff --git a/screens/flow.png b/screens/flow.png new file mode 100644 index 0000000..7ad3145 Binary files /dev/null and b/screens/flow.png differ diff --git a/screens/phone.png b/screens/phone.png new file mode 100644 index 0000000..bef9811 Binary files /dev/null and b/screens/phone.png differ