refactor: re-skin UI with native iOS components
Replace the Opal-style custom presentation with the bare iOS design language, keeping the backend, flows, and accessibility identifiers: - Home: NavigationStack + List; rules now presented as list rows with kind icon, block summary, and trailing live status; '+' in the toolbar - New Rule: plain list of rule types and presets; editor still pushed via navigationDestination - Editor: native Form (DatePicker rows, day circles with summary in the section header, toggle rows with footers, stepper rows); create commits with a prominent 'Add Rule' button replacing Hold to Commit; edit uses toolbar Done plus red Disable/Delete rows - Detail: sheet with inline title + status caption, LabeledContent rows; Edit Rule pushes the editor natively - Default color scheme: drop forced dark mode and custom tint; system appearance and accent throughout - Delete Theme, HoldToCommitButton, RuleCardView and custom chrome - Use concrete Color.primary/secondary in button row labels (the hierarchical styles resolve against the button tint) - Tests: 93 passing; only delta is holdToCommitButton press → commitRuleButton tap in 5 call sites - Spec: add §6 mapping the original presentation to the native one
This commit is contained in:
@@ -5,29 +5,19 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// "On these days:" — seven circular toggles (S M T W T F S) with a
|
||||
/// human-readable summary, as in the reference rule editors.
|
||||
/// Seven circular day toggles (S M T W T F S) using system colors, meant to
|
||||
/// sit inside a Form/List row. The day-set summary is shown by the enclosing
|
||||
/// section header.
|
||||
struct DayOfWeekPicker: View {
|
||||
@Binding var days: Set<Weekday>
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
HStack {
|
||||
Text("On these days:")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
Spacer()
|
||||
Text(days.summary)
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(Theme.textSecondary)
|
||||
}
|
||||
HStack(spacing: 8) {
|
||||
ForEach(Weekday.displayOrder, id: \.self) { day in
|
||||
dayToggle(day)
|
||||
}
|
||||
HStack(spacing: 8) {
|
||||
ForEach(Weekday.displayOrder, id: \.self) { day in
|
||||
dayToggle(day)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Theme.surface, in: RoundedRectangle(cornerRadius: 18))
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
private func dayToggle(_ day: Weekday) -> some View {
|
||||
@@ -40,13 +30,16 @@ struct DayOfWeekPicker: View {
|
||||
}
|
||||
} label: {
|
||||
Text(day.shortLabel)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundStyle(isOn ? .black : Theme.textSecondary)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(isOn ? Color.white : .secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.background(isOn ? Color.white : Theme.surfaceElevated, in: Circle())
|
||||
.background(
|
||||
isOn ? AnyShapeStyle(.tint) : AnyShapeStyle(Color(.tertiarySystemFill)),
|
||||
in: Circle()
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.buttonStyle(.borderless)
|
||||
.accessibilityIdentifier("dayToggle-\(day.rawValue)")
|
||||
.accessibilityLabel(day.abbreviation)
|
||||
.accessibilityAddTraits(isOn ? .isSelected : [])
|
||||
@@ -55,7 +48,15 @@ struct DayOfWeekPicker: View {
|
||||
|
||||
#Preview {
|
||||
@Previewable @State var days = Weekday.weekdays
|
||||
DayOfWeekPicker(days: $days)
|
||||
.padding()
|
||||
.background(Theme.background)
|
||||
Form {
|
||||
Section {
|
||||
DayOfWeekPicker(days: $days)
|
||||
} header: {
|
||||
HStack {
|
||||
Text("On these days").textCase(nil)
|
||||
Spacer()
|
||||
Text(days.summary).textCase(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// HoldToCommitButton.swift
|
||||
// Severed
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// The deliberate-friction commit button: the user must press and hold for a
|
||||
/// second to save a rule, mirroring the reference app's "Hold to Commit".
|
||||
struct HoldToCommitButton: View {
|
||||
var title = "Hold to Commit"
|
||||
var holdDuration: Double = 1.0
|
||||
let action: () -> Void
|
||||
|
||||
@State private var progress: Double = 0
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 28)
|
||||
.fill(Theme.surfaceElevated)
|
||||
GeometryReader { proxy in
|
||||
RoundedRectangle(cornerRadius: 28)
|
||||
.fill(Theme.commitGradient)
|
||||
.opacity(0.85)
|
||||
.frame(width: proxy.size.width * progress)
|
||||
.animation(.linear(duration: 0.1), value: progress == 0)
|
||||
}
|
||||
Text(title)
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
.frame(height: 56)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28))
|
||||
.contentShape(RoundedRectangle(cornerRadius: 28))
|
||||
.onLongPressGesture(minimumDuration: holdDuration) {
|
||||
progress = 0
|
||||
action()
|
||||
} onPressingChanged: { pressing in
|
||||
withAnimation(.linear(duration: pressing ? holdDuration : 0.2)) {
|
||||
progress = pressing ? 1 : 0
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityAddTraits(.isButton)
|
||||
.accessibilityIdentifier("holdToCommitButton")
|
||||
.accessibilityLabel(title)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HoldToCommitButton { }
|
||||
.padding()
|
||||
.background(Theme.background)
|
||||
}
|
||||
Reference in New Issue
Block a user