fix: morph launcher into uninstall review and align its filter

This commit is contained in:
2026-10-12 22:17:49 +02:00
parent b69152e2c0
commit 47aa757526
3 changed files with 124 additions and 54 deletions
+66 -10
View File
@@ -21,6 +21,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private let actionsButton = NSButton(title: "Actions ⌘K", target: nil, action: nil)
private let actions = ApplicationActionsController()
private var uninstallReview: UninstallReviewController?
private var launcherViews: [NSView] = []
private var launcherFrameBeforeReview: NSRect?
init(history: LaunchHistory = LaunchHistory(), uninstaller: ApplicationUninstaller = ApplicationUninstaller()) {
self.history = history
@@ -46,7 +48,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
window.delegate = self
window.navigateSelection = { [weak self] offset in
guard let self else { return }
if self.actions.isShown { self.actions.moveSelection(by: offset) }
if let review = self.uninstallReview { review.moveSelection(offset) }
else if self.actions.isShown { self.actions.moveSelection(by: offset) }
else { self.moveSelection(by: offset) }
}
window.dismiss = { [weak self] in self?.dismiss() }
@@ -69,6 +72,12 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
func present() {
if launcherFrameBeforeReview != nil {
showWindow(nil)
NSApp.activate()
window?.makeKeyAndOrderFront(nil)
return
}
let frontmost = NSWorkspace.shared.frontmostApplication
if frontmost?.processIdentifier != ProcessInfo.processInfo.processIdentifier {
previousApplication = frontmost
@@ -96,6 +105,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
func dismiss(restoringFocus: Bool = true) {
guard !restoringFocus || (!isLaunching && window?.attachedSheet == nil) else { return }
if let review = uninstallReview { review.goBack(); return }
guard launcherFrameBeforeReview == nil else { return }
actions.close()
alignmentGuides?.end()
let previous = previousApplication
@@ -108,7 +119,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
private func savePosition() {
guard let window, let screen = window.screen, let identifier = displayIdentifier(screen) else { return }
guard launcherFrameBeforeReview == nil, let window, let screen = window.screen, let identifier = displayIdentifier(screen) else { return }
UserDefaults.standard.set([
"display": identifier, "x": window.frame.minX, "y": window.frame.minY
], forKey: Self.positionKey)
@@ -148,7 +159,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
func windowDidResignKey(_ notification: Notification) {
guard !isLaunching, window?.attachedSheet == nil else { return }
guard !isLaunching, launcherFrameBeforeReview == nil, window?.attachedSheet == nil else { return }
dismiss(restoringFocus: false)
}
@@ -323,6 +334,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
actionPill.layer?.backgroundColor = SunsetPalette.accent.withAlphaComponent(0.08).cgColor
}
window.initialFirstResponder = searchField
launcherViews = [header, sectionLabel, scrollView, emptyLabel, footer]
}
private func refreshApplications() {
@@ -416,7 +428,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
private func updateOpenButton() {
openButton.isEnabled = !isLaunching && results.indices.contains(tableView.selectedRow)
openButton.isEnabled = !isLaunching && launcherFrameBeforeReview == nil && results.indices.contains(tableView.selectedRow)
actionsButton.isEnabled = openButton.isEnabled
}
@@ -496,7 +508,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
@objc private func showActions() {
guard !isLaunching, window?.attachedSheet == nil, results.indices.contains(tableView.selectedRow) else { return }
guard !isLaunching, launcherFrameBeforeReview == nil, window?.attachedSheet == nil, results.indices.contains(tableView.selectedRow) else { return }
let application = results[tableView.selectedRow]
actions.open = { [weak self] in self?.openApplication(application) }
actions.uninstall = { [weak self] in self?.reviewUninstall(application) }
@@ -505,12 +517,13 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
private func reviewUninstall(_ application: Application) {
guard let window, window.attachedSheet == nil else { return }
guard let window, let content = window.contentView, launcherFrameBeforeReview == nil, window.attachedSheet == nil else { return }
let review = UninstallReviewController(application: application, uninstaller: uninstaller)
uninstallReview = review
review.closed = { [weak self] in
self?.uninstallReview = nil
self?.window?.makeFirstResponder(self?.searchField)
launcherFrameBeforeReview = window.frame
review.closed = { [weak self, weak review] in
guard let self, let review else { return }
self.restoreLauncher(from: review)
}
review.changed = { [weak self] in
guard let self else { return }
@@ -518,7 +531,50 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
self.discoveryTask = nil
self.refreshApplications()
}
review.present(on: window)
review.view.translatesAutoresizingMaskIntoConstraints = false
review.view.alphaValue = 0
content.addSubview(review.view)
NSLayoutConstraint.activate([
review.view.leadingAnchor.constraint(equalTo: content.leadingAnchor),
review.view.trailingAnchor.constraint(equalTo: content.trailingAnchor),
review.view.topAnchor.constraint(equalTo: content.topAnchor),
review.view.bottomAnchor.constraint(equalTo: content.bottomAnchor)
])
for view in launcherViews { view.setAccessibilityHidden(true) }
openButton.keyEquivalent = ""
updateOpenButton()
var frame = window.frame
frame.size.height = min(540, window.screen?.visibleFrame.height ?? 540)
frame.origin.y = window.frame.maxY - frame.height
if let screen = window.screen { frame.origin.y = max(frame.origin.y, screen.visibleFrame.minY) }
NSAnimationContext.runAnimationGroup { context in
context.duration = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion ? 0 : 0.18
window.animator().setFrame(frame, display: true)
for view in launcherViews { view.animator().alphaValue = 0 }
review.view.animator().alphaValue = 1
}
review.present()
}
private func restoreLauncher(from review: UninstallReviewController) {
guard let window, let frame = launcherFrameBeforeReview, uninstallReview === review else { return }
uninstallReview = nil
for view in launcherViews { view.setAccessibilityHidden(false) }
NSAnimationContext.runAnimationGroup { context in
context.duration = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion ? 0 : 0.18
window.animator().setFrame(frame, display: true)
for view in launcherViews { view.animator().alphaValue = 1 }
review.view.animator().alphaValue = 0
} completionHandler: { [weak self] in
MainActor.assumeIsolated {
review.view.removeFromSuperview()
guard let self else { return }
self.launcherFrameBeforeReview = nil
self.openButton.keyEquivalent = "\r"
self.updateOpenButton()
self.window?.makeFirstResponder(self.searchField)
}
}
}
@objc private func openSelection() {