Skip to content

Commit

Permalink
Merge pull request #13215 from dnlfrst/fix-theme-color-modal-backdrop
Browse files Browse the repository at this point in the history
Adapt status bar color to modal backdrop
  • Loading branch information
tgolen committed Dec 7, 2022
2 parents cb9a541 + 93d0076 commit 3e050bd
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 6 deletions.
16 changes: 16 additions & 0 deletions src/components/CustomStatusBar/index.web.js
@@ -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);

// For mobile web browsers, match the default status bar color to the app's background color
document.querySelector('meta[name=theme-color]').content = themeColors.appBG;
}

render() {
return <StatusBar />;
}
}
43 changes: 43 additions & 0 deletions src/components/Modal/index.web.js
@@ -0,0 +1,43 @@
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();
};

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
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
Expand Up @@ -59,6 +59,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
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

0 comments on commit 3e050bd

Please sign in to comment.