// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {Dimensions, Keyboard, NativeModules, View} from 'react-native'; import SafeArea from 'react-native-safe-area'; import EventEmitter from '@mm-redux/utils/event_emitter'; 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 = { backgroundColor: PropTypes.string, children: PropTypes.node.isRequired, excludeHeader: PropTypes.bool, excludeFooter: PropTypes.bool, footerColor: PropTypes.string, footerComponent: PropTypes.node, forceTop: PropTypes.number, keyboardOffset: PropTypes.number.isRequired, navBarBackgroundColor: PropTypes.string, headerComponent: PropTypes.node, theme: PropTypes.object.isRequired, useLandscapeMargin: PropTypes.bool.isRequired, }; static defaultProps = { keyboardOffset: 0, useLandscapeMargin: false, }; constructor(props) { super(props); let insetBottom = 0; if ((DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) && props.excludeFooter) { insetBottom = 20; } this.state = { keyboard: false, safeAreaInsets: { top: DeviceTypes.IS_IPHONE_WITH_INSETS ? 44 : 20, left: 0, bottom: insetBottom, right: 0, }, statusBarHeight: 20, }; } componentDidMount() { this.mounted = true; 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); this.getSafeAreaInsets(); } componentWillUnmount() { 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(); if (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) { const window = dimensions?.window || Dimensions.get('window'); const orientation = window.width > window.height ? LANDSCAPE : PORTRAIT; const {safeAreaInsets} = await SafeArea.getSafeAreaInsetsForRootView(); this.setSafeAreaInsets(safeAreaInsets, orientation); } } setSafeAreaInsets = (safeAreaInsets, orientation) => { if (EphemeralStore.safeAreaInsets[orientation] === null) { EphemeralStore.safeAreaInsets[orientation] = safeAreaInsets; } if (this.mounted) { this.setState({ safeAreaInsets: EphemeralStore.safeAreaInsets[orientation], }); } } getStatusBarHeight = () => { try { StatusBarManager.getHeight( (statusBarFrameData) => { if (this.mounted) { this.setState({statusBarHeight: statusBarFrameData.height}); } }, ); } catch (e) { // not needed } }; 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) { this.getStatusBarHeight(); const {width, height} = Dimensions.get('window'); const orientation = width > height ? LANDSCAPE : PORTRAIT; this.setSafeAreaInsets(safeAreaInsets, orientation); } } keyboardWillHide = () => { this.setState({keyboard: false}); }; keyboardWillShow = () => { this.setState({keyboard: true}); }; renderTopBar = () => { const {safeAreaInsets, statusBarHeight} = this.state; const {headerComponent, excludeHeader, forceTop, navBarBackgroundColor, theme} = this.props; const hideTopBar = excludeHeader || !statusBarHeight; if (hideTopBar) { return null; } let topColor = theme.sidebarHeaderBg; if (navBarBackgroundColor) { topColor = navBarBackgroundColor; } let top = safeAreaInsets.top; if (forceTop && DeviceTypes.IS_IPHONE_WITH_INSETS && !hideTopBar) { top = forceTop; } if (headerComponent) { return ( {headerComponent} ); } return ( ); }; render() { const {backgroundColor, children, excludeFooter, footerColor, footerComponent, keyboardOffset, theme, useLandscapeMargin} = this.props; const {keyboard, safeAreaInsets} = this.state; let bgColor = theme.centerChannelBg; if (backgroundColor) { bgColor = backgroundColor; } let bottomColor = theme.centerChannelBg; if (footerColor) { bottomColor = footerColor; } let offset = 0; if (keyboardOffset && mattermostManaged.hasSafeAreaInsets) { offset = keyboardOffset; } let bottomInset = safeAreaInsets.bottom; if (excludeFooter) { bottomInset = 0; } return ( {this.renderTopBar()} {children} {footerComponent} ); } }