* Store insets in ephemeral store * Address PR review
This commit is contained in:
parent
dc3adcc74f
commit
5aee08e85e
4 changed files with 232 additions and 22 deletions
|
|
@ -3,15 +3,17 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Keyboard, NativeModules, View} from 'react-native';
|
||||
import {Dimensions, Keyboard, NativeModules, View} from 'react-native';
|
||||
import SafeArea from 'react-native-safe-area';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
const {StatusBarManager} = NativeModules;
|
||||
const {PORTRAIT, LANDSCAPE} = ViewTypes;
|
||||
|
||||
export default class SafeAreaIos extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -57,8 +59,12 @@ export default class SafeAreaIos extends PureComponent {
|
|||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
|
||||
SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
Dimensions.addEventListener('change', this.getSafeAreaInsets);
|
||||
EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets);
|
||||
if (EphemeralStore.safeAreaInsets[PORTRAIT] === null || EphemeralStore.safeAreaInsets[LANDSCAPE] === null) {
|
||||
SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
}
|
||||
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
|
||||
|
||||
|
|
@ -66,13 +72,39 @@ export default class SafeAreaIos extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
Dimensions.removeEventListener('change', this.getSafeAreaInsets);
|
||||
EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets);
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
getSafeAreaInsets = async (dimensions) => {
|
||||
this.getStatusBarHeight();
|
||||
|
||||
const safeAreaInsetsStored = EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null;
|
||||
if ((DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) && !safeAreaInsetsStored) {
|
||||
const window = dimensions?.window || Dimensions.get('window');
|
||||
const landscape = window.width > window.length;
|
||||
const {safeAreaInsets} = await SafeArea.getSafeAreaInsetsForRootView();
|
||||
this.setSafeAreaInsets(safeAreaInsets, landscape);
|
||||
}
|
||||
}
|
||||
|
||||
setSafeAreaInsets = (safeAreaInsets, landscape) => {
|
||||
const orientation = landscape ? LANDSCAPE : PORTRAIT;
|
||||
if (EphemeralStore.safeAreaInsets[orientation] === null) {
|
||||
EphemeralStore.safeAreaInsets[orientation] = safeAreaInsets;
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
safeAreaInsets: EphemeralStore.safeAreaInsets[orientation],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBarHeight = () => {
|
||||
try {
|
||||
StatusBarManager.getHeight(
|
||||
|
|
@ -87,26 +119,18 @@ export default class SafeAreaIos extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
getSafeAreaInsets = () => {
|
||||
this.getStatusBarHeight();
|
||||
onSafeAreaInsetsForRootViewChange = ({safeAreaInsets}) => {
|
||||
if (EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null) {
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) {
|
||||
SafeArea.getSafeAreaInsetsForRootView().then((result) => {
|
||||
const {safeAreaInsets} = result;
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({safeAreaInsets});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSafeAreaInsetsForRootViewChange = (result) => {
|
||||
const {safeAreaInsets} = result;
|
||||
|
||||
if (this.mounted && (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets)) {
|
||||
this.getStatusBarHeight();
|
||||
this.setState({safeAreaInsets});
|
||||
|
||||
const {width, height} = Dimensions.get('window');
|
||||
const landscape = width > height;
|
||||
this.setSafeAreaInsets(safeAreaInsets, landscape);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ import {shallow} from 'enzyme';
|
|||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
import SafeAreaIos from './safe_area_view.ios';
|
||||
|
||||
const {PORTRAIT, LANDSCAPE} = ViewTypes;
|
||||
|
||||
describe('SafeAreaIos', () => {
|
||||
const baseProps = {
|
||||
children: [],
|
||||
|
|
@ -37,10 +40,44 @@ describe('SafeAreaIos', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const PORTRAIT_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 111,
|
||||
left: 111,
|
||||
bottom: 111,
|
||||
right: 111,
|
||||
},
|
||||
};
|
||||
|
||||
const LANDSCAPE_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 222,
|
||||
left: 222,
|
||||
bottom: 222,
|
||||
right: 222,
|
||||
},
|
||||
};
|
||||
|
||||
const IGNORED_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 333,
|
||||
left: 333,
|
||||
bottom: 333,
|
||||
right: 333,
|
||||
},
|
||||
};
|
||||
|
||||
SafeArea.getSafeAreaInsetsForRootView = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve(TEST_INSETS_1);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
EphemeralStore.safeAreaInsets = {
|
||||
[PORTRAIT]: null,
|
||||
[LANDSCAPE]: null,
|
||||
};
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
|
|
@ -148,4 +185,144 @@ describe('SafeAreaIos', () => {
|
|||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
const landscape = false;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(PORTRAIT_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets from EphemeralStore', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = PORTRAIT_INSETS.safeAreaInsets;
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
|
||||
const landscape = false;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
const landscape = true;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(LANDSCAPE_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets from EphemeralStore', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = LANDSCAPE_INSETS.safeAreaInsets;
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
|
||||
const landscape = true;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should add safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are not set', () => {
|
||||
const addEventListener = jest.spyOn(SafeArea, 'addEventListener');
|
||||
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
let wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
let instance = wrapper.instance();
|
||||
expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
addEventListener.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
addEventListener.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
expect(addEventListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should remove safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are set', () => {
|
||||
const removeEventListener = jest.spyOn(SafeArea, 'removeEventListener');
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).not.toHaveBeenCalled();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).not.toHaveBeenCalled();
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
});
|
||||
|
||||
test('getSafeAreaInsets should set safe area insets when not already in ephemeral store', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
const setSafeAreaInsets = jest.spyOn(instance, 'setSafeAreaInsets');
|
||||
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
await instance.getSafeAreaInsets();
|
||||
expect(setSafeAreaInsets).toHaveBeenCalled();
|
||||
setSafeAreaInsets.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
await instance.getSafeAreaInsets();
|
||||
expect(setSafeAreaInsets).toHaveBeenCalled();
|
||||
setSafeAreaInsets.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
await instance.getSafeAreaInsets();
|
||||
expect(setSafeAreaInsets).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ const ViewTypes = keyMirror({
|
|||
SELECTED_ACTION_MENU: null,
|
||||
SUBMIT_ATTACHMENT_MENU_ACTION: null,
|
||||
SELECT_CHANNEL_WITH_MEMBER: null,
|
||||
|
||||
PORTRAIT: null,
|
||||
LANDSCAPE: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
class EphemeralStore {
|
||||
constructor() {
|
||||
this.appStarted = false;
|
||||
|
|
@ -9,6 +11,10 @@ class EphemeralStore {
|
|||
this.navigationComponentIdStack = [];
|
||||
this.allNavigationComponentIds = [];
|
||||
this.currentServerUrl = null;
|
||||
this.safeAreaInsets = {
|
||||
[ViewTypes.PORTRAIT]: null,
|
||||
[ViewTypes.LANDSCAPE]: null,
|
||||
};
|
||||
}
|
||||
|
||||
getNavigationTopComponentId = () => this.navigationComponentIdStack[0];
|
||||
|
|
|
|||
Loading…
Reference in a new issue