Ariver
2026-06-28 755ca63e4d37c1a54db7fc6475152319a4e46ceb
src/Apptag/ProEntitlement.swift
@@ -95,8 +95,13 @@
struct ProThemePreviewState: Equatable {
    let theme: AppGridTheme
    let themeTuning: Double?
    let startedAt: Date
    let endsAt: Date
    func remainingSeconds(at date: Date = Date()) -> Int {
        max(0, Int(ceil(endsAt.timeIntervalSince(date))))
    }
}
struct ProNoteQuotaStatus: Equatable {
@@ -120,7 +125,7 @@
    static let firstFreeProVersion = "8.1.0"
    static let legacyFallbackOriginalPurchaseDateISO8601: String? = nil
    static let freeThemes: Set<AppGridTheme> = [.defaultLight, .deepBlue, .black]
    static let freeThemes: Set<AppGridTheme> = [.defaultLight, .black]
    static let freeNoteLimit = 5
    static let defaultThemePreviewDuration: TimeInterval = 5 * 60
@@ -159,6 +164,7 @@
private struct ProEntitlementSnapshot {
    var accessState: ProAccessState = .free
    var previewThemeID: String? = nil
    var previewThemeTuning: Double? = nil
}
private enum ProEntitlementSnapshotStore {
@@ -214,6 +220,19 @@
            return .defaultLight
        }
        return storedTheme
    }
    static func effectiveThemeTuning(for storedTheme: AppGridTheme, storedTuning: Double?) -> Double? {
        let snapshot = ProEntitlementSnapshotStore.read()
        if let previewID = snapshot.previewThemeID,
           let previewTheme = AppGridTheme(rawValue: previewID),
           previewTheme.supportsColorTuning {
            return previewTheme.normalizedTuning(snapshot.previewThemeTuning)
        }
        guard canUseTheme(storedTheme), storedTheme.supportsColorTuning else {
            return nil
        }
        return storedTheme.normalizedTuning(storedTuning)
    }
    static func noteQuotaStatus(in store: TagDatabase.Store? = nil) -> ProNoteQuotaStatus {
@@ -397,7 +416,7 @@
        }
    }
    func startThemePreview(for theme: AppGridTheme) {
    func startThemePreview(for theme: AppGridTheme, tuning: Double? = nil) {
        guard !ProEntitlementConfig.freeThemes.contains(theme) else { return }
        guard !accessState.isUnlocked else { return }
@@ -405,6 +424,7 @@
        let preview = ProThemePreviewState(
            theme: theme,
            themeTuning: theme.supportsColorTuning ? theme.normalizedTuning(tuning) : nil,
            startedAt: Date(),
            endsAt: Date().addingTimeInterval(ProEntitlementConfig.themePreviewDuration)
        )
@@ -416,6 +436,21 @@
            guard !Task.isCancelled else { return }
            self?.stopThemePreview()
        }
    }
    func updateThemePreviewTuning(_ tuning: Double, for theme: AppGridTheme) {
        guard let preview = themePreviewState,
              preview.theme == theme,
              theme.supportsColorTuning,
              !accessState.isUnlocked
        else { return }
        let updatedPreview = ProThemePreviewState(
            theme: preview.theme,
            themeTuning: theme.normalizedTuning(tuning),
            startedAt: preview.startedAt,
            endsAt: preview.endsAt
        )
        applyThemePreview(updatedPreview)
    }
    func stopThemePreview() {
@@ -555,6 +590,7 @@
        themePreviewState = preview
        ProEntitlementSnapshotStore.update { snapshot in
            snapshot.previewThemeID = preview?.theme.rawValue
            snapshot.previewThemeTuning = preview?.themeTuning
        }
    }