Created
November 14, 2023 02:59
-
-
Save tanner-west/fa54b7c70ab5d02137414ef96bcde589 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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