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