From 71a520840eb3debe46173c1f73bc85bbea0b1aa6 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 28 Jun 2026 23:42:38 +0800
Subject: [PATCH] Implement Pro custom hotkeys for 8.2.0

---
 src/Apptag/ProEntitlement.swift |   48 +++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/src/Apptag/ProEntitlement.swift b/src/Apptag/ProEntitlement.swift
index 4841e55..3267c3e 100644
--- a/src/Apptag/ProEntitlement.swift
+++ b/src/Apptag/ProEntitlement.swift
@@ -8,6 +8,7 @@
     case layoutExport
     case unlimitedNotes
     case persistentAppSorting
+    case customHotkeys
 
     var titleKey: String {
         switch self {
@@ -21,6 +22,8 @@
             return "pro.feature.notes"
         case .persistentAppSorting:
             return "pro.feature.sorting"
+        case .customHotkeys:
+            return "pro.feature.customHotkeys"
         }
     }
 
@@ -36,6 +39,8 @@
             return "pro.feature.notes.benefit"
         case .persistentAppSorting:
             return "pro.feature.sorting.benefit"
+        case .customHotkeys:
+            return "pro.feature.customHotkeys.benefit"
         }
     }
 }
@@ -95,6 +100,7 @@
 
 struct ProThemePreviewState: Equatable {
     let theme: AppGridTheme
+    let themeTuning: Double?
     let startedAt: Date
     let endsAt: Date
 
@@ -124,7 +130,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
 
@@ -163,6 +169,7 @@
 private struct ProEntitlementSnapshot {
     var accessState: ProAccessState = .free
     var previewThemeID: String? = nil
+    var previewThemeTuning: Double? = nil
 }
 
 private enum ProEntitlementSnapshotStore {
@@ -200,7 +207,7 @@
 
     static func isUnlocked(_ feature: ProFeature) -> Bool {
         switch feature {
-        case .premiumThemes, .layoutImport, .layoutExport, .unlimitedNotes, .persistentAppSorting:
+        case .premiumThemes, .layoutImport, .layoutExport, .unlimitedNotes, .persistentAppSorting, .customHotkeys:
             return accessState().isUnlocked
         }
     }
@@ -218,6 +225,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 {
@@ -401,7 +421,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 }
 
@@ -409,6 +429,7 @@
 
         let preview = ProThemePreviewState(
             theme: theme,
+            themeTuning: theme.supportsColorTuning ? theme.normalizedTuning(tuning) : nil,
             startedAt: Date(),
             endsAt: Date().addingTimeInterval(ProEntitlementConfig.themePreviewDuration)
         )
@@ -420,6 +441,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() {
@@ -538,9 +574,14 @@
         originalAppVersion: String? = nil,
         originalPurchaseDate: Date? = nil
     ) {
+        let previousState = accessState
         accessState = state
         ProEntitlementSnapshotStore.update { snapshot in
             snapshot.accessState = state
+        }
+
+        if previousState != state {
+            NotificationCenter.default.post(name: .tagLauncherProEntitlementChanged, object: nil)
         }
 
         if persistCache {
@@ -559,6 +600,7 @@
         themePreviewState = preview
         ProEntitlementSnapshotStore.update { snapshot in
             snapshot.previewThemeID = preview?.theme.rawValue
+            snapshot.previewThemeTuning = preview?.themeTuning
         }
     }
 

--
Gitblit v1.9.3