blob: 4d8d7b94e757365377b5ff77df9ad595f43d4398 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/platform/darwin/message_loop_darwin.h"
static constexpr CFTimeInterval kDistantFuture = 1.0e10;
@interface FlutterKeyboardManager ()
/**
* The primary responders added by addPrimaryResponder.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
/**
* The secondary responders added by addSecondaryResponder.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
- (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
complete:(nonnull KeyEventCompleteCallback)callback
API_AVAILABLE(ios(13.4));
@end
@implementation FlutterKeyboardManager {
std::unique_ptr<fml::WeakPtrFactory<FlutterKeyboardManager>> _weakFactory;
}
- (nonnull instancetype)init {
self = [super init];
if (self != nil) {
_primaryResponders = [[NSMutableArray alloc] init];
_secondaryResponders = [[NSMutableArray alloc] init];
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterKeyboardManager>>(self);
}
return self;
}
- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder {
[_primaryResponders addObject:responder];
}
- (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responder {
[_secondaryResponders addObject:responder];
}
- (void)dealloc {
[_primaryResponders removeAllObjects];
[_secondaryResponders removeAllObjects];
[_primaryResponders release];
[_secondaryResponders release];
_primaryResponders = nil;
_secondaryResponders = nil;
[super dealloc];
}
- (fml::WeakPtr<FlutterKeyboardManager>)getWeakPtr {
return _weakFactory->GetWeakPtr();
}
- (void)handlePress:(nonnull FlutterUIPressProxy*)press
nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
// no-op
} else {
return;
}
bool __block wasHandled = false;
KeyEventCompleteCallback completeCallback = ^void(bool handled, FlutterUIPressProxy* press) {
wasHandled = handled;
CFRunLoopStop(CFRunLoopGetCurrent());
};
switch (press.phase) {
case UIPressPhaseBegan:
case UIPressPhaseEnded: {
// Having no primary responders requires extra logic, but Flutter hard-codes
// all primary responders, so this is a situation that Flutter will never
// encounter.
NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
__block auto weakSelf = [self getWeakPtr];
__block int unreplied = [_primaryResponders count];
__block BOOL anyHandled = false;
FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
unreplied--;
NSAssert(unreplied >= 0, @"More primary responders replied than expected.");
anyHandled = anyHandled || handled;
if (unreplied == 0) {
if (!anyHandled && weakSelf) {
[weakSelf.get() dispatchToSecondaryResponders:press complete:completeCallback];
} else {
completeCallback(true, press);
}
}
};
for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
[responder handlePress:press callback:replyCallback];
}
// Create a nested run loop while we wait for a response from the
// framework. Once the completeCallback is called, this run loop will exit
// and the main one will resume. The completeCallback MUST be called, or
// the app will get stuck in this run loop indefinitely.
//
// We need to run in this mode so that UIKit doesn't give us new
// events until we are done processing this one.
CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO);
break;
}
case UIPressPhaseChanged:
case UIPressPhaseCancelled:
case UIPressPhaseStationary:
break;
}
if (!wasHandled) {
next();
}
}
#pragma mark - Private
- (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
complete:(nonnull KeyEventCompleteCallback)callback
API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
// no-op
} else {
callback(false, press);
return;
}
for (id<FlutterKeySecondaryResponder> responder in _secondaryResponders) {
if ([responder handlePress:press]) {
callback(true, press);
return;
}
}
callback(false, press);
}
@end