222 lines
5.0 KiB
TypeScript
222 lines
5.0 KiB
TypeScript
import { Image as ExpoImage } from 'expo-image';
|
|
import { useEffect, useState } from 'react';
|
|
import {
|
|
Animated,
|
|
Easing,
|
|
Pressable,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableWithoutFeedback,
|
|
View,
|
|
} from 'react-native';
|
|
|
|
const LoadingIcon: React.FC = () => {
|
|
const [spinValue] = useState(new Animated.Value(0));
|
|
|
|
useEffect(() => {
|
|
const animation = Animated.loop(
|
|
Animated.timing(spinValue, {
|
|
toValue: 1,
|
|
duration: 1000,
|
|
easing: Easing.linear,
|
|
useNativeDriver: false,
|
|
})
|
|
);
|
|
|
|
animation.start();
|
|
|
|
return () => {
|
|
animation.stop();
|
|
};
|
|
}, []);
|
|
|
|
const spin = spinValue.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: ['0deg', '360deg'],
|
|
});
|
|
|
|
return (
|
|
<Animated.Image
|
|
source={require('@/assets/images/loading.png')}
|
|
style={[styles.icon, { transform: [{ rotate: spin }] }]}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export type MenuItem = {
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
onPress: () => void;
|
|
};
|
|
|
|
export type GenerateBtnProps = {
|
|
onGenerate: () => void;
|
|
menuItems?: MenuItem[];
|
|
loading?: boolean;
|
|
};
|
|
|
|
export const GenerateBtn: React.FC<React.PropsWithChildren & GenerateBtnProps> = ({ onGenerate, menuItems = [], loading = false, children }) => {
|
|
const [menuVisible, setMenuVisible] = useState(false);
|
|
|
|
const handleGenerate = () => {
|
|
onGenerate();
|
|
};
|
|
|
|
const handleMenuItemPress = (onPress: () => void) => {
|
|
setMenuVisible(false);
|
|
onPress();
|
|
};
|
|
|
|
return (
|
|
<View style={styles.wrapper}>
|
|
<View style={{ backgroundColor: `#171717`, borderRadius: 8 }}>
|
|
{children}
|
|
<View style={styles.container}>
|
|
<Pressable
|
|
style={({ pressed }) => [
|
|
styles.generateButton,
|
|
pressed && styles.pressed,
|
|
]}
|
|
onPress={handleGenerate}
|
|
disabled={loading}
|
|
>
|
|
{loading ? (
|
|
<LoadingIcon />
|
|
) : (
|
|
<ExpoImage source={require('@/assets/images/start.png')} style={styles.icon} />
|
|
)}
|
|
<Text style={styles.buttonText}>Generate Video</Text>
|
|
</Pressable>
|
|
{menuItems && menuItems.length > 0 && <Pressable
|
|
style={({ pressed }) => [
|
|
styles.moreButton,
|
|
pressed && styles.pressed,
|
|
]}
|
|
onPress={() => setMenuVisible(true)}
|
|
>
|
|
<ExpoImage source={require('@/assets/images/more.png')} style={styles.icon} />
|
|
</Pressable>}
|
|
</View>
|
|
</View>
|
|
|
|
{menuVisible && (
|
|
<>
|
|
<TouchableWithoutFeedback onPress={() => setMenuVisible(false)}>
|
|
<View style={styles.backdrop} />
|
|
</TouchableWithoutFeedback>
|
|
<View style={styles.menuWrapper}>
|
|
<View style={styles.menu}>
|
|
{menuItems.map((item, index) => (
|
|
<Pressable
|
|
key={index}
|
|
style={({ pressed }) => [
|
|
styles.menuItem,
|
|
pressed && styles.menuItemPressed,
|
|
]}
|
|
onPress={() => handleMenuItemPress(item.onPress)}
|
|
>
|
|
<View style={styles.menuIcon}>{item.icon}</View>
|
|
<Text style={styles.menuLabel}>{item.label}</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
wrapper: {
|
|
position: 'relative',
|
|
},
|
|
container: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
marginHorizontal: 12,
|
|
marginVertical: 27,
|
|
backgroundColor: '#171717'
|
|
},
|
|
generateButton: {
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 8,
|
|
backgroundColor: '#D1FE17',
|
|
borderRadius: 12,
|
|
paddingVertical: 16,
|
|
paddingHorizontal: 24,
|
|
},
|
|
moreButton: {
|
|
width: 56,
|
|
height: 56,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: '#2E3031',
|
|
borderRadius: 12,
|
|
},
|
|
pressed: {
|
|
opacity: 0.7,
|
|
},
|
|
icon: {
|
|
width: 24,
|
|
height: 24,
|
|
},
|
|
buttonText: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: '#000000',
|
|
letterSpacing: 0.2,
|
|
},
|
|
backdrop: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
zIndex: 999,
|
|
},
|
|
menuWrapper: {
|
|
position: 'absolute',
|
|
top: 90,
|
|
right: 12,
|
|
zIndex: 1000,
|
|
},
|
|
menu: {
|
|
width: 140,
|
|
backgroundColor: '#1F1F1F',
|
|
borderRadius: 8,
|
|
paddingVertical: 4,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 8,
|
|
elevation: 8,
|
|
},
|
|
menuItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 12,
|
|
gap: 12,
|
|
},
|
|
menuItemPressed: {
|
|
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
},
|
|
menuIcon: {
|
|
width: 24,
|
|
height: 24,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
menuLabel: {
|
|
flex: 1,
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
color: '#FFFFFF',
|
|
},
|
|
});
|