|
|
@@ -601,7 +601,9 @@ class ViewController: NSViewController {
|
|
|
showHomeView(profile: nil)
|
|
|
syncBrandPositionForCurrentWindowState(animated: false)
|
|
|
scheduleLaunchPaywallIfNeeded()
|
|
|
- DesktopWidgetWindowManager.shared.restore()
|
|
|
+ if storeKitCoordinator.hasPremiumAccess {
|
|
|
+ DesktopWidgetWindowManager.shared.restore()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
override func viewDidLayout() {
|
|
|
@@ -3575,6 +3577,7 @@ class ViewController: NSViewController {
|
|
|
let wasPremium = self.lastKnownPremiumAccess
|
|
|
self.lastKnownPremiumAccess = newIsPremium
|
|
|
self.updatePremiumButtons()
|
|
|
+ self.reconcileDesktopWidgetsForPremiumAccess()
|
|
|
if (wasPremium == false) && newIsPremium {
|
|
|
self.startMeetingsAutoRefresh()
|
|
|
self.triggerMeetingsRefresh(force: true)
|
|
|
@@ -3598,6 +3601,7 @@ class ViewController: NSViewController {
|
|
|
await self?.storeKitCoordinator.start()
|
|
|
await MainActor.run {
|
|
|
self?.updatePremiumButtons()
|
|
|
+ self?.reconcileDesktopWidgetsForPremiumAccess()
|
|
|
self?.scheduleLaunchPaywallIfNeeded()
|
|
|
}
|
|
|
}
|
|
|
@@ -5662,6 +5666,7 @@ class ViewController: NSViewController {
|
|
|
@MainActor
|
|
|
private func updatePremiumLockedSections() {
|
|
|
let shouldLock = isUserLoggedIn() && (storeKitCoordinator.hasPremiumAccess == false)
|
|
|
+ refreshWidgetsPagePremiumState()
|
|
|
homePremiumLockOverlay = updatePremiumLockOverlay(
|
|
|
existing: homePremiumLockOverlay,
|
|
|
in: homeMeetingsPanel,
|
|
|
@@ -5754,7 +5759,7 @@ class ViewController: NSViewController {
|
|
|
}
|
|
|
|
|
|
private func makeWidgetsPageView() -> NSView {
|
|
|
- let canAdd = isUserLoggedIn() && storeKitCoordinator.hasPremiumAccess
|
|
|
+ let canAdd = storeKitCoordinator.hasPremiumAccess
|
|
|
let host = NSHostingView(
|
|
|
rootView: WidgetsRootView(
|
|
|
canAddWidgets: canAdd,
|
|
|
@@ -5768,6 +5773,27 @@ class ViewController: NSViewController {
|
|
|
return host
|
|
|
}
|
|
|
|
|
|
+ @MainActor
|
|
|
+ private func refreshWidgetsPagePremiumState() {
|
|
|
+ guard let host = widgetsPageRootView as? NSHostingView<WidgetsRootView> else { return }
|
|
|
+ host.rootView = WidgetsRootView(
|
|
|
+ canAddWidgets: storeKitCoordinator.hasPremiumAccess,
|
|
|
+ onAddBlocked: { [weak self] in
|
|
|
+ self?.showPaywall()
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @MainActor
|
|
|
+ private func reconcileDesktopWidgetsForPremiumAccess() {
|
|
|
+ refreshWidgetsPagePremiumState()
|
|
|
+ if storeKitCoordinator.hasPremiumAccess {
|
|
|
+ DesktopWidgetWindowManager.shared.restore()
|
|
|
+ } else {
|
|
|
+ DesktopWidgetWindowManager.shared.removeAllWidgets()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@MainActor
|
|
|
private func updateWidgetMeetingStoreFromCurrentMeetings() {
|
|
|
let upcoming = allScheduledMeetings
|
|
|
@@ -7915,6 +7941,18 @@ private final class DesktopWidgetWindowManager: ObservableObject {
|
|
|
return removed.count
|
|
|
}
|
|
|
|
|
|
+ @discardableResult
|
|
|
+ func removeAllWidgets() -> Int {
|
|
|
+ let removedCount = loadInstances().count
|
|
|
+ for panel in windowsByInstanceID.values {
|
|
|
+ panel.close()
|
|
|
+ }
|
|
|
+ windowsByInstanceID.removeAll()
|
|
|
+ saveInstances([])
|
|
|
+ instancesChangeToken += 1
|
|
|
+ return removedCount
|
|
|
+ }
|
|
|
+
|
|
|
func restore() {
|
|
|
for instance in loadInstances() where windowsByInstanceID[instance.id] == nil {
|
|
|
show(instance: instance)
|
|
|
@@ -8174,6 +8212,7 @@ private struct WidgetsRootView: View {
|
|
|
variant: variant,
|
|
|
layoutScale: scale,
|
|
|
isAdded: desktopWidgetManager.hasInstance(variantID: variant.id),
|
|
|
+ isLocked: canAddWidgets == false,
|
|
|
onToggle: { toggleWidget(variant: variant) }
|
|
|
)
|
|
|
}
|
|
|
@@ -8225,16 +8264,16 @@ private struct WidgetsRootView: View {
|
|
|
}
|
|
|
|
|
|
private func toggleWidget(variant: WidgetVariant) {
|
|
|
+ guard canAddWidgets else {
|
|
|
+ showToast("Widgets are Premium only. Upgrade to add widgets.")
|
|
|
+ onAddBlocked?()
|
|
|
+ return
|
|
|
+ }
|
|
|
if desktopWidgetManager.hasInstance(variantID: variant.id) {
|
|
|
let removed = desktopWidgetManager.removeInstances(variantID: variant.id)
|
|
|
guard removed > 0 else { return }
|
|
|
showToast("Removed \(variant.size.title) widget from desktop.")
|
|
|
} else {
|
|
|
- guard canAddWidgets else {
|
|
|
- showToast("Widgets are Premium only. Upgrade to add widgets.")
|
|
|
- onAddBlocked?()
|
|
|
- return
|
|
|
- }
|
|
|
desktopWidgetManager.show(
|
|
|
instance: WidgetInstance(variantID: variant.id, size: variant.size, origin: nil)
|
|
|
)
|
|
|
@@ -8257,6 +8296,7 @@ private struct WidgetPreviewAddCard: View {
|
|
|
let variant: WidgetVariant
|
|
|
var layoutScale: CGFloat = 1
|
|
|
let isAdded: Bool
|
|
|
+ let isLocked: Bool
|
|
|
let onToggle: () -> Void
|
|
|
@State private var hovering = false
|
|
|
|
|
|
@@ -8264,21 +8304,59 @@ private struct WidgetPreviewAddCard: View {
|
|
|
ZStack(alignment: .topTrailing) {
|
|
|
Button(action: onToggle) {
|
|
|
WidgetPreviewCard(variant: variant, layoutScale: layoutScale)
|
|
|
+ .opacity(isLocked ? 0.52 : 1)
|
|
|
.contentShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
|
|
|
+ .overlay {
|
|
|
+ if isLocked {
|
|
|
+ RoundedRectangle(cornerRadius: 18, style: .continuous)
|
|
|
+ .fill(Color.black.opacity(0.28))
|
|
|
+ .frame(
|
|
|
+ width: variant.previewCardSize.width * max(0.01, layoutScale),
|
|
|
+ height: variant.previewCardSize.height * max(0.01, layoutScale)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .overlay {
|
|
|
+ if isLocked {
|
|
|
+ VStack(spacing: 6) {
|
|
|
+ Image(systemName: "lock.fill")
|
|
|
+ .font(.system(size: 18, weight: .bold))
|
|
|
+ Text("Premium")
|
|
|
+ .font(.system(size: 13, weight: .bold))
|
|
|
+ }
|
|
|
+ .foregroundStyle(.white.opacity(0.96))
|
|
|
+ .padding(.horizontal, 14)
|
|
|
+ .padding(.vertical, 10)
|
|
|
+ .background(
|
|
|
+ RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
+ .fill(Color.black.opacity(0.46))
|
|
|
+ )
|
|
|
+ .frame(
|
|
|
+ width: variant.previewCardSize.width * max(0.01, layoutScale),
|
|
|
+ height: variant.previewCardSize.height * max(0.01, layoutScale)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
|
|
|
Button(action: onToggle) {
|
|
|
- Image(systemName: isAdded ? "minus" : "plus")
|
|
|
+ Image(systemName: isLocked ? "lock.fill" : (isAdded ? "minus" : "plus"))
|
|
|
.font(.system(size: 12, weight: .bold))
|
|
|
.foregroundStyle(.white.opacity(0.95))
|
|
|
.frame(width: 26, height: 26)
|
|
|
- .background(Circle().fill(isAdded ? Color.red.opacity(0.92) : Color.green.opacity(0.95)))
|
|
|
+ .background(
|
|
|
+ Circle().fill(
|
|
|
+ isLocked
|
|
|
+ ? Color.orange.opacity(0.95)
|
|
|
+ : (isAdded ? Color.red.opacity(0.92) : Color.green.opacity(0.95))
|
|
|
+ )
|
|
|
+ )
|
|
|
.shadow(color: .black.opacity(0.28), radius: 10, x: 0, y: 6)
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
.offset(x: 10 * layoutScale, y: -10 * layoutScale)
|
|
|
- .opacity(isAdded ? 1 : (hovering ? 1 : 0))
|
|
|
+ .opacity((isAdded || isLocked) ? 1 : (hovering ? 1 : 0))
|
|
|
.animation(.easeOut(duration: 0.12), value: hovering)
|
|
|
}
|
|
|
.onHover { hovering = $0 }
|