Fix android keyboard area when switching apps (#8554)

This commit is contained in:
Elias Nahum 2025-02-05 08:12:57 +08:00 committed by GitHub
parent c8f42ee67e
commit 762bbabfa0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 141 additions and 32 deletions

View file

@ -0,0 +1,98 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNUtils from '@mattermost/rnutils';
import {renderHook} from '@testing-library/react-hooks';
import {Keyboard} from 'react-native';
import {Navigation} from 'react-native-navigation';
import {useAndroidAdjustSoftKeyboard} from '.'; // Update with the correct path
jest.mock('react-native', () => ({
Keyboard: {
isVisible: jest.fn(),
dismiss: jest.fn(),
},
Platform: {
OS: 'android',
select: jest.fn((options) => options.android ?? options.default),
},
}));
jest.mock('react-native-navigation', () => {
const registerComponentListenerMock = jest.fn();
return {
Navigation: {
events: jest.fn(() => ({
registerComponentListener: registerComponentListenerMock,
})),
},
};
});
jest.mock('@mattermost/rnutils', () => ({
setSoftKeyboardToAdjustNothing: jest.fn(),
setSoftKeyboardToAdjustResize: jest.fn(),
}));
describe('useAndroidAdjustSoftKeyboard', () => {
let registerComponentListenerMock: jest.SpyInstance;
const unsubscribeMock = {remove: jest.fn()};
beforeEach(() => {
jest.useFakeTimers();
const events = Navigation.events();
registerComponentListenerMock = jest.spyOn(events, 'registerComponentListener');
registerComponentListenerMock.mockReturnValue(unsubscribeMock);
});
afterEach(() => {
jest.clearAllMocks();
jest.useRealTimers();
});
it('should register component listener and handle lifecycle events', () => {
renderHook(() => useAndroidAdjustSoftKeyboard('Channel'));
expect(registerComponentListenerMock).toHaveBeenCalledWith(
expect.objectContaining({
componentDidAppear: expect.any(Function),
componentDidDisappear: expect.any(Function),
}),
'Channel',
);
const listener = registerComponentListenerMock.mock.calls[0][0];
jest.mocked(Keyboard.isVisible).mockReturnValue(true);
listener.componentDidAppear();
expect(Keyboard.dismiss).toHaveBeenCalled();
jest.runAllTimers();
expect(RNUtils.setSoftKeyboardToAdjustNothing).toHaveBeenCalled();
listener.componentDidDisappear();
expect(RNUtils.setSoftKeyboardToAdjustResize).toHaveBeenCalled();
});
it('should register component listener and handle lifecycle events when the screen is undefined', () => {
renderHook(() => useAndroidAdjustSoftKeyboard(undefined));
expect(registerComponentListenerMock).not.toHaveBeenCalledWith(
expect.objectContaining({
componentDidAppear: expect.any(Function),
componentDidDisappear: expect.any(Function),
}),
undefined,
);
});
it('should clean up on unmount', () => {
const {unmount} = renderHook(() => useAndroidAdjustSoftKeyboard('Channel'));
unmount();
expect(unsubscribeMock.remove).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNUtils from '@mattermost/rnutils';
import {useEffect, useRef} from 'react';
import {Keyboard} from 'react-native';
import {Navigation, type EventSubscription} from 'react-native-navigation';
import type {AvailableScreens} from '@typings/screens/navigation';
export function useAndroidAdjustSoftKeyboard(screen?: AvailableScreens) {
const timeout = useRef<NodeJS.Timeout>();
useEffect(() => {
let unsubscribe: EventSubscription;
const listener = {
componentDidAppear: () => {
if (Keyboard.isVisible()) {
Keyboard.dismiss();
}
timeout.current = setTimeout(() => {
RNUtils.setSoftKeyboardToAdjustNothing();
}, 10);
},
componentDidDisappear: () => {
RNUtils.setSoftKeyboardToAdjustResize();
},
};
if (screen) {
unsubscribe = Navigation.events().registerComponentListener(listener, screen);
}
return () => {
clearTimeout(timeout.current);
unsubscribe?.remove();
};
}, []);
}

View file

@ -1,10 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNUtils from '@mattermost/rnutils';
import React, {useCallback, useEffect, useState} from 'react';
import {type LayoutChangeEvent, StyleSheet, View} from 'react-native';
import {Navigation} from 'react-native-navigation';
import {type Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
import {storeLastViewedChannelIdAndServer, removeLastViewedChannelIdAndServer} from '@actions/app/global';
@ -12,6 +10,7 @@ import FloatingCallContainer from '@calls/components/floating_call_container';
import FreezeScreen from '@components/freeze_screen';
import PostDraft from '@components/post_draft';
import {ExtraKeyboardProvider} from '@context/extra_keyboard';
import {useAndroidAdjustSoftKeyboard} from '@hooks/android_adjust_soft_keyboard';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import {useChannelSwitch} from '@hooks/channel_switch';
import {useIsTablet} from '@hooks/device';
@ -80,20 +79,7 @@ const Channel = ({
}, [componentId]);
useAndroidHardwareBackHandler(componentId, handleBack);
useEffect(() => {
const listener = {
componentDidAppear: () => {
RNUtils.setSoftKeyboardToAdjustNothing();
},
componentDidDisappear: () => {
RNUtils.setSoftKeyboardToAdjustResize();
},
};
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId!);
return () => unsubscribe.remove();
}, []);
useAndroidAdjustSoftKeyboard(componentId);
const marginTop = defaultHeight + (isTablet ? 0 : -insets.top);
useEffect(() => {

View file

@ -1,11 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNUtils from '@mattermost/rnutils';
import {uniqueId} from 'lodash';
import React, {useCallback, useEffect, useState} from 'react';
import {type LayoutChangeEvent, StyleSheet, View} from 'react-native';
import {Navigation} from 'react-native-navigation';
import {type Edge, SafeAreaView} from 'react-native-safe-area-context';
import {storeLastViewedThreadIdAndServer, removeLastViewedThreadIdAndServer} from '@actions/app/global';
@ -15,6 +13,7 @@ import PostDraft from '@components/post_draft';
import RoundedHeaderContext from '@components/rounded_header_context';
import {Screens} from '@constants';
import {ExtraKeyboardProvider} from '@context/extra_keyboard';
import {useAndroidAdjustSoftKeyboard} from '@hooks/android_adjust_soft_keyboard';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import useDidUpdate from '@hooks/did_update';
import {popTopScreen, setButtons} from '@screens/navigation';
@ -58,20 +57,7 @@ const Thread = ({
}, [componentId]);
useAndroidHardwareBackHandler(componentId, close);
useEffect(() => {
const listener = {
componentDidAppear: () => {
RNUtils.setSoftKeyboardToAdjustNothing();
},
componentDidDisappear: () => {
RNUtils.setSoftKeyboardToAdjustResize();
},
};
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId!);
return () => unsubscribe.remove();
}, []);
useAndroidAdjustSoftKeyboard(componentId);
useEffect(() => {
if (isCRTEnabled && rootId) {