Skip to content

Commit

Permalink
fix: confettis
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud AMBROSELLI committed Jul 24, 2024
1 parent 37b034d commit 133bfd9
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 133 deletions.
2 changes: 1 addition & 1 deletion expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"expo-constants": "^16.0.2",
"expo-dev-client": "^4.0.20",
"expo-font": "^12.0.7",
"expo-image": "~1.12.13",
"expo-linking": "^6.3.1",
"expo-notifications": "^0.28.10",
"expo-splash-screen": "^0.27.5",
Expand All @@ -62,7 +63,6 @@
"react-native-awesome-slider": "^2.5.3",
"react-native-device-info": "^11.1.0",
"react-native-draggable-flatlist": "^4.0.1",
"react-native-fast-image": "^8.6.3",
"react-native-gesture-handler": "~2.16.1",
"react-native-get-random-values": "^1.11.0",
"react-native-in-app-review": "^4.3.3",
Expand Down
137 changes: 75 additions & 62 deletions expo/src/components/Confettis.js
Original file line number Diff line number Diff line change
@@ -1,106 +1,119 @@
import React, { useEffect } from "react";
import Animated, { useSharedValue, useAnimatedStyle } from "react-native-reanimated";
import React from "react";
import { View, Dimensions, StyleSheet } from "react-native";
import FastImage from "react-native-fast-image";
import Animated, {
useAnimatedStyle,
useSharedValue,
withDelay,
withRepeat,
withSequence,
withTiming,
Easing,
} from "react-native-reanimated";
import { Image } from "expo-image";
import ConfettiImage from "../assets/images/confetti.png";

const NUM_CONFETTI = 100;
const COLORS = ["#107ed5", "#DE285E", "#39CEC1", "#09aec5", "#FCBC49"];
const CONFETTI_SIZE = 16;

const Confettis = () => {
const { width: screenWidth, height: screenHeight } = Dimensions.get("screen");
const confettiProps = Array.from({ length: NUM_CONFETTI }, (_, i) => ({
initialX: Math.random() * screenWidth,
initialY: -screenHeight - 100, // Start off-screen
xVel: Math.random() * 400 - 200,
yVel: Math.random() * 150 + 400,
angleVel: (Math.random() * 3 - 1.5) * Math.PI,
delay: Math.floor(i / 15) * 0.3,
elasticity: Math.random() * 0.3 + 0.1,
color: COLORS[i % COLORS.length],
}));
const { width: screenWidth, height: screenHeight } = Dimensions.get("window");

return (
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
{confettiProps.map((props, index) => (
<Confetti key={index} {...props} />
<View style={styles.container}>
{Array.from({ length: NUM_CONFETTI }).map((_, index) => (
<Confetti
key={index}
screenWidth={screenWidth}
screenHeight={screenHeight}
color={COLORS[index % COLORS.length]}
delay={index * 50}
/>
))}
</View>
);
};
const Confetti = ({ initialX, initialY, xVel, yVel, angleVel, delay, elasticity, color }) => {
const { width: screenWidth } = Dimensions.get("screen");
const x = useSharedValue(initialX);
const y = useSharedValue(initialY);
const angle = useSharedValue(0); // Assuming initial angle is 0

useEffect(() => {
let frameId;
const Confetti = ({ screenWidth, screenHeight, color, delay }) => {
const x = useSharedValue(Math.random() * screenWidth);
const y = useSharedValue(-screenHeight); // Start well above the screen
const rotation = useSharedValue(0);

const animate = () => {
"worklet";
if (delay > 0) {
// eslint-disable-next-line react-hooks/exhaustive-deps
delay -= 1 / 60; // Assuming 60 FPS
} else {
x.value += xVel / 60;
y.value += yVel / 60;
angle.value += angleVel / 60;
// Randomize horizontal movement
const amplitude = Math.random() * 50 + 50; // Random amplitude between 50 and 250
const period = Math.random() * 2000 + 1000; // Random period between 1000 and 3000 ms

// Check and handle left and right boundaries
if (x.value > screenWidth - CONFETTI_SIZE) {
x.value = screenWidth - CONFETTI_SIZE; // Adjust position
// eslint-disable-next-line react-hooks/exhaustive-deps
xVel *= -elasticity; // Reverse velocity
} else if (x.value < 0) {
x.value = 0; // Adjust position
xVel *= -elasticity; // Reverse velocity
}
}
React.useEffect(() => {
y.value = withDelay(
delay,
withTiming(screenHeight + CONFETTI_SIZE, {
duration: 4000, // 10 seconds for a slower fall
easing: Easing.linear,
})
);

frameId = requestAnimationFrame(animate);
};

frameId = requestAnimationFrame(animate);
x.value = withDelay(
delay,
withRepeat(
withSequence(
withTiming(x.value - amplitude, {
duration: period,
easing: Easing.inOut(Easing.ease),
}),
withTiming(x.value + amplitude, {
duration: period,
easing: Easing.inOut(Easing.ease),
})
),
-1,
true
)
);

return () => {
if (frameId) {
cancelAnimationFrame(frameId);
}
};
rotation.value = withDelay(
delay,
withRepeat(
withTiming(2 * Math.PI, {
duration: 2000,
easing: Easing.linear,
}),
-1
)
);
}, []);

const animatedStyles = useAnimatedStyle(() => {
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: x.value },
{ translateY: y.value },
{ rotate: `${angle.value}rad` },
{ rotate: `${rotation.value}rad` },
],
backgroundColor: color,
};
});

return (
<Animated.View style={[styles.confettiContainer, animatedStyles]}>
<FastImage source={ConfettiImage} style={styles.confetti} />
<Animated.View style={[styles.confetti, animatedStyle]}>
<Image source={ConfettiImage} style={styles.confettiImage} />
</Animated.View>
);
};

const styles = StyleSheet.create({
confettiContainer: {
position: "absolute",
width: CONFETTI_SIZE,
height: CONFETTI_SIZE,
top: 0,
left: 0,
container: {
...StyleSheet.absoluteFillObject,
pointerEvents: "none",
},
confetti: {
position: "absolute",
width: CONFETTI_SIZE,
height: CONFETTI_SIZE,
},
confettiImage: {
width: "100%",
height: "100%",
},
});

export default Confettis;
39 changes: 23 additions & 16 deletions expo/src/components/ModalGainDetails.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { View, TouchableOpacity, Text } from 'react-native';
import Modal from './Modal';
import { hitSlop } from '../styles/theme';
import Confetti from './Confettis';
import React from "react";
import Svg, { Path } from "react-native-svg";
import { View, TouchableOpacity, Text } from "react-native";
import Modal from "./Modal";
import { hitSlop } from "../styles/theme";
import Confettis from "./Confettis";

const ModalGainDetails = ({ content, onClose }) => {
const firstDayMonth = content?.firstDay?.split(' ')[1];
const lastDayMonth = content?.lastDay?.split(' ')[1];
const firstDayDisplay = firstDayMonth === lastDayMonth ? content?.firstDay?.split(' ')[0] : content?.firstDay;
const caloriesTitle = content?.weekKcal <= content?.estimationKcal ? 'KCalories évitées' : 'KCalories en plus';
const eurosTitle = content?.weekExpenses <= content?.estimationExpenses ? 'Euros épargnés' : 'Euros non-épargnés';
const firstDayMonth = content?.firstDay?.split(" ")[1];
const lastDayMonth = content?.lastDay?.split(" ")[1];
const firstDayDisplay =
firstDayMonth === lastDayMonth ? content?.firstDay?.split(" ")[0] : content?.firstDay;
const caloriesTitle =
content?.weekKcal <= content?.estimationKcal ? "KCalories évitées" : "KCalories en plus";
const eurosTitle =
content?.weekExpenses <= content?.estimationExpenses ? "Euros épargnés" : "Euros non-épargnés";
return (
<Modal visible={!!content} animationType="fade" withBackground hideOnTouch>
<View className="bg-white rounded-xl min-w-full">
Expand Down Expand Up @@ -60,7 +63,9 @@ const ModalGainDetails = ({ content, onClose }) => {
<View className="py-2 mt-3 bg-[#F5F6FA] rounded-md">
<Text className="text-center text-[#939EA6] text-xs">{eurosTitle}</Text>
<View className={`mx-auto px-2 py-1 rounded-md mt-2 ${content.eurosColor}`}>
<Text className="text-center text-white font-bold text-xl">{content.savedExpenses}</Text>
<Text className="text-center text-white font-bold text-xl">
{content.savedExpenses}
</Text>
</View>
</View>
<View className="flex flex-row justify-center">
Expand Down Expand Up @@ -90,7 +95,9 @@ const ModalGainDetails = ({ content, onClose }) => {
<Text className="text-center text-[#939EA6] text-xs">{caloriesTitle}</Text>
<View
className={`flex flex-row justify-center mx-auto px-2 py-1 rounded-md mt-2 items-baseline ${content.kcalsColor}`}>
<Text className="text-center font-bold text-xl text-white">{content?.savedKcal}</Text>
<Text className="text-center font-bold text-xl text-white">
{content?.savedKcal}
</Text>
<Text className="text-center font-bold text-base text-white"> KCAL</Text>
</View>
</View>
Expand All @@ -105,9 +112,9 @@ const ModalGainDetails = ({ content, onClose }) => {
</View>
</View>

{eurosTitle === 'Euros épargnés' && caloriesTitle === 'KCalories évitées' && content?.isWeekCompleted && (
<Confetti run={true} />
)}
{eurosTitle === "Euros épargnés" &&
caloriesTitle === "KCalories évitées" &&
content?.isWeekCompleted && <Confettis run={true} />}
</Modal>
);
};
Expand Down
Loading

0 comments on commit 133bfd9

Please sign in to comment.