diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index f1b90008b8..22a697a420 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2119,14 +2119,14 @@ Tap the + to start adding people."; "spaces_creation_cancel_message" = "Your progress will be lost."; "spaces_creation_new_rooms_title" = "What are some discussions you’ll have?"; -"spaces_creation_new_rooms_message" = "We’ll create a room for each one."; +"spaces_creation_new_rooms_message" = "We'll create a room for each one. You can create up to three rooms for this step."; "spaces_creation_new_rooms_room_name_title" = "Room name"; "spaces_creation_new_rooms_general" = "General"; "spaces_creation_new_rooms_random" = "Random"; "spaces_creation_new_rooms_support" = "Support"; "spaces_creation_email_invites_title" = "Invite your team"; -"spaces_creation_email_invites_message" = "You can invite them later too."; +"spaces_creation_email_invites_message" = "You can invite your team members by email or by username."; "spaces_creation_email_invites_email_title" = "Email"; "spaces_creation_sharing_type_title" = "Who are you working with?"; @@ -2158,6 +2158,15 @@ Tap the + to start adding people."; "spaces_add_room_missing_permission_message" = "You do not have permissions to add rooms to this space."; +"spaces_creation_settings_next_hint_error" = "error found"; +"spaces_creation_settings_next_hint" = "applies settings and takes you to the next step"; +"spaces_creation_settings_accessibility_avatar_label" = "space avatar"; +"spaces_creation_settings_accessibility_avatar_hint" = "takes you to the image picker for this space"; + +"spaces_creation_rooms_next_hint" = "Next and creates"; +"spaces_creation_email_invites_next_hint_invalid_emails" = "invalid emails %@"; +"spaces_creation_invite_by_username_accessibility_hint" = "takes you to a list of team members you can invite."; + // Mark: Leave space "leave_space_action" = "Leave space"; @@ -2287,6 +2296,14 @@ Tap the + to start adding people."; "space_detail_nav_title" = "Space detail"; "space_invite_nav_title" = "Space invite"; +"space_selector_list_row_accessibility_unread_messages" = " %@ unread messages"; +"space_selector_list_row_accessibility_selected_space" = "currently selected space"; +"space_selector_list_row_accessibility_switches_to" = "switches to %@"; +"space_selector_list_row_accessibility_invite_hint" = "takes you to space summary and options"; +"space_selector_list_row_accessibility_invite" = "takes you to space summary and options"; +"space_selector_list_row_accessibility_disclosure_label" = "Content of %@"; +"space_selector_list_row_accessibility_disclosure_hint" = "takes you to sub-spaces of %@"; + // Mark: - Polls "poll_edit_form_create_poll" = "Create poll"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 2008451ea5..6e1886eafe 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8119,6 +8119,34 @@ public class VectorL10n: NSObject { public static var spaceSelectorEmptyViewTitle: String { return VectorL10n.tr("Vector", "space_selector_empty_view_title") } + /// takes you to sub-spaces of %@ + public static func spaceSelectorListRowAccessibilityDisclosureHint(_ p1: String) -> String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_disclosure_hint", p1) + } + /// Content of %@ + public static func spaceSelectorListRowAccessibilityDisclosureLabel(_ p1: String) -> String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_disclosure_label", p1) + } + /// takes you to space summary and options + public static var spaceSelectorListRowAccessibilityInvite: String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_invite") + } + /// takes you to space summary and options + public static var spaceSelectorListRowAccessibilityInviteHint: String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_invite_hint") + } + /// currently selected space + public static var spaceSelectorListRowAccessibilitySelectedSpace: String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_selected_space") + } + /// switches to %@ + public static func spaceSelectorListRowAccessibilitySwitchesTo(_ p1: String) -> String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_switches_to", p1) + } + /// %@ unread messages + public static func spaceSelectorListRowAccessibilityUnreadMessages(_ p1: String) -> String { + return VectorL10n.tr("Vector", "space_selector_list_row_accessibility_unread_messages", p1) + } /// My spaces public static var spaceSelectorTitle: String { return VectorL10n.tr("Vector", "space_selector_title") @@ -8219,10 +8247,14 @@ public class VectorL10n: NSObject { public static var spacesCreationEmailInvitesEmailTitle: String { return VectorL10n.tr("Vector", "spaces_creation_email_invites_email_title") } - /// You can invite them later too. + /// You can invite your team members by email or by username. public static var spacesCreationEmailInvitesMessage: String { return VectorL10n.tr("Vector", "spaces_creation_email_invites_message") } + /// invalid emails %@ + public static func spacesCreationEmailInvitesNextHintInvalidEmails(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_email_invites_next_hint_invalid_emails", p1) + } /// Invite your team public static var spacesCreationEmailInvitesTitle: String { return VectorL10n.tr("Vector", "spaces_creation_email_invites_title") @@ -8263,6 +8295,10 @@ public class VectorL10n: NSObject { public static var spacesCreationInviteByUsername: String { return VectorL10n.tr("Vector", "spaces_creation_invite_by_username") } + /// takes you to a list of team members you can invite. + public static var spacesCreationInviteByUsernameAccessibilityHine: String { + return VectorL10n.tr("Vector", "spaces_creation_invite_by_username_accessibility_hine") + } /// You can invite them later too. public static var spacesCreationInviteByUsernameMessage: String { return VectorL10n.tr("Vector", "spaces_creation_invite_by_username_message") @@ -8275,7 +8311,7 @@ public class VectorL10n: NSObject { public static var spacesCreationNewRoomsGeneral: String { return VectorL10n.tr("Vector", "spaces_creation_new_rooms_general") } - /// We’ll create a room for each one. + /// We'll create a room for each one. You can create up to three rooms for this step. public static var spacesCreationNewRoomsMessage: String { return VectorL10n.tr("Vector", "spaces_creation_new_rooms_message") } @@ -8327,10 +8363,30 @@ public class VectorL10n: NSObject { public static var spacesCreationPublicSpaceTitle: String { return VectorL10n.tr("Vector", "spaces_creation_public_space_title") } + /// Next and creates + public static var spacesCreationRoomsNextHint: String { + return VectorL10n.tr("Vector", "spaces_creation_rooms_next_hint") + } + /// takes you to the image picker for this space + public static var spacesCreationSettingsAccessibilityAvatarHint: String { + return VectorL10n.tr("Vector", "spaces_creation_settings_accessibility_avatar_hint") + } + /// space avatar + public static var spacesCreationSettingsAccessibilityAvatarLabel: String { + return VectorL10n.tr("Vector", "spaces_creation_settings_accessibility_avatar_label") + } /// Add some details to help it stand out. You can change these at any point. public static var spacesCreationSettingsMessage: String { return VectorL10n.tr("Vector", "spaces_creation_settings_message") } + /// applies settings and takes you to the next step + public static var spacesCreationSettingsNextHint: String { + return VectorL10n.tr("Vector", "spaces_creation_settings_next_hint") + } + /// error found + public static var spacesCreationSettingsNextHintError: String { + return VectorL10n.tr("Vector", "spaces_creation_settings_next_hint_error") + } /// A private space to organise your rooms public static var spacesCreationSharingTypeJustMeDetail: String { return VectorL10n.tr("Vector", "spaces_creation_sharing_type_just_me_detail") diff --git a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift index 4557f829ce..837b9afca6 100644 --- a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift +++ b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift @@ -133,6 +133,7 @@ class SpaceDetailViewController: UIViewController { self.view.backgroundColor = theme.colors.background self.inviterAvatarView.update(theme: theme) + self.inviterAvatarView.accessibilityElementsHidden = true self.inviterTitleLabel.textColor = theme.colors.secondaryContent self.inviterTitleLabel.font = theme.fonts.calloutSB self.inviterIdLabel.textColor = theme.colors.secondaryContent @@ -144,6 +145,7 @@ class SpaceDetailViewController: UIViewController { self.closeButton.backgroundColor = theme.roomInputTextBorder self.closeButton.tintColor = theme.noticeSecondaryColor self.avatarView.update(theme: theme) + self.avatarView.accessibilityElementsHidden = true self.spaceTypeIconView.tintColor = theme.colors.tertiaryContent self.spaceTypeLabel.font = theme.fonts.callout diff --git a/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift b/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift index 209c61aa10..8524db6e50 100644 --- a/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift @@ -52,6 +52,8 @@ struct ClearViewModifier: ViewModifier { .padding(.top, alignment == .top ? 8 : 0) .padding(.bottom, alignment == .bottom ? 8 : 0) .padding(.trailing, 12) + .accessibilityLabel("clear") + .accessibilityHint("Clears \(text)") } } } diff --git a/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift b/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift index 0e0f38586c..ea4c6e53df 100644 --- a/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift @@ -35,6 +35,7 @@ struct PasswordButtonModifier: ViewModifier { public func body(content: Content) -> some View { HStack(alignment: .center) { content + .accessibilitySortPriority(1) if !text.isEmpty { Button { isSecureTextVisible.toggle() } label: { @@ -47,6 +48,9 @@ struct PasswordButtonModifier: ViewModifier { .padding(.top, alignment == .top ? 8 : 0) .padding(.bottom, alignment == .bottom ? 8 : 0) .padding(.trailing, 12) + .accessibilitySortPriority(0) + .accessibilityLabel("Text visibility toggler") + .accessibilityHint(isSecureTextVisible ? "hide text" : "show text") } } } diff --git a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift index 240b9ebbaa..71b1477e6d 100644 --- a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift +++ b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift @@ -45,6 +45,7 @@ struct RoundedBorderTextEditor: View { .font(theme.fonts.subheadline) .multilineTextAlignment(.leading) .padding(EdgeInsets(top: 0, leading: 0, bottom: 8, trailing: 0)) + .accessibilityHidden(true) } ZStack(alignment: .topLeading) { if text.isEmpty { @@ -53,9 +54,13 @@ struct RoundedBorderTextEditor: View { .font(theme.fonts.callout) .foregroundColor(theme.colors.tertiaryContent) .allowsHitTesting(false) + .accessibilityHidden(true) } if isEnabled { - ThemableTextEditor(text: $text, onEditingChanged: { edit in + ThemableTextEditor(text: $text, + defaultAccessibilityLabel: title ?? placeHolder, + accessibilityHint: self.error, + onEditingChanged: { edit in self.editing = edit onEditingChanged?(edit) }) @@ -67,7 +72,10 @@ struct RoundedBorderTextEditor: View { onTextChanged?(newText) }) } else { - ThemableTextEditor(text: $text, onEditingChanged: { edit in + ThemableTextEditor(text: $text, + defaultAccessibilityLabel: title ?? placeHolder, + accessibilityHint: self.error, + onEditingChanged: { edit in self.editing = edit onEditingChanged?(edit) }) diff --git a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift index 7acf765cc6..bee21ebd75 100644 --- a/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift +++ b/RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift @@ -50,6 +50,7 @@ struct RoundedBorderTextField: View { .font(theme.fonts.subheadline) .multilineTextAlignment(.leading) .padding(.bottom, 8) + .accessibilityHidden(true) } ZStack(alignment: .leading) { @@ -63,6 +64,8 @@ struct RoundedBorderTextField: View { ThemableTextField(placeholder: "", text: $text, + defaultAccessibilityLabel: title ?? placeHolder, + accessibilityHint: self.footerText, configuration: configuration, isSecureTextVisible: $isSecureTextVisible) { isEditing in self.isEditing = isEditing @@ -78,7 +81,6 @@ struct RoundedBorderTextField: View { .frame(height: 30) .allowsHitTesting(isEnabled) .opacity(isEnabled ? 1 : 0.5) - .accessibilityLabel(text.isEmpty ? placeHolder : "") } .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: text.isEmpty ? 8 : 0)) .background(RoundedRectangle(cornerRadius: 8).fill(theme.colors.background)) @@ -91,6 +93,7 @@ struct RoundedBorderTextField: View { .multilineTextAlignment(.leading) .padding(.top, 8) .transition(.opacity) + .accessibilityHidden(true) } } .animation(.easeOut(duration: 0.2)) diff --git a/RiotSwiftUI/Modules/Common/Util/SearchBar.swift b/RiotSwiftUI/Modules/Common/Util/SearchBar.swift index fb05eff952..51d92ae454 100644 --- a/RiotSwiftUI/Modules/Common/Util/SearchBar.swift +++ b/RiotSwiftUI/Modules/Common/Util/SearchBar.swift @@ -47,6 +47,7 @@ struct SearchBar: View { .renderingMode(.template) .foregroundColor(theme.colors.quarterlyContent) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .accessibilityHidden(true) if isEditing, !text.isEmpty { Button(action: { diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift b/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift index 3f7bf833ef..2d357d0b6f 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift @@ -39,6 +39,7 @@ struct ThemableNavigationBar: View { Image(uiImage: Asset.Images.spacesModalBack.image) .renderingMode(.template) .foregroundColor(theme.colors.secondaryContent) + .accessibilityLabel(VectorL10n.back) } .isHidden(!showBackButton) Spacer() @@ -51,6 +52,7 @@ struct ThemableNavigationBar: View { Image(uiImage: Asset.Images.spacesModalClose.image) .renderingMode(.template) .foregroundColor(theme.colors.secondaryContent) + .accessibilityLabel(VectorL10n.close) } } .padding(.horizontal) diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift b/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift index bdc5298222..f8194fb3c8 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift @@ -21,6 +21,8 @@ struct ThemableTextEditor: UIViewRepresentable { @Binding var text: String @State var configuration = UIKitTextInputConfiguration() + var defaultAccessibilityLabel: String? + var accessibilityHint: String? var onEditingChanged: ((_ edit: Bool) -> Void)? // MARK: Private @@ -33,10 +35,14 @@ struct ThemableTextEditor: UIViewRepresentable { // MARK: Setup init(text: Binding, + defaultAccessibilityLabel: String?, + accessibilityHint: String?, configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration(), onEditingChanged: ((_ edit: Bool) -> Void)? = nil) { _text = text _configuration = State(initialValue: configuration) + self.defaultAccessibilityLabel = defaultAccessibilityLabel + self.accessibilityHint = accessibilityHint self.onEditingChanged = onEditingChanged ResponderManager.register(view: textView) @@ -51,6 +57,9 @@ struct ThemableTextEditor: UIViewRepresentable { if internalParams.isFirstResponder { textView.becomeFirstResponder() } + + textView.accessibilityLabel = defaultAccessibilityLabel + textView.accessibilityHint = accessibilityHint return textView } @@ -64,6 +73,9 @@ struct ThemableTextEditor: UIViewRepresentable { if uiView.text != text { uiView.text = text } + + uiView.accessibilityLabel = defaultAccessibilityLabel + uiView.accessibilityHint = accessibilityHint uiView.keyboardType = configuration.keyboardType uiView.returnKeyType = configuration.returnKeyType diff --git a/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift b/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift index a179d36ab9..e7cdd9d131 100644 --- a/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift +++ b/RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift @@ -31,6 +31,8 @@ struct ThemableTextField: UIViewRepresentable { @Binding var text: String @State var configuration = UIKitTextInputConfiguration() @Binding var isSecureTextVisible: Bool + var defaultAccessibilityLabel: String? + var accessibilityHint: String? var onEditingChanged: ((_ edit: Bool) -> Void)? var onCommit: (() -> Void)? @@ -45,6 +47,8 @@ struct ThemableTextField: UIViewRepresentable { init(placeholder: String? = nil, text: Binding, + defaultAccessibilityLabel: String?, + accessibilityHint: String?, configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration(), isSecureTextVisible: Binding = .constant(false), onEditingChanged: ((_ edit: Bool) -> Void)? = nil, @@ -53,6 +57,8 @@ struct ThemableTextField: UIViewRepresentable { _placeholder = State(initialValue: placeholder) _configuration = State(initialValue: configuration) _isSecureTextVisible = isSecureTextVisible + self.defaultAccessibilityLabel = defaultAccessibilityLabel + self.accessibilityHint = accessibilityHint self.onEditingChanged = onEditingChanged self.onCommit = onCommit @@ -67,6 +73,8 @@ struct ThemableTextField: UIViewRepresentable { textField.setContentHuggingPriority(.defaultLow, for: .horizontal) textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) textField.text = text + textField.accessibilityLabel = defaultAccessibilityLabel + textField.accessibilityHint = accessibilityHint textField.addTarget(context.coordinator, action: #selector(Coordinator.textFieldEditingChanged(sender:)), for: .editingChanged) @@ -88,6 +96,9 @@ struct ThemableTextField: UIViewRepresentable { } uiView.placeholder = placeholder + uiView.accessibilityLabel = defaultAccessibilityLabel + uiView.accessibilityHint = accessibilityHint + uiView.keyboardType = configuration.keyboardType uiView.returnKeyType = configuration.returnKeyType uiView.isSecureTextEntry = configuration.isSecureTextEntry ? !isSecureTextVisible : false diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift index 467c69eb98..65b2c341a7 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift @@ -62,6 +62,14 @@ final class MatrixItemChooserCoordinator: Coordinator, Presentable { // Must be used only internally var childCoordinators: [Coordinator] = [] var completion: ((MatrixItemChooserViewModelResult) -> Void)? + var isNavigationBarHidden: Bool { + get { + return matrixItemChooserHostingController.isNavigationBarHidden + } + set { + matrixItemChooserHostingController.isNavigationBarHidden = newValue + } + } // MARK: - Setup diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift index 6b72367886..38b87e6322 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift @@ -72,11 +72,11 @@ struct MatrixItemChooser: View { type: item.type, displayName: item.displayName, detailText: item.detailText, - isSelected: viewModel.viewState.selectedItemIds.contains(item.id) + isSelected: viewModel.viewState.selectedItemIds.contains(item.id), + tapAction: { + viewModel.send(viewAction: .itemTapped(item.id)) + } ) - .onTapGesture { - viewModel.send(viewAction: .itemTapped(item.id)) - } } } } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift index 1d67f2a72e..5974e900da 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift @@ -30,6 +30,11 @@ struct MatrixItemChooserListRow: View { let displayName: String? let detailText: String? let isSelected: Bool + let tapAction: (() -> Void)? + + private var accessibilityLabel: String { + return "\(displayName ?? "")\n\(detailText ?? "")\n\(isSelected ? "item selected" : "")" + } @ViewBuilder var body: some View { @@ -62,6 +67,16 @@ struct MatrixItemChooserListRow: View { .padding(.horizontal) .padding(.vertical, 12) .frame(maxWidth: .infinity) + .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.isButton) + .accessibilityAction { + tapAction?() + } + .accessibilityLabel(accessibilityLabel) + .accessibilityHint("\(isSelected ? "unselects the item" : "selects the item")") + .onTapGesture { + tapAction?() + } } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift index ad7e56d2a1..08218445e1 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift @@ -219,6 +219,7 @@ final class SpaceCreationCoordinator: Coordinator { itemsProcessor: SpaceCreationInviteUsersItemsProcessor(creationParams: parameters.creationParameters) ) let coordinator = MatrixItemChooserCoordinator(parameters: parameters) + coordinator.isNavigationBarHidden = true coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { @@ -243,6 +244,7 @@ final class SpaceCreationCoordinator: Coordinator { itemsProcessor: SpaceCreationAddRoomsItemsProcessor(creationParams: parameters.creationParameters) ) let coordinator = MatrixItemChooserCoordinator(parameters: parameters) + coordinator.isNavigationBarHidden = true coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift index ddaca92f17..be0ebf4171 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift @@ -27,6 +27,29 @@ struct SpaceCreationEmailInvites: View { @Environment(\.theme) private var theme: ThemeSwiftUI + private var nextAccessibilityHint: String { + var invalidEmails = "" + var invites = "" + for (index, invite) in viewModel.emailInvites.enumerated() { + if !viewModel.viewState.emailAddressesValid[index] { + invalidEmails = "\n" + invite + } + if !invite.isEmpty { + invites = "\n" + invite + } + } + + guard invalidEmails.isEmpty else { + return VectorL10n.spacesCreationEmailInvitesNextHintInvalidEmails(invalidEmails) + } + + if invites.isEmpty { + return "" + } + + return "\(VectorL10n.peopleInvitesSection) \(invites)" + } + // MARK: - Public @ViewBuilder @@ -85,7 +108,7 @@ struct SpaceCreationEmailInvites: View { VStack { VStack(spacing: 20) { ForEach(viewModel.emailInvites.indices, id: \.self) { index in - RoundedBorderTextField(title: VectorL10n.spacesCreationEmailInvitesEmailTitle, placeHolder: VectorL10n.spacesCreationEmailInvitesEmailTitle, text: $viewModel.emailInvites[index], footerText: viewModel.viewState.emailAddressesValid[index] ? nil : VectorL10n.authInvalidEmail, isError: !viewModel.viewState.emailAddressesValid[index], configuration: UIKitTextInputConfiguration(keyboardType: .emailAddress, returnKeyType: index < viewModel.emailInvites.endIndex - 1 ? .next : .done, autocapitalizationType: .none, autocorrectionType: .no)) + RoundedBorderTextField(title: "\(VectorL10n.spacesCreationEmailInvitesEmailTitle) \(index + 1)", placeHolder: VectorL10n.spacesCreationEmailInvitesEmailTitle, text: $viewModel.emailInvites[index], footerText: viewModel.viewState.emailAddressesValid[index] ? nil : VectorL10n.authInvalidEmail, isError: !viewModel.viewState.emailAddressesValid[index], configuration: UIKitTextInputConfiguration(keyboardType: .emailAddress, returnKeyType: index < viewModel.emailInvites.endIndex - 1 ? .next : .done, autocapitalizationType: .none, autocorrectionType: .no)) .accessibility(identifier: "emailTextField") } } @@ -95,10 +118,12 @@ struct SpaceCreationEmailInvites: View { .font(theme.fonts.caption1) .foregroundColor(theme.colors.secondaryContent) .padding(.bottom) + .accessibilityHidden(true) OptionButton(icon: Asset.Images.spacesInviteUsers.image, title: VectorL10n.spacesCreationInviteByUsername, detailMessage: nil) { viewModel.send(viewAction: .inviteByUsername) } .padding(.bottom) + .accessibilityHint(VectorL10n.spacesCreationInviteByUsernameAccessibilityHint) } } @@ -107,6 +132,7 @@ struct SpaceCreationEmailInvites: View { ThemableButton(icon: nil, title: VectorL10n.next) { viewModel.send(viewAction: .done) } + .accessibilityLabel("\(VectorL10n.next)\n\(nextAccessibilityHint)") } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift index f1d6f54531..452236981c 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift @@ -24,7 +24,15 @@ struct SpaceCreationMatrixItemChooser: View { // MARK: Private @Environment(\.theme) private var theme: ThemeSwiftUI + + private var hasSelectedItems: Bool { + return !viewModel.viewState.selectedItemIds.isEmpty + } + private var nextButtonHint: String { + return hasSelectedItems ? "Continues and processes all \(viewModel.viewState.selectedItemIds.count) items" : "Continues without processing any item" + } + // MARK: Public @ViewBuilder @@ -53,11 +61,12 @@ struct SpaceCreationMatrixItemChooser: View { @ViewBuilder private var footerView: some View { - ThemableButton(icon: nil, title: viewModel.viewState.selectedItemIds.isEmpty ? VectorL10n.skip : VectorL10n.next) { + ThemableButton(icon: nil, title: hasSelectedItems ? VectorL10n.next : VectorL10n.skip) { viewModel.send(viewAction: .done) } .accessibility(identifier: "doneButton") .padding(.horizontal, 24) .padding(.bottom) + .accessibilityHint(nextButtonHint) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift index 38f99a9359..cc2f8ac014 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift @@ -25,6 +25,15 @@ struct SpaceCreationPostProcess: View { @Environment(\.theme) private var theme: ThemeSwiftUI + private var tasksAccessibilityValue: String { + return viewModel.viewState.tasks.reduce("") { result, task in + guard task.state == .started else { + return result + } + return result + "\n\(task.title)" + } + } + // MARK: Public @ObservedObject var viewModel: SpaceCreationPostProcessViewModel.Context @@ -32,9 +41,15 @@ struct SpaceCreationPostProcess: View { var body: some View { VStack { Spacer() - headerView - Spacer() - tasksList + VStack { + headerView + Spacer() + tasksList + } + .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.updatesFrequently) + .accessibilityLabel(VectorL10n.spacesCreationPostProcessCreatingSpace) + .accessibilityValue(tasksAccessibilityValue) Spacer() buttonsPanel } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift index fbb86d7f42..c93c4abc7a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift @@ -29,6 +29,19 @@ struct SpaceCreationRooms: View { @ObservedObject var viewModel: SpaceCreationRoomsViewModel.Context + private var nextAccessibilityHint: String { + var roomsCreated = "" + for room in viewModel.rooms { + roomsCreated = "\n" + room.name + } + + if roomsCreated.isEmpty { + return "" + } + + return "\(VectorL10n.spacesCreationRoomsNextHint) \(roomsCreated)" + } + var body: some View { VStack { ThemableNavigationBar(title: nil, showBackButton: true) { @@ -61,7 +74,10 @@ struct SpaceCreationRooms: View { .foregroundColor(theme.colors.secondaryContent) Spacer() ForEach(viewModel.rooms.indices, id: \.self) { index in - RoundedBorderTextField(title: VectorL10n.spacesCreationNewRoomsRoomNameTitle, placeHolder: viewModel.rooms[index].defaultName, text: $viewModel.rooms[index].name, configuration: UIKitTextInputConfiguration(returnKeyType: index < viewModel.rooms.endIndex - 1 ? .next : .done)) + RoundedBorderTextField(title: "\(VectorL10n.spacesCreationNewRoomsRoomNameTitle) \(index + 1)", + placeHolder: viewModel.rooms[index].defaultName, + text: $viewModel.rooms[index].name, + configuration: UIKitTextInputConfiguration(returnKeyType: index < viewModel.rooms.endIndex - 1 ? .next : .done)) .accessibility(identifier: "roomTextField") } } @@ -75,6 +91,7 @@ struct SpaceCreationRooms: View { ResponderManager.resignFirstResponder() viewModel.send(viewAction: .done) } + .accessibilityHint(nextAccessibilityHint) } .padding(EdgeInsets(top: 0, leading: 16, bottom: 24, trailing: 16)) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift index 9135bf8cc7..cd79775b3d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift @@ -20,6 +20,7 @@ import Combine import SwiftUI struct SpaceCreationSettings: View { + // MARK: - Properties @ObservedObject var viewModel: SpaceCreationSettingsViewModel.Context @@ -28,6 +29,16 @@ struct SpaceCreationSettings: View { @Environment(\.theme) private var theme: ThemeSwiftUI + private var nextButtonHint: String { + if let error = viewModel.viewState.roomNameError { + return "\(VectorL10n.spacesCreationSettingsNextHintError)\n\(error)" + } + if viewModel.viewState.showRoomAddress, !viewModel.viewState.isAddressValid, let error = viewModel.viewState.addressMessage { + return "\(VectorL10n.spacesCreationSettingsNextHintError)\n\(error)" + } + return VectorL10n.spacesCreationSettingsNextHint + } + // MARK: Public @ViewBuilder @@ -82,6 +93,10 @@ struct SpaceCreationSettings: View { ResponderManager.resignFirstResponder() viewModel.send(viewAction: .pickImage(reader.frame(in: .global))) }) + .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.isButton) + .accessibilityLabel(VectorL10n.spacesCreationSettingsAccessibilityAvatarLabel) + .accessibilityHint(VectorL10n.spacesCreationSettingsAccessibilityAvatarHint) } Image(uiImage: Asset.Images.spaceCreationCamera.image) .renderingMode(.template) @@ -89,6 +104,7 @@ struct SpaceCreationSettings: View { .frame(width: 32, height: 32, alignment: .center) .background(theme.colors.background) .clipShape(Circle()) + .accessibilityHidden(true) }.frame(width: 104, height: 104) } @@ -143,6 +159,7 @@ struct SpaceCreationSettings: View { ResponderManager.resignFirstResponder() viewModel.send(viewAction: .done) } + .accessibilityLabel("\(VectorL10n.next)\n\(nextButtonHint)") } private func scrollDown(reader: ScrollViewProxy) { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift index df9c52906c..b63ec4c3a2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift @@ -51,12 +51,12 @@ struct SpaceSelector: View { isSelected: item.id == viewModel.viewState.selectedSpaceId, notificationCount: item.notificationCount, highlightedNotificationCount: item.highlightedNotificationCount, + selectionAction: { + viewModel.send(viewAction: .spaceSelected(item)) + }, disclosureAction: { viewModel.send(viewAction: .spaceDisclosure(item)) }) - .onTapGesture { - viewModel.send(viewAction: .spaceSelected(item)) - } } } } @@ -67,6 +67,7 @@ struct SpaceSelector: View { Button(VectorL10n.create) { viewModel.send(viewAction: .createSpace) } + .accessibilityHint("starts space creation flow") } ToolbarItem(placement: .cancellationAction) { if viewModel.viewState.showCancel { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift index d9e67c9b93..84d6bbdb78 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelectorListRow.swift @@ -33,8 +33,17 @@ struct SpaceSelectorListRow: View { let isSelected: Bool let notificationCount: UInt let highlightedNotificationCount: UInt + let selectionAction: (() -> Void)? let disclosureAction: (() -> Void)? + private var name: String { + return displayName ?? "" + } + + private var acessibilityName: String { + return notificationCount > 0 ? "\(name)\n\(VectorL10n.spaceSelectorListRowAccessibilityUnreadMessages("\(notificationCount)"))" : name + } + @ViewBuilder var body: some View { ZStack { @@ -45,30 +54,42 @@ struct SpaceSelectorListRow: View { } VStack { HStack { - if let avatar = avatar { - SpaceAvatarImage(avatarData: avatar, size: .xSmall) - } - if let icon = icon { - Image(uiImage: icon) - .renderingMode(.template) + HStack { + if let avatar = avatar { + SpaceAvatarImage(avatarData: avatar, size: .xSmall) + } + if let icon = icon { + Image(uiImage: icon) + .renderingMode(.template) + .foregroundColor(theme.colors.primaryContent) + .frame(width: 32, height: 32) + .background(theme.colors.quinaryContent) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + Text(name) .foregroundColor(theme.colors.primaryContent) - .frame(width: 32, height: 32) - .background(theme.colors.quinaryContent) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - Text(displayName ?? "") - .foregroundColor(theme.colors.primaryContent) - .font(theme.fonts.bodySB) - .accessibility(identifier: "itemName") - Spacer() - if notificationCount > 0 { - badge(with: "\(notificationCount)", color: highlightedNotificationCount > 0 ? theme.colors.alert : theme.colors.secondaryContent) + .font(theme.fonts.bodySB) + .accessibility(identifier: "itemName") + Spacer() + if notificationCount > 0 { + badge(with: "\(notificationCount)", color: highlightedNotificationCount > 0 ? theme.colors.alert : theme.colors.secondaryContent) + } + if !isJoined { + badge(with: "! ", color: theme.colors.alert) + .accessibilityHidden(true) + Image(systemName: "chevron.right") + .renderingMode(.template) + .foregroundColor(theme.colors.secondaryContent) + } } - if !isJoined { - badge(with: "! ", color: theme.colors.alert) - Image(systemName: "chevron.right") - .renderingMode(.template) - .foregroundColor(theme.colors.secondaryContent) + .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.isButton) + .accessibilityLabel(isJoined ? acessibilityName : "invite to \(name)") + .accessibilityHint(isJoined ? (isSelected ? VectorL10n.spaceSelectorListRowAccessibilitySelectedSpace : VectorL10n.spaceSelectorListRowAccessibilitySwitchesTo(name)) : VectorL10n.spaceSelectorListRowAccessibilityInviteHint) + .accessibilityAction { + if !isSelected { + selectionAction?() + } } if hasSubItems, isJoined { Button { @@ -79,6 +100,8 @@ struct SpaceSelectorListRow: View { .foregroundColor(theme.colors.accent) } .accessibility(identifier: "disclosureButton") + .accessibilityLabel(VectorL10n.spaceSelectorListRowAccessibilityDisclosureLabel(name)) + .accessibilityHint(VectorL10n.spaceSelectorListRowAccessibilityDisclosureHint(name)) } } .padding(.vertical, 8) @@ -86,8 +109,10 @@ struct SpaceSelectorListRow: View { .padding(.horizontal) } .padding(.horizontal, 8) - .frame(maxWidth: .infinity) .background(theme.colors.background) + .onTapGesture { + selectionAction?() + } } private func badge(with text: String, color: Color) -> some View { @@ -113,11 +138,11 @@ struct SpaceSelectorListRow_Previews: PreviewProvider { static var sampleView: some View { VStack(spacing: 8) { - SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: false, isJoined: true, isSelected: false, notificationCount: 0, highlightedNotificationCount: 0, disclosureAction: nil) - SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: false, notificationCount: 0, highlightedNotificationCount: 0, disclosureAction: nil) - SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: false, notificationCount: 99, highlightedNotificationCount: 0, disclosureAction: nil) - SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: false, isJoined: true, isSelected: false, notificationCount: 99, highlightedNotificationCount: 1, disclosureAction: nil) - SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: true, notificationCount: 99, highlightedNotificationCount: 1, disclosureAction: nil) + SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: false, isJoined: true, isSelected: false, notificationCount: 0, highlightedNotificationCount: 0, selectionAction: nil, disclosureAction: nil) + SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: false, notificationCount: 0, highlightedNotificationCount: 0, selectionAction: nil, disclosureAction: nil) + SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: false, notificationCount: 99, highlightedNotificationCount: 0, selectionAction: nil, disclosureAction: nil) + SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: false, isJoined: true, isSelected: false, notificationCount: 99, highlightedNotificationCount: 1, selectionAction: nil, disclosureAction: nil) + SpaceSelectorListRow(avatar: nil, icon: UIImage(systemName: "house"), displayName: "Space name", hasSubItems: true, isJoined: true, isSelected: true, notificationCount: 99, highlightedNotificationCount: 1, selectionAction: nil, disclosureAction: nil) } } }