diff --git a/e2e/Keyboard.test.js b/e2e/Keyboard.test.js index 94b2ea08e3..0a0ba95420 100644 --- a/e2e/Keyboard.test.js +++ b/e2e/Keyboard.test.js @@ -1,18 +1,70 @@ import { default as TestIDs, default as testIDs } from '../playground/src/testIDs'; -import Android from './AndroidUtils'; +import { device } from 'detox'; import Utils from './Utils'; const { elementByLabel, elementById } = Utils; +const KBD_OBSCURED_TEXT = 'Keyboard Demo'; + +const androidDriver = { + async init() { + if (device.getPlatform() !== 'android') { + return; + } + + const { id: adbName } = device; + const { adb } = device.deviceDriver; + + if (!adb || !adbName) { + throw new Error(`Keyboard driver init failed (id=${adbName}, hasADB=${!!adb})`); + } + + this.adb = adb; + this.adbName = adbName; + this.kbdEnabled = await adb.shell(adbName, 'settings get Secure show_ime_with_hard_keyboard'); + + if (!(this.kbdEnabled === '0' || this.kbdEnabled === '1')) { + console.warn('[KbdDriver] Unable to get on-screen KBD setting, defaulting to false'); + this.kbdEnabled = '0'; + } + }, + + async enableOnScreenKeyboard() { + if (!this.adb) { + // Not initialized + return; + } + await this.adb.shell(this.adbName, 'settings put Secure show_ime_with_hard_keyboard 1'); + }, + + async restoreOnScreenKeyboard() { + if (!this.adb) { + // Not initialized + return; + } + await this.adb.shell(this.adbName, `settings put Secure show_ime_with_hard_keyboard ${this.kbdEnabled}`); + }, +} + describe.e2e('Keyboard', () => { + beforeAll(async () => { + await androidDriver.init(); + await androidDriver.enableOnScreenKeyboard(); + }); + + afterAll(async () => { + await androidDriver.restoreOnScreenKeyboard(); + }); + beforeEach(async () => { await device.launchApp({ newInstance: true }); await elementById(TestIDs.KEYBOARD_SCREEN_BTN).tap(); }); it('Push - should close keyboard when Back clicked', async () => { + await expect(elementByLabel(KBD_OBSCURED_TEXT)).toBeVisible(); await elementById(TestIDs.TEXT_INPUT1).tap(); - await expect(elementByLabel('Keyboard Demo')).not.toBeVisible(); + await expect(elementByLabel(KBD_OBSCURED_TEXT)).not.toBeVisible(); await elementById(TestIDs.BACK_BUTTON).tap(); await expect(elementById(testIDs.MAIN_BOTTOM_TABS)).toBeVisible(); }); @@ -20,7 +72,7 @@ describe.e2e('Keyboard', () => { it('Modal - should close keyboard when close clicked', async () => { await elementById(TestIDs.MODAL_BTN).tap(); await elementById(TestIDs.TEXT_INPUT1).tap(); - await expect(elementByLabel('Keyboard Demo')).not.toBeVisible(); + await expect(elementByLabel(KBD_OBSCURED_TEXT)).not.toBeVisible(); await elementById(TestIDs.DISMISS_MODAL_TOPBAR_BTN).tap(); await expect(elementById(testIDs.MAIN_BOTTOM_TABS)).toBeVisible(); }); @@ -46,4 +98,10 @@ describe.e2e('Keyboard', () => { await elementById(TestIDs.MODAL_BTN).tap(); await expect(elementById(TestIDs.TEXT_INPUT1)).not.toBeFocused(); }); + + it(':android: should respect UI with keyboard awareness', async () => { + await elementById(TestIDs.PUSH_KEYBOARD_SCREEN_STICKY_FOOTER).tap(); + await elementById(TestIDs.TEXT_INPUT2).tap(); + await expect(elementByLabel(KBD_OBSCURED_TEXT)).toBeVisible(); + }); }); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java index 78be5f656c..b3deb2cd87 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java @@ -316,10 +316,12 @@ public Animator getPopAnimation(Options appearingOptions, Options disappearingOp @Override protected WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) { Insets sysInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - view.setPaddingRelative(0, 0, 0, sysInsets.bottom); - return WindowInsetsCompat.CONSUMED; - } + Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); + int bottomInset = (imeInsets.bottom > 0) ? 0 : sysInsets.bottom; + view.setPaddingRelative(0, 0, 0, bottomInset); + return insets; + } @RestrictTo(RestrictTo.Scope.TESTS) public BottomTabs getBottomTabs() { diff --git a/playground/src/screens/KeyboardScreen.tsx b/playground/src/screens/KeyboardScreen.tsx index d439e72ef4..a199efaacf 100644 --- a/playground/src/screens/KeyboardScreen.tsx +++ b/playground/src/screens/KeyboardScreen.tsx @@ -1,5 +1,15 @@ import React from 'react'; -import { View, ScrollView, Dimensions, StyleSheet, Image, TextInput, Text } from 'react-native'; +import { + SafeAreaView, + View, + ScrollView, + Dimensions, + StyleSheet, + Image, + TextInput, + Text, + KeyboardAvoidingView, +} from 'react-native'; import { NavigationProps, NavigationComponent, @@ -15,6 +25,7 @@ const KEYBOARD_LABEL = 'Keyboard Demo'; interface Props extends NavigationProps { title?: string; autoFocus?: boolean; + stickyFooter?: boolean; } export default class KeyboardScreen extends NavigationComponent { @@ -34,6 +45,7 @@ export default class KeyboardScreen extends NavigationComponent { }, }; } + constructor(props: Props) { super(props); Navigation.events().bindComponent(this); @@ -50,30 +62,41 @@ export default class KeyboardScreen extends NavigationComponent { } render() { + const FooterRoot = this.props.stickyFooter === true ? KeyboardAvoidingView : View; return ( - +