move update pill to sidebar only, add Install Update menu item
This commit is contained in:
parent
de6cfededa
commit
ac4b49d7a4
6 changed files with 17 additions and 122 deletions
|
|
@ -9,7 +9,6 @@ final class UpdatePillReleaseVisibilityTests: XCTestCase {
|
|||
private let filesToCheck = [
|
||||
"Sources/Update/UpdateTitlebarAccessory.swift",
|
||||
"Sources/ContentView.swift",
|
||||
"Sources/WindowToolbarController.swift",
|
||||
]
|
||||
|
||||
func testUpdatePillNotGatedBehindDebug() throws {
|
||||
|
|
|
|||
|
|
@ -311,10 +311,6 @@ struct ContentView: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
if !sidebarState.isVisible {
|
||||
UpdatePill(model: updateViewModel)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
.frame(height: 28)
|
||||
.padding(.top, 2)
|
||||
|
|
|
|||
|
|
@ -65,3 +65,16 @@ struct UpdatePill: View {
|
|||
return size.width
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu item that shows "Install Update and Relaunch" when an update is ready.
|
||||
struct InstallUpdateMenuItem: View {
|
||||
@ObservedObject var model: UpdateViewModel
|
||||
|
||||
var body: some View {
|
||||
if model.state.isInstallable {
|
||||
Button("Install Update and Relaunch") {
|
||||
model.state.confirm()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,6 @@ final class NonDraggableHostingView<Content: View>: NSHostingView<Content> {
|
|||
override var mouseDownCanMoveWindow: Bool { false }
|
||||
}
|
||||
|
||||
private struct TitlebarAccessoryView: View {
|
||||
@ObservedObject var model: UpdateViewModel
|
||||
|
||||
var body: some View {
|
||||
UpdatePill(model: model)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
|
||||
enum TitlebarControlsStyle: Int, CaseIterable, Identifiable {
|
||||
case classic
|
||||
case compact
|
||||
|
|
@ -867,70 +858,6 @@ private struct NotificationPopoverRow: View {
|
|||
}
|
||||
}
|
||||
|
||||
final class UpdateAccessoryViewController: NSTitlebarAccessoryViewController {
|
||||
private let hostingView: NonDraggableHostingView<TitlebarAccessoryView>
|
||||
private let containerView = NSView()
|
||||
private var stateCancellable: AnyCancellable?
|
||||
private var pendingSizeUpdate = false
|
||||
|
||||
init(model: UpdateViewModel) {
|
||||
hostingView = NonDraggableHostingView(rootView: TitlebarAccessoryView(model: model))
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
view = containerView
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = true
|
||||
hostingView.translatesAutoresizingMaskIntoConstraints = true
|
||||
hostingView.autoresizingMask = [.width, .height]
|
||||
containerView.addSubview(hostingView)
|
||||
|
||||
stateCancellable = model.$state
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.scheduleSizeUpdate()
|
||||
}
|
||||
|
||||
scheduleSizeUpdate()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidAppear() {
|
||||
super.viewDidAppear()
|
||||
scheduleSizeUpdate()
|
||||
}
|
||||
|
||||
override func viewDidLayout() {
|
||||
super.viewDidLayout()
|
||||
scheduleSizeUpdate()
|
||||
}
|
||||
|
||||
private func scheduleSizeUpdate() {
|
||||
guard !pendingSizeUpdate else { return }
|
||||
pendingSizeUpdate = true
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.pendingSizeUpdate = false
|
||||
self?.updateSize()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSize() {
|
||||
hostingView.invalidateIntrinsicContentSize()
|
||||
hostingView.layoutSubtreeIfNeeded()
|
||||
let pillSize = hostingView.fittingSize
|
||||
let titlebarHeight = view.window.map { window in
|
||||
window.frame.height - window.contentLayoutRect.height
|
||||
} ?? pillSize.height
|
||||
let containerHeight = max(pillSize.height, titlebarHeight)
|
||||
let yOffset = max(0, (containerHeight - pillSize.height) / 2.0)
|
||||
preferredContentSize = NSSize(width: pillSize.width, height: containerHeight)
|
||||
containerView.frame = NSRect(x: 0, y: 0, width: pillSize.width, height: containerHeight)
|
||||
hostingView.frame = NSRect(x: 0, y: yOffset, width: pillSize.width, height: pillSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
final class UpdateTitlebarAccessoryController {
|
||||
private weak var updateViewModel: UpdateViewModel?
|
||||
private var didStart = false
|
||||
|
|
|
|||
|
|
@ -5,18 +5,13 @@ import SwiftUI
|
|||
@MainActor
|
||||
final class WindowToolbarController: NSObject, NSToolbarDelegate {
|
||||
private let commandItemIdentifier = NSToolbarItem.Identifier("cmux.focusedCommand")
|
||||
private let updateItemIdentifier = NSToolbarItem.Identifier("cmux.updatePill")
|
||||
|
||||
private weak var tabManager: TabManager?
|
||||
private weak var updateViewModel: UpdateViewModel?
|
||||
|
||||
private var commandLabels: [ObjectIdentifier: NSTextField] = [:]
|
||||
private var observers: [NSObjectProtocol] = []
|
||||
private var updateSizeCancellables: [ObjectIdentifier: AnyCancellable] = [:]
|
||||
private var updateViewConstraints: [ObjectIdentifier: (width: NSLayoutConstraint, height: NSLayoutConstraint)] = [:]
|
||||
|
||||
init(updateViewModel: UpdateViewModel) {
|
||||
self.updateViewModel = updateViewModel
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
|
@ -24,9 +19,6 @@ final class WindowToolbarController: NSObject, NSToolbarDelegate {
|
|||
for observer in observers {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
for cancellable in updateSizeCancellables.values {
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func start(tabManager: TabManager) {
|
||||
|
|
@ -105,11 +97,11 @@ final class WindowToolbarController: NSObject, NSToolbarDelegate {
|
|||
// MARK: - NSToolbarDelegate
|
||||
|
||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
[commandItemIdentifier, .flexibleSpace, updateItemIdentifier]
|
||||
[commandItemIdentifier, .flexibleSpace]
|
||||
}
|
||||
|
||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
[commandItemIdentifier, .flexibleSpace, updateItemIdentifier]
|
||||
[commandItemIdentifier, .flexibleSpace]
|
||||
}
|
||||
|
||||
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||
|
|
@ -126,41 +118,8 @@ final class WindowToolbarController: NSObject, NSToolbarDelegate {
|
|||
return item
|
||||
}
|
||||
|
||||
if itemIdentifier == updateItemIdentifier, let updateViewModel {
|
||||
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
|
||||
let view = NonDraggableHostingView(rootView: UpdatePill(model: updateViewModel))
|
||||
let key = ObjectIdentifier(toolbar)
|
||||
item.view = view
|
||||
sizeToolbarItem(for: key, hostingView: view)
|
||||
updateSizeCancellables[key]?.cancel()
|
||||
updateSizeCancellables[key] = updateViewModel.$state
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self, weak view] _ in
|
||||
guard let self, let view else { return }
|
||||
self.sizeToolbarItem(for: key, hostingView: view)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func sizeToolbarItem(for key: ObjectIdentifier, hostingView: NSView) {
|
||||
hostingView.invalidateIntrinsicContentSize()
|
||||
hostingView.layoutSubtreeIfNeeded()
|
||||
let size = hostingView.fittingSize
|
||||
hostingView.setFrameSize(size)
|
||||
hostingView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
hostingView.setContentHuggingPriority(.required, for: .vertical)
|
||||
hostingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
if let constraints = updateViewConstraints[key] {
|
||||
constraints.width.constant = size.width
|
||||
constraints.height.constant = size.height
|
||||
} else {
|
||||
let width = hostingView.widthAnchor.constraint(equalToConstant: size.width)
|
||||
let height = hostingView.heightAnchor.constraint(equalToConstant: size.height)
|
||||
NSLayoutConstraint.activate([width, height])
|
||||
updateViewConstraints[key] = (width: width, height: height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ struct cmuxApp: App {
|
|||
Button("Check for Updates…") {
|
||||
appDelegate.checkForUpdates(nil)
|
||||
}
|
||||
InstallUpdateMenuItem(model: appDelegate.updateViewModel)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue