summaryrefslogtreecommitdiff
path: root/Hutch/Views/Repositories
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 19:06:54 -0500
committerChristian Cleberg <[email protected]>2026-04-13 19:06:54 -0500
commita6ad2b9815d72575206bdca860eef9db850cdf1c (patch)
treeb4d5f27aa1655e5e40bab178550d6e2576d7df8b /Hutch/Views/Repositories
parent02a44d115b3784231cad640e992d9c18a7f67b40 (diff)
downloadhutch-a6ad2b9815d72575206bdca860eef9db850cdf1c.tar.gz
hutch-a6ad2b9815d72575206bdca860eef9db850cdf1c.tar.bz2
hutch-a6ad2b9815d72575206bdca860eef9db850cdf1c.zip
feat(theme): AMOLED true-black rows via themedRow()
Apply listRowBackground(Color.black) per row in AMOLED mode using ThemedRowStyle and themedRow() across Lists and Forms. Use themedList() for scroll/grouped backgrounds. Treat segmented and clear list rows with isAMOLED-aware listRowBackground where Color.clear was required. Removes reliance on UIKit appearance for list cells on iOS 16+. Implements: https://todo.sr.ht/~ccleberg/hutch/24
Diffstat (limited to 'Hutch/Views/Repositories')
-rw-r--r--Hutch/Views/Repositories/ArtifactsView.swift2
-rw-r--r--Hutch/Views/Repositories/CommitLogView.swift3
-rw-r--r--Hutch/Views/Repositories/FileTreeView.swift6
-rw-r--r--Hutch/Views/Repositories/HgRepositoryDetailView.swift12
-rw-r--r--Hutch/Views/Repositories/HgRepositorySettingsView.swift19
-rw-r--r--Hutch/Views/Repositories/ReferencesListView.swift3
-rw-r--r--Hutch/Views/Repositories/RepositoryACLView.swift9
-rw-r--r--Hutch/Views/Repositories/RepositoryListView.swift11
-rw-r--r--Hutch/Views/Repositories/RepositorySettingsView.swift19
9 files changed, 83 insertions, 1 deletions
diff --git a/Hutch/Views/Repositories/ArtifactsView.swift b/Hutch/Views/Repositories/ArtifactsView.swift
index ef3b972..b8caf4c 100644
--- a/Hutch/Views/Repositories/ArtifactsView.swift
+++ b/Hutch/Views/Repositories/ArtifactsView.swift
@@ -13,9 +13,11 @@ struct ArtifactsView: View {
openURL(artifact.url)
}
}
+ .themedRow()
}
}
}
+ .themedList()
.listStyle(.insetGrouped)
.overlay {
if viewModel.isLoadingArtifacts, viewModel.referenceArtifacts.isEmpty {
diff --git a/Hutch/Views/Repositories/CommitLogView.swift b/Hutch/Views/Repositories/CommitLogView.swift
index bc8ccc9..85e4d7a 100644
--- a/Hutch/Views/Repositories/CommitLogView.swift
+++ b/Hutch/Views/Repositories/CommitLogView.swift
@@ -13,6 +13,7 @@ struct CommitLogView: View {
await viewModel.loadMoreCommitsIfNeeded(currentItem: commit)
}
}
+ .themedRow()
if viewModel.isLoadingMoreCommits {
HStack {
@@ -21,8 +22,10 @@ struct CommitLogView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
+ .themedList()
.listStyle(.plain)
.overlay {
if viewModel.isLoadingCommits, viewModel.commits.isEmpty {
diff --git a/Hutch/Views/Repositories/FileTreeView.swift b/Hutch/Views/Repositories/FileTreeView.swift
index ec33822..b49f361 100644
--- a/Hutch/Views/Repositories/FileTreeView.swift
+++ b/Hutch/Views/Repositories/FileTreeView.swift
@@ -233,7 +233,9 @@ private struct FileTreeContentView: View {
await viewModel.navigateInto(entry: entry)
}
}
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
.refreshable {
await viewModel.loadRootTree()
@@ -848,6 +850,7 @@ private struct RefPickerSheet: View {
)
}
.buttonStyle(.plain)
+ .themedRow()
}
if !viewModel.branches.isEmpty {
@@ -868,6 +871,7 @@ private struct RefPickerSheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
@@ -889,9 +893,11 @@ private struct RefPickerSheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
}
+ .themedList()
.listStyle(.insetGrouped)
.navigationTitle("Select Ref")
.navigationBarTitleDisplayMode(.inline)
diff --git a/Hutch/Views/Repositories/HgRepositoryDetailView.swift b/Hutch/Views/Repositories/HgRepositoryDetailView.swift
index 8362467..047a495 100644
--- a/Hutch/Views/Repositories/HgRepositoryDetailView.swift
+++ b/Hutch/Views/Repositories/HgRepositoryDetailView.swift
@@ -396,7 +396,9 @@ struct HgRepositoryDetailView: View {
.onTapGesture {
Task { await viewModel.openFile(file) }
}
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
}
}
@@ -454,6 +456,7 @@ struct HgRepositoryDetailView: View {
await viewModel.loadMoreLogIfNeeded(currentItem: revision)
}
}
+ .themedRow()
if viewModel.isLoadingMoreLog {
HStack {
@@ -462,8 +465,10 @@ struct HgRepositoryDetailView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
+ .themedList()
.listStyle(.plain)
.overlay {
if viewModel.isLoadingLog, viewModel.log.isEmpty {
@@ -498,7 +503,9 @@ struct HgRepositoryDetailView: View {
} else {
List(revisions) { revision in
namedRevisionRow(revision)
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
}
}
@@ -699,6 +706,7 @@ private struct HgBrowseRefPickerSheet: View {
)
}
.buttonStyle(.plain)
+ .themedRow()
}
if !viewModel.branches.isEmpty {
@@ -719,6 +727,7 @@ private struct HgBrowseRefPickerSheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
@@ -740,6 +749,7 @@ private struct HgBrowseRefPickerSheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
@@ -761,9 +771,11 @@ private struct HgBrowseRefPickerSheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
}
+ .themedList()
.listStyle(.insetGrouped)
.navigationTitle("Select Ref")
.navigationBarTitleDisplayMode(.inline)
diff --git a/Hutch/Views/Repositories/HgRepositorySettingsView.swift b/Hutch/Views/Repositories/HgRepositorySettingsView.swift
index a6aa728..e3c97e0 100644
--- a/Hutch/Views/Repositories/HgRepositorySettingsView.swift
+++ b/Hutch/Views/Repositories/HgRepositorySettingsView.swift
@@ -49,6 +49,7 @@ struct HgRepositorySettingsView: View {
histeditSection(viewModel)
deleteSection(viewModel)
}
+ .themedList()
.srhtErrorBanner(error: $vm.error)
.alert(
"Permanently delete \(repository.owner.canonicalName)/\(repository.name)?",
@@ -101,25 +102,30 @@ struct HgRepositorySettingsView: View {
Text("\(repository.owner.canonicalName)/\(repository.name)")
.font(.body.monospaced())
}
+ .themedRow()
LabeledContent("Forge") {
Text(repositoryForgeLabel(repository.service))
}
+ .themedRow()
LabeledContent("Visibility") {
Text(repositoryVisibilityLabel(viewModel.editedVisibility))
}
+ .themedRow()
}
Section("Repository Details") {
TextField("Description", text: Bindable(viewModel).editedDescription, axis: .vertical)
.lineLimit(3...6)
+ .themedRow()
Picker("Visibility", selection: Bindable(viewModel).editedVisibility) {
Text("Public").tag(Visibility.public)
Text("Unlisted").tag(Visibility.unlisted)
Text("Private").tag(Visibility.private)
}
+ .themedRow()
Button {
Task {
@@ -135,6 +141,7 @@ struct HgRepositorySettingsView: View {
}
}
.disabled(viewModel.isSavingInfo || !viewModel.isInfoDirty)
+ .themedRow()
}
}
@@ -147,9 +154,11 @@ struct HgRepositorySettingsView: View {
ProgressView()
Spacer()
}
+ .themedRow()
} else if viewModel.acls.isEmpty {
Text("No access entries yet.")
.foregroundStyle(.secondary)
+ .themedRow()
} else {
ForEach(viewModel.acls) { entry in
HStack {
@@ -167,6 +176,7 @@ struct HgRepositorySettingsView: View {
}
}
}
+ .themedRow()
}
HStack {
@@ -192,9 +202,11 @@ struct HgRepositorySettingsView: View {
}
.disabled(viewModel.isAddingACL || viewModel.newACLEntity.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
+ .themedRow()
Text("Add a SourceHut user and choose read-only or read/write access.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
}
}
@@ -202,9 +214,11 @@ struct HgRepositorySettingsView: View {
private func featuresSection(_ viewModel: HgRepositorySettingsViewModel) -> some View {
Section("Sensitive Settings") {
Toggle("Hide this repository from public listings", isOn: Bindable(viewModel).editedNonPublishing)
+ .themedRow()
Text("Changes stay pending until you save this section.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
Button {
Task {
@@ -220,6 +234,7 @@ struct HgRepositorySettingsView: View {
}
}
.disabled(viewModel.isSavingInfo || !viewModel.isInfoDirty)
+ .themedRow()
}
}
@@ -230,16 +245,19 @@ struct HgRepositorySettingsView: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.disabled(true)
+ .themedRow()
Text("Removing revisions is not available through the public hg.sr.ht API, so Hutch can’t do this yet.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
Button("Remove Revision", role: .destructive) {
// Not implemented: the hg.sr.ht API does not expose a histedit endpoint.
// This button is disabled until the API supports revision removal.
}
.disabled(true)
+ .themedRow()
}
}
@@ -258,6 +276,7 @@ struct HgRepositorySettingsView: View {
}
}
.disabled(viewModel.isDeleting)
+ .themedRow()
} header: {
Text("Danger Zone")
}
diff --git a/Hutch/Views/Repositories/ReferencesListView.swift b/Hutch/Views/Repositories/ReferencesListView.swift
index 6470a42..0e3be0a 100644
--- a/Hutch/Views/Repositories/ReferencesListView.swift
+++ b/Hutch/Views/Repositories/ReferencesListView.swift
@@ -10,6 +10,7 @@ struct ReferencesListView: View {
ForEach(viewModel.branches, id: \.name) { ref in
ReferenceRow(reference: ref, prefix: "refs/heads/")
}
+ .themedRow()
}
}
@@ -18,9 +19,11 @@ struct ReferencesListView: View {
ForEach(viewModel.tags, id: \.name) { ref in
ReferenceRow(reference: ref, prefix: "refs/tags/")
}
+ .themedRow()
}
}
}
+ .themedList()
.listStyle(.insetGrouped)
.overlay {
if viewModel.isLoadingRefs, viewModel.branches.isEmpty, viewModel.tags.isEmpty {
diff --git a/Hutch/Views/Repositories/RepositoryACLView.swift b/Hutch/Views/Repositories/RepositoryACLView.swift
index 0368b4f..29ded84 100644
--- a/Hutch/Views/Repositories/RepositoryACLView.swift
+++ b/Hutch/Views/Repositories/RepositoryACLView.swift
@@ -6,6 +6,7 @@ struct RepositoryACLView: View {
let showsDoneButton: Bool
@Environment(\.dismiss) private var dismiss
+ @Environment(\.isAMOLEDTheme) private var isAMOLED
@State private var viewModel: RepositoryACLViewModel?
@State private var pendingDeletion: RepositoryACLEntry?
@State private var showAddSheet = false
@@ -70,7 +71,7 @@ struct RepositoryACLView: View {
Text("Only the repository owner currently has access.")
}
.frame(maxWidth: .infinity)
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
} else {
Section {
ForEach(viewModel.visibleEntries) { entry in
@@ -86,9 +87,11 @@ struct RepositoryACLView: View {
}
)
}
+ .themedRow()
}
}
}
+ .themedList()
.listStyle(.insetGrouped)
.refreshable {
await viewModel.load()
@@ -192,11 +195,13 @@ private struct RepositoryACLAddUserView: View {
TextField("Username or ~username", text: $viewModel.addUsername)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
+ .themedRow()
if let validation = inlineValidationMessage {
Text(validation)
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
}
}
@@ -207,8 +212,10 @@ private struct RepositoryACLAddUserView: View {
}
}
.pickerStyle(.segmented)
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Add User")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/Repositories/RepositoryListView.swift b/Hutch/Views/Repositories/RepositoryListView.swift
index 0258a58..08ebb5c 100644
--- a/Hutch/Views/Repositories/RepositoryListView.swift
+++ b/Hutch/Views/Repositories/RepositoryListView.swift
@@ -78,6 +78,7 @@ struct RepositoryListView: View {
.foregroundStyle(.secondary)
}
.listRowSeparator(.hidden)
+ .themedRow()
}
ForEach(viewModel.repositories) { repo in
@@ -89,6 +90,7 @@ struct RepositoryListView: View {
}
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
}
+ .themedRow()
if viewModel.isLoadingMore {
HStack {
@@ -97,6 +99,7 @@ struct RepositoryListView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
.themedList()
@@ -223,16 +226,20 @@ private struct CreateRepositorySheet: View {
Text(service.displayName).tag(service)
}
}
+ .themedRow()
TextField("Repository name", text: $name)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
+ .themedRow()
TextField("Short description (optional)", text: $description, axis: .vertical)
.lineLimit(2...4)
+ .themedRow()
Picker("Visibility", selection: $visibility) {
Text("Public").tag(Visibility.public)
Text("Unlisted").tag(Visibility.unlisted)
Text("Private").tag(Visibility.private)
}
+ .themedRow()
}
Section("Import Existing Repository") {
@@ -241,16 +248,20 @@ private struct CreateRepositorySheet: View {
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.keyboardType(.URL)
+ .themedRow()
Text("Import an existing Git repository from a remote URL.")
.font(.footnote)
.foregroundStyle(.secondary)
+ .themedRow()
} else {
Text("Importing a Mercurial repository from a remote URL is not available through the public API.")
.font(.footnote)
.foregroundStyle(.secondary)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle(service == .git ? "New Git Repository" : "New Mercurial Repository")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/Repositories/RepositorySettingsView.swift b/Hutch/Views/Repositories/RepositorySettingsView.swift
index 4736619..44b50ae 100644
--- a/Hutch/Views/Repositories/RepositorySettingsView.swift
+++ b/Hutch/Views/Repositories/RepositorySettingsView.swift
@@ -51,6 +51,7 @@ struct RepositorySettingsView: View {
visibilitySection(viewModel)
deleteSection(viewModel)
}
+ .themedList()
.srhtErrorBanner(error: $vm.error)
.alert(
visibilityConfirmationTitle(for: viewModel),
@@ -97,15 +98,18 @@ struct RepositorySettingsView: View {
Text("\(viewModel.repository.owner.canonicalName)/\(viewModel.repository.name)")
.font(.body.monospaced())
}
+ .themedRow()
LabeledContent("Default Branch") {
Text(viewModel.currentDefaultBranchName)
.font(.body.monospaced())
}
+ .themedRow()
LabeledContent("Visibility") {
Text(repositoryVisibilityLabel(viewModel.repository.visibility))
}
+ .themedRow()
}
}
@@ -115,22 +119,27 @@ struct RepositorySettingsView: View {
TextField("Repository name", text: Bindable(viewModel).editedName)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
+ .themedRow()
TextField("Description", text: Bindable(viewModel).editedDescription, axis: .vertical)
.lineLimit(2...4)
+ .themedRow()
if let metadataValidationMessage = viewModel.metadataValidationMessage {
Text(metadataValidationMessage)
.font(.caption)
.foregroundStyle(.red)
+ .themedRow()
} else if viewModel.normalizedEditedName != viewModel.repository.name {
Text("Changing the repository name updates the repository URL.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
} else {
Text("Name and description stay pending until you save this section.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
}
Button {
@@ -153,6 +162,7 @@ struct RepositorySettingsView: View {
!viewModel.isMetadataDirty ||
viewModel.metadataValidationMessage != nil
)
+ .themedRow()
} header: {
Text("Repository Details")
}
@@ -165,10 +175,12 @@ struct RepositorySettingsView: View {
Text(viewModel.currentDefaultBranchName)
.font(.body.monospaced())
}
+ .themedRow()
if viewModel.branches.isEmpty {
Text("This repository doesn't have any branches yet.")
.foregroundStyle(.secondary)
+ .themedRow()
} else {
Picker("Branch", selection: Bindable(viewModel).editedHead) {
ForEach(viewModel.availableBranchNames, id: \.self) { branch in
@@ -177,10 +189,12 @@ struct RepositorySettingsView: View {
.tag(branch)
}
}
+ .themedRow()
Text("Changes stay pending until you set the new default branch.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
Button {
Task {
@@ -202,6 +216,7 @@ struct RepositorySettingsView: View {
!viewModel.isDefaultBranchDirty ||
viewModel.defaultBranchValidationMessage != nil
)
+ .themedRow()
}
} header: {
Text("Default Branch")
@@ -216,10 +231,12 @@ struct RepositorySettingsView: View {
Text("Unlisted").tag(Visibility.unlisted)
Text("Private").tag(Visibility.private)
}
+ .themedRow()
Text("Visibility changes apply immediately after you confirm them.")
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
Button {
showVisibilityConfirmation = true
@@ -233,6 +250,7 @@ struct RepositorySettingsView: View {
}
}
.disabled(viewModel.isMutating || !viewModel.isVisibilityDirty)
+ .themedRow()
} header: {
Text("Sensitive Settings")
}
@@ -253,6 +271,7 @@ struct RepositorySettingsView: View {
}
}
.disabled(viewModel.isMutating)
+ .themedRow()
} header: {
Text("Danger Zone")
}