T4B_Chat/utilities/loaderUtils.tsx

388 lines
8.7 KiB
TypeScript

/**
* Loader Utilities
* Beautiful custom loading component with chat icon and animations
*/
import React, { useEffect, useRef } from 'react';
import {
View,
Text,
ActivityIndicator,
Animated,
StyleSheet,
Dimensions
} from 'react-native';
//@ts-ignore
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
// Get screen dimensions
const { width, height } = Dimensions.get('window');
/**
* Interface for loader props
*/
export interface LoaderProps {
isLoading: boolean;
loadingText?: string;
showChatIcon?: boolean;
iconName?: string;
iconSize?: number;
backgroundColor?: string;
primaryColor?: string;
textColor?: string;
}
/**
* Beautiful Custom Loader Component
*/
export const CustomLoader: React.FC<LoaderProps> = ({
isLoading,
loadingText = 'Loading...',
showChatIcon = true,
iconName = 'chat-processing',
iconSize = 40,
backgroundColor = 'rgba(255, 255, 255, 1)', // Pure white
primaryColor = '#007AFF',
textColor = '#333333'
}) => {
// Animation values
const fadeAnim = useRef(new Animated.Value(0)).current;
const scaleAnim = useRef(new Animated.Value(0.8)).current;
const pulseAnim = useRef(new Animated.Value(1)).current;
useEffect(() => {
if (isLoading) {
// Instant animations for better UX during navigation
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 0,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 0,
useNativeDriver: true,
}),
]).start();
// Pulse animation with reduced intensity
const pulseAnimation = Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.05,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
])
);
pulseAnimation.start();
return () => {
pulseAnimation.stop();
};
} else {
// Instant hide animations for better UX during navigation
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 0,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 0.8,
duration: 0,
useNativeDriver: true,
}),
]).start();
}
}, [isLoading]);
if (!isLoading) return null;
return (
<Animated.View
style={[
styles.loaderContainer,
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }]
}
]}
>
<View style={styles.loaderContent}>
{/* Chat Icon */}
{showChatIcon && (
<Animated.View
style={[
styles.chatIconContainer,
{
backgroundColor: primaryColor,
transform: [
{ scale: pulseAnim }
]
}
]}
>
<Icon name={iconName} size={iconSize} color="white" />
</Animated.View>
)}
{/* Loading Spinner */}
<View style={styles.spinnerContainer}>
<ActivityIndicator
size="large"
color={primaryColor}
style={styles.spinner}
/>
</View>
{/* Loading Text */}
<Animated.Text
style={[
styles.loadingText,
{
color: textColor,
opacity: fadeAnim
}
]}
>
{loadingText}
</Animated.Text>
{/* Loading Dots Animation */}
<View style={styles.dotsContainer}>
{[0, 1, 2].map((index) => (
<Animated.View
key={index}
style={[
styles.dot,
{
backgroundColor: primaryColor,
opacity: pulseAnim,
transform: [
{
scale: pulseAnim.interpolate({
inputRange: [1, 1.1],
outputRange: [1, 1.2],
})
}
]
}
]}
/>
))}
</View>
</View>
</Animated.View>
);
};
/**
* Enhanced Loader with Progress
*/
export const ProgressLoader: React.FC<LoaderProps & { progress?: number }> = ({
isLoading,
loadingText = 'Loading...',
progress = 0,
...props
}) => {
const progressAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(progressAnim, {
toValue: progress,
duration: 300,
useNativeDriver: false,
}).start();
}, [progress]);
return (
<CustomLoader
isLoading={isLoading}
loadingText={`${loadingText} ${Math.round(progress * 100)}%`}
{...props}
/>
);
};
/**
* Create loader styles
*/
const styles = StyleSheet.create({
loaderContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 9999,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 1)', // Pure white background
},
loaderContent: {
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 40,
paddingVertical: 30,
borderRadius: 20,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.15,
shadowRadius: 4.65,
elevation: 8,
backgroundColor: 'rgba(255, 255, 255, 1)',
borderWidth: 1,
borderColor: 'rgba(0, 0, 0, 0.05)',
},
chatIconContainer: {
width: 80,
height: 80,
borderRadius: 40,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
shadowColor: '#007AFF',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
spinnerContainer: {
marginBottom: 20,
},
spinner: {
transform: [{ scale: 1.2 }],
},
loadingText: {
fontSize: 18,
fontWeight: '600',
textAlign: 'center',
marginBottom: 15,
letterSpacing: 0.5,
},
dotsContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
marginHorizontal: 4,
},
});
/**
* Predefined loader configurations with vector icons
*/
export const LOADER_CONFIGS = {
CHAT: {
loadingText: 'Connecting to chat...',
showChatIcon: true,
iconName: 'chat-processing' as const,
iconSize: 40,
primaryColor: '#007AFF',
},
ODOO: {
loadingText: 'Loading T4B...',
showChatIcon: true,
iconName: 'chat-processing' as const,
iconSize: 40,
primaryColor: '#875A7B',
},
NAVIGATION: {
loadingText: 'Navigating...',
showChatIcon: true,
iconName: 'navigation' as const,
iconSize: 40,
primaryColor: '#28A745',
},
MESSAGE: {
loadingText: 'Sending message...',
showChatIcon: true,
iconName: 'message-text' as const,
iconSize: 40,
primaryColor: '#FF6B35',
},
SYNC: {
loadingText: 'Synchronizing...',
showChatIcon: true,
iconName: 'sync' as const,
iconSize: 40,
primaryColor: '#9C27B0',
},
DEFAULT: {
loadingText: 'Loading...',
showChatIcon: true,
iconName: 'loading' as const,
iconSize: 40,
primaryColor: '#007AFF',
},
} as const;
/**
* Quick loader functions with vector icons
*/
export const createChatLoader = (isLoading: boolean, text?: string) => (
<CustomLoader
isLoading={isLoading}
{...LOADER_CONFIGS.CHAT}
loadingText={text || LOADER_CONFIGS.CHAT.loadingText}
/>
);
export const createOdooLoader = (isLoading: boolean, text?: string) => (
<CustomLoader
isLoading={isLoading}
{...LOADER_CONFIGS.ODOO}
loadingText={text || LOADER_CONFIGS.ODOO.loadingText}
/>
);
export const createNavigationLoader = (isLoading: boolean, text?: string) => (
<CustomLoader
isLoading={isLoading}
{...LOADER_CONFIGS.NAVIGATION}
loadingText={text || LOADER_CONFIGS.NAVIGATION.loadingText}
/>
);
export const createMessageLoader = (isLoading: boolean, text?: string) => (
<CustomLoader
isLoading={isLoading}
{...LOADER_CONFIGS.MESSAGE}
loadingText={text || LOADER_CONFIGS.MESSAGE.loadingText}
/>
);
export const createSyncLoader = (isLoading: boolean, text?: string) => (
<CustomLoader
isLoading={isLoading}
{...LOADER_CONFIGS.SYNC}
loadingText={text || LOADER_CONFIGS.SYNC.loadingText}
/>
);
export const createDefaultLoader = (isLoading: boolean, text?: string) => (
<CustomLoader
isLoading={isLoading}
{...LOADER_CONFIGS.DEFAULT}
loadingText={text || LOADER_CONFIGS.DEFAULT.loadingText}
/>
);