From 5dde78653e01c67e872df63f5b576fee0979eb39 Mon Sep 17 00:00:00 2001 From: Matthew Lam Date: Sun, 8 Mar 2026 19:14:50 -0400 Subject: [PATCH 1/2] chore: add accessibility labels, hints, and hidden markers to Home views Add VoiceOver support to all interactive elements in the Home feature: - Settings gear, year progress tile, details button, yesterday toggles - Focus completion checkboxes (inline edit + yesterday section) - Day detail navigation (close, previous day, next day) - Weekly progress day columns with combined accessibility elements - Swipe-to-delete accessible action and hidden trash icon - Decorative icons hidden (flame, calendar, chevron, checkmark/xmark) - Share button hint on streak card --- App/Features/Home/DayFocusDetailView.swift | 6 ++++++ App/Features/Home/FocusInlineEditRow.swift | 2 ++ App/Features/Home/FocusStatusCardView.swift | 2 ++ App/Features/Home/HomeView.swift | 6 ++++++ App/Features/Home/SwipeToDeleteRow.swift | 2 ++ App/Features/Home/WeeklyProgressGridView.swift | 16 ++++++++++++++++ .../Home/YearProgressPreviewTileView.swift | 2 ++ 7 files changed, 36 insertions(+) diff --git a/App/Features/Home/DayFocusDetailView.swift b/App/Features/Home/DayFocusDetailView.swift index c5903af..e4f88d8 100644 --- a/App/Features/Home/DayFocusDetailView.swift +++ b/App/Features/Home/DayFocusDetailView.swift @@ -54,6 +54,8 @@ struct DayFocusDetailView: View { .foregroundStyle(AppColors.textSecondary) } .buttonStyle(.plain) + .accessibilityLabel("Previous day") + .accessibilityHint("Shows the previous day's focuses") .opacity(canMoveBackward ? 1 : 0.3) .disabled(!canMoveBackward) @@ -73,6 +75,8 @@ struct DayFocusDetailView: View { .foregroundStyle(AppColors.textSecondary) } .buttonStyle(.plain) + .accessibilityLabel("Next day") + .accessibilityHint("Shows the next day's focuses") .opacity(canMoveForward ? 1 : 0.3) .disabled(!canMoveForward) } @@ -89,6 +93,8 @@ struct DayFocusDetailView: View { .clipShape(Circle()) } .buttonStyle(.plain) + .accessibilityLabel("Close") + .accessibilityHint("Dismisses the detail view") Spacer() } diff --git a/App/Features/Home/FocusInlineEditRow.swift b/App/Features/Home/FocusInlineEditRow.swift index 28270a6..37cbdae 100644 --- a/App/Features/Home/FocusInlineEditRow.swift +++ b/App/Features/Home/FocusInlineEditRow.swift @@ -39,6 +39,8 @@ struct FocusInlineEditRow: View { } } .buttonStyle(.plain) + .accessibilityLabel(focus.isCompleted ? "\(focus.title), completed" : "\(focus.title), not completed") + .accessibilityHint(focus.isCompleted ? "Marks as not completed" : "Marks as completed") TextField("Focus", text: $title) .font(.tenxLargeBody) diff --git a/App/Features/Home/FocusStatusCardView.swift b/App/Features/Home/FocusStatusCardView.swift index 9255ea4..7776299 100644 --- a/App/Features/Home/FocusStatusCardView.swift +++ b/App/Features/Home/FocusStatusCardView.swift @@ -30,6 +30,7 @@ struct FocusStatusCardView: View { Image(systemName: "flame.fill") .font(.tenxIconLarge) .foregroundStyle(streak > 0 ? Color.orange : AppColors.textMuted) + .accessibilityHidden(true) } HStack(alignment: .firstTextBaseline, spacing: 12) { @@ -54,6 +55,7 @@ struct FocusStatusCardView: View { } .buttonStyle(.plain) .accessibilityLabel("Share") + .accessibilityHint("Shares your streak as an image") } } .padding(20) diff --git a/App/Features/Home/HomeView.swift b/App/Features/Home/HomeView.swift index ef1fa72..6db5f87 100644 --- a/App/Features/Home/HomeView.swift +++ b/App/Features/Home/HomeView.swift @@ -41,6 +41,7 @@ struct HomeView: View { ) } .buttonStyle(.plain) + .accessibilityHint("Opens the full year progress view") entrySection @@ -81,6 +82,8 @@ struct HomeView: View { .foregroundStyle(AppColors.textSecondary) .frame(width: 32, height: 32) } + .accessibilityLabel("Settings") + .accessibilityHint("Opens the settings screen") } } .onAppear { @@ -187,6 +190,7 @@ struct HomeView: View { .clipShape(Capsule()) } .buttonStyle(.plain) + .accessibilityHint("Opens today's focus details") } } @@ -295,6 +299,8 @@ struct HomeView: View { } } .buttonStyle(.plain) + .accessibilityLabel(focus.isCompleted ? "\(focus.title), completed" : "\(focus.title), not completed") + .accessibilityHint(focus.isCompleted ? "Marks as not completed" : "Marks as completed") Text(focus.title) .font(.tenxLargeBody) diff --git a/App/Features/Home/SwipeToDeleteRow.swift b/App/Features/Home/SwipeToDeleteRow.swift index b2ecadd..339964f 100644 --- a/App/Features/Home/SwipeToDeleteRow.swift +++ b/App/Features/Home/SwipeToDeleteRow.swift @@ -24,6 +24,7 @@ struct SwipeToDeleteRow: View { .offset(x: offset) .background(rowSizeReader) .simultaneousGesture(dragGesture) + .accessibilityAction(named: "Delete") { action() } } .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)) } @@ -37,6 +38,7 @@ struct SwipeToDeleteRow: View { HStack(spacing: 6) { Image(systemName: "trash") .font(.tenxIconSmall) + .accessibilityHidden(true) if width > 64 { Text("Delete") .font(.tenxTinyBold) diff --git a/App/Features/Home/WeeklyProgressGridView.swift b/App/Features/Home/WeeklyProgressGridView.swift index 66a0b60..82f53fa 100644 --- a/App/Features/Home/WeeklyProgressGridView.swift +++ b/App/Features/Home/WeeklyProgressGridView.swift @@ -32,6 +32,7 @@ struct WeeklyProgressGridView: View { Image(systemName: "checkmark") .font(.tenxTinyBold) .foregroundStyle(Color.white) + .accessibilityHidden(true) } else if day.completed > 0 { Text("\(day.completed)/\(day.total)") .font(.tenxCaption) @@ -40,10 +41,13 @@ struct WeeklyProgressGridView: View { Image(systemName: "xmark") .font(.tenxTinyBold) .foregroundStyle(Color.white.opacity(0.7)) + .accessibilityHidden(true) } } } } + .accessibilityElement(children: .combine) + .accessibilityLabel(dayAccessibilityLabel(for: day)) } } @@ -79,6 +83,18 @@ struct WeeklyProgressGridView: View { private func dayLabel(for date: Date) -> String { DateFormatters.weekdayShort.string(from: date) } + + private func dayAccessibilityLabel(for day: WeeklyProgressDay) -> String { + let name = dayLabel(for: day.date) + guard day.total > 0 else { return "\(name), no focuses set" } + if day.maintainsStreak { + return "\(name), all \(day.total) focuses completed" + } + if day.completed > 0 { + return "\(name), \(day.completed) of \(day.total) focuses completed" + } + return "\(name), no focuses completed" + } } private struct LegendItem: View { diff --git a/App/Features/Home/YearProgressPreviewTileView.swift b/App/Features/Home/YearProgressPreviewTileView.swift index d1517df..e641731 100644 --- a/App/Features/Home/YearProgressPreviewTileView.swift +++ b/App/Features/Home/YearProgressPreviewTileView.swift @@ -45,6 +45,7 @@ struct YearProgressPreviewTileView: View { Image(systemName: "calendar") .font(.tenxIconMedium) .foregroundStyle(AppColors.textSecondary) + .accessibilityHidden(true) } } @@ -63,6 +64,7 @@ struct YearProgressPreviewTileView: View { Image(systemName: "chevron.right") .font(.tenxMicroSemibold) .foregroundStyle(AppColors.textMuted) + .accessibilityHidden(true) } } } From 26863a22101689d1ceb0fe18dafe4fd439b9cdd5 Mon Sep 17 00:00:00 2001 From: Matthew Lam Date: Sun, 8 Mar 2026 20:01:33 -0400 Subject: [PATCH 2/2] fix: hide dot grid from VoiceOver and fix footer accessibility label --- App/Features/Home/YearProgressPreviewTileView.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/App/Features/Home/YearProgressPreviewTileView.swift b/App/Features/Home/YearProgressPreviewTileView.swift index e641731..2d319c8 100644 --- a/App/Features/Home/YearProgressPreviewTileView.swift +++ b/App/Features/Home/YearProgressPreviewTileView.swift @@ -19,6 +19,7 @@ struct YearProgressPreviewTileView: View { .frame(height: 96) .background(AppColors.surface) .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + .accessibilityHidden(true) footer } @@ -54,6 +55,7 @@ struct YearProgressPreviewTileView: View { Text(footerText) .font(.tenxCaption) .foregroundStyle(AppColors.textSecondary) + .accessibilityLabel(footerAccessibilityLabel) Spacer() @@ -73,4 +75,9 @@ struct YearProgressPreviewTileView: View { guard summary.totalDays > 0 else { return "Year data will appear here" } return "\(summary.daysLeft)d left • \(String(format: "%.0f%%", summary.yearCompletionPercent))" } + + private var footerAccessibilityLabel: String { + guard summary.totalDays > 0 else { return "Year data will appear here" } + return "\(summary.daysLeft) days left, \(String(format: "%.0f", summary.yearCompletionPercent)) percent complete" + } }