summaryrefslogtreecommitdiff
path: root/Hutch/Views
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
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')
-rw-r--r--Hutch/Views/Auth/AuthView.swift7
-rw-r--r--Hutch/Views/Builds/BuildDetailView.swift25
-rw-r--r--Hutch/Views/Builds/BuildListView.swift14
-rw-r--r--Hutch/Views/Home/HomeView.swift6
-rw-r--r--Hutch/Views/Inbox/ThreadDetailView.swift6
-rw-r--r--Hutch/Views/Lists/MailingListListView.swift2
-rw-r--r--Hutch/Views/Lookup/LookupView.swift5
-rw-r--r--Hutch/Views/Lookup/UserProfileView.swift24
-rw-r--r--Hutch/Views/Lookup/UserRepositoriesView.swift2
-rw-r--r--Hutch/Views/Lookup/UserTrackersView.swift2
-rw-r--r--Hutch/Views/More/AccountSwitcherView.swift3
-rw-r--r--Hutch/Views/More/AddAccountView.swift3
-rw-r--r--Hutch/Views/More/ManPageBrowserView.swift2
-rw-r--r--Hutch/Views/More/MoreView.swift9
-rw-r--r--Hutch/Views/More/ProfileView.swift31
-rw-r--r--Hutch/Views/Pastes/PasteDetailView.swift8
-rw-r--r--Hutch/Views/Pastes/PasteListView.swift8
-rw-r--r--Hutch/Views/Projects/ProjectDetailView.swift6
-rw-r--r--Hutch/Views/Projects/ProjectMailingListView.swift2
-rw-r--r--Hutch/Views/Projects/ProjectsListView.swift1
-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
-rw-r--r--Hutch/Views/Settings/SettingsView.swift23
-rw-r--r--Hutch/Views/SystemStatus/SystemStatusView.swift7
-rw-r--r--Hutch/Views/Tickets/TicketDetailView.swift13
-rw-r--r--Hutch/Views/Tickets/TicketListView.swift23
-rw-r--r--Hutch/Views/Tickets/TrackerListView.swift3
-rw-r--r--Hutch/Views/Tickets/TrackerManagementView.swift37
-rw-r--r--Hutch/Views/Work/WorkView.swift11
36 files changed, 360 insertions, 7 deletions
diff --git a/Hutch/Views/Auth/AuthView.swift b/Hutch/Views/Auth/AuthView.swift
index 5a249ef..d114981 100644
--- a/Hutch/Views/Auth/AuthView.swift
+++ b/Hutch/Views/Auth/AuthView.swift
@@ -15,6 +15,7 @@ struct TokenEntryView: View {
Form {
Section {
Text("Enter your SourceHut personal access token to connect.")
+ .themedRow()
} header: {
Text("Welcome to Hutch")
} footer: {
@@ -27,6 +28,7 @@ struct TokenEntryView: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.disabled(isConnecting)
+ .themedRow()
} header: {
Text("Token")
}
@@ -40,6 +42,7 @@ struct TokenEntryView: View {
.foregroundStyle(.red)
}
.foregroundStyle(.red)
+ .themedRow()
}
}
@@ -56,22 +59,26 @@ struct TokenEntryView: View {
}
}
.disabled(tokenTrimmed.isEmpty || isConnecting)
+ .themedRow()
}
Section {
Link(destination: createAccountURL) {
Label("Create SourceHut account", systemImage: "person.badge.plus")
}
+ .themedRow()
Link(destination: personalAccessTokensURL) {
Label("Create Personal Access Token", systemImage: "key")
}
+ .themedRow()
} header: {
Text("Need an account?")
} footer: {
Text("These links open SourceHut in your browser. After signing up, create a Personal Access Token there and paste it here.")
}
}
+ .themedList()
.navigationTitle("Hutch")
}
}
diff --git a/Hutch/Views/Builds/BuildDetailView.swift b/Hutch/Views/Builds/BuildDetailView.swift
index 5895f48..46fb42d 100644
--- a/Hutch/Views/Builds/BuildDetailView.swift
+++ b/Hutch/Views/Builds/BuildDetailView.swift
@@ -176,26 +176,34 @@ struct BuildDetailView: View {
.font(.subheadline.weight(.medium))
}
}
+ .themedRow()
if let note = job.note, !note.isEmpty {
LabeledContent("Note", value: note)
+ .themedRow()
}
if let image = job.image {
LabeledContent("Image", value: image)
+ .themedRow()
}
if !job.tags.isEmpty {
LabeledContent("Tags", value: job.tags.joined(separator: ", "))
+ .themedRow()
}
if let visibility = job.visibility {
LabeledContent("Visibility", value: visibility.rawValue.capitalized)
+ .themedRow()
}
LabeledContent("Owner", value: job.owner.canonicalName)
+ .themedRow()
LabeledContent("Created", value: job.created.relativeDescription)
+ .themedRow()
LabeledContent("Updated", value: job.updated.relativeDescription)
+ .themedRow()
}
if appState.isDebugModeEnabled {
@@ -211,9 +219,11 @@ struct BuildDetailView: View {
url: \(SRHTWebURL.build(jobId: job.id, ownerCanonicalName: job.owner.canonicalName)?.absoluteString ?? "unavailable")
"""
)
+ .themedRow()
if let rawJobResponse = viewModel.rawJobResponse {
DebugTextBlock(title: "Raw Response", content: rawJobResponse)
+ .themedRow()
}
}
}
@@ -237,6 +247,7 @@ struct BuildDetailView: View {
}
}
.disabled(isOpeningRepository)
+ .themedRow()
}
}
@@ -248,6 +259,7 @@ struct BuildDetailView: View {
openURL(url)
}
}
+ .themedRow()
} header: {
Text("Artifacts")
} footer: {
@@ -275,6 +287,7 @@ struct BuildDetailView: View {
}
}
.foregroundStyle(.primary)
+ .themedRow()
} header: {
HStack(spacing: 6) {
TaskStatusIcon(status: task.status)
@@ -299,6 +312,7 @@ struct BuildDetailView: View {
}
}
.disabled(viewModel.isCancelling)
+ .themedRow()
}
}
@@ -319,6 +333,7 @@ struct BuildDetailView: View {
}
}
.disabled(viewModel.isRebuilding)
+ .themedRow()
Button {
showEditResubmitSheet = true
@@ -326,11 +341,13 @@ struct BuildDetailView: View {
Text("Edit & Resubmit")
}
.disabled(viewModel.isSubmittingEditedBuild)
+ .themedRow()
} footer: {
Text("Creates a new build using this job’s saved manifest, tags, note, and visibility.")
}
}
}
+ .themedList()
.task(id: job.id) {
RecentActivityStore.recordBuild(
jobId: job.id,
@@ -463,26 +480,33 @@ private struct EditResubmitBuildSheet: View {
.lineLimit(12...24)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
+ .themedRow()
}
Section("Build Options") {
TextField("Note (optional)", text: $note)
+ .themedRow()
TextField("Tags (comma-separated, optional)", text: $tagsText)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
+ .themedRow()
Picker("Visibility", selection: $visibility) {
Text("Public").tag(Visibility.public)
Text("Unlisted").tag(Visibility.unlisted)
Text("Private").tag(Visibility.private)
}
+ .themedRow()
Toggle("Start build now", isOn: $execute)
+ .themedRow()
Toggle("Allow build secrets", isOn: $secrets)
+ .themedRow()
}
Section {
Text("This submits a new build. “Start build now” and “Allow build secrets” use local defaults because the current job does not include those original values.")
.font(.footnote)
.foregroundStyle(.secondary)
+ .themedRow()
}
if let actionError = viewModel.actionError {
@@ -494,6 +518,7 @@ private struct EditResubmitBuildSheet: View {
.foregroundStyle(.red)
}
.foregroundStyle(.red)
+ .themedRow()
}
}
}
diff --git a/Hutch/Views/Builds/BuildListView.swift b/Hutch/Views/Builds/BuildListView.swift
index f4647e4..fc91ea1 100644
--- a/Hutch/Views/Builds/BuildListView.swift
+++ b/Hutch/Views/Builds/BuildListView.swift
@@ -5,6 +5,7 @@ struct BuildListView: View {
@AppStorage(AppStorageKeys.buildsAutoRefreshInterval) private var autoRefreshRawValue = 0
@AppStorage(AppStorageKeys.buildsRepoFilter) private var savedRepoFilter = ""
@Environment(AppState.self) private var appState
+ @Environment(\.isAMOLEDTheme) private var isAMOLED
@State private var viewModel: BuildListViewModel?
@State private var showSubmitSheet = false
@State private var submittedJobId: Int?
@@ -134,7 +135,7 @@ struct BuildListView: View {
.padding(.top, 6)
.padding(.bottom, 10)
.listRowInsets(EdgeInsets())
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
.listRowSeparator(.hidden)
ForEach(viewModel.filteredJobs) { job in
@@ -181,6 +182,7 @@ struct BuildListView: View {
await viewModel.loadMoreIfNeeded(currentItem: job)
}
}
+ .themedRow()
if viewModel.isLoadingMore {
HStack {
@@ -189,6 +191,7 @@ struct BuildListView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
}
@@ -279,26 +282,33 @@ private struct SubmitBuildSheet: View {
.lineLimit(12...24)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
+ .themedRow()
}
Section("Build Options") {
TextField("Note (optional)", text: $note)
+ .themedRow()
TextField("Tags (comma-separated, optional)", text: $tagsText)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
+ .themedRow()
Picker("Visibility", selection: $visibility) {
Text("Public").tag(Visibility.public)
Text("Unlisted").tag(Visibility.unlisted)
Text("Private").tag(Visibility.private)
}
+ .themedRow()
Toggle("Start build now", isOn: $execute)
+ .themedRow()
Toggle("Allow build secrets", isOn: $secrets)
+ .themedRow()
}
Section {
Text("You need a valid builds.sr.ht manifest and a token with BUILDS:RW.")
.font(.footnote)
.foregroundStyle(.secondary)
+ .themedRow()
}
if let error = viewModel.error {
@@ -310,9 +320,11 @@ private struct SubmitBuildSheet: View {
.foregroundStyle(.red)
}
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle("Submit Build")
.navigationBarTitleDisplayMode(.inline)
.onDisappear {
diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift
index 105b62b..b10926a 100644
--- a/Hutch/Views/Home/HomeView.swift
+++ b/Hutch/Views/Home/HomeView.swift
@@ -94,6 +94,7 @@ struct HomeView: View {
)
}
.buttonStyle(.plain)
+ .themedRow()
}
}
}
@@ -109,6 +110,7 @@ struct HomeView: View {
emphasis: .action
)
}
+ .themedRow()
}
}
@@ -126,6 +128,7 @@ struct HomeView: View {
.disabled(isOpeningRecentItem)
.listRowSeparator(.hidden)
}
+ .themedRow()
}
}
}
@@ -143,6 +146,7 @@ struct HomeView: View {
emphasis: .monitoring
)
}
+ .themedRow()
}
}
@@ -156,6 +160,7 @@ struct HomeView: View {
} label: {
HomeCompactMessageRow(text: "Pin projects for quick access", systemImage: "pin")
}
+ .themedRow()
} else {
LazyVGrid(
columns: [
@@ -174,6 +179,7 @@ struct HomeView: View {
}
}
.padding(.vertical, 2)
+ .themedRow()
}
}
}
diff --git a/Hutch/Views/Inbox/ThreadDetailView.swift b/Hutch/Views/Inbox/ThreadDetailView.swift
index bde8172..028964c 100644
--- a/Hutch/Views/Inbox/ThreadDetailView.swift
+++ b/Hutch/Views/Inbox/ThreadDetailView.swift
@@ -123,6 +123,7 @@ struct ThreadDetailView: View {
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
+ .themedRow()
}
Section("Related") {
@@ -138,6 +139,7 @@ struct ThreadDetailView: View {
} label: {
Label(thread.listDisplayName, systemImage: "list.bullet")
}
+ .themedRow()
if let repo = self.thread.repo {
Button {
@@ -157,6 +159,7 @@ struct ThreadDetailView: View {
}
}
.disabled(isOpeningRepository)
+ .themedRow()
}
}
@@ -165,6 +168,7 @@ struct ThreadDetailView: View {
Text(partialWarning)
.font(.caption)
.foregroundStyle(.secondary)
+ .themedRow()
}
}
@@ -184,8 +188,10 @@ struct ThreadDetailView: View {
}
)
}
+ .themedRow()
}
}
+ .themedList()
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
diff --git a/Hutch/Views/Lists/MailingListListView.swift b/Hutch/Views/Lists/MailingListListView.swift
index eca7343..2fcc640 100644
--- a/Hutch/Views/Lists/MailingListListView.swift
+++ b/Hutch/Views/Lists/MailingListListView.swift
@@ -142,7 +142,9 @@ struct MailingListListView: View {
.padding(.vertical, 2)
}
}
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
.searchable(
text: $vm.searchText,
diff --git a/Hutch/Views/Lookup/LookupView.swift b/Hutch/Views/Lookup/LookupView.swift
index e53dd33..f45082d 100644
--- a/Hutch/Views/Lookup/LookupView.swift
+++ b/Hutch/Views/Lookup/LookupView.swift
@@ -356,6 +356,7 @@ struct LookupView: View {
}
}
.pickerStyle(.menu)
+ .themedRow()
TextField(
vm.selectedType.inputLabel,
@@ -368,6 +369,7 @@ struct LookupView: View {
.onSubmit {
Task { await vm.lookup() }
}
+ .themedRow()
}
Section {
@@ -382,6 +384,7 @@ struct LookupView: View {
ProgressView()
}
}
+ .themedRow()
}
if !vm.history.isEmpty {
@@ -403,11 +406,13 @@ struct LookupView: View {
}
.disabled(vm.isLooking)
}
+ .themedRow()
Button("Clear History", role: .destructive) {
vm.clearHistory()
}
.disabled(vm.isLooking)
+ .themedRow()
}
}
}
diff --git a/Hutch/Views/Lookup/UserProfileView.swift b/Hutch/Views/Lookup/UserProfileView.swift
index 4fbb5a0..8472a90 100644
--- a/Hutch/Views/Lookup/UserProfileView.swift
+++ b/Hutch/Views/Lookup/UserProfileView.swift
@@ -2,6 +2,7 @@ import SwiftUI
struct UserProfileView: View {
@Environment(AppState.self) private var appState
+ @Environment(\.isAMOLEDTheme) private var isAMOLED
@AppStorage(AppStorageKeys.contributionGraphsEnabled, store: .standard) private var contributionGraphsEnabled = true
let user: User
@@ -55,33 +56,41 @@ struct UserProfileView: View {
}
Spacer()
}
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
}
}
Section {
LabeledContent("Username", value: user.username)
+ .themedRow()
LabeledContent("Canonical Name", value: user.canonicalName)
+ .themedRow()
if let userType = user.userType {
LabeledContent("User Type", value: userType)
+ .themedRow()
}
if let pronouns = user.pronouns {
LabeledContent("Pronouns", value: pronouns)
+ .themedRow()
}
if let suspensionNotice = user.suspensionNotice {
LabeledContent("Suspension Notice", value: suspensionNotice)
+ .themedRow()
}
}
Section {
LabeledContent("Email", value: user.email)
+ .themedRow()
if let urlString = user.url, let url = URL(string: urlString) {
LabeledContent("URL") {
Link(urlString, destination: url)
}
+ .themedRow()
}
if let location = user.location {
LabeledContent("Location", value: location)
+ .themedRow()
}
}
@@ -91,6 +100,7 @@ struct UserProfileView: View {
.frame(maxWidth: .infinity, alignment: .leading)
.tint(.accentColor)
.textSelection(.enabled)
+ .themedRow()
}
}
@@ -98,9 +108,11 @@ struct UserProfileView: View {
Section {
if let created = user.created {
LabeledContent("Joined", value: formattedTimestamp(created))
+ .themedRow()
}
if let updated = user.updated {
LabeledContent("Updated", value: formattedTimestamp(updated))
+ .themedRow()
}
}
}
@@ -118,15 +130,18 @@ struct UserProfileView: View {
error: viewModel.contributionsError ?? viewModel.contributionStatusText,
isIndexedButEmpty: viewModel.isContributionActivityIndexedButEmpty
)
+ .themedRow()
}
}
Section {
if viewModel.isLoadingRepositories && viewModel.repositories.isEmpty {
ProgressView()
+ .themedRow()
} else if viewModel.repositories.isEmpty {
Text("No public repositories.")
.foregroundStyle(.secondary)
+ .themedRow()
} else {
ForEach(viewModel.repositories.prefix(4)) { repo in
NavigationLink {
@@ -137,10 +152,12 @@ struct UserProfileView: View {
RepositoryRowView(repository: repo, buildStatus: .none)
}
}
+ .themedRow()
if viewModel.repositories.count > 4 {
NavigationLink("See All") {
UserRepositoriesView(viewModel: viewModel)
}
+ .themedRow()
}
}
} header: {
@@ -150,9 +167,11 @@ struct UserProfileView: View {
Section {
if viewModel.isLoadingTrackers && viewModel.trackers.isEmpty {
ProgressView()
+ .themedRow()
} else if viewModel.trackers.isEmpty {
Text("No public trackers.")
.foregroundStyle(.secondary)
+ .themedRow()
} else {
ForEach(viewModel.trackers.prefix(4)) { tracker in
NavigationLink {
@@ -161,10 +180,12 @@ struct UserProfileView: View {
UserProfileTrackerRowView(tracker: tracker)
}
}
+ .themedRow()
if viewModel.trackers.count > 4 {
NavigationLink("See All") {
UserTrackersView(viewModel: viewModel)
}
+ .themedRow()
}
}
} header: {
@@ -172,6 +193,7 @@ struct UserProfileView: View {
}
}
}
+ .themedList()
.listStyle(.insetGrouped)
.navigationTitle(user.canonicalName)
.navigationBarTitleDisplayMode(.inline)
diff --git a/Hutch/Views/Lookup/UserRepositoriesView.swift b/Hutch/Views/Lookup/UserRepositoriesView.swift
index 7aeea10..c2ca65c 100644
--- a/Hutch/Views/Lookup/UserRepositoriesView.swift
+++ b/Hutch/Views/Lookup/UserRepositoriesView.swift
@@ -14,7 +14,9 @@ struct UserRepositoriesView: View {
RepositoryRowView(repository: repo, buildStatus: .none)
}
}
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
.navigationTitle("Repositories")
.navigationBarTitleDisplayMode(.inline)
diff --git a/Hutch/Views/Lookup/UserTrackersView.swift b/Hutch/Views/Lookup/UserTrackersView.swift
index 17916da..c98d839 100644
--- a/Hutch/Views/Lookup/UserTrackersView.swift
+++ b/Hutch/Views/Lookup/UserTrackersView.swift
@@ -12,7 +12,9 @@ struct UserTrackersView: View {
UserProfileTrackerRowView(tracker: tracker)
}
}
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
.navigationTitle("Trackers")
.navigationBarTitleDisplayMode(.inline)
diff --git a/Hutch/Views/More/AccountSwitcherView.swift b/Hutch/Views/More/AccountSwitcherView.swift
index 5fde9c5..ce69279 100644
--- a/Hutch/Views/More/AccountSwitcherView.swift
+++ b/Hutch/Views/More/AccountSwitcherView.swift
@@ -48,6 +48,7 @@ struct AccountSwitcherView: View {
pendingRemoval = appState.accounts[index]
}
}
+ .themedRow()
}
Section {
@@ -57,8 +58,10 @@ struct AccountSwitcherView: View {
Label("Add Account", systemImage: "plus.circle")
}
.disabled(isSwitching)
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Accounts")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/More/AddAccountView.swift b/Hutch/Views/More/AddAccountView.swift
index cfaa2b2..c15db09 100644
--- a/Hutch/Views/More/AddAccountView.swift
+++ b/Hutch/Views/More/AddAccountView.swift
@@ -15,6 +15,7 @@ struct AddAccountView: View {
SecureField("Personal Access Token", text: $token)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
+ .themedRow()
} footer: {
Text("Generate a token at meta.sr.ht → OAuth2 clients.")
}
@@ -23,9 +24,11 @@ struct AddAccountView: View {
Section {
Text(errorMessage)
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle("Add Account")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/More/ManPageBrowserView.swift b/Hutch/Views/More/ManPageBrowserView.swift
index f854c79..c22eafe 100644
--- a/Hutch/Views/More/ManPageBrowserView.swift
+++ b/Hutch/Views/More/ManPageBrowserView.swift
@@ -25,8 +25,10 @@ struct ManPageBrowserView: View {
Text(doc.title)
}
}
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Man Pages")
.navigationBarTitleDisplayMode(.inline)
}
diff --git a/Hutch/Views/More/MoreView.swift b/Hutch/Views/More/MoreView.swift
index 8be25ca..4666e53 100644
--- a/Hutch/Views/More/MoreView.swift
+++ b/Hutch/Views/More/MoreView.swift
@@ -16,24 +16,29 @@ struct MoreView: View {
NavigationLink(value: MoreRoute.lookup) {
Label("Look Up", systemImage: "magnifyingglass")
}
+ .themedRow()
}
Section("Other Services") {
NavigationLink(value: MoreRoute.projects) {
Label("Projects", systemImage: "square.stack.3d.up")
}
+ .themedRow()
NavigationLink(value: MoreRoute.lists) {
Label("Mailing Lists", systemImage: "list.bullet.rectangle")
}
+ .themedRow()
NavigationLink(value: MoreRoute.manPageBrowser) {
Label("Man Pages", systemImage: "book")
}
+ .themedRow()
NavigationLink(value: MoreRoute.pastes) {
Label("Pastes", systemImage: "doc.on.clipboard")
}
+ .themedRow()
NavigationLink(value: MoreRoute.systemStatus) {
SystemStatusSummaryRow(
@@ -43,16 +48,19 @@ struct MoreView: View {
isShowingStaleData: viewModel?.isShowingStaleSystemStatus ?? false
)
}
+ .themedRow()
}
Section("Meta") {
NavigationLink(value: MoreRoute.profile) {
Label("Profile", systemImage: "person.text.rectangle")
}
+ .themedRow()
NavigationLink(value: MoreRoute.settings) {
Label("Settings", systemImage: "gear")
}
+ .themedRow()
}
Section {
@@ -61,6 +69,7 @@ struct MoreView: View {
Label(item.title, systemImage: "safari")
}
}
+ .themedRow()
} header: {
Text("External Links")
} footer: {
diff --git a/Hutch/Views/More/ProfileView.swift b/Hutch/Views/More/ProfileView.swift
index db3cb53..c53a89d 100644
--- a/Hutch/Views/More/ProfileView.swift
+++ b/Hutch/Views/More/ProfileView.swift
@@ -79,6 +79,7 @@ struct ProfileView: View {
error: contributionViewModel.contributionsError ?? contributionViewModel.contributionStatusText,
isIndexedButEmpty: contributionViewModel.isContributionActivityIndexedButEmpty
)
+ .themedRow()
}
}
@@ -185,6 +186,7 @@ struct ProfileView: View {
}
}
.padding(.vertical, 4)
+ .themedRow()
if let bio = profile.bio, !bio.isEmpty {
VStack(alignment: .leading, spacing: 2) {
@@ -193,36 +195,44 @@ struct ProfileView: View {
.foregroundStyle(.secondary)
ProfileBioView(markdown: bio)
}
+ .themedRow()
}
if let location = profile.location, !location.isEmpty {
LabeledContent("Location", value: location)
+ .themedRow()
}
if let url = profile.url, !url.isEmpty {
LabeledContent("URL", value: url)
+ .themedRow()
}
if let status = profile.paymentStatus {
LabeledContent("Payment", value: status.capitalized)
+ .themedRow()
}
if let sub = profile.subscription {
if let status = sub.status {
LabeledContent("Subscription", value: status.capitalized)
+ .themedRow()
}
if let interval = sub.interval {
LabeledContent("Interval", value: interval.capitalized)
+ .themedRow()
}
}
Button("Edit Profile") {
viewModel.isEditingProfile = true
}
+ .themedRow()
SRHTShareButton(url: SRHTWebURL.profile(canonicalName: profile.canonicalName), target: .profile) {
SwiftUI.Label("Share Profile", systemImage: "square.and.arrow.up")
}
+ .themedRow()
}
}
@@ -262,11 +272,13 @@ struct ProfileView: View {
}
}
}
+ .themedRow()
if viewModel.isAddingSSHKey {
TextField("Paste SSH public key", text: $vm.newSSHKey, axis: .vertical)
.font(.caption.monospaced())
.lineLimit(3...6)
+ .themedRow()
HStack {
Button("Cancel") {
@@ -280,12 +292,14 @@ struct ProfileView: View {
.buttonStyle(.borderedProminent)
.disabled(viewModel.newSSHKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
+ .themedRow()
} else {
Button {
viewModel.isAddingSSHKey = true
} label: {
SwiftUI.Label("Add SSH Key", systemImage: "key")
}
+ .themedRow()
}
} header: {
Text("SSH Keys")
@@ -316,11 +330,13 @@ struct ProfileView: View {
}
}
}
+ .themedRow()
if viewModel.isAddingPGPKey {
TextField("Paste PGP public key", text: $vm.newPGPKey, axis: .vertical)
.font(.caption.monospaced())
.lineLimit(3...6)
+ .themedRow()
HStack {
Button("Cancel") {
@@ -334,12 +350,14 @@ struct ProfileView: View {
.buttonStyle(.borderedProminent)
.disabled(viewModel.newPGPKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
+ .themedRow()
} else {
Button {
viewModel.isAddingPGPKey = true
} label: {
SwiftUI.Label("Add PGP Key", systemImage: "key.fill")
}
+ .themedRow()
}
} header: {
Text("PGP Keys")
@@ -357,10 +375,12 @@ struct ProfileView: View {
ProgressView()
Spacer()
}
+ .themedRow()
} else if viewModel.personalAccessTokens.isEmpty {
Button("Load Tokens") {
Task { await viewModel.loadPersonalAccessTokens() }
}
+ .themedRow()
} else {
ForEach(viewModel.personalAccessTokens) { token in
VStack(alignment: .leading, spacing: 4) {
@@ -390,6 +410,7 @@ struct ProfileView: View {
}
}
}
+ .themedRow()
}
} header: {
Text("Personal Access Tokens")
@@ -426,6 +447,7 @@ private struct EditProfileSheet: View {
let profile: UserProfile
let viewModel: SettingsViewModel
+ @Environment(\.isAMOLEDTheme) private var isAMOLED
@State private var email: String
@State private var url: String
@State private var location: String
@@ -492,7 +514,7 @@ private struct EditProfileSheet: View {
}
Spacer()
}
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
if profile.avatar != nil || avatarPreview != nil {
HStack {
@@ -506,7 +528,7 @@ private struct EditProfileSheet: View {
.disabled(viewModel.isUploadingAvatar)
Spacer()
}
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
.listRowSeparator(.hidden)
}
}
@@ -522,6 +544,7 @@ private struct EditProfileSheet: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
}
+ .themedRow()
VStack(alignment: .leading, spacing: 4) {
Text("URL")
@@ -533,6 +556,7 @@ private struct EditProfileSheet: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
}
+ .themedRow()
VStack(alignment: .leading, spacing: 4) {
Text("Location")
@@ -540,6 +564,7 @@ private struct EditProfileSheet: View {
.foregroundStyle(.secondary)
TextField("Enter location", text: $location)
}
+ .themedRow()
VStack(alignment: .leading, spacing: 4) {
Text("Bio")
@@ -548,8 +573,10 @@ private struct EditProfileSheet: View {
TextField("Enter bio", text: $bio, axis: .vertical)
.lineLimit(3...6)
}
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Edit Profile")
.navigationBarTitleDisplayMode(.inline)
.onChange(of: selectedPhoto) { _, newItem in
diff --git a/Hutch/Views/Pastes/PasteDetailView.swift b/Hutch/Views/Pastes/PasteDetailView.swift
index 7eb1853..c6fe11a 100644
--- a/Hutch/Views/Pastes/PasteDetailView.swift
+++ b/Hutch/Views/Pastes/PasteDetailView.swift
@@ -326,13 +326,18 @@ private struct PasteInfoSheet: View {
List {
Section("Paste") {
LabeledContent("ID", value: paste.id)
+ .themedRow()
LabeledContent("Owner", value: paste.user.canonicalName)
+ .themedRow()
LabeledContent("Created", value: paste.created.relativeDescription)
+ .themedRow()
LabeledContent("Visibility") {
VisibilityBadge(visibility: paste.visibility)
}
+ .themedRow()
if paste.files.count > 1 {
LabeledContent("Files", value: "\(paste.files.count)")
+ .themedRow()
}
}
@@ -340,6 +345,7 @@ private struct PasteInfoSheet: View {
Section("File") {
if let filename = file.filename, !filename.isEmpty {
LabeledContent("Filename", value: filename)
+ .themedRow()
}
LabeledContent("Hash") {
Text(file.hash)
@@ -347,6 +353,7 @@ private struct PasteInfoSheet: View {
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
+ .themedRow()
}
}
}
@@ -407,6 +414,7 @@ private struct PasteVisibilitySheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
.listStyle(.insetGrouped)
.navigationTitle("Visibility")
diff --git a/Hutch/Views/Pastes/PasteListView.swift b/Hutch/Views/Pastes/PasteListView.swift
index 9363b08..dfd472b 100644
--- a/Hutch/Views/Pastes/PasteListView.swift
+++ b/Hutch/Views/Pastes/PasteListView.swift
@@ -101,6 +101,7 @@ struct PasteListView: View {
await viewModel.loadMoreIfNeeded(currentItem: paste)
}
}
+ .themedRow()
if viewModel.isLoadingMore {
HStack {
@@ -109,6 +110,7 @@ struct PasteListView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
.themedList()
@@ -302,12 +304,14 @@ private struct CreatePasteSheet: View {
files = [PasteUploadDraft()]
}
}
+ .themedRow()
Button {
files.append(PasteUploadDraft())
} label: {
Label("Add File", systemImage: "plus")
}
+ .themedRow()
}
Section("Visibility") {
@@ -316,21 +320,25 @@ private struct CreatePasteSheet: View {
Text("Unlisted").tag(Visibility.unlisted)
Text("Private").tag(Visibility.private)
}
+ .themedRow()
}
Section {
Text("Paste contents are uploaded as UTF-8 text files. Hutch can change visibility later, but the API does not support editing file contents after creation.")
.font(.footnote)
.foregroundStyle(.secondary)
+ .themedRow()
}
if let error = viewModel.error {
Section {
Label(error, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle("New Paste")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/Projects/ProjectDetailView.swift b/Hutch/Views/Projects/ProjectDetailView.swift
index dc8b24d..b5c6cdb 100644
--- a/Hutch/Views/Projects/ProjectDetailView.swift
+++ b/Hutch/Views/Projects/ProjectDetailView.swift
@@ -103,6 +103,7 @@ struct ProjectDetailView: View {
}
}
.padding(.vertical, 4)
+ .themedRow()
}
}
@@ -127,6 +128,7 @@ struct ProjectDetailView: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
}
@@ -155,6 +157,7 @@ struct ProjectDetailView: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
}
@@ -183,6 +186,7 @@ struct ProjectDetailView: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
}
@@ -205,6 +209,7 @@ struct ProjectDetailView: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
}
@@ -218,6 +223,7 @@ struct ProjectDetailView: View {
systemImage: "square.stack.3d.up.slash",
description: Text("This project doesn’t currently expose repositories, trackers, mailing lists, or external links.")
)
+ .themedRow()
}
}
}
diff --git a/Hutch/Views/Projects/ProjectMailingListView.swift b/Hutch/Views/Projects/ProjectMailingListView.swift
index 4ff93e9..d7c0abc 100644
--- a/Hutch/Views/Projects/ProjectMailingListView.swift
+++ b/Hutch/Views/Projects/ProjectMailingListView.swift
@@ -332,7 +332,9 @@ struct MailingListDetailView: View {
.tint(thread.isUnread ? .blue : .gray)
}
}
+ .themedRow()
}
+ .themedList()
.listStyle(.plain)
.searchable(
text: $vm.searchText,
diff --git a/Hutch/Views/Projects/ProjectsListView.swift b/Hutch/Views/Projects/ProjectsListView.swift
index 5786462..12a5149 100644
--- a/Hutch/Views/Projects/ProjectsListView.swift
+++ b/Hutch/Views/Projects/ProjectsListView.swift
@@ -79,6 +79,7 @@ struct ProjectsListView: View {
.buttonStyle(.plain)
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
}
+ .themedRow()
}
.themedList()
.listStyle(.plain)
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")
}
diff --git a/Hutch/Views/Settings/SettingsView.swift b/Hutch/Views/Settings/SettingsView.swift
index 44b3ba5..4ed9216 100644
--- a/Hutch/Views/Settings/SettingsView.swift
+++ b/Hutch/Views/Settings/SettingsView.swift
@@ -60,11 +60,13 @@ struct SettingsView: View {
Text(theme.label).tag(theme)
}
}
+ .themedRow()
Picker("Density", selection: $displayDensity) {
ForEach(DisplayDensity.allCases) { density in
Text(density.label).tag(density)
}
}
+ .themedRow()
} header: {
Text("Appearance")
} footer: {
@@ -76,10 +78,12 @@ struct SettingsView: View {
private func behaviorSection() -> some View {
Section {
Toggle("Swipe actions", isOn: $swipeActionsEnabled)
+ .themedRow()
Toggle("Contribution graphs", isOn: $contributionGraphsEnabled)
.onChange(of: contributionGraphsEnabled) { _, newValue in
ContributionWidgetContextStore.setEnabled(newValue)
}
+ .themedRow()
} header: {
Text("Behavior")
} footer: {
@@ -101,12 +105,14 @@ struct SettingsView: View {
}
}
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
+ .themedRow()
Button {
showAccountSwitcher = true
} label: {
Label("Manage Accounts", systemImage: "person.2")
}
+ .themedRow()
HStack {
Image(systemName: "lock.shield")
@@ -116,14 +122,17 @@ struct SettingsView: View {
.foregroundStyle(.secondary)
}
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
+ .themedRow()
Button("Reset App Data", role: .destructive) {
pendingDestructiveAction = .resetAppData
}
+ .themedRow()
Button("Sign Out", role: .destructive) {
pendingDestructiveAction = .signOut
}
+ .themedRow()
} header: {
Text("Authentication")
} footer: {
@@ -139,6 +148,7 @@ struct SettingsView: View {
} label: {
SwiftUI.Label("About Hutch", systemImage: "info.circle")
}
+ .themedRow()
}
}
}
@@ -211,15 +221,18 @@ private struct AboutView: View {
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
+ .themedRow()
LabeledContent("Version", value: version)
.onTapGesture {
developerRevealCount = min(developerRevealCount + 1, 5)
}
+ .themedRow()
LabeledContent("Build", value: build)
.onTapGesture {
developerRevealCount = min(developerRevealCount + 1, 5)
}
+ .themedRow()
} footer: {
Text(developerRevealFooterText)
}
@@ -228,34 +241,41 @@ private struct AboutView: View {
Link(destination: URL(string: "https://sr.ht")!) {
SwiftUI.Label("SourceHut", systemImage: "link")
}
+ .themedRow()
Link(destination: URL(string: "https://man.sr.ht")!) {
SwiftUI.Label("SourceHut Manuals", systemImage: "book")
}
+ .themedRow()
Link(destination: URL(string: "https://sr.ht/~ccleberg/Hutch")!) {
SwiftUI.Label("Project Repository", systemImage: "folder")
}
+ .themedRow()
}
Section("Support") {
Link(destination: URL(string: "mailto:[email protected]")!) {
SwiftUI.Label("Email Support", systemImage: "envelope")
}
+ .themedRow()
}
Section("Privacy") {
Text("Hutch uses your SourceHut personal access token to make requests on your behalf. The token is stored locally in the iOS keychain.")
.font(.subheadline)
.foregroundStyle(.secondary)
+ .themedRow()
Link(destination: URL(string: "https://zerolabs.sh/hutch/privacy-policy/")!) {
SwiftUI.Label("Privacy Policy", systemImage: "hand.raised")
}
+ .themedRow()
}
Section("Acknowledgements") {
Text("Built for SourceHut users who want quick access to repositories, builds, and tickets on iOS.")
.font(.subheadline)
.foregroundStyle(.secondary)
+ .themedRow()
}
if developerToolsVisible {
@@ -264,18 +284,21 @@ private struct AboutView: View {
get: { appState.isDebugModeEnabled },
set: { appState.isDebugModeEnabled = $0 }
))
+ .themedRow()
NavigationLink {
HomePrototypeView()
} label: {
SwiftUI.Label("Home Prototype", systemImage: "house")
}
+ .themedRow()
NavigationLink {
WorkPrototypeView()
} label: {
SwiftUI.Label("Work Prototype", systemImage: "tray.full")
}
+ .themedRow()
} header: {
Text("Developer")
} footer: {
diff --git a/Hutch/Views/SystemStatus/SystemStatusView.swift b/Hutch/Views/SystemStatus/SystemStatusView.swift
index 08a46eb..a3ce5b3 100644
--- a/Hutch/Views/SystemStatus/SystemStatusView.swift
+++ b/Hutch/Views/SystemStatus/SystemStatusView.swift
@@ -36,6 +36,7 @@ struct SystemStatusView: View {
Label(staleDataMessage, systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90")
.font(.subheadline)
.foregroundStyle(.secondary)
+ .themedRow()
}
}
@@ -47,6 +48,7 @@ struct SystemStatusView: View {
recentIncidentsSection(viewModel.recentIncidents)
}
+ .themedList()
.listStyle(.insetGrouped)
.refreshable {
await viewModel.load(forceRefresh: true)
@@ -99,6 +101,7 @@ struct SystemStatusView: View {
.font(.subheadline.weight(.medium))
}
.padding(.vertical, 4)
+ .themedRow()
}
}
@@ -119,6 +122,7 @@ struct SystemStatusView: View {
}
.padding(.vertical, 2)
}
+ .themedRow()
}
}
@@ -129,6 +133,7 @@ struct SystemStatusView: View {
ForEach(incidents) { incident in
incidentRow(incident)
}
+ .themedRow()
}
}
}
@@ -142,10 +147,12 @@ struct SystemStatusView: View {
systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90",
description: Text("The status feed didn’t return any recent incidents.")
)
+ .themedRow()
} else {
ForEach(incidents) { incident in
incidentRow(incident)
}
+ .themedRow()
}
}
}
diff --git a/Hutch/Views/Tickets/TicketDetailView.swift b/Hutch/Views/Tickets/TicketDetailView.swift
index 32ef89c..0028083 100644
--- a/Hutch/Views/Tickets/TicketDetailView.swift
+++ b/Hutch/Views/Tickets/TicketDetailView.swift
@@ -739,8 +739,10 @@ private struct ResolveSheet: View {
}
.pickerStyle(.inline)
.labelsHidden()
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Resolve Ticket")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -801,6 +803,7 @@ private struct AssignSheet: View {
.buttonStyle(.plain)
}
}
+ .themedRow()
}
}
@@ -809,6 +812,7 @@ private struct AssignSheet: View {
.textContentType(.username)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
+ .themedRow()
Button("Add Assignee") {
let name = username.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -822,8 +826,10 @@ private struct AssignSheet: View {
username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|| viewModel.isPerformingAction
)
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Assignees")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -877,7 +883,9 @@ private struct LabelsSheet: View {
}
}
}
+ .themedRow()
}
+ .themedList()
}
}
.navigationTitle("Labels")
@@ -917,11 +925,14 @@ private struct CreateLabelSheet: View {
Section("Label Details") {
TextField("Label name", text: $labelName)
.autocorrectionDisabled()
+ .themedRow()
}
Section("Colors") {
ColorPicker("Background color", selection: $backgroundColor, supportsOpacity: false)
+ .themedRow()
ColorPicker("Text color", selection: $foregroundColor, supportsOpacity: false)
+ .themedRow()
}
Section("Preview") {
@@ -936,8 +947,10 @@ private struct CreateLabelSheet: View {
.clipShape(Capsule())
Spacer()
}
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("New Label")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/Tickets/TicketListView.swift b/Hutch/Views/Tickets/TicketListView.swift
index e58d194..940a282 100644
--- a/Hutch/Views/Tickets/TicketListView.swift
+++ b/Hutch/Views/Tickets/TicketListView.swift
@@ -6,6 +6,7 @@ struct TicketListView: View {
@AppStorage(AppStorageKeys.swipeActionsEnabled, store: .standard) private var swipeActionsEnabled = true
@Environment(AppState.self) private var appState
+ @Environment(\.isAMOLEDTheme) private var isAMOLED
@Environment(\.dismiss) private var dismiss
@Environment(\.openURL) private var openURL
@State private var tracker: TrackerSummary
@@ -277,7 +278,7 @@ struct TicketListView: View {
vm.deleteSavedFilter(savedFilter)
}
.listRowInsets(EdgeInsets())
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
.listRowSeparator(.hidden)
ForEach(viewModel.filteredTickets) { ticket in
@@ -344,6 +345,7 @@ struct TicketListView: View {
await viewModel.loadMoreIfNeeded(currentItem: ticket)
}
}
+ .themedRow()
if viewModel.isLoadingMore {
HStack {
@@ -352,9 +354,11 @@ struct TicketListView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
}
+ .themedList()
.listStyle(.plain)
.listSectionSpacing(.compact)
.searchable(
@@ -636,10 +640,13 @@ private struct CreateTicketSheet: View {
Form {
Section("Ticket Details") {
TextField("Title", text: $subject)
+ .themedRow()
TextField("Description (optional)", text: $descriptionText, axis: .vertical)
.lineLimit(6...12)
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("New Ticket")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -705,7 +712,9 @@ private struct TicketLabelsSheet: View {
}
}
}
+ .themedRow()
}
+ .themedList()
}
} else {
ContentUnavailableView(
@@ -921,6 +930,7 @@ private struct TicketFilterLabelsSheet: View {
}
}
.padding(.vertical, 4)
+ .themedRow()
}
}
@@ -940,8 +950,10 @@ private struct TicketFilterLabelsSheet: View {
}
.buttonStyle(.plain)
}
+ .themedRow()
}
}
+ .themedList()
}
}
.navigationTitle("Filter Labels")
@@ -982,8 +994,10 @@ private struct SaveTicketFilterSheet: View {
Section("Name") {
TextField("Filter name", text: $name)
.textInputAutocapitalization(.words)
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Save Filter")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -1085,6 +1099,7 @@ private struct BulkResolveSheet: View {
Text("\(viewModel.selectedTicketCount) ticket\(viewModel.selectedTicketCount == 1 ? "" : "s") selected")
.font(.subheadline)
.foregroundStyle(.secondary)
+ .themedRow()
}
Section("Resolution") {
@@ -1095,8 +1110,10 @@ private struct BulkResolveSheet: View {
}
.pickerStyle(.inline)
.labelsHidden()
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Close Tickets")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -1133,6 +1150,7 @@ private struct BulkAssignSheet: View {
Text("\(viewModel.selectedTicketCount) ticket\(viewModel.selectedTicketCount == 1 ? "" : "s") selected")
.font(.subheadline)
.foregroundStyle(.secondary)
+ .themedRow()
}
if let currentUser {
@@ -1146,6 +1164,7 @@ private struct BulkAssignSheet: View {
}
}
.disabled(viewModel.selectedTicketCount == 0 || viewModel.isPerformingAction)
+ .themedRow()
}
}
@@ -1154,8 +1173,10 @@ private struct BulkAssignSheet: View {
.textContentType(.username)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Assign Tickets")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/Tickets/TrackerListView.swift b/Hutch/Views/Tickets/TrackerListView.swift
index db0fdf2..9ca3340 100644
--- a/Hutch/Views/Tickets/TrackerListView.swift
+++ b/Hutch/Views/Tickets/TrackerListView.swift
@@ -162,6 +162,7 @@ struct TrackerListView: View {
await viewModel.loadMoreIfNeeded(currentItem: tracker)
}
}
+ .themedRow()
if viewModel.isLoadingMore {
HStack {
@@ -170,8 +171,10 @@ struct TrackerListView: View {
Spacer()
}
.listRowSeparator(.hidden)
+ .themedRow()
}
}
+ .themedList()
.listStyle(.plain)
.searchable(
text: $vm.searchText,
diff --git a/Hutch/Views/Tickets/TrackerManagementView.swift b/Hutch/Views/Tickets/TrackerManagementView.swift
index cf923b6..4e04bbd 100644
--- a/Hutch/Views/Tickets/TrackerManagementView.swift
+++ b/Hutch/Views/Tickets/TrackerManagementView.swift
@@ -654,22 +654,27 @@ struct TrackerEditorSheet: View {
TextField("Tracker 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()
}
if let error, !error.isEmpty {
Section {
Text(error)
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -718,10 +723,12 @@ struct TrackerACLManagementSheet: View {
List {
Section("Default Access") {
TrackerPermissionSummary(permissions: viewModel.defaultACL.permissions)
+ .themedRow()
Button("Update Default ACL") {
editingDefaultACL = true
}
.disabled(viewModel.isSavingACL)
+ .themedRow()
}
Section {
@@ -731,9 +738,11 @@ struct TrackerACLManagementSheet: View {
ProgressView()
Spacer()
}
+ .themedRow()
} else if viewModel.acls.isEmpty {
Text("No tracker-specific ACLs yet.")
.foregroundStyle(.secondary)
+ .themedRow()
} else {
ForEach(viewModel.acls) { entry in
VStack(alignment: .leading, spacing: 6) {
@@ -756,6 +765,7 @@ struct TrackerACLManagementSheet: View {
.tint(.blue)
}
}
+ .themedRow()
}
} header: {
Text("User ACLs")
@@ -763,6 +773,7 @@ struct TrackerACLManagementSheet: View {
Text("Each ACL must include all five permission flags.")
}
}
+ .themedList()
.navigationTitle("ACLs")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -908,6 +919,7 @@ private struct TrackerACLEditorSheet: View {
TextField("Username or ~username", text: $username)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
+ .themedRow()
}
permissionSection
@@ -916,9 +928,11 @@ private struct TrackerACLEditorSheet: View {
Section {
Text(error)
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -950,10 +964,15 @@ private struct TrackerACLEditorSheet: View {
private var permissionSection: some View {
Section("Permissions") {
Toggle("Browse", isOn: $browse)
+ .themedRow()
Toggle("Submit", isOn: $submit)
+ .themedRow()
Toggle("Comment", isOn: $comment)
+ .themedRow()
Toggle("Edit", isOn: $edit)
+ .themedRow()
Toggle("Triage", isOn: $triage)
+ .themedRow()
}
}
@@ -1003,19 +1022,26 @@ private struct TrackerDefaultACLEditorSheet: View {
Form {
Section("Permissions") {
Toggle("Browse", isOn: $browse)
+ .themedRow()
Toggle("Submit", isOn: $submit)
+ .themedRow()
Toggle("Comment", isOn: $comment)
+ .themedRow()
Toggle("Edit", isOn: $edit)
+ .themedRow()
Toggle("Triage", isOn: $triage)
+ .themedRow()
}
if let error, !error.isEmpty {
Section {
Text(error)
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle("Default ACL")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -1073,6 +1099,7 @@ struct TrackerLabelManagementSheet: View {
Text("Labels are managed here and reused throughout the tracker.")
.font(.footnote)
.foregroundStyle(.secondary)
+ .themedRow()
}
if viewModel.isLoadingLabels {
@@ -1081,12 +1108,14 @@ struct TrackerLabelManagementSheet: View {
ProgressView()
Spacer()
}
+ .themedRow()
} else if viewModel.labels.isEmpty {
ContentUnavailableView(
"No Labels",
systemImage: "tag",
description: Text("Create labels for triage and organization.")
)
+ .themedRow()
} else {
ForEach(viewModel.labels) { label in
Button {
@@ -1110,8 +1139,10 @@ struct TrackerLabelManagementSheet: View {
}
}
}
+ .themedRow()
}
}
+ .themedList()
.navigationTitle("Labels")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -1261,8 +1292,11 @@ private struct TrackerLabelEditorSheet: View {
Form {
Section("Details") {
TextField("Label name", text: $name)
+ .themedRow()
ColorPicker("Foreground", selection: $foregroundColor, supportsOpacity: false)
+ .themedRow()
ColorPicker("Background", selection: $backgroundColor, supportsOpacity: false)
+ .themedRow()
}
Section("Preview") {
@@ -1274,15 +1308,18 @@ private struct TrackerLabelEditorSheet: View {
foregroundColor: foregroundColor.hexString
)
)
+ .themedRow()
}
if let error, !error.isEmpty {
Section {
Text(error)
.foregroundStyle(.red)
+ .themedRow()
}
}
}
+ .themedList()
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
diff --git a/Hutch/Views/Work/WorkView.swift b/Hutch/Views/Work/WorkView.swift
index a7fbd1c..2326236 100644
--- a/Hutch/Views/Work/WorkView.swift
+++ b/Hutch/Views/Work/WorkView.swift
@@ -11,6 +11,7 @@ struct WorkView: View {
@AppStorage(AppStorageKeys.swipeActionsEnabled, store: .standard) private var swipeActionsEnabled = true
@Environment(AppState.self) private var appState
+ @Environment(\.isAMOLEDTheme) private var isAMOLED
@Environment(\.scenePhase) private var scenePhase
@State private var viewModel: HomeViewModel?
@State private var scope: Scope = .all
@@ -74,6 +75,7 @@ struct WorkView: View {
}
}
.padding(.vertical, 2)
+ .themedRow()
}
}
@@ -88,6 +90,7 @@ struct WorkView: View {
if workCount(viewModel) == 0 {
Section {
WorkCompactMessageRow(text: "Nothing to do", systemImage: "checkmark.circle")
+ .themedRow()
}
}
}
@@ -100,7 +103,7 @@ struct WorkView: View {
}
}
.pickerStyle(.segmented)
- .listRowBackground(Color.clear)
+ .listRowBackground(isAMOLED ? Color.black : Color.clear)
.listRowInsets(EdgeInsets())
}
}
@@ -110,9 +113,11 @@ struct WorkView: View {
Section {
if isLoadingUnread(viewModel) {
WorkLoadingRow(label: "Loading unread threads")
+ .themedRow()
} else if viewModel.unreadInboxThreads.isEmpty {
if compactWhenEmpty {
WorkCompactMessageRow(text: "No unread threads", systemImage: "tray")
+ .themedRow()
}
} else {
ForEach(viewModel.unreadInboxThreads) { thread in
@@ -137,6 +142,7 @@ struct WorkView: View {
}
}
}
+ .themedRow()
}
} header: {
Text("Unread Threads")
@@ -155,8 +161,10 @@ struct WorkView: View {
Section {
if viewModel.isLoadingAssignedTickets && viewModel.assignedTickets.isEmpty {
WorkLoadingRow(label: "Loading assigned tickets")
+ .themedRow()
} else if viewModel.assignedTickets.isEmpty {
WorkCompactMessageRow(text: "No assigned tickets", systemImage: "person.crop.circle.badge.checkmark")
+ .themedRow()
} else {
ForEach(viewModel.assignedTickets) { ticket in
NavigationLink {
@@ -200,6 +208,7 @@ struct WorkView: View {
}
}
}
+ .themedRow()
}
} header: {
Text("Assigned Tickets")