Skip to content

Commit

Permalink
Programmatic methods (#26)
Browse files Browse the repository at this point in the history
* Add start, resume and stop API

* Update flow config

* Update example to start, resume and stop animation

* Add unit tests
  • Loading branch information
VincentCATILLON authored Apr 30, 2020
1 parent e24c5b6 commit 46d98c2
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js

; Example folder
example

[untyped]
.*/node_modules/@react-native-community/cli/.*/.*

Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,24 @@ const MyComponent = () => (
| fallSpeed | number | fall duration (ms) from top to bottom | | 3000 |
| fadeOut | boolean | make the confettis disappear at the end | | false |
| colors | string[] | give your own colors to the confettis | | default colors |
| autoStart | boolean | give your own colors to the confettis | | true |

## Events

| Name | Returns | Description | Required | Default |
|-------------------|-----------------------|--------------------------------------------|----------|----------------|
| onAnimationStart | Item[] | callback triggered at animation start | | |
| onAnimationResume | Item[] | callback triggered at animation resume | | |
| onAnimationStop | Item[] | callback triggered at animation stop | | |
| onAnimationEnd | Item[] | callback triggered at animation end | | |

## Methods

| Name | Returns | Description | Required | Default |
|------------------|------------------------|--------------------------------------------|----------|----------------|
| onAnimationStart | Item[] | callback triggered at animation start | | |
| onAnimationEnd | Item[] | callback triggered at animation end | | |
| start | Item[] | start the animation programmatically | | |
| resume | Item[] | resume the animation programmatically | | |
| stop | Item[] | stop the animation programmatically | | |

## Try yourself

Expand Down
50 changes: 36 additions & 14 deletions example/App.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,51 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import ConfettiCannon from 'react-native-confetti-cannon';

export default function App() {
class App extends React.PureComponent {
confettiCannon;

handleAnimationStart = () => console.log('Animation start');

handleAnimationResume = () => console.log('Animation resume');

handleAnimationStop = () => console.log('Animation stop');

handleAnimationEnd = () => console.log('Animation end');

return (
<View style={styles.container}>
<ConfettiCannon
count={200}
origin={{x: -10, y: 0}}
onAnimationStart={this.handleAnimationStart}
onAnimationEnd={this.handleAnimationEnd}
/>
</View>
);
handleStartPress = () => this.confettiCannon.start();

handleResumePress = () => this.confettiCannon.resume();

handleStopPress = () => this.confettiCannon.stop();

render() {
return (
<View style={styles.container}>
<ConfettiCannon
count={200}
origin={{x: -10, y: 0}}
onAnimationStart={this.handleAnimationStart}
onAnimationResume={this.handleAnimationResume}
onAnimationStop={this.handleAnimationStop}
onAnimationEnd={this.handleAnimationEnd}
ref={ref => this.confettiCannon = ref}
/>
<Button title="Start" onPress={this.handleStartPress} />
<Button title="Resume" onPress={this.handleResumePress} />
<Button title="Stop" onPress={this.handleStopPress} />
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
});

export default App;
5 changes: 3 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"prestart": "rm -rf ./node_modules/react-native-confetti-cannon/src && cp -R ../src ./node_modules/react-native-confetti-cannon/src",
"prestart": "npm run -s build",
"start": "expo start",
"android": "npm run -s start --android",
"ios": "npm run -s start --ios",
"web": "npm run -s start --web",
"eject": "expo eject"
"eject": "expo eject",
"build": "rm -rf ./node_modules/react-native-confetti-cannon/src && cp -R ../src ./node_modules/react-native-confetti-cannon/src"
},
"dependencies": {
"expo": "~37.0.3",
Expand Down
81 changes: 81 additions & 0 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ describe('index', () => {

it('should trigger animations callbacks', () => {
const handleAnimationStart = jest.fn();
const handleAnimationResume = jest.fn();
const handleAnimationStop = jest.fn();
const handleAnimationEnd = jest.fn();
const count = 10;

Expand All @@ -20,6 +22,8 @@ describe('index', () => {
count={count}
origin={{x: -10, y: 0}}
onAnimationStart={handleAnimationStart}
onAnimationResume={handleAnimationResume}
onAnimationStop={handleAnimationStop}
onAnimationEnd={handleAnimationEnd}
/>
);
Expand All @@ -32,6 +36,8 @@ describe('index', () => {

expect(handleAnimationEnd).toHaveBeenCalledTimes(1);
expect(handleAnimationEnd.mock.calls[0][0].length).toEqual(count);
expect(handleAnimationResume).toHaveBeenCalledTimes(0);
expect(handleAnimationStop).toHaveBeenCalledTimes(0);
});

it('should be able to customize speeds', () => {
Expand Down Expand Up @@ -59,4 +65,79 @@ describe('index', () => {

expect(handleAnimationEnd).toHaveBeenCalledTimes(1);
});

it('should not start is autoStart is disabled', () => {
const handleAnimationStart = jest.fn();
const count = 10;

renderer.create(
<ConfettiCannon
count={count}
origin={{x: -10, y: 0}}
autoStart={false}
onAnimationStart={handleAnimationStart}
/>
);

jest.advanceTimersByTime(DEFAULT_EXPLOSION_SPEED + DEFAULT_FALL_SPEED);

expect(handleAnimationStart).toHaveBeenCalledTimes(0);
});

it('should be able to start animation programmatically', () => {
const handleAnimationStart = jest.fn();
const handleAnimationResume = jest.fn();
const handleAnimationStop = jest.fn();
const handleAnimationEnd = jest.fn();
const ref = jest.fn();
const count = 10;

renderer.create(
<ConfettiCannon
count={count}
origin={{x: -10, y: 0}}
autoStart={false}
onAnimationStart={handleAnimationStart}
onAnimationResume={handleAnimationResume}
onAnimationStop={handleAnimationStop}
onAnimationEnd={handleAnimationEnd}
// $FlowFixMe this is a mock
ref={ref}
/>
);

const [confettiCannon] = ref.mock.calls[0];

confettiCannon.start();

expect(handleAnimationStart.mock.calls[0][0].length).toEqual(count);
expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(0);
expect(handleAnimationStop).toHaveBeenCalledTimes(0);
expect(handleAnimationEnd).toHaveBeenCalledTimes(0);

confettiCannon.stop();

expect(handleAnimationStop.mock.calls[0][0].length).toEqual(count);
expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(0);
expect(handleAnimationStop).toHaveBeenCalledTimes(1);
expect(handleAnimationEnd).toHaveBeenCalledTimes(0);

confettiCannon.resume();

expect(handleAnimationResume.mock.calls[0][0].length).toEqual(count);
expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(1);
expect(handleAnimationStop).toHaveBeenCalledTimes(1);
expect(handleAnimationEnd).toHaveBeenCalledTimes(0);

jest.advanceTimersByTime(DEFAULT_EXPLOSION_SPEED + DEFAULT_FALL_SPEED);

expect(handleAnimationEnd.mock.calls[0][0].length).toEqual(count);
expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(1);
expect(handleAnimationStop).toHaveBeenCalledTimes(1);
expect(handleAnimationEnd).toHaveBeenCalledTimes(1);
});
});
82 changes: 59 additions & 23 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import * as React from 'react';
import { Animated, Dimensions, Easing } from 'react-native';
import type { CompositeAnimation } from 'react-native/Libraries/Animated/src/AnimatedImplementation';
import type { EndResult } from 'react-native/Libraries/Animated/src/animations/Animation';

import Confetti from './components/confetti';
import { randomValue } from './utils';
Expand All @@ -16,7 +18,10 @@ type Props = {|
fallSpeed?: number,
colors?: Array<string>,
fadeOut?: boolean,
autoStart?: boolean,
onAnimationStart?: Array<Item> => void,
onAnimationResume?: Array<Item> => void,
onAnimationStop?: Array<Item> => void,
onAnimationEnd?: Array<Item> => void
|};

Expand Down Expand Up @@ -50,15 +55,21 @@ export const DEFAULT_FALL_SPEED = 3000;

class Explosion extends React.PureComponent<Props> {
props: Props;

animation: Animated.Value = new Animated.Value(0);

start: () => void;
resume: () => void;
stop: () => void;
sequence: CompositeAnimation | null;
items: Array<Item> = [];
animation: Animated.Value = new Animated.Value(0);

constructor(props: Props) {
super(props);

const {count} = props;
const { count } = this.props;

this.start = this.start.bind(this);
this.resume = this.resume.bind(this);
this.stop = this.stop.bind(this);

this.items = Array(count).fill().map((): Item => ({
leftDelta: randomValue(0, 1),
Expand All @@ -73,34 +84,59 @@ class Explosion extends React.PureComponent<Props> {
}

componentDidMount = () => {
this.animate();
const { autoStart = true } = this.props;

if (autoStart) {
this.start();
}
};

animate = () => {
start = (resume?: boolean = false) => {
const {
explosionSpeed = DEFAULT_EXPLOSION_SPEED,
fallSpeed = DEFAULT_FALL_SPEED,
onAnimationStart,
onAnimationResume,
onAnimationEnd
} = this.props;

onAnimationStart && onAnimationStart(this.items);

Animated.sequence([
Animated.timing(this.animation, {toValue: 0, duration: 0, useNativeDriver: true}),
Animated.timing(this.animation, {
toValue: 1,
duration: explosionSpeed,
easing: Easing.out(Easing.quad),
useNativeDriver: true
}),
Animated.timing(this.animation, {
toValue: 2,
duration: fallSpeed,
easing: Easing.quad,
useNativeDriver: true
}),
]).start(() => onAnimationEnd && onAnimationEnd(this.items));
if (resume) {
onAnimationResume && onAnimationResume(this.items);
} else {
this.sequence = Animated.sequence([
Animated.timing(this.animation, {toValue: 0, duration: 0, useNativeDriver: true}),
Animated.timing(this.animation, {
toValue: 1,
duration: explosionSpeed,
easing: Easing.out(Easing.quad),
useNativeDriver: true
}),
Animated.timing(this.animation, {
toValue: 2,
duration: fallSpeed,
easing: Easing.quad,
useNativeDriver: true
}),
]);

onAnimationStart && onAnimationStart(this.items);
}

this.sequence && this.sequence.start(({finished}: EndResult) => {
if (finished) {
onAnimationEnd && onAnimationEnd(this.items);
}
});
};

resume = () => this.start(true);

stop = () => {
const { onAnimationStop } = this.props;

onAnimationStop && onAnimationStop(this.items);

this.sequence && this.sequence.stop();
};

render() {
Expand Down

0 comments on commit 46d98c2

Please sign in to comment.