react-native-biometric-verifier 0.0.57 → 0.0.59

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-biometric-verifier",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "description": "A React Native module for biometric verification with face recognition and QR code scanning",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -1,32 +1,23 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import { Animated, Text, Platform, StyleSheet } from 'react-native';
3
3
  import Icon from 'react-native-vector-icons/MaterialIcons';
4
- import PropTypes from 'prop-types';
5
4
  import { Global } from '../utils/Global';
6
5
 
7
- export const Notification = ({ notification, fadeAnim, slideAnim }) => {
8
- if (!notification || typeof notification !== 'object') {
9
- console.warn('Notification: Invalid or missing notification object');
10
- return null;
11
- }
6
+ export const Notification = ({ notification = {}, fadeAnim, slideAnim }) => {
7
+ const { visible = false, type = 'info', message = '' } = notification;
12
8
 
13
- const { visible, type = 'info', message = '' } = notification;
14
- if (!visible) return null;
9
+ // Animations (must ALWAYS exist)
10
+ const scaleAnim = useRef(new Animated.Value(1)).current;
11
+ const shakeAnim = useRef(new Animated.Value(0)).current;
15
12
 
16
- // Icon and color mapping
17
13
  const iconMap = {
18
14
  success: { name: 'check-circle', color: Global.AppTheme.success },
19
15
  error: { name: 'error', color: Global.AppTheme.error },
20
16
  info: { name: 'info', color: Global.AppTheme.info },
21
17
  };
22
18
 
23
- const { name: iconName, color: iconColor } = iconMap[type] || iconMap.info;
24
-
25
- // Heartbeat animation (scale in/out)
26
- const scaleAnim = useRef(new Animated.Value(1)).current;
27
-
28
- // Shake animation (rotation wiggle)
29
- const shakeAnim = useRef(new Animated.Value(0)).current;
19
+ const { name: iconName, color: iconColor } =
20
+ iconMap[type] || iconMap.info;
30
21
 
31
22
  useEffect(() => {
32
23
  const heartbeat = Animated.loop(
@@ -78,20 +69,20 @@ export const Notification = ({ notification, fadeAnim, slideAnim }) => {
78
69
  heartbeat.stop();
79
70
  shake.stop();
80
71
  };
81
- }, [visible, type, scaleAnim, shakeAnim]);
72
+ }, [visible, type]);
82
73
 
83
- // Shake rotation mapping (small wiggle)
84
- const shakeInterpolate = shakeAnim.interpolate({
74
+ const shakeRotate = shakeAnim.interpolate({
85
75
  inputRange: [-1, 1],
86
76
  outputRange: ['-10deg', '10deg'],
87
77
  });
88
78
 
89
79
  return (
90
80
  <Animated.View
81
+ pointerEvents={visible ? 'auto' : 'none'}
91
82
  style={[
92
83
  styles.container,
93
84
  {
94
- opacity: fadeAnim instanceof Animated.Value ? fadeAnim : 1,
85
+ opacity: visible ? 1 : 0,
95
86
  transform: [
96
87
  { translateY: slideAnim instanceof Animated.Value ? slideAnim : 0 },
97
88
  ],
@@ -102,12 +93,13 @@ export const Notification = ({ notification, fadeAnim, slideAnim }) => {
102
93
  style={{
103
94
  transform: [
104
95
  { scale: scaleAnim },
105
- ...(type === 'error' ? [{ rotate: shakeInterpolate }] : []),
96
+ ...(type === 'error' ? [{ rotate: shakeRotate }] : []),
106
97
  ],
107
98
  }}
108
99
  >
109
- <Icon name={iconName} size={50} color={iconColor} style={styles.icon} />
100
+ <Icon name={iconName} size={50} color={iconColor} />
110
101
  </Animated.View>
102
+
111
103
  <Text style={styles.message}>
112
104
  {message || 'No message provided'}
113
105
  </Text>
@@ -142,20 +134,4 @@ const styles = StyleSheet.create({
142
134
  },
143
135
  });
144
136
 
145
- Notification.propTypes = {
146
- notification: PropTypes.shape({
147
- visible: PropTypes.bool.isRequired,
148
- type: PropTypes.oneOf(['success', 'error', 'info']),
149
- message: PropTypes.string,
150
- }),
151
- fadeAnim: PropTypes.instanceOf(Animated.Value),
152
- slideAnim: PropTypes.instanceOf(Animated.Value),
153
- };
154
-
155
- Notification.defaultProps = {
156
- notification: { visible: false, type: 'info', message: '' },
157
- fadeAnim: new Animated.Value(1),
158
- slideAnim: new Animated.Value(0),
159
- };
160
-
161
137
  export default Notification;
@@ -1,71 +1,131 @@
1
1
  import { useCallback } from 'react';
2
- import { Platform, PermissionsAndroid } from 'react-native';
2
+ import {
3
+ Platform,
4
+ PermissionsAndroid,
5
+ Linking,
6
+ } from 'react-native';
3
7
  import Geolocation from 'react-native-geolocation-service';
4
8
 
5
9
  /**
6
- * Custom hook for requesting location permission and fetching current location.
10
+ * High-accuracy geolocation hook with GPS warm-up
7
11
  *
8
- * @param {Function} notifyMessage - Callback to show notifications (message, type).
9
- * @returns {Object} { requestLocationPermission, getCurrentLocation }
12
+ * @param {Function} notifyMessage - (message, type) => void
10
13
  */
11
14
  export const useGeolocation = (notifyMessage) => {
12
15
  /**
13
- * Requests location permission (Android only).
14
- * @returns {Promise<boolean>} - True if permission granted, else false.
16
+ * Request location permission (Android only)
15
17
  */
16
18
  const requestLocationPermission = useCallback(async () => {
17
- if (Platform.OS !== 'android') return true;
19
+ if (Platform.OS !== 'android') {
20
+ return true;
21
+ }
18
22
 
19
23
  try {
20
- const granted = await PermissionsAndroid.request(
24
+ const result = await PermissionsAndroid.requestMultiple([
21
25
  PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
22
- {
23
- title: 'Location Permission Required',
24
- message: 'This app needs your location to verify QR code scans.',
25
- buttonPositive: 'OK',
26
- buttonNegative: 'Cancel',
27
- }
28
- );
26
+ PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
27
+ ]);
29
28
 
30
- const hasPermission = granted === PermissionsAndroid.RESULTS.GRANTED;
29
+ const fineGranted =
30
+ result[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION] ===
31
+ PermissionsAndroid.RESULTS.GRANTED;
31
32
 
32
- if (!hasPermission) {
33
- notifyMessage?.('Cannot get location without permission.', 'error');
33
+ if (!fineGranted) {
34
+ notifyMessage?.(
35
+ 'Precise location permission is required for accurate location.',
36
+ 'error'
37
+ );
38
+ return false;
34
39
  }
35
40
 
36
- return hasPermission;
41
+ return true;
37
42
  } catch (error) {
38
- console.error('Location permission error:', error);
43
+ console.error('Permission error:', error);
39
44
  notifyMessage?.('Location permission error.', 'error');
40
45
  return false;
41
46
  }
42
47
  }, [notifyMessage]);
43
48
 
44
49
  /**
45
- * Fetches the current location of the device.
46
- * @returns {Promise<GeolocationCoordinates>}
50
+ * Get current location with GPS warm-up
47
51
  */
48
- const getCurrentLocation = useCallback(
49
- () =>
50
- new Promise((resolve, reject) => {
51
- Geolocation.getCurrentPosition(
52
- (position) => {
52
+ const getCurrentLocation = useCallback(() => {
53
+ return new Promise(async (resolve, reject) => {
54
+ const hasPermission = await requestLocationPermission();
55
+ if (!hasPermission) {
56
+ return reject('Permission denied');
57
+ }
58
+
59
+ let watchId = null;
60
+ let timeoutId = null;
61
+
62
+ const cleanup = () => {
63
+ if (watchId !== null) {
64
+ Geolocation.clearWatch(watchId);
65
+ }
66
+ if (timeoutId) {
67
+ clearTimeout(timeoutId);
68
+ }
69
+ };
70
+
71
+ // Safety timeout (avoid infinite GPS wait)
72
+ timeoutId = setTimeout(() => {
73
+ cleanup();
74
+ notifyMessage?.(
75
+ 'Unable to get accurate location. Please try again outdoors.',
76
+ 'error'
77
+ );
78
+ reject('Location timeout');
79
+ }, 25000);
80
+
81
+ watchId = Geolocation.watchPosition(
82
+ (position) => {
83
+ const { accuracy } = position.coords;
84
+
85
+ /**
86
+ * Accept the location only when GPS is accurate enough
87
+ * Typical threshold: 15–25 meters
88
+ */
89
+ if (accuracy && accuracy <= 20) {
90
+ cleanup();
53
91
  resolve(position.coords);
54
- },
55
- (error) => {
56
- notifyMessage?.('Unable to fetch current location.', 'error');
57
- reject(error);
58
- },
59
- {
60
- enableHighAccuracy: true,
61
- timeout: 15000,
62
- maximumAge: 10000,
63
- distanceFilter: 1,
64
92
  }
65
- );
66
- }),
67
- [notifyMessage]
68
- );
93
+ },
94
+ (error) => {
95
+ cleanup();
96
+
97
+ // GPS disabled
98
+ if (error.code === 2) {
99
+ notifyMessage?.(
100
+ 'Please enable GPS for accurate location.',
101
+ 'warning'
102
+ );
103
+ Linking.openSettings();
104
+ } else {
105
+ notifyMessage?.('Unable to fetch location.', 'error');
106
+ }
107
+
108
+ reject(error);
109
+ },
110
+ {
111
+ enableHighAccuracy: true,
112
+ accuracy: {
113
+ android: 'high',
114
+ ios: 'best',
115
+ },
116
+ distanceFilter: 0,
117
+ interval: 1000,
118
+ fastestInterval: 500,
119
+ forceRequestLocation: true,
120
+ showLocationDialog: true,
121
+ maximumAge: 0,
122
+ }
123
+ );
124
+ });
125
+ }, [notifyMessage, requestLocationPermission]);
69
126
 
70
- return { requestLocationPermission, getCurrentLocation };
127
+ return {
128
+ requestLocationPermission,
129
+ getCurrentLocation,
130
+ };
71
131
  };