From 05f35c296d91d946acf4edd94106fbdd0dd69a29 Mon Sep 17 00:00:00 2001 From: nossbigg Date: Fri, 1 Feb 2019 14:15:58 -0800 Subject: [PATCH] Expose isLocalUserInfoKey to keyboard event notifications (#23245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Given two apps loaded side-by-side and when a `Keyboard` event is triggered, there is no way to ascertain which app triggered the keyboard event. This ambiguity can arise in slide over/split view scenarios. This pull request exposes the `isLocalUserInfoKey` property of the native `UIKeyboard` iOS events to the `Keyboard` event listener; this property will return `true` for the app that triggered the keyboard event. (Also, I threw in a couple of Keyboard.js tests just for fun 😅) [iOS][Added] - Expose isLocalUserInfoKey to keyboard event notifications 1. Load two apps side-by-side, with the app on the left side subscribing to the keyboard events (and logging out the events as they happen) 1. Trigger a keyboard to appear with the left app. The logged keyboard event will contain the `isEventFromThisApp` property which will be true. 1. Dismiss the keyboard 1. Trigger a keyboard to appear with the right app. The left app will still log the keyboard event, but the event's `isEventFromThisApp` property will be false (because the left app didn't trigger the keyboard event) Pull Request resolved: https://github.com/facebook/react-native/pull/23245 Differential Revision: D13928612 Pulled By: hramos fbshipit-source-id: 6d74d2565e2af62328485fd9da86f15f9e2ccfab --- Libraries/Components/Keyboard/Keyboard.js | 1 + .../Keyboard/__tests__/Keyboard-test.js | 93 +++++++++++++++++++ React/Modules/RCTKeyboardObserver.m | 2 + 3 files changed, 96 insertions(+) create mode 100644 Libraries/Components/Keyboard/__tests__/Keyboard-test.js diff --git a/Libraries/Components/Keyboard/Keyboard.js b/Libraries/Components/Keyboard/Keyboard.js index 4cd19ca398139d..6930a0578fbc12 100644 --- a/Libraries/Components/Keyboard/Keyboard.js +++ b/Libraries/Components/Keyboard/Keyboard.js @@ -37,6 +37,7 @@ export type KeyboardEvent = $ReadOnly<{| easing?: string, endCoordinates: ScreenRect, startCoordinates?: ScreenRect, + isEventFromThisApp?: boolean, |}>; type KeyboardEventListener = (e: KeyboardEvent) => void; diff --git a/Libraries/Components/Keyboard/__tests__/Keyboard-test.js b/Libraries/Components/Keyboard/__tests__/Keyboard-test.js new file mode 100644 index 00000000000000..98f8b363521196 --- /dev/null +++ b/Libraries/Components/Keyboard/__tests__/Keyboard-test.js @@ -0,0 +1,93 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + * @emails oncall+react_native + */ + +'use strict'; + +const Keyboard = require('Keyboard'); +const dismissKeyboard = require('dismissKeyboard'); +const LayoutAnimation = require('LayoutAnimation'); + +const NativeEventEmitter = require('NativeEventEmitter'); +const NativeModules = require('NativeModules'); + +jest.mock('LayoutAnimation'); + +describe('Keyboard', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('exposes KeyboardEventEmitter methods', () => { + const KeyboardObserver = NativeModules.KeyboardObserver; + const KeyboardEventEmitter = new NativeEventEmitter(KeyboardObserver); + + // $FlowFixMe + expect(Keyboard._subscriber).toBe(KeyboardEventEmitter._subscriber); + expect(Keyboard._nativeModule).toBe(KeyboardEventEmitter._nativeModule); + }); + + it('uses dismissKeyboard utility', () => { + expect(Keyboard.dismiss).toBe(dismissKeyboard); + }); + + describe('scheduling layout animation', () => { + const scheduleLayoutAnimation = ( + duration: number | null, + easing: string | null, + ): void => Keyboard.scheduleLayoutAnimation({duration, easing}); + + it('triggers layout animation', () => { + scheduleLayoutAnimation(12, 'spring'); + expect(LayoutAnimation.configureNext).toHaveBeenCalledWith({ + duration: 12, + update: { + duration: 12, + type: 'spring', + }, + }); + }); + + it('does not trigger animation when duration is null', () => { + scheduleLayoutAnimation(null, 'spring'); + expect(LayoutAnimation.configureNext).not.toHaveBeenCalled(); + }); + + it('does not trigger animation when duration is 0', () => { + scheduleLayoutAnimation(0, 'spring'); + expect(LayoutAnimation.configureNext).not.toHaveBeenCalled(); + }); + + describe('animation update type', () => { + const assertAnimationUpdateType = type => + expect(LayoutAnimation.configureNext).toHaveBeenCalledWith( + expect.objectContaining({ + duration: expect.anything(), + update: {duration: expect.anything(), type}, + }), + ); + + it('retrieves type from LayoutAnimation', () => { + scheduleLayoutAnimation(12, 'linear'); + assertAnimationUpdateType('linear'); + }); + + it("defaults to 'keyboard' when key in LayoutAnimation is not found", () => { + scheduleLayoutAnimation(12, 'some-unknown-animation-type'); + assertAnimationUpdateType('keyboard'); + }); + + it("defaults to 'keyboard' when easing is null", () => { + scheduleLayoutAnimation(12, null); + assertAnimationUpdateType('keyboard'); + }); + }); + }); +}); diff --git a/React/Modules/RCTKeyboardObserver.m b/React/Modules/RCTKeyboardObserver.m index ea04868cb1064f..999083c160a927 100644 --- a/React/Modules/RCTKeyboardObserver.m +++ b/React/Modules/RCTKeyboardObserver.m @@ -110,12 +110,14 @@ - (void)EVENT:(NSNotification *)notification \ CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + NSInteger isLocalUserInfoKey = [userInfo[UIKeyboardIsLocalUserInfoKey] integerValue]; return @{ @"startCoordinates": RCTRectDictionaryValue(beginFrame), @"endCoordinates": RCTRectDictionaryValue(endFrame), @"duration": @(duration * 1000.0), // ms @"easing": RCTAnimationNameForCurve(curve), + @"isEventFromThisApp": isLocalUserInfoKey == 1 ? @YES : @NO, }; #endif }