created spin animation placed wip on tags

This commit is contained in:
ue 2019-09-27 22:01:29 +03:00
parent d63bd3df6e
commit 0527f2277e
17 changed files with 362 additions and 575 deletions

View File

@ -0,0 +1,44 @@
import React from 'react';
import { View, Image } from 'react-native';
import { SpinIndicator } from '../spinIndicator/spinIndicator';
// Styles
import styles from './boostIndicatorStyles';
const BoostIndicatorAnimation = ({ isSpinning }) => {
return (
<View style={styles.spinIndicatorContainer}>
<SpinIndicator
size={230}
animationDuration={2400}
color={!isSpinning ? '#f2f2f2' : '#1a509a'}
breadth={17}
animating={isSpinning}
initStart={0}
/>
<SpinIndicator
size={180}
animationDuration={2000}
color={!isSpinning ? '#f2f2f2' : '#357ce6'}
breadth={17}
animating={isSpinning}
initStart={20}
/>
<SpinIndicator
size={130}
animationDuration={1700}
color={!isSpinning ? '#f2f2f2' : '#4da1f1'}
breadth={17}
animating={isSpinning}
initStart={40}
/>
<Image
style={{ width: 80, height: 80 }}
source={require('../../../assets/esteem_logo_transparent.png')}
/>
</View>
);
};
export { BoostIndicatorAnimation };

View File

@ -0,0 +1,10 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
spinIndicatorContainer: {
backgroundColor: '$primaryBackgroundColor',
justifyContent: 'center',
alignItems: 'center',
flex: 1,
},
});

View File

@ -1,4 +1,5 @@
import PulseAnimation from './pulse/pulseAnimation';
import { PulseAnimation } from './pulse/pulseAnimation';
import { SpinIndicator } from './spinIndicator/spinIndicator';
import { BoostIndicatorAnimation } from './boostIndicator/boostIndicatorAnimation';
export { PulseAnimation };
export default PulseAnimation;
export { PulseAnimation, SpinIndicator, BoostIndicatorAnimation };

View File

@ -14,7 +14,7 @@ const styles = StyleSheet.create({
},
});
export default class PulseAnimation extends Component {
class PulseAnimation extends Component {
static defaultProps = {
color: 'blue',
diameter: 400,
@ -151,3 +151,5 @@ export default class PulseAnimation extends Component {
);
}
}
export { PulseAnimation };

View File

@ -0,0 +1,104 @@
import React, { PureComponent } from 'react';
import { Animated, Easing } from 'react-native';
export default class Indicator extends PureComponent {
constructor(props) {
super(props);
this.state = {
progress: new Animated.Value(0),
};
this.mounted = false;
}
componentDidMount() {
const { animating } = this.props;
this.mounted = true;
if (animating) {
this._startAnimation();
}
}
componentWillReceiveProps(nextProps) {
const { animating } = this.props;
if (animating !== nextProps.animating) {
if (animating) {
this._stopAnimation();
} else {
this._startAnimation();
}
}
}
componentWillUnmount() {
this.mounted = false;
}
_startAnimation = ({ finished } = {}) => {
const { progress } = this.state;
const { interaction, animationEasing, animationDuration } = this.props;
if (!this.mounted || finished === false) {
return;
}
const animation = Animated.timing(progress, {
duration: animationDuration,
easing: animationEasing,
useNativeDriver: true,
isInteraction: interaction,
toValue: 1,
});
Animated.loop(animation).start();
this.setState({ animation });
};
_stopAnimation = () => {
const { animation } = this.state;
if (animation == null) {
return;
}
animation.stop();
this.setState({ animation: null });
};
_renderComponent = (undefined, index) => {
const { progress } = this.state;
const { renderComponent } = this.props;
if (renderComponent) {
return renderComponent({ index, progress });
}
return null;
};
render() {
const { count, ...props } = this.props;
return (
<Animated.View {...props}>
{Array.from(new Array(count), this._renderComponent)}
</Animated.View>
);
}
}
Indicator.defaultProps = {
animationEasing: Easing.linear,
animationDuration: 1200,
animating: true,
interaction: true,
count: 1,
};
export { Indicator };

View File

@ -0,0 +1,126 @@
import React, { PureComponent } from 'react';
import { View, Animated, Easing } from 'react-native';
import { Indicator } from './indicator';
import styles from './spinIndicatorStyles';
class SpinIndicator extends PureComponent {
_renderComponent = ({ index, progress }) => {
const { size, color, animationDuration, breadth, animating, initStart } = this.props;
const frames = (60 * animationDuration) / 1000;
const easing = Easing.bezier(0.4, 0.0, 0.7, 1.0);
const inputRange = Array.from(
new Array(frames),
(undefined, frameIndex) => frameIndex / (frames - 1),
);
const outputRange = Array.from(new Array(frames), (undefined, frameIndex) => {
let _progress = (2 * frameIndex) / (frames - 1);
const rotation = index ? +(360 - 15) : -(180 - 15);
if (_progress > 1.0) {
_progress = 2.0 - _progress;
}
const direction = index ? -1 : +1;
return `${direction * (180 - 30) * easing(_progress) + rotation}deg`;
});
const layerStyle = {
width: size,
height: size,
transform: [
{
rotate: progress.interpolate({
inputRange: [0, 1],
outputRange: [`${initStart + 30 + 15}deg`, `${2 * (360 + initStart)}deg`],
// outputRange: [`${0 + 30 + 15}deg`, `${2 * 360 + 30 + 15}deg`],
}),
},
],
};
const viewportStyle = {
width: size,
height: size,
transform: [
{
translateY: index ? -size / 2 : 0,
},
{
rotate: progress.interpolate({ inputRange, outputRange }),
},
],
};
const containerStyle = {
width: !animating ? 300 : size,
height: !animating ? 300 : size / 2,
overflow: 'hidden',
};
const offsetStyle = index ? { top: size / 2 } : null;
const lineStyle = {
width: size,
height: size,
borderColor: color,
borderWidth: breadth || size / 10,
borderRadius: size / 2,
};
return (
<Animated.View style={styles.layer} {...{ key: index }}>
<Animated.View style={layerStyle}>
<Animated.View style={[containerStyle, offsetStyle]} collapsable={false}>
<Animated.View style={viewportStyle}>
<Animated.View style={containerStyle} collapsable={false}>
<Animated.View style={lineStyle} />
</Animated.View>
</Animated.View>
</Animated.View>
</Animated.View>
</Animated.View>
);
};
render() {
const {
style,
size: width,
size: height,
animationDuration,
interaction,
animating,
animationEasing,
} = this.props;
return (
<View style={[styles.container, style]}>
<Indicator
style={{ width, height }}
renderComponent={this._renderComponent}
interaction={interaction}
animationEasing={animationEasing}
animationDuration={animationDuration}
animating={animating}
count={2}
/>
</View>
);
}
}
SpinIndicator.defaultProps = {
animationDuration: 2400,
color: '#1a509a',
animating: true,
size: 40,
initStart: 0,
};
export { SpinIndicator };

View File

@ -0,0 +1,15 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
},
layer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
},
});

View File

@ -1,5 +1,6 @@
import { AvatarHeader } from './avatarHeader';
import { BasicHeader } from './basicHeader';
import { BoostIndicatorAnimation } from './animations';
import { BottomTabBar } from './bottomTabBar';
import { CheckBox } from './checkbox';
import { CircularButton, TextButton, SquareButton } from './buttons';
@ -36,6 +37,7 @@ import { SearchInput } from './searchInput';
import { SearchModal } from './searchModal';
import { SettingsItem } from './settingsItem';
import { SideMenu } from './sideMenu';
import { SpinIndicator } from './animations';
import { SummaryArea, TagArea, TextArea, TitleArea } from './editorElements';
import { TabBar } from './tabBar';
import { TextInput } from './textInput';
@ -46,7 +48,6 @@ import { Upvote } from './upvote';
import { UserAvatar } from './userAvatar';
import Logo from './logo/logo';
import PostButton from './postButton/postButtonView';
import SpinGame from './spinGame/view/spinGameView';
import ProfileEditForm from './profileEditForm/profileEditFormView';
import ScaleSlider from './scaleSlider/scaleSliderView';
@ -98,6 +99,7 @@ export {
ListPlaceHolder,
BoostPlaceHolder,
NoInternetConnection,
BoostIndicatorAnimation,
NoPost,
PostCardPlaceHolder,
PostPlaceHolder,
@ -132,7 +134,7 @@ export {
LeaderBoard,
LoginHeader,
Logo,
SpinGame,
SpinIndicator,
MainButton,
MarkdownEditor,
Modal,

View File

@ -1,115 +0,0 @@
import React, { PureComponent } from 'react';
import { Animated, Easing } from 'react-native';
import RN from 'react-native/package';
const [major, minor] = RN.version.split('.').map(item => Number(item));
const hasLoopSupport = !major && minor >= 45;
export default class Indicator extends PureComponent {
static defaultProps = {
animationEasing: Easing.linear,
animationDuration: 1200,
animating: true,
interaction: true,
count: 1,
};
constructor(props) {
super(props);
this.renderComponent = this.renderComponent.bind(this);
this.startAnimation = this.startAnimation.bind(this);
this.stopAnimation = this.stopAnimation.bind(this);
this.state = {
progress: new Animated.Value(0),
};
this.mounted = false;
}
startAnimation({ finished } = {}) {
const { progress } = this.state;
const { interaction, animationEasing, animationDuration } = this.props;
if (!this.mounted || false === finished) {
return;
}
const animation = Animated.timing(progress, {
duration: animationDuration,
easing: animationEasing,
useNativeDriver: true,
isInteraction: interaction,
toValue: 1,
});
if (hasLoopSupport) {
Animated.loop(animation).start();
} else {
progress.setValue(0);
animation.start(this.startAnimation);
}
this.setState({ animation });
}
stopAnimation() {
const { animation } = this.state;
if (null == animation) {
return;
}
animation.stop();
this.setState({ animation: null });
}
componentDidMount() {
const { animating } = this.props;
this.mounted = true;
if (animating) {
this.startAnimation();
}
}
componentWillUnmount() {
this.mounted = false;
}
componentWillReceiveProps(props) {
const { animating } = this.props;
if (animating ^ props.animating) {
if (animating) {
this.stopAnimation();
} else {
this.startAnimation();
}
}
}
renderComponent(undefined, index) {
const { progress } = this.state;
const { renderComponent, count } = this.props;
if ('function' === typeof renderComponent) {
return renderComponent({ index, count, progress });
} else {
return null;
}
}
render() {
const { count, ...props } = this.props;
return (
<Animated.View {...props}>{Array.from(new Array(count), this.renderComponent)}</Animated.View>
);
}
}

View File

@ -1,56 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
class RouletteItem extends Component {
constructor(props) {
super(props);
this.state = {
coordX: props.radius,
coordY: props.radius,
};
}
getCoordinates({ width, height }) {
const { radius, index, step, distance } = this.props;
const coordX = Math.round(
radius / 2 + distance * -Math.sin(index * step - Math.PI) - width / 2,
);
const coordY = Math.round(
radius / 2 + distance * Math.cos(index * step - Math.PI) - height / 2,
);
this.setState({ coordX, coordY });
}
render() {
const { item, rouletteRotate } = this.props;
const { coordX, coordY } = this.state;
return (
<View
style={{
position: 'absolute',
left: coordX,
top: coordY,
transform: [{ rotate: `${-rouletteRotate}deg` }],
}}
onLayout={event => this.getCoordinates(event.nativeEvent.layout)}
>
{item}
</View>
);
}
}
RouletteItem.propTypes = {
step: PropTypes.number,
index: PropTypes.number,
radius: PropTypes.number,
distance: PropTypes.number,
rouletteRotate: PropTypes.number,
item: PropTypes.element.isRequired,
};
export default RouletteItem;

View File

@ -1,286 +0,0 @@
// import React, { Component, Children } from 'react';
// import { View, Animated, PanResponder, Easing, ImageBackground, Image } from 'react-native';
// import RouletteItem from './rouletteItem';
// import styles from './styles';
// class Roulette extends Component {
// constructor(props) {
// super(props);
// this.state = {
// _animatedValue: new Animated.Value(0),
// activeItem: 0,
// };
// this.step = props.step || (2 * Math.PI) / props.options.length;
// this.panResponder = PanResponder.create({
// onMoveShouldSetResponderCapture: () => true,
// onMoveShouldSetPanResponderCapture: () => true,
// onPanResponderRelease: () => {
// const { enableUserRotate } = this.props;
// if (enableUserRotate) this.triggerSpin();
// },
// });
// }
// triggerSpin(spinToIndex) {
// const { options, turns, onRotate, onRotateChange, duration, easing } = this.props;
// const { activeItem } = this.state;
// const randomSelected = Math.floor(Math.random() * options.length);
// const selectedIndex = spinToIndex != null ? spinToIndex : randomSelected;
// const turnsMultiplier = options.length * turns;
// const nextItem = selectedIndex + turnsMultiplier;
// this.state._animatedValue.setValue(activeItem);
// const animation = Animated.timing(this.state._animatedValue, {
// toValue: nextItem,
// easing,
// duration,
// });
// if (onRotateChange) onRotateChange('start');
// animation.start(() => {
// if (onRotateChange) onRotateChange('stop');
// });
// let newActiveItem = nextItem > options.length ? nextItem % options.length : nextItem;
// if (newActiveItem == 0) {
// newActiveItem = options.length;
// }
// this.setState(
// { activeItem: newActiveItem },
// () => onRotate && onRotate(options[options.length - newActiveItem]),
// );
// }
// render() {
// const {
// options,
// radius,
// distance,
// customStyle,
// rouletteRotate,
// background,
// marker,
// centerImage,
// markerWidth,
// markerTop,
// centerWidth,
// centerTop,
// markerStyle,
// centerStyle,
// rotateEachElement,
// } = this.props;
// const interpolatedRotateAnimation = this.state._animatedValue.interpolate({
// inputRange: [0, options.length],
// outputRange: [`${rouletteRotate}deg`, `${360 + rouletteRotate}deg`],
// });
// const displayOptions =
// options && options.length > 0 && options[0] && React.isValidElement(options[0]);
// return (
// <View>
// <Animated.View
// {...this.panResponder.panHandlers}
// style={[
// styles.container,
// { width: radius, height: radius, borderRadius: radius / 2 },
// { transform: [{ rotate: interpolatedRotateAnimation }] },
// customStyle,
// ]}
// >
// <ImageBackground
// width={radius}
// height={radius}
// style={{ width: radius, height: radius, zIndex: 100 }}
// source={background}
// >
// {displayOptions &&
// Children.map(options, (child, index) => (
// <RouletteItem
// item={child}
// index={index}
// radius={radius}
// step={this.step}
// distance={distance}
// rouletteRotate={rotateEachElement(index)}
// />
// ))}
// </ImageBackground>
// </Animated.View>
// <Image
// source={marker}
// resizeMode="contain"
// style={[
// styles.marker,
// {
// zIndex: 9999,
// top: markerTop,
// width: markerWidth,
// left: radius / 2 - markerWidth / 2,
// },
// markerStyle,
// ]}
// />
// {centerImage && (
// <Image
// source={centerImage}
// resizeMode="contain"
// style={[
// styles.marker,
// {
// zIndex: 9999,
// top: centerTop,
// width: centerWidth,
// left: radius / 2 - centerWidth / 2,
// },
// centerStyle,
// ]}
// />
// )}
// </View>
// );
// }
// }
// Roulette.defaultProps = {
// radius: 300,
// distance: 100,
// rouletteRotate: 0,
// enableUserRotate: false,
// background: null,
// turns: 5,
// rotateEachElement: index => 0,
// // onRotate: () => {},
// // onRotateChange: () => {},
// duration: 3500,
// easing: Easing.inOut(Easing.ease),
// markerTop: 0,
// markerWidth: 20,
// centerWidth: 20,
// centerTop: 0,
// centerImage: null,
// markerStyle: {},
// };
// export default Roulette;
import React, { PureComponent } from 'react';
import { View, Animated, Easing } from 'react-native';
import Indicator from './indicator';
import styles from './styles';
export default class MaterialIndicator extends PureComponent {
static defaultProps = {
animationDuration: 2400,
color: 'rgb(0, 0, 0)',
size: 40,
};
constructor(props) {
super(props);
this.renderComponent = this.renderComponent.bind(this);
}
renderComponent({ index, count, progress }) {
const { size, color, animationDuration } = this.props;
const frames = (60 * animationDuration) / 1000;
const easing = Easing.bezier(0.4, 0.0, 0.7, 1.0);
const inputRange = Array.from(
new Array(frames),
(undefined, frameIndex) => frameIndex / (frames - 1),
);
const outputRange = Array.from(new Array(frames), (undefined, frameIndex) => {
let progress = (2 * frameIndex) / (frames - 1);
const rotation = index ? +(360 - 15) : -(180 - 15);
if (progress > 1.0) {
progress = 2.0 - progress;
}
const direction = index ? -1 : +1;
return direction * (180 - 30) * easing(progress) + rotation + 'deg';
});
const layerStyle = {
width: size,
height: size,
transform: [
{
rotate: progress.interpolate({
inputRange: [0, 1],
outputRange: [0 + 30 + 15 + 'deg', 2 * 360 + 30 + 15 + 'deg'],
}),
},
],
};
const viewportStyle = {
width: size,
height: size,
transform: [
{
translateY: index ? -size / 2 : 0,
},
{
rotate: progress.interpolate({ inputRange, outputRange }),
},
],
};
const containerStyle = {
width: size,
height: size / 2,
overflow: 'hidden',
};
const offsetStyle = index ? { top: size / 2 } : null;
const lineStyle = {
width: size,
height: size,
borderColor: color,
borderWidth: size / 10,
borderRadius: size / 2,
};
return (
<Animated.View style={styles.layer} {...{ key: index }}>
<Animated.View style={layerStyle}>
<Animated.View style={[containerStyle, offsetStyle]} collapsable={false}>
<Animated.View style={viewportStyle}>
<Animated.View style={containerStyle} collapsable={false}>
<Animated.View style={lineStyle} />
</Animated.View>
</Animated.View>
</Animated.View>
</Animated.View>
</Animated.View>
);
}
render() {
const { style, size: width, size: height, ...props } = this.props;
return (
<View style={[styles.container, style]}>
<Indicator
style={{ width, height }}
renderComponent={this.renderComponent}
{...props}
count={2}
/>
</View>
);
}
}

View File

@ -1,30 +0,0 @@
// import { StyleSheet } from 'react-native';
// const styles = StyleSheet.create({
// container: {
// justifyContent: 'center',
// alignItems: 'center',
// },
// marker: {
// position: 'absolute',
// },
// });
// export default styles;
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
layer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
},
});

View File

@ -119,9 +119,9 @@ class BoostContainer extends Component {
_buyItem = async sku => {
const { navigation } = this.props;
await this.setState({ isProcessing: true });
if (sku !== 'freePoints') {
await this.setState({ isProcessing: true });
try {
RNIap.requestPurchase(sku, false);
} catch (err) {

View File

@ -1,15 +1,13 @@
import React, { PureComponent, Fragment } from 'react';
import { injectIntl } from 'react-intl';
import { View, Text } from 'react-native';
import get from 'lodash/get';
import wheel from './wheel.png';
import marker from './marker.png';
import { TouchableOpacity, Text, View } from 'react-native';
// Container
import { PointsContainer } from '../../../containers';
// Components
import { BasicHeader } from '../../../components/basicHeader';
import { SpinGame } from '../../../components';
import { BoostIndicatorAnimation, MainButton } from '../../../components';
// Styles
import styles from './freeEstmStyles';
@ -23,97 +21,50 @@ class FreeEstmScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
rouletteState: '',
option: '',
rouletteCustomState: 'stop',
optionCustom: '',
isSpinning: false,
spinRight: 1,
};
}
_onRotateChange(state) {
this.setState({
rouletteState: state,
});
}
_handleOnSpinPress = () => {
const { spinRight } = this.state;
_onRotate(option) {
this.setState({
option: option.index,
isSpinning: true,
spinRight: spinRight - 1,
});
}
_onRotateCustomChange(state) {
// this.setState({
// rouletteCustomState: state,
// });
}
_onRotateCustom(option) {
this.setState({
optionCustom: option.props.index,
});
}
};
render() {
const { intl } = this.props;
const numbers = [
100,
320,
150,
192,
43,
213,
23,
235,
137,
334,
633,
273,
133,
363,
113,
303,
83,
233,
130,
53,
234,
136,
33,
];
const options = numbers.map(o => ({ index: o }));
const { rouletteCustomState, rouletteState, option, optionCustom } = this.state;
const { isSpinning, spinRight } = this.state;
// const options = numbers.map(o => ({ index: o }));
const customOptions = numbers.map(o => <Text index={o}>{o}</Text>);
return (
<PointsContainer>
{({ isLoading, balance: _balance }) => (
<Fragment>
<BasicHeader title={intl.formatMessage({ id: 'free_estm.title' })} />
<View style={styles.container}>
<Text>{rouletteState}</Text>
<Text>{option}</Text>
<Text>{optionCustom}</Text>
<SpinGame
enableUserRotate={rouletteCustomState === 'stop'}
background={null}
onRotate={_option =>
this.setState({
optionCustom: _option.props.index,
})
}
onRotateChange={state =>
this.setState({
rouletteCustomState: state,
})
}
marker={marker}
options={customOptions}
rotateEachElement={index => ((index * 360) / options.length) * -1 - 90}
markerWidth={20}
/>
<View style={styles.textWrapper}>
{!isSpinning && (
<Fragment>
<Text style={styles.count}>{spinRight}</Text>
<Text style={styles.countDesc}>Spin Left</Text>
</Fragment>
)}
</View>
<View style={styles.spinnerWrapper}>
<BoostIndicatorAnimation isSpinning={isSpinning} />
<View style={{ flex: 1 }}>
{!isSpinning && (
<MainButton
style={{ marginTop: 50 }}
onPress={this._handleOnSpinPress}
text="SPIN & WIN"
/>
)}
</View>
</View>
</View>
</Fragment>
)}

View File

@ -4,6 +4,25 @@ export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
alignItems: 'center',
},
textWrapper: {
flex: 0.2,
backgroundColor: '$primaryBackgroundColor',
justifyContent: 'center',
alignItems: 'center',
},
count: {
fontSize: 72,
fontWeight: '700',
color: '$primaryDarkGray',
},
countDesc: {
color: '$primaryDarkGray',
fontSize: 16,
fontWeight: '700',
},
spinnerWrapper: {
flex: 1,
},
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB