Move no-update auto-dismiss to controller
This commit is contained in:
parent
db17170b26
commit
8db5ccbb58
3 changed files with 70 additions and 51 deletions
|
|
@ -90,10 +90,17 @@ final class UpdatePillUITests: XCTestCase {
|
|||
return XCTWaiter().wait(for: [expectation], timeout: timeout) == .completed
|
||||
}
|
||||
|
||||
private func assertVisibleSize(_ element: XCUIElement) {
|
||||
let size = element.frame.size
|
||||
XCTAssertGreaterThan(size.width, 20)
|
||||
XCTAssertGreaterThan(size.height, 10)
|
||||
private func assertVisibleSize(_ element: XCUIElement, timeout: TimeInterval = 2.0) {
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
var size = element.frame.size
|
||||
while Date() < deadline {
|
||||
size = element.frame.size
|
||||
if size.width > 20 && size.height > 10 {
|
||||
return
|
||||
}
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
|
||||
}
|
||||
XCTFail("Expected UpdatePill to have visible size, got \(size)")
|
||||
}
|
||||
|
||||
private func attachScreenshot(name: String, screenshot: XCUIScreenshot = XCUIScreen.main.screenshot()) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import Sparkle
|
||||
import Cocoa
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
/// Controller for managing Sparkle updates in cmuxterm.
|
||||
class UpdateController {
|
||||
private(set) var updater: SPUUpdater
|
||||
private let userDriver: UpdateDriver
|
||||
private var installCancellable: AnyCancellable?
|
||||
private var noUpdateDismissCancellable: AnyCancellable?
|
||||
private var noUpdateDismissWorkItem: DispatchWorkItem?
|
||||
|
||||
var viewModel: UpdateViewModel {
|
||||
userDriver.viewModel
|
||||
|
|
@ -26,10 +29,13 @@ class UpdateController {
|
|||
userDriver: userDriver,
|
||||
delegate: userDriver
|
||||
)
|
||||
installNoUpdateDismissObserver()
|
||||
}
|
||||
|
||||
deinit {
|
||||
installCancellable?.cancel()
|
||||
noUpdateDismissCancellable?.cancel()
|
||||
noUpdateDismissWorkItem?.cancel()
|
||||
}
|
||||
|
||||
/// Start the updater. If startup fails, the error is shown via the custom UI.
|
||||
|
|
@ -105,4 +111,57 @@ class UpdateController {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func installNoUpdateDismissObserver() {
|
||||
noUpdateDismissCancellable = Publishers.CombineLatest(viewModel.$state, viewModel.$overrideState)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] state, overrideState in
|
||||
self?.scheduleNoUpdateDismiss(for: state, overrideState: overrideState)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleNoUpdateDismiss(for state: UpdateState, overrideState: UpdateState?) {
|
||||
noUpdateDismissWorkItem?.cancel()
|
||||
noUpdateDismissWorkItem = nil
|
||||
|
||||
guard overrideState == nil else { return }
|
||||
guard case .notFound(let notFound) = state else { return }
|
||||
|
||||
recordUITestTimestamp(key: "noUpdateShownAt")
|
||||
let workItem = DispatchWorkItem { [weak self] in
|
||||
guard let self else { return }
|
||||
guard self.viewModel.overrideState == nil,
|
||||
case .notFound = self.viewModel.state else { return }
|
||||
|
||||
withAnimation(.easeInOut(duration: 0.25)) {
|
||||
self.recordUITestTimestamp(key: "noUpdateHiddenAt")
|
||||
self.viewModel.state = .idle
|
||||
}
|
||||
notFound.acknowledgement()
|
||||
}
|
||||
noUpdateDismissWorkItem = workItem
|
||||
DispatchQueue.main.asyncAfter(
|
||||
deadline: .now() + UpdateTiming.noUpdateDisplayDuration,
|
||||
execute: workItem
|
||||
)
|
||||
}
|
||||
|
||||
private func recordUITestTimestamp(key: String) {
|
||||
#if DEBUG
|
||||
let env = ProcessInfo.processInfo.environment
|
||||
guard env["CMUX_UI_TEST_MODE"] == "1" else { return }
|
||||
guard let path = env["CMUX_UI_TEST_TIMING_PATH"] else { return }
|
||||
|
||||
let url = URL(fileURLWithPath: path)
|
||||
var payload: [String: Double] = [:]
|
||||
if let data = try? Data(contentsOf: url),
|
||||
let object = try? JSONSerialization.jsonObject(with: data) as? [String: Double] {
|
||||
payload = object
|
||||
}
|
||||
payload[key] = Date().timeIntervalSince1970
|
||||
if let data = try? JSONSerialization.data(withJSONObject: payload) {
|
||||
try? data.write(to: url)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import SwiftUI
|
|||
struct UpdatePill: View {
|
||||
@ObservedObject var model: UpdateViewModel
|
||||
@State private var showPopover = false
|
||||
@State private var resetTask: Task<Void, Never>?
|
||||
|
||||
private let textFont = NSFont.systemFont(ofSize: 11, weight: .medium)
|
||||
|
||||
|
|
@ -22,12 +21,6 @@ struct UpdatePill: View {
|
|||
UpdatePopoverView(model: model)
|
||||
}
|
||||
.transition(.opacity.combined(with: .scale(scale: 0.95)))
|
||||
.onAppear {
|
||||
scheduleNoUpdateDismiss(for: model.effectiveState)
|
||||
}
|
||||
.onChange(of: model.effectiveState) { newState in
|
||||
scheduleNoUpdateDismiss(for: newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,44 +64,4 @@ struct UpdatePill: View {
|
|||
let size = (model.maxWidthText as NSString).size(withAttributes: attributes)
|
||||
return size.width
|
||||
}
|
||||
|
||||
private func recordUITestTimestamp(key: String) {
|
||||
#if DEBUG
|
||||
let env = ProcessInfo.processInfo.environment
|
||||
guard env["CMUX_UI_TEST_MODE"] == "1" else { return }
|
||||
guard let path = env["CMUX_UI_TEST_TIMING_PATH"] else { return }
|
||||
|
||||
let url = URL(fileURLWithPath: path)
|
||||
var payload: [String: Double] = [:]
|
||||
if let data = try? Data(contentsOf: url),
|
||||
let object = try? JSONSerialization.jsonObject(with: data) as? [String: Double] {
|
||||
payload = object
|
||||
}
|
||||
payload[key] = Date().timeIntervalSince1970
|
||||
if let data = try? JSONSerialization.data(withJSONObject: payload) {
|
||||
try? data.write(to: url)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func scheduleNoUpdateDismiss(for state: UpdateState) {
|
||||
resetTask?.cancel()
|
||||
if case .notFound(let notFound) = state, model.overrideState == nil {
|
||||
recordUITestTimestamp(key: "noUpdateShownAt")
|
||||
resetTask = Task { [weak model] in
|
||||
let delay = UInt64(UpdateTiming.noUpdateDisplayDuration * 1_000_000_000)
|
||||
try? await Task.sleep(nanoseconds: delay)
|
||||
guard !Task.isCancelled, case .notFound? = model?.state else { return }
|
||||
await MainActor.run {
|
||||
withAnimation(.easeInOut(duration: 0.25)) {
|
||||
recordUITestTimestamp(key: "noUpdateHiddenAt")
|
||||
model?.state = .idle
|
||||
}
|
||||
}
|
||||
notFound.acknowledgement()
|
||||
}
|
||||
} else {
|
||||
resetTask = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue