react-native-biometric-verifier
Version:
A React Native module for biometric verification with face recognition and QR code scanning
162 lines (145 loc) • 4.37 kB
JavaScript
import React, { useEffect, useRef } from 'react';
import { Animated, Text, Platform, StyleSheet } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import PropTypes from 'prop-types';
import { Global } from '../utils/Global';
export const Notification = ({ notification, fadeAnim, slideAnim }) => {
if (!notification || typeof notification !== 'object') {
console.warn('Notification: Invalid or missing notification object');
return null;
}
const { visible, type = 'info', message = '' } = notification;
if (!visible) return null;
// Icon and color mapping
const iconMap = {
success: { name: 'check-circle', color: Global.AppTheme.success },
error: { name: 'error', color: Global.AppTheme.error },
info: { name: 'info', color: Global.AppTheme.info },
};
const { name: iconName, color: iconColor } = iconMap[type] || iconMap.info;
// Heartbeat animation (scale in/out)
const scaleAnim = useRef(new Animated.Value(1)).current;
// Shake animation (rotation wiggle)
const shakeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
const heartbeat = Animated.loop(
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 1.2,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
])
);
const shake = Animated.loop(
Animated.sequence([
Animated.timing(shakeAnim, {
toValue: 1,
duration: 80,
useNativeDriver: true,
}),
Animated.timing(shakeAnim, {
toValue: -1,
duration: 80,
useNativeDriver: true,
}),
Animated.timing(shakeAnim, {
toValue: 0,
duration: 80,
useNativeDriver: true,
}),
])
);
if (visible) {
heartbeat.start();
if (type === 'error') shake.start();
} else {
heartbeat.stop();
shake.stop();
scaleAnim.setValue(1);
shakeAnim.setValue(0);
}
return () => {
heartbeat.stop();
shake.stop();
};
}, [visible, type, scaleAnim, shakeAnim]);
// Shake rotation mapping (small wiggle)
const shakeInterpolate = shakeAnim.interpolate({
inputRange: [-1, 1],
outputRange: ['-10deg', '10deg'],
});
return (
<Animated.View
style={[
styles.container,
{
opacity: fadeAnim instanceof Animated.Value ? fadeAnim : 1,
transform: [
{ translateY: slideAnim instanceof Animated.Value ? slideAnim : 0 },
],
},
]}
>
<Animated.View
style={{
transform: [
{ scale: scaleAnim },
...(type === 'error' ? [{ rotate: shakeInterpolate }] : []),
],
}}
>
<Icon name={iconName} size={50} color={iconColor} style={styles.icon} />
</Animated.View>
<Text style={styles.message}>
{message || 'No message provided'}
</Text>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
padding: 15,
borderRadius: 12,
marginVertical: 10,
width: '100%',
flexDirection: 'row',
alignItems: 'center',
backgroundColor: Global.AppTheme.dark,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
icon: {
marginRight: 10,
},
message: {
fontSize: 14,
color: Global.AppTheme.light,
fontWeight: '500',
flex: 1,
fontFamily: Platform.OS === 'ios' ? 'Helvetica Neue' : 'sans-serif',
},
});
Notification.propTypes = {
notification: PropTypes.shape({
visible: PropTypes.bool.isRequired,
type: PropTypes.oneOf(['success', 'error', 'info']),
message: PropTypes.string,
}),
fadeAnim: PropTypes.instanceOf(Animated.Value),
slideAnim: PropTypes.instanceOf(Animated.Value),
};
Notification.defaultProps = {
notification: { visible: false, type: 'info', message: '' },
fadeAnim: new Animated.Value(1),
slideAnim: new Animated.Value(0),
};
export default Notification;