react-native-biometric-verifier 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/CCard.js +136 -0
- package/src/components/CaptureImageWithoutEdit.js +457 -0
- package/src/components/CountdownTimer.js +40 -9
- package/src/components/Loader.js +159 -97
- package/src/components/Notification.js +43 -15
- package/src/components/StepIcon.js +71 -0
- package/src/components/StepIndicator.js +91 -0
- package/src/hooks/useNotifyMessage.js +28 -6
- package/src/index.js +329 -78
- package/src/utils/constants.js +15 -0
- package/src/components/EmployeeCard.js +0 -61
- package/src/components/styles.js +0 -200
package/src/components/Loader.js
CHANGED
|
@@ -1,113 +1,175 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Dimensions,
|
|
5
|
+
View,
|
|
6
|
+
Modal,
|
|
7
|
+
Animated,
|
|
8
|
+
Easing,
|
|
9
|
+
Text
|
|
10
|
+
} from "react-native";
|
|
6
11
|
import FastImage from 'react-native-fast-image';
|
|
12
|
+
import { normalize } from "react-native-elements";
|
|
7
13
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
export default function Loader({
|
|
15
|
+
visible = false,
|
|
16
|
+
overlayColor = 'rgba(0,0,0,0.4)',
|
|
17
|
+
loaderColor = 'lightblue',
|
|
18
|
+
size = 50,
|
|
19
|
+
gifSource = {uri:`http://emr.amalaims.org:9393/file/getCommonFile/image/heartpulse.gif`},
|
|
20
|
+
message = '',
|
|
21
|
+
messageStyle = {},
|
|
22
|
+
animationType = 'fade',
|
|
23
|
+
hasBackground = true,
|
|
24
|
+
borderRadius = 20,
|
|
25
|
+
shadow = true
|
|
26
|
+
}) {
|
|
27
|
+
const [rotation] = useState(new Animated.Value(0));
|
|
28
|
+
const [pulse] = useState(new Animated.Value(1));
|
|
29
|
+
const [fade] = useState(new Animated.Value(0));
|
|
15
30
|
|
|
31
|
+
// Rotation animation
|
|
16
32
|
useEffect(() => {
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
state === ANIMATION_STATES.PROCESSING ||
|
|
23
|
-
state === ANIMATION_STATES.FACE_SCAN ||
|
|
24
|
-
state === ANIMATION_STATES.QR_SCAN
|
|
25
|
-
) {
|
|
26
|
-
spinAnimation.current = Animated.loop(
|
|
27
|
-
Animated.timing(spinValue, {
|
|
33
|
+
if (!gifSource) { // Only animate if not using a GIF
|
|
34
|
+
Animated.loop(
|
|
35
|
+
Animated.timing(rotation, {
|
|
28
36
|
toValue: 1,
|
|
29
37
|
duration: 1500,
|
|
30
38
|
easing: Easing.linear,
|
|
31
|
-
useNativeDriver: true
|
|
39
|
+
useNativeDriver: true
|
|
32
40
|
})
|
|
33
|
-
);
|
|
34
|
-
spinAnimation.current.start();
|
|
35
|
-
}
|
|
36
|
-
else if (
|
|
37
|
-
state === ANIMATION_STATES.SUCCESS ||
|
|
38
|
-
state === ANIMATION_STATES.ERROR
|
|
39
|
-
) {
|
|
40
|
-
pulseAnimation.current = Animated.loop(
|
|
41
|
-
Animated.sequence([
|
|
42
|
-
Animated.timing(pulseValue, {
|
|
43
|
-
toValue: 1.1,
|
|
44
|
-
duration: 500,
|
|
45
|
-
useNativeDriver: true,
|
|
46
|
-
}),
|
|
47
|
-
Animated.timing(pulseValue, {
|
|
48
|
-
toValue: 1,
|
|
49
|
-
duration: 500,
|
|
50
|
-
useNativeDriver: true,
|
|
51
|
-
}),
|
|
52
|
-
]),
|
|
53
|
-
{ iterations: 2 }
|
|
54
|
-
);
|
|
55
|
-
pulseAnimation.current.start();
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
spinValue.setValue(0);
|
|
59
|
-
pulseValue.setValue(1);
|
|
41
|
+
).start();
|
|
60
42
|
}
|
|
61
43
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
44
|
+
// Pulse animation
|
|
45
|
+
Animated.loop(
|
|
46
|
+
Animated.sequence([
|
|
47
|
+
Animated.timing(pulse, {
|
|
48
|
+
toValue: 1.1,
|
|
49
|
+
duration: 800,
|
|
50
|
+
useNativeDriver: true
|
|
51
|
+
}),
|
|
52
|
+
Animated.timing(pulse, {
|
|
53
|
+
toValue: 1,
|
|
54
|
+
duration: 800,
|
|
55
|
+
useNativeDriver: true
|
|
56
|
+
})
|
|
57
|
+
])
|
|
58
|
+
).start();
|
|
59
|
+
|
|
60
|
+
// Fade in animation
|
|
61
|
+
Animated.timing(fade, {
|
|
62
|
+
toValue: 1,
|
|
63
|
+
duration: 300,
|
|
64
|
+
useNativeDriver: true
|
|
65
|
+
}).start();
|
|
66
|
+
}, []);
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
const spin = rotation.interpolate({
|
|
69
|
+
inputRange: [0, 1],
|
|
70
|
+
outputRange: ['0deg', '360deg']
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const loaderContent = gifSource ? (
|
|
74
|
+
<FastImage
|
|
75
|
+
style={[styles.icon_style, { width: normalize(size), height: normalize(size) }]}
|
|
76
|
+
source={gifSource}
|
|
77
|
+
/>
|
|
78
|
+
) : (
|
|
79
|
+
<Animated.View style={[
|
|
80
|
+
styles.defaultLoader,
|
|
81
|
+
{
|
|
82
|
+
borderColor: loaderColor,
|
|
83
|
+
transform: [{ rotate: spin }, { scale: pulse }],
|
|
84
|
+
width: normalize(size),
|
|
85
|
+
height: normalize(size),
|
|
86
|
+
borderWidth: normalize(size / 10)
|
|
87
|
+
}
|
|
88
|
+
]}>
|
|
89
|
+
<View style={[
|
|
90
|
+
styles.innerCircle,
|
|
91
|
+
{
|
|
92
|
+
backgroundColor: loaderColor,
|
|
93
|
+
width: normalize(size / 2),
|
|
94
|
+
height: normalize(size / 2)
|
|
95
|
+
}
|
|
96
|
+
]} />
|
|
97
|
+
</Animated.View>
|
|
98
|
+
);
|
|
84
99
|
|
|
85
100
|
return (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
>
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
<Modal
|
|
102
|
+
animationType={animationType}
|
|
103
|
+
transparent={true}
|
|
104
|
+
visible={visible}
|
|
105
|
+
onRequestClose={() => {}}
|
|
106
|
+
>
|
|
107
|
+
<Animated.View style={[
|
|
108
|
+
styles.modalContainer,
|
|
109
|
+
{
|
|
110
|
+
backgroundColor: overlayColor,
|
|
111
|
+
opacity: fade
|
|
112
|
+
}
|
|
113
|
+
]}>
|
|
114
|
+
<Animated.View style={[
|
|
115
|
+
styles.loaderContainer,
|
|
116
|
+
{
|
|
117
|
+
backgroundColor: hasBackground ? 'white' : 'transparent',
|
|
118
|
+
borderRadius: normalize(borderRadius),
|
|
119
|
+
transform: [{ scale: pulse }],
|
|
120
|
+
...(shadow && styles.shadowStyle)
|
|
121
|
+
}
|
|
122
|
+
]}>
|
|
123
|
+
{console.log('loadersource--------------------',JSON.stringify(gifSource))}
|
|
124
|
+
{loaderContent}
|
|
125
|
+
{message ? (
|
|
126
|
+
<Text style={[styles.messageText, messageStyle]}>
|
|
127
|
+
{message}
|
|
128
|
+
</Text>
|
|
129
|
+
) : null}
|
|
130
|
+
</Animated.View>
|
|
108
131
|
</Animated.View>
|
|
109
|
-
</
|
|
132
|
+
</Modal>
|
|
110
133
|
);
|
|
111
|
-
}
|
|
134
|
+
}
|
|
112
135
|
|
|
113
|
-
|
|
136
|
+
const styles = StyleSheet.create({
|
|
137
|
+
modalContainer: {
|
|
138
|
+
flex: 1,
|
|
139
|
+
justifyContent: 'center',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
},
|
|
142
|
+
loaderContainer: {
|
|
143
|
+
padding: normalize(20),
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
},
|
|
147
|
+
icon_style: {
|
|
148
|
+
justifyContent: "center",
|
|
149
|
+
alignItems: "center"
|
|
150
|
+
},
|
|
151
|
+
defaultLoader: {
|
|
152
|
+
borderRadius: normalize(100),
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
alignItems: 'center',
|
|
155
|
+
},
|
|
156
|
+
innerCircle: {
|
|
157
|
+
borderRadius: normalize(100),
|
|
158
|
+
},
|
|
159
|
+
messageText: {
|
|
160
|
+
marginTop: normalize(15),
|
|
161
|
+
fontSize: normalize(14),
|
|
162
|
+
color: '#555',
|
|
163
|
+
textAlign: 'center'
|
|
164
|
+
},
|
|
165
|
+
shadowStyle: {
|
|
166
|
+
shadowColor: "#000",
|
|
167
|
+
shadowOffset: {
|
|
168
|
+
width: 0,
|
|
169
|
+
height: 2,
|
|
170
|
+
},
|
|
171
|
+
shadowOpacity: 0.25,
|
|
172
|
+
shadowRadius: 3.84,
|
|
173
|
+
elevation: 5,
|
|
174
|
+
}
|
|
175
|
+
});
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Animated, Text } from 'react-native';
|
|
2
|
+
import { Animated, Text, Platform } from 'react-native';
|
|
3
3
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import { COLORS } from '../utils/constants';
|
|
6
|
-
import { styles } from './styles';
|
|
7
6
|
|
|
8
7
|
export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
9
|
-
// Defensive
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
typeof notification !== 'object' ||
|
|
13
|
-
!notification.visible
|
|
14
|
-
) {
|
|
8
|
+
// Defensive checks
|
|
9
|
+
if (!notification || typeof notification !== 'object') {
|
|
10
|
+
console.warn('Notification: Invalid or missing notification object');
|
|
15
11
|
return null;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
const { type = 'info', message = '' } = notification;
|
|
14
|
+
const { visible, type = 'info', message = '' } = notification;
|
|
15
|
+
|
|
16
|
+
if (!visible) return null;
|
|
19
17
|
|
|
20
18
|
// Icon and color mapping
|
|
21
19
|
const iconMap = {
|
|
@@ -29,8 +27,20 @@ export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
|
29
27
|
return (
|
|
30
28
|
<Animated.View
|
|
31
29
|
style={[
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
{
|
|
31
|
+
padding: 15,
|
|
32
|
+
borderRadius: 12,
|
|
33
|
+
marginVertical: 10,
|
|
34
|
+
width: '100%',
|
|
35
|
+
flexDirection: 'row',
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
backgroundColor: COLORS.dark, // Added for visibility
|
|
38
|
+
shadowColor: '#000',
|
|
39
|
+
shadowOffset: { width: 0, height: 2 },
|
|
40
|
+
shadowOpacity: 0.1,
|
|
41
|
+
shadowRadius: 4,
|
|
42
|
+
elevation: 2,
|
|
43
|
+
},
|
|
34
44
|
{
|
|
35
45
|
opacity: fadeAnim instanceof Animated.Value ? fadeAnim : 1,
|
|
36
46
|
transform: [
|
|
@@ -45,9 +55,19 @@ export const Notification = ({ notification, fadeAnim, slideAnim }) => {
|
|
|
45
55
|
name={iconName}
|
|
46
56
|
size={20}
|
|
47
57
|
color={iconColor}
|
|
48
|
-
style={
|
|
58
|
+
style={{ marginRight: 10 }}
|
|
49
59
|
/>
|
|
50
|
-
<Text
|
|
60
|
+
<Text
|
|
61
|
+
style={{
|
|
62
|
+
fontSize: 14,
|
|
63
|
+
color: COLORS.light,
|
|
64
|
+
fontWeight: '500',
|
|
65
|
+
flex: 1,
|
|
66
|
+
fontFamily: Platform.OS === 'ios' ? 'Helvetica Neue' : 'sans-serif',
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{message || 'No message provided'}
|
|
70
|
+
</Text>
|
|
51
71
|
</Animated.View>
|
|
52
72
|
);
|
|
53
73
|
};
|
|
@@ -58,6 +78,14 @@ Notification.propTypes = {
|
|
|
58
78
|
type: PropTypes.oneOf(['success', 'error', 'info']),
|
|
59
79
|
message: PropTypes.string,
|
|
60
80
|
}),
|
|
61
|
-
fadeAnim: PropTypes.
|
|
62
|
-
slideAnim: PropTypes.
|
|
81
|
+
fadeAnim: PropTypes.instanceOf(Animated.Value),
|
|
82
|
+
slideAnim: PropTypes.instanceOf(Animated.Value),
|
|
63
83
|
};
|
|
84
|
+
|
|
85
|
+
Notification.defaultProps = {
|
|
86
|
+
notification: { visible: false, type: 'info', message: '' },
|
|
87
|
+
fadeAnim: new Animated.Value(1),
|
|
88
|
+
slideAnim: new Animated.Value(0),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default Notification;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// components/StepIcon.js
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Animated } from 'react-native';
|
|
4
|
+
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
5
|
+
import { COLORS } from '../utils/constants';
|
|
6
|
+
|
|
7
|
+
const StepIcon = ({
|
|
8
|
+
currentStep,
|
|
9
|
+
animationState,
|
|
10
|
+
scaleAnim,
|
|
11
|
+
opacityAnim
|
|
12
|
+
}) => {
|
|
13
|
+
// Get appropriate icon for current step
|
|
14
|
+
const getStepIcon = () => {
|
|
15
|
+
switch (currentStep) {
|
|
16
|
+
case "Start":
|
|
17
|
+
return "play-arrow";
|
|
18
|
+
case "Identity Verification":
|
|
19
|
+
return animationState === "success"
|
|
20
|
+
? "check-circle"
|
|
21
|
+
: "face";
|
|
22
|
+
case "Location Verification":
|
|
23
|
+
return animationState === "success"
|
|
24
|
+
? "check-circle"
|
|
25
|
+
: "location-on";
|
|
26
|
+
default:
|
|
27
|
+
return "info";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Get appropriate icon color for current state
|
|
32
|
+
const getIconColor = () => {
|
|
33
|
+
switch (animationState) {
|
|
34
|
+
case "success":
|
|
35
|
+
return COLORS.success;
|
|
36
|
+
case "error":
|
|
37
|
+
return COLORS.error;
|
|
38
|
+
default:
|
|
39
|
+
return COLORS.primary;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Animated.View
|
|
45
|
+
style={[
|
|
46
|
+
styles.iconContainer,
|
|
47
|
+
{
|
|
48
|
+
transform: [{ scale: scaleAnim }],
|
|
49
|
+
opacity: opacityAnim
|
|
50
|
+
}
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
<Icon
|
|
54
|
+
name={getStepIcon()}
|
|
55
|
+
size={40}
|
|
56
|
+
color={getIconColor()}
|
|
57
|
+
/>
|
|
58
|
+
</Animated.View>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const styles = {
|
|
63
|
+
iconContainer: {
|
|
64
|
+
marginRight: 15,
|
|
65
|
+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
66
|
+
borderRadius: 30,
|
|
67
|
+
padding: 10,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default StepIcon;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// components/StepIndicator.js
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View, Text, StyleSheet, Platform } from 'react-native';
|
|
4
|
+
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
5
|
+
import { COLORS } from '../utils/constants';
|
|
6
|
+
|
|
7
|
+
const StepIndicator = ({ currentStep }) => {
|
|
8
|
+
return (
|
|
9
|
+
<View style={styles.statusContainer}>
|
|
10
|
+
<View style={styles.statusItem}>
|
|
11
|
+
<Icon
|
|
12
|
+
name="face"
|
|
13
|
+
size={20}
|
|
14
|
+
color={
|
|
15
|
+
currentStep === "Identity Verification" ||
|
|
16
|
+
currentStep === "Location Verification" ||
|
|
17
|
+
currentStep === "Complete"
|
|
18
|
+
? COLORS.primary
|
|
19
|
+
: COLORS.light
|
|
20
|
+
}
|
|
21
|
+
style={styles.statusIcon}
|
|
22
|
+
/>
|
|
23
|
+
<Text style={[
|
|
24
|
+
styles.statusText,
|
|
25
|
+
(currentStep === "Identity Verification" ||
|
|
26
|
+
currentStep === "Location Verification" ||
|
|
27
|
+
currentStep === "Complete") && styles.statusTextActive
|
|
28
|
+
]}>
|
|
29
|
+
Identity
|
|
30
|
+
</Text>
|
|
31
|
+
</View>
|
|
32
|
+
|
|
33
|
+
<View style={styles.statusSeparator} />
|
|
34
|
+
|
|
35
|
+
<View style={styles.statusItem}>
|
|
36
|
+
<Icon
|
|
37
|
+
name="location-on"
|
|
38
|
+
size={20}
|
|
39
|
+
color={
|
|
40
|
+
currentStep === "Location Verification" ||
|
|
41
|
+
currentStep === "Complete"
|
|
42
|
+
? COLORS.primary
|
|
43
|
+
: COLORS.light
|
|
44
|
+
}
|
|
45
|
+
style={styles.statusIcon}
|
|
46
|
+
/>
|
|
47
|
+
<Text style={[
|
|
48
|
+
styles.statusText,
|
|
49
|
+
(currentStep === "Location Verification" ||
|
|
50
|
+
currentStep === "Complete") && styles.statusTextActive
|
|
51
|
+
]}>
|
|
52
|
+
Location
|
|
53
|
+
</Text>
|
|
54
|
+
</View>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const styles = StyleSheet.create({
|
|
60
|
+
statusContainer: {
|
|
61
|
+
flexDirection: 'row',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
marginBottom: 20,
|
|
64
|
+
paddingHorizontal: 20,
|
|
65
|
+
},
|
|
66
|
+
statusItem: {
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
},
|
|
70
|
+
statusIcon: {
|
|
71
|
+
marginBottom: 5,
|
|
72
|
+
},
|
|
73
|
+
statusText: {
|
|
74
|
+
fontSize: 12,
|
|
75
|
+
color: COLORS.light,
|
|
76
|
+
opacity: 0.6,
|
|
77
|
+
},
|
|
78
|
+
statusTextActive: {
|
|
79
|
+
opacity: 1,
|
|
80
|
+
fontWeight: '600',
|
|
81
|
+
},
|
|
82
|
+
statusSeparator: {
|
|
83
|
+
width: 40,
|
|
84
|
+
height: 1,
|
|
85
|
+
backgroundColor: COLORS.light,
|
|
86
|
+
opacity: 0.3,
|
|
87
|
+
marginHorizontal: 15,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export default StepIndicator;
|
|
@@ -18,6 +18,7 @@ export const useNotifyMessage = () => {
|
|
|
18
18
|
|
|
19
19
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
|
20
20
|
const slideAnim = useRef(new Animated.Value(-20)).current;
|
|
21
|
+
const timeoutRef = useRef(null);
|
|
21
22
|
|
|
22
23
|
// Styles for notification container based on type
|
|
23
24
|
const getNotificationStyle = useCallback((type) => {
|
|
@@ -51,7 +52,25 @@ export const useNotifyMessage = () => {
|
|
|
51
52
|
textAlign: 'center',
|
|
52
53
|
}), []);
|
|
53
54
|
|
|
55
|
+
const clearNotification = useCallback(() => {
|
|
56
|
+
// Clear any pending timeout
|
|
57
|
+
if (timeoutRef.current) {
|
|
58
|
+
clearTimeout(timeoutRef.current);
|
|
59
|
+
timeoutRef.current = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Reset animations
|
|
63
|
+
fadeAnim.setValue(0);
|
|
64
|
+
slideAnim.setValue(-20);
|
|
65
|
+
|
|
66
|
+
// Hide notification
|
|
67
|
+
setNotification({ visible: false, message: '', type: 'info' });
|
|
68
|
+
}, [fadeAnim, slideAnim]);
|
|
69
|
+
|
|
54
70
|
const showNotification = useCallback((message, type = 'info') => {
|
|
71
|
+
// Clear any existing notification first
|
|
72
|
+
clearNotification();
|
|
73
|
+
|
|
55
74
|
setNotification({ visible: true, message, type });
|
|
56
75
|
|
|
57
76
|
fadeAnim.setValue(0);
|
|
@@ -69,7 +88,8 @@ export const useNotifyMessage = () => {
|
|
|
69
88
|
useNativeDriver: false,
|
|
70
89
|
}),
|
|
71
90
|
]).start(() => {
|
|
72
|
-
|
|
91
|
+
// Store timeout reference so we can clear it later
|
|
92
|
+
timeoutRef.current = setTimeout(() => {
|
|
73
93
|
Animated.parallel([
|
|
74
94
|
Animated.timing(fadeAnim, {
|
|
75
95
|
toValue: 0,
|
|
@@ -81,12 +101,13 @@ export const useNotifyMessage = () => {
|
|
|
81
101
|
duration: 300,
|
|
82
102
|
useNativeDriver: false,
|
|
83
103
|
}),
|
|
84
|
-
]).start(() =>
|
|
85
|
-
|
|
86
|
-
|
|
104
|
+
]).start(() => {
|
|
105
|
+
timeoutRef.current = null;
|
|
106
|
+
setNotification({ visible: false, message: '', type: 'info' });
|
|
107
|
+
});
|
|
87
108
|
}, 3000);
|
|
88
109
|
});
|
|
89
|
-
}, [fadeAnim, slideAnim]);
|
|
110
|
+
}, [fadeAnim, slideAnim, clearNotification]);
|
|
90
111
|
|
|
91
112
|
const notifyMessage = useCallback(
|
|
92
113
|
(msg, type = 'info') => {
|
|
@@ -125,7 +146,8 @@ export const useNotifyMessage = () => {
|
|
|
125
146
|
fadeAnim,
|
|
126
147
|
slideAnim,
|
|
127
148
|
notifyMessage,
|
|
149
|
+
clearNotification,
|
|
128
150
|
getNotificationStyle,
|
|
129
151
|
notificationTextStyle,
|
|
130
152
|
};
|
|
131
|
-
};
|
|
153
|
+
};
|