Add missing accessibility identifiers to all interactive UI elements
Audit found ~50+ interactive elements (buttons, toggles, pickers, alerts, links) missing accessibility identifiers across 13 view files. Added centralized ID definitions and applied them to every entry detail button, guided reflection control, settings toggle, paywall unlock button, subscription/IAP button, lock screen control, and photo action dialog.
This commit is contained in:
@@ -31,7 +31,8 @@ struct GuidedReflectionInfoView: View {
|
||||
title: String(localized: "guided_reflection_about_cbt_title"),
|
||||
body: String(localized: "guided_reflection_about_cbt_body"),
|
||||
citation: "Beck, J. S. (2020). Cognitive Behavior Therapy: Basics and Beyond, 3rd ed.",
|
||||
url: cbtURL
|
||||
url: cbtURL,
|
||||
linkID: AccessibilityID.GuidedReflection.cbtLearnMoreLink
|
||||
)
|
||||
|
||||
// ACT
|
||||
@@ -41,7 +42,8 @@ struct GuidedReflectionInfoView: View {
|
||||
title: String(localized: "guided_reflection_about_act_title"),
|
||||
body: String(localized: "guided_reflection_about_act_body"),
|
||||
citation: "Harris, R. (2009). ACT Made Simple. New Harbinger Publications.",
|
||||
url: actURL
|
||||
url: actURL,
|
||||
linkID: AccessibilityID.GuidedReflection.actLearnMoreLink
|
||||
)
|
||||
|
||||
// BA
|
||||
@@ -51,7 +53,8 @@ struct GuidedReflectionInfoView: View {
|
||||
title: String(localized: "guided_reflection_about_ba_title"),
|
||||
body: String(localized: "guided_reflection_about_ba_body"),
|
||||
citation: "Martell, C. R., Dimidjian, S., & Herman-Dunn, R. (2010). Behavioral Activation for Depression.",
|
||||
url: baURL
|
||||
url: baURL,
|
||||
linkID: AccessibilityID.GuidedReflection.baLearnMoreLink
|
||||
)
|
||||
|
||||
// Disclaimer
|
||||
@@ -69,6 +72,7 @@ struct GuidedReflectionInfoView: View {
|
||||
Button(String(localized: "Done")) {
|
||||
dismiss()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.GuidedReflection.infoDoneButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +87,8 @@ struct GuidedReflectionInfoView: View {
|
||||
title: String,
|
||||
body: String,
|
||||
citation: String,
|
||||
url: URL
|
||||
url: URL,
|
||||
linkID: String
|
||||
) -> some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(spacing: 10) {
|
||||
@@ -113,6 +118,7 @@ struct GuidedReflectionInfoView: View {
|
||||
}
|
||||
.font(.caption)
|
||||
}
|
||||
.accessibilityIdentifier(linkID)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -101,7 +101,9 @@ struct GuidedReflectionView: View {
|
||||
Button(String(localized: "guided_reflection_discard"), role: .destructive) {
|
||||
dismiss()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.GuidedReflection.discardButton)
|
||||
Button(String(localized: "Cancel"), role: .cancel) { }
|
||||
.accessibilityIdentifier(AccessibilityID.GuidedReflection.keepEditingButton)
|
||||
} message: {
|
||||
Text(String(localized: "guided_reflection_unsaved_message"))
|
||||
}
|
||||
@@ -180,6 +182,7 @@ struct GuidedReflectionView: View {
|
||||
Image(systemName: "info.circle")
|
||||
}
|
||||
.accessibilityLabel(String(localized: "guided_reflection_about_title"))
|
||||
.accessibilityIdentifier(AccessibilityID.GuidedReflection.infoButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ struct IAPWarningView: View {
|
||||
.padding(.vertical, 12)
|
||||
.background(RoundedRectangle(cornerRadius: 10).fill(Color.pink))
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.IAPWarning.subscribeButton)
|
||||
}
|
||||
.padding()
|
||||
.background(theme.currentTheme.secondaryBGColor)
|
||||
|
||||
@@ -218,6 +218,7 @@ struct InsightsView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.accessibilityIdentifier(AccessibilityID.Paywall.insightsUnlockButton)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -332,6 +332,7 @@ struct ReportsView: View {
|
||||
viewModel.generateReport()
|
||||
}
|
||||
.font(.subheadline.weight(.medium))
|
||||
.accessibilityIdentifier(AccessibilityID.Reports.retryButton)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
@@ -403,6 +404,7 @@ struct ReportsView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.accessibilityIdentifier(AccessibilityID.Paywall.reportsUnlockButton)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -1670,6 +1670,7 @@ struct LockScreenView: View {
|
||||
.opacity(showContent ? 1 : 0)
|
||||
.offset(y: showContent ? 0 : 30)
|
||||
.padding(.horizontal, 32)
|
||||
.accessibilityIdentifier(AccessibilityID.LockScreen.unlockButton)
|
||||
.accessibilityLabel("Unlock")
|
||||
.accessibilityHint("Double tap to authenticate with \(authManager.biometricName)")
|
||||
|
||||
@@ -1705,7 +1706,9 @@ struct LockScreenView: View {
|
||||
await authManager.authenticate()
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.LockScreen.tryAgainButton)
|
||||
Button("Cancel", role: .cancel) { }
|
||||
.accessibilityIdentifier(AccessibilityID.LockScreen.cancelButton)
|
||||
} message: {
|
||||
Text("Unable to verify your identity. Please try again.")
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ struct MonthDetailView: View {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.foregroundColor(textColor)
|
||||
.padding(.trailing)
|
||||
.accessibilityIdentifier(AccessibilityID.MonthDetail.shareButton)
|
||||
.onTapGesture {
|
||||
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
|
||||
impactMed.impactOccurred()
|
||||
@@ -97,6 +98,7 @@ struct MonthDetailView: View {
|
||||
showUpdateEntryAlert = false
|
||||
selectedEntry = nil
|
||||
})
|
||||
.accessibilityIdentifier(AccessibilityID.MonthDetail.moodButton(mood.strValue))
|
||||
}
|
||||
|
||||
if let selectedEntry = selectedEntry,
|
||||
@@ -107,6 +109,7 @@ struct MonthDetailView: View {
|
||||
updateEntries()
|
||||
showUpdateEntryAlert = false
|
||||
})
|
||||
.accessibilityIdentifier(AccessibilityID.MonthDetail.deleteButton)
|
||||
}
|
||||
|
||||
Button(String(localized: "content_view_fill_in_missing_entry_cancel"), role: .cancel, action: {
|
||||
|
||||
@@ -324,6 +324,7 @@ struct MonthView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.accessibilityIdentifier(AccessibilityID.Paywall.monthUnlockButton)
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
@@ -81,6 +81,7 @@ struct NoteEditorView: View {
|
||||
Button("Done") {
|
||||
isTextFieldFocused = false
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.NoteEditor.keyboardDoneButton)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -226,7 +227,9 @@ struct EntryDetailView: View {
|
||||
onDelete()
|
||||
dismiss()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.deleteConfirmButton)
|
||||
Button("Cancel", role: .cancel) { }
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.deleteCancelButton)
|
||||
} message: {
|
||||
Text("Are you sure you want to delete this mood entry? This cannot be undone.")
|
||||
}
|
||||
@@ -461,6 +464,7 @@ struct EntryDetailView: View {
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.reflectionBeginButton)
|
||||
}
|
||||
|
||||
Button {
|
||||
@@ -507,6 +511,7 @@ struct EntryDetailView: View {
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.reflectionCard)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +531,7 @@ struct EntryDetailView: View {
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoButton)
|
||||
}
|
||||
.zIndex(1)
|
||||
|
||||
@@ -542,6 +548,7 @@ struct EntryDetailView: View {
|
||||
.onTapGesture {
|
||||
showFullScreenPhoto = true
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoImage)
|
||||
} else {
|
||||
Button {
|
||||
showPhotoOptions = true
|
||||
@@ -568,22 +575,27 @@ struct EntryDetailView: View {
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoPlaceholder)
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Photo", isPresented: $showPhotoOptions, titleVisibility: .visible) {
|
||||
Button("Take Photo") {
|
||||
showCamera = true
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoTakeButton)
|
||||
Button("Choose from Library") {
|
||||
showPhotoPicker = true
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoChooseButton)
|
||||
if let photoID = entry.photoID {
|
||||
Button("Remove Photo", role: .destructive) {
|
||||
_ = PhotoManager.shared.deletePhoto(id: photoID)
|
||||
_ = DataController.shared.updatePhoto(forDate: entry.forDate, photoID: nil)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoRemoveButton)
|
||||
}
|
||||
Button("Cancel", role: .cancel) { }
|
||||
.accessibilityIdentifier(AccessibilityID.EntryDetail.photoCancelButton)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,9 +646,11 @@ struct FullScreenPhotoView: View {
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
.padding()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.FullScreenPhoto.closeButton)
|
||||
}
|
||||
.onTapGesture {
|
||||
dismiss()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.FullScreenPhoto.dismissArea)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ struct PurchaseButtonView: View {
|
||||
.foregroundColor(.blue)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Purchase.manageSubscriptionButton)
|
||||
|
||||
// Show other subscription options
|
||||
if iapManager.sortedProducts.count > 1 {
|
||||
@@ -98,6 +99,7 @@ struct PurchaseButtonView: View {
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Purchase.changePlanButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,6 +186,7 @@ struct PurchaseButtonView: View {
|
||||
.font(.body)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Purchase.restorePurchasesButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -814,6 +814,7 @@ struct SettingsContentView: View {
|
||||
}
|
||||
))
|
||||
.labelsHidden()
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.privacyLockToggle)
|
||||
.accessibilityLabel(String(localized: "Privacy Lock"))
|
||||
.accessibilityHint(String(localized: "Require biometric authentication to open app"))
|
||||
}
|
||||
@@ -913,6 +914,7 @@ struct SettingsContentView: View {
|
||||
))
|
||||
.labelsHidden()
|
||||
.disabled(iapManager.shouldShowPaywall)
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.healthSyncToggle)
|
||||
.accessibilityLabel(String(localized: "Apple Health"))
|
||||
.accessibilityHint(String(localized: "Sync mood data with Apple Health"))
|
||||
} else {
|
||||
@@ -1012,6 +1014,7 @@ struct SettingsContentView: View {
|
||||
))
|
||||
.labelsHidden()
|
||||
.disabled(iapManager.shouldShowPaywall)
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.weatherToggle)
|
||||
.accessibilityLabel(String(localized: "Weather"))
|
||||
.accessibilityHint(String(localized: "Show weather details for each day"))
|
||||
}
|
||||
@@ -1128,6 +1131,7 @@ struct SettingsContentView: View {
|
||||
.onChange(of: hapticFeedbackEnabled) { _, newValue in
|
||||
AnalyticsManager.shared.track(.hapticFeedbackToggled(enabled: newValue))
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.hapticFeedbackToggle)
|
||||
.accessibilityLabel(String(localized: "Haptic Feedback"))
|
||||
.accessibilityHint(String(localized: "Toggle vibration feedback when voting"))
|
||||
}
|
||||
@@ -1145,6 +1149,7 @@ struct SettingsContentView: View {
|
||||
AnalyticsManager.shared.track(.deleteToggleChanged(enabled: newValue))
|
||||
}
|
||||
.foregroundColor(textColor)
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.deleteToggle)
|
||||
.accessibilityHint(String(localized: "Allow deleting mood entries by swiping"))
|
||||
.padding()
|
||||
}
|
||||
@@ -1274,6 +1279,7 @@ struct ReminderTimePickerView: View {
|
||||
)
|
||||
.datePickerStyle(.wheel)
|
||||
.labelsHidden()
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.reminderTimePicker)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
@@ -1285,12 +1291,14 @@ struct ReminderTimePickerView: View {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.reminderCancelButton)
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") {
|
||||
saveReminderTime()
|
||||
dismiss()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.reminderSaveButton)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
@@ -1545,6 +1553,7 @@ struct SettingsView: View {
|
||||
}
|
||||
))
|
||||
.labelsHidden()
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.privacyLockToggle)
|
||||
}
|
||||
.padding()
|
||||
.background(theme.currentTheme.secondaryBGColor)
|
||||
@@ -1614,6 +1623,7 @@ struct SettingsView: View {
|
||||
))
|
||||
.labelsHidden()
|
||||
.disabled(iapManager.shouldShowPaywall)
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.healthSyncToggle)
|
||||
} else {
|
||||
Text("Not Available")
|
||||
.font(.caption)
|
||||
@@ -1705,6 +1715,7 @@ struct SettingsView: View {
|
||||
))
|
||||
.labelsHidden()
|
||||
.disabled(iapManager.shouldShowPaywall)
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.weatherToggle)
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -1963,13 +1974,14 @@ struct SettingsView: View {
|
||||
Text(String(localized: "settings_view_show_onboarding"))
|
||||
.foregroundColor(textColor)
|
||||
})
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.showOnboardingButton)
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(theme.currentTheme.secondaryBGColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
|
||||
}
|
||||
|
||||
|
||||
private var eulaButton: some View {
|
||||
Button(action: {
|
||||
AnalyticsManager.shared.track(.eulaViewed)
|
||||
@@ -1978,13 +1990,14 @@ struct SettingsView: View {
|
||||
Text(String(localized: "settings_view_show_eula"))
|
||||
.foregroundColor(textColor)
|
||||
})
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.eulaButton)
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(theme.currentTheme.secondaryBGColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
|
||||
}
|
||||
|
||||
|
||||
private var privacyButton: some View {
|
||||
Button(action: {
|
||||
AnalyticsManager.shared.track(.privacyPolicyViewed)
|
||||
@@ -1993,6 +2006,7 @@ struct SettingsView: View {
|
||||
Text(String(localized: "settings_view_show_privacy"))
|
||||
.foregroundColor(textColor)
|
||||
})
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.privacyPolicyButton)
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(theme.currentTheme.secondaryBGColor)
|
||||
@@ -2062,6 +2076,7 @@ struct SettingsView: View {
|
||||
.onChange(of: hapticFeedbackEnabled) { _, newValue in
|
||||
AnalyticsManager.shared.track(.hapticFeedbackToggled(enabled: newValue))
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.hapticFeedbackToggle)
|
||||
.accessibilityLabel(String(localized: "Haptic Feedback"))
|
||||
.accessibilityHint(String(localized: "Toggle vibration feedback when voting"))
|
||||
}
|
||||
@@ -2079,6 +2094,7 @@ struct SettingsView: View {
|
||||
AnalyticsManager.shared.track(.deleteToggleChanged(enabled: newValue))
|
||||
}
|
||||
.foregroundColor(textColor)
|
||||
.accessibilityIdentifier(AccessibilityID.Settings.deleteToggle)
|
||||
.padding()
|
||||
}
|
||||
.background(theme.currentTheme.secondaryBGColor)
|
||||
|
||||
@@ -260,6 +260,7 @@ struct YearView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.accessibilityIdentifier(AccessibilityID.Paywall.yearUnlockButton)
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Reference in New Issue
Block a user