diff --git a/Shared/AccessibilityIdentifiers.swift b/Shared/AccessibilityIdentifiers.swift index 1c1e69f..8ddcb03 100644 --- a/Shared/AccessibilityIdentifiers.swift +++ b/Shared/AccessibilityIdentifiers.swift @@ -49,6 +49,17 @@ enum AccessibilityID { static let noteButton = "entry_detail_note_button" static let noteArea = "entry_detail_note_area" static let moodGrid = "entry_detail_mood_grid" + static let reflectionBeginButton = "entry_detail_reflection_begin" + static let reflectionCard = "entry_detail_reflection_card" + static let photoButton = "entry_detail_photo_button" + static let photoPlaceholder = "entry_detail_photo_placeholder" + static let photoImage = "entry_detail_photo_image" + static let photoTakeButton = "entry_detail_photo_take" + static let photoChooseButton = "entry_detail_photo_choose" + static let photoRemoveButton = "entry_detail_photo_remove" + static let photoCancelButton = "entry_detail_photo_cancel" + static let deleteConfirmButton = "entry_detail_delete_confirm" + static let deleteCancelButton = "entry_detail_delete_cancel" } // MARK: - Note Editor @@ -56,6 +67,7 @@ enum AccessibilityID { static let textEditor = "note_editor_text" static let saveButton = "note_editor_save" static let cancelButton = "note_editor_cancel" + static let keyboardDoneButton = "note_editor_keyboard_done" } // MARK: - Guided Reflection @@ -67,6 +79,9 @@ enum AccessibilityID { static let backButton = "guided_reflection_back" static let saveButton = "guided_reflection_save" static let cancelButton = "guided_reflection_cancel" + static let infoButton = "guided_reflection_info" + static let discardButton = "guided_reflection_discard" + static let keepEditingButton = "guided_reflection_keep_editing" static func questionLabel(step: Int) -> String { "guided_reflection_question_\(step)" } @@ -75,6 +90,11 @@ enum AccessibilityID { static func chip(label: String) -> String { "guided_reflection_chip_\(label)" } + // Info view + static let infoDoneButton = "guided_reflection_info_done" + static let cbtLearnMoreLink = "guided_reflection_cbt_learn_more" + static let actLearnMoreLink = "guided_reflection_act_learn_more" + static let baLearnMoreLink = "guided_reflection_ba_learn_more" } // MARK: - Settings @@ -92,6 +112,14 @@ enum AccessibilityID { static let bypassSubscriptionToggle = "settings_bypass_subscription" static let eulaButton = "settings_eula" static let privacyPolicyButton = "settings_privacy_policy" + static let hapticFeedbackToggle = "settings_haptic_feedback_toggle" + static let deleteToggle = "settings_delete_toggle" + static let privacyLockToggle = "settings_privacy_lock_toggle" + static let healthSyncToggle = "settings_health_sync_toggle" + static let weatherToggle = "settings_weather_toggle" + static let reminderTimePicker = "settings_reminder_time_picker" + static let reminderSaveButton = "settings_reminder_save" + static let reminderCancelButton = "settings_reminder_cancel" } // MARK: - Customize @@ -126,6 +154,10 @@ enum AccessibilityID { static let monthOverlay = "paywall_month_overlay" static let yearOverlay = "paywall_year_overlay" static let insightsOverlay = "paywall_insights_overlay" + static let monthUnlockButton = "paywall_month_unlock" + static let yearUnlockButton = "paywall_year_unlock" + static let insightsUnlockButton = "paywall_insights_unlock" + static let reportsUnlockButton = "paywall_reports_unlock" } // MARK: - Day View Section Headers @@ -149,6 +181,15 @@ enum AccessibilityID { static let shareButton = "month_share_button" } + // MARK: - Month Detail + enum MonthDetail { + static let shareButton = "month_detail_share" + static let deleteButton = "month_detail_delete" + static func moodButton(_ mood: String) -> String { + "month_detail_mood_\(mood.lowercased())" + } + } + // MARK: - Year View enum YearView { static let heatmap = "year_heatmap" @@ -187,6 +228,36 @@ enum AccessibilityID { static let privacyConfirmation = "reports_privacy_confirmation" static let minimumEntriesWarning = "reports_minimum_entries_warning" static let exportDataButton = "reports_export_data_button" + static let retryButton = "reports_retry_button" + } + + // MARK: - Purchase / Subscription + enum Purchase { + static let manageSubscriptionButton = "purchase_manage_subscription" + static let changePlanButton = "purchase_change_plan" + static let restorePurchasesButton = "purchase_restore" + static let subscribeButton = "purchase_subscribe" + } + + // MARK: - IAP Warning + enum IAPWarning { + static let subscribeButton = "iap_warning_subscribe" + } + + // MARK: - Lock Screen + enum LockScreen { + static let unlockButton = "lock_screen_unlock" + static let tryAgainButton = "lock_screen_try_again" + static let cancelButton = "lock_screen_cancel" + static func passcodeButton(_ digit: Int) -> String { + "lock_screen_passcode_\(digit)" + } + } + + // MARK: - Full Screen Photo + enum FullScreenPhoto { + static let closeButton = "full_screen_photo_close" + static let dismissArea = "full_screen_photo_dismiss" } // MARK: - Common diff --git a/Shared/Views/GuidedReflectionInfoView.swift b/Shared/Views/GuidedReflectionInfoView.swift index f7112e6..8554f88 100644 --- a/Shared/Views/GuidedReflectionInfoView.swift +++ b/Shared/Views/GuidedReflectionInfoView.swift @@ -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() diff --git a/Shared/Views/GuidedReflectionView.swift b/Shared/Views/GuidedReflectionView.swift index 25e8048..a6a1f93 100644 --- a/Shared/Views/GuidedReflectionView.swift +++ b/Shared/Views/GuidedReflectionView.swift @@ -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) } } diff --git a/Shared/Views/IAPWarningView.swift b/Shared/Views/IAPWarningView.swift index 201f45c..e575fdf 100644 --- a/Shared/Views/IAPWarningView.swift +++ b/Shared/Views/IAPWarningView.swift @@ -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) diff --git a/Shared/Views/InsightsView/InsightsView.swift b/Shared/Views/InsightsView/InsightsView.swift index 303e047..31545fa 100644 --- a/Shared/Views/InsightsView/InsightsView.swift +++ b/Shared/Views/InsightsView/InsightsView.swift @@ -218,6 +218,7 @@ struct InsightsView: View { .clipShape(RoundedRectangle(cornerRadius: 14)) } .padding(.horizontal, 24) + .accessibilityIdentifier(AccessibilityID.Paywall.insightsUnlockButton) Spacer() } diff --git a/Shared/Views/InsightsView/ReportsView.swift b/Shared/Views/InsightsView/ReportsView.swift index ee6edbb..1b74554 100644 --- a/Shared/Views/InsightsView/ReportsView.swift +++ b/Shared/Views/InsightsView/ReportsView.swift @@ -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() } diff --git a/Shared/Views/LockScreenView.swift b/Shared/Views/LockScreenView.swift index 34ebb30..62f7dc7 100644 --- a/Shared/Views/LockScreenView.swift +++ b/Shared/Views/LockScreenView.swift @@ -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.") } diff --git a/Shared/Views/MonthView/MonthDetailView.swift b/Shared/Views/MonthView/MonthDetailView.swift index 55e75c5..d6bb53e 100644 --- a/Shared/Views/MonthView/MonthDetailView.swift +++ b/Shared/Views/MonthView/MonthDetailView.swift @@ -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: { diff --git a/Shared/Views/MonthView/MonthView.swift b/Shared/Views/MonthView/MonthView.swift index 4e1d1f3..63c018e 100644 --- a/Shared/Views/MonthView/MonthView.swift +++ b/Shared/Views/MonthView/MonthView.swift @@ -324,6 +324,7 @@ struct MonthView: View { .clipShape(RoundedRectangle(cornerRadius: 14)) } .padding(.horizontal, 24) + .accessibilityIdentifier(AccessibilityID.Paywall.monthUnlockButton) } .padding(.vertical, 24) .frame(maxWidth: .infinity) diff --git a/Shared/Views/NoteEditorView.swift b/Shared/Views/NoteEditorView.swift index 6948f20..360b6bc 100644 --- a/Shared/Views/NoteEditorView.swift +++ b/Shared/Views/NoteEditorView.swift @@ -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) } } diff --git a/Shared/Views/PurchaseButtonView.swift b/Shared/Views/PurchaseButtonView.swift index 0c46eb8..5ccfcbd 100644 --- a/Shared/Views/PurchaseButtonView.swift +++ b/Shared/Views/PurchaseButtonView.swift @@ -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) } } diff --git a/Shared/Views/SettingsView/SettingsView.swift b/Shared/Views/SettingsView/SettingsView.swift index 4aca96e..65851ed 100644 --- a/Shared/Views/SettingsView/SettingsView.swift +++ b/Shared/Views/SettingsView/SettingsView.swift @@ -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) diff --git a/Shared/Views/YearView/YearView.swift b/Shared/Views/YearView/YearView.swift index 7e28d93..6c91291 100644 --- a/Shared/Views/YearView/YearView.swift +++ b/Shared/Views/YearView/YearView.swift @@ -260,6 +260,7 @@ struct YearView: View { .clipShape(RoundedRectangle(cornerRadius: 14)) } .padding(.horizontal, 24) + .accessibilityIdentifier(AccessibilityID.Paywall.yearUnlockButton) } .padding(.vertical, 24) .frame(maxWidth: .infinity)