· 5 years ago · Jun 23, 2020, 08:34 PM
1import React from 'react';
2import {
3 Modal,
4 ScrollView,
5 Platform,
6 StyleSheet,
7 Image,
8 ActivityIndicator,
9 TouchableOpacity,
10 TextInput,
11 Text,
12 View,
13} from 'react-native';
14import Geolocation from '@react-native-community/geolocation';
15import ModalSelector from 'react-native-modal-selector';
16import { AppStyles, ModalHeaderStyle, ModalSelectorStyle } from '../AppStyles';
17import TextButton from 'react-native-button';
18import FastImage from 'react-native-fast-image';
19import { Configuration } from '../Configuration';
20import { connect } from 'react-redux';
21import ImagePicker from 'react-native-image-picker';
22import Icon from 'react-native-vector-icons/FontAwesome';
23import FilterViewModal from '../components/FilterViewModal';
24import SelectLocationModal from '../components/SelectLocationModal';
25import ActionSheet from 'react-native-actionsheet';
26import Geocoder from 'react-native-geocoding';
27import ServerConfiguration from '../ServerConfiguration';
28import { firebaseStorage } from '../Core/firebase/storage';
29import ListingAppConfig from '../ListingAppConfig';
30import { firebaseListing } from '../firebase';
31import { IMLocalized } from '../Core/localization/IMLocalization';
32import DynamicAppStyles from '../DynamicAppStyles';
33
34class PostModal extends React.Component {
35 static navigationOptions = ({ navigation }) => ({
36 title: 'Home',
37 });
38
39 constructor(props) {
40 super(props);
41
42 Geocoder.init(ListingAppConfig.reverseGeoCodingAPIKey);
43
44 const { selectedItem, categories } = this.props;
45 let category = { name: IMLocalized('Select...') };
46 let title = '';
47 let description = '';
48 let location = {
49 latitude: Configuration.map.origin.latitude,
50 longitude: Configuration.map.origin.longitude,
51 };
52 let localPhotos = [];
53 let photoUrls = [];
54 let price = '$1000';
55 let textInputValue = '';
56 let filter = {};
57 let filterValue = IMLocalized('Select...');
58 let address: 'Checking...';
59
60 // if (categories.length > 0) category = categories[0];
61 if (selectedItem) {
62 const {
63 name,
64 latitude,
65 longitude,
66 photos,
67 filters,
68 place,
69 } = selectedItem;
70
71 category = categories.find(
72 (category) => selectedItem.categoryID === category.id,
73 );
74 title = name;
75 description = selectedItem.description;
76 location = {
77 latitude,
78 longitude,
79 };
80 localPhotos = photos;
81 photoUrls = photos;
82 price = selectedItem.price;
83 filter = filters;
84 address = place;
85 }
86
87 this.state = {
88 categories: categories,
89 category: category,
90 title,
91 description,
92 location,
93 localPhotos,
94 photoUrls,
95 price,
96 textInputValue,
97 filter,
98 filterValue,
99 address,
100 filterModalVisible: false,
101 locationModalVisible: false,
102 loading: false,
103 };
104 }
105
106 componentDidMount() {
107 this.setFilterString(this.state.filter);
108 Geolocation.getCurrentPosition(
109 (position) => {
110 this.setState({ location: position.coords });
111 this.onChangeLocation(position.coords);
112 },
113 (error) => alert(error.message),
114 { enableHighAccuracy: false, timeout: 1000 },
115 );
116 }
117
118 selectLocation = () => {
119 this.setState({ locationModalVisible: true });
120 };
121
122 onChangeLocation = (location) => {
123 Geocoder.from(location.latitude, location.longitude)
124 .then((json) => {
125 const addressComponent = json.results[0].formatted_address;
126 this.setState({ address: addressComponent });
127 })
128 .catch((error) => {
129 console.log(error);
130 this.setState({ address: 'Unknown' });
131 });
132 };
133
134 setFilterString = (filter) => {
135 let filterValue = '';
136 Object.keys(filter).forEach(function (key) {
137 if (filter[key] != 'Any' && filter[key] != 'All') {
138 filterValue += ' ' + filter[key];
139 }
140 });
141
142 if (filterValue == '') {
143 if (Object.keys(filter).length > 0) {
144 filterValue = 'Any';
145 } else {
146 filterValue = IMLocalized('Select...');
147 }
148 }
149
150 this.setState({ filterValue: filterValue });
151 };
152
153 onSelectLocationDone = (location) => {
154 this.setState({ location: location });
155 this.setState({ locationModalVisible: false });
156 this.onChangeLocation(location);
157 };
158
159 onSelectLocationCancel = () => {
160 this.setState({ locationModalVisible: false });
161 };
162
163 selectFilter = () => {
164 if (!this.state.category.id) {
165 alert(IMLocalized('You must choose a category first.'));
166 } else {
167 this.setState({ filterModalVisible: true });
168 }
169 };
170
171 onSelectFilterCancel = () => {
172 this.setState({ filterModalVisible: false });
173 };
174
175 onSelectFilterDone = (filter) => {
176 this.setState({ filter: filter });
177 this.setState({ filterModalVisible: false });
178 this.setFilterString(filter);
179 };
180
181 onPressAddPhotoBtn = () => {
182 // More info on all the options is below in the API Reference... just some common use cases shown here
183 const options = {
184 title: IMLocalized('Select a photo'),
185 storageOptions: {
186 skipBackup: true,
187 path: 'images',
188 },
189 };
190
191 /**
192 * The first arg is the options object for customization (it can also be null or omitted for default options),
193 * The second arg is the callback which sends object: response (more info in the API Reference)
194 */
195 ImagePicker.showImagePicker(options, (response) => {
196 if (response.didCancel) {
197 console.log('User cancelled image picker');
198 } else if (response.error) {
199 alert(response.error);
200 console.log('ImagePicker Error: ', response.error);
201 } else if (response.customButton) {
202 console.log('User tapped custom button: ', response.customButton);
203 } else {
204 this.setState({
205 localPhotos: [...this.state.localPhotos, response.uri],
206 });
207 }
208 });
209 };
210
211 onCancel = () => {
212 this.props.onCancel();
213 };
214
215 onPost = () => {
216 const self = this;
217 const onCancel = self.onCancel;
218
219 if (!self.state.title) {
220 alert(IMLocalized('Title was not provided.'));
221 return;
222 }
223 if (!self.state.description) {
224 alert(IMLocalized('Description was not set.'));
225 return;
226 }
227 if (!self.state.price) {
228 alert(IMLocalized('Price is empty.'));
229 return;
230 }
231 if (self.state.localPhotos.length == 0) {
232 alert(IMLocalized('Please choose at least one photo.'));
233 return;
234 }
235
236 if (Object.keys(self.state.filter).length == 0) {
237 alert(IMLocalized('Please set the filters.'));
238 return;
239 }
240
241 self.setState({ loading: true });
242
243 let photoUrls = [];
244
245 if (self.props.selectedItem) {
246 photoUrls = [...self.props.selectedItem.photos];
247 }
248
249 const uploadPromiseArray = [];
250 self.state.localPhotos.forEach((uri) => {
251 if (!uri.startsWith('https://')) {
252 uploadPromiseArray.push(
253 new Promise((resolve, reject) => {
254 firebaseStorage.uploadImage(uri).then((response) => {
255 if (response.downloadURL) {
256 photoUrls.push(response.downloadURL);
257 }
258 resolve();
259 });
260 }),
261 );
262 }
263 });
264
265 Promise.all(uploadPromiseArray)
266 .then((values) => {
267 const location = {
268 latitude: self.state.location.latitude,
269 longitude: self.state.location.longitude,
270 };
271 const uploadObject = {
272 isApproved: !ServerConfiguration.isApprovalProcessEnabled,
273 authorID: self.props.user.id,
274 author: self.props.user,
275 categoryID: self.state.category.id,
276 description: self.state.description,
277 latitude: self.state.location.latitude,
278 longitude: self.state.location.longitude,
279 filters: self.state.filter,
280 title: self.state.title,
281 price: self.state.price,
282 //TODO:
283 place: 'San Francisco, CA',
284 photo: photoUrls.length > 0 ? photoUrls[0] : null,
285 photos: photoUrls,
286 photoURLs: photoUrls,
287 };
288
289 firebaseListing.postListing(
290 self.props.selectedItem,
291 uploadObject,
292 photoUrls,
293 location,
294
295 ({ success }) => {
296 if (success) {
297 self.setState({ loading: false }, () => {
298 onCancel();
299 });
300 } else {
301 alert(error);
302 }
303 },
304 );
305 })
306 .catch((reason) => {
307 console.log(reason);
308 });
309 };
310
311 showActionSheet = (index) => {
312 this.setState({
313 selectedPhotoIndex: index,
314 });
315 this.ActionSheet.show();
316 };
317
318 onActionDone = (index) => {
319 if (index == 0) {
320 var array = [...this.state.localPhotos];
321 array.splice(this.state.selectedPhotoIndex, 1);
322 this.setState({ localPhotos: array });
323 }
324 };
325
326 render() {
327 var categoryData = this.state.categories.map((category, index) => ({
328 key: category.id,
329 label: category.name,
330 }));
331 categoryData.unshift({ key: 'section', label: 'Category', section: true });
332
333 const photos = this.state.localPhotos.map((photo, index) => (
334 <TouchableOpacity
335 key={index}
336 onPress={() => {
337 this.showActionSheet(index);
338 }}>
339 <FastImage style={styles.photo} source={{ uri: photo }} />
340 </TouchableOpacity>
341 ));
342 return (
343 <Modal
344 visible={this.props.isVisible}
345 animationType="slide"
346 transparent={false}
347 onRequestClose={this.onCancel}>
348 <View style={ModalHeaderStyle.bar}>
349 <Text style={ModalHeaderStyle.title}>Add Listing</Text>
350 <TextButton
351 style={[ModalHeaderStyle.rightButton, styles.rightButton]}
352 onPress={this.onCancel}>
353 Cancel
354 </TextButton>
355 </View>
356 <ScrollView style={styles.body}>
357 <View style={styles.section}>
358 <Text style={styles.sectionTitle}>Title</Text>
359 <TextInput
360 style={styles.input}
361 value={this.state.title}
362 onChangeText={(text) => this.setState({ title: text })}
363 placeholder="Start typing"
364 placeholderTextColor={AppStyles.color.grey}
365 underlineColorAndroid="transparent"
366 />
367 </View>
368 <View style={styles.divider} />
369 <View style={styles.section}>
370 <Text style={styles.sectionTitle}>Description</Text>
371 <TextInput
372 multiline={true}
373 numberOfLines={2}
374 style={styles.input}
375 onChangeText={(text) => this.setState({ description: text })}
376 value={this.state.description}
377 placeholder="Start typing"
378 placeholderTextColor={AppStyles.color.grey}
379 underlineColorAndroid="transparent"
380 />
381 </View>
382 <View style={styles.divider} />
383 <View style={styles.section}>
384 <View style={styles.row}>
385 <Text style={styles.title}>Price</Text>
386 <TextInput
387 style={styles.priceInput}
388 keyboardType="numeric"
389 value={this.state.price}
390 onChangeText={(text) => this.setState({ price: text })}
391 placeholderTextColor={AppStyles.color.grey}
392 underlineColorAndroid="transparent"
393 />
394 </View>
395 <ModalSelector
396 touchableActiveOpacity={0.9}
397 data={categoryData}
398 sectionTextStyle={ModalSelectorStyle.sectionTextStyle}
399 optionTextStyle={ModalSelectorStyle.optionTextStyle}
400 optionContainerStyle={ModalSelectorStyle.optionContainerStyle}
401 cancelContainerStyle={ModalSelectorStyle.cancelContainerStyle}
402 cancelTextStyle={ModalSelectorStyle.cancelTextStyle}
403 selectedItemTextStyle={ModalSelectorStyle.selectedItemTextStyle}
404 backdropPressToClose={true}
405 cancelText={IMLocalized('Cancel')}
406 initValue={this.state.category.name}
407 onChange={(option) => {
408 this.setState((prevState) => ({
409 category: { id: option.key, name: option.label },
410 filterValue:
411 prevState.category.id === option.key
412 ? this.state.filterValue
413 : IMLocalized('Select...'),
414 filter:
415 prevState.category.id === option.key
416 ? this.state.filter
417 : {},
418 }));
419 }}>
420 <View style={styles.row}>
421 <Text style={styles.title}>{IMLocalized('Category')}</Text>
422 <Text style={styles.value}>{this.state.category.name}</Text>
423 </View>
424 </ModalSelector>
425 <TouchableOpacity onPress={this.selectFilter}>
426 <View style={styles.row}>
427 <Text style={styles.title}>{IMLocalized('Filters')}</Text>
428 <Text style={styles.value}>{this.state.filterValue}</Text>
429 </View>
430 </TouchableOpacity>
431 <TouchableOpacity onPress={this.selectLocation}>
432 <View style={styles.row}>
433 <Text style={styles.title}>{IMLocalized('Location')}</Text>
434 <View style={styles.location}>
435 <Text style={styles.value}>{this.state.address}</Text>
436 </View>
437 </View>
438 </TouchableOpacity>
439 <Text style={styles.addPhotoTitle}>
440 {IMLocalized('Add Photos')}
441 </Text>
442 <ScrollView style={styles.photoList} horizontal={true}>
443 {photos}
444 <TouchableOpacity onPress={this.onPressAddPhotoBtn.bind(this)}>
445 <View style={[styles.addButton, styles.photo]}>
446 <Icon name="camera" size={30} color="white" />
447 </View>
448 </TouchableOpacity>
449 </ScrollView>
450 </View>
451 {this.state.filterModalVisible && (
452 <FilterViewModal
453 value={this.state.filter}
454 onCancel={this.onSelectFilterCancel}
455 onDone={this.onSelectFilterDone}
456 category={this.state.category}
457 />
458 )}
459 {this.state.locationModalVisible && (
460 <SelectLocationModal
461 location={this.state.location}
462 onCancel={this.onSelectLocationCancel}
463 onDone={this.onSelectLocationDone}
464 />
465 )}
466 </ScrollView>
467 {this.state.loading ? (
468 <View style={styles.activityIndicatorContainer}>
469 <ActivityIndicator size="large" color={AppStyles.color.main} />
470 </View>
471 ) : (
472 <TextButton
473 containerStyle={styles.addButtonContainer}
474 onPress={this.onPost}
475 style={styles.addButtonText}>
476 {IMLocalized('Add Listing')}
477 </TextButton>
478 )}
479 <ActionSheet
480 ref={(o) => (this.ActionSheet = o)}
481 title={IMLocalized('Confirm to delete?')}
482 options={[IMLocalized('Confirm'), IMLocalized('Cancel')]}
483 cancelButtonIndex={1}
484 destructiveButtonIndex={0}
485 onPress={(index) => {
486 this.onActionDone(index);
487 }}
488 />
489 </Modal>
490 );
491 }
492}
493const actionSheetStyles = {
494 titleBox: {
495 backgroundColor: 'pink',
496 },
497 titleText: {
498 fontSize: 16,
499 color: '#000fff',
500 },
501};
502
503const styles = StyleSheet.create({
504 body: {
505 flex: 1,
506 backgroundColor: AppStyles.color.white,
507 },
508 divider: {
509 backgroundColor: AppStyles.color.background,
510 height: 10,
511 },
512 container: {
513 justifyContent: 'center',
514 height: 65,
515 alignItems: 'center',
516 flexDirection: 'row',
517 borderBottomWidth: 1,
518 borderBottomColor: AppStyles.color.grey,
519 },
520 rightButton: {
521 marginRight: 10,
522 },
523 sectionTitle: {
524 textAlign: 'left',
525 alignItems: 'center',
526 color: AppStyles.color.title,
527 fontSize: 19,
528 padding: 10,
529 paddingTop: 15,
530 paddingBottom: 7,
531 fontFamily: AppStyles.fontName.bold,
532 fontWeight: 'bold',
533 // borderBottomWidth: 2,
534 // borderBottomColor: AppStyles.color.grey
535 },
536 input: {
537 width: '100%',
538 fontSize: 19,
539 padding: 10,
540 textAlignVertical: 'top',
541 justifyContent: 'flex-start',
542 paddingRight: 0,
543 fontFamily: AppStyles.fontName.main,
544 color: AppStyles.color.text,
545 },
546 priceInput: {
547 flex: 1,
548 borderRadius: 5,
549 borderColor: AppStyles.color.grey,
550 borderWidth: 0.5,
551 height: 40,
552 textAlign: 'right',
553 paddingRight: 5,
554 fontFamily: AppStyles.fontName.main,
555 color: AppStyles.color.text,
556 },
557 title: {
558 flex: 2,
559 textAlign: 'left',
560 alignItems: 'center',
561 color: AppStyles.color.title,
562 fontSize: 19,
563 fontFamily: AppStyles.fontName.bold,
564 fontWeight: 'bold',
565 },
566 value: {
567 textAlign: 'right',
568 color: AppStyles.color.description,
569 fontFamily: AppStyles.fontName.main,
570 },
571 section: {
572 backgroundColor: 'white',
573 marginBottom: 10,
574 },
575 row: {
576 height: 50,
577 flexDirection: 'row',
578 alignItems: 'center',
579 paddingLeft: 10,
580 paddingRight: 10,
581 },
582 addPhotoTitle: {
583 color: AppStyles.color.title,
584 fontSize: 19,
585 paddingLeft: 10,
586 marginTop: 10,
587 fontFamily: AppStyles.fontName.bold,
588 fontWeight: 'bold',
589 },
590 photoList: {
591 height: 70,
592 marginTop: 20,
593 marginRight: 10,
594 },
595 location: {
596 alignItems: 'center',
597 width: '50%',
598 },
599 photo: {
600 marginLeft: 10,
601 width: 70,
602 height: 70,
603 borderRadius: 10,
604 },
605
606 addButton: {
607 alignItems: 'center',
608 justifyContent: 'center',
609 backgroundColor: DynamicAppStyles.colorSet.mainThemeForegroundColor,
610 },
611 photoIcon: {
612 width: 50,
613 height: 50,
614 },
615 addButtonContainer: {
616 backgroundColor: DynamicAppStyles.colorSet.mainThemeForegroundColor,
617 borderRadius: 5,
618 padding: 15,
619 margin: 10,
620 marginVertical: 27,
621 },
622 activityIndicatorContainer: {
623 width: '100%',
624 justifyContent: 'center',
625 alignItems: 'center',
626 marginBottom: 30,
627 },
628 addButtonText: {
629 color: AppStyles.color.white,
630 fontWeight: 'bold',
631 fontSize: 15,
632 },
633});
634
635const mapStateToProps = (state) => ({
636 user: state.auth.user,
637});
638
639export default connect(mapStateToProps)(PostModal);