未验证 提交 f1d37398 编写于 作者: G gaaclarke 提交者: GitHub

ios accessibility: started ignoring route changes when presenting modal view controllers (#18544)

上级 895ef337
......@@ -39,6 +39,7 @@ NSNotificationName const FlutterViewControllerShowHomeIndicator =
@interface FlutterViewController () <FlutterBinaryMessenger, UIScrollViewDelegate>
@property(nonatomic, readwrite, getter=isDisplayingFlutterUI) BOOL displayingFlutterUI;
@property(nonatomic, assign) BOOL isHomeIndicatorHidden;
@property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
@end
// The following conditional compilation defines an API 13 concept on earlier API targets so that
......@@ -1240,4 +1241,22 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
return [_engine.get() valuePublishedByPlugin:pluginKey];
}
- (void)presentViewController:(UIViewController*)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion {
self.isPresentingViewControllerAnimating = YES;
[super presentViewController:viewControllerToPresent
animated:flag
completion:^{
self.isPresentingViewControllerAnimating = NO;
if (completion) {
completion();
}
}];
}
- (BOOL)isPresentingViewController {
return self.presentedViewController != nil || self.isPresentingViewControllerAnimating;
}
@end
......@@ -23,6 +23,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator;
@interface FlutterViewController ()
@property(nonatomic, readonly) BOOL isPresentingViewController;
- (fml::WeakPtr<FlutterViewController>)getWeakPtr;
- (flutter::FlutterPlatformViewsController*)platformViewsController;
......
......@@ -36,9 +36,21 @@ class PlatformViewIOS;
*/
class AccessibilityBridge final : public AccessibilityBridgeIos {
public:
/** Delegate for handling iOS operations. */
class IosDelegate {
public:
virtual ~IosDelegate() = default;
/// Returns true when the FlutterViewController associated with the `view`
/// is presenting a modal view controller.
virtual bool IsFlutterViewControllerPresentingModalViewController(UIView* view) = 0;
virtual void PostAccessibilityNotification(UIAccessibilityNotifications notification,
id argument) = 0;
};
AccessibilityBridge(UIView* view,
PlatformViewIOS* platform_view,
FlutterPlatformViewsController* platform_views_controller);
FlutterPlatformViewsController* platform_views_controller,
std::unique_ptr<IosDelegate> ios_delegate = nullptr);
~AccessibilityBridge();
void UpdateSemantics(flutter::SemanticsNodeUpdates nodes,
......@@ -75,6 +87,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos {
int32_t previous_route_id_;
std::unordered_map<int32_t, flutter::CustomAccessibilityAction> actions_;
std::vector<int32_t> previous_routes_;
std::unique_ptr<IosDelegate> ios_delegate_;
FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge);
};
......
......@@ -4,6 +4,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
......@@ -13,17 +14,54 @@
FLUTTER_ASSERT_NOT_ARC
namespace flutter {
namespace {
FlutterViewController* _Nullable GetFlutterViewControllerForView(UIView* view) {
// There is no way to get a view's view controller in UIKit directly, this is
// somewhat of a hacky solution to get that. This could be eliminated if the
// bridge actually kept a reference to a FlutterViewController instead of a
// UIView.
id nextResponder = [view nextResponder];
if ([nextResponder isKindOfClass:[FlutterViewController class]]) {
return nextResponder;
} else if ([nextResponder isKindOfClass:[UIView class]]) {
return GetFlutterViewControllerForView(nextResponder);
} else {
return nil;
}
}
class DefaultIosDelegate : public AccessibilityBridge::IosDelegate {
public:
bool IsFlutterViewControllerPresentingModalViewController(UIView* view) override {
FlutterViewController* viewController = GetFlutterViewControllerForView(view);
if (viewController) {
return viewController.isPresentingViewController;
} else {
return false;
}
}
void PostAccessibilityNotification(UIAccessibilityNotifications notification,
id argument) override {
UIAccessibilityPostNotification(notification, argument);
}
};
} // namespace
AccessibilityBridge::AccessibilityBridge(UIView* view,
PlatformViewIOS* platform_view,
FlutterPlatformViewsController* platform_views_controller)
FlutterPlatformViewsController* platform_views_controller,
std::unique_ptr<IosDelegate> ios_delegate)
: view_(view),
platform_view_(platform_view),
platform_views_controller_(platform_views_controller),
objects_([[NSMutableDictionary alloc] init]),
weak_factory_(this),
previous_route_id_(0),
previous_routes_({}) {
previous_routes_({}),
ios_delegate_(ios_delegate ? std::move(ios_delegate)
: std::make_unique<DefaultIosDelegate>()) {
accessibility_channel_.reset([[FlutterBasicMessageChannel alloc]
initWithName:@"flutter/accessibility"
binaryMessenger:platform_view->GetOwnerViewController().get().engine.binaryMessenger
......@@ -137,15 +175,18 @@ void AccessibilityBridge::UpdateSemantics(flutter::SemanticsNodeUpdates nodes,
layoutChanged = layoutChanged || [doomed_uids count] > 0;
if (routeChanged) {
NSString* routeName = [lastAdded routeName];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, routeName);
if (!ios_delegate_->IsFlutterViewControllerPresentingModalViewController(view_)) {
NSString* routeName = [lastAdded routeName];
ios_delegate_->PostAccessibilityNotification(UIAccessibilityScreenChangedNotification,
routeName);
}
} else if (layoutChanged) {
// TODO(goderbauer): figure out which node to focus next.
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification, nil);
}
if (scrollOccured) {
// TODO(tvolkert): provide meaningful string (e.g. "page 2 of 5")
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, @"");
ios_delegate_->PostAccessibilityNotification(UIAccessibilityPageScrolledNotification, @"");
}
}
......@@ -233,7 +274,7 @@ void AccessibilityBridge::HandleEvent(NSDictionary<NSString*, id>* annotatedEven
NSString* type = annotatedEvent[@"type"];
if ([type isEqualToString:@"announce"]) {
NSString* message = annotatedEvent[@"data"][@"message"];
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, message);
ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, message);
}
}
......
......@@ -86,6 +86,22 @@ class MockDelegate : public PlatformView::Delegate {
void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
};
class MockIosDelegate : public AccessibilityBridge::IosDelegate {
public:
bool IsFlutterViewControllerPresentingModalViewController(UIView* view) override {
return result_IsFlutterViewControllerPresentingModalViewController_;
};
void PostAccessibilityNotification(UIAccessibilityNotifications notification,
id argument) override {
if (on_PostAccessibilityNotification_) {
on_PostAccessibilityNotification_(notification, argument);
}
}
std::function<void(UIAccessibilityNotifications, id)> on_PostAccessibilityNotification_;
bool result_IsFlutterViewControllerPresentingModalViewController_ = false;
};
} // namespace
} // namespace flutter
......@@ -238,4 +254,112 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
XCTAssertNil(gMockPlatformView);
}
- (void)testAnnouncesRouteChanges {
flutter::MockDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*task_runners=*/runners);
id mockFlutterView = OCMClassMock([FlutterView class]);
std::string label = "some label";
NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
[[[NSMutableArray alloc] init] autorelease];
auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
ios_delegate->on_PostAccessibilityNotification_ =
[accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
[accessibility_notifications addObject:@{
@"notification" : @(notification),
@"argument" : argument ? argument : [NSNull null],
}];
};
__block auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view=*/mockFlutterView,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil,
/*ios_delegate=*/std::move(ios_delegate));
flutter::CustomAccessibilityActionUpdates actions;
flutter::SemanticsNodeUpdates nodes;
flutter::SemanticsNode route_node;
route_node.id = 1;
route_node.label = label;
route_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
route_node.label = "route";
nodes[route_node.id] = route_node;
flutter::SemanticsNode root_node;
root_node.id = kRootNodeId;
root_node.label = label;
root_node.childrenInTraversalOrder = {1};
root_node.childrenInHitTestOrder = {1};
nodes[root_node.id] = root_node;
bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
XCTAssertEqual([accessibility_notifications count], 1ul);
XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"route");
XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
UIAccessibilityScreenChangedNotification);
}
- (void)testAnnouncesIgnoresRouteChangesWhenModal {
flutter::MockDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*task_runners=*/runners);
id mockFlutterView = OCMClassMock([FlutterView class]);
std::string label = "some label";
NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
[[[NSMutableArray alloc] init] autorelease];
auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
ios_delegate->on_PostAccessibilityNotification_ =
[accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
[accessibility_notifications addObject:@{
@"notification" : @(notification),
@"argument" : argument ? argument : [NSNull null],
}];
};
ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
__block auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view=*/mockFlutterView,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil,
/*ios_delegate=*/std::move(ios_delegate));
flutter::CustomAccessibilityActionUpdates actions;
flutter::SemanticsNodeUpdates nodes;
flutter::SemanticsNode route_node;
route_node.id = 1;
route_node.label = label;
route_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
route_node.label = "route";
nodes[route_node.id] = route_node;
flutter::SemanticsNode root_node;
root_node.id = kRootNodeId;
root_node.label = label;
root_node.childrenInTraversalOrder = {1};
root_node.childrenInHitTestOrder = {1};
nodes[root_node.id] = root_node;
bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
XCTAssertEqual([accessibility_notifications count], 0ul);
}
@end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册