mirror of
https://github.com/status-im/react-native.git
synced 2025-01-25 08:48:58 +00:00
18f38ecdc0
Summary:This adds a `HEIGHT` constant on `StatusBar` on Android. I needed only this for now but I will work on a better status bar dimensions API later (see TODO). It also improves the implementation to fix a bug that happened when multiple `StatusBar` components get updated in the same frame as well as remove useless calls to the `StatusBarModule` when values did not change. Instead of calling the `StatusBarManager` immediately when the component gets updated and relying on the order of the calls that get dispatched to native we now wait at the end of the frame to send the calls to the `StatusBarManager` using `setImmediate`. To make this work properly we need to change the data structure of the props stack a little bit to store the desired transition/animation too for each value. Finally this updates the example to only show the ones that work for the current platform. **Test plan** In the UIExplorer Example, in the 'StatusBar dimensions' section it should show 25 for the height of the status bar. A Closes https://github.com/facebook/react-native/pull/6195 Differential Revision: D3017559 fb-gh-sync-id: d6f4c6a72a2dfde83496ecc0f56dca4abaf3055e shipit-source-id: d6f4c6a72a2dfde83496ecc0f56dca4abaf3055e
330 lines
9.9 KiB
JavaScript
330 lines
9.9 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule StatusBar
|
|
* @flow
|
|
*/
|
|
'use strict';
|
|
|
|
const React = require('React');
|
|
const ColorPropType = require('ColorPropType');
|
|
const Platform = require('Platform');
|
|
|
|
const processColor = require('processColor');
|
|
|
|
const StatusBarManager = require('NativeModules').StatusBarManager;
|
|
|
|
export type StatusBarStyle = $Enum<{
|
|
'default': string,
|
|
'light-content': string,
|
|
}>;
|
|
|
|
export type StatusBarAnimation = $Enum<{
|
|
'none': string,
|
|
'fade': string,
|
|
'slide': string,
|
|
}>;
|
|
|
|
type DefaultProps = {
|
|
animated: boolean;
|
|
};
|
|
|
|
/**
|
|
* Merges the prop stack with the default values.
|
|
*/
|
|
function mergePropsStack(propsStack: Array<Object>, defaultValues: Object): Object {
|
|
return propsStack.reduce((prev, cur) => {
|
|
for (let 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 is also an imperative
|
|
* API exposed as static functions on the component. It is however not recommended
|
|
* to use the static API and the compoment for the same prop because any value
|
|
* set by the static API will get overriden by the one set by the component in
|
|
* the next render.
|
|
*/
|
|
const StatusBar = React.createClass({
|
|
statics: {
|
|
_propsStack: [],
|
|
_defaultProps: createStackEntry({
|
|
animated: false,
|
|
showHideTransition: 'fade',
|
|
backgroundColor: 'black',
|
|
barStyle: 'default',
|
|
translucent: false,
|
|
hidden: false,
|
|
networkActivityIndicatorVisible: false,
|
|
}),
|
|
// Timer for updating the native module values at the end of the frame.
|
|
_updateImmediate: null,
|
|
// The current merged values from the props stack.
|
|
_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
|
|
*/
|
|
currentHeight: StatusBarManager.HEIGHT,
|
|
|
|
// Provide an imperative API as static functions of the component.
|
|
// See the corresponding prop for more detail.
|
|
setHidden(hidden: boolean, animation?: StatusBarAnimation) {
|
|
animation = animation || 'none';
|
|
StatusBar._defaultProps.hidden.value = hidden;
|
|
if (Platform.OS === 'ios') {
|
|
StatusBarManager.setHidden(hidden, animation);
|
|
} else if (Platform.OS === 'android') {
|
|
StatusBarManager.setHidden(hidden);
|
|
}
|
|
},
|
|
|
|
setBarStyle(style: StatusBarStyle, animated?: boolean) {
|
|
if (Platform.OS !== 'ios') {
|
|
console.warn('`setBarStyle` is only available on iOS');
|
|
return;
|
|
}
|
|
animated = animated || false;
|
|
StatusBar._defaultProps.barStyle.value = style;
|
|
StatusBarManager.setStyle(style, animated);
|
|
},
|
|
|
|
setNetworkActivityIndicatorVisible(visible: boolean) {
|
|
if (Platform.OS !== 'ios') {
|
|
console.warn('`setNetworkActivityIndicatorVisible` is only available on iOS');
|
|
return;
|
|
}
|
|
StatusBar._defaultProps.networkActivityIndicatorVisible = visible;
|
|
StatusBarManager.setNetworkActivityIndicatorVisible(visible);
|
|
},
|
|
|
|
setBackgroundColor(color, animated?: boolean) {
|
|
if (Platform.OS !== 'android') {
|
|
console.warn('`setBackgroundColor` is only available on Android');
|
|
return;
|
|
}
|
|
animated = animated || false;
|
|
StatusBar._defaultProps.backgroundColor.value = color;
|
|
StatusBarManager.setColor(processColor(color), animated);
|
|
},
|
|
|
|
setTranslucent(translucent: boolean) {
|
|
if (Platform.OS !== 'android') {
|
|
console.warn('`setTranslucent` is only available on Android');
|
|
return;
|
|
}
|
|
StatusBar._defaultProps.translucent = translucent;
|
|
StatusBarManager.setTranslucent(translucent);
|
|
},
|
|
},
|
|
|
|
propTypes: {
|
|
/**
|
|
* If the status bar is hidden.
|
|
*/
|
|
hidden: React.PropTypes.bool,
|
|
/**
|
|
* If the transition between status bar property changes should be animated.
|
|
* Supported for backgroundColor, barStyle and hidden.
|
|
*/
|
|
animated: React.PropTypes.bool,
|
|
/**
|
|
* The background color of the status bar.
|
|
* @platform android
|
|
*/
|
|
backgroundColor: ColorPropType,
|
|
/**
|
|
* 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: React.PropTypes.bool,
|
|
/**
|
|
* Sets the color of the status bar text.
|
|
*
|
|
* @platform ios
|
|
*/
|
|
barStyle: React.PropTypes.oneOf([
|
|
'default',
|
|
'light-content',
|
|
]),
|
|
/**
|
|
* If the network activity indicator should be visible.
|
|
*
|
|
* @platform ios
|
|
*/
|
|
networkActivityIndicatorVisible: React.PropTypes.bool,
|
|
/**
|
|
* The transition effect when showing and hiding the status bar using the `hidden`
|
|
* prop. Defaults to 'fade'.
|
|
*
|
|
* @platform ios
|
|
*/
|
|
showHideTransition: React.PropTypes.oneOf([
|
|
'fade',
|
|
'slide',
|
|
]),
|
|
},
|
|
|
|
getDefaultProps(): DefaultProps {
|
|
return {
|
|
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 hierachy will have priority.
|
|
this._stackEntry = createStackEntry(this.props);
|
|
StatusBar._propsStack.push(this._stackEntry);
|
|
this._updatePropsStack();
|
|
},
|
|
|
|
componentWillUnmount() {
|
|
// When a StatusBar is unmounted, remove itself from the stack and update
|
|
// the native bar with the next props.
|
|
const index = StatusBar._propsStack.indexOf(this._stackEntry);
|
|
StatusBar._propsStack.splice(index, 1);
|
|
|
|
this._updatePropsStack();
|
|
},
|
|
|
|
componentDidUpdate() {
|
|
const index = StatusBar._propsStack.indexOf(this._stackEntry);
|
|
this._stackEntry = createStackEntry(this.props);
|
|
StatusBar._propsStack[index] = this._stackEntry;
|
|
|
|
this._updatePropsStack();
|
|
},
|
|
|
|
/**
|
|
* Updates the native status bar with the props from the stack.
|
|
*/
|
|
_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) {
|
|
StatusBarManager.setStyle(
|
|
mergedProps.barStyle.value,
|
|
mergedProps.barStyle.animated,
|
|
);
|
|
}
|
|
if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
|
|
StatusBarManager.setHidden(
|
|
mergedProps.hidden.value,
|
|
mergedProps.hidden.animated ?
|
|
mergedProps.hidden.transition :
|
|
'none',
|
|
);
|
|
}
|
|
|
|
if (!oldProps || oldProps.networkActivityIndicatorVisible !== mergedProps.networkActivityIndicatorVisible) {
|
|
StatusBarManager.setNetworkActivityIndicatorVisible(
|
|
mergedProps.networkActivityIndicatorVisible
|
|
);
|
|
}
|
|
} else if (Platform.OS === 'android') {
|
|
if (!oldProps || oldProps.backgroundColor.value !== mergedProps.backgroundColor.value) {
|
|
StatusBarManager.setColor(
|
|
processColor(mergedProps.backgroundColor.value),
|
|
mergedProps.backgroundColor.animated,
|
|
);
|
|
}
|
|
if (!oldProps || oldProps.hidden.value !== mergedProps.hidden.value) {
|
|
StatusBarManager.setHidden(mergedProps.hidden.value);
|
|
}
|
|
if (!oldProps || oldProps.translucent !== mergedProps.translucent) {
|
|
StatusBarManager.setTranslucent(mergedProps.translucent);
|
|
}
|
|
}
|
|
// Update the current prop values.
|
|
StatusBar._currentValues = mergedProps;
|
|
});
|
|
},
|
|
|
|
render(): ?ReactElement {
|
|
return null;
|
|
},
|
|
});
|
|
|
|
module.exports = StatusBar;
|