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

Add a basic FTU #42

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions js/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import {

import { theme } from '../config';

import Settings from './scenes/Settings';
import Profile from './scenes/Profile';
import Compass from './scenes/Compass';
import Demos from './scenes/Demos';
import FTU from './scenes/FTU';
import Home from './scenes/Home';
import Item from './scenes/Item';
import Map from './scenes/Map';
import Profile from './scenes/Profile';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this reordering?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really missing that we cannot do a dynamic import :( :( :( would love to load all this scenes like fte, debug, etc. on demand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new order reflects the order the routes are declared in the constructor below.

Also I know it's bad to import everything but I wouldn't worry too much as the components are not instantiated.
In the final product, I think it makes sense to be able to access the FTU (remember Firefox OS?)

import Settings from './scenes/Settings';
import Compass from './scenes/Compass';
import Demos from './scenes/Demos';

import { connect } from 'react-redux';
import { watchLocation } from './store/actions';
Expand All @@ -28,6 +29,7 @@ export class App extends Component {
super(props);

this.routes = {
ftu: { component: FTU },
home: { component: Home },
item: { component: Item },
map: { component: Map },
Expand Down
85 changes: 85 additions & 0 deletions js/components/ViewPagerIndicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { PropTypes } from 'react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to be a bit picky, could you add some documentation explaining what this component does?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has now better documentation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, much better :)

import {
View,
Animated,
Dimensions,
StyleSheet,
} from 'react-native';

const { width } = Dimensions.get('window');
const DOT_SIZE = 8;
const DOT_SPACING = 6;
const DOT_FULL_WIDTH = DOT_SIZE + DOT_SPACING * 2;

/**
* This component is used to style the dots (or bullets) used by
* react-native-viewpager. It's a purely stateless functional component that is
* only concerned with rendering the dots and transitioning the active dot from
* one page of the view pager to another.
*
* @param pageCount {number} The total number of pages.
* @param activePage {number} The currently active page.
* @param scrollValue {Animated.Value} Used to transition the active dot.
* @param scrollOffset {number} Used to reset dot position after a transition.
* @return {Component}
*/
const ViewPagerIndicator = ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this a shorthand declaration, but I'd rather we just be consistent with how we define components (ie. class).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more than just a shorthand. These stateless functional components are the recommended way to build components that are purely visual. They've been added in React 0.0.14 and they're likely to get an optimisation boost in the next versions. Ideally I'd like all the components in the components folder to use this syntax.

You can read more in the announcement blog and this blog post.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting thanks!

pageCount,
activePage,
scrollValue,
scrollOffset,
}) => {
// Initial left position of the active dot used during transition.
const offsetX = (width - DOT_FULL_WIDTH * pageCount) / 2 +
(activePage - scrollOffset) * DOT_FULL_WIDTH;

// The CSS `left` property.
const left = scrollValue.interpolate({
inputRange: [0, 1],
outputRange: [offsetX, offsetX + DOT_FULL_WIDTH],
});

// Create the elements for the inactive dots.
const indicators = [];
for (let i = 0; i < pageCount; i++) {
indicators.push(
<View key={i} style={styles.dot}/>
);
}

return (
<View style={styles.container}>
{indicators}
<Animated.View style={[styles.dot, styles.activeDot, { left }]}/>
</View>
);
};

ViewPagerIndicator.propTypes = {
goToPage: PropTypes.func,
pageCount: PropTypes.number,
activePage: PropTypes.number,
scrollValue: PropTypes.object,
scrollOffset: PropTypes.number,
};

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'center',
marginBottom: 20,
},
dot: {
width: DOT_SIZE,
height: DOT_SIZE,
borderRadius: DOT_SIZE / 2,
backgroundColor: 'rgba(255,255,255,0.5)',
marginHorizontal: DOT_SPACING,
},
activeDot: {
position: 'absolute',
backgroundColor: '#fff',
},
});

export default ViewPagerIndicator;
Binary file added js/images/ftu/background.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions js/scenes/Demos.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export class List extends Component {
<Header
title="Component Demos"
navigator={navigator}/>
<TouchableOpacity
onPress={this.onItemPress.bind(this, 'ftu')}>
<Text style={styles.text}>Go to FTU scene</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.onItemPress.bind(this, 'map')}>
<Text style={styles.text}>Go to Map scene</Text>
Expand Down
169 changes: 169 additions & 0 deletions js/scenes/FTU.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import React, { Component, PropTypes } from 'react';
import {
View,
Image,
Text,
Button,
Dimensions,
StyleSheet,
Platform,
} from 'react-native';
import ViewPager from 'react-native-viewpager';
import ViewPagerIndicator from '../components/ViewPagerIndicator';
import { connect } from 'react-redux';
import { theme } from '../../config';

const CONTENT = [
{
title: 'Hear the story of London\'s vibrant street art',
main: 'This month Project Magnet brings you a selection of amazing street art from around London.',
footer: 'Not in London? Tell us where you\'d like to go next.',
},
{
title: 'Turn on notifications?',
main: 'Notifications can alert you when you\'re close to a point of interest, even when you\'re not inside the app.',
},
];

export class FTU extends Component {
constructor(props) {
super(props);

this.navigator = this.props.navigator;

const PAGES = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we render the pages on demand? Or are we going to force to show always the two pages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pages are rendered on demand. The code used to build each page is only called when required (think of <ListView> component).
The API seems flexible so it can also handle complicated case where you go to different pages depending on which button you tap.

() => this.renderPage1(),
() => this.renderPage2(),
];
const dataSource = new ViewPager.DataSource({
pageHasChanged: (p1, p2) => p1 !== p2,
});

this.state = {
dataSource: dataSource.cloneWithPages(PAGES),
};
}

render() {
return (
<Image
source={require('../images/ftu/background.jpg')}
resizeMode="cover"
style={styles.container}>
<View style={styles.scrim}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a scrim?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n. A transparent fabric used as a drop in the theatre to create special effects of lights or atmosphere.

That's a word that came up when I was working on the settings drawer of Firefox OS. I think @djabberwocky was working on the spec back then so he may know more about this term.
That may not be appropriate in this case.

<ViewPager
ref="viewPager"
autoPlay={false}
isLoop={false}
locked={true}
dataSource={this.state.dataSource}
renderPage={(render) => render()}
renderPageIndicator={() => <ViewPagerIndicator/>}
style={styles.viewPager}/>
</View>
</Image>
);
}

renderPage1() {
const DATA = CONTENT[0];

return (
<View style={styles.screen}>
<Text style={styles.title}>{DATA.title.toUpperCase()}</Text>
<Text style={styles.text}>{DATA.main}</Text>
<View style={styles.button}>
<View style={styles.border}>
<Button
title="GET STARTED"
accessibilityLabel="Continue to the next screen."
onPress={() => this.refs.viewPager.goToPage(1)}
color={Platform.OS === 'ios' ? 'white' : 'transparent'}/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm our current design we have a black button with white border as our primary-call-to-action button. Can we be consistent and have a single <PrimaryButton> component that we use everywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after our UX discussions of yesterday, let's not focus too much on design. All will change.
I agree we need a specific button. Let's open an issue for this.

</View>
</View>
<Text style={styles.text}>{DATA.footer}</Text>
</View>
);
}

renderPage2() {
const DATA = CONTENT[1];

return (
<View style={styles.screen}>
<Text style={styles.title}>{DATA.title.toUpperCase()}</Text>
<Text style={styles.text}>{DATA.main}</Text>
<View style={styles.button}>
<View style={styles.border}>
<Button
title="TURN ON"
accessibilityLabel="Turn on notifications."
onPress={() => this.navigator.push({ id: 'home' })}
color={Platform.OS === 'ios' ? 'white' : 'transparent'}/>
</View>
<View style={styles.border}>
<Button
title="SKIP"
accessibilityLabel="Skip this step."
onPress={() => this.navigator.push({ id: 'home' })}
color={Platform.OS === 'ios' ? 'white' : 'transparent'}/>
</View>
</View>
</View>
);
}

static propTypes = {
navigator: PropTypes.object,
}
}

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
container: {
flex: 1,
width,
height,
},
viewPager: {
flex: 1,
},
scrim: {
width,
height,
backgroundColor: 'rgba(0,0,0,0.5)',
},
screen: {
width,
padding: 30,
flexDirection: 'column',
justifyContent: 'space-around',
},
title: {
fontFamily: theme.fontBook,
fontSize: 30,
lineHeight: Math.round(30 * 1.5),
color: 'white',
},
text: {
fontFamily: theme.fontBook,
fontSize: 20,
lineHeight: Math.round(20 * 1.5),
color: 'white',
},
button: {
flexDirection: 'column',
alignItems: 'center',
},
border: {
width: width / 2,
borderRadius: 15,
borderColor: 'white',
borderWidth: 2,
padding: 4,
marginBottom: 10,
},
});

export default connect()(FTU);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react-native-heading": "git://github.com/arcturus/react-native-heading.git",
"react-native-maps": "^0.13.0",
"react-native-share": "^1.0.18",
"react-native-viewpager": "^0.2.13",
"react-redux": "^5.0.1",
"realm": "^0.15.4",
"redux": "^3.6.0",
Expand Down
15 changes: 14 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3501,6 +3501,13 @@ react-native-share@^1.0.18:
version "1.0.19"
resolved "https://registry.yarnpkg.com/react-native-share/-/react-native-share-1.0.19.tgz#82c210efb156175d67ee271efee29ed9cf8e1449"

react-native-viewpager@^0.2.13:
version "0.2.13"
resolved "https://registry.yarnpkg.com/react-native-viewpager/-/react-native-viewpager-0.2.13.tgz#75e421ae90e89efcd77d2c077c0525382b7c39c5"
dependencies:
react-timer-mixin "^0.13.3"
warning "^2.1.0"

[email protected]:
version "0.40.0"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.40.0.tgz#ca7b86a8e8fbc7653634ad47ca2ffd69fdf18ad5"
Expand Down Expand Up @@ -3606,7 +3613,7 @@ react-test-renderer@~15.4.0-rc.4:
fbjs "^0.8.4"
object-assign "^4.1.0"

react-timer-mixin@^0.13.2:
react-timer-mixin@^0.13.2, react-timer-mixin@^0.13.3:
version "0.13.3"
resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.3.tgz#0da8b9f807ec07dc3e854d082c737c65605b3d22"

Expand Down Expand Up @@ -4422,6 +4429,12 @@ walker@~1.0.5:
dependencies:
makeerror "1.0.x"

warning@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/warning/-/warning-2.1.0.tgz#21220d9c63afc77a8c92111e011af705ce0c6901"
dependencies:
loose-envify "^1.0.0"

watch@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc"
Expand Down