summaryrefslogtreecommitdiff
path: root/Hutch/Views/Repositories/ArtifactsView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-16 00:29:34 -0500
committerChristian Cleberg <[email protected]>2026-07-16 00:29:34 -0500
commit6898bc3fc00decee7224895ea75908daf0f97f59 (patch)
treeee4cdf2760d45586d20c106212a6cfb1b7d0d9aa /Hutch/Views/Repositories/ArtifactsView.swift
parent2514b58a96ccb73b4feea8f74a2367c72ba824c8 (diff)
downloadhutch-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.swift52
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 {