diff options
| author | Christian Cleberg <[email protected]> | 2026-07-16 00:29:34 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-16 00:29:34 -0500 |
| commit | 6898bc3fc00decee7224895ea75908daf0f97f59 (patch) | |
| tree | ee4cdf2760d45586d20c106212a6cfb1b7d0d9aa /Hutch/Views/Repositories/ArtifactsView.swift | |
| parent | 2514b58a96ccb73b4feea8f74a2367c72ba824c8 (diff) | |
| download | hutch-6898bc3fc00decee7224895ea75908daf0f97f59.tar.gz hutch-6898bc3fc00decee7224895ea75908daf0f97f59.tar.bz2 hutch-6898bc3fc00decee7224895ea75908daf0f97f59.zip | |
fix: blank mailing list from Projects, swipe flicker, hidden upload
Three problems from manual testing.
Opening a mailing list from More → Projects showed a blank screen, while the
same tap on a project pinned to Home worked. handleTabNavigation reset the
target path and appended to it two Task.yields later. When the target tab is
already on screen — Projects lives under More — the reset starts an animated pop
of the view the user is standing on and the appends land mid-animation. From
Home the tab actually changes, so the More stack is quiescent and the appends
land cleanly. Each case now builds its path and assigns it once, so SwiftUI gets
a single diff with nothing to race.
Destructive swipe actions made the row vanish and spring back while the
confirmation was still up. role: .destructive makes SwiftUI perform the row
removal on activation, which allowsFullSwipe: false does not prevent — the
report was a tap, not a full swipe. These buttons only record pending state and
wait for an answer, so they are plain buttons tinted red instead. Six sites: the
two added here, plus trackers, pastes, and tracker ACLs and labels, which had
the same flicker already.
The artifacts upload control was invisible. It was declared as a toolbar item
from a view that is a segment inside RepositoryDetailView's tab switch rather
than its own navigation destination, so it never reached the navigation bar. It
is a row in the list now, and also an action on the empty state — the overlay
covers the list, and a repository with no artifacts is precisely the one that
needs uploading.
Diffstat (limited to 'Hutch/Views/Repositories/ArtifactsView.swift')
| -rw-r--r-- | Hutch/Views/Repositories/ArtifactsView.swift | 52 |
1 files changed, 32 insertions, 20 deletions
diff --git a/Hutch/Views/Repositories/ArtifactsView.swift b/Hutch/Views/Repositories/ArtifactsView.swift index 037c092..752c7c5 100644 --- a/Hutch/Views/Repositories/ArtifactsView.swift +++ b/Hutch/Views/Repositories/ArtifactsView.swift @@ -16,6 +16,21 @@ struct ArtifactsView: View { var body: some View { List { + // In the list rather than the toolbar: this view is a segment inside + // RepositoryDetailView's tab switch, not its own navigation + // destination, and a toolbar declared from there does not reliably + // reach the navigation bar. It also has to be reachable when there are + // no artifacts at all, which is the state a new tag is in. + if isOwnedByCurrentUser { + Button { + showTagPicker = true + } label: { + SwiftUI.Label("Upload Artifact…", systemImage: "square.and.arrow.up") + } + .disabled(viewModel.isMutatingArtifact || viewModel.tags.isEmpty) + .themedRow() + } + ForEach(viewModel.referenceArtifacts) { refArtifacts in Section { ForEach(refArtifacts.artifacts) { artifact in @@ -26,11 +41,12 @@ struct ArtifactsView: View { // action animates the row out before the confirmation. .swipeActions(edge: .trailing, allowsFullSwipe: false) { if isOwnedByCurrentUser { - Button(role: .destructive) { + Button { pendingDeletion = artifact } label: { SwiftUI.Label("Delete", systemImage: "trash") } + .tint(.red) } } } @@ -83,20 +99,6 @@ struct ArtifactsView: View { } message: { _ in Text("This permanently removes the artifact from the tag. This cannot be undone.") } - // The sections above only list tags that already have an artifact, so - // without this there would be no way to attach the first one to a tag. - .toolbar { - if isOwnedByCurrentUser { - ToolbarItem(placement: .topBarTrailing) { - Button { - showTagPicker = true - } label: { - SwiftUI.Label("Upload Artifact", systemImage: "square.and.arrow.up") - } - .disabled(viewModel.isMutatingArtifact || viewModel.tags.isEmpty) - } - } - } .confirmationDialog("Upload to Tag", isPresented: $showTagPicker, titleVisibility: .visible) { ForEach(viewModel.tags.prefix(12), id: \.name) { tag in Button(RepositorySummary.displayBranchName(for: tag.name)) { @@ -125,11 +127,21 @@ struct ArtifactsView: View { retryAction: { await viewModel.loadArtifacts() } ) } else if viewModel.referenceArtifacts.isEmpty { - ContentUnavailableView( - "No Artifacts", - systemImage: "archivebox", - description: Text("This repository has no release artifacts.") - ) + // The overlay covers the whole list, so the upload row above is + // hidden underneath it — and a repository with no artifacts is + // exactly the one that needs uploading. Offer it here too. + ContentUnavailableView { + SwiftUI.Label("No Artifacts", systemImage: "archivebox") + } description: { + Text("This repository has no release artifacts.") + } actions: { + if isOwnedByCurrentUser { + Button("Upload Artifact…") { + showTagPicker = true + } + .disabled(viewModel.isMutatingArtifact || viewModel.tags.isEmpty) + } + } } } .task { |
