1.0.5: Eating out slots, rule violations panel, Crashlytics, search bar fix

- Eating out: tap any empty slot → "Mark as eating out"; shows teal indicator,
  skipped by auto-assign, counts as complete, tap again to unmark
- Rule violations panel: access via wand long-press context menu when conflicts exist;
  shows all isRuleOverridden slots with Fix (clear) or Ignore (acknowledge) actions
- Firebase Crashlytics integrated: CrashlyticsService + dSYM upload build phase,
  isPremium property tracked per session
- PremiumSyncService: extracted premium state machine, StoreKit Transaction.updates
  listener, isPremium no longer synced via iCloud to avoid stale state
- StoreManager: analytics on purchase/restore, bundle ID fallback for product ID lookup
- Search bar contrast bug fixed: TextField now has explicit foreground color for dark mode
- WelcomeStepView: redesigned onboarding welcome screen with week preview
- Version bump: 1.0.5 build 22

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-04-27 09:48:20 +02:00
parent 6c7e12b41f
commit 110807d239
111 changed files with 3116 additions and 154 deletions
+16 -5
View File
@@ -77,6 +77,7 @@ final class StoreManager: ObservableObject {
if case .verified(let transaction) = verification {
await transaction.finish()
isPremium = true
AnalyticsService.logPremiumPurchased()
return .success
}
return .failed
@@ -99,14 +100,18 @@ final class StoreManager: ObservableObject {
do {
try await AppStore.sync()
let wasNotPremium = !isPremium
await checkPremiumStatus()
if wasNotPremium && isPremium {
AnalyticsService.logPremiumRestored()
}
} catch {
print("Restore failed: \(error)")
}
}
private func checkPremiumStatus() async {
isPremium = await Self.hasActiveSubscription(productIds: productIds)
isPremium = await Self.hasActiveSubscription()
}
var monthlyProduct: Product? {
@@ -133,12 +138,18 @@ final class StoreManager: ObservableObject {
var debugProductIds: [String] { productIds }
static func hasActiveSubscription(productIds: [String] = [
StoreManager.monthlyProductId
]) async -> Bool {
static func hasActiveSubscription() async -> Bool {
// Check all known product ID variants to handle both the canonical ID
// and any bundle-prefixed IDs that may have been used at purchase time.
var knownIds: [String] = [monthlyProductId]
if let bundleId = Bundle.main.bundleIdentifier {
knownIds.append("\(bundleId).premium.monthly")
}
let ids = Set(knownIds)
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result,
productIds.contains(transaction.productID),
ids.contains(transaction.productID),
transaction.revocationDate == nil {
if let expirationDate = transaction.expirationDate, expirationDate < Date() {
continue