enhancements for top alert post notification for accessibility
This commit is contained in:
parent
ebb2c35c55
commit
3d556a850c
@ -9,10 +9,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import WebKit
|
||||||
|
|
||||||
public enum AccessibilityNotificationType: String, Codable {
|
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.
|
//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,
|
//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
|
//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 {
|
var delay: Double {
|
||||||
switch self {
|
switch self {
|
||||||
case .controllerChanged:
|
case .controllerChanged, .webPageLoaded:
|
||||||
return 1.5
|
return 1.5
|
||||||
case .screenChanged, .layoutChanged:
|
case .screenChanged, .layoutChanged:
|
||||||
return 0.0
|
return 0.0
|
||||||
@ -37,7 +38,7 @@ public enum AccessibilityNotificationType: String, Codable {
|
|||||||
return .screenChanged
|
return .screenChanged
|
||||||
case .layoutChanged, .controllerChanged:
|
case .layoutChanged, .controllerChanged:
|
||||||
return .layoutChanged
|
return .layoutChanged
|
||||||
case .webPageChanged:
|
case .webPageChanged, .webPageLoaded:
|
||||||
return .layoutChanged
|
return .layoutChanged
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +88,8 @@ open class AccessibilityHandler {
|
|||||||
guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil }
|
guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil }
|
||||||
return MVMCoreActionUtility.fatalClassCheck(object: shared)
|
return MVMCoreActionUtility.fatalClassCheck(object: shared)
|
||||||
}
|
}
|
||||||
public let webPageNavigated = PassthroughSubject<MVMCoreLoadObject?, Never>()
|
public weak var delegate: MVMCoreViewControllerProtocol?
|
||||||
|
public var previousAccessiblityElement: Any?
|
||||||
|
|
||||||
private var accessibilityOperationQueue: OperationQueue = {
|
private var accessibilityOperationQueue: OperationQueue = {
|
||||||
let queue = OperationQueue()
|
let queue = OperationQueue()
|
||||||
@ -95,16 +97,15 @@ open class AccessibilityHandler {
|
|||||||
return queue
|
return queue
|
||||||
}()
|
}()
|
||||||
private var anyCancellable: Set<AnyCancellable> = []
|
private var anyCancellable: Set<AnyCancellable> = []
|
||||||
private weak var delegate: MVMCoreViewControllerProtocol?
|
|
||||||
private var accessibilityId: String?
|
private var accessibilityId: String?
|
||||||
private var previousAccessiblityElement: Any?
|
private var announcementText: String?
|
||||||
|
private var hasTopNotitificationInPage: Bool = false
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
registerWithNotificationCenter()
|
registerWithNotificationCenter()
|
||||||
registerForPageChanges()
|
registerForPageChanges()
|
||||||
registerForFocusChanges()
|
registerForFocusChanges()
|
||||||
registerForTopNotificationsChanges()
|
registerForTopNotificationsChanges()
|
||||||
registerForWebpageNavigation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers with the notification center to know when json is updated.
|
/// 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))
|
NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded))
|
||||||
.sink { [weak self] notification in
|
.sink { [weak self] notification in
|
||||||
self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId")
|
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)
|
}.store(in: &anyCancellable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,22 +131,31 @@ open class AccessibilityHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func registerForTopNotificationsChanges() {
|
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
|
NotificationHandler.shared()?.onNotificationShown
|
||||||
.sink { [weak self] (view, model) in
|
.sink { [weak self] (view, model) in
|
||||||
self?.post(notification: .layoutChanged, argument: view)
|
self?.post(notification: .layoutChanged, argument: view)
|
||||||
}.store(in: &anyCancellable)
|
}.store(in: &anyCancellable)
|
||||||
NotificationHandler.shared()?.onNotificationDismissed
|
NotificationHandler.shared()?.onNotificationWillDismiss
|
||||||
.sink { [weak self] (view, model) in
|
.sink { [weak self] (view, model) in
|
||||||
self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"))
|
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)
|
}.store(in: &anyCancellable)
|
||||||
print(anyCancellable)
|
print(anyCancellable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func registerForWebpageNavigation() {
|
open func capturePreviousFocusElement(for model: MoleculeModelProtocol) {
|
||||||
webPageNavigated.sink { [weak self] _ in
|
previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver)
|
||||||
self?.post(notification: .layoutChanged, argument: self?.getFirstFocusedElementOnScreen())
|
}
|
||||||
}.store(in: &anyCancellable)
|
|
||||||
|
open func postAccessbilityToPrevElement(for model: MoleculeModelProtocol) {
|
||||||
|
post(notification: .layoutChanged, argument: previousAccessiblityElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func add(operation: Operation) {
|
private func add(operation: Operation) {
|
||||||
@ -155,11 +166,19 @@ open class AccessibilityHandler {
|
|||||||
accessibilityOperationQueue.cancelAllOperations()
|
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) {
|
public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) {
|
||||||
guard UIAccessibility.isVoiceOverRunning else { return }
|
guard UIAccessibility.isVoiceOverRunning else { return }
|
||||||
let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument)
|
let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument)
|
||||||
add(operation: accessbilityOperation)
|
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.
|
//Subclass can decide to trigger Accessibility notification on screen change.
|
||||||
@ -169,7 +188,12 @@ open class AccessibilityHandler {
|
|||||||
extension AccessibilityHandler: MVMCorePresentationDelegateProtocol {
|
extension AccessibilityHandler: MVMCorePresentationDelegateProtocol {
|
||||||
|
|
||||||
public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) {
|
public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) {
|
||||||
|
previousAccessiblityElement = nil
|
||||||
delegate = viewController as? MVMCoreViewControllerProtocol
|
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) {
|
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" {
|
if let presentationStyle = delegate?.loadObject??.pageJSON?.optionalStringForKey(KeyPresentationStyle) ?? delegate?.loadObject??.requestParameters?.actionMap?.optionalStringForKey(KeyPresentationStyle), presentationStyle == "root" {
|
||||||
navigationOperationType = .set
|
navigationOperationType = .set
|
||||||
}*/
|
}*/
|
||||||
let accessbilityElement = getAccessbilityFocusedElement()
|
if hasTopNotitificationInPage {
|
||||||
post(notification: navigationController.children.count == 1 ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen())
|
previousAccessiblityElement = getFirstFocusedElementOnScreen()
|
||||||
accessibilityId = nil
|
} 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
|
return true
|
||||||
}.first
|
}.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 {
|
extension UIView {
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol {
|
open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol, AccessibilityElementProtocol {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The style of the notification:
|
The style of the notification:
|
||||||
@ -35,18 +35,20 @@ open class NotificationMoleculeModel: ContainerModel, MoleculeModelProtocol {
|
|||||||
public var button: ButtonModel?
|
public var button: ButtonModel?
|
||||||
public var closeButton: NotificationXButtonModel?
|
public var closeButton: NotificationXButtonModel?
|
||||||
public var style: NotificationMoleculeModel.Style = .success
|
public var style: NotificationMoleculeModel.Style = .success
|
||||||
|
public var id: String?
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Initializer
|
// 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.headline = headline
|
||||||
self.style = style
|
self.style = style
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.body = body
|
self.body = body
|
||||||
self.button = button
|
self.button = button
|
||||||
self.closeButton = closeButton
|
self.closeButton = closeButton
|
||||||
|
self.id = id
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,16 +25,6 @@ public class NotificationContainerView: UIView {
|
|||||||
super.init(coder: coder)
|
super.init(coder: coder)
|
||||||
setupView()
|
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 {
|
extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
||||||
@ -56,7 +46,6 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
|||||||
self.superview?.layoutIfNeeded()
|
self.superview?.layoutIfNeeded()
|
||||||
} completion: { finished in
|
} completion: { finished in
|
||||||
self.superview?.layoutIfNeeded()
|
self.superview?.layoutIfNeeded()
|
||||||
self.updateAccessibilityForTopAlert(notification)
|
|
||||||
continuation.resume()
|
continuation.resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,14 +53,11 @@ extension NotificationContainerView: NotificationTransitionDelegateProtocol {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public func hide(notification: UIView) async {
|
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
|
await withCheckedContinuation { continuation in
|
||||||
UIView.animate(withDuration: 0.5) {
|
UIView.animate(withDuration: 0.5) {
|
||||||
self.height.isActive = true
|
self.height.isActive = true
|
||||||
self.superview?.layoutIfNeeded()
|
self.superview?.layoutIfNeeded()
|
||||||
} completion: { finished in
|
} completion: { finished in
|
||||||
UIAccessibility.post(notification: .layoutChanged, argument: nil)
|
|
||||||
self.currentNotificationView?.removeFromSuperview()
|
self.currentNotificationView?.removeFromSuperview()
|
||||||
self.currentNotificationView = nil
|
self.currentNotificationView = nil
|
||||||
continuation.resume()
|
continuation.resume()
|
||||||
|
|||||||
@ -11,11 +11,7 @@ import MVMCore
|
|||||||
|
|
||||||
@objcMembers open class CoreUIObject: MVMCoreObject {
|
@objcMembers open class CoreUIObject: MVMCoreObject {
|
||||||
public var alertHandler: AlertHandler?
|
public var alertHandler: AlertHandler?
|
||||||
public var topNotificationHandler: NotificationHandler? {
|
public var topNotificationHandler: NotificationHandler?
|
||||||
didSet {
|
|
||||||
accessibilityHandler = AccessibilityHandler()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public var accessibilityHandler: AccessibilityHandler?
|
public var accessibilityHandler: AccessibilityHandler?
|
||||||
|
|
||||||
open override func defaultInitialSetup() {
|
open override func defaultInitialSetup() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user