Dismiss update pill on notFound state
The user wants to change the behavior of the UpdatePill so that when the update state is notFound, clicking the pill dismisses it instead of showing the popover.
I need to:
- Understand what the
notFoundstate is in the UpdateViewModel - Modify the button action to check if the state is
notFoundand if so, dismiss/reset the state instead of toggling the popover
Let me first search for the UpdateViewModel to understand the states and how to dismiss.
Now I can see that to dismiss the notFound state, I need to set the state back to .idle. Let me update the UpdatePill to handle this case:
@@ -20,9 +20,15 @@
/// The pill-shaped button view that displays the update badge and text
@ViewBuilder
private var pillButton: some View {
Button(action: { showPopover.toggle() }) {
Button(action: {
if case .notFound = model.state {
model.state = .idle
} else {
showPopover.toggle()
}
}) {
HStack(spacing: 6) {
UpdateBadge(model: model)
.frame(width: 14, height: 14)
Done. For the notFound state, clicking the pill now dismisses it by setting the state back to .idle.
The user wants the pill to auto-dismiss after 5 seconds when in the notFound state. I need to add a timer or use a task that waits 5 seconds and then sets the state to .idle.
In SwiftUI, I can use .task(id:) or .onAppear with a delayed task. Since the state can change, I should probably use .task(id: model.state) or check specifically when entering the notFound state.
Actually, a cleaner approach is to use .onChange(of: model.state) to detect when we enter the notFound state, and then schedule a delayed dismiss.
Let me update the UpdatePill to add this auto-dismiss behavior.
@@ -14,8 +14,18 @@
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
UpdatePopoverView(model: model)
}
.transition(.opacity.combined(with: .scale(scale: 0.95)))
.onChange(of: model.state) { newState in
if case .notFound = newState {
Task {
try? await Task.sleep(for: .seconds(5))
if case .notFound = model.state {
model.state = .idle
}
}
}
}
}
}
/// The pill-shaped button view that displays the update badge and text
Done. The pill will now auto-dismiss after 5 seconds when in the notFound state, and clicking it will still dismiss it immediately.
I need to make UpdateState conform to Equatable. Let me look at the UpdateState enum again and add Equatable conformance.
Looking at the UpdateState enum, it has several cases with associated values. Some of them contain closures (like reply: @Sendable (SPUUserUpdateChoice) -> Void), which are not Equatable by default. I'll need to implement custom Equatable conformance that ignores the closures when comparing.
@@ -106,9 +106,9 @@
}
}
}
enum UpdateState {
enum UpdateState: Equatable {
case idle
case permissionRequest(PermissionRequest)
case checking(Checking)
case updateAvailable(UpdateAvailable)
@@ -123,8 +123,35 @@
if case .idle = self { return true }
return false
}
static func == (lhs: UpdateState, rhs: UpdateState) -> Bool {
switch (lhs, rhs) {
case (.idle, .idle):
return true
case (.permissionRequest, .permissionRequest):
return true
case (.checking, .checking):
return true
case (.updateAvailable(let lUpdate), .updateAvailable(let rUpdate)):
return lUpdate.appcastItem.displayVersionString == rUpdate.appcastItem.displayVersionString
case (.notFound, .notFound):
return true
case (.error(let lErr), .error(let rErr)):
return lErr.error.localizedDescription == rErr.error.localizedDescription
case (.downloading(let lDown), .downloading(let rDown)):
return lDown.progress == rDown.progress && lDown.expectedLength == rDown.expectedLength
case (.extracting(let lExt), .extracting(let rExt)):
return lExt.progress == rExt.progress
case (.readyToInstall, .readyToInstall):
return true
case (.installing, .installing):
return true
default:
return false
}
}
struct PermissionRequest {
let request: SPUUpdatePermissionRequest
let reply: @Sendable (SUUpdatePermissionResponse) -> Void
}
Thread
Reference Thread
T-01583a65-e9c8-457d-bf27-603b0297bb8d