From 0ae3a9359e4cdf6ae5b669c4affd91662463f370 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Wed, 01 Jul 2026 23:17:36 +0800
Subject: [PATCH] Prepare TagLauncher 8.3.1 candidate

---
 src/Apptag/ProEntitlement.swift |   54 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 53 insertions(+), 1 deletions(-)

diff --git a/src/Apptag/ProEntitlement.swift b/src/Apptag/ProEntitlement.swift
index 1674af4..b0b6f46 100644
--- a/src/Apptag/ProEntitlement.swift
+++ b/src/Apptag/ProEntitlement.swift
@@ -7,6 +7,7 @@
     case layoutImport
     case layoutExport
     case customTagColors
+    case customContainerQuota
     case unlimitedNotes
     case persistentAppSorting
     case customHotkeys
@@ -21,6 +22,8 @@
             return "pro.feature.export"
         case .customTagColors:
             return "pro.feature.customTagColors"
+        case .customContainerQuota:
+            return "pro.feature.customContainerQuota"
         case .unlimitedNotes:
             return "pro.feature.notes"
         case .persistentAppSorting:
@@ -40,6 +43,8 @@
             return "pro.feature.export.benefit"
         case .customTagColors:
             return "pro.feature.customTagColors.benefit"
+        case .customContainerQuota:
+            return "pro.feature.customContainerQuota.benefit"
         case .unlimitedNotes:
             return "pro.feature.notes.benefit"
         case .persistentAppSorting:
@@ -139,6 +144,21 @@
     case blocked(ProNoteQuotaStatus)
 }
 
+struct ProCustomContainerQuotaStatus: Equatable {
+    let isUnlimited: Bool
+    let used: Int
+    let limit: Int
+
+    var remaining: Int {
+        max(0, limit - used)
+    }
+}
+
+enum ProCustomContainerMaintenanceDecision: Equatable {
+    case allow(ProCustomContainerQuotaStatus)
+    case blocked(ProCustomContainerQuotaStatus)
+}
+
 enum ProEntitlementConfig {
     // 发布前只改这里,不允许把商品信息和边界版本散落到业务模块。
     static let lifetimeProductID = "com.taglauncher.pro.lifetime"
@@ -147,6 +167,7 @@
 
     static let freeThemes: Set<AppGridTheme> = [.defaultLight, .black]
     static let freeNoteLimit = 3
+    static let freeCustomContainerLimit = 6
     static let defaultThemePreviewDuration: TimeInterval = 5 * 60
 
     static let qaStateEnvKey = "TAGLAUNCHER_QA_PRO_STATE"
@@ -225,7 +246,7 @@
 
     static func isUnlocked(_ feature: ProFeature) -> Bool {
         switch feature {
-        case .premiumThemes, .layoutImport, .layoutExport, .customTagColors, .unlimitedNotes, .persistentAppSorting, .customHotkeys:
+        case .premiumThemes, .layoutImport, .layoutExport, .customTagColors, .customContainerQuota, .unlimitedNotes, .persistentAppSorting, .customHotkeys:
             return accessState().isUnlocked
         }
     }
@@ -344,6 +365,37 @@
             throw ProEntitlementError.noteQuotaExceeded(status)
         }
     }
+
+    static func customContainerQuotaStatus(in store: TagDatabase.Store? = nil) -> ProCustomContainerQuotaStatus {
+        guard !isUnlocked(.customContainerQuota) else {
+            return ProCustomContainerQuotaStatus(isUnlimited: true, used: 0, limit: Int.max)
+        }
+        let used = TagDatabase.customMaintainedContainerCount(in: store)
+        return ProCustomContainerQuotaStatus(
+            isUnlimited: false,
+            used: used,
+            limit: ProEntitlementConfig.freeCustomContainerLimit
+        )
+    }
+
+    static func customContainerMaintenanceDecision(
+        addingCustomContainerCount addedCount: Int,
+        in store: TagDatabase.Store? = nil
+    ) -> ProCustomContainerMaintenanceDecision {
+        let status = customContainerQuotaStatus(in: store)
+        guard !status.isUnlimited else { return .allow(status) }
+        guard addedCount > 0 else { return .allow(status) }
+        guard status.used + addedCount <= status.limit else {
+            return .blocked(status)
+        }
+        return .allow(
+            ProCustomContainerQuotaStatus(
+                isUnlimited: false,
+                used: status.used + addedCount,
+                limit: status.limit
+            )
+        )
+    }
 }
 
 @MainActor

--
Gitblit v1.9.3