Complete accessibility identifier coverage across all 152 project files

Exhaustive file-by-file audit of every Swift file in the project (iOS app,
Watch app, Widget extension). Every interactive UI element — buttons, toggles,
pickers, links, menus, tap gestures, text editors, color pickers, photo
pickers — now has an accessibilityIdentifier for XCUITest automation.

46 files changed across Shared/, Onboarding/, Watch App/, and Widget targets.
Added ~100 new ID definitions covering settings debug controls, export/photo
views, sharing templates, customization subviews, onboarding flows, tip
modals, widget voting buttons, and watch mood buttons.
This commit is contained in:
Trey T
2026-03-26 08:34:56 -05:00
parent e7648ddd8a
commit ed8205cd88
46 changed files with 391 additions and 13 deletions

View File

@@ -111,6 +111,7 @@ struct MoodButton: View {
.cornerRadius(12) .cornerRadius(12)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Watch.moodButton(mood.strValue))
} }
} }

View File

@@ -214,6 +214,7 @@ struct NonSubscriberView: View {
} }
.accessibilityLabel(String(localized: "Track Your Mood")) .accessibilityLabel(String(localized: "Track Your Mood"))
.accessibilityHint(String(localized: "Tap to open app and subscribe")) .accessibilityHint(String(localized: "Tap to open app and subscribe"))
.accessibilityIdentifier(AccessibilityID.Widget.subscribeLink)
} }
} }

View File

@@ -93,6 +93,7 @@ struct VotingView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood")) .accessibilityHint(String(localized: "Log this mood"))
.accessibilityIdentifier(AccessibilityID.Widget.voteMoodButton(mood.strValue))
} else { } else {
Link(destination: URL(string: "reflect://subscribe")!) { Link(destination: URL(string: "reflect://subscribe")!) {
moodIcon(for: mood, size: size) moodIcon(for: mood, size: size)
@@ -100,6 +101,7 @@ struct VotingView: View {
} }
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe")) .accessibilityHint(String(localized: "Open app to subscribe"))
.accessibilityIdentifier(AccessibilityID.Widget.subscribeLink)
} }
} }
@@ -119,12 +121,14 @@ struct VotingView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood")) .accessibilityHint(String(localized: "Log this mood"))
.accessibilityIdentifier(AccessibilityID.Widget.voteMoodButton(mood.strValue))
} else { } else {
Link(destination: URL(string: "reflect://subscribe")!) { Link(destination: URL(string: "reflect://subscribe")!) {
content content
} }
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe")) .accessibilityHint(String(localized: "Open app to subscribe"))
.accessibilityIdentifier(AccessibilityID.Widget.subscribeLink)
} }
} }
@@ -196,12 +200,14 @@ struct LargeVotingView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood")) .accessibilityHint(String(localized: "Log this mood"))
.accessibilityIdentifier(AccessibilityID.Widget.voteMoodButton(mood.strValue))
} else { } else {
Link(destination: URL(string: "reflect://subscribe")!) { Link(destination: URL(string: "reflect://subscribe")!) {
moodButtonContent(for: mood) moodButtonContent(for: mood)
} }
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe")) .accessibilityHint(String(localized: "Open app to subscribe"))
.accessibilityIdentifier(AccessibilityID.Widget.subscribeLink)
} }
} }
@@ -261,12 +267,14 @@ struct InlineVotingView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood")) .accessibilityHint(String(localized: "Log this mood"))
.accessibilityIdentifier(AccessibilityID.Widget.voteMoodButton(mood.strValue))
} else { } else {
Link(destination: URL(string: "reflect://subscribe")!) { Link(destination: URL(string: "reflect://subscribe")!) {
moodIcon(for: mood) moodIcon(for: mood)
} }
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe")) .accessibilityHint(String(localized: "Open app to subscribe"))
.accessibilityIdentifier(AccessibilityID.Widget.subscribeLink)
} }
} }

View File

@@ -120,6 +120,45 @@ enum AccessibilityID {
static let reminderTimePicker = "settings_reminder_time_picker" static let reminderTimePicker = "settings_reminder_time_picker"
static let reminderSaveButton = "settings_reminder_save" static let reminderSaveButton = "settings_reminder_save"
static let reminderCancelButton = "settings_reminder_cancel" static let reminderCancelButton = "settings_reminder_cancel"
static let reminderTimeButton = "settings_reminder_time"
static let changeTrialDateButton = "settings_change_trial_date"
static let trialDatePickerDoneButton = "settings_trial_date_done"
static let trialDatePicker = "settings_trial_date_picker"
static let paywallPreviewButton = "settings_paywall_preview"
static let tipsPreviewButton = "settings_tips_preview"
static let testNotificationsButton = "settings_test_notifications"
static let exportWidgetsButton = "settings_export_widgets"
static let exportVotingLayoutsButton = "settings_export_voting_layouts"
static let exportWatchViewsButton = "settings_export_watch_views"
static let exportInsightsButton = "settings_export_insights"
static let generateScreenshotsButton = "settings_generate_screenshots"
static let addTestDataButton = "settings_add_test_data"
static let deleteHealthKitButton = "settings_delete_health_kit"
static let locationAlertOpenSettingsButton = "settings_location_open_settings"
static let locationAlertCancelButton = "settings_location_cancel"
static let fontAwesomeLink = "settings_font_awesome_link"
static let chartsLink = "settings_charts_link"
static let exportDataButton = "settings_export_data"
static let closeButton = "settings_close"
static let resetLaunchDateButton = "settings_reset_launch_date"
static let fixWeekdayButton = "settings_fix_weekday"
static let whyBackgroundModeButton = "settings_why_bg_mode"
static let exportLegacyButton = "settings_export_legacy"
static let importButton = "settings_import"
static let randomIconsButton = "settings_random_icons"
static let doneButton = "settings_done"
static let specialThanksButton = "settings_special_thanks"
}
// MARK: - TipModal
enum TipModal {
static let dismissButton = "tip_modal_dismiss"
static let resetTipsButton = "tip_modal_reset_tips"
static let tipsEnabledToggle = "tip_modal_tips_enabled"
static let doneButton = "tip_modal_done"
static func tipPreviewButton(_ index: Int) -> String {
"tip_modal_preview_\(index)"
}
} }
// MARK: - Customize // MARK: - Customize
@@ -129,6 +168,7 @@ enum AccessibilityID {
static let appThemePickerDoneButton = "apptheme_picker_done" static let appThemePickerDoneButton = "apptheme_picker_done"
static let appThemePreviewCancelButton = "apptheme_preview_cancel" static let appThemePreviewCancelButton = "apptheme_preview_cancel"
static let appThemePreviewApplyButton = "apptheme_preview_apply" static let appThemePreviewApplyButton = "apptheme_preview_apply"
static let widgetHowToLink = "customize_widget_how_to_link"
static func themeButton(_ name: String) -> String { static func themeButton(_ name: String) -> String {
"customize_theme_\(name.lowercased())" "customize_theme_\(name.lowercased())"
} }
@@ -147,6 +187,31 @@ enum AccessibilityID {
static func appThemeCard(_ name: String) -> String { static func appThemeCard(_ name: String) -> String {
"apptheme_card_\(name.lowercased())" "apptheme_card_\(name.lowercased())"
} }
static func customWidget(_ index: Int) -> String {
"customize_widget_\(index)"
}
static let customWidgetAdd = "customize_widget_add"
static func shapeOption(_ name: String) -> String {
"customize_shape_\(name.lowercased())"
}
static let shapeRefresh = "customize_shape_refresh"
static func imagePackOption(_ name: String) -> String {
"customize_imagepack_option_\(name.lowercased())"
}
static func personalityPackOption(_ name: String) -> String {
"customize_personalitypack_option_\(name.lowercased())"
}
static func celebrationAnimationButton(_ name: String) -> String {
"customize_celebration_\(name.lowercased())"
}
static let manageSubscriptionButton = "customize_manage_subscription"
static let unlockPremiumButton = "customize_unlock_premium"
static func dayFilterButton(_ day: String) -> String {
"customize_day_filter_\(day.lowercased())"
}
static func iconButton(_ name: String) -> String {
"customize_icon_\(name.lowercased())"
}
} }
// MARK: - Paywall // MARK: - Paywall
@@ -173,21 +238,32 @@ enum AccessibilityID {
static let monthSection = "insights_month_section" static let monthSection = "insights_month_section"
static let yearSection = "insights_year_section" static let yearSection = "insights_year_section"
static let allTimeSection = "insights_all_time_section" static let allTimeSection = "insights_all_time_section"
static let expandCollapseButton = "insights_expand_collapse"
} }
// MARK: - Month View // MARK: - Month View
enum MonthView { enum MonthView {
static let grid = "month_grid" static let grid = "month_grid"
static let shareButton = "month_share_button" static let shareButton = "month_share_button"
static let statsToggleButton = "month_stats_toggle"
static let settingsButton = "month_settings_button"
static func dayCell(dateString: String) -> String {
"month_day_cell_\(dateString)"
}
static let debugDemoToggle = "month_debug_demo_toggle"
} }
// MARK: - Month Detail // MARK: - Month Detail
enum MonthDetail { enum MonthDetail {
static let shareButton = "month_detail_share" static let shareButton = "month_detail_share"
static let deleteButton = "month_detail_delete" static let deleteButton = "month_detail_delete"
static let cancelButton = "month_detail_cancel"
static func moodButton(_ mood: String) -> String { static func moodButton(_ mood: String) -> String {
"month_detail_mood_\(mood.lowercased())" "month_detail_mood_\(mood.lowercased())"
} }
static func entryCell(_ dateString: String) -> String {
"month_detail_entry_\(dateString)"
}
} }
// MARK: - Year View // MARK: - Year View
@@ -198,6 +274,7 @@ enum AccessibilityID {
static let statsSection = "year_stats_section" static let statsSection = "year_stats_section"
static func cardHeader(year: Int) -> String { "year_card_header_\(year)" } static func cardHeader(year: Int) -> String { "year_card_header_\(year)" }
static let shareButton = "year_share_button" static let shareButton = "year_share_button"
static let debugDemoToggle = "year_debug_demo_toggle"
} }
// MARK: - Onboarding // MARK: - Onboarding
@@ -213,12 +290,23 @@ enum AccessibilityID {
static let subscribeButton = "onboarding_subscribe_button" static let subscribeButton = "onboarding_subscribe_button"
static let skipButton = "onboarding_skip_button" static let skipButton = "onboarding_skip_button"
static let nextButton = "onboarding_next_button" static let nextButton = "onboarding_next_button"
static let timePicker = "onboarding_time_picker"
static let wrapupContinue = "onboarding_wrapup_continue"
static let titleOptionButton = "onboarding_title_option"
static func styleThemeButton(_ name: String) -> String {
"onboarding_style_theme_\(name.lowercased())"
}
} }
// MARK: - Reports // MARK: - Reports
enum Reports { enum Reports {
static let segmentedPicker = "reports_segmented_picker" static let segmentedPicker = "reports_segmented_picker"
static let dateRangePicker = "reports_date_range_picker" static let dateRangePicker = "reports_date_range_picker"
static let previousMonthButton = "reports_previous_month"
static let nextMonthButton = "reports_next_month"
static func dayCell(dateString: String) -> String {
"reports_day_cell_\(dateString)"
}
static let quickSummaryButton = "reports_quick_summary_button" static let quickSummaryButton = "reports_quick_summary_button"
static let detailedReportButton = "reports_detailed_report_button" static let detailedReportButton = "reports_detailed_report_button"
static let generateButton = "reports_generate_button" static let generateButton = "reports_generate_button"
@@ -229,6 +317,8 @@ enum AccessibilityID {
static let minimumEntriesWarning = "reports_minimum_entries_warning" static let minimumEntriesWarning = "reports_minimum_entries_warning"
static let exportDataButton = "reports_export_data_button" static let exportDataButton = "reports_export_data_button"
static let retryButton = "reports_retry_button" static let retryButton = "reports_retry_button"
static let privacyShareButton = "reports_privacy_share"
static let privacyCancelButton = "reports_privacy_cancel"
} }
// MARK: - Purchase / Subscription // MARK: - Purchase / Subscription
@@ -239,6 +329,11 @@ enum AccessibilityID {
static let subscribeButton = "purchase_subscribe" static let subscribeButton = "purchase_subscribe"
} }
// MARK: - Subscription Store
enum SubscriptionStore {
static let closeButton = "subscription_store_close"
}
// MARK: - IAP Warning // MARK: - IAP Warning
enum IAPWarning { enum IAPWarning {
static let subscribeButton = "iap_warning_subscribe" static let subscribeButton = "iap_warning_subscribe"
@@ -249,6 +344,7 @@ enum AccessibilityID {
static let unlockButton = "lock_screen_unlock" static let unlockButton = "lock_screen_unlock"
static let tryAgainButton = "lock_screen_try_again" static let tryAgainButton = "lock_screen_try_again"
static let cancelButton = "lock_screen_cancel" static let cancelButton = "lock_screen_cancel"
static let passcodeUnlockButton = "lock_screen_passcode_unlock"
static func passcodeButton(_ digit: Int) -> String { static func passcodeButton(_ digit: Int) -> String {
"lock_screen_passcode_\(digit)" "lock_screen_passcode_\(digit)"
} }
@@ -260,6 +356,135 @@ enum AccessibilityID {
static let dismissArea = "full_screen_photo_dismiss" static let dismissArea = "full_screen_photo_dismiss"
} }
// MARK: - Export
enum Export {
static let cancelButton = "export_cancel"
static let exportButton = "export_export"
static let alertOKButton = "export_alert_ok"
static func formatButton(_ format: String) -> String {
"export_format_\(format.lowercased())"
}
static func rangeButton(_ range: String) -> String {
"export_range_\(range.lowercased())"
}
}
// MARK: - Photo Picker
enum PhotoPicker {
static let cameraButton = "photo_picker_camera"
static let cancelButton = "photo_picker_cancel"
static let closeButton = "photo_picker_close"
static let shareButton = "photo_picker_share"
static let deleteButton = "photo_picker_delete"
static let deleteConfirmButton = "photo_picker_delete_confirm"
static let deleteCancelButton = "photo_picker_delete_cancel"
static let photosPicker = "photo_picker_library"
static let photoImage = "photo_picker_image"
static let menuButton = "photo_picker_menu"
}
// MARK: - Sharing
enum Sharing {
static let exitButton = "sharing_exit"
static let shareButton = "sharing_share"
static func moodMenuButton(_ mood: String) -> String {
"sharing_mood_menu_\(mood.lowercased())"
}
static let moodMenu = "sharing_mood_menu"
static func templateButton(_ description: String) -> String {
"sharing_template_\(description.lowercased().replacingOccurrences(of: " ", with: "_"))"
}
}
// MARK: - Sharing Templates
enum SharingTemplate {
static let dismissButton = "sharing_template_dismiss"
static let shareButton = "sharing_template_share"
static let moodMenu = "sharing_template_mood_menu"
static func moodMenuButton(_ mood: String) -> String {
"sharing_template_mood_menu_\(mood.lowercased())"
}
}
// MARK: - Custom Widget
enum CustomWidget {
static func colorPicker(_ name: String) -> String {
"custom_widget_color_\(name.lowercased())"
}
static let leftEyeButton = "custom_widget_left_eye"
static let rightEyeButton = "custom_widget_right_eye"
static let mouthButton = "custom_widget_mouth"
static func backgroundOption(_ index: Int) -> String {
"custom_widget_bg_\(index)"
}
static let randomBackgroundButton = "custom_widget_random_bg"
static let shuffleButton = "custom_widget_shuffle"
static let saveButton = "custom_widget_save"
static let useButton = "custom_widget_use"
static let deleteButton = "custom_widget_delete"
static func imageOption(_ name: String) -> String {
"custom_widget_image_\(name.lowercased())"
}
}
// MARK: - Debug / Preview
enum Debug {
static let animationDoneButton = "debug_animation_done"
static func animationCard(_ name: String) -> String {
"debug_animation_\(name.lowercased())"
}
static func debugMoodButton(_ mood: String) -> String {
"debug_mood_\(mood.lowercased())"
}
static let paywallPreviewDoneButton = "debug_paywall_done"
static let viewFullPaywallButton = "debug_view_full_paywall"
static func paywallStyleOption(_ name: String) -> String {
"debug_paywall_style_\(name.lowercased())"
}
static let liveActivityResetButton = "debug_live_activity_reset"
static let liveActivityToggleButton = "debug_live_activity_toggle"
static let liveActivityRecordButton = "debug_live_activity_record"
static let liveActivityDismissButton = "debug_live_activity_dismiss"
static let liveActivityExportButton = "debug_live_activity_export"
}
// MARK: - Sample Entry
enum SampleEntry {
static let refreshButton = "sample_entry_refresh"
}
// MARK: - Switchable View
enum SwitchableView {
static let headerToggle = "switchable_view_header_toggle"
}
// MARK: - Neon Mood Button (voting layout)
enum NeonMoodButton {
static func id(for mood: String) -> String {
"neon_mood_button_\(mood.lowercased())"
}
}
// MARK: - App Alerts
enum AppAlert {
static let storageUnavailableOK = "app_alert_storage_ok"
}
// MARK: - Watch
enum Watch {
static func moodButton(_ mood: String) -> String {
"watch_mood_button_\(mood.lowercased())"
}
}
// MARK: - Widget
enum Widget {
static func voteMoodButton(_ mood: String) -> String {
"widget_vote_mood_\(mood.lowercased())"
}
static let subscribeLink = "widget_subscribe_link"
}
// MARK: - Common // MARK: - Common
enum Common { enum Common {
static let lockScreen = "lock_screen" static let lockScreen = "lock_screen"

View File

@@ -195,6 +195,7 @@ struct OnboardingThemeCard: View {
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Onboarding.styleThemeButton(theme.name))
} }
} }

View File

@@ -65,6 +65,7 @@ struct OnboardingTime: View {
.datePickerStyle(.wheel) .datePickerStyle(.wheel)
.labelsHidden() .labelsHidden()
.colorScheme(.light) .colorScheme(.light)
.accessibilityIdentifier(AccessibilityID.Onboarding.timePicker)
.accessibilityLabel(String(localized: "Reminder time")) .accessibilityLabel(String(localized: "Reminder time"))
.accessibilityHint(String(localized: "Select when you want to be reminded")) .accessibilityHint(String(localized: "Select when you want to be reminded"))
} }

View File

@@ -36,6 +36,7 @@ struct OnboardingTitle: View {
.cornerRadius(10) .cornerRadius(10)
}) })
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
.accessibilityIdentifier(AccessibilityID.Onboarding.titleOptionButton)
.padding([.top], 10) .padding([.top], 10)
} }

View File

@@ -60,6 +60,7 @@ struct OnboardingWrapup: View {
.background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(Color.white)) .background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(Color.white))
.cornerRadius(10) .cornerRadius(10)
}) })
.accessibilityIdentifier(AccessibilityID.Onboarding.wrapupContinue)
.padding([.top], 65) .padding([.top], 65)
} }
.multilineTextAlignment(.center) .multilineTextAlignment(.center)

View File

@@ -73,6 +73,7 @@ struct ReflectApp: App {
.alert("Data Storage Unavailable", .alert("Data Storage Unavailable",
isPresented: $showStorageFallbackAlert) { isPresented: $showStorageFallbackAlert) {
Button("OK", role: .cancel) { } Button("OK", role: .cancel) { }
.accessibilityIdentifier(AccessibilityID.AppAlert.storageUnavailableOK)
} message: { } message: {
Text("Your mood data cannot be saved permanently. Please restart the app. If the problem persists, reinstall the app.") Text("Your mood data cannot be saved permanently. Please restart the app. If the problem persists, reinstall the app.")
} }

View File

@@ -696,7 +696,7 @@ struct NeonEqualizerBar: View {
} }
.buttonStyle(NeonBarButtonStyle(isPressed: $isPressed)) .buttonStyle(NeonBarButtonStyle(isPressed: $isPressed))
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.accessibilityIdentifier(AccessibilityID.MoodButton.id(for: mood.widgetDisplayName)) .accessibilityIdentifier(AccessibilityID.NeonMoodButton.id(for: mood.widgetDisplayName))
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Select this mood")) .accessibilityHint(String(localized: "Select this mood"))
} }

View File

@@ -109,7 +109,8 @@ struct CreateWidgetView: View {
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity) .frame(minHeight: 40, maxHeight: .infinity)
.background(.blue) .background(.blue)
.accessibilityIdentifier(AccessibilityID.CustomWidget.shuffleButton)
Button(action: { Button(action: {
AnalyticsManager.shared.track(.widgetCreated) AnalyticsManager.shared.track(.widgetCreated)
UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: false) UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: false)
@@ -127,7 +128,8 @@ struct CreateWidgetView: View {
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity) .frame(minHeight: 40, maxHeight: .infinity)
.background(.green) .background(.green)
.accessibilityIdentifier(AccessibilityID.CustomWidget.saveButton)
Button(action: { Button(action: {
AnalyticsManager.shared.track(.widgetUsed) AnalyticsManager.shared.track(.widgetUsed)
UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: true) UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: true)
@@ -145,7 +147,8 @@ struct CreateWidgetView: View {
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity) .frame(minHeight: 40, maxHeight: .infinity)
.background(.pink) .background(.pink)
.accessibilityIdentifier(AccessibilityID.CustomWidget.useButton)
if customWidget.isSaved { if customWidget.isSaved {
Button(action: { Button(action: {
AnalyticsManager.shared.track(.widgetDeleted) AnalyticsManager.shared.track(.widgetDeleted)
@@ -163,6 +166,7 @@ struct CreateWidgetView: View {
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity) .frame(minHeight: 40, maxHeight: .infinity)
.background(.orange) .background(.orange)
.accessibilityIdentifier(AccessibilityID.CustomWidget.deleteButton)
} }
} }
.frame(minHeight: 40, maxHeight: .infinity) .frame(minHeight: 40, maxHeight: .infinity)
@@ -178,6 +182,7 @@ struct CreateWidgetView: View {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "background")) AnalyticsManager.shared.track(.widgetColorUpdated(part: "background"))
} }
.labelsHidden() .labelsHidden()
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("bg"))
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
@@ -188,6 +193,7 @@ struct CreateWidgetView: View {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "inner")) AnalyticsManager.shared.track(.widgetColorUpdated(part: "inner"))
} }
.labelsHidden() .labelsHidden()
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("inner"))
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
@@ -198,6 +204,7 @@ struct CreateWidgetView: View {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "outline")) AnalyticsManager.shared.track(.widgetColorUpdated(part: "outline"))
} }
.labelsHidden() .labelsHidden()
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("stroke"))
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
} }
@@ -210,6 +217,7 @@ struct CreateWidgetView: View {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "left_eye")) AnalyticsManager.shared.track(.widgetColorUpdated(part: "left_eye"))
} }
.labelsHidden() .labelsHidden()
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("leftEye"))
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
@@ -220,6 +228,7 @@ struct CreateWidgetView: View {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "right_eye")) AnalyticsManager.shared.track(.widgetColorUpdated(part: "right_eye"))
} }
.labelsHidden() .labelsHidden()
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("rightEye"))
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
@@ -230,6 +239,7 @@ struct CreateWidgetView: View {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "mouth")) AnalyticsManager.shared.track(.widgetColorUpdated(part: "mouth"))
} }
.labelsHidden() .labelsHidden()
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("mouth"))
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
} }
@@ -250,16 +260,19 @@ struct CreateWidgetView: View {
.frame(minWidth: 10, idealWidth: 40, maxWidth: 40, .frame(minWidth: 10, idealWidth: 40, maxWidth: 40,
minHeight: 10, idealHeight: 40, maxHeight: 40, minHeight: 10, idealHeight: 40, maxHeight: 40,
alignment: .center) alignment: .center)
.accessibilityIdentifier(AccessibilityID.CustomWidget.backgroundOption(CustomWidgetBackGroundOptions.selectable.firstIndex(of: bg) ?? 0))
.onTapGesture { .onTapGesture {
update(background: bg) update(background: bg)
} }
} }
mixBG mixBG
.accessibilityIdentifier(AccessibilityID.CustomWidget.randomBackgroundButton)
.onTapGesture { .onTapGesture {
update(background: .random) update(background: .random)
} }
Divider() Divider()
ColorPicker("", selection: $customWidget.bgOverlayColor) ColorPicker("", selection: $customWidget.bgOverlayColor)
.accessibilityIdentifier(AccessibilityID.CustomWidget.colorPicker("bgOverlay"))
} }
.padding() .padding()
.background( .background(
@@ -270,6 +283,7 @@ struct CreateWidgetView: View {
var faceImageOptions: some View { var faceImageOptions: some View {
HStack(alignment: .center) { HStack(alignment: .center) {
Text(String(localized: "create_widget_view_left_eye")) Text(String(localized: "create_widget_view_left_eye"))
.accessibilityIdentifier(AccessibilityID.CustomWidget.leftEyeButton)
.onTapGesture(perform: { .onTapGesture(perform: {
showLeftEyeImagePicker.toggle() showLeftEyeImagePicker.toggle()
}) })
@@ -278,6 +292,7 @@ struct CreateWidgetView: View {
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
Divider() Divider()
Text(String(localized: "create_widget_view_right_eye")) Text(String(localized: "create_widget_view_right_eye"))
.accessibilityIdentifier(AccessibilityID.CustomWidget.rightEyeButton)
.onTapGesture(perform: { .onTapGesture(perform: {
showRightEyeImagePicker.toggle() showRightEyeImagePicker.toggle()
}) })
@@ -285,6 +300,7 @@ struct CreateWidgetView: View {
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
Divider() Divider()
Text(String(localized: "create_widget_view_mouth")) Text(String(localized: "create_widget_view_mouth"))
.accessibilityIdentifier(AccessibilityID.CustomWidget.mouthButton)
.onTapGesture(perform: { .onTapGesture(perform: {
showMuthImagePicker.toggle() showMuthImagePicker.toggle()
}) })

View File

@@ -538,6 +538,7 @@ struct CelebrationAnimationPickerCompact: View {
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Customize.celebrationAnimationButton(animation.rawValue))
} }
} }
.padding(.horizontal, 4) .padding(.horizontal, 4)
@@ -666,6 +667,7 @@ struct CustomWidgetSection: View {
CustomWidgetView(customWidgetModel: widget) CustomWidgetView(customWidgetModel: widget)
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.cornerRadius(12) .cornerRadius(12)
.accessibilityIdentifier(AccessibilityID.Customize.customWidget(UserDefaultsStore.getCustomWidgets().firstIndex(where: { $0.uuid == widget.uuid }) ?? 0))
.onTapGesture { .onTapGesture {
AnalyticsManager.shared.track(.widgetViewed) AnalyticsManager.shared.track(.widgetViewed)
selectedWidget.selectedItem = widget.copy() as? CustomWidgetModel selectedWidget.selectedItem = widget.copy() as? CustomWidgetModel
@@ -689,6 +691,7 @@ struct CustomWidgetSection: View {
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
} }
.accessibilityIdentifier(AccessibilityID.Customize.customWidgetAdd)
} }
} }
@@ -701,6 +704,7 @@ struct CustomWidgetSection: View {
} }
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} }
.accessibilityIdentifier(AccessibilityID.Customize.widgetHowToLink)
} }
.sheet(isPresented: $selectedWidget.showSheet) { .sheet(isPresented: $selectedWidget.showSheet) {
if let selectedItem = selectedWidget.selectedItem { if let selectedItem = selectedWidget.selectedItem {
@@ -822,6 +826,7 @@ struct SubscriptionBannerView: View {
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 8) .padding(.vertical, 8)
.background(Capsule().fill(Color.green.opacity(0.15))) .background(Capsule().fill(Color.green.opacity(0.15)))
.accessibilityIdentifier(AccessibilityID.Customize.manageSubscriptionButton)
} }
.padding(16) .padding(16)
} }
@@ -866,6 +871,7 @@ struct SubscriptionBannerView: View {
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Customize.unlockPremiumButton)
} }
private func openSubscriptionManagement() async { private func openSubscriptionManagement() async {

View File

@@ -23,6 +23,7 @@ struct CustomWigetView: View {
CustomWidgetView(customWidgetModel: widget) CustomWidgetView(customWidgetModel: widget)
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.cornerRadius(10) .cornerRadius(10)
.accessibilityIdentifier(AccessibilityID.Customize.customWidget(UserDefaultsStore.getCustomWidgets().firstIndex(where: { $0.uuid == widget.uuid }) ?? 0))
.onTapGesture { .onTapGesture {
AnalyticsManager.shared.track(.widgetViewed) AnalyticsManager.shared.track(.widgetViewed)
selectedWidget.selectedItem = widget.copy() as? CustomWidgetModel selectedWidget.selectedItem = widget.copy() as? CustomWidgetModel
@@ -34,6 +35,7 @@ struct CustomWigetView: View {
.overlay( .overlay(
Image(systemName: "plus") Image(systemName: "plus")
) )
.accessibilityIdentifier(AccessibilityID.Customize.customWidgetAdd)
.onTapGesture { .onTapGesture {
AnalyticsManager.shared.track(.widgetCreateTapped) AnalyticsManager.shared.track(.widgetCreateTapped)
selectedWidget.selectedItem = CustomWidgetModel.randomWidget selectedWidget.selectedItem = CustomWidgetModel.randomWidget
@@ -47,6 +49,7 @@ struct CustomWigetView: View {
.cornerRadius(10) .cornerRadius(10)
Text("[\(String(localized: "how_to_add_widget"))](https://support.apple.com/guide/iphone/add-widgets-iphb8f1bf206/ios)") Text("[\(String(localized: "how_to_add_widget"))](https://support.apple.com/guide/iphone/add-widgets-iphb8f1bf206/ios)")
.accessibilityIdentifier(AccessibilityID.Customize.widgetHowToLink)
.accentColor(textColor) .accentColor(textColor)
.padding(.bottom) .padding(.bottom)
} }

View File

@@ -45,6 +45,7 @@ struct DayFilterPickerView: View {
.cornerRadius(8) .cornerRadius(8)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Customize.dayFilterButton(day))
} }
} }
Text(String(localized: "day_picker_view_text")) Text(String(localized: "day_picker_view_text"))

View File

@@ -64,6 +64,7 @@ struct IconPickerView: View {
}) })
.accessibilityLabel(String(localized: "Default app icon")) .accessibilityLabel(String(localized: "Default app icon"))
.accessibilityHint(String(localized: "Double tap to select")) .accessibilityHint(String(localized: "Double tap to select"))
.accessibilityIdentifier(AccessibilityID.Customize.iconButton("default"))
ForEach(iconSets, id: \.self.0){ iconSet in ForEach(iconSets, id: \.self.0){ iconSet in
@@ -80,6 +81,7 @@ struct IconPickerView: View {
}) })
.accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))")) .accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))"))
.accessibilityHint(String(localized: "Double tap to select")) .accessibilityHint(String(localized: "Double tap to select"))
.accessibilityIdentifier(AccessibilityID.Customize.iconButton(iconSet.1))
} }
} }
.padding() .padding()

View File

@@ -41,6 +41,7 @@ struct ImagePackPickerView: View {
.fill(imagePack == images ? theme.currentTheme.bgColor : .clear) .fill(imagePack == images ? theme.currentTheme.bgColor : .clear)
.padding([.top, .bottom], -3) .padding([.top, .bottom], -3)
) )
.accessibilityIdentifier(AccessibilityID.Customize.imagePackOption(String(describing: images)))
.onTapGesture { .onTapGesture {
let impactMed = UIImpactFeedbackGenerator(style: .heavy) let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred() impactMed.impactOccurred()

View File

@@ -38,6 +38,7 @@ struct PersonalityPackPickerView: View {
.fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear) .fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear)
.padding(5) .padding(5)
) )
.accessibilityIdentifier(AccessibilityID.Customize.personalityPackOption(aPack.title()))
.onTapGesture { .onTapGesture {
let impactMed = UIImpactFeedbackGenerator(style: .heavy) let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred() impactMed.impactOccurred()

View File

@@ -31,6 +31,7 @@ struct ShapePickerView: View {
.resizable() .resizable()
.frame(width: 20, height: 20, alignment: .trailing) .frame(width: 20, height: 20, alignment: .trailing)
.foregroundColor(Color(UIColor.systemGray)) .foregroundColor(Color(UIColor.systemGray))
.accessibilityIdentifier(AccessibilityID.Customize.shapeRefresh)
.onTapGesture { .onTapGesture {
shapeRefreshToggleThing.toggle() shapeRefreshToggleThing.toggle()
} }
@@ -43,6 +44,7 @@ struct ShapePickerView: View {
bgColor: moodTint.color(forMood: Mood.allValues.randomElement()!), textColor: textColor) bgColor: moodTint.color(forMood: Mood.allValues.randomElement()!), textColor: textColor)
.frame(height: 50) .frame(height: 50)
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
.accessibilityIdentifier(AccessibilityID.Customize.shapeOption(String(describing: ashape)))
.onTapGesture { .onTapGesture {
let impactMed = UIImpactFeedbackGenerator(style: .heavy) let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred() impactMed.impactOccurred()

View File

@@ -58,6 +58,7 @@ struct ThemePickerView: View {
.fill(selectedTheme == theme ? selectedTheme.currentTheme.bgColor : .clear) .fill(selectedTheme == theme ? selectedTheme.currentTheme.bgColor : .clear)
.padding(-5) .padding(-5)
) )
.accessibilityIdentifier(AccessibilityID.Customize.themeButton(theme.title))
} }
private func selectTheme(_ theme: Theme) { private func selectTheme(_ theme: Theme) {

View File

@@ -59,6 +59,7 @@ struct VotingLayoutPickerView: View {
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Customize.votingLayoutButton(layout.displayName))
} }
} }
.padding(.horizontal) .padding(.horizontal)

View File

@@ -104,6 +104,7 @@ struct ExportView: View {
Button("Cancel") { Button("Cancel") {
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.Export.cancelButton)
} }
} }
.sheet(isPresented: $showShareSheet) { .sheet(isPresented: $showShareSheet) {
@@ -113,6 +114,7 @@ struct ExportView: View {
} }
.alert("Export Failed", isPresented: $showError) { .alert("Export Failed", isPresented: $showError) {
Button("OK", role: .cancel) { } Button("OK", role: .cancel) { }
.accessibilityIdentifier(AccessibilityID.Export.alertOKButton)
} message: { } message: {
Text(errorMessage) Text(errorMessage)
} }
@@ -230,6 +232,7 @@ struct ExportView: View {
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Export.formatButton(format.rawValue))
} }
} }
} }
@@ -260,6 +263,7 @@ struct ExportView: View {
.background(Color(.systemBackground)) .background(Color(.systemBackground))
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Export.rangeButton(range.rawValue))
if range != DateRange.allCases.last { if range != DateRange.allCases.last {
Divider() Divider()
@@ -293,6 +297,7 @@ struct ExportView: View {
.clipShape(RoundedRectangle(cornerRadius: 14)) .clipShape(RoundedRectangle(cornerRadius: 14))
} }
.disabled(isExporting || validEntries.isEmpty) .disabled(isExporting || validEntries.isEmpty)
.accessibilityIdentifier(AccessibilityID.Export.exportButton)
.padding(.top, 8) .padding(.top, 8)
} }

View File

@@ -29,6 +29,7 @@ struct ImagePickerGridView: View {
.scaledToFit() .scaledToFit()
.frame(width: 40, height: 40) .frame(width: 40, height: 40)
.foregroundColor(textColor) .foregroundColor(textColor)
.accessibilityIdentifier(AccessibilityID.CustomWidget.imageOption(item.rawValue))
.onTapGesture { .onTapGesture {
pickedImageClosure(item) pickedImageClosure(item)
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()

View File

@@ -277,6 +277,7 @@ struct InsightsSectionView: View {
.padding(.vertical, 14) .padding(.vertical, 14)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.Insights.expandCollapseButton)
.accessibilityAddTraits(.isHeader) .accessibilityAddTraits(.isHeader)
// Insights List (collapsible) // Insights List (collapsible)

View File

@@ -146,6 +146,7 @@ struct ReportDateRangePicker: View {
.background(Color.accentColor.opacity(0.15)) .background(Color.accentColor.opacity(0.15))
.clipShape(Circle()) .clipShape(Circle())
} }
.accessibilityIdentifier(AccessibilityID.Reports.previousMonthButton)
.accessibilityLabel("Previous month") .accessibilityLabel("Previous month")
Spacer() Spacer()
@@ -172,6 +173,7 @@ struct ReportDateRangePicker: View {
.background(Color.accentColor.opacity(0.15)) .background(Color.accentColor.opacity(0.15))
.clipShape(Circle()) .clipShape(Circle())
} }
.accessibilityIdentifier(AccessibilityID.Reports.nextMonthButton)
.accessibilityLabel("Next month") .accessibilityLabel("Next month")
.disabled(isDisplayingCurrentMonth) .disabled(isDisplayingCurrentMonth)
} }
@@ -341,6 +343,7 @@ private struct ReportDayCell: View {
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.disabled(isFuture) .disabled(isFuture)
.accessibilityIdentifier(AccessibilityID.Reports.dayCell(dateString: dayNumber))
.frame(height: 40) .frame(height: 40)
} }
} }

View File

@@ -95,7 +95,9 @@ struct ReportsView: View {
viewModel.exportDataPDF() viewModel.exportDataPDF()
} }
} }
.accessibilityIdentifier(AccessibilityID.Reports.privacyShareButton)
Button(String(localized: "Cancel"), role: .cancel) {} Button(String(localized: "Cancel"), role: .cancel) {}
.accessibilityIdentifier(AccessibilityID.Reports.privacyCancelButton)
} message: { } message: {
Text("This report contains your personal mood data and journal notes. Only share it with people you trust.") Text("This report contains your personal mood data and journal notes. Only share it with people you trust.")
} }

View File

@@ -1691,6 +1691,7 @@ struct LockScreenView: View {
.disabled(authManager.isAuthenticating) .disabled(authManager.isAuthenticating)
.padding(.top, 16) .padding(.top, 16)
.opacity(showContent ? 1 : 0) .opacity(showContent ? 1 : 0)
.accessibilityIdentifier(AccessibilityID.LockScreen.passcodeUnlockButton)
.accessibilityLabel("Use device passcode") .accessibilityLabel("Use device passcode")
.accessibilityHint("Double tap to authenticate with your device passcode") .accessibilityHint("Double tap to authenticate with your device passcode")
} }

View File

@@ -117,6 +117,7 @@ struct MonthDetailView: View {
selectedEntry = nil selectedEntry = nil
showUpdateEntryAlert = false showUpdateEntryAlert = false
}) })
.accessibilityIdentifier(AccessibilityID.MonthDetail.cancelButton)
} }
} }
@@ -153,6 +154,7 @@ struct MonthDetailView: View {
LazyVGrid(columns: columns, spacing: 25) { LazyVGrid(columns: columns, spacing: 25) {
ForEach(entries, id: \.self) { entry in ForEach(entries, id: \.self) { entry in
listViewEntry(forEntry: entry) listViewEntry(forEntry: entry)
.accessibilityIdentifier(AccessibilityID.MonthDetail.entryCell(DateFormattingCache.shared.string(for: entry.forDate, format: .dateMedium)))
.onTapGesture(perform: { .onTapGesture(perform: {
if entry.canEdit { if entry.canEdit {
selectedEntry = entry selectedEntry = entry

View File

@@ -378,6 +378,7 @@ struct MonthView: View {
.preferredColorScheme(theme.preferredColorScheme) .preferredColorScheme(theme.preferredColorScheme)
#if DEBUG #if DEBUG
// Triple-tap to toggle demo mode for video recording // Triple-tap to toggle demo mode for video recording
.accessibilityIdentifier(AccessibilityID.MonthView.debugDemoToggle)
.onTapGesture(count: 3) { .onTapGesture(count: 3) {
if demoManager.isDemoMode { if demoManager.isDemoMode {
demoManager.stopDemoMode() demoManager.stopDemoMode()
@@ -591,6 +592,7 @@ struct MonthCard: View, Equatable {
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.MonthView.statsToggleButton)
.accessibilityLabel("\(Random.monthName(fromMonthInt: month)) \(String(year)), \(showStats ? "expanded" : "collapsed")") .accessibilityLabel("\(Random.monthName(fromMonthInt: month)) \(String(year)), \(showStats ? "expanded" : "collapsed")")
.accessibilityHint("Double tap to toggle statistics") .accessibilityHint("Double tap to toggle statistics")
@@ -661,6 +663,7 @@ struct MonthCard: View, Equatable {
.fill(theme.currentTheme.secondaryBGColor) .fill(theme.currentTheme.secondaryBGColor)
) )
.contentShape(Rectangle()) .contentShape(Rectangle())
.accessibilityIdentifier(AccessibilityID.MonthView.dayCell(dateString: "\(month)_\(year)"))
.onTapGesture { .onTapGesture {
onTap() onTap()
} }
@@ -867,6 +870,7 @@ extension MonthView {
} }
.padding(.top, 60) .padding(.top, 60)
.padding(.trailing) .padding(.trailing)
.accessibilityIdentifier(AccessibilityID.MonthView.settingsButton)
Spacer() Spacer()
} }
} }

View File

@@ -76,6 +76,7 @@ struct PhotoPickerView: View {
) )
} }
.disabled(isProcessing) .disabled(isProcessing)
.accessibilityIdentifier(AccessibilityID.PhotoPicker.photosPicker)
// Camera // Camera
Button { Button {
@@ -111,6 +112,7 @@ struct PhotoPickerView: View {
) )
} }
.disabled(isProcessing) .disabled(isProcessing)
.accessibilityIdentifier(AccessibilityID.PhotoPicker.cameraButton)
} }
.padding(.horizontal) .padding(.horizontal)
@@ -130,6 +132,7 @@ struct PhotoPickerView: View {
Button("Cancel") { Button("Cancel") {
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.cancelButton)
} }
} }
.onChange(of: selectedItem) { _, newItem in .onChange(of: selectedItem) { _, newItem in
@@ -278,6 +281,7 @@ struct PhotoGalleryView: View {
} }
} }
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.photoImage)
} else { } else {
VStack(spacing: 16) { VStack(spacing: 16) {
Image(systemName: "photo.badge.exclamationmark") Image(systemName: "photo.badge.exclamationmark")
@@ -301,6 +305,7 @@ struct PhotoGalleryView: View {
.font(.title2) .font(.title2)
.foregroundStyle(.white.opacity(0.7)) .foregroundStyle(.white.opacity(0.7))
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.closeButton)
} }
ToolbarItem(placement: .primaryAction) { ToolbarItem(placement: .primaryAction) {
@@ -310,17 +315,20 @@ struct PhotoGalleryView: View {
} label: { } label: {
Label("Share", systemImage: "square.and.arrow.up") Label("Share", systemImage: "square.and.arrow.up")
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.shareButton)
Button(role: .destructive) { Button(role: .destructive) {
showDeleteConfirmation = true showDeleteConfirmation = true
} label: { } label: {
Label("Delete", systemImage: "trash") Label("Delete", systemImage: "trash")
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.deleteButton)
} label: { } label: {
Image(systemName: "ellipsis.circle.fill") Image(systemName: "ellipsis.circle.fill")
.font(.title2) .font(.title2)
.foregroundStyle(.white.opacity(0.7)) .foregroundStyle(.white.opacity(0.7))
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.menuButton)
} }
} }
.confirmationDialog("Delete Photo", isPresented: $showDeleteConfirmation, titleVisibility: .visible) { .confirmationDialog("Delete Photo", isPresented: $showDeleteConfirmation, titleVisibility: .visible) {
@@ -328,7 +336,9 @@ struct PhotoGalleryView: View {
onDelete() onDelete()
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.deleteConfirmButton)
Button("Cancel", role: .cancel) { } Button("Cancel", role: .cancel) { }
.accessibilityIdentifier(AccessibilityID.PhotoPicker.deleteCancelButton)
} message: { } message: {
Text("Are you sure you want to delete this photo?") Text("Are you sure you want to delete this photo?")
} }

View File

@@ -175,6 +175,7 @@ struct PurchaseButtonView: View {
.background(Color.pink) .background(Color.pink)
.cornerRadius(10) .cornerRadius(10)
} }
.accessibilityIdentifier(AccessibilityID.Purchase.subscribeButton)
// Restore purchases // Restore purchases
Button { Button {

View File

@@ -46,6 +46,7 @@ struct ReflectSubscriptionStoreView: View {
} }
.padding(16) .padding(16)
.accessibilityLabel("Close") .accessibilityLabel("Close")
.accessibilityIdentifier(AccessibilityID.SubscriptionStore.closeButton)
} }
.onAppear { .onAppear {
AppLogger.iap.info("SubscriptionStoreView appeared — source: \(source), productIDs: \(IAPManager.productIdentifiers.sorted().joined(separator: ", ")), groupID: \(IAPManager.subscriptionGroupID)") AppLogger.iap.info("SubscriptionStoreView appeared — source: \(source), productIDs: \(IAPManager.productIdentifiers.sorted().joined(separator: ", ")), groupID: \(IAPManager.subscriptionGroupID)")

View File

@@ -22,6 +22,7 @@ struct SampleEntryView: View {
.resizable() .resizable()
.frame(width: 20, height: 20, alignment: .trailing) .frame(width: 20, height: 20, alignment: .trailing)
.foregroundColor(Color(UIColor.systemGray)) .foregroundColor(Color(UIColor.systemGray))
.accessibilityIdentifier(AccessibilityID.SampleEntry.refreshButton)
.onTapGesture { .onTapGesture {
sampleListEntry = DataController.shared.generateObjectNotInArray(forDate: Date(), withMood: sampleListEntry.mood.next) sampleListEntry = DataController.shared.generateObjectNotInArray(forDate: Date(), withMood: sampleListEntry.mood.next)
} }

View File

@@ -54,6 +54,7 @@ struct DebugAnimationSettingsView: View {
Button("Done") { Button("Done") {
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.Debug.animationDoneButton)
} }
} }
} }
@@ -217,6 +218,7 @@ struct AnimationCard: View {
) )
.scaleEffect(isPressed ? 0.95 : (isSelected ? 1.02 : 1.0)) .scaleEffect(isPressed ? 0.95 : (isSelected ? 1.02 : 1.0))
} }
.accessibilityIdentifier(AccessibilityID.Debug.animationCard(type.rawValue))
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
.onLongPressGesture(minimumDuration: .infinity, pressing: { pressing in .onLongPressGesture(minimumDuration: .infinity, pressing: { pressing in
withAnimation(.easeInOut(duration: 0.15)) { withAnimation(.easeInOut(duration: 0.15)) {
@@ -336,6 +338,7 @@ struct DebugVotingContentView: View {
.fill(mood.color.opacity(0.15)) .fill(mood.color.opacity(0.15))
) )
} }
.accessibilityIdentifier(AccessibilityID.Debug.debugMoodButton(mood.strValue))
} }
} }

View File

@@ -58,6 +58,7 @@ struct LiveActivityPreviewView: View {
.background(Color.gray.opacity(0.2)) .background(Color.gray.opacity(0.2))
.cornerRadius(12) .cornerRadius(12)
} }
.accessibilityIdentifier(AccessibilityID.Debug.liveActivityResetButton)
Button(action: toggleAnimation) { Button(action: toggleAnimation) {
Label(isAnimating ? "Pause" : "Start", systemImage: isAnimating ? "pause.fill" : "play.fill") Label(isAnimating ? "Pause" : "Start", systemImage: isAnimating ? "pause.fill" : "play.fill")
@@ -68,6 +69,7 @@ struct LiveActivityPreviewView: View {
.foregroundColor(.white) .foregroundColor(.white)
.cornerRadius(12) .cornerRadius(12)
} }
.accessibilityIdentifier(AccessibilityID.Debug.liveActivityToggleButton)
} }
Button(action: { showRecordingMode = true }) { Button(action: { showRecordingMode = true }) {
@@ -79,6 +81,7 @@ struct LiveActivityPreviewView: View {
.foregroundColor(.white) .foregroundColor(.white)
.cornerRadius(12) .cornerRadius(12)
} }
.accessibilityIdentifier(AccessibilityID.Debug.liveActivityRecordButton)
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)
.padding(.bottom, 40) .padding(.bottom, 40)
@@ -264,6 +267,7 @@ struct LiveActivityRecordingView: View {
.background(Color.orange) .background(Color.orange)
.foregroundColor(.white) .foregroundColor(.white)
.cornerRadius(12) .cornerRadius(12)
.accessibilityIdentifier(AccessibilityID.Debug.liveActivityDismissButton)
} else if isExporting { } else if isExporting {
Text("Exporting frames...") Text("Exporting frames...")
.font(.title2.bold()) .font(.title2.bold())
@@ -282,6 +286,7 @@ struct LiveActivityRecordingView: View {
} }
} }
} }
.accessibilityIdentifier(AccessibilityID.Debug.liveActivityExportButton)
.onTapGesture { .onTapGesture {
if !isExporting && !exportComplete { if !isExporting && !exportComplete {
startExport() startExport()

View File

@@ -35,6 +35,7 @@ struct PaywallPreviewSettingsView: View {
Button("Done") { Button("Done") {
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.Debug.paywallPreviewDoneButton)
} }
} }
.sheet(isPresented: $showFullPreview) { .sheet(isPresented: $showFullPreview) {
@@ -160,6 +161,7 @@ struct PaywallPreviewSettingsView: View {
.foregroundColor(.white) .foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 14)) .clipShape(RoundedRectangle(cornerRadius: 14))
} }
.accessibilityIdentifier(AccessibilityID.Debug.viewFullPaywallButton)
} }
private var gradientColors: [Color] { private var gradientColors: [Color] {
@@ -241,6 +243,7 @@ struct StyleOptionRow: View {
.stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 2) .stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 2)
) )
} }
.accessibilityIdentifier(AccessibilityID.Debug.paywallStyleOption(style.displayName))
.buttonStyle(.plain) .buttonStyle(.plain)
} }

View File

@@ -255,6 +255,7 @@ struct WhyUpgradeView: View {
Button("Done") { Button("Done") {
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.Settings.doneButton)
} }
} }
} }

View File

@@ -149,6 +149,7 @@ struct SettingsContentView: View {
} }
.padding() .padding()
}) })
.accessibilityIdentifier(AccessibilityID.Settings.reminderTimeButton)
.accessibilityLabel(String(localized: "Reminder Time")) .accessibilityLabel(String(localized: "Reminder Time"))
.accessibilityValue(formattedReminderTime) .accessibilityValue(formattedReminderTime)
.accessibilityHint(String(localized: "Opens time picker to change reminder time")) .accessibilityHint(String(localized: "Opens time picker to change reminder time"))
@@ -269,6 +270,7 @@ struct SettingsContentView: View {
showTrialDatePicker = true showTrialDatePicker = true
} }
.font(.subheadline.weight(.medium)) .font(.subheadline.weight(.medium))
.accessibilityIdentifier(AccessibilityID.Settings.changeTrialDateButton)
} }
.padding() .padding()
} }
@@ -283,6 +285,7 @@ struct SettingsContentView: View {
displayedComponents: .date displayedComponents: .date
) )
.datePickerStyle(.graphical) .datePickerStyle(.graphical)
.accessibilityIdentifier(AccessibilityID.Settings.trialDatePicker)
.padding() .padding()
.navigationTitle("Set Trial Start Date") .navigationTitle("Set Trial Start Date")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@@ -295,6 +298,7 @@ struct SettingsContentView: View {
await iapManager.checkSubscriptionStatus() await iapManager.checkSubscriptionStatus()
} }
} }
.accessibilityIdentifier(AccessibilityID.Settings.trialDatePickerDoneButton)
} }
} }
} }
@@ -338,6 +342,7 @@ struct SettingsContentView: View {
} }
.padding() .padding()
} }
.accessibilityIdentifier(AccessibilityID.Settings.paywallPreviewButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -381,6 +386,7 @@ struct SettingsContentView: View {
} }
.padding() .padding()
} }
.accessibilityIdentifier(AccessibilityID.Settings.tipsPreviewButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -418,6 +424,7 @@ struct SettingsContentView: View {
} }
.padding() .padding()
} }
.accessibilityIdentifier(AccessibilityID.Settings.testNotificationsButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -470,6 +477,7 @@ struct SettingsContentView: View {
.padding() .padding()
} }
.disabled(isExportingWidgets) .disabled(isExportingWidgets)
.accessibilityIdentifier(AccessibilityID.Settings.exportWidgetsButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -522,6 +530,7 @@ struct SettingsContentView: View {
.padding() .padding()
} }
.disabled(isExportingVotingLayouts) .disabled(isExportingVotingLayouts)
.accessibilityIdentifier(AccessibilityID.Settings.exportVotingLayoutsButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -574,6 +583,7 @@ struct SettingsContentView: View {
.padding() .padding()
} }
.disabled(isExportingWatchViews) .disabled(isExportingWatchViews)
.accessibilityIdentifier(AccessibilityID.Settings.exportWatchViewsButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -632,6 +642,7 @@ struct SettingsContentView: View {
.padding() .padding()
} }
.disabled(isExportingInsights) .disabled(isExportingInsights)
.accessibilityIdentifier(AccessibilityID.Settings.exportInsightsButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -691,6 +702,7 @@ struct SettingsContentView: View {
.padding() .padding()
} }
.disabled(isGeneratingScreenshots) .disabled(isGeneratingScreenshots)
.accessibilityIdentifier(AccessibilityID.Settings.generateScreenshotsButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -741,6 +753,7 @@ struct SettingsContentView: View {
.padding() .padding()
} }
.disabled(isDeletingHealthKitData) .disabled(isDeletingHealthKitData)
.accessibilityIdentifier(AccessibilityID.Settings.deleteHealthKitButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -852,11 +865,12 @@ struct SettingsContentView: View {
} }
.padding() .padding()
} }
.accessibilityIdentifier(AccessibilityID.Settings.addTestDataButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }
private var healthKitToggle: some View { private var healthKitToggle: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(spacing: 12) { HStack(spacing: 12) {
@@ -1048,7 +1062,9 @@ struct SettingsContentView: View {
UIApplication.shared.open(url) UIApplication.shared.open(url)
} }
} }
.accessibilityIdentifier(AccessibilityID.Settings.locationAlertOpenSettingsButton)
Button(String(localized: "Cancel"), role: .cancel) {} Button(String(localized: "Cancel"), role: .cancel) {}
.accessibilityIdentifier(AccessibilityID.Settings.locationAlertCancelButton)
} message: { } message: {
Text("Reflect needs location access to show weather. You can enable it in Settings.") Text("Reflect needs location access to show weather. You can enable it in Settings.")
} }
@@ -1084,6 +1100,7 @@ struct SettingsContentView: View {
} }
.padding() .padding()
}) })
.accessibilityIdentifier(AccessibilityID.Settings.exportDataButton)
.accessibilityLabel(String(localized: "Export Data")) .accessibilityLabel(String(localized: "Export Data"))
.accessibilityHint(String(localized: "Export your mood data as CSV or PDF")) .accessibilityHint(String(localized: "Export your mood data as CSV or PDF"))
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
@@ -1382,6 +1399,7 @@ struct SettingsView: View {
Divider() Divider()
Text("Test builds only") Text("Test builds only")
Toggle("Bypass Subscription", isOn: $iapManager.bypassSubscription) Toggle("Bypass Subscription", isOn: $iapManager.bypassSubscription)
.accessibilityIdentifier(AccessibilityID.Settings.bypassSubscriptionToggle)
addTestDataCell addTestDataCell
clearDB clearDB
// fixWeekday // fixWeekday
@@ -1745,7 +1763,9 @@ struct SettingsView: View {
UIApplication.shared.open(url) UIApplication.shared.open(url)
} }
} }
.accessibilityIdentifier(AccessibilityID.Settings.locationAlertOpenSettingsButton)
Button(String(localized: "Cancel"), role: .cancel) {} Button(String(localized: "Cancel"), role: .cancel) {}
.accessibilityIdentifier(AccessibilityID.Settings.locationAlertCancelButton)
} message: { } message: {
Text("Reflect needs location access to show weather. You can enable it in Settings.") Text("Reflect needs location access to show weather. You can enable it in Settings.")
} }
@@ -1781,6 +1801,7 @@ struct SettingsView: View {
} }
.padding() .padding()
}) })
.accessibilityIdentifier(AccessibilityID.Settings.exportDataButton)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
@@ -1797,6 +1818,7 @@ struct SettingsView: View {
.font(.body) .font(.body)
.foregroundColor(Color(UIColor.systemBlue)) .foregroundColor(Color(UIColor.systemBlue))
}) })
.accessibilityIdentifier(AccessibilityID.Settings.closeButton)
} }
} }
@@ -1811,17 +1833,20 @@ struct SettingsView: View {
Text(String(localized: "settings_view_special_thanks_to_title")) Text(String(localized: "settings_view_special_thanks_to_title"))
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.specialThanksButton)
.padding() .padding()
if showSpecialThanks { if showSpecialThanks {
Divider() Divider()
Link("Font Awesome", destination: URL(string: "https://fontawesome.com")!) Link("Font Awesome", destination: URL(string: "https://fontawesome.com")!)
.accessibilityIdentifier(AccessibilityID.Settings.fontAwesomeLink)
.accentColor(textColor) .accentColor(textColor)
.padding(.bottom) .padding(.bottom)
Divider() Divider()
Link("Charts", destination: URL(string: "https://github.com/danielgindi/Charts")!) Link("Charts", destination: URL(string: "https://github.com/danielgindi/Charts")!)
.accessibilityIdentifier(AccessibilityID.Settings.chartsLink)
.accentColor(textColor) .accentColor(textColor)
.padding(.bottom) .padding(.bottom)
} }
@@ -1838,6 +1863,7 @@ struct SettingsView: View {
Text("Add test data") Text("Add test data")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.addTestDataButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
@@ -1867,6 +1893,7 @@ struct SettingsView: View {
showTrialDatePicker = true showTrialDatePicker = true
} }
.font(.subheadline.weight(.medium)) .font(.subheadline.weight(.medium))
.accessibilityIdentifier(AccessibilityID.Settings.changeTrialDateButton)
} }
.padding() .padding()
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
@@ -1880,6 +1907,7 @@ struct SettingsView: View {
displayedComponents: .date displayedComponents: .date
) )
.datePickerStyle(.graphical) .datePickerStyle(.graphical)
.accessibilityIdentifier(AccessibilityID.Settings.trialDatePicker)
.padding() .padding()
.navigationTitle("Set Trial Start Date") .navigationTitle("Set Trial Start Date")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@@ -1892,6 +1920,7 @@ struct SettingsView: View {
await iapManager.checkSubscriptionStatus() await iapManager.checkSubscriptionStatus()
} }
} }
.accessibilityIdentifier(AccessibilityID.Settings.trialDatePickerDoneButton)
} }
} }
} }
@@ -1909,6 +1938,7 @@ struct SettingsView: View {
Text("Reset luanch date to current date") Text("Reset luanch date to current date")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.resetLaunchDateButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
@@ -1923,6 +1953,7 @@ struct SettingsView: View {
Text("Clear DB") Text("Clear DB")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.clearDataButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
@@ -1937,6 +1968,7 @@ struct SettingsView: View {
Text("Fix Weekday") Text("Fix Weekday")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.fixWeekdayButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
@@ -1954,6 +1986,7 @@ struct SettingsView: View {
Text(String(localized: "settings_view_why_bg_mode_title")) Text(String(localized: "settings_view_why_bg_mode_title"))
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.whyBackgroundModeButton)
.padding() .padding()
if showWhyBGMode { if showWhyBGMode {
Text(String(localized: "settings_view_why_bg_mode_body")) Text(String(localized: "settings_view_why_bg_mode_body"))
@@ -2110,13 +2143,14 @@ struct SettingsView: View {
Text("Export") Text("Export")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.exportLegacyButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }
private var importData: some View { private var importData: some View {
Button(action: { Button(action: {
showingImporter.toggle() showingImporter.toggle()
@@ -2125,13 +2159,14 @@ struct SettingsView: View {
Text("Import") Text("Import")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.importButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }
private var randomIcons: some View { private var randomIcons: some View {
Button(action: { Button(action: {
var iconViews = [UIImage]() var iconViews = [UIImage]()
@@ -2255,6 +2290,7 @@ struct SettingsView: View {
Text("Create random icons") Text("Create random icons")
.foregroundColor(textColor) .foregroundColor(textColor)
}) })
.accessibilityIdentifier(AccessibilityID.Settings.randomIconsButton)
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(theme.currentTheme.secondaryBGColor) .background(theme.currentTheme.secondaryBGColor)

View File

@@ -154,10 +154,10 @@ struct SharingListView: View {
}, label: { }, label: {
ZStack { ZStack {
theme.currentTheme.secondaryBGColor theme.currentTheme.secondaryBGColor
item.preview item.preview
.frame(height: 88) .frame(height: 88)
VStack { VStack {
Spacer() Spacer()
Text(item.description) Text(item.description)
@@ -179,6 +179,7 @@ struct SharingListView: View {
.contentShape(Rectangle()) .contentShape(Rectangle())
.padding([.leading, .trailing]) .padding([.leading, .trailing])
}) })
.accessibilityIdentifier(AccessibilityID.Sharing.templateButton(item.description))
} }
} }

View File

@@ -52,6 +52,7 @@ struct SharingStylePickerView: View {
.font(.headline) .font(.headline)
.foregroundColor(.red) .foregroundColor(.red)
} }
.accessibilityIdentifier(AccessibilityID.Sharing.exitButton)
Spacer() Spacer()
@@ -104,6 +105,7 @@ struct SharingStylePickerView: View {
.background(Color.green) .background(Color.green)
.cornerRadius(14) .cornerRadius(14)
} }
.accessibilityIdentifier(AccessibilityID.Sharing.shareButton)
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.top, 12) .padding(.top, 12)
.padding(.bottom, 24) .padding(.bottom, 24)
@@ -160,6 +162,7 @@ struct LongestStreakPickerView: View {
.font(.headline) .font(.headline)
.foregroundColor(.red) .foregroundColor(.red)
} }
.accessibilityIdentifier(AccessibilityID.Sharing.exitButton)
Spacer() Spacer()
@@ -169,6 +172,7 @@ struct LongestStreakPickerView: View {
selectedMood = mood selectedMood = mood
recomputeStreak() recomputeStreak()
} }
.accessibilityIdentifier(AccessibilityID.Sharing.moodMenuButton(mood.strValue))
} }
} label: { } label: {
HStack(spacing: 6) { HStack(spacing: 6) {
@@ -180,6 +184,7 @@ struct LongestStreakPickerView: View {
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
} }
} }
.accessibilityIdentifier(AccessibilityID.Sharing.moodMenu)
Spacer() Spacer()
@@ -225,6 +230,7 @@ struct LongestStreakPickerView: View {
.background(Color.green) .background(Color.green)
.cornerRadius(14) .cornerRadius(14)
} }
.accessibilityIdentifier(AccessibilityID.Sharing.shareButton)
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.top, 12) .padding(.top, 12)
.padding(.bottom, 24) .padding(.bottom, 24)

View File

@@ -176,12 +176,13 @@ struct AllMoodsTotalTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.shareButton)
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.background( .background(
Color.green Color.green
) )
.padding(.trailing, -5) .padding(.trailing, -5)
Button(action: { Button(action: {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
}, label: { }, label: {
@@ -191,6 +192,7 @@ struct AllMoodsTotalTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.dismissButton)
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.background( .background(
Color.red Color.red

View File

@@ -119,6 +119,7 @@ struct CurrentStreakTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.shareButton)
.sheet(isPresented: self.$shareImage.showSheet) { .sheet(isPresented: self.$shareImage.showSheet) {
if let uiImage = self.shareImage.selectedShareImage { if let uiImage = self.shareImage.selectedShareImage {
ShareSheet(photo: uiImage) ShareSheet(photo: uiImage)
@@ -129,7 +130,7 @@ struct CurrentStreakTemplate: View, SharingTemplate {
Color.green Color.green
) )
.padding(.trailing, -5) .padding(.trailing, -5)
Button(action: { Button(action: {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
}, label: { }, label: {
@@ -139,6 +140,7 @@ struct CurrentStreakTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.dismissButton)
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.background( .background(
Color.red Color.red

View File

@@ -167,6 +167,7 @@ struct LongestStreakTemplate: View, SharingTemplate {
selectedMood = mood selectedMood = mood
configureData(fakeData: self.fakeData, mood: self.selectedMood) configureData(fakeData: self.fakeData, mood: self.selectedMood)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.moodMenuButton(mood.strValue))
} }
}, label: { }, label: {
Text("Pick Mood") Text("Pick Mood")
@@ -174,6 +175,7 @@ struct LongestStreakTemplate: View, SharingTemplate {
.foregroundColor(textColor) .foregroundColor(textColor)
.padding() .padding()
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.moodMenu)
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.background( .background(
theme.currentTheme.secondaryBGColor theme.currentTheme.secondaryBGColor
@@ -194,6 +196,7 @@ struct LongestStreakTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.shareButton)
.sheet(isPresented: self.$shareImage.showSheet) { .sheet(isPresented: self.$shareImage.showSheet) {
if let uiImage = self.shareImage.selectedShareImage { if let uiImage = self.shareImage.selectedShareImage {
ShareSheet(photo: uiImage) ShareSheet(photo: uiImage)
@@ -204,7 +207,7 @@ struct LongestStreakTemplate: View, SharingTemplate {
Color.green Color.green
) )
.padding(.trailing, -5) .padding(.trailing, -5)
Button(action: { Button(action: {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
}, label: { }, label: {
@@ -214,6 +217,7 @@ struct LongestStreakTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.dismissButton)
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.background( .background(
Color.red Color.red

View File

@@ -159,6 +159,7 @@ struct MonthTotalTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.shareButton)
.sheet(isPresented: self.$shareImage.showSheet) { .sheet(isPresented: self.$shareImage.showSheet) {
if let uiImage = self.shareImage.selectedShareImage { if let uiImage = self.shareImage.selectedShareImage {
ShareSheet(photo: uiImage) ShareSheet(photo: uiImage)
@@ -169,7 +170,7 @@ struct MonthTotalTemplate: View, SharingTemplate {
Color.green Color.green
) )
.padding(.trailing, -5) .padding(.trailing, -5)
Button(action: { Button(action: {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
}, label: { }, label: {
@@ -179,6 +180,7 @@ struct MonthTotalTemplate: View, SharingTemplate {
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.top, 20) .padding(.top, 20)
}) })
.accessibilityIdentifier(AccessibilityID.SharingTemplate.dismissButton)
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.background( .background(
Color.red Color.red

View File

@@ -93,6 +93,7 @@ struct SwitchableView: View {
theme.currentTheme.secondaryBGColor theme.currentTheme.secondaryBGColor
) )
.contentShape(Rectangle()) .contentShape(Rectangle())
.accessibilityIdentifier(AccessibilityID.SwitchableView.headerToggle)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
.padding(.bottom, 30) .padding(.bottom, 30)
.onTapGesture { .onTapGesture {

View File

@@ -140,6 +140,7 @@ struct TipModalView: View {
y: 6 y: 6
) )
} }
.accessibilityIdentifier(AccessibilityID.TipModal.dismissButton)
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.bottom, 24) .padding(.bottom, 24)
.opacity(appeared ? 1 : 0) .opacity(appeared ? 1 : 0)
@@ -308,6 +309,7 @@ struct TipsPreviewView: View {
} }
.padding(.vertical, 4) .padding(.vertical, 4)
} }
.accessibilityIdentifier(AccessibilityID.TipModal.tipPreviewButton(index))
} }
} header: { } header: {
Text("Tap to preview") Text("Tap to preview")
@@ -320,11 +322,13 @@ struct TipsPreviewView: View {
ReflectTipsManager.shared.resetAllTips() ReflectTipsManager.shared.resetAllTips()
} }
.foregroundColor(.red) .foregroundColor(.red)
.accessibilityIdentifier(AccessibilityID.TipModal.resetTipsButton)
Toggle("Tips Enabled", isOn: Binding( Toggle("Tips Enabled", isOn: Binding(
get: { ReflectTipsManager.shared.tipsEnabled }, get: { ReflectTipsManager.shared.tipsEnabled },
set: { ReflectTipsManager.shared.tipsEnabled = $0 } set: { ReflectTipsManager.shared.tipsEnabled = $0 }
)) ))
.accessibilityIdentifier(AccessibilityID.TipModal.tipsEnabledToggle)
} header: { } header: {
Text("Settings") Text("Settings")
} }
@@ -346,6 +350,7 @@ struct TipsPreviewView: View {
Button("Done") { Button("Done") {
dismiss() dismiss()
} }
.accessibilityIdentifier(AccessibilityID.TipModal.doneButton)
} }
} }
.sheet(item: Binding( .sheet(item: Binding(

View File

@@ -308,6 +308,7 @@ struct YearView: View {
.preferredColorScheme(theme.preferredColorScheme) .preferredColorScheme(theme.preferredColorScheme)
#if DEBUG #if DEBUG
// Triple-tap to toggle demo mode for video recording // Triple-tap to toggle demo mode for video recording
.accessibilityIdentifier(AccessibilityID.YearView.debugDemoToggle)
.onTapGesture(count: 3) { .onTapGesture(count: 3) {
if demoManager.isDemoMode { if demoManager.isDemoMode {
demoManager.stopDemoMode() demoManager.stopDemoMode()