enhancements for top alert post notification for accessibility

This commit is contained in:
Krishna Kishore Bandaru 2023-07-14 22:46:22 +05:30
parent ebb2c35c55
commit 3d556a850c
4 changed files with 50 additions and 43 deletions

View File

@ -9,10 +9,11 @@
import Foundation
import Combine
import MVMCore
import WebKit
public enum AccessibilityNotificationType: String, Codable {
case controllerChanged, layoutChanged, screenChanged, announcement, webPageChanged
case controllerChanged, layoutChanged, screenChanged, announcement, webPageChanged, webPageLoaded
//TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher.
//https://developer.apple.com/forums/thread/132699,
@ -20,7 +21,7 @@ public enum AccessibilityNotificationType: String, Codable {
//By default from iOS 13+ focus is getting shifted to first interactive element inside viewcontroller not to the navigationitem left barbutton item so posting layoutChanged notification with delay to push to leftbarbutton item on new screen push
var delay: Double {
switch self {
case .controllerChanged:
case .controllerChanged, .webPageLoaded:
return 1.5
case .screenChanged, .layoutChanged:
return 0.0
@ -37,7 +38,7 @@ public enum AccessibilityNotificationType: String, Codable {
return .screenChanged
case .layoutChanged, .controllerChanged:
return .layoutChanged
case .webPageChanged:
case .webPageChanged, .webPageLoaded:
return .layoutChanged
}
}
@ -87,7 +88,8 @@ open class AccessibilityHandler {
guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil }
return MVMCoreActionUtility.fatalClassCheck(object: shared)
}
public let webPageNavigated = PassthroughSubject<MVMCoreLoadObject?, Never>()
public weak var delegate: MVMCoreViewControllerProtocol?
public var previousAccessiblityElement: Any?
private var accessibilityOperationQueue: OperationQueue = {
let queue = OperationQueue()
@ -95,16 +97,15 @@ open class AccessibilityHandler {
return queue
}()
private var anyCancellable: Set<AnyCancellable> = []
private weak var delegate: MVMCoreViewControllerProtocol?
private var accessibilityId: String?
private var previousAccessiblityElement: Any?
private var announcementText: String?
private var hasTopNotitificationInPage: Bool = false
public init() {
registerWithNotificationCenter()
registerForPageChanges()
registerForFocusChanges()
registerForTopNotificationsChanges()
registerForWebpageNavigation()
}
/// Registers with the notification center to know when json is updated.
@ -112,6 +113,7 @@ open class AccessibilityHandler {
NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded))
.sink { [weak self] notification in
self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId")
self?.announcementText = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("announcementText")
}.store(in: &anyCancellable)
}
@ -129,22 +131,31 @@ open class AccessibilityHandler {
}
private func registerForTopNotificationsChanges() {
NotificationHandler.shared()?.onNotificationWillShow.sink { [weak self] (_, model) in
self?.hasTopNotitificationInPage = true
self?.capturePreviousFocusElement(for: model.molecule)
}.store(in: &anyCancellable)
NotificationHandler.shared()?.onNotificationShown
.sink { [weak self] (view, model) in
self?.post(notification: .layoutChanged, argument: view)
}.store(in: &anyCancellable)
NotificationHandler.shared()?.onNotificationDismissed
NotificationHandler.shared()?.onNotificationWillDismiss
.sink { [weak self] (view, model) in
self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"))
self?.post(notification: .screenChanged, argument: self?.previousAccessiblityElement)
}.store(in: &anyCancellable)
NotificationHandler.shared()?.onNotificationDismissed
.sink { [weak self] (view, model) in
self?.postAccessbilityToPrevElement(for: model.molecule)
}.store(in: &anyCancellable)
print(anyCancellable)
}
private func registerForWebpageNavigation() {
webPageNavigated.sink { [weak self] _ in
self?.post(notification: .layoutChanged, argument: self?.getFirstFocusedElementOnScreen())
}.store(in: &anyCancellable)
open func capturePreviousFocusElement(for model: MoleculeModelProtocol) {
previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver)
}
open func postAccessbilityToPrevElement(for model: MoleculeModelProtocol) {
post(notification: .layoutChanged, argument: previousAccessiblityElement)
}
private func add(operation: Operation) {
@ -155,11 +166,19 @@ open class AccessibilityHandler {
accessibilityOperationQueue.cancelAllOperations()
}
open func post(webpageChanged type: AccessibilityNotificationType, argument: Any? = nil) {
post(notification: type, argument: argument)
}
public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) {
guard UIAccessibility.isVoiceOverRunning else { return }
let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument)
add(operation: accessbilityOperation)
previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver)
}
//To get first foucs element on the screen
open func getFirstFocusedElementOnScreen() -> Any? {
(delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar
}
//Subclass can decide to trigger Accessibility notification on screen change.
@ -169,7 +188,12 @@ open class AccessibilityHandler {
extension AccessibilityHandler: MVMCorePresentationDelegateProtocol {
public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) {
previousAccessiblityElement = nil
delegate = viewController as? MVMCoreViewControllerProtocol
if let announcementText {
let accessbilityOperation = AccessbilityOperation(notificationType: .announcement, argument: announcementText)
add(operation: accessbilityOperation)
}
}
public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
@ -180,9 +204,13 @@ extension AccessibilityHandler: MVMCorePresentationDelegateProtocol {
if let presentationStyle = delegate?.loadObject??.pageJSON?.optionalStringForKey(KeyPresentationStyle) ?? delegate?.loadObject??.requestParameters?.actionMap?.optionalStringForKey(KeyPresentationStyle), presentationStyle == "root" {
navigationOperationType = .set
}*/
let accessbilityElement = getAccessbilityFocusedElement()
post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen())
accessibilityId = nil
if hasTopNotitificationInPage {
previousAccessiblityElement = getFirstFocusedElementOnScreen()
} else {
let accessbilityElement = getAccessbilityFocusedElement()
post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen())
accessibilityId = nil
}
}
}
@ -202,11 +230,6 @@ extension AccessibilityHandler {
return true
}.first
}
//To get first foucs element on the screen
private func getFirstFocusedElementOnScreen() -> Any? {
(delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar
}
}
extension UIView {

View File

@ -7,7 +7,7 @@
//
open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol {
open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol, AccessibilityElementProtocol {
/**
The style of the notification:
@ -35,18 +35,20 @@ open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol {
public var button: ButtonModel?
public var closeButton: NotificationXButtonModel?
public var style: NotificationMoleculeModel.Style = .success
public var id: String?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil) {
public init(with headline: LabelModel, style: NotificationMoleculeModel.Style = .success, backgroundColor: Color? = nil, body: LabelModel? = nil, button: ButtonModel? = nil, closeButton: NotificationXButtonModel? = nil, id: String? = nil) {
self.headline = headline
self.style = style
self.backgroundColor = backgroundColor
self.body = body
self.button = button
self.closeButton = closeButton
self.id = id
super.init()
}

View File

@ -25,16 +25,6 @@ public class NotificationContainerView: UIView {
super.init(coder: coder)
setupView()
}
/// Posts a layout change with taking the arguments from the view following the AccessibilityProtocol.
private func updateAccessibilityForTopAlert(_ view: UIView) {
// Update accessibility with top alert
var accessibilityArgument: Any? = view
if let view = view as? AccessibilityProtocol {
accessibilityArgument = view.getAccessibilityLayoutChangedArgument()
}
UIAccessibility.post(notification: .layoutChanged, argument: accessibilityArgument)
}
}
extension NotificationContainerView: NotificationTransitionDelegateProtocol {
@ -56,7 +46,6 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
self.superview?.layoutIfNeeded()
} completion: { finished in
self.superview?.layoutIfNeeded()
self.updateAccessibilityForTopAlert(notification)
continuation.resume()
}
}
@ -64,14 +53,11 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
@MainActor
public func hide(notification: UIView) async {
// accessibility - below line added to notify VI user through voiceover user when the top alert is closed
UIAccessibility.post(notification: .screenChanged, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"))
await withCheckedContinuation { continuation in
UIView.animate(withDuration: 0.5) {
self.height.isActive = true
self.superview?.layoutIfNeeded()
} completion: { finished in
UIAccessibility.post(notification: .layoutChanged, argument: nil)
self.currentNotificationView?.removeFromSuperview()
self.currentNotificationView = nil
continuation.resume()

View File

@ -11,11 +11,7 @@ import MVMCore
@objcMembers open class CoreUIObject: MVMCoreObject {
public var alertHandler: AlertHandler?
public var topNotificationHandler: NotificationHandler? {
didSet {
accessibilityHandler = AccessibilityHandler()
}
}
public var topNotificationHandler: NotificationHandler?
public var accessibilityHandler: AccessibilityHandler?
open override func defaultInitialSetup() {