import React from 'react';
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome5';
import { fontStyles } from '../../../styles/common';
import Svg, { Circle } from 'react-native-svg';
import Animated, {
useSharedValue,
useAnimatedProps,
withTiming,
runOnJS,
useAnimatedStyle,
useAnimatedReaction,
interpolate,
Extrapolate,
runOnUI,
} from 'react-native-reanimated';
import { mockTheme, useAppThemeFromContext } from '../../../util/theme';
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
const radius = 14;
const strokeWidth = 2;
const iconSize = radius - 4;
const innerRadius = radius - strokeWidth / 2;
const circumference = 2 * Math.PI * innerRadius;
const animationDuration = 1200;
const createStyles = (colors: any) =>
StyleSheet.create({
container: {
backgroundColor: colors.primary.default,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 99,
},
progressContainer: {
height: radius * 2,
width: radius * 2,
marginRight: 12,
},
absoluteFillWithCenter: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
justifyContent: 'center',
},
absoluteFill: {
...StyleSheet.absoluteFillObject,
},
preCompletedContainerStyle: {
...StyleSheet.absoluteFillObject,
borderRadius: radius,
backgroundColor: colors.primary.default,
},
outerCircle: {
...StyleSheet.absoluteFillObject,
borderRadius: radius,
backgroundColor: colors.primary.inverse,
},
innerCircle: {
flex: 1,
borderRadius: radius - strokeWidth,
margin: strokeWidth,
backgroundColor: colors.primary.default,
},
label: {
color: colors.primary.inverse,
fontSize: 18,
...(fontStyles.normal as any),
},
animatedCircle: {
transform: [
{
rotate: '-90deg',
},
],
},
});
interface Props {
onLongPress: () => void;
label: string;
}
const ButtonReveal = ({ onLongPress, label }: Props) => {
// Values for animating the stroke
const progressOrigin = useSharedValue(innerRadius * 2 * Math.PI);
const progressDestination = useSharedValue(0);
const preCompleteControl = useSharedValue(progressOrigin.value);
// Value for animating the icon & button
const pressControl = useSharedValue(1);
// Value for animating the progress container
const progressContainerOpacity = useSharedValue(1);
// Value for scaling down the progress container
const postCompleteControl = useSharedValue(0);
// Colors
const { colors } = useAppThemeFromContext() || mockTheme;
const styles = createStyles(colors);
// Animate SVG via props
const animatedProps = useAnimatedProps(() => ({
strokeDashoffset: preCompleteControl.value,
}));
const resetAnimatedValues = () => {
'worklet';
progressOrigin.value = innerRadius * 2 * Math.PI;
progressDestination.value = 0;
preCompleteControl.value = innerRadius * 2 * Math.PI;
pressControl.value = 1;
postCompleteControl.value = withTiming(
0,
{
duration: 300,
},
() => {
progressContainerOpacity.value = withTiming(1, {
duration: 150,
});
},
);
};
// Reset button to original state
const resetButton = () =>
setTimeout(() => {
runOnUI(resetAnimatedValues)();
}, 1500);
// Post animation from long press
useAnimatedReaction(
() => preCompleteControl.value,
(val) => {
if (val === progressDestination.value) {
// Trigger post long press animation
progressContainerOpacity.value = 0;
postCompleteControl.value = withTiming(1, {
duration: 400,
});
pressControl.value = withTiming(1, {
duration: 400,
});
}
},
);
// Trigger action from long press
useAnimatedReaction(
() => postCompleteControl.value,
(val) => {
if (val === 1) {
// Trigger long press action
runOnJS(onLongPress)();
runOnJS(resetButton)();
}
},
);
// Button is pressed
const triggerPressStart = () => {
const duration =
(preCompleteControl.value / progressOrigin.value) * animationDuration;
preCompleteControl.value = withTiming(progressDestination.value, {
duration,
});
pressControl.value = withTiming(0, {
duration: 200,
});
};
// Button is released
const triggerPressEnd = () => {
const duration =
((progressOrigin.value - preCompleteControl.value) /
progressOrigin.value) *
animationDuration;
preCompleteControl.value = withTiming(progressOrigin.value, {
duration,
});
pressControl.value = withTiming(1, {
duration: 400,
});
};
const outerCircleStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(
postCompleteControl.value,
[0, 0.5],
[1, 0],
Extrapolate.CLAMP,
),
},
],
}));
const innerCircleStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(
postCompleteControl.value,
[0, 0.5],
[1, 0],
Extrapolate.CLAMP,
),
},
],
}));
const preCompletedContainerStyle = useAnimatedStyle(() => ({
opacity: progressContainerOpacity.value,
}));
const lockIconStyle = useAnimatedStyle(() => ({
opacity: pressControl.value,
// transform: [{ scale: pressControl.value }],
}));
const checkIconStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(
postCompleteControl.value,
[0.5, 1],
[0, 1],
Extrapolate.CLAMP,
),
},
],
}));
const containerStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(
pressControl.value,
[0, 1],
[0.97, 1],
Extrapolate.CLAMP,
),
},
],
}));
const renderPostCompletedContent = () => (
);
const renderPreCompletedContent = () => (
);
return (
{renderPostCompletedContent()}
{renderPreCompletedContent()}
{label}
);
};
export default ButtonReveal;