@widergy/mobile-ui 0.32.3 → 0.34.0
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/CHANGELOG.md +21 -0
- package/lib/components/Capture/index.js +54 -0
- package/lib/components/Capture/propTypes.js +14 -0
- package/lib/components/Capture/styles.js +11 -0
- package/lib/components/FilePicker/index.js +10 -16
- package/lib/components/PhotoAlbum/components/AddAnotherPhotoButton/index.js +19 -0
- package/lib/components/PhotoAlbum/components/AddAnotherPhotoButton/propTypes.js +7 -0
- package/lib/components/PhotoAlbum/components/AddAnotherPhotoButton/styles.js +29 -0
- package/lib/components/PhotoAlbum/components/DeleteView/index.js +19 -0
- package/lib/components/PhotoAlbum/components/DeleteView/propTypes.js +6 -0
- package/lib/components/PhotoAlbum/components/DeleteView/styles.js +9 -0
- package/lib/components/PhotoAlbum/components/Photo/index.js +69 -0
- package/lib/components/PhotoAlbum/components/Photo/propTypes.js +10 -0
- package/lib/components/PhotoAlbum/components/Photo/styles.js +53 -0
- package/lib/components/PhotoAlbum/components/ReplaceView/index.js +18 -0
- package/lib/components/PhotoAlbum/components/ReplaceView/propTypes.js +6 -0
- package/lib/components/PhotoAlbum/components/ReplaceView/styles.js +15 -0
- package/lib/components/PhotoAlbum/components/SvgIconCheck/index.js +17 -0
- package/lib/components/PhotoAlbum/components/SvgIconCheck/propTypes.js +5 -0
- package/lib/components/PhotoAlbum/components/SvgIconCheck/styles.js +18 -0
- package/lib/components/PhotoAlbum/index.js +168 -0
- package/lib/components/PhotoAlbum/propTypes.js +17 -0
- package/lib/components/PhotoAlbum/styles.js +51 -0
- package/lib/components/UTProgressBar/utils.js +4 -1
- package/lib/index.js +4 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [0.34.0](https://github.com/widergy/mobile-ui/compare/v0.33.0...v0.34.0) (2022-04-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* photo album and capture component added ([08f0749](https://github.com/widergy/mobile-ui/commit/08f07498f274b36bfc2a3be901527bd73f338a7a))
|
|
7
|
+
|
|
8
|
+
# [0.33.0](https://github.com/widergy/mobile-ui/compare/v0.32.4...v0.33.0) (2022-03-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* allow any mime type ([#217](https://github.com/widergy/mobile-ui/issues/217)) ([9b99614](https://github.com/widergy/mobile-ui/commit/9b99614b93d98e14a57c409f0e7674c5832d1b58))
|
|
14
|
+
|
|
15
|
+
## [0.32.4](https://github.com/widergy/mobile-ui/compare/v0.32.3...v0.32.4) (2022-03-07)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* round the percentage in progress bar in routes ([#216](https://github.com/widergy/mobile-ui/issues/216)) ([9a1c459](https://github.com/widergy/mobile-ui/commit/9a1c4593fbce214c700bc708aecd16c8b10716cd))
|
|
21
|
+
|
|
1
22
|
## [0.32.3](https://github.com/widergy/mobile-ui/compare/v0.32.2...v0.32.3) (2022-02-25)
|
|
2
23
|
|
|
3
24
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import Label from '../Label';
|
|
4
|
+
import PhotoAlbum from '../PhotoAlbum';
|
|
5
|
+
import Portal from '../Portal';
|
|
6
|
+
|
|
7
|
+
import styles from './styles';
|
|
8
|
+
import propTypes from './propTypes';
|
|
9
|
+
|
|
10
|
+
const Capture = ({
|
|
11
|
+
label,
|
|
12
|
+
helpText,
|
|
13
|
+
maxImages,
|
|
14
|
+
images,
|
|
15
|
+
selectedImages,
|
|
16
|
+
setSelectedImages,
|
|
17
|
+
deletePhoto,
|
|
18
|
+
onPressAddAnotherPhoto,
|
|
19
|
+
onUnknownCameraError,
|
|
20
|
+
selectedMode,
|
|
21
|
+
sourceCameraImage,
|
|
22
|
+
sourceEditImage,
|
|
23
|
+
messageErrorPermissionCamera
|
|
24
|
+
}) => {
|
|
25
|
+
const isOnlyOnePicture = maxImages === 1 && images.length === 1;
|
|
26
|
+
const isItPossibleToAddAnotherPhoto = isOnlyOnePicture ? false : images.length <= maxImages;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Portal.Host>
|
|
30
|
+
<Label semiBold big style={styles.paddingText}>
|
|
31
|
+
{label}
|
|
32
|
+
</Label>
|
|
33
|
+
<PhotoAlbum
|
|
34
|
+
images={images}
|
|
35
|
+
selectedImages={selectedImages}
|
|
36
|
+
setSelectedImages={setSelectedImages}
|
|
37
|
+
deletePhoto={deletePhoto}
|
|
38
|
+
isItPossibleToAddAnotherPhoto={isItPossibleToAddAnotherPhoto}
|
|
39
|
+
onPressAddAnotherPhoto={onPressAddAnotherPhoto}
|
|
40
|
+
onUnknownCameraError={onUnknownCameraError}
|
|
41
|
+
selectedMode={selectedMode}
|
|
42
|
+
numberOfPhotosInTheAlbumHasExceeded={images.length === maxImages}
|
|
43
|
+
isOnlyOnePicture={isOnlyOnePicture}
|
|
44
|
+
sourceCameraImage={sourceCameraImage}
|
|
45
|
+
sourceEditImage={sourceEditImage}
|
|
46
|
+
messageErrorPermissionCamera={messageErrorPermissionCamera}
|
|
47
|
+
/>
|
|
48
|
+
<Label style={styles.paddingText}>{helpText}</Label>
|
|
49
|
+
</Portal.Host>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
Capture.propTypes = propTypes;
|
|
53
|
+
|
|
54
|
+
export default Capture;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { bool, string, func, array, number } from 'prop-types';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
label: string,
|
|
5
|
+
helpText: string,
|
|
6
|
+
maxImages: number,
|
|
7
|
+
images: array,
|
|
8
|
+
selectedImages: array,
|
|
9
|
+
setSelectedImages: func,
|
|
10
|
+
deletePhoto: func,
|
|
11
|
+
onPressAddAnotherPhoto: func,
|
|
12
|
+
onUnknownCameraError: func,
|
|
13
|
+
selectedMode: bool
|
|
14
|
+
};
|
|
@@ -26,37 +26,31 @@ class FilePicker extends Component {
|
|
|
26
26
|
onError,
|
|
27
27
|
onChange,
|
|
28
28
|
maxFileByteSize,
|
|
29
|
+
avoidRetrieveFile,
|
|
29
30
|
fileTypeError,
|
|
30
31
|
allowedPDFUploadSizes,
|
|
31
32
|
pdfFormatError
|
|
32
33
|
} = this.props;
|
|
33
|
-
const promisePicker = new Promise(async (resolve, reject) => {
|
|
34
|
-
try {
|
|
35
|
-
const picker = await DocumentPicker.pick({
|
|
36
|
-
type: allowedTypes && !onlyPDFAllowed(allowedTypes) ? allowedTypes : DocumentPicker.types.allFiles
|
|
37
|
-
});
|
|
38
|
-
return resolve(picker[0]);
|
|
39
|
-
} catch (error) {
|
|
40
|
-
return reject(error);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
34
|
try {
|
|
44
|
-
const
|
|
35
|
+
const documents = await DocumentPicker.pick({
|
|
36
|
+
type: allowedTypes && !onlyPDFAllowed(allowedTypes) ? allowedTypes : DocumentPicker.types.allFiles
|
|
37
|
+
});
|
|
38
|
+
const document = documents[0];
|
|
45
39
|
const isPDF = document.type.includes('pdf');
|
|
46
40
|
const isImage = document.type.includes('image');
|
|
47
41
|
if (onlyPDFAllowed(allowedTypes) && !isPDF) {
|
|
48
42
|
throw new Error(fileTypeError || 'El tipo de archivo debe ser PDF.');
|
|
49
43
|
}
|
|
50
|
-
if (!isImage && !isImageByUri(document.uri) && !isPDF) {
|
|
44
|
+
if (allowedTypes && !isImage && !isImageByUri(document.uri) && !isPDF) {
|
|
51
45
|
throw new Error(fileTypeError || 'Tipo de archivo inválido.');
|
|
52
46
|
}
|
|
53
|
-
if (document.size > maxFileByteSize) {
|
|
47
|
+
if (maxFileByteSize && document.size > maxFileByteSize) {
|
|
54
48
|
onMaxSizeError(document.size, maxFileByteSize);
|
|
55
49
|
return;
|
|
56
50
|
}
|
|
57
|
-
const file = await retrieveFile(document.uri, document.type);
|
|
51
|
+
const file = !avoidRetrieveFile && (await retrieveFile(document.uri, document.type));
|
|
58
52
|
|
|
59
|
-
if (isPDF && !isEmpty(allowedPDFUploadSizes)) {
|
|
53
|
+
if (file && isPDF && !isEmpty(allowedPDFUploadSizes)) {
|
|
60
54
|
const isWrongFormat = await pdfAspectRatioValidation(file, allowedPDFUploadSizes);
|
|
61
55
|
if (isWrongFormat) {
|
|
62
56
|
throw new Error(
|
|
@@ -68,7 +62,7 @@ class FilePicker extends Component {
|
|
|
68
62
|
}
|
|
69
63
|
}
|
|
70
64
|
if (onChange) {
|
|
71
|
-
onChange({ file });
|
|
65
|
+
onChange(avoidRetrieveFile ? { document } : { file });
|
|
72
66
|
}
|
|
73
67
|
this.setState({ fileName: document.name });
|
|
74
68
|
} catch (err) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Image, TouchableOpacity } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import styles from './styles';
|
|
5
|
+
import propTypes from './propTypes';
|
|
6
|
+
|
|
7
|
+
const AddAnotherPhotoButton = ({ sourceCameraImage, onPress, disabled }) => (
|
|
8
|
+
<TouchableOpacity
|
|
9
|
+
onPress={onPress}
|
|
10
|
+
style={[styles.container, disabled && styles.disabledContainer]}
|
|
11
|
+
disabled={disabled}
|
|
12
|
+
>
|
|
13
|
+
<Image source={sourceCameraImage} style={[styles.image, disabled && styles.imageDisabled]} />
|
|
14
|
+
</TouchableOpacity>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
AddAnotherPhotoButton.propTypes = propTypes;
|
|
18
|
+
|
|
19
|
+
export default AddAnotherPhotoButton;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Dimensions, StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { moderateVerticalScale, moderateHorizontalScale } from '../../../../utils/scaleUtils';
|
|
4
|
+
|
|
5
|
+
const IMAGE_SIZE = (Dimensions.get('window').width - 30) * 0.333333 - 10;
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
container: {
|
|
8
|
+
width: IMAGE_SIZE,
|
|
9
|
+
height: IMAGE_SIZE,
|
|
10
|
+
paddingHorizontal: moderateHorizontalScale(10),
|
|
11
|
+
borderRadius: 5,
|
|
12
|
+
marginBottom: moderateVerticalScale(10),
|
|
13
|
+
backgroundColor: '#F7F8F9',
|
|
14
|
+
justifyContent: 'center',
|
|
15
|
+
alignItems: 'center'
|
|
16
|
+
},
|
|
17
|
+
disabledContainer: {
|
|
18
|
+
backgroundColor: 'rgba(247, 248, 249, 0.5)'
|
|
19
|
+
},
|
|
20
|
+
image: {
|
|
21
|
+
width: moderateHorizontalScale(48),
|
|
22
|
+
height: moderateHorizontalScale(48)
|
|
23
|
+
},
|
|
24
|
+
imageDisabled: {
|
|
25
|
+
opacity: 0.5
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default styles;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import Icon from '../../../Icon';
|
|
5
|
+
import Label from '../../../Label';
|
|
6
|
+
|
|
7
|
+
import styles from './styles';
|
|
8
|
+
import propTypes from './propTypes';
|
|
9
|
+
|
|
10
|
+
const DeleteView = ({ label, color }) => (
|
|
11
|
+
<View style={styles.buttonContainer}>
|
|
12
|
+
<Icon name="trash-2" type="feather" size={20} color={color} />
|
|
13
|
+
<Label color={color}>{label}</Label>
|
|
14
|
+
</View>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
DeleteView.propTypes = propTypes;
|
|
18
|
+
|
|
19
|
+
export default DeleteView;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Image, TouchableOpacity, Modal, ImageBackground } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import SvgIconCheck from '../SvgIconCheck';
|
|
5
|
+
import Icon from '../../../Icon';
|
|
6
|
+
import DeleteView from '../DeleteView';
|
|
7
|
+
|
|
8
|
+
import styles from './styles';
|
|
9
|
+
import propTypes from './propTypes';
|
|
10
|
+
|
|
11
|
+
const Photo = ({ image, onError, onLongPressImage, selectedImages, deletePhoto, isSelectedMode }) => {
|
|
12
|
+
const uri = image.uri ?? image;
|
|
13
|
+
const [fullScreenPhotoModalVisible, setFullScreenPhotoModalVisible] = useState(false);
|
|
14
|
+
|
|
15
|
+
const wasTheImagePressed = selectedImages.includes(uri);
|
|
16
|
+
|
|
17
|
+
const deletePhotoInModal = () => {
|
|
18
|
+
setFullScreenPhotoModalVisible(false);
|
|
19
|
+
deletePhoto(uri);
|
|
20
|
+
};
|
|
21
|
+
return (
|
|
22
|
+
<View>
|
|
23
|
+
<TouchableOpacity
|
|
24
|
+
onPress={() => {
|
|
25
|
+
if (isSelectedMode) {
|
|
26
|
+
onLongPressImage(uri);
|
|
27
|
+
} else {
|
|
28
|
+
setFullScreenPhotoModalVisible(true);
|
|
29
|
+
}
|
|
30
|
+
}}
|
|
31
|
+
onLongPress={() => {
|
|
32
|
+
onLongPressImage(uri);
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{wasTheImagePressed ? (
|
|
36
|
+
<ImageBackground borderRadius={5} source={{ uri }} style={styles.image}>
|
|
37
|
+
<SvgIconCheck size={24} />
|
|
38
|
+
</ImageBackground>
|
|
39
|
+
) : (
|
|
40
|
+
<Image onError={onError} source={{ uri }} style={styles.image} />
|
|
41
|
+
)}
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
<Modal animationType="fade" visible={fullScreenPhotoModalVisible}>
|
|
44
|
+
<View style={styles.modalContainer}>
|
|
45
|
+
<View style={styles.headerContainerModal}>
|
|
46
|
+
<TouchableOpacity
|
|
47
|
+
onPress={() => {
|
|
48
|
+
setFullScreenPhotoModalVisible(!fullScreenPhotoModalVisible);
|
|
49
|
+
}}
|
|
50
|
+
style={styles.backButtonHeaderModal}
|
|
51
|
+
>
|
|
52
|
+
<Icon name="arrowleft" type="antdesign" size={24} color="white" />
|
|
53
|
+
</TouchableOpacity>
|
|
54
|
+
</View>
|
|
55
|
+
<Image onError={onError} source={{ uri }} style={styles.imageFullScreen} />
|
|
56
|
+
{deletePhoto && (
|
|
57
|
+
<TouchableOpacity onPress={deletePhotoInModal} style={styles.footerContainerModal}>
|
|
58
|
+
<DeleteView label="Eliminar" color="white" />
|
|
59
|
+
</TouchableOpacity>
|
|
60
|
+
)}
|
|
61
|
+
</View>
|
|
62
|
+
</Modal>
|
|
63
|
+
</View>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
Photo.propTypes = propTypes;
|
|
68
|
+
|
|
69
|
+
export default Photo;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Dimensions, StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { moderateVerticalScale, moderateHorizontalScale } from '../../../../utils/scaleUtils';
|
|
4
|
+
|
|
5
|
+
const IMAGE_SIZE = (Dimensions.get('window').width - 30) * 0.333333 - 10;
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
image: {
|
|
8
|
+
width: IMAGE_SIZE,
|
|
9
|
+
height: IMAGE_SIZE,
|
|
10
|
+
borderRadius: 5,
|
|
11
|
+
marginBottom: moderateVerticalScale(10)
|
|
12
|
+
},
|
|
13
|
+
imageFullScreen: {
|
|
14
|
+
flex: 1,
|
|
15
|
+
width: '100%',
|
|
16
|
+
height: '100%',
|
|
17
|
+
resizeMode: 'cover'
|
|
18
|
+
},
|
|
19
|
+
selectedImageIcon: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
alignItems: 'flex-start'
|
|
22
|
+
},
|
|
23
|
+
modalContainer: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
justifyContent: 'space-between'
|
|
26
|
+
},
|
|
27
|
+
headerContainerModal: {
|
|
28
|
+
flexBasis: moderateHorizontalScale(54),
|
|
29
|
+
backgroundColor: '#3A3A3A',
|
|
30
|
+
flexDirection: 'row',
|
|
31
|
+
justifyContent: 'space-between',
|
|
32
|
+
alignItems: 'center',
|
|
33
|
+
paddingLeft: moderateHorizontalScale(15)
|
|
34
|
+
},
|
|
35
|
+
backButtonHeaderModal: {
|
|
36
|
+
width: moderateHorizontalScale(38),
|
|
37
|
+
height: moderateVerticalScale(38),
|
|
38
|
+
borderRadius: 50,
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
alignItems: 'center'
|
|
41
|
+
},
|
|
42
|
+
footerContainerModal: {
|
|
43
|
+
flexBasis: moderateHorizontalScale(57),
|
|
44
|
+
backgroundColor: '#3A3A3A',
|
|
45
|
+
justifyContent: 'center',
|
|
46
|
+
alignItems: 'center'
|
|
47
|
+
},
|
|
48
|
+
buttonContainerFooterModal: {
|
|
49
|
+
alignItems: 'center'
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default styles;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Image } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import Label from '../../../Label';
|
|
5
|
+
|
|
6
|
+
import styles from './styles';
|
|
7
|
+
import propTypes from './propTypes';
|
|
8
|
+
|
|
9
|
+
const ReplaceView = ({ label, color, sourceEditImage }) => (
|
|
10
|
+
<View style={styles.buttonContainer}>
|
|
11
|
+
<Image source={sourceEditImage} style={styles.image} />
|
|
12
|
+
<Label color={color}>{label}</Label>
|
|
13
|
+
</View>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
ReplaceView.propTypes = propTypes;
|
|
17
|
+
|
|
18
|
+
export default ReplaceView;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { moderateHorizontalScale, moderateVerticalScale } from '../../../../utils/scaleUtils';
|
|
4
|
+
|
|
5
|
+
const styles = StyleSheet.create({
|
|
6
|
+
buttonContainer: {
|
|
7
|
+
alignItems: 'center'
|
|
8
|
+
},
|
|
9
|
+
image: {
|
|
10
|
+
width: moderateHorizontalScale(24),
|
|
11
|
+
height: moderateVerticalScale(24)
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default styles;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import Icon from '../../../Icon';
|
|
5
|
+
|
|
6
|
+
import { styles, colorIcon } from './styles';
|
|
7
|
+
import propTypes from './propTypes';
|
|
8
|
+
|
|
9
|
+
const SvgIconCheck = ({ size }) => (
|
|
10
|
+
<View style={styles(size).backgroundIcon}>
|
|
11
|
+
<Icon color={colorIcon} name="check" size={16} />
|
|
12
|
+
</View>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
SvgIconCheck.propTypes = propTypes;
|
|
16
|
+
|
|
17
|
+
export default SvgIconCheck;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { moderateVerticalScale } from '../../../../utils/scaleUtils';
|
|
4
|
+
|
|
5
|
+
export const styles = size =>
|
|
6
|
+
StyleSheet.create({
|
|
7
|
+
backgroundIcon: {
|
|
8
|
+
margin: moderateVerticalScale(10),
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
justifyContent: 'center',
|
|
11
|
+
backgroundColor: '#4D98FA',
|
|
12
|
+
borderRadius: 1000,
|
|
13
|
+
width: size,
|
|
14
|
+
height: size
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const colorIcon = 'white';
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React, { Fragment, useEffect, useState } from 'react';
|
|
2
|
+
// eslint-disable-next-line react-native/split-platform-components
|
|
3
|
+
import { View, TouchableOpacity, PermissionsAndroid } from 'react-native';
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
5
|
+
import { launchCamera } from 'react-native-image-picker';
|
|
6
|
+
|
|
7
|
+
import Label from '../Label';
|
|
8
|
+
import Portal from '../Portal';
|
|
9
|
+
|
|
10
|
+
import AddAnotherPhotoButton from './components/AddAnotherPhotoButton';
|
|
11
|
+
import DeleteView from './components/DeleteView';
|
|
12
|
+
import ReplaceView from './components/ReplaceView';
|
|
13
|
+
import Photo from './components/Photo';
|
|
14
|
+
import styles from './styles';
|
|
15
|
+
import propTypes from './propTypes';
|
|
16
|
+
|
|
17
|
+
const PhotoAlbum = ({
|
|
18
|
+
images,
|
|
19
|
+
selectedImages,
|
|
20
|
+
setSelectedImages,
|
|
21
|
+
deletePhoto,
|
|
22
|
+
selectedMode,
|
|
23
|
+
isItPossibleToAddAnotherPhoto,
|
|
24
|
+
onPressAddAnotherPhoto,
|
|
25
|
+
onError,
|
|
26
|
+
title,
|
|
27
|
+
subTitle,
|
|
28
|
+
onUnknownCameraError,
|
|
29
|
+
numberOfPhotosInTheAlbumHasExceeded,
|
|
30
|
+
isOnlyOnePicture,
|
|
31
|
+
sourceCameraImage,
|
|
32
|
+
sourceEditImage,
|
|
33
|
+
messageErrorPermissionCamera
|
|
34
|
+
}) => {
|
|
35
|
+
const requestPermission = async () => {
|
|
36
|
+
const permissionsResponse = await PermissionsAndroid.requestMultiple([
|
|
37
|
+
PermissionsAndroid.PERMISSIONS.CAMERA,
|
|
38
|
+
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const granted =
|
|
42
|
+
permissionsResponse[PermissionsAndroid.PERMISSIONS.CAMERA] === 'granted' &&
|
|
43
|
+
permissionsResponse[PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE] === 'granted';
|
|
44
|
+
|
|
45
|
+
if (!granted) {
|
|
46
|
+
throw messageErrorPermissionCamera;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const [isSelectedMode, setIsSelectedMode] = useState(false);
|
|
51
|
+
const onLongPressImage = uri => {
|
|
52
|
+
if (selectedImages.includes(uri)) {
|
|
53
|
+
setSelectedImages(selectedImages.filter(aPicUri => aPicUri !== uri));
|
|
54
|
+
} else {
|
|
55
|
+
setSelectedImages([...selectedImages, uri]);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const deleteSelectedPhotos = () => {
|
|
59
|
+
deletePhoto();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const openCamera = async () => {
|
|
63
|
+
await requestPermission();
|
|
64
|
+
|
|
65
|
+
const response = await launchCamera({});
|
|
66
|
+
const { didCancel, errorCode, errorMessage, assets } = response;
|
|
67
|
+
if (didCancel || errorCode) {
|
|
68
|
+
if (errorCode) {
|
|
69
|
+
onUnknownCameraError(`[${errorCode}] : ${errorMessage}`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const { uri } = assets[0];
|
|
73
|
+
onPressAddAnotherPhoto({ newImage: { uri }, numberOfPhotosInTheAlbumHasExceeded });
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const isDivisibleInModuleOfThree = () => {
|
|
78
|
+
const numberOfObjectsInView = isItPossibleToAddAnotherPhoto ? images.length + 1 : images.length;
|
|
79
|
+
return numberOfObjectsInView % 3 !== 0;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const cameraOnPress = () =>
|
|
83
|
+
openCamera().catch(error => {
|
|
84
|
+
if (typeof error === 'string') {
|
|
85
|
+
onError(error);
|
|
86
|
+
} else {
|
|
87
|
+
onUnknownCameraError(error);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const onPressAddAnotherPhotoButton = deleteSelectedImages => {
|
|
92
|
+
if (deleteSelectedImages) {
|
|
93
|
+
setSelectedImages([]);
|
|
94
|
+
}
|
|
95
|
+
cameraOnPress();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (selectedImages.length > 0) {
|
|
100
|
+
setIsSelectedMode(true);
|
|
101
|
+
} else {
|
|
102
|
+
setIsSelectedMode(false);
|
|
103
|
+
}
|
|
104
|
+
}, [selectedImages]);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Fragment>
|
|
108
|
+
{title && (
|
|
109
|
+
<Label big semiBold color="#666666" style={styles.title}>
|
|
110
|
+
{title}
|
|
111
|
+
</Label>
|
|
112
|
+
)}
|
|
113
|
+
{subTitle && (
|
|
114
|
+
<Label color="#3A3A3A" style={styles.subTitle}>
|
|
115
|
+
{subTitle}
|
|
116
|
+
</Label>
|
|
117
|
+
)}
|
|
118
|
+
<View style={styles.container}>
|
|
119
|
+
{images.length !== 0 &&
|
|
120
|
+
images.map(image => (
|
|
121
|
+
<Photo
|
|
122
|
+
key={image.uri ?? image}
|
|
123
|
+
image={image}
|
|
124
|
+
onLongPressImage={onLongPressImage}
|
|
125
|
+
selectedImages={selectedImages}
|
|
126
|
+
deletePhoto={deletePhoto}
|
|
127
|
+
isSelectedMode={isSelectedMode}
|
|
128
|
+
onError={onError}
|
|
129
|
+
/>
|
|
130
|
+
))}
|
|
131
|
+
{isItPossibleToAddAnotherPhoto && (
|
|
132
|
+
<AddAnotherPhotoButton
|
|
133
|
+
onPress={onPressAddAnotherPhotoButton}
|
|
134
|
+
disabled={numberOfPhotosInTheAlbumHasExceeded}
|
|
135
|
+
sourceCameraImage={sourceCameraImage}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
{isDivisibleInModuleOfThree() && <View style={styles.image} />}
|
|
139
|
+
{selectedMode && isSelectedMode && (
|
|
140
|
+
<Portal>
|
|
141
|
+
<View style={styles.selectedModeContainer}>
|
|
142
|
+
<View style={styles.selectionLabelContainer}>
|
|
143
|
+
<Label color="white">{`${selectedImages.length} selección`}</Label>
|
|
144
|
+
</View>
|
|
145
|
+
<View style={styles.buttonsContainer}>
|
|
146
|
+
{isOnlyOnePicture && (
|
|
147
|
+
<TouchableOpacity
|
|
148
|
+
onPress={() => onPressAddAnotherPhotoButton(numberOfPhotosInTheAlbumHasExceeded)}
|
|
149
|
+
style={styles.deleteSelectedPhotos}
|
|
150
|
+
>
|
|
151
|
+
<ReplaceView label="Remplazar" color="#3A3A3A" sourceEditImage={sourceEditImage} />
|
|
152
|
+
</TouchableOpacity>
|
|
153
|
+
)}
|
|
154
|
+
<TouchableOpacity onPress={deleteSelectedPhotos} style={styles.deleteSelectedPhotos}>
|
|
155
|
+
<DeleteView label="Eliminar" color="#3A3A3A" />
|
|
156
|
+
</TouchableOpacity>
|
|
157
|
+
</View>
|
|
158
|
+
</View>
|
|
159
|
+
</Portal>
|
|
160
|
+
)}
|
|
161
|
+
</View>
|
|
162
|
+
</Fragment>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
PhotoAlbum.propTypes = propTypes;
|
|
167
|
+
|
|
168
|
+
export default PhotoAlbum;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { bool, string, func, array } from 'prop-types';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
images: array,
|
|
5
|
+
selectedImages: array,
|
|
6
|
+
setSelectedImages: func,
|
|
7
|
+
deletePhoto: func,
|
|
8
|
+
selectedMode: bool,
|
|
9
|
+
isItPossibleToAddAnotherPhoto: bool,
|
|
10
|
+
onPressAddAnotherPhoto: func,
|
|
11
|
+
onError: func,
|
|
12
|
+
title: string,
|
|
13
|
+
subTitle: string,
|
|
14
|
+
onUnknownCameraError: func,
|
|
15
|
+
numberOfPhotosInTheAlbumHasExceeded: bool,
|
|
16
|
+
isOnlyOnePicture: bool
|
|
17
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Dimensions, StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { moderateHorizontalScale, moderateVerticalScale, WINDOW_WIDTH } from '../../utils/scaleUtils';
|
|
4
|
+
|
|
5
|
+
const IMAGE_SIZE = (Dimensions.get('window').width - 30) * 0.333333 - 10;
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
container: {
|
|
8
|
+
flexDirection: 'row',
|
|
9
|
+
flexWrap: 'wrap',
|
|
10
|
+
justifyContent: 'space-between',
|
|
11
|
+
paddingHorizontal: moderateHorizontalScale(5)
|
|
12
|
+
},
|
|
13
|
+
selectedModeContainer: {
|
|
14
|
+
width: WINDOW_WIDTH,
|
|
15
|
+
position: 'absolute',
|
|
16
|
+
bottom: 0
|
|
17
|
+
},
|
|
18
|
+
selectionLabelContainer: {
|
|
19
|
+
height: moderateVerticalScale(27),
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
backgroundColor: '#4D98FA'
|
|
22
|
+
},
|
|
23
|
+
buttonsContainer: {
|
|
24
|
+
flexDirection: 'row',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'space-around',
|
|
27
|
+
backgroundColor: '#E4E6EA',
|
|
28
|
+
paddingHorizontal: 60
|
|
29
|
+
},
|
|
30
|
+
deleteSelectedPhotos: {
|
|
31
|
+
height: moderateVerticalScale(58),
|
|
32
|
+
justifyContent: 'center'
|
|
33
|
+
},
|
|
34
|
+
title: {
|
|
35
|
+
marginBottom: moderateVerticalScale(10),
|
|
36
|
+
paddingHorizontal: moderateHorizontalScale(5)
|
|
37
|
+
},
|
|
38
|
+
subTitle: {
|
|
39
|
+
marginBottom: moderateVerticalScale(5),
|
|
40
|
+
paddingHorizontal: moderateHorizontalScale(5)
|
|
41
|
+
},
|
|
42
|
+
image: {
|
|
43
|
+
width: IMAGE_SIZE,
|
|
44
|
+
height: IMAGE_SIZE,
|
|
45
|
+
borderRadius: 5,
|
|
46
|
+
marginBottom: moderateVerticalScale(10),
|
|
47
|
+
paddingHorizontal: moderateHorizontalScale(10)
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export default styles;
|
package/lib/index.js
CHANGED
|
@@ -69,3 +69,7 @@ export { default as withLoading } from './components/WithLoading';
|
|
|
69
69
|
// Hooks
|
|
70
70
|
export { default as useEffectOnlyOnUpdates } from './hooks/useEffectOnlyOnUpdates';
|
|
71
71
|
export { default as useTogglableState } from './hooks/useTogglableState';
|
|
72
|
+
|
|
73
|
+
// Photo
|
|
74
|
+
export { default as PhotoAlbum } from './components/PhotoAlbum';
|
|
75
|
+
export { default as Capture } from './components/Capture';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@widergy/mobile-ui",
|
|
3
3
|
"description": "Widergy Mobile Components",
|
|
4
4
|
"author": "widergy",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.34.0",
|
|
6
6
|
"repository": "https://github.com/widergy/mobile-ui.git",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"files": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"react": "*",
|
|
27
27
|
"react-native": "*",
|
|
28
28
|
"react-native-document-picker": "^3.4.0",
|
|
29
|
-
"react-native-image-picker": "
|
|
29
|
+
"react-native-image-picker": "4.x.x",
|
|
30
30
|
"react-native-image-resizer": "^1.2.2",
|
|
31
31
|
"react-native-svg": "*",
|
|
32
32
|
"react-native-vector-icons": "*"
|