Skip to content

Commit

Permalink
Merge pull request #15778 from janicduplessis/@janic/status-bar-2
Browse files Browse the repository at this point in the history
Make status bar transparent on android
  • Loading branch information
Julesssss authored May 25, 2023
2 parents 7a42755 + a94bc4f commit c53c89b
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 75 deletions.
26 changes: 21 additions & 5 deletions android/app/src/main/java/com/expensify/chat/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import android.os.Bundle;
import android.content.pm.ActivityInfo;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowInsets;

import com.expensify.chat.bootsplash.BootSplash;
import com.expensify.reactnativekeycommand.KeyCommandModule;
import com.facebook.react.ReactActivity;
Expand Down Expand Up @@ -45,6 +48,19 @@ protected void onCreate(Bundle savedInstanceState) {
if (getResources().getBoolean(R.bool.portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

// Sets translucent status bar. This code is based on what the react-native StatusBar
// module does, but we need to do it here to avoid the splash screen jumping on app start.
View decorView = getWindow().getDecorView();
decorView.setOnApplyWindowInsetsListener(
(v, insets) -> {
WindowInsets defaultInsets = v.onApplyWindowInsets(insets);
return defaultInsets.replaceSystemWindowInsets(
defaultInsets.getSystemWindowInsetLeft(),
0,
defaultInsets.getSystemWindowInsetRight(),
defaultInsets.getSystemWindowInsetBottom());
});
}

/**
Expand All @@ -54,26 +70,26 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Disabling hardware ESCAPE support which is handled by Android
if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) {
return false;
if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) {
return false;
}
KeyCommandModule.getInstance().onKeyDownEvent(keyCode, event);
return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
// Disabling hardware ESCAPE support which is handled by Android
if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { return false; }
KeyCommandModule.getInstance().onKeyDownEvent(keyCode, event);
return super.onKeyLongPress(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Disabling hardware ESCAPE support which is handled by Android
if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { return false; }
KeyCommandModule.getInstance().onKeyDownEvent(keyCode, event);
return super.onKeyUp(keyCode, event);
}
}
}
1 change: 0 additions & 1 deletion android/app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<resources>
<color name="bootsplash_background">#03D47C</color>
<color name="status_bar_background">#061B09</color>
<color name="white">#FFFFFF</color>
<color name="accent">#03D47C</color>
<color name="dark">#0b1b34</color>
Expand Down
4 changes: 2 additions & 2 deletions android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<!-- Base application theme. Applied to all Android versions -->
<style name="BaseAppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:colorEdgeEffect">@color/gray4</item>
<item name="android:statusBarColor">@color/status_bar_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="colorAccent">@color/accent</item>
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="popupTheme">@style/AppTheme.Popup</item>
Expand Down Expand Up @@ -71,7 +71,7 @@
</style>

<style name="BootTheme" parent="Theme.SplashScreen">
<item name="android:statusBarColor">@color/status_bar_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/bootsplash_logo</item>
<item name="windowSplashScreenBackground">@color/bootsplash_background</item>
</style>
Expand Down
20 changes: 6 additions & 14 deletions src/components/CustomStatusBar/index.android.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import React from 'react';
import {StatusBar} from 'react-native';
import themeColors from '../../styles/themes/default';

/**
* Only the Android platform supports "setBackgroundColor"
* On Android we setup the status bar in native code.
*/

export default class CustomStatusBar extends React.Component {
componentDidMount() {
StatusBar.setBarStyle('light-content');
StatusBar.setBackgroundColor(themeColors.appBG);
}

render() {
return <StatusBar />;
}
export default function CustomStatusBar() {
// Prefer to not render the StatusBar component in Android as it can cause
// issues with edge to edge display. We setup the status bar appearance in
// MainActivity.java and styles.xml.
return null;
}
8 changes: 3 additions & 5 deletions src/components/Modal/BaseModal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {PureComponent} from 'react';
import {StatusBar, View} from 'react-native';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import ReactNativeModal from 'react-native-modal';
import {SafeAreaInsetsContext} from 'react-native-safe-area-context';
Expand Down Expand Up @@ -127,9 +127,7 @@ class BaseModal extends PureComponent {
hasBackdrop={this.props.fullscreen}
coverScreen={this.props.fullscreen}
style={modalStyle}
// When `statusBarTranslucent` is true on Android, the modal fully covers the status bar.
// Since `windowHeight` doesn't include status bar height, it should be added in the `deviceHeight` calculation.
deviceHeight={this.props.windowHeight + ((this.props.statusBarTranslucent && StatusBar.currentHeight) || 0)}
deviceHeight={this.props.windowHeight}
deviceWidth={this.props.windowWidth}
animationIn={this.props.animationIn || animationIn}
animationOut={this.props.animationOut || animationOut}
Expand All @@ -147,7 +145,7 @@ class BaseModal extends PureComponent {
paddingBottom: safeAreaPaddingBottom,
paddingLeft: safeAreaPaddingLeft,
paddingRight: safeAreaPaddingRight,
} = StyleUtils.getSafeAreaPadding(insets, this.props.statusBarTranslucent);
} = StyleUtils.getSafeAreaPadding(insets);

const modalPaddingStyles = StyleUtils.getModalPaddingStyles({
safeAreaPaddingTop,
Expand Down
3 changes: 1 addition & 2 deletions src/components/SplashScreenHider/index.native.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useCallback, useRef} from 'react';
import PropTypes from 'prop-types';
import {StatusBar, StyleSheet} from 'react-native';
import {StyleSheet} from 'react-native';
import Reanimated, {useSharedValue, withTiming, Easing, useAnimatedStyle, runOnJS} from 'react-native-reanimated';
import BootSplash from '../../libs/BootSplash';
import Logo from '../../../assets/images/new-expensify-dark.svg';
Expand Down Expand Up @@ -64,7 +64,6 @@ const SplashScreenHider = (props) => {
opacityStyle,
{
// Apply negative margins to center the logo on window (instead of screen)
marginTop: -(StatusBar.currentHeight || 0),
marginBottom: -(BootSplash.navigationBarHeight || 0),
},
]}
Expand Down
41 changes: 26 additions & 15 deletions src/components/withWindowDimensions.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable react/no-unused-state */
import React, {forwardRef, createContext} from 'react';
import PropTypes from 'prop-types';
import {Dimensions} from 'react-native';
import {SafeAreaInsetsContext} from 'react-native-safe-area-context';
import getComponentDisplayName from '../libs/getComponentDisplayName';
import variables from '../styles/variables';
import getWindowHeightAdjustment from '../libs/getWindowHeightAdjustment';

const WindowDimensionsContext = createContext(null);
const windowDimensionsPropTypes = {
Expand All @@ -16,7 +17,7 @@ const windowDimensionsPropTypes = {
// Is the window width narrow, like on a mobile device?
isSmallScreenWidth: PropTypes.bool.isRequired,

// Is the window width narrow, like on a tablet device?
// Is the window width medium sized, like on a tablet device?
isMediumScreenWidth: PropTypes.bool.isRequired,

// Is the window width wide, like on a browser or desktop?
Expand All @@ -35,18 +36,12 @@ class WindowDimensionsProvider extends React.Component {
this.onDimensionChange = this.onDimensionChange.bind(this);

const initialDimensions = Dimensions.get('window');
const isSmallScreenWidth = initialDimensions.width <= variables.mobileResponsiveWidthBreakpoint;
const isMediumScreenWidth = initialDimensions.width > variables.mobileResponsiveWidthBreakpoint && initialDimensions.width <= variables.tabletResponsiveWidthBreakpoint;
const isLargeScreenWidth = !isSmallScreenWidth && !isMediumScreenWidth;

this.dimensionsEventListener = null;

this.state = {
windowHeight: initialDimensions.height,
windowWidth: initialDimensions.width,
isSmallScreenWidth,
isMediumScreenWidth,
isLargeScreenWidth,
};
}

Expand All @@ -69,20 +64,36 @@ class WindowDimensionsProvider extends React.Component {
*/
onDimensionChange(newDimensions) {
const {window} = newDimensions;
const isSmallScreenWidth = window.width <= variables.mobileResponsiveWidthBreakpoint;
const isMediumScreenWidth = !isSmallScreenWidth && window.width <= variables.tabletResponsiveWidthBreakpoint;
const isLargeScreenWidth = !isSmallScreenWidth && !isMediumScreenWidth;

this.setState({
windowHeight: window.height,
windowWidth: window.width,
isSmallScreenWidth,
isMediumScreenWidth,
isLargeScreenWidth,
});
}

render() {
return <WindowDimensionsContext.Provider value={this.state}>{this.props.children}</WindowDimensionsContext.Provider>;
return (
<SafeAreaInsetsContext.Consumer>
{(insets) => {
const isSmallScreenWidth = this.state.windowWidth <= variables.mobileResponsiveWidthBreakpoint;
const isMediumScreenWidth = !isSmallScreenWidth && this.state.windowWidth <= variables.tabletResponsiveWidthBreakpoint;
const isLargeScreenWidth = !isSmallScreenWidth && !isMediumScreenWidth;
return (
<WindowDimensionsContext.Provider
value={{
windowHeight: this.state.windowHeight + getWindowHeightAdjustment(insets),
windowWidth: this.state.windowWidth,
isSmallScreenWidth,
isMediumScreenWidth,
isLargeScreenWidth,
}}
>
{this.props.children}
</WindowDimensionsContext.Provider>
);
}}
</SafeAreaInsetsContext.Consumer>
);
}
}

Expand Down
12 changes: 0 additions & 12 deletions src/libs/getSafeAreaPaddingTop/index.android.js

This file was deleted.

9 changes: 0 additions & 9 deletions src/libs/getSafeAreaPaddingTop/index.js

This file was deleted.

4 changes: 4 additions & 0 deletions src/libs/getWindowHeightAdjustment/index.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// On Android the window height does not include the status bar height, so we need to add it manually.
export default function getWindowHeightAdjustment(insets) {
return insets.top;
}
4 changes: 4 additions & 0 deletions src/libs/getWindowHeightAdjustment/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Some platforms need to adjust the window height.
export default function getWindowHeightAdjustment() {
return 0;
}
11 changes: 8 additions & 3 deletions src/pages/signin/SignInPage.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import {View} from 'react-native';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
import {SafeAreaView} from 'react-native-safe-area-context';
import {withSafeAreaInsets} from 'react-native-safe-area-context';
import ONYXKEYS from '../../ONYXKEYS';
import styles from '../../styles/styles';
import compose from '../../libs/compose';
Expand All @@ -19,6 +20,7 @@ import Permissions from '../../libs/Permissions';
import UnlinkLoginForm from './UnlinkLoginForm';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
import * as Localize from '../../libs/Localize';
import * as StyleUtils from '../../styles/StyleUtils';

const propTypes = {
/* Onyx Props */
Expand Down Expand Up @@ -145,7 +147,9 @@ class SignInPage extends Component {
}

return (
<SafeAreaView style={[styles.signInPage]}>
// There is an issue SafeAreaView on Android where wrong insets flicker on app start.
// Can be removed once https://github.com/th3rdwave/react-native-safe-area-context/issues/364 is resolved.
<View style={[styles.signInPage, StyleUtils.getSafeAreaPadding(this.props.insets, 1)]}>
<SignInPageLayout
welcomeHeader={welcomeHeader}
welcomeText={welcomeText}
Expand All @@ -162,7 +166,7 @@ class SignInPage extends Component {
{showResendValidationForm && <ResendValidationForm />}
{showUnlinkLoginForm && <UnlinkLoginForm />}
</SignInPageLayout>
</SafeAreaView>
</View>
);
}
}
Expand All @@ -171,6 +175,7 @@ SignInPage.propTypes = propTypes;
SignInPage.defaultProps = defaultProps;

export default compose(
withSafeAreaInsets,
withLocalize,
withWindowDimensions,
withOnyx({
Expand Down
13 changes: 6 additions & 7 deletions src/styles/StyleUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import colors from './colors';
import positioning from './utilities/positioning';
import styles from './styles';
import * as ReportUtils from '../libs/ReportUtils';
import getSafeAreaPaddingTop from '../libs/getSafeAreaPaddingTop';

const workspaceColorOptions = [
{backgroundColor: colors.blue200, fill: colors.blue700},
Expand Down Expand Up @@ -172,15 +171,15 @@ function getDefaultWorkspaceAvatarColor(workspaceName) {
* Takes safe area insets and returns padding to use for a View
*
* @param {Object} insets
* @param {Boolean} statusBarTranslucent
* @param {Number} [insetsPercentage] - Percentage of the insets to use for sides and bottom padding
* @returns {Object}
*/
function getSafeAreaPadding(insets, statusBarTranslucent) {
function getSafeAreaPadding(insets, insetsPercentage = variables.safeInsertPercentage) {
return {
paddingTop: getSafeAreaPaddingTop(insets, statusBarTranslucent),
paddingBottom: insets.bottom * variables.safeInsertPercentage,
paddingLeft: insets.left * variables.safeInsertPercentage,
paddingRight: insets.right * variables.safeInsertPercentage,
paddingTop: insets.top,
paddingBottom: insets.bottom * insetsPercentage,
paddingLeft: insets.left * insetsPercentage,
paddingRight: insets.right * insetsPercentage,
};
}

Expand Down

0 comments on commit c53c89b

Please sign in to comment.