Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt status bar color to modal backdrop #13215

Merged
merged 13 commits into from
Dec 7, 2022
16 changes: 16 additions & 0 deletions src/components/CustomStatusBar/index.web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import {StatusBar} from 'react-native';
import themeColors from '../../styles/themes/default';

export default class CustomStatusBar extends React.Component {
componentDidMount() {
StatusBar.setBarStyle('light-content', true);

// Match the default status bar color to the app's background color.
dnlfrst marked this conversation as resolved.
Show resolved Hide resolved
document.querySelector('meta[name=theme-color]').content = themeColors.appBG;
}

render() {
return <StatusBar />;
}
}
41 changes: 41 additions & 0 deletions src/components/Modal/index.web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import withWindowDimensions from '../withWindowDimensions';
import BaseModal from './BaseModal';
import {propTypes, defaultProps} from './modalPropTypes';
import * as StyleUtils from '../../styles/StyleUtils';
import themeColors from '../../styles/themes/default';

const Modal = (props) => {
const setStatusBarColor = (color = themeColors.appBG) => {
if (!props.fullscreen) {
return;
}

// Change the color of the status bar to align with the modal's backdrop (refer to https://github.com/Expensify/App/issues/12156).
document.querySelector('meta[name=theme-color]').content = color;
};
const hideModal = () => {
setStatusBarColor();
props.onModalHide();
};
const showModal = () => {
setStatusBarColor(StyleUtils.getThemeBackgroundColor());
props.onModalShow();
};
dnlfrst marked this conversation as resolved.
Show resolved Hide resolved

return (
<BaseModal
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
onModalHide={hideModal}
onModalShow={showModal}
>
{props.children}
</BaseModal>
);
};

Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
Modal.displayName = 'Modal';
export default withWindowDimensions(Modal);
94 changes: 88 additions & 6 deletions src/styles/StyleUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ function getBackgroundColorStyle(backgroundColor) {
};
}

/**
* Converts a color in hexadecimal notation into RGB notation.
*
* @param {String} hexadecimal A color in hexadecimal notation.
* @returns {Array} `undefined` if the input color is not in hexadecimal notation. Otherwise, the RGB components of the input color.
*/
function hexadecimalToRGBArray(hexadecimal) {
const components = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexadecimal);

if (components === null) { return undefined; }

return _.map(components.slice(1), component => parseInt(component, 16));
}

/**
* Returns a background color with opacity style
*
Expand All @@ -210,13 +224,10 @@ function getBackgroundColorStyle(backgroundColor) {
* @returns {Object}
*/
function getBackgroundColorWithOpacityStyle(backgroundColor, opacity) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(backgroundColor);
if (result !== null) {
const r = parseInt(result[1], 16);
const g = parseInt(result[2], 16);
const b = parseInt(result[3], 16);
const result = hexadecimalToRGBArray(backgroundColor);
if (result !== undefined) {
return {
backgroundColor: `rgba(${r}, ${g}, ${b}, ${opacity})`,
backgroundColor: `rgba(${result[0]}, ${result[1]}, ${result[2]}, ${opacity})`,
};
}
return {};
Expand Down Expand Up @@ -448,6 +459,76 @@ function getPaymentMethodMenuWidth(isSmallScreenWidth) {
return {width: !isSmallScreenWidth ? variables.sideBarWidth - (margin * 2) : undefined};
}

/**
* Converts a color in RGBA notation to an equivalent color in RGB notation.
*
* @param {Array} foregroundRGB The three components of the foreground color in RGB notation.
* @param {Array} backgroundRGB The three components of the background color in RGB notation.
* @param {number} opacity The desired opacity of the foreground color.
* @returns {Array} The RGB components of the RGBA color converted to RGB.
*/
function convertRGBAToRGB(foregroundRGB, backgroundRGB, opacity) {
const [foregroundRed, foregroundGreen, foregroundBlue] = foregroundRGB;
const [backgroundRed, backgroundGreen, backgroundBlue] = backgroundRGB;

return [
((1 - opacity) * backgroundRed) + (opacity * foregroundRed),
((1 - opacity) * backgroundGreen) + (opacity * foregroundGreen),
((1 - opacity) * backgroundBlue) + (opacity * foregroundBlue),
];
}

/**
* Converts three unit values to the three components of a color in RGB notation.
*
* @param {number} red A unit value representing the first component of a color in RGB notation.
* @param {number} green A unit value representing the second component of a color in RGB notation.
* @param {number} blue A unit value representing the third component of a color in RGB notation.
* @returns {Array} An array with the three components of a color in RGB notation.
*/
function convertUnitValuesToRGB(red, green, blue) {
return [Math.floor(red * 255), Math.floor(green * 255), Math.floor(blue * 255)];
}

/**
* Converts the three components of a color in RGB notation to three unit values.
*
* @param {number} red The first component of a color in RGB notation.
* @param {number} green The second component of a color in RGB notation.
* @param {number} blue The third component of a color in RGB notation.
* @returns {Array} An array with three unit values representing the components of a color in RGB notation.
*/
function convertRGBToUnitValues(red, green, blue) {
return [red / 255, green / 255, blue / 255];
}

/**
* Determines the theme color for a modal based on the app's background color,
* the modal's backdrop, and the backdrop's opacity.
*
* @returns {String} The theme color as an RGB value.
*/
function getThemeBackgroundColor() {
const backdropOpacity = variables.modalFullscreenBackdropOpacity;

const [backgroundRed, backgroundGreen, backgroundBlue] = hexadecimalToRGBArray(themeColors.appBG);
const [backdropRed, backdropGreen, backdropBlue] = hexadecimalToRGBArray(themeColors.modalBackdrop);
const normalizedBackdropRGB = convertRGBToUnitValues(backdropRed, backdropGreen, backdropBlue);
const normalizedBackgroundRGB = convertRGBToUnitValues(
backgroundRed,
backgroundGreen,
backgroundBlue,
);
const themeRGBNormalized = convertRGBAToRGB(
normalizedBackdropRGB,
normalizedBackgroundRGB,
backdropOpacity,
);
const themeRGB = convertUnitValuesToRGB(...themeRGBNormalized);

return `rgb(${themeRGB.join(', ')})`;
}

/**
* Parse styleParam and return Styles array
* @param {Object|Object[]} styleParam
Expand Down Expand Up @@ -565,6 +646,7 @@ export {
getMiniReportActionContextMenuWrapperStyle,
getKeyboardShortcutsModalWidth,
getPaymentMethodMenuWidth,
getThemeBackgroundColor,
parseStyleAsArray,
combineStyles,
getPaddingLeft,
Expand Down
1 change: 1 addition & 0 deletions src/styles/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default {
emojiLineHeight: 28,
iouAmountTextSize: 40,
mobileResponsiveWidthBreakpoint: 800,
modalFullscreenBackdropOpacity: 0.5,
tabletResponsiveWidthBreakpoint: 1024,
safeInsertPercentage: 0.7,
sideBarWidth: 375,
Expand Down
1 change: 1 addition & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8" />
<title>New Expensify</title>
<meta name="description" content="Corporate cards, reimbursements, receipt scanning, invoicing, and bill pay. One app, all free.">
<meta name="theme-color" content="">
<meta property="twitter:card" content="summary">
<meta property="twitter:site" content="@expensify">
<meta property="og:type" content="website">
Expand Down