diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index adb72deb1..49152e981 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -112,6 +112,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var hangUpButton: UIButton = { let result = UIButton(type: .custom) + result.accessibilityLabel = "End call button" result.setImage( UIImage(named: "EndCall")? .withRenderingMode(.alwaysTemplate), diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 3c1ef4444..1c67c9e25 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -13,6 +13,8 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let profileId: String let role: GroupMember.Role let profile: Profile? + let accessibilityLabel: String? + let accessibilityId: String? } private let threadId: String @@ -30,6 +32,8 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat private lazy var groupNameLabel: UILabel = { let result: UILabel = UILabel() + result.accessibilityLabel = "Group name" + result.isAccessibilityElement = true result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.themeTextColor = .textPrimary result.lineBreakMode = .byTruncatingTail @@ -44,12 +48,16 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat usesDefaultHeight: false ) result.textAlignment = .center + result.isAccessibilityElement = true + result.accessibilityIdentifier = "Group name text field" return result }() private lazy var addMembersButton: SessionButton = { let result: SessionButton = SessionButton(style: .bordered, size: .medium) + result.accessibilityLabel = "Add members" + result.isAccessibilityElement = true result.setTitle("vc_conversation_settings_invite_button_title".localized(), for: .normal) result.addTarget(self, action: #selector(addMembers), for: UIControl.Event.touchUpInside) result.contentEdgeInsets = UIEdgeInsets(top: 0, leading: Values.mediumSpacing, bottom: 0, trailing: Values.mediumSpacing) @@ -59,6 +67,9 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat @objc private lazy var tableView: UITableView = { let result: UITableView = UITableView() + result.accessibilityLabel = "Contact" + result.accessibilityIdentifier = "Contact" + result.isAccessibilityElement = true result.dataSource = self result.delegate = self result.separatorStyle = .none @@ -264,6 +275,12 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat } let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDoneButtonTapped)) + if isEditingGroupName { + doneButton.accessibilityLabel = "Accept name change" + } + else { + doneButton.accessibilityLabel = "Apply changes" + } doneButton.themeTintColor = .textPrimary navigationItem.rightBarButtonItem = doneButton } @@ -343,7 +360,9 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat GroupMemberDisplayInfo( profileId: profile.id, role: .standard, - profile: profile + profile: profile, + accessibilityLabel: "Contact", + accessibilityId: "Contact" ) } self?.membersAndZombies = (self?.membersAndZombies ?? []) diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index efadfcde6..49ed7ae16 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -48,6 +48,8 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate result.themeBorderColor = .borderSeparator result.layer.cornerRadius = 13 result.delegate = self + result.accessibilityIdentifier = "Group name input" + result.isAccessibilityElement = true return result }() @@ -133,6 +135,8 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate result.translatesAutoresizingMaskIntoConstraints = false result.setTitle("CREATE_GROUP_BUTTON_TITLE".localized(), for: .normal) result.addTarget(self, action: #selector(createClosedGroup), for: .touchUpInside) + result.accessibilityIdentifier = "Create group" + result.isAccessibilityElement = true result.set(.width, to: 160) return result @@ -151,6 +155,8 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close)) closeButton.themeTintColor = .textPrimary navigationItem.rightBarButtonItem = closeButton + navigationItem.leftBarButtonItem?.accessibilityIdentifier = "Cancel" + navigationItem.leftBarButtonItem?.isAccessibilityElement = true // Set up content setUpViewHierarchy() @@ -204,8 +210,9 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate title: profile.displayName(), rightAccessory: .radio(isSelected: { [weak self] in self?.selectedContacts.contains(profile.id) == true - }) - ), + }), + accessibilityIdentifier: "Contact" + ), style: .edgeToEdge, position: Position.with(indexPath.row, count: data[indexPath.section].elements.count) ) @@ -286,6 +293,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate explanation: message, cancelTitle: "BUTTON_OK".localized(), cancelStyle: .alert_text + ) ) present(modal, animated: true) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 282f90ade..b8f19816b 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -10,6 +10,7 @@ extension ContextMenuVC { let isEmojiAction: Bool let isEmojiPlus: Bool let isDismissAction: Bool + let accessibilityLabel: String? let work: () -> Void // MARK: - Initialization @@ -20,6 +21,7 @@ extension ContextMenuVC { isEmojiAction: Bool = false, isEmojiPlus: Bool = false, isDismissAction: Bool = false, + accessibilityLabel: String? = nil, work: @escaping () -> Void ) { self.icon = icon @@ -27,6 +29,7 @@ extension ContextMenuVC { self.isEmojiAction = isEmojiAction self.isEmojiPlus = isEmojiPlus self.isDismissAction = isDismissAction + self.accessibilityLabel = accessibilityLabel self.work = work } @@ -35,7 +38,8 @@ extension ContextMenuVC { static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action { return Action( icon: UIImage(named: "ic_reply"), - title: "context_menu_reply".localized() + title: "context_menu_reply".localized(), + accessibilityLabel: "Reply to message" ) { delegate?.reply(cellViewModel) } } @@ -56,14 +60,16 @@ extension ContextMenuVC { static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action { return Action( icon: UIImage(named: "ic_trash"), - title: "TXT_DELETE_TITLE".localized() + title: "TXT_DELETE_TITLE".localized(), + accessibilityLabel: "Delete message" ) { delegate?.delete(cellViewModel) } } static func save(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action { return Action( icon: UIImage(named: "ic_download"), - title: "context_menu_save".localized() + title: "context_menu_save".localized(), + accessibilityLabel: "Save attachment" ) { delegate?.save(cellViewModel) } } diff --git a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift index 07ebbc5ca..ac061dd5d 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift @@ -40,7 +40,7 @@ extension ContextMenuVC { self.dismiss = dismiss super.init(frame: CGRect.zero) - + self.accessibilityLabel = action.accessibilityLabel setUpViewHierarchy() } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index afd586d78..83cbbb45a 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -69,6 +69,8 @@ extension ConversationVC: title: "modal_call_permission_request_title".localized(), explanation: "modal_call_permission_request_explanation".localized(), confirmTitle: "vc_settings_title".localized(), + confirmAccessibilityLabel: "Settings", + cancelAccessibilityLabel: "Cancel", dismissOnConfirm: false // Custom dismissal logic ) { [weak self] _ in self?.dismiss(animated: true) { @@ -135,6 +137,8 @@ extension ConversationVC: range: (message as NSString).range(of: self.viewModel.threadData.displayName) ), confirmTitle: "modal_blocked_button_title".localized(), + confirmAccessibilityLabel: "Confirm block", + cancelAccessibilityLabel: "Cancel block", dismissOnConfirm: false // Custom dismissal logic ) { [weak self] _ in self?.viewModel.unblockContact() @@ -854,6 +858,8 @@ extension ConversationVC: range: (message as NSString).range(of: cellViewModel.authorName) ), confirmTitle: "modal_download_button_title".localized(), + confirmAccessibilityLabel: "Download media", + cancelAccessibilityLabel: "Don't download media", dismissOnConfirm: false // Custom dismissal logic ) { [weak self] _ in self?.viewModel.trustContact() diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index b6bc805e8..e906db92d 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -196,8 +196,11 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl lazy var blockedBanner: InfoBanner = { let result: InfoBanner = InfoBanner( message: self.viewModel.blockedBannerMessage, - backgroundColor: .danger + backgroundColor: .danger, + messageLabelAccessibilityLabel: "Blocked banner text" ) + result.accessibilityLabel = "Blocked banner" + result.isAccessibilityElement = true let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(unblock)) result.addGestureRecognizer(tapGestureRecognizer) @@ -245,6 +248,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl private lazy var messageRequestAcceptButton: UIButton = { let result: SessionButton = SessionButton(style: .bordered, size: .medium) + result.accessibilityLabel = "Accept message request" + result.isAccessibilityElement = true result.translatesAutoresizingMaskIntoConstraints = false result.setTitle("TXT_DELETE_ACCEPT".localized(), for: .normal) result.addTarget(self, action: #selector(acceptMessageRequest), for: .touchUpInside) @@ -254,6 +259,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl private lazy var messageRequestDeleteButton: UIButton = { let result: SessionButton = SessionButton(style: .destructive, size: .medium) + result.accessibilityLabel = "Decline message request" + result.isAccessibilityElement = true result.translatesAutoresizingMaskIntoConstraints = false result.setTitle("TXT_DECLINE_TITLE".localized(), for: .normal) result.addTarget(self, action: #selector(deleteMessageRequest), for: .touchUpInside) @@ -263,6 +270,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl private lazy var messageRequestBlockButton: UIButton = { let result: UIButton = UIButton() + result.accessibilityLabel = "Block message request" result.translatesAutoresizingMaskIntoConstraints = false result.clipsToBounds = true result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) @@ -1099,7 +1107,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl profilePictureView.addGestureRecognizer(tapGestureRecognizer) let settingsButtonItem: UIBarButtonItem = UIBarButtonItem(customView: profilePictureView) - settingsButtonItem.accessibilityLabel = "Settings button" + settingsButtonItem.accessibilityLabel = "More options" settingsButtonItem.isAccessibilityElement = true if SessionCall.isEnabled && !threadData.threadIsNoteToSelf { @@ -1109,6 +1117,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl target: self, action: #selector(startCall) ) + callButton.accessibilityLabel = "Call button" navigationItem.rightBarButtonItems = [settingsButtonItem, callButton] } @@ -1118,7 +1127,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl default: let rightBarButtonItem: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) - rightBarButtonItem.accessibilityLabel = "Settings button" + rightBarButtonItem.accessibilityLabel = "More options" rightBarButtonItem.isAccessibilityElement = true navigationItem.rightBarButtonItems = [rightBarButtonItem] diff --git a/Session/Conversations/Input View/ExpandingAttachmentsButton.swift b/Session/Conversations/Input View/ExpandingAttachmentsButton.swift index f2e983a03..cff2fda71 100644 --- a/Session/Conversations/Input View/ExpandingAttachmentsButton.swift +++ b/Session/Conversations/Input View/ExpandingAttachmentsButton.swift @@ -26,28 +26,28 @@ final class ExpandingAttachmentsButton: UIView, InputViewButtonDelegate { // MARK: UI Components lazy var gifButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_gif_black"), delegate: self, hasOpaqueBackground: true) - result.accessibilityLabel = "accessibility_gif_button".localized() + result.accessibilityLabel = "GIF button" return result }() lazy var gifButtonContainer = container(for: gifButton) lazy var documentButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_document_black"), delegate: self, hasOpaqueBackground: true) - result.accessibilityLabel = "accessibility_document_button".localized() + result.accessibilityLabel = "Documents folder" return result }() lazy var documentButtonContainer = container(for: documentButton) lazy var libraryButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_roll_black"), delegate: self, hasOpaqueBackground: true) - result.accessibilityLabel = "accessibility_library_button".localized() + result.accessibilityLabel = "Images folder" return result }() lazy var libraryButtonContainer = container(for: libraryButton) lazy var cameraButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "actionsheet_camera_black"), delegate: self, hasOpaqueBackground: true) - result.accessibilityLabel = "accessibility_camera_button".localized() + result.accessibilityLabel = "Select camera button" return result }() diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 8fd6d99f6..b1b40c892 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -50,12 +50,18 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M // MARK: - UI private var bottomStackView: UIStackView? - private lazy var attachmentsButton = ExpandingAttachmentsButton(delegate: delegate) + private lazy var attachmentsButton: ExpandingAttachmentsButton = { + let result = ExpandingAttachmentsButton(delegate: delegate) + result.accessibilityLabel = "Attachments button" + result.isAccessibilityElement = true + + return result + }() private lazy var voiceMessageButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "Microphone"), delegate: self) - result.accessibilityLabel = "VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE".localized() - result.accessibilityHint = "VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE".localized() + result.accessibilityLabel = "New voice message" + result.isAccessibilityElement = true return result }() @@ -63,7 +69,8 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M private lazy var sendButton: InputViewButton = { let result = InputViewButton(icon: #imageLiteral(resourceName: "ArrowUp"), isSendButton: true, delegate: self) result.isHidden = true - result.accessibilityLabel = "ATTACHMENT_APPROVAL_SEND_BUTTON".localized() + result.accessibilityLabel = "Send message button" + result.isAccessibilityElement = true return result }() @@ -78,6 +85,8 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M private lazy var mentionsViewContainer: UIView = { let result: UIView = UIView() + result.accessibilityLabel = "Mentions list" + result.isAccessibilityElement = true result.alpha = 0 let backgroundView = UIView() @@ -107,7 +116,11 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M // setUpViewHierarchy() for why these values are the way they are. let adjustment = (InputViewButton.expandedSize - InputViewButton.size) / 2 let maxWidth = UIScreen.main.bounds.width - 2 * InputViewButton.expandedSize - 2 * Values.smallSpacing - 2 * (Values.mediumSpacing - adjustment) - return InputTextView(delegate: self, maxWidth: maxWidth) + let result = InputTextView(delegate: self, maxWidth: maxWidth) + result.accessibilityLabel = "Message input box" + result.isAccessibilityElement = true + + return result }() private lazy var disabledInputLabel: UILabel = { diff --git a/Session/Conversations/Input View/MentionSelectionView.swift b/Session/Conversations/Input View/MentionSelectionView.swift index 70ceb6b4f..881059cc3 100644 --- a/Session/Conversations/Input View/MentionSelectionView.swift +++ b/Session/Conversations/Input View/MentionSelectionView.swift @@ -24,6 +24,7 @@ final class MentionSelectionView: UIView, UITableViewDataSource, UITableViewDele private lazy var tableView: UITableView = { let result: UITableView = UITableView() + result.accessibilityLabel = "Contact" result.dataSource = self result.delegate = self result.separatorStyle = .none diff --git a/Session/Conversations/Message Cells/Content Views/MediaPlaceholderView.swift b/Session/Conversations/Message Cells/Content Views/MediaPlaceholderView.swift index 09939731c..754ded084 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaPlaceholderView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaPlaceholderView.swift @@ -12,7 +12,7 @@ final class MediaPlaceholderView: UIView { init(cellViewModel: MessageViewModel, textColor: ThemeValue) { super.init(frame: CGRect.zero) - + self.accessibilityLabel = "Untrusted attachment message" setUpViewHierarchy(cellViewModel: cellViewModel, textColor: textColor) } diff --git a/Session/Conversations/Message Cells/InfoMessageCell.swift b/Session/Conversations/Message Cells/InfoMessageCell.swift index 2b8484f14..7c4a90248 100644 --- a/Session/Conversations/Message Cells/InfoMessageCell.swift +++ b/Session/Conversations/Message Cells/InfoMessageCell.swift @@ -70,6 +70,8 @@ final class InfoMessageCell: MessageCell { ) { guard cellViewModel.variant.isInfoMessage else { return } + self.accessibilityIdentifier = "Configuration message" + self.isAccessibilityElement = true self.viewModel = cellViewModel let icon: UIImage? = { diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 2c47807d8..45145bb22 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -144,6 +144,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { internal lazy var messageStatusImageView: UIImageView = { let result = UIImageView() + result.accessibilityLabel = "Message sent status tick" result.contentMode = .scaleAspectFit result.layer.cornerRadius = VisibleMessageCell.messageStatusImageViewSize / 2 result.layer.masksToBounds = true @@ -268,7 +269,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { lastSearchText: String? ) { self.viewModel = cellViewModel - + self.bubbleView.accessibilityIdentifier = "Message Body" + self.bubbleView.isAccessibilityElement = true + self.bubbleView.accessibilityLabel = cellViewModel.body // We want to add spacing between "clusters" of messages to indicate that time has // passed (even if there wasn't enough time to warrant showing a date header) let shouldAddTopInset: Bool = ( diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index fe23f06f8..bc410492b 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -120,7 +120,7 @@ class ThreadSettingsViewModel: SessionTableViewModel Void)? @@ -41,6 +44,9 @@ extension SessionCell { isEnabled: Bool = true, shouldHaveBackground: Bool = true, accessibilityIdentifier: String? = nil, + accessibilityLabel: String? = nil, + leftAccessoryAccessibilityLabel: String? = nil, + rightAccessoryAccessibilityLabel: String? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: ((UIView?) -> Void)? ) { @@ -55,6 +61,9 @@ extension SessionCell { self.isEnabled = isEnabled self.shouldHaveBackground = shouldHaveBackground self.accessibilityIdentifier = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel + self.leftAccessoryAccessibilityLabel = leftAccessoryAccessibilityLabel + self.rightAccessoryAccessibilityLabel = rightAccessoryAccessibilityLabel self.confirmationInfo = confirmationInfo self.onTap = onTap } @@ -71,6 +80,9 @@ extension SessionCell { isEnabled: Bool = true, shouldHaveBackground: Bool = true, accessibilityIdentifier: String? = nil, + accessibilityLabel: String? = nil, + leftAccessoryAccessibilityLabel: String? = nil, + rightAccessoryAccessibilityLabel: String? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: (() -> Void)? = nil ) { @@ -85,6 +97,9 @@ extension SessionCell { self.isEnabled = isEnabled self.shouldHaveBackground = shouldHaveBackground self.accessibilityIdentifier = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel + self.leftAccessoryAccessibilityLabel = leftAccessoryAccessibilityLabel + self.rightAccessoryAccessibilityLabel = rightAccessoryAccessibilityLabel self.confirmationInfo = confirmationInfo self.onTap = (onTap != nil ? { _ in onTap?() } : nil) } @@ -104,6 +119,9 @@ extension SessionCell { isEnabled.hash(into: &hasher) shouldHaveBackground.hash(into: &hasher) accessibilityIdentifier.hash(into: &hasher) + accessibilityLabel.hash(into: &hasher) + leftAccessoryAccessibilityLabel.hash(into: &hasher) + rightAccessoryAccessibilityLabel.hash(into: &hasher) confirmationInfo.hash(into: &hasher) } @@ -118,7 +136,10 @@ extension SessionCell { lhs.extraAction == rhs.extraAction && lhs.isEnabled == rhs.isEnabled && lhs.shouldHaveBackground == rhs.shouldHaveBackground && - lhs.accessibilityIdentifier == rhs.accessibilityIdentifier + lhs.accessibilityIdentifier == rhs.accessibilityIdentifier && + lhs.accessibilityLabel == rhs.accessibilityLabel && + lhs.leftAccessoryAccessibilityLabel == rhs.leftAccessoryAccessibilityLabel && + lhs.rightAccessoryAccessibilityLabel == rhs.rightAccessoryAccessibilityLabel ) } } diff --git a/Session/Shared/Types/SessionTableViewModel+NavItem.swift b/Session/Shared/Types/SessionTableViewModel+NavItem.swift index b1eb7714b..17efd8153 100644 --- a/Session/Shared/Types/SessionTableViewModel+NavItem.swift +++ b/Session/Shared/Types/SessionTableViewModel+NavItem.swift @@ -12,6 +12,7 @@ extension SessionTableViewModel { let style: UIBarButtonItem.Style let systemItem: UIBarButtonItem.SystemItem? let accessibilityIdentifier: String + let accessibilityLabel: String? let action: (() -> Void)? // MARK: - Initialization @@ -20,6 +21,7 @@ extension SessionTableViewModel { id: NavItemId, systemItem: UIBarButtonItem.SystemItem?, accessibilityIdentifier: String, + accessibilityLabel: String? = nil, action: (() -> Void)? = nil ) { self.id = id @@ -27,6 +29,7 @@ extension SessionTableViewModel { self.style = .plain self.systemItem = systemItem self.accessibilityIdentifier = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel self.action = action } @@ -35,6 +38,7 @@ extension SessionTableViewModel { image: UIImage?, style: UIBarButtonItem.Style, accessibilityIdentifier: String, + accessibilityLabel: String? = nil, action: (() -> Void)? = nil ) { self.id = id @@ -42,6 +46,7 @@ extension SessionTableViewModel { self.style = style self.systemItem = nil self.accessibilityIdentifier = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel self.action = action } @@ -54,7 +59,8 @@ extension SessionTableViewModel { style: style, target: nil, action: nil, - accessibilityIdentifier: accessibilityIdentifier + accessibilityIdentifier: accessibilityIdentifier, + accessibilityLabel: accessibilityLabel ) } @@ -62,7 +68,8 @@ extension SessionTableViewModel { barButtonSystemItem: systemItem, target: nil, action: nil, - accessibilityIdentifier: accessibilityIdentifier + accessibilityIdentifier: accessibilityIdentifier, + accessibilityLabel: accessibilityLabel ) } diff --git a/Session/Shared/UserSelectionVC.swift b/Session/Shared/UserSelectionVC.swift index 3bbc97ab4..0c920f101 100644 --- a/Session/Shared/UserSelectionVC.swift +++ b/Session/Shared/UserSelectionVC.swift @@ -48,7 +48,10 @@ final class UserSelectionVC: BaseVC, UITableViewDataSource, UITableViewDelegate setNavBarTitle(navBarTitle) - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDoneButtonTapped)) + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDoneButtonTapped)) + doneButton.accessibilityLabel = "Done" + navigationItem.rightBarButtonItem = doneButton + view.addSubview(tableView) tableView.pin(to: view) } @@ -69,7 +72,8 @@ final class UserSelectionVC: BaseVC, UITableViewDataSource, UITableViewDelegate title: profile.displayName(), rightAccessory: .radio(isSelected: { [weak self] in self?.selectedUsers.contains(profile.id) == true - }) + }), + accessibilityIdentifier: "Contact" ), style: .edgeToEdge, position: Position.with(indexPath.row, count: users.count) diff --git a/Session/Shared/Views/SessionAvatarCell.swift b/Session/Shared/Views/SessionAvatarCell.swift index 3ccea1689..c27d16ca7 100644 --- a/Session/Shared/Views/SessionAvatarCell.swift +++ b/Session/Shared/Views/SessionAvatarCell.swift @@ -52,6 +52,8 @@ class SessionAvatarCell: UITableViewCell { fileprivate let profilePictureView: ProfilePictureView = { let view: ProfilePictureView = ProfilePictureView() + view.accessibilityLabel = "Profile picture" + view.isAccessibilityElement = true view.translatesAutoresizingMaskIntoConstraints = false view.size = Values.largeProfilePictureSize @@ -61,7 +63,7 @@ class SessionAvatarCell: UITableViewCell { fileprivate let displayNameContainer: UIView = { let view: UIView = UIView() view.translatesAutoresizingMaskIntoConstraints = false - view.accessibilityLabel = "Edit name text field" + view.accessibilityLabel = "Username" view.isAccessibilityElement = true return view @@ -69,6 +71,7 @@ class SessionAvatarCell: UITableViewCell { private lazy var displayNameLabel: UILabel = { let label: UILabel = UILabel() + label.isAccessibilityElement = true label.translatesAutoresizingMaskIntoConstraints = false label.font = .ows_mediumFont(withSize: Values.veryLargeFontSize) label.themeTextColor = .textPrimary @@ -83,7 +86,9 @@ class SessionAvatarCell: UITableViewCell { let textField: TextField = TextField(placeholder: "Enter a name", usesDefaultHeight: false) textField.translatesAutoresizingMaskIntoConstraints = false textField.textAlignment = .center - textField.accessibilityLabel = "Edit name text field" + textField.accessibilityIdentifier = "Nickname" + textField.accessibilityLabel = "Nickname" + textField.isAccessibilityElement = true textField.alpha = 0 return textField @@ -98,6 +103,7 @@ class SessionAvatarCell: UITableViewCell { private let descriptionLabel: SRCopyableLabel = { let label: SRCopyableLabel = SRCopyableLabel() + label.accessibilityLabel = "Session ID" label.translatesAutoresizingMaskIntoConstraints = false label.themeTextColor = .textPrimary label.textAlignment = .center diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index c904af354..dffda5793 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -225,7 +225,8 @@ extension SessionCell { public func update( with accessory: Accessory?, tintColor: ThemeValue, - isEnabled: Bool + isEnabled: Bool, + accessibilityLabel: String? ) { guard let accessory: Accessory = accessory else { return } @@ -234,6 +235,7 @@ extension SessionCell { switch accessory { case .icon(let image, let iconSize, let customTint, let shouldFill): + imageView.accessibilityLabel = accessibilityLabel imageView.image = image imageView.themeTintColor = (customTint ?? tintColor) imageView.contentMode = (shouldFill ? .scaleAspectFill : .scaleAspectFit) @@ -256,6 +258,7 @@ extension SessionCell { case .iconAsync(let iconSize, let customTint, let shouldFill, let setter): setter(imageView) + imageView.accessibilityLabel = accessibilityLabel imageView.themeTintColor = (customTint ?? tintColor) imageView.contentMode = (shouldFill ? .scaleAspectFill : .scaleAspectFit) imageView.isHidden = false @@ -276,6 +279,7 @@ extension SessionCell { imageViewConstraints.forEach { $0.isActive = true } case .toggle(let dataSource): + toggleSwitch.accessibilityLabel = accessibilityLabel toggleSwitch.isHidden = false toggleSwitch.isEnabled = isEnabled toggleSwitchConstraints.forEach { $0.isActive = true } @@ -287,6 +291,7 @@ extension SessionCell { } case .dropDown(let dataSource): + dropDownLabel.accessibilityLabel = accessibilityLabel dropDownLabel.text = dataSource.currentStringValue dropDownStackView.isHidden = false dropDownStackViewConstraints.forEach { $0.isActive = true } @@ -302,6 +307,7 @@ extension SessionCell { ) radioBorderView.layer.cornerRadius = (size.borderSize / 2) + radioView.accessibilityLabel = accessibilityLabel radioView.alpha = (wasOldSelection ? 0.3 : 1) radioView.isHidden = (!isSelected && !storedSelection) radioView.themeBackgroundColor = (isSelected || wasOldSelection ? @@ -322,12 +328,14 @@ extension SessionCell { radioBorderViewConstraints.forEach { $0.isActive = true } case .highlightingBackgroundLabel(let title): + highlightingBackgroundLabel.accessibilityLabel = accessibilityLabel highlightingBackgroundLabel.text = title highlightingBackgroundLabel.themeTextColor = tintColor highlightingBackgroundLabel.isHidden = false highlightingBackgroundLabelConstraints.forEach { $0.isActive = true } case .profile(let profileId, let profile): + profilePictureView.accessibilityLabel = accessibilityLabel profilePictureView.update( publicKey: profileId, profile: profile, @@ -338,6 +346,7 @@ extension SessionCell { case .customView(let viewGenerator): let generatedView: UIView = viewGenerator() + generatedView.accessibilityLabel = accessibilityLabel addSubview(generatedView) generatedView.pin(.top, to: .top, of: self) diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 0cc4c6a4a..e6fa6c033 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -308,6 +308,8 @@ public class SessionCell: UITableViewCell { self.subtitleExtraView = info.subtitleExtraViewGenerator?() self.onExtraActionTap = info.extraAction?.onTap self.accessibilityIdentifier = info.accessibilityIdentifier + self.accessibilityLabel = info.accessibilityLabel + self.isAccessibilityElement = true let leftFitToEdge: Bool = (info.leftAccessory?.shouldFitToEdge == true) let rightFitToEdge: Bool = (!leftFitToEdge && info.rightAccessory?.shouldFitToEdge == true) @@ -315,12 +317,14 @@ public class SessionCell: UITableViewCell { leftAccessoryView.update( with: info.leftAccessory, tintColor: info.tintColor, - isEnabled: info.isEnabled + isEnabled: info.isEnabled, + accessibilityLabel: info.leftAccessoryAccessibilityLabel ) rightAccessoryView.update( with: info.rightAccessory, tintColor: info.tintColor, - isEnabled: info.isEnabled + isEnabled: info.isEnabled, + accessibilityLabel: info.rightAccessoryAccessibilityLabel ) rightAccessoryFillConstraint.isActive = rightFitToEdge contentStackView.layoutMargins = UIEdgeInsets( diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index eb8c4264c..0ecc1d0a6 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -228,7 +228,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ParentType.NavItem( id: .cancel, systemItem: .cancel, - accessibilityIdentifier: "Cancel button" + accessibilityIdentifier: "Cancel" ) ])) expect(viewModel.rightNavItems.firstValue()) @@ -236,7 +236,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ParentType.NavItem( id: .done, systemItem: .done, - accessibilityIdentifier: "Done button" + accessibilityIdentifier: "Done" ) ])) } @@ -369,7 +369,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ParentType.NavItem( id: .done, systemItem: .done, - accessibilityIdentifier: "Done button" + accessibilityIdentifier: "Done" ) ])) } diff --git a/SessionUIKit/Components/ConfirmationModal.swift b/SessionUIKit/Components/ConfirmationModal.swift index 5c75e5851..d98dd9f5f 100644 --- a/SessionUIKit/Components/ConfirmationModal.swift +++ b/SessionUIKit/Components/ConfirmationModal.swift @@ -21,10 +21,14 @@ public class ConfirmationModal: Modal { let title: String let explanation: String? let attributedExplanation: NSAttributedString? + let accessibilityLabel: String? + let accessibilityId: String? public let stateToShow: State let confirmTitle: String? + let confirmAccessibilityLabel: String? let confirmStyle: ThemeValue let cancelTitle: String + let cancelAccessibilityLabel: String? let cancelStyle: ThemeValue let dismissOnConfirm: Bool let onConfirm: ((UIViewController) -> ())? @@ -36,10 +40,14 @@ public class ConfirmationModal: Modal { title: String, explanation: String? = nil, attributedExplanation: NSAttributedString? = nil, + accessibilityLabel: String? = nil, + accessibilityId: String? = nil, stateToShow: State = .always, confirmTitle: String? = nil, + confirmAccessibilityLabel: String? = nil, confirmStyle: ThemeValue = .alert_text, cancelTitle: String = "TXT_CANCEL_TITLE".localized(), + cancelAccessibilityLabel: String? = nil, cancelStyle: ThemeValue = .danger, dismissOnConfirm: Bool = true, onConfirm: ((UIViewController) -> ())? = nil, @@ -48,10 +56,14 @@ public class ConfirmationModal: Modal { self.title = title self.explanation = explanation self.attributedExplanation = attributedExplanation + self.accessibilityLabel = accessibilityLabel + self.accessibilityId = accessibilityId self.stateToShow = stateToShow self.confirmTitle = confirmTitle + self.confirmAccessibilityLabel = confirmAccessibilityLabel self.confirmStyle = confirmStyle self.cancelTitle = cancelTitle + self.cancelAccessibilityLabel = cancelAccessibilityLabel self.cancelStyle = cancelStyle self.dismissOnConfirm = dismissOnConfirm self.onConfirm = onConfirm @@ -67,10 +79,14 @@ public class ConfirmationModal: Modal { return Info( title: self.title, explanation: self.explanation, + attributedExplanation: self.attributedExplanation, + accessibilityLabel: self.accessibilityLabel, stateToShow: self.stateToShow, confirmTitle: self.confirmTitle, + confirmAccessibilityLabel: self.confirmAccessibilityLabel, confirmStyle: self.confirmStyle, cancelTitle: self.cancelTitle, + cancelAccessibilityLabel: self.cancelAccessibilityLabel, cancelStyle: self.cancelStyle, dismissOnConfirm: self.dismissOnConfirm, onConfirm: (onConfirm ?? self.onConfirm), @@ -85,10 +101,13 @@ public class ConfirmationModal: Modal { lhs.title == rhs.title && lhs.explanation == rhs.explanation && lhs.attributedExplanation == rhs.attributedExplanation && + lhs.accessibilityLabel == rhs.accessibilityLabel && lhs.stateToShow == rhs.stateToShow && lhs.confirmTitle == rhs.confirmTitle && + lhs.confirmAccessibilityLabel == rhs.confirmAccessibilityLabel && lhs.confirmStyle == rhs.confirmStyle && lhs.cancelTitle == rhs.cancelTitle && + lhs.cancelAccessibilityLabel == rhs.cancelAccessibilityLabel && lhs.cancelStyle == rhs.cancelStyle && lhs.dismissOnConfirm == rhs.dismissOnConfirm ) @@ -98,10 +117,13 @@ public class ConfirmationModal: Modal { title.hash(into: &hasher) explanation.hash(into: &hasher) attributedExplanation.hash(into: &hasher) + accessibilityLabel.hash(into: &hasher) stateToShow.hash(into: &hasher) confirmTitle.hash(into: &hasher) + confirmAccessibilityLabel.hash(into: &hasher) confirmStyle.hash(into: &hasher) cancelTitle.hash(into: &hasher) + cancelAccessibilityLabel.hash(into: &hasher) cancelStyle.hash(into: &hasher) dismissOnConfirm.hash(into: &hasher) } @@ -207,11 +229,20 @@ public class ConfirmationModal: Modal { info.explanation == nil && info.attributedExplanation == nil ) + confirmButton.accessibilityLabel = info.confirmAccessibilityLabel + confirmButton.accessibilityIdentifier = info.confirmAccessibilityLabel + confirmButton.isAccessibilityElement = true confirmButton.setTitle(info.confirmTitle, for: .normal) confirmButton.setThemeTitleColor(info.confirmStyle, for: .normal) confirmButton.isHidden = (info.confirmTitle == nil) + cancelButton.accessibilityLabel = info.cancelAccessibilityLabel + cancelButton.accessibilityIdentifier = info.cancelAccessibilityLabel + cancelButton.isAccessibilityElement = true cancelButton.setTitle(info.cancelTitle, for: .normal) cancelButton.setThemeTitleColor(info.cancelStyle, for: .normal) + + self.accessibilityLabel = info.accessibilityLabel + self.contentView.accessibilityIdentifier = info.accessibilityId } required init?(coder: NSCoder) { diff --git a/SignalUtilitiesKit/Utilities/UIView+OWS.swift b/SignalUtilitiesKit/Utilities/UIView+OWS.swift index a2a257324..b3a9978e4 100644 --- a/SignalUtilitiesKit/Utilities/UIView+OWS.swift +++ b/SignalUtilitiesKit/Utilities/UIView+OWS.swift @@ -291,43 +291,43 @@ public extension UIBezierPath { @objc public extension UIBarButtonItem { - convenience init(image: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) { + convenience init(image: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String, accessibilityLabel: String? = nil) { self.init(image: image, style: style, target: target, action: action) self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel == nil ? accessibilityIdentifier : accessibilityLabel self.isAccessibilityElement = true } - convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) { + convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String, accessibilityLabel: String? = nil) { self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: target, action: action) self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel == nil ? accessibilityIdentifier : accessibilityLabel self.isAccessibilityElement = true } - convenience init(title: String?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String) { + convenience init(title: String?, style: UIBarButtonItem.Style, target: Any?, action: Selector?, accessibilityIdentifier: String, accessibilityLabel: String? = nil) { self.init(title: title, style: style, target: target, action: action) self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel == nil ? accessibilityIdentifier : accessibilityLabel self.isAccessibilityElement = true } - convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, target: Any?, action: Selector?, accessibilityIdentifier: String) { + convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, target: Any?, action: Selector?, accessibilityIdentifier: String, accessibilityLabel: String? = nil) { self.init(barButtonSystemItem: systemItem, target: target, action: action) self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel == nil ? accessibilityIdentifier : accessibilityLabel self.isAccessibilityElement = true } - convenience init(customView: UIView, accessibilityIdentifier: String) { + convenience init(customView: UIView, accessibilityIdentifier: String, accessibilityLabel: String? = nil) { self.init(customView: customView) self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityIdentifier + self.accessibilityLabel = accessibilityLabel == nil ? accessibilityIdentifier : accessibilityLabel self.isAccessibilityElement = true } }