Add role-only AsyncButton initializer - #28
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📝 WalkthroughWalkthrough
Sequence Diagram(s)sequenceDiagram
participant TestUI
participant AsyncButton
participant actionSignal
participant ViewState
TestUI->>AsyncButton: Tap role-only button
AsyncButton->>ViewState: Enter processing state
AsyncButton->>actionSignal: Run async action
actionSignal-->>AsyncButton: Complete action
AsyncButton->>ViewState: Exit processing state
AsyncButton-->>TestUI: Show completion and re-enable
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
Sources/SpeziViews/Views/Button/AsyncButton.swift (1)
279-295: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a non-throwing role-only initializer without
state.The existing custom-label initializers provide variants for both throwing actions (requiring a
statebinding) and non-throwing actions (omitting thestatebinding). To maintain API symmetry and developer ergonomics, consider adding a non-throwing role-only initializer that omits thestateparameter and defaults to.constant(.idle).💡 Proposed addition
+ /// Creates an async button with a system-provided label for the supplied role. + /// - Parameters: + /// - role: A button role that determines the system-provided label. + /// - action: An asynchronous button action. + `@available`(iOS 26, macOS 26, tvOS 26, watchOS 26, visionOS 26, *) + public init( + role: ButtonRole, + action: `@MainActor` `@escaping` () async -> Void + ) where Label == DefaultButtonLabel { + self.role = role + self._viewState = .constant(.idle) + self.action = action + self.label = nil + } + /// Creates an async throwing button with a system-provided label for the supplied role. /// - Parameters:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/SpeziViews/Views/Button/AsyncButton.swift` around lines 279 - 295, In AsyncButton’s role-only initializer set, add a non-throwing overload accepting only ButtonRole and an asynchronous non-throwing action, without a state parameter. Initialize the view state with a constant idle binding and preserve the system-provided DefaultButtonLabel behavior of the existing role-based initializer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/SpeziViews/Views/Button/AsyncButton.swift`:
- Around line 124-132: The .task setup in the AsyncButton view recreates
actionSignal and can discard taps submitted before the task starts. Preserve a
single action-delivery path across view appearances by removing per-appearance
stream replacement and using stable Task management or a persisted continuation;
ensure any action queued while buttonState is .disabled is recovered and
processed when the task begins, and cancel active work on disappearance as
appropriate.
---
Nitpick comments:
In `@Sources/SpeziViews/Views/Button/AsyncButton.swift`:
- Around line 279-295: In AsyncButton’s role-only initializer set, add a
non-throwing overload accepting only ButtonRole and an asynchronous non-throwing
action, without a state parameter. Initialize the view state with a constant
idle binding and preserve the system-provided DefaultButtonLabel behavior of the
existing role-based initializer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 69130631-7c25-417e-9482-bbc717a3011e
📒 Files selected for processing (3)
Sources/SpeziViews/Views/Button/AsyncButton.swiftTests/SpeziViewsTests/UITests/TestApp/ViewsTests/AsyncButtonToolbarTest.swiftTests/SpeziViewsTests/UITests/TestAppUITests/SpeziViews/ViewsTests.swift
|
Belated answer to the follow-up-issue question — no, please don't auto-open one. If this gets filed it should be by a maintainer who has decided it's real, not by a bot off a PR thread; an issue nobody chose to own is just noise in the tracker. Since I'm here though, one correction to my own earlier reply, because I was less precise than the finding deserved. I said the lifecycle predates this PR and left it there. That's true and it's still why it stays out of private func submitAction() {
guard buttonState == .idle else { return }
buttonState = .disabled // set BEFORE the yield
self.actionSignal.continuation.yield(.runAction(self.action))
}and the only path back is What I have not shown is that the window is reachable — whether a tap can actually land between the button becoming hit-testable and If it is worth closing regardless of reachability, the shape that removes the window entirely is to stop replacing the stream: keep the one made at
|
7022585 to
e0d5417
Compare
The custom-label initializers come in throwing and non-throwing pairs, but the role-only initializer existed only in the throwing form that requires a ViewState binding. Add the non-throwing overload so callers that do not need error propagation can use the system-provided role label, keeping the constant idle state and the DefaultButtonLabel rendering path. Cover the new initializer in the existing AsyncButton toolbar fixture.
e0d5417 to
d7ad5aa
Compare
Summary
AsyncButton(role:state:action:)initializer requested in [SpeziViews] addAsyncButton.init(role:state:action:)#26.overlayand.listRowprocessing states in the existing UI fixtureTesting
testAsyncButtonInToolbartestAsyncButtonDebounceSpeziViewsSwift package target buildCloses #26