Skip to content

Instantly share code, notes, and snippets.

@tanner-west
Created November 20, 2023 13:52
Show Gist options
  • Save tanner-west/1517fd0077b0b74e344fb0344711a5ae to your computer and use it in GitHub Desktop.
Save tanner-west/1517fd0077b0b74e344fb0344711a5ae to your computer and use it in GitHub Desktop.
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