Skip to content

Instantly share code, notes, and snippets.

@tanner-west
Created November 14, 2023 02:59
Show Gist options
  • Save tanner-west/fa54b7c70ab5d02137414ef96bcde589 to your computer and use it in GitHub Desktop.
Save tanner-west/fa54b7c70ab5d02137414ef96bcde589 to your computer and use it in GitHub Desktop.
import React, { useState } from "react";
import { View, useWindowDimensions, Text, SafeAreaView } from "react-native";
import {
Canvas,
Fill,
Image,
useImage,
BackdropFilter,
Blur,
useTouchHandler,
ClipDef,
SkImage,
} from "@shopify/react-native-skia";
import Animated, {
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withDelay,
withTiming,
} from "react-native-reanimated";
import PagerView from "react-native-pager-view";
import { Ionicons } from "@expo/vector-icons";
const forecastData = [
[
{
delay: 0,
text: "Monday",
icon: "md-partly-sunny-outline",
high: 100,
low: 80,
},
{
delay: 50,
text: "Tuesday",
icon: "md-sunny-outline",
high: 99,
low: 81,
},
{
delay: 100,
text: "Wednesday",
icon: "md-rainy-outline",
high: 88,
low: 77,
},
{
delay: 150,
text: "Thursday",
icon: "md-cloudy-outline",
high: 101,
low: 90,
},
{
delay: 200,
text: "Friday",
icon: "md-sunny-outline",
high: 90,
low: 80,
},
],
[
{
delay: 0,
text: "Monday",
icon: "md-partly-sunny-outline",
high: 45,
low: 30,
},
{
delay: 50,
text: "Tuesday",
icon: "md-sunny-outline",
high: 40,
low: 29,
},
{
delay: 100,
text: "Wednesday",
icon: "md-rainy-outline",
high: 41,
low: 32,
},
{
delay: 150,
text: "Thursday",
icon: "md-cloudy-outline",
high: 39,
low: 21,
},
{
delay: 200,
text: "Friday",
icon: "md-sunny-outline",
high: 30,
low: 19,
},
],
[
{
delay: 0,
text: "Monday",
icon: "md-partly-sunny-outline",
high: 72,
low: 60,
},
{
delay: 50,
text: "Tuesday",
icon: "md-sunny-outline",
high: 75,
low: 68,
},
{
delay: 100,
text: "Wednesday",
icon: "md-rainy-outline",
high: 68,
low: 62,
},
{
delay: 150,
text: "Thursday",
icon: "md-cloudy-outline",
high: 73,
low: 68,
},
{
delay: 200,
text: "Friday",
icon: "md-sunny-outline",
high: 80,
low: 71,
},
],
];
const Forecast: React.FC<{ forecastData: any[] }> = ({ forecastData }) => {
return (
<>
{forecastData.map((data) => (
<AnimatedText
key={data.text}
delay={data.delay}
text={data.text}
iconName={data.icon}
high={data.high}
low={data.low}
/>
))}
</>
);
};
const Location = ({
imageSrc,
name,
forecastData,
temp,
icon,
}: {
imageSrc: SkImage | null;
name: string;
forecastData: any[];
temp: number;
icon: string;
}) => {
const [expanded, setExpanded] = useState(false);
const screenWidth = useWindowDimensions().width;
const screenHeight = useWindowDimensions().height;
const r = 36;
const cardWidth = screenWidth - 60;
const cardY = screenHeight - 130;
const duration = 250;
const iconY = useSharedValue(screenHeight - 110);
const iconX = useSharedValue(screenWidth * 0.5 - 15);
const textY = useSharedValue(screenHeight - 100);
const textX = useSharedValue(60);
const tempTextY = useSharedValue(screenHeight - 100);
const tempTextX = useSharedValue(screenWidth * 0.6);
const iconSize = useSharedValue(1);
const rectWidth = useSharedValue(cardWidth);
const rectHeight = useSharedValue(100);
const rectX = useSharedValue(30);
const rectY = useSharedValue(cardY);
const textFont = useSharedValue(30);
const animatedIconStyles = useAnimatedStyle(() => {
return {
position: "absolute",
top: iconY.value,
left: iconX.value,
transform: [{ scale: iconSize.value }],
};
});
const animatedTextStyle = useAnimatedStyle(() => {
return {
position: "absolute",
fontSize: textFont.value,
};
});
const animatedTempStyle = useAnimatedStyle(() => {
return {
position: "absolute",
fontSize: textFont.value,
};
});
const animatedTextContainerStyle = useAnimatedStyle(() => {
return {
top: textY.value,
left: textX.value,
};
});
const animatedTempContainerStyle = useAnimatedStyle(() => {
return {
top: tempTextY.value,
left: tempTextX.value,
};
});
const onTouch = useTouchHandler({
onEnd: (_event) => {
if (rectHeight.value === screenHeight) {
setExpanded(false);
rectHeight.value = withTiming(100, { duration });
rectWidth.value = withTiming(cardWidth, { duration });
rectX.value = withTiming(30, { duration });
rectY.value = withTiming(cardY, { duration });
iconY.value = withTiming(screenHeight - 110, { duration });
iconX.value = withTiming(screenWidth * 0.5 - 15, { duration });
textY.value = withTiming(screenHeight - 100, { duration });
textX.value = withTiming(60, { duration });
tempTextX.value = withTiming(screenWidth * 0.6, { duration });
tempTextY.value = withTiming(screenHeight - 100, { duration });
iconSize.value = withTiming(1, { duration });
textFont.value = withTiming(30, { duration });
} else {
setExpanded(true);
rectHeight.value = withTiming(screenHeight, { duration });
rectWidth.value = withTiming(screenWidth, { duration });
rectX.value = withTiming(0, { duration });
rectY.value = withTiming(0, { duration });
textX.value = withTiming(screenWidth / 2 - 50, { duration });
textY.value = withTiming(75, { duration });
iconX.value = withTiming(screenWidth / 2 - 20, { duration });
iconY.value = withTiming(165, { duration });
tempTextX.value = withTiming(screenWidth / 2 - 50, { duration });
tempTextY.value = withTiming(250, { duration });
iconSize.value = withTiming(2, { duration });
textFont.value = withTiming(40, { duration });
}
},
});
const rectClip = useDerivedValue<ClipDef>(() => {
return {
rect: {
x: rectX.value,
y: rectY.value,
width: rectWidth.value,
height: rectHeight.value,
},
ry: r,
rx: r,
};
});
return (
<View
key={1}
style={{
flex: 1,
}}
>
<Canvas
style={{
width: screenWidth,
height: screenHeight,
position: "absolute",
}}
onTouch={onTouch}
>
<Image
image={imageSrc}
fit={"cover"}
width={screenWidth}
height={screenHeight}
/>
<BackdropFilter clip={rectClip} filter={<Blur blur={8} />}>
<Fill color="rgba(0, 0, 0, 0.5)" />
</BackdropFilter>
</Canvas>
<SafeAreaView
style={{
position: "absolute",
width: screenWidth,
top: screenHeight / 2.5,
}}
>
{expanded ? <Forecast forecastData={forecastData} /> : null}
</SafeAreaView>
<Animated.View style={animatedIconStyles}>
<Ionicons name={icon} size={50} color="white" />
</Animated.View>
<Animated.View
style={[
animatedTextContainerStyle,
{ flexDirection: "row", justifyContent: "center", width: 100 },
]}
>
<Animated.Text
style={[animatedTextStyle, { color: "white", textAlign: "center" }]}
>
{name}
</Animated.Text>
</Animated.View>
<Animated.View
style={[
animatedTempContainerStyle,
{ flexDirection: "row", justifyContent: "center", width: 100 },
]}
>
<Animated.Text
style={[animatedTempStyle, { color: "white", textAlign: "center" }]}
>
{temp}°
</Animated.Text>
</Animated.View>
</View>
);
};
const AnimatedText = ({
delay,
text,
iconName,
high,
low,
}: {
delay: number;
text: string;
iconName: string;
high: number;
low: number;
}) => {
const animation = () => {
"worklet";
const animations = {
opacity: withDelay(delay, withTiming(1, { duration: 1000 })),
};
const initialValues = {
opacity: 0,
};
return {
initialValues,
animations,
};
};
return (
<Animated.View
entering={animation}
style={{
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
paddingHorizontal: 10,
marginBottom: 10,
}}
>
<Text style={{ color: "white", fontSize: 30, flex: 1.5 }}>{text}</Text>
<View
style={{
flex: 0.5,
flexDirection: "row",
justifyContent: "flex-start",
}}
>
<Ionicons name={iconName} size={30} color="white" />
</View>
<View
style={{
flex: 1,
justifyContent: "space-around",
alignItems: "center",
flexDirection: "row",
}}
>
<Text
style={{
color: "white",
fontSize: 30,
textAlign: "center",
}}
>
{low}°
</Text>
<Ionicons name={"caret-forward-outline"} size={25} color="white" />
<Text
style={{
color: "white",
fontSize: 30,
textAlign: "center",
}}
>
{high}°
</Text>
</View>
</Animated.View>
);
};
const Cloudy = () => {
const desertImage = useImage(require("../assets/images/scenes/desert.png"));
const alpineImage = useImage(require("../assets/images/scenes/alpine.png"));
const forestImage = useImage(require("../assets/images/scenes/forest.png"));
return (
<PagerView style={{ flex: 1 }}>
<Location
imageSrc={alpineImage}
name={"Zermatt"}
forecastData={forecastData[1]}
temp={35}
icon={"md-cloudy-outline"}
/>
<Location
imageSrc={desertImage}
name={"Moab"}
forecastData={forecastData[0]}
temp={100}
icon={"md-sunny-outline"}
/>
<Location
imageSrc={forestImage}
name={"Ainokura"}
forecastData={forecastData[2]}
temp={72}
icon={"md-partly-sunny-outline"}
/>
</PagerView>
);
};
export default Cloudy;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment