Skip to content
Open
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
8 changes: 8 additions & 0 deletions insta-stories/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}
63 changes: 63 additions & 0 deletions insta-stories/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module.exports = {
extends: ["plugin:flowtype/recommended", "airbnb"],
plugins: ["flowtype", "react-native"],
parser: "babel-eslint",
parserOptions: {
ecmaVersion: 2016,
sourceType: "module",
ecmaFeatures: {
jsx: true
}
},
settings: {
"import/resolver": {
"react-native": {},
"babel-plugin-root-import": {
"rootPathPrefix": "~",
"rootPathSuffix": "src"
}
},
"react": {
"version": "16.3"
}
},
env: {
jest: true
},
rules: {
"no-console": 2,
"no-use-before-define": 0,
"jsx-a11y/accessible-emoji": 0,
"jsx-a11y/anchor-is-valid": 0,
"react/jsx-filename-extension": [1, { extensions: [".js", ".jsx"] }],
"max-len": ["error", { code: 120 }],
"flowtype/delimiter-dangle": [2, "always-multiline"],
"flowtype/semi": [2, "always"],
"flowtype/array-style-simple-type": [2, "shorthand"],
"flowtype/no-primitive-constructor-types": 2,
"flowtype/require-valid-file-annotation": [
2,
"always",
{
"annotationStyle": "line"
}
],
"react/sort-comp": 0,
"react/no-did-mount-set-state": 0,
"react/prop-types": 0,
"react/no-did-update-set-state": 0,
"react-native/no-unused-styles": 2,
"react-native/split-platform-components": 2,
"react-native/no-inline-styles": 0,
"react-native/no-color-literals": 0,
},
globals: {
fetch: false,
btoa: false,
Headers: false,
window: false,
navigator: false,
requestAnimationFrame: true,
cancelAnimationFrame: true
}
};
3 changes: 3 additions & 0 deletions insta-stories/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/**/*
.expo/*
npm-debug.*
1 change: 1 addition & 0 deletions insta-stories/.watchmanconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
71 changes: 71 additions & 0 deletions insta-stories/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// @flow
import React from 'react';
import { StatusBar } from 'react-native';
import { Asset, AppLoading } from 'expo';
// Two implementations of the story components.
// One using linear interpolation which doesn't make it a perfect cube and one with setNativeProps
import { Stories, Stories2, Stories3 } from './components';

const stories = [
{
id: '2',
source: require('./assets/stories/2.jpg'),
user: 'derek.russel',
avatar: require('./assets/avatars/derek.russel.png'),
},
{
id: '4',
source: require('./assets/stories/4.jpg'),
user: 'jmitch',
avatar: require('./assets/avatars/jmitch.png'),
},
{
id: '5',
source: require('./assets/stories/5.jpg'),
user: 'monicaa',
avatar: require('./assets/avatars/monicaa.png'),
},
{
id: '3',
source: require('./assets/stories/3.jpg'),
user: 'alexandergarcia',
avatar: require('./assets/avatars/alexandergarcia.png'),
},
{
id: '1',
source: require('./assets/stories/1.jpg'),
user: 'andrea.schmidt',
avatar: require('./assets/avatars/andrea.schmidt.png'),
},
];

type AppState = {
ready: boolean,
};

export default class App extends React.Component<{}, AppState> {
state = {
ready: false,
};

async componentDidMount() {
await Promise.all(stories.map(story => Promise.all([
Asset.loadAsync(story.source),
Asset.loadAsync(story.avatar),
])));
this.setState({ ready: true });
}

render() {
const { ready } = this.state;
if (!ready) {
return <AppLoading />;
}
return (
<React.Fragment>
<StatusBar barStyle="light-content" />
<Stories3 {...{ stories }} />
</React.Fragment>
);
}
}
27 changes: 27 additions & 0 deletions insta-stories/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"expo": {
"name": "instagram-stories",
"description": "This project is really great.",
"slug": "instagram-stories",
"privacy": "public",
"sdkVersion": "30.0.0",
"platforms": ["ios", "android"],
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
}
}
}
Binary file added insta-stories/assets/avatars/alexandergarcia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/avatars/andrea.schmidt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/avatars/derek.russel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/avatars/jmitch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/avatars/monicaa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/stories/1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/stories/2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/stories/3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/stories/4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added insta-stories/assets/stories/5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions insta-stories/components/Avatar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @flow
import * as React from 'react';
import {
StyleSheet, View, Image, Text, Platform,
} from 'react-native';
import { Constants } from 'expo';
import type { ImageSourcePropType } from 'react-native/Libraries/Image/ImageSourcePropType';

type AvatarProps = {
user: string,
avatar: ImageSourcePropType,
};

export default class Avatar extends React.PureComponent<AvatarProps> {
render(): React.Node {
const { user, avatar: source } = this.props;
return (
<View style={styles.container}>
<Image {...{ source }} style={styles.avatar} />
<Text style={styles.username}>{user}</Text>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
padding: 16,
alignItems: 'center',
marginTop: Platform.OS === 'android' ? Constants.statusBarHeight : 0,
},
avatar: {
width: 36,
height: 36,
borderRadius: 36 / 2,
marginRight: 16,
},
username: {
color: 'white',
fontSize: 16,
},
});
128 changes: 128 additions & 0 deletions insta-stories/components/Stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// @flow
import * as React from 'react';
import {
StyleSheet, Animated, Dimensions, Platform, View,
} from 'react-native';

import Story, { type StoryModel } from './Story';

const { width } = Dimensions.get('window');
const perspective = width;
const angle = Math.atan(perspective / (width / 2));
const ratio = Platform.OS === 'ios' ? 2 : 1.2;

type StoriesProps = {
stories: StoryModel[],
};

type StoriesState = {
x: Animated.Value,
};

export default class Stories extends React.PureComponent<StoriesProps, StoriesState> {
state = {
x: new Animated.Value(0),
};

getStyle(index: number) {
const { x } = this.state;
const offset = index * width;

const inputRange = [offset - width, offset + width];

const translateX = x.interpolate({
inputRange,
outputRange: [width / ratio, -width / ratio],
extrapolate: 'clamp',
});
const rotateY = x.interpolate({
inputRange,
outputRange: [`${angle}rad`, `-${angle}rad`],
extrapolate: 'clamp',
});

const translateX1 = x.interpolate({
inputRange,
outputRange: [(width / 2), -width / 2],
extrapolate: 'clamp',
});

const extra = ((width / ratio) / Math.cos(angle / 2)) - width / ratio;
const translateX2 = x.interpolate({
inputRange,
outputRange: [-extra, extra],
extrapolate: 'clamp',
});

return {
...StyleSheet.absoluteFillObject,
transform: [
{ perspective },
{ translateX },
{ rotateY },
{ translateX: translateX1 },
{ translateX: translateX2 },
],
};
}

getMaskStyle(index: number) {
const { x } = this.state;
const offset = index * width;
const inputRange = [offset - width, offset, offset + width];
const opacity = x.interpolate({
inputRange,
outputRange: [0.75, 0, 0.75],
extrapolate: 'clamp',
});
return {
backgroundColor: 'black',
...StyleSheet.absoluteFillObject,
opacity,
};
}

render(): React.Node {
const { x } = this.state;
const { stories } = this.props;
return (
<View style={styles.container}>
{
stories.map((story, i) => (
<Animated.View style={this.getStyle(i)} key={story.id}>
<Story {...{ story }} />
<Animated.View style={this.getMaskStyle(i)} />
</Animated.View>
))
}
<Animated.ScrollView
ref={this.scroll}
style={StyleSheet.absoluteFillObject}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
snapToInterval={width}
contentContainerStyle={{ width: width * stories.length }}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: { x },
},
},
],
{ useNativeDriver: true },
)}
decelerationRate={0.99}
horizontal
/>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black',
},
});
Loading