Skip to content

Instantly share code, notes, and snippets.

@tanner-west
Created October 16, 2023 22:27
Show Gist options
  • Save tanner-west/495bbe7b1895d1006108ec77d3e7cb57 to your computer and use it in GitHub Desktop.
Save tanner-west/495bbe7b1895d1006108ec77d3e7cb57 to your computer and use it in GitHub Desktop.
import { StatusBar } from "expo-status-bar";
import React, { useState } from "react";
import {
FlatList,
View,
Text,
Image,
StyleSheet,
SafeAreaView,
useWindowDimensions,
TouchableOpacity,
} from "react-native";
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
withSequence,
FadeIn,
runOnJS,
FadeOut,
} from "react-native-reanimated";
import {
GestureDetector,
Gesture,
GestureStateChangeEvent,
TapGestureHandlerEventPayload,
} from "react-native-gesture-handler";
import Icon from "@expo/vector-icons/MaterialCommunityIcons";
import IonIcon from "@expo/vector-icons/Ionicons";
const data = [
{
imageSource: require(`../assets/images/feed/feed_3.png`),
description: "We really out here",
profilePic: require(`../assets/images/faces/birds/bird-0.png`),
profileName: "space_lover_67",
},
{
imageSource: require(`../assets/images/feed/feed_7.png`),
description: "Current mood...",
profilePic: require(`../assets/images/faces/birds/bird-3.png`),
profileName: "astro_not",
},
{
imageSource: require(`../assets/images/feed/feed_2.png`),
description: "Look at all this!",
profilePic: require(`../assets/images/faces/birds/bird-8.png`),
profileName: "b1gappet1t3",
},
{
imageSource: require(`../assets/images/feed/feed_8.png`),
description: "look who i met!",
profilePic: require(`../assets/images/faces/birds/bird-5.png`),
profileName: "seen_them",
},
{
imageSource: require(`../assets/images/feed/feed_4.png`),
description: "Wow, great read",
profilePic: require(`../assets/images/faces/birds/bird-2.png`),
profileName: "old_bookz",
},
{
imageSource: require(`../assets/images/feed/feed_1.png`),
description: "Can't stop listening to this album!",
profilePic: require(`../assets/images/faces/birds/bird-7.png`),
profileName: "metalhead7777777",
},
];
const Rocket = ({ x, y }: { x: number; y: number }) => {
const windowWidth = useWindowDimensions().width;
const getRandomRotation = () => {
const min = -Math.PI / 2;
const max = Math.PI / 2;
const rot = Math.random() * (max - min) + min;
const radius = windowWidth;
const xPos = radius * Math.sin(rot) + x;
const yPos = radius * -Math.cos(rot) + y;
return { rot, xPos, yPos };
};
const { rot, xPos, yPos } = getRandomRotation();
const rotation = `${rot}rad`;
const rocketStyle = useAnimatedStyle(() => {
return {
position: "absolute",
left: withSequence(
withTiming(x - 75, { duration: 0 }),
withTiming(xPos, { duration: 1000 })
),
top: withSequence(
withTiming(y - 75, { duration: 0 }),
withTiming(yPos, { duration: 1000 })
),
opacity: withSequence(
withTiming(1, { duration: 0 }),
withTiming(0, { duration: 1000 })
),
transform: [{ rotate: rotation }],
};
});
return (
<Animated.View style={rocketStyle}>
<Icon name="rocket" color="white" size={150} />
</Animated.View>
);
};
const Post = ({
item,
onTap,
}: {
item: {
imageSource: number;
description: string;
profilePic: number;
profileName: string;
};
onTap: (e: GestureStateChangeEvent<TapGestureHandlerEventPayload>) => void;
}) => {
const [isLiked, setIsLiked] = useState(false);
const rocketY = useSharedValue(0);
const rocketX = useSharedValue(0);
const windowWidth = useWindowDimensions().width;
const gesture = Gesture.Tap()
.maxDuration(250)
.numberOfTaps(2)
.onStart((e) => {
rocketX.value = e.absoluteX;
rocketY.value = e.absoluteY;
runOnJS(onTap)(e);
runOnJS(setIsLiked)(!isLiked);
});
return (
<Animated.View>
<View style={styles.container}>
<View style={styles.profileContainer}>
<Image source={item.profilePic} style={styles.profilePic} />
<Text style={styles.profileName}>{item.profileName}</Text>
</View>
<GestureDetector gesture={gesture}>
<Image
source={item.imageSource}
style={{ width: windowWidth, height: windowWidth }}
/>
</GestureDetector>
<View style={styles.icons}>
<TouchableOpacity onPress={() => setIsLiked((isLiked) => !isLiked)}>
{isLiked ? (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
key={Math.random()}
>
<IonIcon
style={{ marginHorizontal: 5 }}
name="md-rocket-sharp"
color="white"
size={40}
/>
</Animated.View>
) : (
<Animated.View entering={FadeIn} exiting={FadeOut}>
<IonIcon
style={{ marginHorizontal: 5 }}
name="md-rocket-outline"
color="white"
size={40}
key={Math.random()}
/>
</Animated.View>
)}
</TouchableOpacity>
<Icon name="comment-outline" color="white" size={40} />
</View>
<Text style={styles.description}>{item.description}</Text>
</View>
</Animated.View>
);
};
const LoveTapScreen = () => {
const [rockets, setRockets] = useState<
Array<{ x: number; y: number; createdAt: number }>
>([]);
const onTap = (e: GestureStateChangeEvent<TapGestureHandlerEventPayload>) => {
const newRockets = [
...rockets.filter((r) => {
return r.createdAt > Date.now() - 1000;
}),
{ x: e.absoluteX, y: e.absoluteY, createdAt: Date.now() },
];
setRockets(newRockets);
};
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#222" }}>
<StatusBar style="light" />
<FlatList
data={data}
keyExtractor={(item) => item.imageSource.toString()}
renderItem={({ item }) => <Post item={item} onTap={onTap} />}
/>
{rockets.map((coords) => (
<Rocket key={coords.createdAt} x={coords.x} y={coords.y} />
))}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginBottom: 30,
},
profileContainer: {
flexDirection: "row",
alignItems: "center",
padding: 10,
},
profilePic: {
width: 50,
height: 50,
borderRadius: 25,
},
profileName: {
marginLeft: 10,
fontWeight: "bold",
color: "white",
},
description: {
color: "white",
marginHorizontal: 5,
},
icons: {
flexDirection: "row",
marginVertical: 10,
},
});
export default LoveTapScreen;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment