react-native-radar 3.7.6-beta.1 → 3.7.6-beta.2

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.
@@ -1,5 +1,5 @@
1
1
  // Autocomplete.js
2
- import React, { useState, useCallback, useRef } from 'react';
2
+ import React, { useState, useCallback, useRef, useEffect } from "react";
3
3
  import {
4
4
  View,
5
5
  TextInput,
@@ -8,163 +8,65 @@ import {
8
8
  Text,
9
9
  StyleSheet,
10
10
  Image,
11
- } from 'react-native';
12
- import Radar from '../index.native';
13
-
14
- const defaultStyles = StyleSheet.create({
15
- container: {
16
- width: '100%',
17
- alignItems: 'center',
18
- justifyContent: 'center',
19
- padding: 8,
20
- },
21
- inputContainer: {
22
- flexDirection: 'row',
23
- alignItems: 'center',
24
- width: '95%',
25
- backgroundColor: 'white',
26
- borderRadius: 5,
27
- shadowColor: "#061A2B",
28
- shadowOffset: {
29
- width: 0,
30
- height: 4,
31
- },
32
- shadowOpacity: 0.1,
33
- shadowRadius: 12,
34
- elevation: 5,
35
- },
36
- inputIcon: {
37
- marginLeft: 10,
38
- height: 18,
39
- width: 18,
40
- backgroundColor: 'white',
41
- color: 'white',
42
- },
43
- input: {
44
- flex: 1,
45
- backgroundColor: 'white',
46
- height: 40,
47
- fontSize: 14,
48
- paddingTop: 2,
49
- paddingHorizontal: 8,
50
- borderRadius: 5,
51
- shadowOpacity: 0,
52
- },
53
- inputFocused: {
54
- shadowColor: "#81BEFF",
55
- shadowOffset: {
56
- width: 0,
57
- height: 0,
58
- },
59
- shadowOpacity: 1,
60
- shadowRadius: 4,
61
- elevation: 4,
62
- },
63
- resultListWrapper: {
64
- width: '95%',
65
- marginTop: 8,
66
- backgroundColor: 'white',
67
- borderRadius: 5,
68
- paddingVertical: 8,
69
- paddingHorizontal: 8,
70
- shadowColor: "#061A2B",
71
- shadowOffset: {
72
- width: 0,
73
- height: 4,
74
- },
75
- shadowOpacity: 0.1,
76
- shadowRadius: 12,
77
- elevation: 5,
78
- },
79
- resultList: {
80
- width: '100%',
81
- },
82
- resultItem: {
83
- paddingRight: 16,
84
- paddingVertical: 8,
85
- height: 56,
86
- fontSize: 12,
87
- },
88
- addressContainer: {
89
- flexDirection: 'row',
90
- alignItems: 'center',
91
- },
92
- pinIconContainer: {
93
- width: 28,
94
- },
95
- pinIcon: {
96
- height: 20,
97
- width: 20,
98
- marginRight: 8,
99
- marginBottom: 14,
100
- },
101
- addressTextContainer: {
102
- flex: 1,
103
- },
104
- addressText: {
105
- fontSize: 14,
106
- color: '#000',
107
- fontWeight: '600',
108
- },
109
- addressSubtext: {
110
- fontSize: 12,
111
- color: '#5A6872',
112
- },
113
- footerContainer: {
114
- flexDirection: 'row',
115
- alignItems: 'center',
116
- padding: 10,
117
- alignSelf: 'flex-end',
118
- },
119
- footerText: {
120
- marginTop: 2,
121
- marginRight: 4,
122
- fontSize: 10,
123
- color: '#5A6872',
124
- },
125
- logo: {
126
- width: 50,
127
- height: 15,
128
- resizeMode: 'contain',
129
- },
130
- });
11
+ Modal,
12
+ KeyboardAvoidingView,
13
+ Animated,
14
+ Dimensions,
15
+ Easing,
16
+ Keyboard,
17
+ SafeAreaView,
18
+ Pressable,
19
+ } from "react-native";
20
+ import Radar from "../index.native";
21
+ import {
22
+ BACK_ICON,
23
+ SEARCH_ICON,
24
+ RADAR_LOGO,
25
+ MARKER_ICON,
26
+ CLOSE_ICON,
27
+ } from "./images";
28
+ import { default as defaultStyles } from "./styles";
131
29
 
132
30
  const defaultAutocompleteOptions = {
133
31
  debounceMS: 200,
134
32
  threshold: 3,
135
33
  limit: 8,
136
- placeholder: 'Search address',
34
+ placeholder: "Search address",
137
35
  showPin: true,
138
36
  };
139
37
 
140
38
  const autocompleteUI = ({ options = {}, onSelect, location, style = {} }) => {
141
- const [query, setQuery] = useState('');
39
+ const [query, setQuery] = useState("");
142
40
  const [results, setResults] = useState([]);
143
41
  const [isOpen, setIsOpen] = useState(false);
144
- const [isInputFocused, setInputFocused] = useState(false);
42
+ const animationValue = useRef(new Animated.Value(0)).current; // animation value
145
43
  const timerRef = useRef(null);
44
+ const textInputRef = useRef(null);
146
45
 
147
46
  const config = { ...defaultAutocompleteOptions, ...options };
148
47
 
149
- const fetchResults = useCallback(async (searchQuery) => {
150
- if (searchQuery.length < config.threshold) return;
48
+ const fetchResults = useCallback(
49
+ async (searchQuery) => {
50
+ if (searchQuery.length < config.threshold) return;
151
51
 
152
- const params = { query: searchQuery, limit: config.limit };
52
+ const params = { query: searchQuery, limit: config.limit };
153
53
 
154
- if (location && location.latitude && location.longitude) {
155
- params.near = location;
156
- }
54
+ if (location && location.latitude && location.longitude) {
55
+ params.near = location;
56
+ }
157
57
 
158
- try {
159
- const result = await Radar.autocomplete(params);
160
- setResults(result.addresses);
161
- setIsOpen(true);
162
- } catch (error) {
163
- if (config.onError && typeof config.onError === 'function') {
164
- config.onError(error);
58
+ try {
59
+ const result = await Radar.autocomplete(params);
60
+ setResults(result.addresses);
61
+ setIsOpen(true);
62
+ } catch (error) {
63
+ if (config.onError && typeof config.onError === "function") {
64
+ config.onError(error);
65
+ }
165
66
  }
166
- }
167
- }, [config]);
67
+ },
68
+ [config]
69
+ );
168
70
 
169
71
  const handleInput = useCallback(
170
72
  (text) => {
@@ -184,14 +86,14 @@ const autocompleteUI = ({ options = {}, onSelect, location, style = {} }) => {
184
86
  fetchResults(text);
185
87
  }, config.debounceMS);
186
88
  },
187
- [config, fetchResults],
89
+ [config, fetchResults]
188
90
  );
189
91
 
190
92
  const handleSelect = (item) => {
191
93
  setQuery(item.formattedAddress);
192
94
  setIsOpen(false);
193
95
 
194
- if (typeof onSelect === 'function') {
96
+ if (typeof onSelect === "function") {
195
97
  onSelect(item);
196
98
  }
197
99
  };
@@ -201,78 +103,208 @@ const autocompleteUI = ({ options = {}, onSelect, location, style = {} }) => {
201
103
 
202
104
  return (
203
105
  <View style={styles.footerContainer}>
204
- <View style={{ flexDirection: 'row', alignItems: 'center' }}>
106
+ <View style={{ flexDirection: "row", alignItems: "center" }}>
205
107
  <Text style={styles.footerText}>Powered by</Text>
206
- <Image source={require('./radar-logo.png')} resizeMode="contain" style={defaultStyles.logo} />
108
+ <Image
109
+ source={RADAR_LOGO}
110
+ resizeMode="contain"
111
+ style={defaultStyles.logo}
112
+ />
207
113
  </View>
208
114
  </View>
209
115
  );
210
116
  };
211
117
 
212
118
  const renderItem = ({ item }) => (
213
- <TouchableOpacity style={styles.resultItem} onPress={() => handleSelect(item)}>
119
+ <Pressable
120
+ style={({ pressed }) => [
121
+ {
122
+ ...styles.resultItem,
123
+ backgroundColor: pressed
124
+ ? styles.resultItem.pressedBackgroundColor
125
+ : styles.resultItem.backgroundColor,
126
+ },
127
+ ]}
128
+ onPress={() => handleSelect(item)}
129
+ >
214
130
  <View style={styles.addressContainer}>
215
131
  <View style={styles.pinIconContainer}>
216
132
  {config.showPin ? (
217
- <Image source={require('./marker.png')} style={styles.pinIcon} />
133
+ <Image source={MARKER_ICON} style={styles.pinIcon} />
218
134
  ) : null}
219
135
  </View>
220
136
  <View style={styles.addressTextContainer}>
221
137
  <Text numberOfLines={1} style={styles.addressText}>
222
138
  {item.addressLabel || item?.placeLabel}
223
139
  </Text>
224
- <Text numberOfLines={1} style={styles.addressSubtext}>
225
- {item?.formattedAddress?.replace(`${item?.addressLabel || item?.placeLabel}, `, '')}
226
- </Text>
140
+ {item?.formattedAddress.length > 0 && (
141
+ <Text numberOfLines={1} style={styles.addressSubtext}>
142
+ {item?.formattedAddress?.replace(
143
+ `${item?.addressLabel || item?.placeLabel}, `,
144
+ ""
145
+ )}
146
+ </Text>
147
+ )}
227
148
  </View>
228
149
  </View>
229
- </TouchableOpacity>
150
+ </Pressable>
230
151
  );
231
152
 
232
153
  const styles = {
233
154
  ...defaultStyles,
234
155
  container: StyleSheet.compose(defaultStyles.container, style.container),
235
156
  input: StyleSheet.compose(defaultStyles.input, style.input),
157
+ inputContainer: StyleSheet.compose(
158
+ defaultStyles.inputContainer,
159
+ style.inputContainer
160
+ ),
161
+ modalInputContainer: StyleSheet.compose(
162
+ defaultStyles.modalInputContainer,
163
+ style.modalInputContainer
164
+ ),
236
165
  resultList: StyleSheet.compose(defaultStyles.resultList, style.resultList),
237
166
  resultItem: StyleSheet.compose(defaultStyles.resultItem, style.resultItem),
238
- addressContainer: StyleSheet.compose(defaultStyles.addressContainer, style.addressContainer),
239
- pinIconContainer: StyleSheet.compose(defaultStyles.pinIconContainer, style.pinIconContainer),
167
+ addressContainer: StyleSheet.compose(
168
+ defaultStyles.addressContainer,
169
+ style.addressContainer
170
+ ),
171
+ pinIconContainer: StyleSheet.compose(
172
+ defaultStyles.pinIconContainer,
173
+ style.pinIconContainer
174
+ ),
240
175
  pinIcon: StyleSheet.compose(defaultStyles.pinIcon, style.pinIcon),
241
- addressTextContainer: StyleSheet.compose(defaultStyles.addressTextContainer, style.addressTextContainer),
242
- addressText: StyleSheet.compose(defaultStyles.addressText, style.addressText),
243
- addressSubtext: StyleSheet.compose(defaultStyles.addressSubtext, style.addressSubtext),
244
- footerContainer: StyleSheet.compose(defaultStyles.footerContainer, style.footerContainer),
176
+ addressTextContainer: StyleSheet.compose(
177
+ defaultStyles.addressTextContainer,
178
+ style.addressTextContainer
179
+ ),
180
+ addressText: StyleSheet.compose(
181
+ defaultStyles.addressText,
182
+ style.addressText
183
+ ),
184
+ addressSubtext: StyleSheet.compose(
185
+ defaultStyles.addressSubtext,
186
+ style.addressSubtext
187
+ ),
188
+ footerContainer: StyleSheet.compose(
189
+ defaultStyles.footerContainer,
190
+ style.footerContainer
191
+ ),
245
192
  footerText: StyleSheet.compose(defaultStyles.footerText, style.footerText),
246
193
  };
247
194
 
248
- const inputStyle = isInputFocused
249
- ? StyleSheet.compose(styles.inputContainer, defaultStyles.inputFocused)
250
- : styles.inputContainer;
195
+ useEffect(() => {
196
+ Animated.timing(animationValue, {
197
+ toValue: isOpen ? 1 : 0,
198
+ duration: 300,
199
+ easing: Easing.inOut(Easing.ease),
200
+ useNativeDriver: false,
201
+ }).start();
202
+ }, [isOpen]);
203
+
204
+ const screenHeight = Dimensions.get("window").height;
205
+
206
+ const inputHeight = animationValue.interpolate({
207
+ inputRange: [0, 1],
208
+ outputRange: [40, screenHeight],
209
+ });
210
+
211
+ const modalOpacity = animationValue.interpolate({
212
+ inputRange: [0, 0.5, 1],
213
+ outputRange: [0, 0, 1],
214
+ });
251
215
 
252
216
  return (
253
217
  <View style={styles.container}>
254
- <View style={inputStyle}>
255
- <Image source={require('./search.png')} style={styles.inputIcon} />
256
- <TextInput
257
- style={styles.input}
258
- onChangeText={handleInput}
259
- onFocus={() => setInputFocused(true)}
260
- onBlur={() => setInputFocused(false)}
261
- value={query}
262
- placeholder={config.placeholder}
263
- />
264
- </View>
265
- {isOpen && (
266
- <View style={defaultStyles.resultListWrapper}>
267
- <FlatList
268
- style={styles.resultList}
269
- data={results}
270
- renderItem={renderItem}
271
- keyExtractor={(item) => item.formattedAddress + item.postalCode}
272
- ListFooterComponent={renderFooter}
218
+ <Animated.View style={{ height: inputHeight }}>
219
+ <TouchableOpacity
220
+ style={styles.inputContainer}
221
+ onPress={() => {
222
+ setIsOpen(true);
223
+ // Set the focus on the other textinput after it opens
224
+ setTimeout(() => {
225
+ textInputRef.current.focus();
226
+ }, 100);
227
+ }}
228
+ >
229
+ <Image source={SEARCH_ICON} style={styles.inputIcon} />
230
+ <TextInput
231
+ style={styles.input}
232
+ onFocus={() => {
233
+ setIsOpen(true);
234
+ setTimeout(() => {
235
+ textInputRef.current.focus();
236
+ }, 100);
237
+ }}
238
+ value={query}
239
+ returnKeyType="done"
240
+ placeholder={config.placeholder}
241
+ placeholderTextColor="#acbdc8"
273
242
  />
274
- </View>
275
- )}
243
+ </TouchableOpacity>
244
+ </Animated.View>
245
+ <Modal
246
+ animationType="slide"
247
+ transparent={false}
248
+ visible={isOpen}
249
+ onRequestClose={() => setIsOpen(false)}
250
+ >
251
+ <Animated.View style={{ flex: 1, opacity: modalOpacity }}>
252
+ <SafeAreaView>
253
+ <KeyboardAvoidingView
254
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
255
+ keyboardVerticalOffset={50}
256
+ style={styles.container}
257
+ >
258
+ <View style={styles.modalInputContainer}>
259
+ <TouchableOpacity
260
+ onPress={() => {
261
+ setIsOpen(false);
262
+ }}
263
+ >
264
+ <Image source={BACK_ICON} style={styles.inputIcon} />
265
+ </TouchableOpacity>
266
+ <TextInput
267
+ ref={textInputRef}
268
+ style={styles.input}
269
+ onChangeText={handleInput}
270
+ value={query}
271
+ placeholder={config.placeholder}
272
+ returnKeyType="done"
273
+ onSubmitEditing={() => {
274
+ setIsOpen(false);
275
+ }}
276
+ placeholderTextColor="#acbdc8"
277
+ />
278
+ <TouchableOpacity
279
+ onPress={() => {
280
+ setQuery("");
281
+ }}
282
+ >
283
+ <Image source={CLOSE_ICON} style={styles.closeIcon} />
284
+ </TouchableOpacity>
285
+ </View>
286
+ {results.length > 0 && (
287
+ <View style={styles.resultListWrapper}>
288
+ <FlatList
289
+ style={styles.resultList}
290
+ data={results}
291
+ onScroll={() => {
292
+ textInputRef.current.blur();
293
+ Keyboard.dismiss();
294
+ }}
295
+ keyboardShouldPersistTaps="handled"
296
+ renderItem={renderItem}
297
+ keyExtractor={(item) =>
298
+ item.formattedAddress + item.postalCode
299
+ }
300
+ />
301
+ {renderFooter()}
302
+ </View>
303
+ )}
304
+ </KeyboardAvoidingView>
305
+ </SafeAreaView>
306
+ </Animated.View>
307
+ </Modal>
276
308
  </View>
277
309
  );
278
310
  };
package/js/ui/back.png ADDED
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ export const BACK_ICON = require('./back.png');
2
+ export const SEARCH_ICON = require('./search.png');
3
+ export const RADAR_LOGO = require('./radar-logo.png');
4
+ export const MARKER_ICON = require('./marker.png');
5
+ export const CLOSE_ICON = require('./close.png');
package/js/ui/search.png CHANGED
Binary file
@@ -0,0 +1,112 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ container: {
5
+ width: '100%',
6
+ height: '100%',
7
+ alignItems: 'center',
8
+ paddingTop: 8,
9
+ },
10
+ inputContainer: {
11
+ flexDirection: 'row',
12
+ alignItems: 'center',
13
+ marginHorizontal: 16,
14
+ backgroundColor: 'white',
15
+ borderRadius: 5,
16
+ borderColor: '#DBE5EB',
17
+ borderWidth: 1,
18
+ width: '95%', // only difference between this and the modalInputContainer
19
+ },
20
+ modalInputContainer: {
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ marginHorizontal: 16,
24
+ backgroundColor: 'white',
25
+ borderRadius: 5,
26
+ borderColor: '#DBE5EB',
27
+ borderWidth: 1,
28
+ },
29
+ inputIcon: {
30
+ marginLeft: 10,
31
+ height: 18,
32
+ width: 18,
33
+ backgroundColor: 'white',
34
+ color: 'white',
35
+ },
36
+ closeIcon: {
37
+ marginRight: 10,
38
+ height: 18,
39
+ width: 18,
40
+ backgroundColor: 'white',
41
+ color: 'white',
42
+ },
43
+ input: {
44
+ flex: 1,
45
+ backgroundColor: 'white',
46
+ height: 40,
47
+ fontSize: 14,
48
+ paddingHorizontal: 8,
49
+ borderRadius: 5,
50
+ },
51
+ resultListWrapper: {
52
+ width: '100%',
53
+ marginBottom: 30,
54
+ backgroundColor: 'white',
55
+ borderRadius: 5,
56
+ paddingVertical: 8,
57
+ },
58
+ resultList: {
59
+ width: '100%',
60
+ },
61
+ resultItem: {
62
+ paddingRight: 16,
63
+ paddingVertical: 8,
64
+ paddingHorizontal: 16,
65
+ fontSize: 12,
66
+ backgroundColor: 'white',
67
+ pressedBackgroundColor: '#F6FAFC',
68
+ },
69
+ addressContainer: {
70
+ flexDirection: 'row',
71
+ alignItems: 'center',
72
+ },
73
+ pinIconContainer: {
74
+ width: 28,
75
+ marginRight: 8,
76
+ },
77
+ pinIcon: {
78
+ height: 26,
79
+ width: 26,
80
+ },
81
+ addressTextContainer: {
82
+ flex: 1,
83
+ },
84
+ addressText: {
85
+ fontSize: 14,
86
+ lineHeight: 24,
87
+ color: '#000',
88
+ fontWeight: '600',
89
+ },
90
+ addressSubtext: {
91
+ fontSize: 14,
92
+ color: '#5A6872',
93
+ },
94
+ footerContainer: {
95
+ flexDirection: 'row',
96
+ alignItems: 'center',
97
+ paddingVertical: 10,
98
+ marginRight: 16,
99
+ alignSelf: 'flex-end',
100
+ },
101
+ footerText: {
102
+ marginTop: 2,
103
+ marginRight: 4,
104
+ fontSize: 10,
105
+ color: '#5A6872',
106
+ },
107
+ logo: {
108
+ width: 50,
109
+ height: 15,
110
+ resizeMode: 'contain',
111
+ },
112
+ });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "React Native module for Radar, the leading geofencing and location tracking platform",
4
4
  "homepage": "https://radar.com",
5
5
  "license": "Apache-2.0",
6
- "version": "3.7.6-beta.1",
6
+ "version": "3.7.6-beta.2",
7
7
  "main": "js/index.js",
8
8
  "files": [
9
9
  "android",