82 lines
2.9 KiB
Swift
82 lines
2.9 KiB
Swift
import Adw
|
|
|
|
/// Adds and dismisses toasts on a mounted ``ToastOverlay``.
|
|
///
|
|
/// Create one, attach it with ``WidgetView/toastManager(_:)``, then call
|
|
/// ``addToast(_:)`` and ``dismissAll()``:
|
|
///
|
|
/// ```swift
|
|
/// private let toasts = ToastManager()
|
|
///
|
|
/// var body: some View {
|
|
/// ToastOverlay {
|
|
/// Button().label("Save").onClicked {
|
|
/// toasts.addToast(Toast(title: "Saved", buttonLabel: "Undo") { undo() })
|
|
/// }
|
|
/// }
|
|
/// .toastManager(toasts)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Toasts added before the overlay mounts are queued and flushed on attach, so
|
|
/// a manager is usable from `init` or from a `.task` that races the first mount.
|
|
///
|
|
/// The manager holds its overlay strongly. Portico does not retain the Swift
|
|
/// wrapper objects for widgets it mounts into a container, so a weak reference
|
|
/// would be cleared as soon as the mount returned. There is no retain cycle: the
|
|
/// overlay does not reference the manager.
|
|
@MainActor public final class ToastManager {
|
|
private var overlay: Adw.ToastOverlay?
|
|
private var pending: [Adw.Toast] = []
|
|
|
|
/// The attached overlay, or `nil` before mount and after teardown.
|
|
@_spi(Portico) public var _attachedOverlay: Adw.ToastOverlay? { overlay }
|
|
|
|
/// How many toasts are queued awaiting attachment.
|
|
@_spi(Portico) public var _pendingCount: Int { pending.count }
|
|
|
|
/// Creates a manager that is not yet attached to an overlay.
|
|
public init() {}
|
|
|
|
/// Shows `toast` on the attached overlay, queueing it if none is attached yet.
|
|
///
|
|
/// - Parameter toast: The toast to display. Ownership follows libadwaita's
|
|
/// `(transfer full)` contract for `adw_toast_overlay_add_toast`; the caller
|
|
/// may release its reference immediately.
|
|
public func addToast(_ toast: Adw.Toast) {
|
|
guard let overlay else {
|
|
pending.append(toast)
|
|
return
|
|
}
|
|
overlay.addToast(toast: toast)
|
|
}
|
|
|
|
/// Dismisses every toast shown or queued on the attached overlay, and
|
|
/// discards any toasts queued here before attachment.
|
|
public func dismissAll() {
|
|
pending.removeAll()
|
|
overlay?.dismissAll()
|
|
}
|
|
|
|
/// Binds this manager to `overlay` and flushes the queue.
|
|
///
|
|
/// The most recent attachment wins.
|
|
///
|
|
/// - Parameter overlay: The freshly mounted overlay to publish toasts to.
|
|
@_spi(Portico) public func _attach(_ overlay: Adw.ToastOverlay) {
|
|
self.overlay = overlay
|
|
let queued = pending
|
|
pending.removeAll()
|
|
for toast in queued { overlay.addToast(toast: toast) }
|
|
}
|
|
|
|
/// Releases `overlay` if it is still the attached one.
|
|
///
|
|
/// Guarding on identity stops an older overlay's teardown from detaching a
|
|
/// newer one that has already attached.
|
|
///
|
|
/// - Parameter overlay: The overlay being torn down.
|
|
@_spi(Portico) public func _detach(_ overlay: Adw.ToastOverlay) {
|
|
if self.overlay === overlay { self.overlay = nil }
|
|
}
|
|
}
|