mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Added keyEvent support for iOS 13.4+ (flutter/engine#20972)
This commit is contained in:
parent
4e7a837cfa
commit
8ca4f647dd
@ -322,6 +322,14 @@ FLUTTER_EXPORT
|
||||
*/
|
||||
@property(nonatomic, readonly) FlutterBasicMessageChannel* settingsChannel;
|
||||
|
||||
/**
|
||||
* The `FlutterBasicMessageChannel` used for communicating key events
|
||||
* from physical keyboards
|
||||
*
|
||||
* Can be nil after `destroyContext` is called.
|
||||
*/
|
||||
@property(nonatomic, readonly) FlutterBasicMessageChannel* keyEventChannel;
|
||||
|
||||
/**
|
||||
* The `NSURL` of the observatory for the service isolate.
|
||||
*
|
||||
|
||||
@ -81,6 +81,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
fml::scoped_nsobject<FlutterBasicMessageChannel> _lifecycleChannel;
|
||||
fml::scoped_nsobject<FlutterBasicMessageChannel> _systemChannel;
|
||||
fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel;
|
||||
fml::scoped_nsobject<FlutterBasicMessageChannel> _keyEventChannel;
|
||||
|
||||
int64_t _nextTextureId;
|
||||
|
||||
@ -350,6 +351,9 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
- (FlutterBasicMessageChannel*)settingsChannel {
|
||||
return _settingsChannel.get();
|
||||
}
|
||||
- (FlutterBasicMessageChannel*)keyEventChannel {
|
||||
return _keyEventChannel.get();
|
||||
}
|
||||
|
||||
- (NSURL*)observatoryUrl {
|
||||
return [_publisher.get() url];
|
||||
@ -364,6 +368,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
_lifecycleChannel.reset();
|
||||
_systemChannel.reset();
|
||||
_settingsChannel.reset();
|
||||
_keyEventChannel.reset();
|
||||
}
|
||||
|
||||
- (void)startProfiler:(NSString*)threadLabel {
|
||||
@ -436,6 +441,11 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
binaryMessenger:self.binaryMessenger
|
||||
codec:[FlutterJSONMessageCodec sharedInstance]]);
|
||||
|
||||
_keyEventChannel.reset([[FlutterBasicMessageChannel alloc]
|
||||
initWithName:@"flutter/keyevent"
|
||||
binaryMessenger:self.binaryMessenger
|
||||
codec:[FlutterJSONMessageCodec sharedInstance]]);
|
||||
|
||||
_textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
|
||||
_textInputPlugin.get().textInputDelegate = self;
|
||||
|
||||
|
||||
@ -1024,6 +1024,58 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
[self updateViewportMetrics];
|
||||
}
|
||||
|
||||
- (void)dispatchPresses:(NSSet<UIPress*>*)presses API_AVAILABLE(ios(13.4)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
for (UIPress* press in presses) {
|
||||
if (press.key == nil || press.phase == UIPressPhaseStationary ||
|
||||
press.phase == UIPressPhaseChanged) {
|
||||
continue;
|
||||
}
|
||||
NSMutableDictionary* keyMessage = [@{
|
||||
@"keymap" : @"ios",
|
||||
@"type" : @"unknown",
|
||||
@"keyCode" : @(press.key.keyCode),
|
||||
@"modifiers" : @(press.key.modifierFlags),
|
||||
@"characters" : press.key.characters,
|
||||
@"charactersIgnoringModifiers" : press.key.charactersIgnoringModifiers
|
||||
} mutableCopy];
|
||||
|
||||
if (press.phase == UIPressPhaseBegan) {
|
||||
keyMessage[@"type"] = @"keydown";
|
||||
} else if (press.phase == UIPressPhaseEnded || press.phase == UIPressPhaseCancelled) {
|
||||
keyMessage[@"type"] = @"keyup";
|
||||
}
|
||||
|
||||
[[_engine.get() keyEventChannel] sendMessage:keyMessage];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
[self dispatchPresses:presses];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
[self dispatchPresses:presses];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
[self dispatchPresses:presses];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pressesCancelled:(NSSet<UIPress*>*)presses
|
||||
withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
[self dispatchPresses:presses];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Orientation updates
|
||||
|
||||
- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
|
||||
|
||||
@ -62,6 +62,7 @@ typedef enum UIAccessibilityContrast : NSInteger {
|
||||
@interface FlutterViewController (Tests)
|
||||
- (void)surfaceUpdated:(BOOL)appeared;
|
||||
- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
|
||||
- (void)dispatchPresses:(NSSet<UIPress*>*)presses;
|
||||
@end
|
||||
|
||||
@implementation FlutterViewControllerTest
|
||||
@ -549,4 +550,150 @@ typedef enum UIAccessibilityContrast : NSInteger {
|
||||
OCMVerify([engine notifyLowMemory]);
|
||||
}
|
||||
|
||||
- (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
// noop
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
id engine = OCMClassMock([FlutterEngine class]);
|
||||
|
||||
id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
|
||||
OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
|
||||
|
||||
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
|
||||
id testSet = [self fakeUiPressSetForPhase:UIPressPhaseBegan
|
||||
keyCode:UIKeyboardHIDUsageKeyboardA
|
||||
modifierFlags:UIKeyModifierShift
|
||||
characters:@"a"
|
||||
charactersIgnoringModifiers:@"A"];
|
||||
|
||||
// Exercise behavior under test.
|
||||
[vc dispatchPresses:testSet];
|
||||
|
||||
// Verify behavior.
|
||||
OCMVerify([keyEventChannel
|
||||
sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
|
||||
return [message[@"keymap"] isEqualToString:@"ios"] &&
|
||||
[message[@"type"] isEqualToString:@"keydown"] &&
|
||||
[message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] &&
|
||||
[message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] &&
|
||||
[message[@"characters"] isEqualToString:@"a"] &&
|
||||
[message[@"charactersIgnoringModifiers"] isEqualToString:@"A"];
|
||||
}]]);
|
||||
|
||||
// Clean up mocks
|
||||
[engine stopMocking];
|
||||
[keyEventChannel stopMocking];
|
||||
}
|
||||
|
||||
- (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
// noop
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
id engine = OCMClassMock([FlutterEngine class]);
|
||||
|
||||
id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
|
||||
OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
|
||||
|
||||
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
|
||||
id testSet = [self fakeUiPressSetForPhase:UIPressPhaseEnded
|
||||
keyCode:UIKeyboardHIDUsageKeyboardA
|
||||
modifierFlags:UIKeyModifierShift
|
||||
characters:@"a"
|
||||
charactersIgnoringModifiers:@"A"];
|
||||
|
||||
// Exercise behavior under test.
|
||||
[vc dispatchPresses:testSet];
|
||||
|
||||
// Verify behavior.
|
||||
OCMVerify([keyEventChannel
|
||||
sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
|
||||
return [message[@"keymap"] isEqualToString:@"ios"] &&
|
||||
[message[@"type"] isEqualToString:@"keyup"] &&
|
||||
[message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] &&
|
||||
[message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] &&
|
||||
[message[@"characters"] isEqualToString:@"a"] &&
|
||||
[message[@"charactersIgnoringModifiers"] isEqualToString:@"A"];
|
||||
}]]);
|
||||
|
||||
// Clean up mocks
|
||||
[engine stopMocking];
|
||||
[keyEventChannel stopMocking];
|
||||
}
|
||||
|
||||
- (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
// noop
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
id engine = OCMClassMock([FlutterEngine class]);
|
||||
|
||||
id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
|
||||
OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
|
||||
|
||||
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
|
||||
id emptySet = [NSSet set];
|
||||
id ignoredSet = [self fakeUiPressSetForPhase:UIPressPhaseStationary
|
||||
keyCode:UIKeyboardHIDUsageKeyboardA
|
||||
modifierFlags:UIKeyModifierShift
|
||||
characters:@"a"
|
||||
charactersIgnoringModifiers:@"A"];
|
||||
|
||||
id mockUiPress = OCMClassMock([UIPress class]);
|
||||
OCMStub([mockUiPress phase]).andReturn(UIPressPhaseBegan);
|
||||
id emptyKeySet = [NSSet setWithArray:@[ mockUiPress ]];
|
||||
// Exercise behavior under test.
|
||||
[vc dispatchPresses:emptySet];
|
||||
[vc dispatchPresses:ignoredSet];
|
||||
[vc dispatchPresses:emptyKeySet];
|
||||
|
||||
// Verify behavior.
|
||||
OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
|
||||
|
||||
// Clean up mocks
|
||||
[engine stopMocking];
|
||||
[keyEventChannel stopMocking];
|
||||
}
|
||||
|
||||
- (NSSet<UIPress*>*)fakeUiPressSetForPhase:(UIPressPhase)phase
|
||||
keyCode:(UIKeyboardHIDUsage)keyCode
|
||||
modifierFlags:(UIKeyModifierFlags)modifierFlags
|
||||
characters:(NSString*)characters
|
||||
charactersIgnoringModifiers:(NSString*)charactersIgnoringModifiers
|
||||
API_AVAILABLE(ios(13.4)) {
|
||||
if (@available(iOS 13.4, *)) {
|
||||
// noop
|
||||
} else {
|
||||
return [NSSet set];
|
||||
}
|
||||
id mockUiPress = OCMClassMock([UIPress class]);
|
||||
OCMStub([mockUiPress phase]).andReturn(phase);
|
||||
|
||||
id mockUiKey = OCMClassMock([UIKey class]);
|
||||
OCMStub([mockUiKey keyCode]).andReturn(keyCode);
|
||||
OCMStub([mockUiKey modifierFlags]).andReturn(modifierFlags);
|
||||
OCMStub([mockUiKey characters]).andReturn(characters);
|
||||
OCMStub([mockUiKey charactersIgnoringModifiers]).andReturn(charactersIgnoringModifiers);
|
||||
|
||||
OCMStub([mockUiPress key]).andReturn(mockUiKey);
|
||||
|
||||
return [NSSet setWithArray:@[ mockUiPress ]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user