Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native Editor - VoiceOver improvements #4737

Merged
merged 11 commits into from Feb 8, 2024
Expand Up @@ -52,6 +52,7 @@ public struct WKSourceEditorLocalizedStrings {
let keyboardTemplateButtonAccessibility: String
let keyboardCommentButtonAccessibility: String

let wikitextEditorAccessibility: String
let findTextFieldAccessibility: String
let findClearButtonAccessibility: String
let findCurrentMatchInfoFormatAccessibility: String
Expand All @@ -66,7 +67,7 @@ public struct WKSourceEditorLocalizedStrings {
let replaceTypeSingleAccessibility: String
let replaceTypeAllAccessibility: String

public init(keyboardTextFormattingTitle: String, keyboardParagraph: String, keyboardHeading: String, keyboardSubheading1: String, keyboardSubheading2: String, keyboardSubheading3: String, keyboardSubheading4: String, findAndReplaceTitle: String, replaceTypeSingle: String, replaceTypeAll: String, replaceTextfieldPlaceholder: String, replaceTypeContextMenuTitle: String, toolbarOpenTextFormatMenuButtonAccessibility: String, toolbarReferenceButtonAccessibility: String, toolbarLinkButtonAccessibility: String, toolbarTemplateButtonAccessibility: String, toolbarImageButtonAccessibility: String, toolbarFindButtonAccessibility: String, toolbarExpandButtonAccessibility: String, toolbarListUnorderedButtonAccessibility: String, toolbarListOrderedButtonAccessibility: String, toolbarIndentIncreaseButtonAccessibility: String, toolbarIndentDecreaseButtonAccessibility: String, toolbarCursorUpButtonAccessibility: String, toolbarCursorDownButtonAccessibility: String, toolbarCursorPreviousButtonAccessibility: String, toolbarCursorNextButtonAccessibility: String, toolbarBoldButtonAccessibility: String, toolbarItalicsButtonAccessibility: String, keyboardCloseTextFormatMenuButtonAccessibility: String, keyboardBoldButtonAccessibility: String, keyboardItalicsButtonAccessibility: String, keyboardUnderlineButtonAccessibility: String, keyboardStrikethroughButtonAccessibility: String, keyboardReferenceButtonAccessibility: String, keyboardLinkButtonAccessibility: String, keyboardListUnorderedButtonAccessibility: String, keyboardListOrderedButtonAccessibility: String, keyboardIndentIncreaseButtonAccessibility: String, keyboardIndentDecreaseButtonAccessibility: String, keyboardSuperscriptButtonAccessibility: String, keyboardSubscriptButtonAccessibility: String, keyboardTemplateButtonAccessibility: String, keyboardCommentButtonAccessibility: String, findTextFieldAccessibility: String, findClearButtonAccessibility: String, findCurrentMatchInfoFormatAccessibility: String, findCurrentMatchInfoZeroResultsAccessibility: String, findCloseButtonAccessibility: String, findNextButtonAccessibility: String, findPreviousButtonAccessibility: String, replaceTextFieldAccessibility: String, replaceClearButtonAccessibility: String, replaceButtonAccessibilityFormat: String, replaceTypeButtonAccessibilityFormat: String, replaceTypeSingleAccessibility: String, replaceTypeAllAccessibility: String) {
public init(keyboardTextFormattingTitle: String, keyboardParagraph: String, keyboardHeading: String, keyboardSubheading1: String, keyboardSubheading2: String, keyboardSubheading3: String, keyboardSubheading4: String, findAndReplaceTitle: String, replaceTypeSingle: String, replaceTypeAll: String, replaceTextfieldPlaceholder: String, replaceTypeContextMenuTitle: String, toolbarOpenTextFormatMenuButtonAccessibility: String, toolbarReferenceButtonAccessibility: String, toolbarLinkButtonAccessibility: String, toolbarTemplateButtonAccessibility: String, toolbarImageButtonAccessibility: String, toolbarFindButtonAccessibility: String, toolbarExpandButtonAccessibility: String, toolbarListUnorderedButtonAccessibility: String, toolbarListOrderedButtonAccessibility: String, toolbarIndentIncreaseButtonAccessibility: String, toolbarIndentDecreaseButtonAccessibility: String, toolbarCursorUpButtonAccessibility: String, toolbarCursorDownButtonAccessibility: String, toolbarCursorPreviousButtonAccessibility: String, toolbarCursorNextButtonAccessibility: String, toolbarBoldButtonAccessibility: String, toolbarItalicsButtonAccessibility: String, keyboardCloseTextFormatMenuButtonAccessibility: String, keyboardBoldButtonAccessibility: String, keyboardItalicsButtonAccessibility: String, keyboardUnderlineButtonAccessibility: String, keyboardStrikethroughButtonAccessibility: String, keyboardReferenceButtonAccessibility: String, keyboardLinkButtonAccessibility: String, keyboardListUnorderedButtonAccessibility: String, keyboardListOrderedButtonAccessibility: String, keyboardIndentIncreaseButtonAccessibility: String, keyboardIndentDecreaseButtonAccessibility: String, keyboardSuperscriptButtonAccessibility: String, keyboardSubscriptButtonAccessibility: String, keyboardTemplateButtonAccessibility: String, keyboardCommentButtonAccessibility: String, wikitextEditorAccessibility: String, findTextFieldAccessibility: String, findClearButtonAccessibility: String, findCurrentMatchInfoFormatAccessibility: String, findCurrentMatchInfoZeroResultsAccessibility: String, findCloseButtonAccessibility: String, findNextButtonAccessibility: String, findPreviousButtonAccessibility: String, replaceTextFieldAccessibility: String, replaceClearButtonAccessibility: String, replaceButtonAccessibilityFormat: String, replaceTypeButtonAccessibilityFormat: String, replaceTypeSingleAccessibility: String, replaceTypeAllAccessibility: String) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an interesting situation we've backed ourselves into transiting localizations into Components through these structs and this pattern because of, in practice, how verbose and exploded our call sites become. 🤠 To be clear, I'm ok with this pattern because even though it's verbose, it's at least 100% clear about the components data needs over something like an arbitrary Dictionary. But it may be worth brainstorming a little during an eng. sync as a group to see if we can think of any cleaner alternatives (for the sake of the client call sites) in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

self.keyboardTextFormattingTitle = keyboardTextFormattingTitle
self.keyboardParagraph = keyboardParagraph
self.keyboardHeading = keyboardHeading
Expand Down Expand Up @@ -111,6 +112,7 @@ public struct WKSourceEditorLocalizedStrings {
self.keyboardSubscriptButtonAccessibility = keyboardSubscriptButtonAccessibility
self.keyboardTemplateButtonAccessibility = keyboardTemplateButtonAccessibility
self.keyboardCommentButtonAccessibility = keyboardCommentButtonAccessibility
self.wikitextEditorAccessibility = wikitextEditorAccessibility
self.findTextFieldAccessibility = findTextFieldAccessibility
self.findClearButtonAccessibility = findClearButtonAccessibility
self.findCurrentMatchInfoFormatAccessibility = findCurrentMatchInfoFormatAccessibility
Expand Down
Expand Up @@ -13,6 +13,15 @@ fileprivate var needsTextKit2: Bool {
}
}

class WKSourceEditorTextView: UITextView {
override func accessibilityActivate() -> Bool {

UIAccessibility.post(notification: .announcement, argument: "Loading editor text.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patterning usage elsewhere, should we localize this argument string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good catch!


return super.accessibilityActivate()
}
}

@objc final class WKSourceEditorSelectionState: NSObject {
let isBold: Bool
let isItalics: Bool
Expand Down Expand Up @@ -101,7 +110,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject {
let textView: UITextView
if needsTextKit2 {
if #available(iOS 16, *) {
textView = UITextView(usingTextLayoutManager: true)
textView = WKSourceEditorTextView(usingTextLayoutManager: true)
textKit2Storage = textView.textLayoutManager?.textContentManager as? NSTextContentStorage
} else {
fatalError("iOS 15 cannot handle TextKit2")
Expand All @@ -118,7 +127,7 @@ final class WKSourceEditorTextFrameworkMediator: NSObject {
layoutManager.addTextContainer(container)
textKit1Storage?.addLayoutManager(layoutManager)

textView = UITextView(frame: .zero, textContainer: container)
textView = WKSourceEditorTextView(frame: .zero, textContainer: container)
textKit2Storage = nil
}

Expand Down
Expand Up @@ -149,6 +149,7 @@ public class WKSourceEditorViewController: WKComponentViewController {
textView.delegate = self
textFrameworkMediator.delegate = self
textView.isEditable = !viewModel.needsReadOnly
textView.accessibilityLabel = WKSourceEditorLocalizedStrings.current.wikitextEditorAccessibility

view.addSubview(textView)
updateColorsAndFonts()
Expand Down
3 changes: 2 additions & 1 deletion Components/Tests/ComponentsTests/WKSourceEditorTests.swift
Expand Up @@ -104,7 +104,8 @@ extension WKSourceEditorLocalizedStrings {
keyboardSuperscriptButtonAccessibility: "",
keyboardSubscriptButtonAccessibility: "",
keyboardTemplateButtonAccessibility: "",
keyboardCommentButtonAccessibility: "",
keyboardCommentButtonAccessibility: "",
wikitextEditorAccessibility: "",
findTextFieldAccessibility: "",
findClearButtonAccessibility: "",
findCurrentMatchInfoFormatAccessibility: "",
Expand Down
1 change: 1 addition & 0 deletions WMF Framework/CommonStrings.swift
Expand Up @@ -606,6 +606,7 @@ public class CommonStrings: NSObject {

public static let editorKeyboardButtonCloseTextFormatMenuAccessiblityLabel = WMFLocalizedString("editor-keyboard-close-text-format-menu-accessibility", value: "Close text formatting menu", comment: "Accessibility label for close keyboard button on the editor. This button closes the keyboard text formatting menu.")

public static let editorWikitextTextViewAccessibility = WMFLocalizedString("editor-wikitext-textview-accessibility", value: "Wiki text editor", comment: "Accessibility label for the wikitext editor textview.")
public static let editorFindTextFieldAccessibilityLabel = WMFLocalizedString("editor-find-textfield-accessibility", value: "Find", comment: "Accessibility label for the find textfield on the editor")
public static let editorFindClearButtonAccessibilityLabel = WMFLocalizedString("editor-find-clear-button-accessibility", value: "Clear find", comment: "Accessibility label for the clear find button on the editor. This button clears the text in the find textfield.")
public static let editorFindCurrentMatchInfoFormatAccessibilityLabel = WMFLocalizedString("editor-find-current-match-info-accessibility", value: "%1$@ total matches found. Highlighted match number %2$@", comment: "Accessibility text for the match results informational label. %1$@ is replaced by the total number of matches found. %2$@ is replaced by which number match is currently highlighted.")
Expand Down
10 changes: 10 additions & 0 deletions Wikipedia/Code/EditNoticesView.swift
Expand Up @@ -229,6 +229,8 @@ final class EditNoticesView: SetupView {
footerStack.topAnchor.constraint(equalTo: footerContainer.topAnchor, constant: 16),
footerStack.bottomAnchor.constraint(equalTo: footerContainer.safeAreaLayoutGuide.bottomAnchor, constant: -16)
])

changeTextViewVoiceOverVisibility(isVisible: false)
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
Expand All @@ -239,6 +241,14 @@ final class EditNoticesView: SetupView {
}

// MARK: - Public

func changeTextViewVoiceOverVisibility(isVisible: Bool) {
if !isVisible {
accessibilityElements = [doneButton, editNoticesTitle, editNoticesSubtitle, footerSwitchLabel, toggleSwitch]
} else {
accessibilityElements = [doneButton, editNoticesTitle, editNoticesSubtitle, textView, footerSwitchLabel, toggleSwitch]
}
}

func configure(viewModel: EditNoticesViewModel, theme: Theme) {
let attributedNoticeString = NSMutableAttributedString()
Expand Down
11 changes: 11 additions & 0 deletions Wikipedia/Code/EditNoticesViewController.swift
Expand Up @@ -38,6 +38,17 @@ class EditNoticesViewController: ThemeableViewController, RMessageSuppressing {
editNoticesView.toggleSwitch.isOn = UserDefaults.standard.wmf_alwaysDisplayEditNotices
editNoticesView.textView.delegate = self
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

UIAccessibility.post(notification: .screenChanged, argument: editNoticesView.editNoticesTitle)

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.editNoticesView.changeTextViewVoiceOverVisibility(isVisible: true)
UIAccessibility.post(notification: .layoutChanged, argument: nil)
}
}

// MARK: - Actions

Expand Down
6 changes: 6 additions & 0 deletions Wikipedia/Code/InsertMediaSettingsViewController.swift
Expand Up @@ -209,6 +209,12 @@ final class InsertMediaSettingsViewController: ViewController {
tableView.tableHeaderView = imageView
tableView.tableFooterView = buttonView
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

UIAccessibility.post(notification: .screenChanged, argument: nil)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
Expand Down
2 changes: 2 additions & 0 deletions Wikipedia/Code/PageEditorViewController.swift
Expand Up @@ -428,6 +428,7 @@ final class PageEditorViewController: UIViewController {
keyboardSubscriptButtonAccessibility: CommonStrings.editorKeyboardButtonSubscriptAccessiblityLabel,
keyboardTemplateButtonAccessibility: CommonStrings.editorKeyboardButtonTemplateAccessiblityLabel,
keyboardCommentButtonAccessibility: CommonStrings.editorKeyboardButtonCommentAccessiblityLabel,
wikitextEditorAccessibility: CommonStrings.editorWikitextTextViewAccessibility,
findTextFieldAccessibility: CommonStrings.editorFindTextFieldAccessibilityLabel,
findClearButtonAccessibility: CommonStrings.editorFindClearButtonAccessibilityLabel,
findCurrentMatchInfoFormatAccessibility: CommonStrings.editorFindCurrentMatchInfoFormatAccessibilityLabel,
Expand Down Expand Up @@ -615,6 +616,7 @@ extension PageEditorViewController: WKSourceEditorViewControllerDelegate {
}

func sourceEditorViewControllerDidTapImage() {
sourceEditor.removeFocus()
let insertMediaViewController = InsertMediaViewController(articleTitle: pageURL.wmf_title, siteURL: pageURL.wmf_site)
insertMediaViewController.delegate = self
insertMediaViewController.apply(theme: theme)
Expand Down
1 change: 1 addition & 0 deletions Wikipedia/Localizations/en.lproj/Localizable.strings
Expand Up @@ -398,6 +398,7 @@
"editor-toolbar-reference-accessibility" = "Reference text formatting";
"editor-toolbar-show-more-accessibility" = "Show more formatting options";
"editor-toolbar-template-accessibility" = "Template text formatting";
"editor-wikitext-textview-accessibility" = "Wiki text editor";
"empty-diff-compare-title" = "No differences between revisions";
"empty-diff-single-title" = "No viewable changes made";
"empty-insert-media-title" = "Select a file from Wikimedia Commons";
Expand Down
1 change: 1 addition & 0 deletions Wikipedia/Localizations/qqq.lproj/Localizable.strings
Expand Up @@ -398,6 +398,7 @@
"editor-toolbar-reference-accessibility" = "Accessibility label for reference toolbar button on the editor.";
"editor-toolbar-show-more-accessibility" = "Accessibility label for expand button on the formatting toolbar in editor. This button reveals more formatting toolbar buttons.";
"editor-toolbar-template-accessibility" = "Accessibility label for template toolbar button on the editor.";
"editor-wikitext-textview-accessibility" = "Accessibility label for the wikitext editor textview.";
"empty-diff-compare-title" = "Text for placeholder label visible when diff comparision between revisions is empty.";
"empty-diff-single-title" = "Text for placeholder label visible when diff returned for single revision is empty.";
"empty-insert-media-title" = "Text for placeholder label visible when no file was selected or uploaded";
Expand Down
Binary file modified Wikipedia/iOS Native Localizations/en.lproj/Localizable.strings
Binary file not shown.
2 changes: 1 addition & 1 deletion WikipediaUITests/UITestHelperViewController.swift
Expand Up @@ -84,7 +84,7 @@ public class UITestHelperViewController: WKCanvasViewController {
keyboardSuperscriptButtonAccessibility: CommonStrings.editorKeyboardButtonSuperscriptAccessiblityLabel,
keyboardSubscriptButtonAccessibility: CommonStrings.editorKeyboardButtonSubscriptAccessiblityLabel,
keyboardTemplateButtonAccessibility: CommonStrings.editorKeyboardButtonTemplateAccessiblityLabel,
keyboardCommentButtonAccessibility: CommonStrings.editorKeyboardButtonCommentAccessiblityLabel,
keyboardCommentButtonAccessibility: CommonStrings.editorKeyboardButtonCommentAccessiblityLabel, wikitextEditorAccessibility: CommonStrings.editorWikitextTextViewAccessibility,
findTextFieldAccessibility: CommonStrings.editorFindTextFieldAccessibilityLabel,
findClearButtonAccessibility: CommonStrings.editorFindClearButtonAccessibilityLabel,
findCurrentMatchInfoFormatAccessibility: CommonStrings.editorFindCurrentMatchInfoFormatAccessibilityLabel,
Expand Down