Add debug bypass subscription toggle, tests, and data layer improvements

- Add runtime toggle in Settings (DEBUG only) to bypass subscription/hide trial banner
- IAPManager.bypassSubscription is now a @Published var persisted via UserDefaults
- Hide upgrade banner in SettingsTabView and trial warnings when bypass is enabled
- Add FeelsTests directory with integration tests
- Update DataController, DataControllerGET, DataControllerUPDATE
- Update Xcode project and scheme configuration
- Update localization strings and App Store screen docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-15 17:12:56 -06:00
parent 7c142568be
commit 7639f881da
14 changed files with 1064 additions and 34 deletions

View File

@@ -36,11 +36,13 @@ class IAPManager: ObservableObject {
// MARK: - Debug Toggle
/// Set to `true` to bypass all subscription checks and grant full access (for development only)
/// Set to `false` to test trial/subscription behavior in DEBUG builds
/// Togglable at runtime in DEBUG builds via Settings > Debug > Bypass Subscription
#if DEBUG
static let bypassSubscription = false
@Published var bypassSubscription: Bool {
didSet { UserDefaults.standard.set(bypassSubscription, forKey: "debug_bypassSubscription") }
}
#else
static let bypassSubscription = false
let bypassSubscription = false
#endif
// MARK: - Constants
@@ -96,7 +98,7 @@ class IAPManager: ObservableObject {
}
var hasFullAccess: Bool {
if Self.bypassSubscription { return true }
if bypassSubscription { return true }
switch state {
case .subscribed, .billingRetry, .gracePeriod, .inTrial:
return true
@@ -106,7 +108,7 @@ class IAPManager: ObservableObject {
}
var shouldShowPaywall: Bool {
if Self.bypassSubscription { return false }
if bypassSubscription { return false }
switch state {
case .trialExpired, .expired, .revoked:
return true
@@ -116,6 +118,7 @@ class IAPManager: ObservableObject {
}
var shouldShowTrialWarning: Bool {
if bypassSubscription { return false }
if case .inTrial = state { return true }
return false
}
@@ -137,6 +140,9 @@ class IAPManager: ObservableObject {
// MARK: - Initialization
init() {
#if DEBUG
self.bypassSubscription = UserDefaults.standard.bool(forKey: "debug_bypassSubscription")
#endif
restoreCachedSubscriptionState()
updateListenerTask = listenForTransactions()
@@ -219,7 +225,7 @@ class IAPManager: ObservableObject {
/// Sync subscription status to UserDefaults for widget access
private func syncSubscriptionStatusToUserDefaults() {
let accessValue = Self.bypassSubscription ? true : hasFullAccess
let accessValue = bypassSubscription ? true : hasFullAccess
GroupUserDefaults.groupDefaults.set(accessValue, forKey: UserDefaultsStore.Keys.hasActiveSubscription.rawValue)
}
@@ -373,7 +379,7 @@ class IAPManager: ObservableObject {
case .unknown:
status = "unknown"
isSubscribed = false
hasFullAccess = Self.bypassSubscription
hasFullAccess = bypassSubscription
willAutoRenew = nil
isInGracePeriod = nil
trialDaysRemaining = nil