|
|
@@ -1,6 +1,7 @@
|
|
|
import SwiftUI
|
|
|
import AppKit
|
|
|
import UniformTypeIdentifiers
|
|
|
+import WebKit
|
|
|
|
|
|
struct LauncherRootView: View {
|
|
|
enum LayoutMode {
|
|
|
@@ -12,7 +13,9 @@ struct LauncherRootView: View {
|
|
|
@State private var query = ""
|
|
|
@State private var selectedApp: LauncherApp?
|
|
|
@State private var showingPremiumScreen = false
|
|
|
+ @State private var showingManageSubscriptionSheet = false
|
|
|
@State private var showingCreateAppSheet = false
|
|
|
+ @State private var previouslyPremiumUnlocked = false
|
|
|
@State private var customApps: [LauncherApp] = []
|
|
|
@State private var appOrder: [UUID] = []
|
|
|
@State private var draggedAppID: UUID?
|
|
|
@@ -115,14 +118,18 @@ struct LauncherRootView: View {
|
|
|
)
|
|
|
.padding(.top, 12)
|
|
|
|
|
|
- if !premiumStore.isPremiumUnlocked {
|
|
|
- PromoBanner(
|
|
|
- onCloseTap: { showingPremiumScreen = true },
|
|
|
- onUpgradeTap: { showingPremiumScreen = true }
|
|
|
- )
|
|
|
- .padding(.bottom, 8)
|
|
|
- .zIndex(1)
|
|
|
- }
|
|
|
+ PromoBanner(
|
|
|
+ isPremiumUnlocked: premiumStore.isPremiumUnlocked,
|
|
|
+ onCloseTap: {
|
|
|
+ if !premiumStore.isPremiumUnlocked {
|
|
|
+ showingPremiumScreen = true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onUpgradeTap: { showingPremiumScreen = true },
|
|
|
+ onManageSubscriptionTap: { showingManageSubscriptionSheet = true }
|
|
|
+ )
|
|
|
+ .padding(.bottom, 8)
|
|
|
+ .zIndex(1)
|
|
|
|
|
|
appsPage
|
|
|
}
|
|
|
@@ -144,6 +151,16 @@ struct LauncherRootView: View {
|
|
|
}
|
|
|
.frame(minWidth: 760, minHeight: 420)
|
|
|
}
|
|
|
+ .sheet(isPresented: $showingManageSubscriptionSheet) {
|
|
|
+ ManageSubscriptionSheet(
|
|
|
+ activePremiumProductID: premiumStore.activePremiumProductID,
|
|
|
+ onUpgradeTap: {
|
|
|
+ showingManageSubscriptionSheet = false
|
|
|
+ showingPremiumScreen = true
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .frame(minWidth: 900, minHeight: 640)
|
|
|
+ }
|
|
|
.task {
|
|
|
await premiumStore.refreshEntitlements()
|
|
|
}
|
|
|
@@ -153,7 +170,14 @@ struct LauncherRootView: View {
|
|
|
await premiumStore.refreshEntitlements()
|
|
|
}
|
|
|
}
|
|
|
+ .onChange(of: premiumStore.isPremiumUnlocked) { unlocked in
|
|
|
+ if previouslyPremiumUnlocked && !unlocked {
|
|
|
+ showingPremiumScreen = true
|
|
|
+ }
|
|
|
+ previouslyPremiumUnlocked = unlocked
|
|
|
+ }
|
|
|
.onAppear {
|
|
|
+ previouslyPremiumUnlocked = premiumStore.isPremiumUnlocked
|
|
|
loadCustomAppsFromStorage()
|
|
|
loadAppOrderFromStorage()
|
|
|
normalizeAppOrderAndPersist()
|
|
|
@@ -790,8 +814,10 @@ private func encodeUUIDSetGlobal(_ set: Set<UUID>) -> String {
|
|
|
}
|
|
|
|
|
|
private struct PromoBanner: View {
|
|
|
+ let isPremiumUnlocked: Bool
|
|
|
let onCloseTap: () -> Void
|
|
|
let onUpgradeTap: () -> Void
|
|
|
+ let onManageSubscriptionTap: () -> Void
|
|
|
|
|
|
var body: some View {
|
|
|
HStack(spacing: 10) {
|
|
|
@@ -802,15 +828,15 @@ private struct PromoBanner: View {
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
|
|
|
- Text("Update to Premium to unlock all features and remove ads")
|
|
|
+ Text(isPremiumUnlocked ? "Premium is active. Manage your subscription any time." : "Update to Premium to unlock all features and remove ads")
|
|
|
.font(.system(size: 13, weight: .medium))
|
|
|
.foregroundStyle(.blue.opacity(0.95))
|
|
|
.lineLimit(1)
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
- Button(action: onUpgradeTap) {
|
|
|
- Text("Upgrade")
|
|
|
+ Button(action: isPremiumUnlocked ? onManageSubscriptionTap : onUpgradeTap) {
|
|
|
+ Text(isPremiumUnlocked ? "Manage Subscription" : "Upgrade")
|
|
|
.font(.system(size: 13, weight: .semibold))
|
|
|
.foregroundStyle(.white)
|
|
|
.padding(.horizontal, 12)
|
|
|
@@ -835,6 +861,62 @@ private struct PromoBanner: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+private struct ManageSubscriptionSheet: View {
|
|
|
+ @Environment(\.dismiss) private var dismiss
|
|
|
+ let activePremiumProductID: PremiumProductID?
|
|
|
+ let onUpgradeTap: () -> Void
|
|
|
+ private let url = URL(string: "https://apps.apple.com/account/subscriptions")!
|
|
|
+
|
|
|
+ var body: some View {
|
|
|
+ VStack(spacing: 0) {
|
|
|
+ HStack {
|
|
|
+ Text("Manage Subscription")
|
|
|
+ .font(.system(size: 15, weight: .semibold))
|
|
|
+ .foregroundStyle(.white.opacity(0.92))
|
|
|
+ Spacer()
|
|
|
+ Button("Done") { dismiss() }
|
|
|
+ .buttonStyle(.borderedProminent)
|
|
|
+ .controlSize(.small)
|
|
|
+ }
|
|
|
+ .padding(12)
|
|
|
+ .background(Color.black.opacity(0.25))
|
|
|
+
|
|
|
+ if activePremiumProductID == .yearly {
|
|
|
+ HStack(spacing: 10) {
|
|
|
+ Text("You are on the yearly plan. Upgrade to Lifetime for permanent access.")
|
|
|
+ .font(.system(size: 12.5, weight: .medium))
|
|
|
+ .foregroundStyle(.white.opacity(0.9))
|
|
|
+ Spacer()
|
|
|
+ Button("Upgrade Package") {
|
|
|
+ onUpgradeTap()
|
|
|
+ }
|
|
|
+ .buttonStyle(.borderedProminent)
|
|
|
+ .controlSize(.small)
|
|
|
+ }
|
|
|
+ .padding(.horizontal, 12)
|
|
|
+ .padding(.vertical, 10)
|
|
|
+ .background(Color.blue.opacity(0.18))
|
|
|
+ }
|
|
|
+
|
|
|
+ SubscriptionWebView(url: url)
|
|
|
+ }
|
|
|
+ .background(Color.black.opacity(0.3))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private struct SubscriptionWebView: NSViewRepresentable {
|
|
|
+ let url: URL
|
|
|
+
|
|
|
+ func makeNSView(context: Context) -> WKWebView {
|
|
|
+ let webView = WKWebView()
|
|
|
+ webView.allowsBackForwardNavigationGestures = true
|
|
|
+ webView.load(URLRequest(url: url))
|
|
|
+ return webView
|
|
|
+ }
|
|
|
+
|
|
|
+ func updateNSView(_ webView: WKWebView, context: Context) {}
|
|
|
+}
|
|
|
+
|
|
|
private struct HideMenuIndicatorIfAvailable: ViewModifier {
|
|
|
func body(content: Content) -> some View {
|
|
|
if #available(macOS 13.0, *) {
|