123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- /**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @format
- * @flow
- */
- 'use strict';
- const Platform = require('../../Utilities/Platform');
- const React = require('react');
- const invariant = require('invariant');
- const processColor = require('../../StyleSheet/processColor');
- import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
- import NativeStatusBarManagerAndroid from './NativeStatusBarManagerAndroid';
- import NativeStatusBarManagerIOS from './NativeStatusBarManagerIOS';
- /**
- * Status bar style
- */
- export type StatusBarStyle = $Keys<{
- /**
- * Default status bar style (dark for iOS, light for Android)
- */
- default: string,
- /**
- * Dark background, white texts and icons
- */
- 'light-content': string,
- /**
- * Light background, dark texts and icons
- */
- 'dark-content': string,
- ...
- }>;
- /**
- * Status bar animation
- */
- export type StatusBarAnimation = $Keys<{
- /**
- * No animation
- */
- none: string,
- /**
- * Fade animation
- */
- fade: string,
- /**
- * Slide animation
- */
- slide: string,
- ...
- }>;
- type AndroidProps = $ReadOnly<{|
- /**
- * The background color of the status bar.
- * @platform android
- */
- backgroundColor?: ?ColorValue,
- /**
- * If the status bar is translucent.
- * When translucent is set to true, the app will draw under the status bar.
- * This is useful when using a semi transparent status bar color.
- *
- * @platform android
- */
- translucent?: ?boolean,
- |}>;
- type IOSProps = $ReadOnly<{|
- /**
- * If the network activity indicator should be visible.
- *
- * @platform ios
- */
- networkActivityIndicatorVisible?: ?boolean,
- /**
- * The transition effect when showing and hiding the status bar using the `hidden`
- * prop. Defaults to 'fade'.
- *
- * @platform ios
- */
- showHideTransition?: ?('fade' | 'slide'),
- |}>;
- type Props = $ReadOnly<{|
- ...AndroidProps,
- ...IOSProps,
- /**
- * If the status bar is hidden.
- */
- hidden?: ?boolean,
- /**
- * If the transition between status bar property changes should be animated.
- * Supported for backgroundColor, barStyle and hidden.
- */
- animated?: ?boolean,
- /**
- * Sets the color of the status bar text.
- */
- barStyle?: ?('default' | 'light-content' | 'dark-content'),
- |}>;
- /**
- * Merges the prop stack with the default values.
- */
- function mergePropsStack(
- propsStack: Array<Object>,
- defaultValues: Object,
- ): Object {
- return propsStack.reduce((prev, cur) => {
- for (const prop in cur) {
- if (cur[prop] != null) {
- prev[prop] = cur[prop];
- }
- }
- return prev;
- }, Object.assign({}, defaultValues));
- }
- /**
- * Returns an object to insert in the props stack from the props
- * and the transition/animation info.
- */
- function createStackEntry(props: any): any {
- return {
- backgroundColor:
- props.backgroundColor != null
- ? {
- value: props.backgroundColor,
- animated: props.animated,
- }
- : null,
- barStyle:
- props.barStyle != null
- ? {
- value: props.barStyle,
- animated: props.animated,
- }
- : null,
- translucent: props.translucent,
- hidden:
- props.hidden != null
- ? {
- value: props.hidden,
- animated: props.animated,
- transition: props.showHideTransition,
- }
- : null,
- networkActivityIndicatorVisible: props.networkActivityIndicatorVisible,
- };
- }
- /**
- * Component to control the app status bar.
- *
- * ### Usage with Navigator
- *
- * It is possible to have multiple `StatusBar` components mounted at the same
- * time. The props will be merged in the order the `StatusBar` components were
- * mounted. One use case is to specify status bar styles per route using `Navigator`.
- *
- * ```
- * <View>
- * <StatusBar
- * backgroundColor="blue"
- * barStyle="light-content"
- * />
- * <Navigator
- * initialRoute={{statusBarHidden: true}}
- * renderScene={(route, navigator) =>
- * <View>
- * <StatusBar hidden={route.statusBarHidden} />
- * ...
- * </View>
- * }
- * />
- * </View>
- * ```
- *
- * ### Imperative API
- *
- * For cases where using a component is not ideal, there are static methods
- * to manipulate the `StatusBar` display stack. These methods have the same
- * behavior as mounting and unmounting a `StatusBar` component.
- *
- * For example, you can call `StatusBar.pushStackEntry` to update the status bar
- * before launching a third-party native UI component, and then call
- * `StatusBar.popStackEntry` when completed.
- *
- * ```
- * const openThirdPartyBugReporter = async () => {
- * // The bug reporter has a dark background, so we push a new status bar style.
- * const stackEntry = StatusBar.pushStackEntry({barStyle: 'light-content'});
- *
- * // `open` returns a promise that resolves when the UI is dismissed.
- * await BugReporter.open();
- *
- * // Don't forget to call `popStackEntry` when you're done.
- * StatusBar.popStackEntry(stackEntry);
- * };
- * ```
- *
- * There is a legacy imperative API that enables you to manually update the
- * status bar styles. However, the legacy API does not update the internal
- * `StatusBar` display stack, which means that any changes will be overridden
- * whenever a `StatusBar` component is mounted or unmounted.
- *
- * It is strongly advised that you use `pushStackEntry`, `popStackEntry`, or
- * `replaceStackEntry` instead of the static methods beginning with `set`.
- *
- * ### Constants
- *
- * `currentHeight` (Android only) The height of the status bar.
- */
- class StatusBar extends React.Component<Props> {
- static _propsStack = [];
- static _defaultProps = createStackEntry({
- animated: false,
- showHideTransition: 'fade',
- backgroundColor:
- Platform.OS === 'android'
- ? NativeStatusBarManagerAndroid.getConstants()
- .DEFAULT_BACKGROUND_COLOR ?? 'black'
- : 'black',
- barStyle: 'default',
- translucent: false,
- hidden: false,
- networkActivityIndicatorVisible: false,
- });
- // Timer for updating the native module values at the end of the frame.
- static _updateImmediate = null;
- // The current merged values from the props stack.
- static _currentValues = null;
- // TODO(janic): Provide a real API to deal with status bar height. See the
- // discussion in #6195.
- /**
- * The current height of the status bar on the device.
- *
- * @platform android
- */
- static currentHeight: ?number =
- Platform.OS === 'android'
- ? NativeStatusBarManagerAndroid.getConstants().HEIGHT
- : null;
- // Provide an imperative API as static functions of the component.
- // See the corresponding prop for more detail.
- /**
- * Show or hide the status bar
- * @param hidden Hide the status bar.
- * @param animation Optional animation when
- * changing the status bar hidden property.
- */
- static setHidden(hidden: boolean, animation?: StatusBarAnimation) {
- animation = animation || 'none';
- StatusBar._defaultProps.hidden.value = hidden;
- if (Platform.OS === 'ios') {
- NativeStatusBarManagerIOS.setHidden(hidden, animation);
- } else if (Platform.OS === 'android') {
- NativeStatusBarManagerAndroid.setHidden(hidden);
- }
- }
- /**
- * Set the status bar style
- * @param style Status bar style to set
- * @param animated Animate the style change.
- */
- static setBarStyle(style: StatusBarStyle, animated?: boolean) {
- animated = animated || false;
- StatusBar._defaultProps.barStyle.value = style;
- if (Platform.OS === 'ios') {
- NativeStatusBarManagerIOS.setStyle(style, animated);
- } else if (Platform.OS === 'android') {
- NativeStatusBarManagerAndroid.setStyle(style);
- }
- }
- /**
- * Control the visibility of the network activity indicator
- * @param visible Show the indicator.
- */
- static setNetworkActivityIndicatorVisible(visible: boolean) {
- if (Platform.OS !== 'ios') {
- console.warn(
- '`setNetworkActivityIndicatorVisible` is only available on iOS',
- );
- return;
- }
- StatusBar._defaultProps.networkActivityIndicatorVisible = visible;
- NativeStatusBarManagerIOS.setNetworkActivityIndicatorVisible(visible);
- }
- /**
- * Set the background color for the status bar
- * @param color Background color.
- * @param animated Animate the style change.
- */
- static setBackgroundColor(color: string, animated?: boolean) {
- if (Platform.OS !== 'android') {
- console.warn('`setBackgroundColor` is only available on Android');
- return;
- }
- animated = animated || false;
- StatusBar._defaultProps.backgroundColor.value = color;
- const processedColor = processColor(color);
- if (processedColor == null) {
- console.warn(
- `\`StatusBar.setBackgroundColor\`: Color ${color} parsed to null or undefined`,
- );
- return;
- }
- invariant(
- typeof processedColor === 'number',
- 'Unexpected color given for StatusBar.setBackgroundColor',
- );
- NativeStatusBarManagerAndroid.setColor(processedColor, animated);
- }
- /**
- * Control the translucency of the status bar
- * @param translucent Set as translucent.
- */
- static setTranslucent(translucent: boolean) {
- if (Platform.OS !== 'android') {
- console.warn('`setTranslucent` is only available on Android');
- return;
- }
- StatusBar._defaultProps.translucent = translucent;
- NativeStatusBarManagerAndroid.setTranslucent(translucent);
- }
- /**
- * Push a StatusBar entry onto the stack.
- * The return value should be passed to `popStackEntry` when complete.
- *
- * @param props Object containing the StatusBar props to use in the stack entry.
- */
- static pushStackEntry(props: any): any {
- const entry = createStackEntry(props);
- StatusBar._propsStack.push(entry);
- StatusBar._updatePropsStack();
- return entry;
- }
- /**
- * Pop a StatusBar entry from the stack.
- *
- * @param entry Entry returned from `pushStackEntry`.
- */
- static popStackEntry(entry: any) {
- const index = StatusBar._propsStack.indexOf(entry);
- if (index !== -1) {
- StatusBar._propsStack.splice(index, 1);
- }
- StatusBar._updatePropsStack();
- }
- /**
- * Replace an existing StatusBar stack entry with new props.
- *
- * @param entry Entry returned from `pushStackEntry` to replace.
- * @param props Object containing the StatusBar props to use in the replacement stack entry.
- */
- static replaceStackEntry(entry: any, props: any): any {
- const newEntry = createStackEntry(props);
- const index = StatusBar._propsStack.indexOf(entry);
- if (index !== -1) {
- StatusBar._propsStack[index] = newEntry;
- }
- StatusBar._updatePropsStack();
- return newEntry;
- }
- static defaultProps: {|
- animated: boolean,
- showHideTransition: $TEMPORARY$string<'fade'>,
- |} = {
- animated: false,
- showHideTransition: 'fade',
- };
- _stackEntry = null;
- componentDidMount() {
- // Every time a StatusBar component is mounted, we push it's prop to a stack
- // and always update the native status bar with the props from the top of then
- // stack. This allows having multiple StatusBar components and the one that is
- // added last or is deeper in the view hierarchy will have priority.
- this._stackEntry = StatusBar.pushStackEntry(this.props);
- }
- componentWillUnmount() {
- // When a StatusBar is unmounted, remove itself from the stack and update
- // the native bar with the next props.
- StatusBar.popStackEntry(this._stackEntry);
- }
- componentDidUpdate() {
- this._stackEntry = StatusBar.replaceStackEntry(
- this._stackEntry,
- this.props,
- );
- }
- /**
- * Updates the native status bar with the props from the stack.
- */
- static _updatePropsStack = () => {
- // Send the update to the native module only once at the end of the frame.
- clearImmediate(StatusBar._updateImmediate);
- StatusBar._updateImmediate = setImmediate(() => {
- const oldProps = StatusBar._currentValues;
- const mergedProps = mergePropsStack(
- StatusBar._propsStack,
- StatusBar._defaultProps,
- );
- // Update the props that have changed using the merged values from the props stack.
- if (Platform.OS === 'ios') {
- if (
- !oldProps ||
- oldProps.barStyle.value !== mergedProps.barStyle.value
- ) {
- NativeStatusBarManagerIOS.setStyle(
- mergedProps.barStyle.value,
- mergedProps.barStyle.animated || false,
- );
- }
- if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
- NativeStatusBarManagerIOS.setHidden(
- mergedProps.hidden.value,
- mergedProps.hidden.animated
- ? mergedProps.hidden.transition
- : 'none',
- );
- }
- if (
- !oldProps ||
- oldProps.networkActivityIndicatorVisible !==
- mergedProps.networkActivityIndicatorVisible
- ) {
- NativeStatusBarManagerIOS.setNetworkActivityIndicatorVisible(
- mergedProps.networkActivityIndicatorVisible,
- );
- }
- } else if (Platform.OS === 'android') {
- //todo(T60684787): Add back optimization to only update bar style and
- //background color if the new value is different from the old value.
- NativeStatusBarManagerAndroid.setStyle(mergedProps.barStyle.value);
- const processedColor = processColor(mergedProps.backgroundColor.value);
- if (processedColor == null) {
- console.warn(
- `\`StatusBar._updatePropsStack\`: Color ${
- mergedProps.backgroundColor.value
- } parsed to null or undefined`,
- );
- } else {
- invariant(
- typeof processedColor === 'number',
- 'Unexpected color given in StatusBar._updatePropsStack',
- );
- NativeStatusBarManagerAndroid.setColor(
- processedColor,
- mergedProps.backgroundColor.animated,
- );
- }
- if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
- NativeStatusBarManagerAndroid.setHidden(mergedProps.hidden.value);
- }
- if (!oldProps || oldProps.translucent !== mergedProps.translucent) {
- NativeStatusBarManagerAndroid.setTranslucent(mergedProps.translucent);
- }
- }
- // Update the current prop values.
- StatusBar._currentValues = mergedProps;
- });
- };
- render(): React.Node {
- return null;
- }
- }
- module.exports = StatusBar;
|