|
@@ -362,6 +362,8 @@ final class ViewController: NSViewController {
|
|
|
private weak var calendarPageDaySummaryLabel: NSTextField?
|
|
private weak var calendarPageDaySummaryLabel: NSTextField?
|
|
|
private var calendarPageActionPopover: NSPopover?
|
|
private var calendarPageActionPopover: NSPopover?
|
|
|
private var calendarPageCreatePopover: NSPopover?
|
|
private var calendarPageCreatePopover: NSPopover?
|
|
|
|
|
+ private weak var topToastView: NSVisualEffectView?
|
|
|
|
|
+ private var topToastHideWorkItem: DispatchWorkItem?
|
|
|
|
|
|
|
|
/// In-app browser navigation: `.allowAll` or `.whitelist(hostSuffixes:)` (e.g. `["google.com"]` matches `meet.google.com`).
|
|
/// In-app browser navigation: `.allowAll` or `.whitelist(hostSuffixes:)` (e.g. `["google.com"]` matches `meet.google.com`).
|
|
|
private let inAppBrowserDefaultPolicy: InAppBrowserURLPolicy = .allowAll
|
|
private let inAppBrowserDefaultPolicy: InAppBrowserURLPolicy = .allowAll
|
|
@@ -867,6 +869,95 @@ private extension ViewController {
|
|
|
alert.runModal()
|
|
alert.runModal()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private func showTopToast(message: String, isError: Bool) {
|
|
|
|
|
+ topToastHideWorkItem?.cancel()
|
|
|
|
|
+ topToastHideWorkItem = nil
|
|
|
|
|
+ topToastView?.removeFromSuperview()
|
|
|
|
|
+ topToastView = nil
|
|
|
|
|
+
|
|
|
|
|
+ let toast = NSVisualEffectView()
|
|
|
|
|
+ toast.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ toast.material = darkModeEnabled ? .hudWindow : .popover
|
|
|
|
|
+ toast.blendingMode = .withinWindow
|
|
|
|
|
+ toast.state = .active
|
|
|
|
|
+ toast.wantsLayer = true
|
|
|
|
|
+ toast.layer?.cornerRadius = 10
|
|
|
|
|
+ toast.layer?.masksToBounds = true
|
|
|
|
|
+ toast.layer?.borderWidth = 1
|
|
|
|
|
+ toast.layer?.borderColor = NSColor.white.withAlphaComponent(darkModeEnabled ? 0.10 : 0.18).cgColor
|
|
|
|
|
+ toast.layer?.backgroundColor = NSColor.black.withAlphaComponent(darkModeEnabled ? 0.68 : 0.78).cgColor
|
|
|
|
|
+ toast.alphaValue = 0
|
|
|
|
|
+
|
|
|
|
|
+ let row = NSStackView()
|
|
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.orientation = .horizontal
|
|
|
|
|
+ row.alignment = .centerY
|
|
|
|
|
+ row.spacing = 8
|
|
|
|
|
+ row.distribution = .fill
|
|
|
|
|
+
|
|
|
|
|
+ let icon = NSImageView()
|
|
|
|
|
+ icon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ icon.imageScaling = .scaleProportionallyDown
|
|
|
|
|
+ icon.image = NSImage(
|
|
|
|
|
+ systemSymbolName: isError ? "xmark.circle.fill" : "checkmark.circle.fill",
|
|
|
|
|
+ accessibilityDescription: isError ? "Error" : "Success"
|
|
|
|
|
+ )
|
|
|
|
|
+ icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
|
|
|
|
|
+ icon.contentTintColor = isError ? NSColor.systemRed : NSColor.systemGreen
|
|
|
|
|
+
|
|
|
|
|
+ let label = textLabel(
|
|
|
|
|
+ message,
|
|
|
|
|
+ font: NSFont.systemFont(ofSize: 13, weight: .semibold),
|
|
|
|
|
+ color: NSColor.white
|
|
|
|
|
+ )
|
|
|
|
|
+ label.alignment = .left
|
|
|
|
|
+ label.maximumNumberOfLines = 2
|
|
|
|
|
+ label.lineBreakMode = .byWordWrapping
|
|
|
|
|
+ row.addArrangedSubview(icon)
|
|
|
|
|
+ row.addArrangedSubview(label)
|
|
|
|
|
+ toast.addSubview(row)
|
|
|
|
|
+
|
|
|
|
|
+ view.addSubview(toast)
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ toast.topAnchor.constraint(equalTo: view.topAnchor, constant: 14),
|
|
|
|
|
+ toast.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
|
|
|
+ toast.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, constant: -40),
|
|
|
|
|
+ toast.heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
|
|
|
|
|
+
|
|
|
|
|
+ icon.widthAnchor.constraint(equalToConstant: 16),
|
|
|
|
|
+ icon.heightAnchor.constraint(equalToConstant: 16),
|
|
|
|
|
+
|
|
|
|
|
+ row.leadingAnchor.constraint(equalTo: toast.leadingAnchor, constant: 14),
|
|
|
|
|
+ row.trailingAnchor.constraint(equalTo: toast.trailingAnchor, constant: -14),
|
|
|
|
|
+ row.topAnchor.constraint(equalTo: toast.topAnchor, constant: 10),
|
|
|
|
|
+ row.bottomAnchor.constraint(equalTo: toast.bottomAnchor, constant: -10)
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ topToastView = toast
|
|
|
|
|
+
|
|
|
|
|
+ NSAnimationContext.runAnimationGroup { context in
|
|
|
|
|
+ context.duration = 0.18
|
|
|
|
|
+ context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
|
|
|
+ toast.animator().alphaValue = 1
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let hideWorkItem = DispatchWorkItem { [weak self, weak toast] in
|
|
|
|
|
+ guard let self, let toast else { return }
|
|
|
|
|
+ NSAnimationContext.runAnimationGroup({ context in
|
|
|
|
|
+ context.duration = 0.2
|
|
|
|
|
+ context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
|
|
|
+ toast.animator().alphaValue = 0
|
|
|
|
|
+ }, completionHandler: {
|
|
|
|
|
+ toast.removeFromSuperview()
|
|
|
|
|
+ if self.topToastView === toast {
|
|
|
|
|
+ self.topToastView = nil
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ topToastHideWorkItem = hideWorkItem
|
|
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: hideWorkItem)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private func confirmPremiumUpgrade() -> Bool {
|
|
private func confirmPremiumUpgrade() -> Bool {
|
|
|
let alert = NSAlert()
|
|
let alert = NSAlert()
|
|
|
alert.messageText = "Already Premium"
|
|
alert.messageText = "Already Premium"
|
|
@@ -4686,9 +4777,12 @@ private extension ViewController {
|
|
|
self.calendarPageMonthLabel?.stringValue = self.calendarMonthTitleText(for: self.calendarPageMonthAnchor)
|
|
self.calendarPageMonthLabel?.stringValue = self.calendarMonthTitleText(for: self.calendarPageMonthAnchor)
|
|
|
self.renderCalendarMonthGrid()
|
|
self.renderCalendarMonthGrid()
|
|
|
self.renderCalendarSelectedDay()
|
|
self.renderCalendarSelectedDay()
|
|
|
|
|
+ self.showTopToast(message: "Meeting added successfully.", isError: false)
|
|
|
}
|
|
}
|
|
|
} catch {
|
|
} catch {
|
|
|
- self.showSimpleError("Couldn’t create meeting.", error: error)
|
|
|
|
|
|
|
+ await MainActor.run {
|
|
|
|
|
+ self.showTopToast(message: "An issue occurred. Please try again.", isError: true)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|