Skip to content

Commit 3c6b6aa

Browse files
authored
Merge pull request #42 from uphold-forks/feature/update-status-bar
Update the Android status bar to match the background color
2 parents b9f674a + d39a8af commit 3c6b6aa

File tree

4 files changed

+96
-110
lines changed

4 files changed

+96
-110
lines changed

AppIntro.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import assign from 'assign-deep';
22
import React, { Component, PropTypes } from 'react';
33
import {
4+
StatusBar,
45
StyleSheet,
56
Text,
67
View,
@@ -240,7 +241,7 @@ export default class AppIntro extends Component {
240241
const AnimatedStyle1 = this.getTransform(index, 10, level);
241242
const AnimatedStyle2 = this.getTransform(index, 0, level);
242243
const AnimatedStyle3 = this.getTransform(index, 15, level);
243-
const imgSource = (typeof img === 'string') ? {uri: img} : img;
244+
const imgSource = (typeof img === 'string') ? {uri: img} : img;
244245
const pageView = (
245246
<View style={[this.styles.slide, { backgroundColor }]} showsPagination={false} key={index}>
246247
<Animated.View style={[this.styles.header, ...AnimatedStyle1.transform]}>
@@ -284,6 +285,22 @@ export default class AppIntro extends Component {
284285
return animatedChild;
285286
}
286287

288+
shadeStatusBarColor(color, percent) {
289+
const first = parseInt(color.slice(1), 16);
290+
const black = first & 0x0000FF;
291+
const green = first >> 8 & 0x00FF;
292+
const percentage = percent < 0 ? percent * -1 : percent;
293+
const red = first >> 16;
294+
const theme = percent < 0 ? 0 : 255;
295+
const finalColor = (0x1000000 + (Math.round((theme - red) * percentage) + red) * 0x10000 + (Math.round((theme - green) * percentage) + green) * 0x100 + (Math.round((theme - black) * percentage) + black)).toString(16).slice(1);
296+
297+
return `#${finalColor}`;
298+
}
299+
300+
isToTintStatusBar() {
301+
return this.props.pageArray && this.props.pageArray.length > 0 && Platform.OS === 'android'
302+
}
303+
287304
render() {
288305
const childrens = this.props.children;
289306
const { pageArray } = this.props;
@@ -314,6 +331,11 @@ export default class AppIntro extends Component {
314331
});
315332
}
316333
}
334+
335+
if (this.isToTintStatusBar()) {
336+
StatusBar.setBackgroundColor(this.shadeStatusBarColor(this.props.pageArray[0].backgroundColor, -0.3), false);
337+
}
338+
317339
return (
318340
<View>
319341
{androidPages}
@@ -322,6 +344,10 @@ export default class AppIntro extends Component {
322344
index={this.props.defaultIndex}
323345
renderPagination={this.renderPagination}
324346
onMomentumScrollEnd={(e, state) => {
347+
if (this.isToTintStatusBar()) {
348+
StatusBar.setBackgroundColor(this.shadeStatusBarColor(this.props.pageArray[state.index].backgroundColor, -0.3), false);
349+
}
350+
325351
this.props.onSlideChange(state.index, state.total);
326352
}}
327353
onScroll={Animated.event(

Example/AppIntro.js

Lines changed: 66 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import assign from 'assign-deep';
22
import React, { Component, PropTypes } from 'react';
33
import {
4+
StatusBar,
45
StyleSheet,
56
Text,
67
View,
@@ -11,6 +12,10 @@ import {
1112
Platform,
1213
} from 'react-native';
1314
import Swiper from 'react-native-swiper';
15+
import DoneButton from './components/DoneButton';
16+
import SkipButton from './components/SkipButton';
17+
import RenderDots from './components/Dots';
18+
1419
const windowsWidth = Dimensions.get('window').width;
1520
const windowsHeight = Dimensions.get('window').height;
1621

@@ -62,13 +67,6 @@ const defaulStyles = {
6267
},
6368
activeDotStyle: {
6469
backgroundColor: '#fff',
65-
width: 13,
66-
height: 13,
67-
borderRadius: 7,
68-
marginLeft: 7,
69-
marginRight: 7,
70-
marginTop: 7,
71-
marginBottom: 7,
7270
},
7371
paginationContainer: {
7472
position: 'absolute',
@@ -189,21 +187,6 @@ export default class AppIntro extends Component {
189187
}
190188

191189
renderPagination = (index, total, context) => {
192-
const { activeDotColor, dotColor, rightTextColor, leftTextColor } = this.props;
193-
const ActiveDot = (
194-
<View
195-
style={[this.styles.activeDotStyle, { backgroundColor: activeDotColor }]}
196-
/>
197-
);
198-
const Dot = <View style={[this.styles.dotStyle, { backgroundColor: dotColor }]} />;
199-
let dots = [];
200-
for (let i = 0; i < total; i++) {
201-
dots.push(i === index ?
202-
React.cloneElement(ActiveDot, { key: i })
203-
:
204-
React.cloneElement(Dot, { key: i })
205-
);
206-
}
207190
let isDoneBtnShow;
208191
let isSkipBtnShow;
209192
if (index === total - 1) {
@@ -219,91 +202,31 @@ export default class AppIntro extends Component {
219202
isDoneBtnShow = false;
220203
isSkipBtnShow = true;
221204
}
222-
let controllBts;
223-
if (Platform.OS === 'ios') {
224-
controllBts = (
225-
<View style={this.styles.paginationContainer}>
226-
<Animated.View style={[this.styles.btnContainer, {
227-
opacity: this.state.skipFadeOpacity,
228-
transform: [{
229-
translateX: this.state.skipFadeOpacity.interpolate({
230-
inputRange: [0, 1],
231-
outputRange: [0, 15],
232-
}),
233-
}],
234-
}]}
235-
>
236-
<TouchableOpacity
237-
style={this.styles.full}
238-
onPress={isSkipBtnShow ? () => this.props.onSkipBtnClick(index) : null}
239-
>
240-
<Text style={[this.styles.controllText, { color: leftTextColor }]}>{this.props.skipBtnLabel}</Text>
241-
</TouchableOpacity>
242-
</Animated.View>
243-
<View style={this.styles.dotContainer}>
244-
{dots}
245-
</View>
246-
<View style={this.styles.btnContainer}>
247-
<Animated.View style={[this.styles.full, { height: 0 }, {
248-
opacity: this.state.doneFadeOpacity,
249-
transform: [{
250-
translateX: this.state.skipFadeOpacity.interpolate({
251-
inputRange: [0, 1],
252-
outputRange: [0, 20],
253-
}),
254-
}],
255-
}]}
256-
>
257-
<View style={this.styles.full}>
258-
<Text style={[this.styles.controllText, {
259-
color: rightTextColor, paddingRight: 30,
260-
}]}
261-
>{this.props.doneBtnLabel}</Text>
262-
</View>
263-
</Animated.View>
264-
<Animated.View style={[this.styles.full, { height: 0 }, { opacity: this.state.nextOpacity }]}>
265-
<TouchableOpacity style={this.styles.full}
266-
onPress={ isDoneBtnShow ?
267-
this.props.onDoneBtnClick : this.onNextBtnClick.bind(this, context)}
268-
>
269-
<Text style={[this.styles.nextButtonText, { color: rightTextColor }]}>{this.props.nextBtnLabel}</Text>
270-
</TouchableOpacity>
271-
</Animated.View>
272-
</View>
273-
</View>
274-
);
275-
} else {
276-
controllBts = (
277-
<View style={this.styles.paginationContainer}>
278-
<View style={[this.styles.btnContainer, {
279-
paddingBottom: 5,
280-
opacity: isSkipBtnShow ? 1 : 0,
281-
}]}
282-
>
283-
<TouchableOpacity
284-
style={this.styles.full}
285-
onPress={isSkipBtnShow ? () => this.props.onSkipBtnClick(index) : null}
286-
>
287-
<Text style={[this.styles.controllText, { color: leftTextColor }]}>{this.props.skipBtnLabel}</Text>
288-
</TouchableOpacity>
289-
</View>
290-
<View style={this.styles.dotContainer}>
291-
{dots}
292-
</View>
293-
<View style={[this.styles.btnContainer, { height: 0, paddingBottom: 5 }]}>
294-
<TouchableOpacity style={this.styles.full}
295-
onPress={ isDoneBtnShow ?
296-
this.props.onDoneBtnClick : this.onNextBtnClick.bind(this, context)}
297-
>
298-
<Text style={[this.styles.nextButtonText, { color: rightTextColor }]}>
299-
{isDoneBtnShow ? this.props.doneBtnLabel : this.props.nextBtnLabel}
300-
</Text>
301-
</TouchableOpacity>
302-
</View>
303-
</View>
304-
);
305-
}
306-
return controllBts;
205+
return (
206+
<View style={[this.styles.paginationContainer]}>
207+
{this.props.showSkipButton ? <SkipButton
208+
{...this.props}
209+
{...this.state}
210+
isSkipBtnShow={isSkipBtnShow}
211+
styles={this.styles}
212+
onSkipBtnClick={() => this.props.onSkipBtnClick(index)} /> :
213+
<View style={this.styles.btnContainer} />
214+
}
215+
{this.props.showDots && RenderDots(index, total, {
216+
...this.props,
217+
styles: this.styles
218+
})}
219+
{this.props.showDoneButton ? <DoneButton
220+
{...this.props}
221+
{...this.state}
222+
isDoneBtnShow={isDoneBtnShow}
223+
styles={this.styles}
224+
onNextBtnClick={this.onNextBtnClick.bind(this, context)}
225+
onDoneBtnClick={this.props.onDoneBtnClick} /> :
226+
<View style={this.styles.btnContainer} />
227+
}
228+
</View>
229+
);
307230
}
308231

309232
renderBasicSlidePage = (index, {
@@ -318,10 +241,11 @@ export default class AppIntro extends Component {
318241
const AnimatedStyle1 = this.getTransform(index, 10, level);
319242
const AnimatedStyle2 = this.getTransform(index, 0, level);
320243
const AnimatedStyle3 = this.getTransform(index, 15, level);
244+
const imgSource = (typeof img === 'string') ? {uri: img} : img;
321245
const pageView = (
322246
<View style={[this.styles.slide, { backgroundColor }]} showsPagination={false} key={index}>
323247
<Animated.View style={[this.styles.header, ...AnimatedStyle1.transform]}>
324-
<Image style={imgStyle} source={{ uri: img }} />
248+
<Image style={imgStyle} source={imgSource} />
325249
</Animated.View>
326250
<View style={this.styles.info}>
327251
<Animated.View style={AnimatedStyle2.transform}>
@@ -361,6 +285,22 @@ export default class AppIntro extends Component {
361285
return animatedChild;
362286
}
363287

288+
shadeStatusBarColor(color, percent) {
289+
const first = parseInt(color.slice(1), 16);
290+
const black = first & 0x0000FF;
291+
const green = first >> 8 & 0x00FF;
292+
const percentage = percent < 0 ? percent * -1 : percent;
293+
const red = first >> 16;
294+
const theme = percent < 0 ? 0 : 255;
295+
const finalColor = (0x1000000 + (Math.round((theme - red) * percentage) + red) * 0x10000 + (Math.round((theme - green) * percentage) + green) * 0x100 + (Math.round((theme - black) * percentage) + black)).toString(16).slice(1);
296+
297+
return `#${finalColor}`;
298+
}
299+
300+
isToTintStatusBar() {
301+
return this.props.pageArray && this.props.pageArray.length > 0 && Platform.OS === 'android'
302+
}
303+
364304
render() {
365305
const childrens = this.props.children;
366306
const { pageArray } = this.props;
@@ -391,13 +331,23 @@ export default class AppIntro extends Component {
391331
});
392332
}
393333
}
334+
335+
if (this.isToTintStatusBar()) {
336+
StatusBar.setBackgroundColor(this.shadeStatusBarColor(this.props.pageArray[0].backgroundColor, -0.3), false);
337+
}
338+
394339
return (
395340
<View>
396341
{androidPages}
397342
<Swiper
398343
loop={false}
344+
index={this.props.defaultIndex}
399345
renderPagination={this.renderPagination}
400346
onMomentumScrollEnd={(e, state) => {
347+
if (this.isToTintStatusBar()) {
348+
StatusBar.setBackgroundColor(this.shadeStatusBarColor(this.props.pageArray[state.index].backgroundColor, -0.3), false);
349+
}
350+
401351
this.props.onSlideChange(state.index, state.total);
402352
}}
403353
onScroll={Animated.event(
@@ -434,6 +384,10 @@ AppIntro.propTypes = {
434384
PropTypes.element,
435385
]),
436386
customStyles: PropTypes.object,
387+
defaultIndex: PropTypes.number,
388+
showSkipButton: PropTypes.bool,
389+
showDoneButton: PropTypes.bool,
390+
showDots: PropTypes.bool,
437391
};
438392

439393
AppIntro.defaultProps = {
@@ -449,4 +403,8 @@ AppIntro.defaultProps = {
449403
doneBtnLabel: 'Done',
450404
skipBtnLabel: 'Skip',
451405
nextBtnLabel: '›',
406+
defaultIndex: 0,
407+
showSkipButton: true,
408+
showDoneButton: true,
409+
showDots: true
452410
};

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ $ npm i react-native-app-intro --save
1919

2020
### Basic Usage
2121

22-
You can use pageArray quick generation your app intro with parallax effect.
22+
You can use pageArray quick generation your app intro with parallax effect. With the basic usage, the Android status bar will be updated to match your slide background color.
2323

2424
<img src="http://i.giphy.com/l3V0khy22aUviTTaM.gif">
2525

26+
<img src="./assets/sample-android.gif">
27+
2628
```javascript
2729
import React, { Component } from 'react';
2830
import { AppRegistry, Alert } from 'react-native';

assets/sample-android.gif

9.83 MB
Loading

0 commit comments

Comments
 (0)