summaryrefslogtreecommitdiff
path: root/Hutch/Views/Tickets
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/Tickets
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/Tickets')
-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
4 files changed, 75 insertions, 1 deletions
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 {