Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions App/AppearanceStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ struct SecondaryText: ViewModifier {
}
}

/// Error and warning text inside sheets and Settings: 4.5:1 or better on the sheet in every appearance (#252), where
/// the system's red and orange are not.
struct InlineText: ViewModifier {
let tone: Palette.InlineTone
@Environment(\.appearanceMode) private var mode
@Environment(\.colorScheme) private var scheme

func body(content: Content) -> some View {
content.foregroundStyle(Color(Palette.inlineText(tone, mode: mode, systemDark: scheme == .dark)))
}
}

extension View {
func secondaryText() -> some View { modifier(SecondaryText()) }
func errorText() -> some View { modifier(InlineText(tone: .error)) }
func warningText() -> some View { modifier(InlineText(tone: .warning)) }
func inlineText(_ tone: Palette.InlineTone) -> some View { modifier(InlineText(tone: tone)) }
}
6 changes: 3 additions & 3 deletions App/ChannelNamesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct ChannelNamesView: View {
HStack {
Text("\(row.number)")
.monospacedDigit()
.foregroundStyle(.secondary)
.secondaryText()
.frame(minWidth: 28, alignment: .trailing)
TextField(row.hint, text: binding(for: row))
.focused($focused, equals: row.number)
Expand All @@ -29,7 +29,7 @@ struct ChannelNamesView: View {
.accessibilityLabel("Name for USB Channel \(row.number)")
if !(drafts[row.number] ?? "").isEmpty {
Button { drafts[row.number] = nil; commit(row.number) } label: {
Image(systemName: "xmark.circle.fill").foregroundStyle(.secondary)
Image(systemName: "xmark.circle.fill").secondaryText()
}
.buttonStyle(.borderless)
.frame(minWidth: 44, minHeight: 44)
Expand All @@ -41,7 +41,7 @@ struct ChannelNamesView: View {
}
Section {
Button("Copy from last Show") { copyFromLastShow() }
if let copyMessage { Text(copyMessage).font(.footnote).foregroundStyle(.secondary) }
if let copyMessage { Text(copyMessage).font(.footnote).secondaryText() }
} footer: {
Text("The files take the names when the Take ends. A name stays for the rest of the Show.")
}
Expand Down
10 changes: 5 additions & 5 deletions App/DeleteShowSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ struct DeleteShowSheet: View {
NavigationStack {
Form {
if let blocked {
Section { Label(blocked, systemImage: "lock.fill").foregroundStyle(.secondary) }
Section { Label(blocked, systemImage: "lock.fill").secondaryText() }
}
Section {
if show.deviceCopy != nil {
Toggle("Device copy", isOn: $fromDevice).onChange(of: fromDevice) { check = nil }
} else {
LabeledContent("Device copy", value: "Not there").foregroundStyle(.secondary)
LabeledContent("Device copy", value: "Not there").secondaryText()
}
if show.driveCopy != nil {
Toggle("Drive copy", isOn: $fromDrive).onChange(of: fromDrive) { check = nil }
} else {
LabeledContent("Drive copy", value: "Not there").foregroundStyle(.secondary)
LabeledContent("Drive copy", value: "Not there").secondaryText()
}
} header: {
Text("Delete from")
Expand All @@ -65,7 +65,7 @@ struct DeleteShowSheet: View {
if checking { ProgressView() }
switch check {
case .passed: Label("The Drive copy matches. The Device copy can be deleted.", systemImage: "checkmark.seal.fill").foregroundStyle(.green)
case .failed(let reasons): ForEach(reasons, id: \.self) { Text($0).foregroundStyle(.red) }
case .failed(let reasons): ForEach(reasons, id: \.self) { Text($0).errorText() }
case nil: EmptyView()
}
} header: {
Expand Down Expand Up @@ -97,7 +97,7 @@ struct DeleteShowSheet: View {
} message: {
Text(footer)
}
if let message { Text(message).foregroundStyle(.red) }
if let message { Text(message).errorText() }
}
}
.navigationTitle(channel != nil ? "Delete File" : take == nil ? "Delete Show" : "Delete Take")
Expand Down
4 changes: 2 additions & 2 deletions App/FindMixersRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ struct FindMixersRow: View {
.disabled(searching || disabled)
if noNetwork {
Text("Not on a Wi-Fi or Ethernet network.")
.font(.footnote).foregroundStyle(.secondary)
.font(.footnote).secondaryText()
} else if let found, found.isEmpty, !searching {
// On iOS the first search is also the first local-network send, so it asks for Local Network access and
// the replies are lost while it asks.
Text("No mixers answered. Check this device is on the mixer's network and Local Network access is on. If it just asked about Local Network, tap Find mixers again.")
.font(.footnote).foregroundStyle(.secondary)
.font(.footnote).secondaryText()
} else if let found {
ForEach(found, id: \.host) { mixer in
Button {
Expand Down
10 changes: 5 additions & 5 deletions App/MarkerListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ struct MarkerListView: View {
List {
if model.recorder.markerEntries.isEmpty {
Text("No Markers yet. Press the flag during a Take to place one.")
.foregroundStyle(.secondary)
.secondaryText()
}
ForEach(model.recorder.markerEntries) { entry in
row(entry)
}
if model.recorder.takePreRollSeconds > 0, !model.recorder.markerEntries.isEmpty {
Text("Times are from when you pressed Record. The Stems, the report and the Reaper project count from the start of the Pre-roll, \(Int(model.recorder.takePreRollSeconds.rounded())) s earlier.")
.font(.footnote)
.foregroundStyle(.secondary)
.secondaryText()
}
if let message {
Label(message, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
.warningText()
}
}
.alert("Rename Marker", isPresented: Binding(get: { renaming != nil }, set: { if !$0 { renaming = nil } })) {
Expand All @@ -44,14 +44,14 @@ struct MarkerListView: View {
let label = HStack {
Text(ElapsedTime.format(seconds: Int(entry.secondsSincePress)))
.monospacedDigit()
.foregroundStyle(.secondary)
.secondaryText()
.frame(minWidth: 56, alignment: .leading)
if entry.isDropout {
Label("Dropout: audio was lost here", systemImage: "waveform.badge.exclamationmark")
} else {
Text(entry.name)
Spacer()
Image(systemName: "pencil").foregroundStyle(.secondary)
Image(systemName: "pencil").secondaryText()
}
}
.frame(minHeight: 44)
Expand Down
2 changes: 1 addition & 1 deletion App/NewShowSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct NewShowSheet: View {
Text(sheet.startShowNote)
}
if let message {
Section { Text(message).foregroundStyle(.red) }
Section { Text(message).errorText() }
}
}
.navigationTitle(sheet.title)
Expand Down
2 changes: 1 addition & 1 deletion App/PaywallSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct PaywallSheet: View {
Button("Restore Purchases") { Task { await store.restore() } }
.disabled(store.busy)
if let note = store.note {
Text(note).font(.footnote).foregroundStyle(.secondary)
Text(note).font(.footnote).secondaryText()
}
} footer: {
Text(paywall.footnote)
Expand Down
4 changes: 2 additions & 2 deletions App/ProSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct ProSection: View {
Button("Restore Purchases") { Task { await store.restore() } }
.disabled(store.busy)
if let note = store.note {
Text(note).font(.footnote).foregroundStyle(.secondary)
Text(note).font(.footnote).secondaryText()
}
if tier == .free {
freePicker
Expand Down Expand Up @@ -79,7 +79,7 @@ struct ProSection: View {
.pickerStyle(.menu)
}
Text("Free records 2 USB Channels. Every channel is still metered.")
.font(.footnote).foregroundStyle(.secondary)
.font(.footnote).secondaryText()
}
}
}
20 changes: 10 additions & 10 deletions App/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct SettingsView: View {
if model.recorder.isRecording {
Section {
Label("Input, Pre-roll, Drive and Mixer Link can't be changed during a Take.", systemImage: "lock.fill")
.foregroundStyle(.secondary)
.secondaryText()
}
}
Section {
Expand All @@ -53,7 +53,7 @@ struct SettingsView: View {
}
.onChange(of: model.sampleRateChoice) { model.sampleRateChanged() }
.disabled(locked(.input))
Text(model.usbChannelSummary).foregroundStyle(.secondary).monospacedDigit()
Text(model.usbChannelSummary).secondaryText().monospacedDigit()
Button("Channel names…") { showingChannelNames = true }
.disabled(model.meterModel.channelCount == 0)
Picker("Pre-roll", selection: $model.preRollSeconds) {
Expand All @@ -63,7 +63,7 @@ struct SettingsView: View {
}
.onChange(of: model.preRollSeconds) { model.preRollChanged() }
.disabled(locked(.preRoll))
Text(model.preRollNote).font(.footnote).foregroundStyle(.secondary)
Text(model.preRollNote).font(.footnote).secondaryText()
// A dropdown menu. The choice is stored the moment it is picked (in the binding's setter), so it does
// not depend on the screen redrawing when the appearance changes under the open menu.
Picker("Appearance", selection: Binding(
Expand All @@ -75,7 +75,7 @@ struct SettingsView: View {
}
}
.pickerStyle(.menu)
Text(model.appearance.footnote).font(.footnote).foregroundStyle(.secondary)
Text(model.appearance.footnote).font(.footnote).secondaryText()
#if os(iOS)
Toggle("Record button on the left in landscape", isOn: $model.transportLeading)
.onChange(of: model.transportLeading) { model.saveTransportSide() }
Expand Down Expand Up @@ -132,12 +132,12 @@ struct SettingsView: View {
switch model.drive.status {
case .notChosen:
Label("No Drive folder. Takes go to this device only.", systemImage: "externaldrive.badge.questionmark")
.foregroundStyle(.secondary)
.secondaryText()
case .available(let drive):
Label("Drive: \(drive.name) · \(drive.availableBytes.formatted(.byteCount(style: .file))) free", systemImage: "externaldrive.fill.badge.checkmark")
case .unavailable(let problem):
Label(problem.message, systemImage: "externaldrive.badge.exclamationmark")
.foregroundStyle(.orange)
.warningText()
}
}

Expand All @@ -152,23 +152,23 @@ struct SettingsView: View {
} else { nil }
Text("Time left · This device: \(device.description)" + (drive.map { " · Drive: \($0.description)" } ?? ""))
.monospacedDigit()
.foregroundStyle(.secondary)
.secondaryText()
}
}

@ViewBuilder private var mixerStatus: some View {
switch model.mixerLink.status {
case .idle:
Label("Not connected to a mixer. Stems use USB Channel names.", systemImage: "link")
.foregroundStyle(.secondary)
.secondaryText()
case .connecting(let host):
Label("Connecting to \(host)…", systemImage: "antenna.radiowaves.left.and.right")
.foregroundStyle(.secondary)
.secondaryText()
case .up(let identity, let path):
Label("Mixer Link up · \(identity.networkName) (\(identity.model)) · \(path == .wifi ? "Wi-Fi" : "USB")", systemImage: "checkmark.circle.fill")
case .down(let problem):
Label(problem.message, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
.warningText()
}
}
}
10 changes: 5 additions & 5 deletions App/ShowDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ struct ShowDetailView: View {
if let takes {
List {
if takes.isEmpty {
Text("No Takes yet").foregroundStyle(.secondary)
Text("No Takes yet").secondaryText()
}
ForEach(takes) { take in
Section {
if take.markers.isEmpty {
Text("No Markers").foregroundStyle(.secondary)
Text("No Markers").secondaryText()
}
ForEach(Array(take.markers.enumerated()), id: \.offset) { _, marker in row(marker, take: take.number) }
if !take.channels.isEmpty {
DisclosureGroup("Channel files (\(take.channels.count))") {
ForEach(take.channels, id: \.number) { channel in
HStack {
Text("\(channel.number)").monospacedDigit().foregroundStyle(.secondary)
Text("\(channel.number)").monospacedDigit().secondaryText()
Text(channel.name)
Spacer()
Button("Delete…", role: .destructive) { deletingFile = (take.number, channel.number) }
Expand All @@ -52,7 +52,7 @@ struct ShowDetailView: View {
}
}
if let message {
Section { Text(message).foregroundStyle(.red) }
Section { Text(message).errorText() }
}
}
} else {
Expand Down Expand Up @@ -105,7 +105,7 @@ struct ShowDetailView: View {
let content = HStack {
Text(marker.name)
Spacer()
Text(Self.clock(marker.seconds)).monospacedDigit().foregroundStyle(.secondary)
Text(Self.clock(marker.seconds)).monospacedDigit().secondaryText()
}
if marker.operatorIndex != nil {
Button { newName = marker.name; renaming = (take, marker) } label: { content }
Expand Down
4 changes: 2 additions & 2 deletions App/ShowListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ struct ShowListView: View {
HStack(spacing: 8) {
VStack(alignment: .leading, spacing: 4) {
Text(show.name).font(.headline)
Text(detail(show)).font(.subheadline).foregroundStyle(.secondary)
Text(detail(show)).font(.subheadline).secondaryText()
if let note = show.copyNote {
Text(note.text).font(.caption).foregroundStyle(note.severity == .problem ? Color.red : .orange)
Text(note.text).font(.caption).inlineText(note.severity == .problem ? .error : .warning)
}
}
.accessibilityElement(children: .combine)
Expand Down
4 changes: 2 additions & 2 deletions App/TrialSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ struct TrialSheet: View {
}
.disabled(store.trialProduct == nil || store.busy)
if let note = store.note {
Text(note).font(.footnote).foregroundStyle(.secondary)
Text(note).font(.footnote).secondaryText()
}
}
} else {
Text(store.loadingProducts ? "Loading…" : "The Trial isn't available right now.")
.foregroundStyle(.secondary)
.secondaryText()
}
}
.formStyle(.grouped)
Expand Down
17 changes: 17 additions & 0 deletions Packages/ShowRecorderKit/Sources/Recording/Appearance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ public enum Palette {
mode == .sunlight ? RGB(0.24, 0.24, 0.26) : RGB(0.74, 0.75, 0.78)
}

/// Problem text inside sheets and Settings: an error ("couldn't…") or a warning.
public enum InlineTone: CaseIterable, Sendable {
case error, warning
}

/// Error and warning text on a sheet's background, 4.5:1 or better (#252). The system's red and orange are
/// under that on white, so System gets its own pair for light and dark too.
public static func inlineText(_ tone: InlineTone, mode: AppearanceMode, systemDark: Bool) -> RGB {
let dark = mode == .dark || (mode == .system && systemDark)
switch (tone, dark) {
case (.error, false): return RGB(0.70, 0.0, 0.0)
case (.warning, false): return RGB(0.62, 0.30, 0.0)
case (.error, true): return RGB(1.0, 0.45, 0.42)
case (.warning, true): return RGB(1.0, 0.70, 0.25)
}
}

/// The colors the level meters draw with.
public enum MeterColor: CaseIterable, Sendable {
case green, yellow, red, low, orange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,24 @@ struct AppearanceTests {
if weak && mode != .system { #expect(Palette.meterOutline(mode) != nil, "\(mode)") }
}
}

@Test("Error and warning text inside sheets is at least 4.5:1 on the sheet's background in every appearance, System's light and dark included (#252)")
func inlineProblemText() {
let backgrounds: [(AppearanceMode, Bool, [RGB])] = [
(.sunlight, false, [Palette.background(.sunlight)]),
(.dark, true, [Palette.background(.dark)]),
// System: a grouped Form's rows and the gray behind them, light and dark.
(.system, false, [RGB(1, 1, 1), RGB(0.95, 0.95, 0.97)]),
(.system, true, [RGB(0, 0, 0), RGB(0.11, 0.11, 0.12)]),
]
for (mode, systemDark, behind) in backgrounds {
for tone in Palette.InlineTone.allCases {
let text = Palette.inlineText(tone, mode: mode, systemDark: systemDark)
for background in behind {
let ratio = text.contrast(with: background)
#expect(ratio >= 4.5, "\(tone) in \(mode) (dark \(systemDark)): \(ratio)")
}
}
}
}
}
Loading