Skip to content

Commit

Permalink
feat(apps/mobile): add animated cat button
Browse files Browse the repository at this point in the history
  • Loading branch information
hassankhan committed Oct 14, 2024
1 parent 7f8129f commit dae7cff
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 9 deletions.
165 changes: 165 additions & 0 deletions apps/mobile/src/components/CatButton/CatButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { CatEarL } from './CatEarL';
import { CatEarR } from './CatEarR';
import { CatHead } from './CatHead';
import { CatNose } from './CatNose';
import { CatEye } from './CatEye';
import { GestureResponderEvent, Pressable, View } from 'react-native';
import Animated, {
interpolate,
useAnimatedStyle,
useSharedValue, withDelay,
withRepeat,
withSequence, withSpring, withTiming
} from 'react-native-reanimated';
import { useCallback, useEffect } from 'react';

type CatButtonProps = {
onPress: ((event: GestureResponderEvent) => void) | null | undefined;
}

const CatButton = ({ onPress }: CatButtonProps) => {

const xVal = useSharedValue(-1);
const yVal = useSharedValue(0);
const blinkVal = useSharedValue(1);
const boopVal = useSharedValue(0);

const idle = useCallback(() => {
'worklet';
xVal.value = withRepeat(
withSequence(
withDelay(2000 + Math.random() * 2000, withSpring(-1 + Math.random() * 2)),
withDelay(2000 + Math.random() * 3000, withSpring(0)),
withDelay(2000 + Math.random() * 2000, withSpring(-1 + Math.random() * 2)),
withDelay(2000 + Math.random() * 3000, withSpring(0)),
),
0
);
yVal.value = withRepeat(
withSequence(
withDelay(2000 + Math.random() * 3000, withSpring(-1 + Math.random() * 2)),
withDelay(2000 + Math.random() * 3000, withSpring(0)),
withDelay(2000 + Math.random() * 3000, withSpring(-1 + Math.random() * 2)),
withDelay(2000 + Math.random() * 3000, withSpring(0)),
),
0
);
blinkVal.value = withRepeat(
withSequence(
withSpring(0.9),
withTiming(0, { duration: 50 }),
withTiming(1, { duration: 100 }),
withDelay(2000, withSpring(0.5)),
withDelay(1000, withSpring(0.9)),
withTiming(0, { duration: 50 }),
withTiming(1, { duration: 100 }),
withDelay(2000, withSpring(0.8)),
),
0
)
}, [blinkVal, xVal, yVal]);

const boop = useCallback(() => {
'worklet';
xVal.value = withSpring(0);
yVal.value = 1;
yVal.value = withSpring(-2, { stiffness: 600 });
blinkVal.value = withTiming(0.2);
boopVal.value = withSequence(
withTiming(1, { duration: 50 }),
withTiming(0, { duration: 50 }),
);

setTimeout(() => {
idle();
if (onPress) {
// @ts-ignore
onPress();
}
}, 400);
}, [blinkVal, idle, xVal, yVal])

useEffect(() => {
idle();
}, [idle]);

const noseStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateY: interpolate(yVal.value, [-1, 1], [20, -20]) },
{ translateX: interpolate(xVal.value, [-1, 1], [-50, 30]) }
]
}
});

const leftEyeStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateY: interpolate(yVal.value, [-1, 1], [-30, -50]) },
{ translateX: interpolate(xVal.value, [-1, 1], [-100, -60]) },
{ scaleY: interpolate(blinkVal.value, [0, 1], [0, 1]) }
]
}
});

const rightEyeStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateY: interpolate(yVal.value, [-1, 1], [-30, -50]) },
{ translateX: interpolate(xVal.value, [-1, 1], [60, 100]) },
{ scaleY: interpolate(blinkVal.value, [0, 1], [0, 1]) }
]
}
});

const leftEarStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: -160 },
{ translateY: interpolate(yVal.value, [-1, 1], [-160, -140]) },
{ scale: interpolate(xVal.value, [-1, 1], [0.95, 1.05]) }
]
}
});

const rightEarStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: 60 },
{ translateY: interpolate(yVal.value, [-1, 1], [-160, -140]) },
{ scale: interpolate(xVal.value, [-1, 1], [1.05, 0.95]) }
]
}
});

return (
<Pressable onPress={boop}>
<View style={{ flex: 1, transform: [ { scale: 0.5 }], shadowOpacity: 1, shadowRadius: 10, shadowColor: '#000' }}>

<Animated.View style={[leftEarStyle, { position: 'absolute' }]}>
<CatEarL stroke={'#444'} strokeWidth={5} color={'#000'} secondaryColor={'#fff'} />
</Animated.View>

<Animated.View style={[rightEarStyle, { position: 'absolute' }]}>
<CatEarR stroke={'#444'} strokeWidth={5} color={'#000'} secondaryColor={'#fff'} />
</Animated.View>

<CatHead stroke={'#777'} strokeWidth={5} color={'#000'} style={{ position: 'absolute', transform: [ { translateX: -140 }, { translateY: -100 }]}}/>

<Animated.View style={[noseStyle, { position: 'absolute' }]}>
<CatNose color={'#fff'} />
</Animated.View>

<Animated.View style={[leftEyeStyle, { position: 'absolute', transform: [ { translateX: -80 }, { translateY: -50 }]}]}>
<CatEye color={'#fff'} />
</Animated.View>

<Animated.View style={[rightEyeStyle, { position: 'absolute', transform: [ { translateX: 80 }, { translateY: -40 }]}]}>
<CatEye color={'#fff'} />
</Animated.View>
</View>
</Pressable>
)
}

export default CatButton;
17 changes: 17 additions & 0 deletions apps/mobile/src/components/CatButton/CatEarL.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
import { memo } from "react"
const SvgComponent = (props: SvgProps & { secondaryColor: string }) => (
<Svg width={115} height={149} fill="none" {...props}>
<Path
fill={props.color}
d="M25.215 97.155c9.53 51.779 26.273 48.698 40.45 46.089 14.178-2.609 45.089-22.487 36.926-50.55C94.429 64.63 26.657 9.136 26.657 9.136s-10.971 36.24-1.442 88.02Z"
/>
<Path
fill={props.secondaryColor}
d="M31.832 81.034c4.418 28.843 6.199 25.275 18.125 16.743 8.51-6.088 20.82-14.445 17.095-30.068C63.326 52.086 32.154 31.95 32.154 31.95s-4.74 20.242-.322 49.084Z"
/>
</Svg>
);
const Memo = memo(SvgComponent)
export { Memo as CatEarL }
17 changes: 17 additions & 0 deletions apps/mobile/src/components/CatButton/CatEarR.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
import { memo } from "react"
const SvgComponent = (props: SvgProps & { secondaryColor: string }) => (
<Svg width={116} height={149} fill="none" {...props}>
<Path
fill={props.color}
d="M90.075 97.038c-9.53 51.779-26.273 48.698-40.45 46.089-14.178-2.609-45.09-22.487-36.926-50.55C20.862 64.512 88.633 9.017 88.633 9.017s10.971 36.241 1.442 88.02Z"
/>
<Path
fill={props.secondaryColor}
d="M82.873 81.034c-4.418 28.843-6.199 25.275-18.126 16.743-8.51-6.088-20.82-14.445-17.094-30.068C51.378 52.086 82.55 31.95 82.55 31.95s4.74 20.242.323 49.084Z"
/>
</Svg>
);
const Memo = memo(SvgComponent)
export { Memo as CatEarR }
10 changes: 10 additions & 0 deletions apps/mobile/src/components/CatButton/CatEye.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from "react"
import Svg, { SvgProps, Circle } from "react-native-svg"
import { memo } from "react"
const SvgComponent = (props: SvgProps) => (
<Svg width={19} height={19} fill="none" {...props}>
<Circle cx={9.5} cy={9.5} r={9.5} fill={props.color} />
</Svg>
);
const Memo = memo(SvgComponent)
export { Memo as CatEye }
19 changes: 19 additions & 0 deletions apps/mobile/src/components/CatButton/CatHead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
import { memo } from "react"
const SvgComponent = (props: SvgProps) => (
<Svg
width={297}
height={246}
scale={[0.5]}
fill="none"
{...props}
>
<Path
fill={props.color}
d="M296.29 128c0 39.785-15.699 43.904-41.239 70.5-26.933 28.044-64.808 47.5-106.761 47.5-41.953 0-79.828-17.456-106.76-45.5C15.988 173.904.29 167.785.29 128c0-81.738 66.262-128 148-128s148 46.262 148 128Z"
/>
</Svg>
)
const Memo = memo(SvgComponent)
export { Memo as CatHead }
21 changes: 21 additions & 0 deletions apps/mobile/src/components/CatButton/CatNose.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
import { memo } from 'react';
import Animated from 'react-native-reanimated';

const AnimatedPath = Animated.createAnimatedComponent(Path);
const SvgComponent = (props: SvgProps) => {

return (
<Svg width={51} height={32} fill="none" {...props}>
<AnimatedPath
fill={`rgb(99, 68, 108)`}
d="M50.133 13c0 6.57-12.33 6.72-16.519 11.165-4.561 4.84-1.283 7.689-8.48 7.689-7.198 0-3.92-2.85-8.482-7.69C12.464 19.72.133 19.57.133 13 .133-.727 12-1.5 25.133 1.854 39.5-1 50.133-.727 50.133 13Z"
/>
</Svg>
);

}

const Memo = memo(SvgComponent);
export { Memo as CatNose }
13 changes: 13 additions & 0 deletions apps/mobile/src/components/CatButton/CatSnout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
import { memo } from "react"
const SvgComponent = (props: SvgProps) => (
<Svg width={189} height={116} fill="none" {...props}>
<Path
fill={props.color}
d="M189 58.791c0 18.274-10.024 20.165-26.332 32.381C145.472 104.053 121.287 116 94.5 116c-26.787 0-50.971-11.028-68.168-23.91C10.024 79.876 0 77.066 0 58.792 0 21.248 42.31 0 94.5 0 146.691 0 189 21.248 189 58.791Z"
/>
</Svg>
);
const Memo = memo(SvgComponent)
export { Memo as CatSnout }
12 changes: 3 additions & 9 deletions apps/mobile/src/components/UploadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { ComponentProps } from 'react';
import { Pressable, StyleProp, View, ViewStyle } from 'react-native';
import { Pressable, StyleProp, ViewStyle } from 'react-native';
import { createStyleSheet, useStyles } from 'react-native-unistyles';
import CatButton from './CatButton/CatButton';

export type UploadButtonProps = ComponentProps<typeof Pressable>;

Expand All @@ -10,13 +10,7 @@ export const UploadButton = ({ style, ...props }: UploadButtonProps) => {

return (
<Pressable style={[styles.root, style as StyleProp<ViewStyle>]} {...props}>
<View>
<View style={styles.leftEar}></View>
<View style={styles.rightEar}></View>
<View style={styles.button}>
<FontAwesome size={28} name="camera" color="white" />
</View>
</View>
<CatButton onPress={props.onPress} />
</Pressable>
);
};
Expand Down

0 comments on commit dae7cff

Please sign in to comment.