Add Shortcut "Double R" to Reload JS in iOS

Summary:
Enable double tap R on iOS, consistent with Android.
Keep the existing Cmd+R on iOS because people are already used to it.
Make Cmd+Key and Double Key both invalid when focus is in textview or textfield.
Also try to add Cmd+R in Android, but seems no good.

Reviewed By: nicklockwood

Differential Revision: D3343907

fbshipit-source-id: 68f7e3b0393711c137e1d932db33e1b6a2a19e09
This commit is contained in:
Siqi Liu 2016-06-01 03:29:11 -07:00 committed by Facebook Github Bot 9
parent a49558def8
commit 2d32a6df27
3 changed files with 200 additions and 6 deletions

View File

@ -172,6 +172,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
userInfo:nil];
}];
[commands registerDoublePressKeyCommandWithInput:@"r"
modifierFlags:0
action:^(__unused UIKeyCommand *command) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
object:nil
userInfo:nil];
}];
#endif
}

View File

@ -14,22 +14,41 @@
+ (instancetype)sharedInstance;
/**
* Register a keyboard command.
* Register a single-press keyboard command.
*/
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *command))block;
/**
* Unregister a keyboard command.
* Unregister a single-press keyboard command.
*/
- (void)unregisterKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags;
/**
* Check if a command is registered.
* Check if a single-press command is registered.
*/
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags;
/**
* Register a double-press keyboard command.
*/
- (void)registerDoublePressKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *command))block;
/**
* Unregister a double-press keyboard command.
*/
- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags;
/**
* Check if a double-press command is registered.
*/
- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags;
@end

View File

@ -83,17 +83,60 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
@implementation UIResponder (RCTKeyCommands)
+ (UIResponder *)RCT_getFirstResponder:(UIResponder *)view
{
UIResponder *firstResponder = nil;
if (view.isFirstResponder) {
return view;
} else if ([view isKindOfClass:[UIViewController class]]) {
if ([(UIViewController *)view parentViewController]) {
firstResponder = [UIResponder RCT_getFirstResponder: [(UIViewController *)view parentViewController]];
}
return firstResponder ? firstResponder : [UIResponder RCT_getFirstResponder: [(UIViewController *)view view]];
} else if ([view isKindOfClass:[UIView class]]) {
for (UIView *subview in [(UIView *)view subviews]) {
firstResponder = [UIResponder RCT_getFirstResponder: subview];
if (firstResponder) {
return firstResponder;
}
}
}
return firstResponder;
}
- (NSArray<UIKeyCommand *> *)RCT_keyCommands
{
/**
* If current first responder is UITextView or UITextField, disable the shortcut key commands.
* For example, double-pressing a key should type two characters into the text view,
* not invoke a predefined keyboard shortcut.
*/
NSString *name = NSStringFromClass(self.class);
if ([name isEqualToString : @"AppDelegate"]) {
return nil;
}
UIResponder *firstResponder = [UIResponder RCT_getFirstResponder: self];
if ([firstResponder isKindOfClass:[UITextView class]] ||
[firstResponder isKindOfClass:[UITextField class]] ||
[firstResponder.class conformsToProtocol:@protocol(UITextViewDelegate)]) {
return nil;
}
NSSet<RCTKeyCommand *> *commands = [RCTKeyCommands sharedInstance].commands;
return [[commands valueForKeyPath:@"keyCommand"] allObjects];
}
/**
* Single Press Key Command Response
* Command + KeyEvent (Command + R/D, etc.)
*/
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key
{
// NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
// method gets called repeatedly if the command key is held down.
static NSTimeInterval lastCommand = 0;
if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastCommand > 0.5) {
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
@ -108,6 +151,61 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
}
/**
* Double Press Key Command Response
* Double KeyEvent (Double R, etc.)
*/
- (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key
{
static BOOL firstPress = YES;
static NSTimeInterval lastCommand = 0;
static NSTimeInterval lastDoubleCommand = 0;
static NSString *lastInput = nil;
static UIKeyModifierFlags lastModifierFlags = nil;
if (firstPress) {
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
if ([command.keyCommand.input isEqualToString:key.input] &&
command.keyCommand.modifierFlags == key.modifierFlags &&
command.block) {
firstPress = NO;
lastCommand = CACurrentMediaTime();
lastInput = key.input;
lastModifierFlags = key.modifierFlags;
return;
}
}
} else {
// Second keyevent within 0.2 second,
// with the same key as the first one.
if (CACurrentMediaTime() - lastCommand < 0.2 &&
lastInput == key.input &&
lastModifierFlags == key.modifierFlags) {
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
if ([command.keyCommand.input isEqualToString:key.input] &&
command.keyCommand.modifierFlags == key.modifierFlags &&
command.block) {
// NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
// method gets called repeatedly if the command key is held down.
if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastDoubleCommand > 0.5) {
command.block(key);
lastDoubleCommand = CACurrentMediaTime();
}
firstPress = YES;
return;
}
}
}
lastCommand = CACurrentMediaTime();
lastInput = key.input;
lastModifierFlags = key.modifierFlags;
}
}
@end
@implementation UIApplication (RCTKeyCommands)
@ -118,6 +216,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if (action == @selector(RCT_handleKeyCommand:)) {
[self RCT_handleKeyCommand:sender];
return YES;
} else if (action == @selector(RCT_handleDoublePressKeyCommand:)) {
[self RCT_handleDoublePressKeyCommand:sender];
return YES;
}
return [self RCT_sendAction:action to:target from:sender forEvent:event];
}
@ -218,6 +319,58 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return NO;
}
- (void)registerDoublePressKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block
{
RCTAssertMainThread();
if (input.length && flags && RCTIsIOS8OrEarlier()) {
// Workaround around the first cmd not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This ensures that
// command-key modified commands will work first time. Fixed in iOS 9.
[self registerDoublePressKeyCommandWithInput:@""
modifierFlags:flags
action:nil];
}
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
modifierFlags:flags
action:@selector(RCT_handleDoublePressKeyCommand:)];
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
[_commands removeObject:keyCommand];
[_commands addObject:keyCommand];
}
- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
{
RCTAssertMainThread();
for (RCTKeyCommand *command in _commands.allObjects) {
if ([command matchesInput:input flags:flags]) {
[_commands removeObject:command];
break;
}
}
}
- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
{
RCTAssertMainThread();
for (RCTKeyCommand *command in _commands) {
if ([command matchesInput:input flags:flags]) {
return YES;
}
}
return NO;
}
@end
#else
@ -242,6 +395,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return NO;
}
- (void)registerDoublePressKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block {}
- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags {}
- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
{
return NO;
}
@end
#endif