1 Commits

Author SHA1 Message Date
mathysPaul
1d45a6b934 [IMP] InteractiveCC: split in InteractiveBase & AnimatedBase 2024-01-09 17:18:14 +01:00
5 changed files with 348 additions and 20 deletions

View File

@@ -0,0 +1,128 @@
import React, { useRef, useEffect } from 'react';
import { Animated, StyleProp, StyleSheet, ViewStyle } from 'react-native';
type StyleObject = Record<string, any>;
type InterpolatedStyleObject = Record<string, Animated.AnimatedInterpolation<any>>;
interface AnimatedBaseProps {
style?: StyleObject;
defaultStyle: StyleObject;
hoverStyle: StyleObject;
pressStyle: StyleObject;
currentState: number;
duration?: number;
children?: React.ReactNode;
styleContainer?: StyleProp<ViewStyle>;
}
const AnimatedBase: React.FC<AnimatedBaseProps> = ({
style,
defaultStyle,
hoverStyle,
pressStyle,
currentState,
children,
duration = 250,
styleContainer,
}) => {
const animatedValues = useRef<Record<string, Animated.Value>>({}).current;
const extractTransformKeys = (styleObject: StyleObject) => {
return styleObject.transform
? styleObject.transform.map((t: any) => Object.keys(t)[0])
: [];
};
useEffect(() => {
const allStyleKeys = new Set([
...Object.keys(defaultStyle),
...Object.keys(hoverStyle),
...Object.keys(pressStyle),
]);
allStyleKeys.forEach((key) => {
if (!animatedValues[key]) {
animatedValues[key] = new Animated.Value(0);
console.log('key; ', key);
}
});
const allTransformKeys = new Set([
...extractTransformKeys(defaultStyle),
...extractTransformKeys(hoverStyle),
...extractTransformKeys(pressStyle),
]);
allTransformKeys.forEach((key) => {
if (!animatedValues[key]) {
animatedValues[key] = new Animated.Value(0);
console.log('keyxx; ', key);
}
});
}, [defaultStyle, hoverStyle, pressStyle]);
useEffect(() => {
animateToState(currentState);
}, [currentState]);
const getTransformValue = (key: string, style: StyleObject) => {
const transformObject = style.transform?.find((t: any) => t.hasOwnProperty(key));
return transformObject ? transformObject[key] : 0;
};
const interpolateStyle = (stateStyle: StyleObject): InterpolatedStyleObject => {
const interpolatedStyle: InterpolatedStyleObject = {};
const transform: any = [];
Object.keys(animatedValues).forEach((key) => {
if (stateStyle.transform?.some((t: any) => t.hasOwnProperty(key))) {
const defaultValue = getTransformValue(key, defaultStyle);
const hoverValue = getTransformValue(key, hoverStyle);
const pressValue = getTransformValue(key, pressStyle);
const interpolated = animatedValues[key]!.interpolate({
inputRange: [0, 1, 2],
outputRange: [defaultValue, hoverValue, pressValue],
});
transform.push({ [key]: interpolated });
} else if (stateStyle[key]) {
const defaultValue = defaultStyle[key] || 0;
const hoverValue = hoverStyle[key] !== undefined ? hoverStyle[key] : defaultValue;
const pressValue = pressStyle[key] !== undefined ? pressStyle[key] : defaultValue;
interpolatedStyle[key] = animatedValues[key]!.interpolate({
inputRange: [0, 1, 2],
outputRange: [defaultValue, hoverValue, pressValue],
});
}
});
if (transform.length > 0) {
interpolatedStyle.transform = transform;
}
return interpolatedStyle;
};
const animateToState = (stateValue: number) => {
Object.keys(animatedValues).forEach((key) => {
Animated.timing(animatedValues[key]!, {
toValue: stateValue,
duration: duration,
useNativeDriver: false,
}).start();
});
};
const animatedStyle = StyleSheet.flatten([
styleContainer,
interpolateStyle(defaultStyle),
interpolateStyle(hoverStyle),
interpolateStyle(pressStyle),
]);
return <Animated.View style={[style, animatedStyle]}>{children && children}</Animated.View>;
};
export default AnimatedBase;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { Pressable } from 'react-native';
interface InteractiveBaseProps {
handleHoverIn: () => void;
handleHoverOut: () => void;
handlePressIn: () => void;
handlePressOut: () => void;
children: React.ReactNode;
style?: any;
}
const InteractiveBase: React.FC<InteractiveBaseProps> = ({
handleHoverIn,
handleHoverOut,
handlePressIn,
handlePressOut,
children,
style,
}) => {
return (
<Pressable
style={style}
onHoverIn={handleHoverIn}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onHoverOut={handleHoverOut}
>
{children}
</Pressable>
);
};
export default InteractiveBase;

View File

@@ -5,19 +5,6 @@ import { Animated, StyleSheet, Pressable, ViewStyle, StyleProp } from 'react-nat
type StyleObject = Record<string, any>; type StyleObject = Record<string, any>;
type InterpolatedStyleObject = Record<string, Animated.AnimatedInterpolation<any>>; type InterpolatedStyleObject = Record<string, Animated.AnimatedInterpolation<any>>;
const TRANSFORM_WHITELIST = {
translateX: true,
translateY: true,
scale: true,
scaleX: true,
scaleY: true,
rotate: true,
rotateX: true,
rotateY: true,
rotateZ: true,
perspective: true,
};
interface InteractiveCCProps { interface InteractiveCCProps {
defaultStyle: StyleObject; defaultStyle: StyleObject;
hoverStyle: StyleObject; hoverStyle: StyleObject;

View File

@@ -0,0 +1,48 @@
import { useState, useCallback } from 'react';
const InteractionStates = {
NORMAL: 0,
HOVER: 1,
PRESSED: 2,
};
interface InteractionStateProps {
onHoverIn?: () => void;
onHoverOut?: () => void;
onPressIn?: () => void;
onPressOut?: () => void;
}
const useInteractionState = ({ onHoverIn, onHoverOut, onPressIn, onPressOut }: InteractionStateProps = {}) => {
const [state, setState] = useState(InteractionStates.NORMAL);
const handleHoverIn = useCallback(() => {
setState(InteractionStates.HOVER);
if (onHoverIn) onHoverIn();
}, [onHoverIn]);
const handleHoverOut = useCallback(() => {
setState(InteractionStates.NORMAL);
if (onHoverOut) onHoverOut();
}, [onHoverOut]);
const handlePressIn = useCallback(() => {
setState(InteractionStates.PRESSED);
if (onPressIn) onPressIn();
}, [onPressIn]);
const handlePressOut = useCallback(() => {
setState(InteractionStates.HOVER);
if (onPressOut) onPressOut();
}, [onPressOut]);
return {
state,
handleHoverIn,
handleHoverOut,
handlePressIn,
handlePressOut,
};
};
export default useInteractionState;

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { useBreakpointValue, useTheme } from 'native-base'; import { View, useBreakpointValue, useTheme, Text } from 'native-base';
import { useWindowDimensions } from 'react-native'; import { StyleProp, ViewStyle, useWindowDimensions } from 'react-native';
import { import {
TabView, TabView,
SceneMap, SceneMap,
@@ -20,6 +20,68 @@ import API from '../API';
import { LoadingView } from '../components/Loading'; import { LoadingView } from '../components/Loading';
import { useLikeSongMutation } from '../utils/likeSongMutation'; import { useLikeSongMutation } from '../utils/likeSongMutation';
import Song from '../models/Song'; import Song from '../models/Song';
import InteractiveCC from '../components/UI/InteractiveCC';
import ButtonBase from '../components/UI/ButtonBase';
import InteractiveBase from '../components/UI/InteractiveBaseV2';
import AnimatedBase from '../components/UI/AnimatedBase';
import useInteractionState from '../components/UI/useInteractionState';
// import React from 'react';
// import { Text, View } from 'react-native';
// import InteractiveBase from './InteractiveBase';
// import AnimatedBase from './AnimatedBase';
// import useInteractionState from './useInteractionState';
interface LinkBaseProps {
text: string;
style?: StyleProp<ViewStyle>;
textStyle?: StyleProp<ViewStyle>;
underlineStyle?: StyleProp<ViewStyle>;
fontSize?: number;
onPress: () => void;
}
const AnimatedLink = ({ text, style, textStyle, underlineStyle, fontSize = 14 }: LinkBaseProps) => {
const interaction = useInteractionState({
onPressOut: () => { console.log("AnimatedLink is activate")}
});
const { colors } = useTheme();
const defaultUnderlineStyle = { height: fontSize / 8, bottom: 0 };
const hoverUnderlineStyle = { height: fontSize * 1.5, bottom: 0 };
const pressUnderlineStyle = { height: 0, bottom: fontSize * 1.5 };
return (
<View style={{ flex: 1, alignItems: 'flex-start', position: 'relative'}}>
<InteractiveBase {...interaction} style={style}>
<AnimatedBase
defaultStyle={{ fontSize: 14 }}
hoverStyle={{ fontSize: 16 }}
pressStyle={{ fontSize: 8 }}
currentState={interaction.state}
>
<Text selectable={false} style={[textStyle]}>
{/* {fontSize: fontSize}, */}
{text}
</Text>
</AnimatedBase>
<AnimatedBase
style={[{
minWidth: '100%',
position: 'absolute',
zIndex: -1,
backgroundColor: colors.primary[600],
}, underlineStyle && {underlineStyle}]}
defaultStyle={{ ...defaultUnderlineStyle }}
hoverStyle={{ ...hoverUnderlineStyle }}
pressStyle={{ ...pressUnderlineStyle }}
currentState={interaction.state}
/>
</InteractiveBase>
</View>
);
};
type MusicListCCProps = { type MusicListCCProps = {
data: Song[] | undefined; data: Song[] | undefined;
@@ -58,12 +120,81 @@ const MusicListCC = ({ data, isLoading, refetch }: MusicListCCProps) => {
const FavoritesMusic = () => { const FavoritesMusic = () => {
const likedSongs = useQuery(API.getLikedSongs(['artist', 'SongHistory', 'likedByUsers'])); const likedSongs = useQuery(API.getLikedSongs(['artist', 'SongHistory', 'likedByUsers']));
const { colors } = useTheme();
const interaction = useInteractionState();
return ( return (
<MusicListCC <>
data={likedSongs.data?.map((x) => x.song)} <View style={{margin: 30}}>
isLoading={likedSongs.isLoading} <AnimatedLink text="coucou Je suis un link zosidjofsijdfosijfosifdjo" onPress={() => console.log("Je suis le lien !!!")}/>
refetch={likedSongs.refetch} <InteractiveBase {...interaction} style={{ marginTop: 20 }}>
/> <AnimatedBase
defaultStyle={{
backgroundColor: colors.primary[300],
}}
hoverStyle={{
backgroundColor: colors.primary[900],
}}
pressStyle={{
backgroundColor: colors.primary[100],
}}
currentState={interaction.state}
>
<Text>
Text
</Text>
</AnimatedBase>
</InteractiveBase>
<InteractiveCC
// duration={80}
styleContainer={{
borderRadius: 10,
}}
style={{
width: '100%',
paddingHorizontal: 20,
paddingVertical: 10,
// borderRadius: 10,
}}
defaultStyle={{
transform: [{ scale: 1,}],
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 8,
backgroundColor: colors.primary[300],
}}
hoverStyle={{
transform: [{ scale: 1.02,}],
shadowOpacity: 0.37,
shadowRadius: 7.49,
elevation: 12,
backgroundColor: colors.primary[400],
}}
pressStyle={{
transform: [{ scale: 0.98,}],
shadowOpacity: 0.23,
shadowRadius: 2.62,
elevation: 4,
backgroundColor: colors.primary[500],
}}
onPress={() => console.log("A que coucou!")}
>
<Text selectable={false} style={{color: '#fff'}}>
Coucou
</Text>
</InteractiveCC>
<ButtonBase
title="Coucou"
style={{ marginTop: 20 }}
type={'filled'}
/>
</View>
<MusicListCC
data={likedSongs.data?.map((x) => x.song)}
isLoading={likedSongs.isLoading}
refetch={likedSongs.refetch}
/>
</>
); );
}; };