Created
November 20, 2023 13:52
-
-
Save tanner-west/1517fd0077b0b74e344fb0344711a5ae to your computer and use it in GitHub Desktop.
This file contains hidden or 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, { useEffect, useState, useRef } from "react"; | |
import { | |
View, | |
SafeAreaView, | |
StyleSheet, | |
Image, | |
useWindowDimensions, | |
} from "react-native"; | |
import Animated, { | |
interpolate, | |
useAnimatedProps, | |
useAnimatedStyle, | |
useSharedValue, | |
withTiming, | |
} from "react-native-reanimated"; | |
import { TextInput } from "react-native-gesture-handler"; | |
import { useInterval, usePrevious } from "../util"; | |
const woodgrainImage = require(`../assets/images/misc/wood-grain.png`); | |
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); | |
const Digit = ({ digit }: { digit: string }) => { | |
const prevDigit = usePrevious(digit); | |
useEffect(() => { | |
progress.value = 0; | |
progress.value = withTiming(1, { duration: 500 }); | |
}, [digit]); | |
const progress = useSharedValue(0); | |
const animatedStyle = useAnimatedStyle(() => { | |
const translateY = interpolate(progress.value, [0, 0.5, 1], [0, -50, -100]); | |
const perspective = interpolate( | |
progress.value, | |
[0, 0.5, 1], | |
[-500, 1, 1000] | |
); | |
const rotX = interpolate(progress.value, [0, 0.5, 1], [0, -45, 0]); | |
const scaleY = interpolate(progress.value, [0, 0.5, 1], [1, 0, 1]); | |
return { | |
transform: [ | |
{ translateY }, | |
{ perspective }, | |
{ rotateX: `${rotX}deg` }, | |
{ scaleY }, | |
], | |
}; | |
}); | |
const topTransform = 57; | |
const bottomTransform = -43; | |
const animatedTextProps = useAnimatedProps(() => { | |
const text = progress.value < 0.5 ? prevDigit : digit; | |
return { | |
text, | |
}; | |
}); | |
const animatedPrevTextProps = useAnimatedProps(() => { | |
const text = progress.value < 1 ? prevDigit : digit; | |
return { | |
text, | |
}; | |
}); | |
const animatedTextStyle = useAnimatedStyle(() => { | |
const progressValue = progress.value; | |
return { | |
transform: [ | |
{ translateY: progressValue < 0.5 ? bottomTransform : topTransform }, | |
], | |
}; | |
}); | |
return ( | |
<> | |
<View style={{ alignItems: "center" }}> | |
<View style={[styles.flap]}> | |
<AnimatedTextInput | |
style={[ | |
styles.text, | |
{ | |
height: 200, | |
transform: [{ translateY: topTransform }], | |
}, | |
]} | |
animatedProps={animatedPrevTextProps} | |
/> | |
</View> | |
<View | |
style={{ | |
width: 120, | |
height: 5, | |
backgroundColor: "black", | |
position: "absolute", | |
zIndex: 200, | |
top: 100, | |
}} | |
></View> | |
<Animated.View | |
style={[ | |
styles.flap, | |
{ | |
zIndex: 100, | |
}, | |
animatedStyle, | |
]} | |
> | |
<AnimatedTextInput | |
style={[ | |
styles.text, | |
{ | |
height: 200, | |
}, | |
animatedTextStyle, | |
]} | |
animatedProps={animatedTextProps} | |
/> | |
</Animated.View> | |
<Animated.View | |
style={[ | |
styles.flap, | |
{ | |
position: "absolute", | |
bottom: 0, | |
}, | |
]} | |
> | |
<AnimatedTextInput | |
value={digit} | |
style={[ | |
styles.text, | |
{ | |
height: 200, | |
transform: [{ translateY: bottomTransform }], | |
}, | |
]} | |
/> | |
</Animated.View> | |
</View> | |
</> | |
); | |
}; | |
export const Flip = () => { | |
const [time, setTime] = useState({ | |
secondsOnes: 0, | |
secondsTens: 0, | |
minutesOnes: 0, | |
minutesTens: 0, | |
hoursOnes: 0, | |
hoursTens: 0, | |
}); | |
const letters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; | |
useInterval(() => { | |
const secondsArray = new Date().getSeconds().toString().split(""); | |
const hourArray = new Date().getHours().toString().split(""); | |
const minutesArray = new Date().getMinutes().toString().split(""); | |
setTime((time) => { | |
return { | |
secondsOnes: | |
secondsArray.length === 1 | |
? parseInt(secondsArray[0]) | |
: parseInt(secondsArray[1]), | |
secondsTens: secondsArray.length === 1 ? 0 : parseInt(secondsArray[0]), | |
minutesOnes: | |
minutesArray.length === 1 | |
? parseInt(minutesArray[0]) | |
: parseInt(minutesArray[1]), | |
minutesTens: minutesArray.length === 1 ? 0 : parseInt(minutesArray[0]), | |
hoursOnes: | |
hourArray.length === 1 | |
? parseInt(hourArray[0]) | |
: parseInt(hourArray[1]), | |
hoursTens: hourArray.length === 1 ? 0 : parseInt(hourArray[0]), | |
}; | |
}); | |
}, 1000); | |
return ( | |
<> | |
<Image | |
source={woodgrainImage} | |
style={{ | |
width: useWindowDimensions().width, | |
height: useWindowDimensions().height, | |
position: "absolute", | |
top: 0, | |
left: 0, | |
}} | |
/> | |
<SafeAreaView | |
style={{ padding: 20, flexDirection: "column", alignItems: "center" }} | |
> | |
<View style={styles.row}> | |
<View style={{ marginRight: 10 }}> | |
<Digit digit={letters[time.hoursTens]} /> | |
</View> | |
<View> | |
<Digit digit={letters[time.hoursOnes]} /> | |
</View> | |
</View> | |
<View style={styles.row}> | |
<View style={{ marginRight: 10 }}> | |
<Digit digit={letters[time.minutesTens]} /> | |
</View> | |
<View> | |
<Digit digit={letters[time.minutesOnes]} /> | |
</View> | |
</View> | |
<View style={styles.row}> | |
<View style={{ marginRight: 10 }}> | |
<Digit digit={letters[time.secondsTens]} /> | |
</View> | |
<View> | |
<Digit digit={letters[time.secondsOnes]} /> | |
</View> | |
</View> | |
</SafeAreaView> | |
</> | |
); | |
}; | |
const styles = StyleSheet.create({ | |
flap: { | |
height: 100, | |
width: 180, | |
overflow: "hidden", | |
backgroundColor: "black", | |
borderRadius: 10, | |
padding: 10, | |
alignItems: "center", | |
justifyContent: "center", | |
}, | |
row: { flexDirection: "row", marginBottom: 20 }, | |
text: { | |
fontSize: 180, | |
verticalAlign: "middle", | |
lineHeight: 210, | |
color: "white", | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment