Browse Source

Add premium test mode and widget access gating.

Allow all users to open and browse the widgets page while restricting widget-add actions to premium users, with a debug-only premium testing toggle for faster QA.

Made-with: Cursor
huzaifahayat12 3 tháng trước cách đây
mục cha
commit
899834048f

+ 25 - 3
google_apps/AppTileView.swift

@@ -3,8 +3,10 @@ import AppKit
 
 struct AppTileView: View {
     let app: LauncherApp
+    let canAddWidget: Bool
     let onTap: () -> Void
     let onHide: (() -> Void)?
+    let onPremiumRequired: (() -> Void)?
 
     @State private var isHovering = false
     @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
@@ -152,9 +154,11 @@ struct AppTileView: View {
     }
 
     private func addWidget() {
-        var updated = decodeUUIDSet(from: widgetAppIDsData)
-        updated.insert(app.id)
-        widgetAppIDsData = encodeUUIDSet(updated)
+        if canAddWidget {
+            var updated = decodeUUIDSet(from: widgetAppIDsData)
+            updated.insert(app.id)
+            widgetAppIDsData = encodeUUIDSet(updated)
+        }
         NotificationCenter.default.post(
             name: .openWidgetsPage,
             object: nil,
@@ -217,14 +221,32 @@ struct AppTileView: View {
 extension AppTileView {
     init(app: LauncherApp, onTap: @escaping () -> Void) {
         self.app = app
+        self.canAddWidget = false
         self.onTap = onTap
         self.onHide = nil
+        self.onPremiumRequired = nil
     }
 
     init(app: LauncherApp, onTap: @escaping () -> Void, onHide: @escaping () -> Void) {
         self.app = app
+        self.canAddWidget = false
+        self.onTap = onTap
+        self.onHide = onHide
+        self.onPremiumRequired = nil
+    }
+
+    init(
+        app: LauncherApp,
+        canAddWidget: Bool,
+        onTap: @escaping () -> Void,
+        onHide: @escaping () -> Void,
+        onPremiumRequired: @escaping () -> Void
+    ) {
+        self.app = app
+        self.canAddWidget = canAddWidget
         self.onTap = onTap
         self.onHide = onHide
+        self.onPremiumRequired = onPremiumRequired
     }
 }
 

+ 28 - 6
google_apps/LauncherRootView.swift

@@ -23,6 +23,16 @@ struct LauncherRootView: View {
     @AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
     @AppStorage("widgetAppIDsData") private var widgetAppIDsData = ""
     @AppStorage("hiddenLauncherAppIDsData") private var hiddenLauncherAppIDsData = ""
+    @AppStorage("isPremiumUser") private var isPremiumUser = false
+    @AppStorage("isPremiumTestMode") private var isPremiumTestMode = false
+
+    private var canAddWidget: Bool {
+#if DEBUG
+        return isPremiumUser || isPremiumTestMode
+#else
+        return isPremiumUser
+#endif
+    }
 
     private var apps: [LauncherApp] {
         let hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
@@ -182,13 +192,15 @@ struct LauncherRootView: View {
             if layoutMode == .panel {
                 IconPanelView(
                     apps: filteredApps,
+                    canAddWidget: canAddWidget,
                     onAppTap: { app in
                         handleAppTap(app)
                     },
                     draggedAppID: $draggedAppID,
                     lastDropDestinationAppID: $lastDropDestinationAppID,
                     onMove: moveApp,
-                    onHideApp: hideApp
+                    onHideApp: hideApp,
+                    onPremiumRequired: { showingPremiumScreen = true }
                 )
             } else {
                 ScrollView {
@@ -196,8 +208,10 @@ struct LauncherRootView: View {
                         ForEach(filteredApps) { app in
                             AppTileView(
                                 app: app,
+                                canAddWidget: canAddWidget,
                                 onTap: { handleAppTap(app) },
-                                onHide: { hideApp(id: app.id) }
+                                onHide: { hideApp(id: app.id) },
+                                onPremiumRequired: { showingPremiumScreen = true }
                             )
                             .onDrag {
                                 draggedAppID = app.id
@@ -505,11 +519,13 @@ private struct SearchHeader: View {
 
 private struct IconPanelView: View {
     let apps: [LauncherApp]
+    let canAddWidget: Bool
     let onAppTap: (LauncherApp) -> Void
     @Binding var draggedAppID: UUID?
     @Binding var lastDropDestinationAppID: UUID?
     let onMove: (UUID, UUID) -> Void
     let onHideApp: (UUID) -> Void
+    let onPremiumRequired: () -> Void
 
     var body: some View {
         ScrollView {
@@ -517,11 +533,13 @@ private struct IconPanelView: View {
                 ForEach(apps) { app in
                     PanelAppRowView(
                         app: app,
+                        canAddWidget: canAddWidget,
                         onAppTap: onAppTap,
                         draggedAppID: $draggedAppID,
                         lastDropDestinationAppID: $lastDropDestinationAppID,
                         onMove: onMove,
-                        onHideApp: onHideApp
+                        onHideApp: onHideApp,
+                        onPremiumRequired: onPremiumRequired
                     )
                 }
             }
@@ -533,11 +551,13 @@ private struct IconPanelView: View {
 
 private struct PanelAppRowView: View {
     let app: LauncherApp
+    let canAddWidget: Bool
     let onAppTap: (LauncherApp) -> Void
     @Binding var draggedAppID: UUID?
     @Binding var lastDropDestinationAppID: UUID?
     let onMove: (UUID, UUID) -> Void
     let onHideApp: (UUID) -> Void
+    let onPremiumRequired: () -> Void
 
     @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
     @AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
@@ -659,9 +679,11 @@ private struct PanelAppRowView: View {
     }
 
     private func addWidget() {
-        var updated = decodeUUIDSetGlobal(from: widgetAppIDsData)
-        updated.insert(app.id)
-        widgetAppIDsData = encodeUUIDSetGlobal(updated)
+        if canAddWidget {
+            var updated = decodeUUIDSetGlobal(from: widgetAppIDsData)
+            updated.insert(app.id)
+            widgetAppIDsData = encodeUUIDSetGlobal(updated)
+        }
         NotificationCenter.default.post(
             name: .openWidgetsPage,
             object: nil,

+ 35 - 9
google_apps/PremiumFeaturesView.swift

@@ -10,6 +10,9 @@ struct PremiumFeaturesView: View {
     @State private var selectedPlan: PlanOption = .perpetual
     @State private var showingCheckout = false
     @State private var showingSuccessAlert = false
+    @State private var showingRestoreAlert = false
+    @AppStorage("isPremiumUser") private var isPremiumUser = false
+    @AppStorage("isPremiumTestMode") private var isPremiumTestMode = false
 
     private let featureRows: [(String, String)] = [
         ("Desktop Widgets", "Add frequently used apps as desktop widgets"),
@@ -61,15 +64,19 @@ struct PremiumFeaturesView: View {
 
                         Spacer()
 
-                        Text("Restore")
-                            .font(.system(size: 11, weight: .semibold))
-                            .foregroundStyle(.white.opacity(0.9))
-                            .padding(.horizontal, 8)
-                            .padding(.vertical, 3)
-                            .background(
-                                RoundedRectangle(cornerRadius: 7, style: .continuous)
-                                    .fill(Color.white.opacity(0.16))
-                            )
+                        Button("Restore") {
+                            isPremiumUser = true
+                            showingRestoreAlert = true
+                        }
+                        .buttonStyle(.plain)
+                        .font(.system(size: 11, weight: .semibold))
+                        .foregroundStyle(.white.opacity(0.9))
+                        .padding(.horizontal, 8)
+                        .padding(.vertical, 3)
+                        .background(
+                            RoundedRectangle(cornerRadius: 7, style: .continuous)
+                                .fill(Color.white.opacity(0.16))
+                        )
                     }
 
                     Text("Upgrade to unlock all\nPremium Features")
@@ -108,6 +115,19 @@ struct PremiumFeaturesView: View {
                         selectedPlan = .yearly
                     }
 
+#if DEBUG
+                    Toggle("Enable Premium (Testing)", isOn: $isPremiumTestMode)
+                        .toggleStyle(.switch)
+                        .font(.system(size: 12, weight: .semibold))
+                        .foregroundStyle(.white.opacity(0.9))
+                        .padding(.horizontal, 12)
+                        .padding(.vertical, 10)
+                        .background(
+                            RoundedRectangle(cornerRadius: 12, style: .continuous)
+                                .fill(Color.white.opacity(0.08))
+                        )
+#endif
+
                     Button(action: { showingCheckout = true }) {
                         Text("Continue")
                             .font(.system(size: 16, weight: .bold))
@@ -144,6 +164,7 @@ struct PremiumFeaturesView: View {
                 onCancel: { showingCheckout = false },
                 onConfirm: {
                     showingCheckout = false
+                    isPremiumUser = true
                     showingSuccessAlert = true
                 }
             )
@@ -154,6 +175,11 @@ struct PremiumFeaturesView: View {
         } message: {
             Text("Your \(selectedPlan == .perpetual ? "Perpetual" : "Yearly") plan has been activated.")
         }
+        .alert("Purchase Restored", isPresented: $showingRestoreAlert) {
+            Button("Done") { dismiss() }
+        } message: {
+            Text("Premium features are now unlocked on this device.")
+        }
     }
 
     private func featureCard(title: String, subtitle: String, color: Color) -> some View {

+ 14 - 0
google_apps/Widgets/WidgetsRootView.swift

@@ -9,6 +9,8 @@ struct WidgetsRootView: View {
     let apps: [LauncherApp]
     @Binding var selectedAppID: UUID?
     @Binding var widgetLibraryIDsData: String
+    @AppStorage("isPremiumUser") private var isPremiumUser = false
+    @AppStorage("isPremiumTestMode") private var isPremiumTestMode = false
 
     @State private var query = ""
     @State private var toastMessage: String?
@@ -30,6 +32,14 @@ struct WidgetsRootView: View {
         return WidgetTemplates.variants(for: selectedApp)
     }
 
+    private var canAddWidget: Bool {
+#if DEBUG
+        return isPremiumUser || isPremiumTestMode
+#else
+        return isPremiumUser
+#endif
+    }
+
     var body: some View {
         HStack(spacing: 14) {
             sidebar
@@ -121,6 +131,7 @@ struct WidgetsRootView: View {
                         WidgetPreviewAddCard(
                             app: selectedApp,
                             variant: variant,
+                            canAddWidget: canAddWidget,
                             onAdd: { addToDesktop(app: selectedApp, variant: variant) }
                         )
                     }
@@ -182,6 +193,7 @@ struct WidgetsRootView: View {
     }
 
     private func addToDesktop(app: LauncherApp, variant: WidgetVariant) {
+        guard canAddWidget else { return }
         ensureInWidgetLibrary(appID: app.id)
         let instance = WidgetInstance(
             appID: app.id,
@@ -248,6 +260,7 @@ private struct WidgetSidebarRow: View {
 private struct WidgetPreviewAddCard: View {
     let app: LauncherApp
     let variant: WidgetVariant
+    let canAddWidget: Bool
     let onAdd: () -> Void
 
     @State private var hovering = false
@@ -264,6 +277,7 @@ private struct WidgetPreviewAddCard: View {
                         .shadow(color: .black.opacity(0.28), radius: 10, x: 0, y: 6)
                 }
                 .buttonStyle(.plain)
+                .disabled(!canAddWidget)
                 .offset(x: 10, y: -10)
                 .opacity(hovering ? 1 : 0)
                 .animation(.easeOut(duration: 0.12), value: hovering)