@fto-consult/expo-ui 8.46.3 → 8.47.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.
@@ -31,6 +31,7 @@
31
31
  "react-native-gesture-handler": "~2.14.0",
32
32
  "react-native-reanimated": "~3.6.2",
33
33
  "react-native-view-shot": "3.8.0",
34
- "expo-intent-launcher": "~10.11.0"
34
+ "expo-intent-launcher": "~10.11.0",
35
+ "expo-image-manipulator": "~11.8.0"
35
36
  };
36
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "8.46.3",
3
+ "version": "8.47.0",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "react-native-paper-doc": "https://github.com/callstack/react-native-paper/tree/main/docs/docs/guides",
6
6
  "scripts": {
@@ -0,0 +1,311 @@
1
+ import React, { Component } from 'react'
2
+ import {
3
+ Dimensions,
4
+ Image,
5
+ ScrollView,
6
+ Modal,
7
+ View,
8
+ Text,
9
+ SafeAreaView,
10
+ TouchableOpacity,
11
+ } from 'react-native'
12
+ import * as ImageManipulator from 'expo-image-manipulator'
13
+ import PropTypes from 'prop-types'
14
+ import AutoHeightImage from '../../Avatar/AutoHeightImage'
15
+ import Icon from "$ecomponents/Icon";
16
+ import {isIphoneX } from 'react-native-iphone-x-helper'
17
+ import ImageCropOverlay from './ImageCropOverlay'
18
+
19
+ const { width, height } = Dimensions.get('window')
20
+
21
+
22
+ class ExpoImageManipulator extends Component {
23
+ constructor(props) {
24
+ super(props)
25
+ const { squareAspect } = this.props
26
+ this.state = {
27
+ cropMode: true,
28
+ processing: false,
29
+ zoomScale: 1,
30
+ squareAspect,
31
+ }
32
+
33
+ this.scrollOffset = 0
34
+
35
+ this.currentPos = {
36
+ left: 0,
37
+ top: 0,
38
+ }
39
+
40
+ this.currentSize = {
41
+ width: 0,
42
+ height: 0,
43
+ }
44
+
45
+ this.maxSizes = {
46
+ width: 0,
47
+ height: 0,
48
+ }
49
+
50
+ this.actualSize = {
51
+ width: 0,
52
+ height: 0
53
+ }
54
+ }
55
+
56
+ async componentDidMount() {
57
+ await this.onConvertImageToEditableSize()
58
+ }
59
+
60
+ async onConvertImageToEditableSize() {
61
+ const { photo: { uri: rawUri } } = this.props
62
+ const { uri, width, height } = await ImageManipulator.manipulateAsync(rawUri,
63
+ [
64
+ {
65
+ resize: {
66
+ width: 1080,
67
+ },
68
+ },
69
+ ])
70
+ this.setState({
71
+ uri,
72
+ })
73
+ this.actualSize.width = width
74
+ this.actualSize.height = height
75
+ }
76
+ onCropImage = () => {
77
+ this.setState({ processing: true })
78
+ const { uri } = this.state
79
+ Image.getSize(uri, async (actualWidth, actualHeight) => {
80
+ let cropObj = this.getCropBounds(actualWidth, actualHeight);
81
+ if (cropObj.height > 0 && cropObj.width > 0) {
82
+ let uriToCrop = uri
83
+ const { uri: uriCroped, base64, width: croppedWidth, height: croppedHeight } = await this.crop(cropObj, uriToCrop)
84
+
85
+ this.actualSize.width = croppedWidth
86
+ this.actualSize.height = croppedHeight
87
+
88
+ this.setState({
89
+ uri: uriCroped, base64, cropMode: false, processing: false,
90
+ })
91
+ } else {
92
+ this.setState({cropMode: false, processing: false})
93
+ }
94
+ })
95
+ }
96
+
97
+ onRotateImage = async () => {
98
+ const { uri } = this.state
99
+ let uriToCrop = uri
100
+ Image.getSize(uri, async (width2, height2) => {
101
+ const { uri: rotUri, base64 } = await this.rotate(uriToCrop, width2, height2)
102
+ this.setState({ uri: rotUri, base64 })
103
+ })
104
+ }
105
+
106
+ onFlipImage = async (orientation) => {
107
+ const { uri } = this.state
108
+ let uriToCrop = uri
109
+ Image.getSize(uri, async (width2, height2) => {
110
+ const { uri: rotUri, base64 } = await this.filp(uriToCrop, orientation)
111
+ this.setState({ uri: rotUri, base64 })
112
+ })
113
+ }
114
+
115
+ onHandleScroll = (event) => {
116
+ this.scrollOffset = event.nativeEvent.contentOffset.y
117
+ }
118
+
119
+ getCropBounds = (actualWidth, actualHeight) => {
120
+ let imageRatio = actualHeight / actualWidth
121
+ var originalHeight = Dimensions.get('window').height - 64
122
+ if (isIphoneX()) {
123
+ originalHeight = Dimensions.get('window').height - 122
124
+ }
125
+ let renderedImageWidth = imageRatio < (originalHeight / width) ? width : originalHeight / imageRatio
126
+ let renderedImageHeight = imageRatio < (originalHeight / width) ? width * imageRatio : originalHeight
127
+
128
+ let renderedImageY = (originalHeight - renderedImageHeight) / 2.0
129
+ let renderedImageX = (width - renderedImageWidth) / 2.0
130
+
131
+ const renderImageObj = {
132
+ left: renderedImageX,
133
+ top: renderedImageY,
134
+ width: renderedImageWidth,
135
+ height: renderedImageHeight,
136
+ }
137
+ const cropOverlayObj = {
138
+ left: this.currentPos.left,
139
+ top: this.currentPos.top,
140
+ width: this.currentSize.width,
141
+ height: this.currentSize.height,
142
+ }
143
+
144
+ var intersectAreaObj = {}
145
+
146
+ let x = Math.max(renderImageObj.left, cropOverlayObj.left);
147
+ let num1 = Math.min(renderImageObj.left + renderImageObj.width, cropOverlayObj.left + cropOverlayObj.width);
148
+ let y = Math.max(renderImageObj.top, cropOverlayObj.top);
149
+ let num2 = Math.min(renderImageObj.top + renderImageObj.height, cropOverlayObj.top + cropOverlayObj.height);
150
+ if (num1 >= x && num2 >= y)
151
+ intersectAreaObj = {
152
+ originX: (x - renderedImageX) * (actualWidth / renderedImageWidth) ,
153
+ originY: (y - renderedImageY) * (actualWidth / renderedImageWidth),
154
+ width: (num1 - x) * (actualWidth / renderedImageWidth),
155
+ height: (num2 - y) * (actualWidth / renderedImageWidth)
156
+ }
157
+ else {
158
+ intersectAreaObj = {
159
+ originX: x - renderedImageX,
160
+ originY: y - renderedImageY,
161
+ width: 0,
162
+ height: 0
163
+ }
164
+ }
165
+ return intersectAreaObj
166
+ }
167
+
168
+ filp = async (uri, orientation) => {
169
+ const { saveOptions } = this.props
170
+ const manipResult = await ImageManipulator.manipulateAsync(uri, [{
171
+ flip: orientation == 'vertical' ? ImageManipulator.FlipType.Vertical : ImageManipulator.FlipType.Horizontal
172
+ }],
173
+ saveOptions
174
+ );
175
+ return manipResult;
176
+ };
177
+
178
+ rotate = async (uri, width2, height2) => {
179
+ const { saveOptions } = this.props
180
+ const manipResult = await ImageManipulator.manipulateAsync(uri, [{
181
+ rotate: -90,
182
+ }, {
183
+ resize: {
184
+ width: this.trueWidth || width2,
185
+ // height: this.trueHeight || height2,
186
+ },
187
+ }], saveOptions)
188
+ return manipResult
189
+ }
190
+
191
+ crop = async (cropObj, uri) => {
192
+ const { saveOptions } = this.props
193
+ if (cropObj.height > 0 && cropObj.width > 0) {
194
+ const manipResult = await ImageManipulator.manipulateAsync(
195
+ uri,
196
+ [{
197
+ crop: cropObj,
198
+ }],
199
+ saveOptions,
200
+ )
201
+ return manipResult
202
+ }
203
+ return {
204
+ uri: null,
205
+ base64: null,
206
+ }
207
+ };
208
+
209
+ calculateMaxSizes = (event) => {
210
+ let w1 = event.nativeEvent.layout.width || 100
211
+ let h1 = event.nativeEvent.layout.height || 100
212
+ if (this.state.squareAspect) {
213
+ if (w1 < h1) h1 = w1
214
+ else w1 = h1
215
+ }
216
+ this.maxSizes.width = w1
217
+ this.maxSizes.height = h1
218
+ };
219
+
220
+ // eslint-disable-next-line camelcase
221
+ async UNSAFE_componentWillReceiveProps() {
222
+ await this.onConvertImageToEditableSize()
223
+ }
224
+
225
+ zoomImage() {
226
+ // this.refs.imageScrollView.zoomScale = 5
227
+ // this.setState({width: width})
228
+ // this.setState({zoomScale: 5})
229
+
230
+ // this.setState(curHeight)
231
+ }
232
+
233
+ render() {
234
+ const {
235
+ uri,
236
+ base64,
237
+ cropMode,
238
+ processing,
239
+ zoomScale
240
+ } = this.state
241
+
242
+ let imageRatio = this.actualSize.height / this.actualSize.width
243
+ var originalHeight = Dimensions.get('window').height - 64
244
+ if (isIphoneX()) {
245
+ originalHeight = Dimensions.get('window').height - 122
246
+ }
247
+
248
+ let cropRatio = originalHeight / width
249
+
250
+ let cropWidth = imageRatio < cropRatio ? width : originalHeight / imageRatio
251
+ let cropHeight = imageRatio < cropRatio ? width * imageRatio : originalHeight
252
+
253
+ let cropInitialTop = (originalHeight - cropHeight) / 2.0
254
+ let cropInitialLeft = (width - cropWidth) / 2.0
255
+
256
+
257
+ if (this.currentSize.width == 0 && cropMode) {
258
+ this.currentSize.width = cropWidth;
259
+ this.currentSize.height = cropHeight;
260
+
261
+ this.currentPos.top = cropInitialTop;
262
+ this.currentPos.left = cropInitialLeft;
263
+ }
264
+ return <>
265
+ <AutoHeightImage
266
+ style={{ backgroundColor: 'black' }}
267
+ source={{ uri }}
268
+ resizeMode={imageRatio >= 1 ? "contain" : 'contain'}
269
+ width={width}
270
+ height={originalHeight}
271
+ onLayout={this.calculateMaxSizes}
272
+ />
273
+ {!!cropMode && (
274
+ <ImageCropOverlay onLayoutChanged={(top, left, width, height) => {
275
+ this.currentSize.width = width;
276
+ this.currentSize.height = height;
277
+ this.currentPos.top = top
278
+ this.currentPos.left = left
279
+ }} initialWidth={cropWidth} initialHeight={cropHeight} initialTop={cropInitialTop} initialLeft={cropInitialLeft} minHeight={100} minWidth={100} />
280
+ )}
281
+ </>
282
+ }
283
+ }
284
+
285
+ export default ExpoImageManipulator
286
+
287
+ ExpoImageManipulator.defaultProps = {
288
+ onPictureChoosed: ({ uri, base64 }) => console.log('URI:', uri, base64),
289
+ btnTexts: {
290
+ crop: 'Crop',
291
+ rotate: 'Rotate',
292
+ done: 'Done',
293
+ processing: 'Processing',
294
+ },
295
+ dragVelocity: 100,
296
+ resizeVelocity: 50,
297
+ saveOptions: {
298
+ compress: 1,
299
+ format: ImageManipulator.SaveFormat.PNG,
300
+ base64: false,
301
+ },
302
+ }
303
+
304
+ ExpoImageManipulator.propTypes = {
305
+ onPictureChoosed: PropTypes.func,
306
+ btnTexts: PropTypes.object,
307
+ saveOptions: PropTypes.object,
308
+ photo: PropTypes.object.isRequired,
309
+ dragVelocity: PropTypes.number,
310
+ resizeVelocity: PropTypes.number,
311
+ }
@@ -0,0 +1,219 @@
1
+ import React, { Component } from 'react'
2
+ import { View, PanResponder, Dimensions } from 'react-native';
3
+
4
+ class ImageCropOverlay extends React.Component {
5
+
6
+ state = {
7
+ draggingTL: false,
8
+ draggingTM: false,
9
+ draggingTR: false,
10
+ draggingML: false,
11
+ draggingMM: false,
12
+ draggingMR: false,
13
+ draggingBL: false,
14
+ draggingBM: false,
15
+ draggingBR: false,
16
+ initialTop: this.props.initialTop,
17
+ initialLeft: this.props.initialLeft,
18
+ initialWidth: this.props.initialWidth,
19
+ initialHeight: this.props.initialHeight,
20
+
21
+ offsetTop: 0,
22
+ offsetLeft: 0,
23
+ }
24
+
25
+ panResponder = {}
26
+
27
+ UNSAFE_componentWillMount() {
28
+ this.panResponder = PanResponder.create({
29
+ onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
30
+ onPanResponderGrant: this.handlePanResponderGrant,
31
+ onPanResponderMove: this.handlePanResponderMove,
32
+ onPanResponderRelease: this.handlePanResponderEnd,
33
+ onPanResponderTerminate: this.handlePanResponderEnd,
34
+ })
35
+ }
36
+
37
+ render() {
38
+ const { draggingTL, draggingTM, draggingTR, draggingML, draggingMM, draggingMR, draggingBL, draggingBM, draggingBR, initialTop, initialLeft, initialHeight, initialWidth, offsetTop, offsetLeft} = this.state
39
+ const style = {}
40
+
41
+ style.top = initialTop + ((draggingTL || draggingTM || draggingTR || draggingMM) ? offsetTop : 0)
42
+ style.left = initialLeft + ((draggingTL || draggingML || draggingBL || draggingMM) ? offsetLeft : 0)
43
+ style.width = initialWidth + ((draggingTL || draggingML || draggingBL) ? - offsetLeft : (draggingTM || draggingMM || draggingBM) ? 0 : offsetLeft)
44
+ style.height = initialHeight + ((draggingTL || draggingTM || draggingTR) ? - offsetTop : (draggingML || draggingMM || draggingMR) ? 0 : offsetTop)
45
+
46
+ if (style.width > this.props.initialWidth) {
47
+ style.width = this.props.initialWidth
48
+ }
49
+ if (style.width < this.props.minWidth) {
50
+ style.width = this.props.minWidth
51
+ }
52
+ if (style.height > this.props.initialHeight) {
53
+ style.height = this.props.initialHeight
54
+ }
55
+ if (style.height < this.props.minHeight) {
56
+ style.height = this.props.minHeight
57
+ }
58
+ return (
59
+ <View {...this.panResponder.panHandlers} style={[{flex: 1, justifyContent: 'center', alignItems: 'center', position: 'absolute', borderStyle: 'solid', borderWidth: 2, borderColor: '#a4a4a4', backgroundColor: 'rgb(0,0,0,0.5)'}, style]}>
60
+ <View style={{flexDirection: 'row', width: '100%', flex: 1/3, backgroundColor: 'transparent'}}>
61
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingTL ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
62
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingTM ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
63
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingTR ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
64
+ </View>
65
+ <View style={{flexDirection: 'row', width: '100%', flex: 1/3, backgroundColor: 'transparent'}}>
66
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingML ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
67
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingMM ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
68
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingMR ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
69
+ </View>
70
+ <View style={{flexDirection: 'row', width: '100%', flex: 1/3, backgroundColor: 'transparent'}}>
71
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingBL ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
72
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingBM ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
73
+ <View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingBR ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
74
+ </View>
75
+ <View style={{top: 0, left: 0, width: '100%', height: '100%', position: 'absolute', backgroundColor: 'rgba(0, 0, 0, 0.5)'}}>
76
+ <View style={{flex: 1/3, flexDirection: 'row'}}>
77
+ <View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
78
+ <View style={{ position: 'absolute', left: 5, top: 5, borderLeftWidth: 2, borderTopWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
79
+ </View>
80
+ <View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
81
+ </View>
82
+ <View style={{ flex: 3, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
83
+ <View style={{ position: 'absolute', right: 5, top: 5, borderRightWidth: 2, borderTopWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
84
+ </View>
85
+ </View>
86
+ <View style={{flex: 1/3, flexDirection: 'row'}}>
87
+ <View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
88
+ </View>
89
+ <View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
90
+ </View>
91
+ <View style={{ flex: 3, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
92
+ </View>
93
+ </View>
94
+ <View style={{flex: 1/3, flexDirection: 'row'}}>
95
+ <View style={{ flex: 3, borderRightWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid', position: 'relative', }}>
96
+ <View style={{ position: 'absolute', left: 5, bottom: 5, borderLeftWidth: 2, borderBottomWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
97
+ </View>
98
+ <View style={{ flex: 3, borderRightWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
99
+ </View>
100
+ <View style={{ flex: 3, position: 'relative' }}>
101
+ <View style={{ position: 'absolute', right: 5, bottom: 5, borderRightWidth: 2, borderBottomWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
102
+ </View>
103
+ </View>
104
+ </View>
105
+ </View>
106
+ )
107
+ }
108
+
109
+ getTappedItem(x, y) {
110
+ const { initialLeft, initialTop, initialWidth, initialHeight } = this.state
111
+ let xPos = parseInt((x - initialLeft) / (initialWidth / 3))
112
+ let yPos = parseInt((y - initialTop - 64) / (initialHeight / 3))
113
+
114
+ let index = yPos * 3 + xPos
115
+ if (index == 0) {
116
+ return 'tl';
117
+ } else if (index == 1) {
118
+ return 'tm';
119
+ } else if (index == 2) {
120
+ return 'tr';
121
+ } else if (index == 3) {
122
+ return 'ml';
123
+ } else if (index == 4) {
124
+ return 'mm';
125
+ } else if (index == 5) {
126
+ return 'mr';
127
+ } else if (index == 6) {
128
+ return 'bl';
129
+ } else if (index == 7) {
130
+ return 'bm';
131
+ } else if (index == 8) {
132
+ return 'br';
133
+ } else {
134
+ return '';
135
+ }
136
+ }
137
+
138
+ // Should we become active when the user presses down on the square?
139
+ handleStartShouldSetPanResponder = (event) => {
140
+ return true
141
+ }
142
+
143
+ // We were granted responder status! Let's update the UI
144
+ handlePanResponderGrant = (event) => {
145
+ // console.log(event.nativeEvent.locationX + ', ' + event.nativeEvent.locationY)
146
+
147
+ let selectedItem = this.getTappedItem(event.nativeEvent.pageX, event.nativeEvent.pageY)
148
+ if (selectedItem == 'tl') {
149
+ this.setState({draggingTL: true})
150
+ } else if (selectedItem == 'tm') {
151
+ this.setState({draggingTM: true})
152
+ } else if (selectedItem == 'tr') {
153
+ this.setState({draggingTR: true})
154
+ } else if (selectedItem == 'ml') {
155
+ this.setState({draggingML: true})
156
+ } else if (selectedItem == 'mm') {
157
+ this.setState({draggingMM: true})
158
+ } else if (selectedItem == 'mr') {
159
+ this.setState({draggingMR: true})
160
+ } else if (selectedItem == 'bl') {
161
+ this.setState({draggingBL: true})
162
+ } else if (selectedItem == 'bm') {
163
+ this.setState({draggingBM: true})
164
+ } else if (selectedItem == 'br') {
165
+ this.setState({draggingBR: true})
166
+ }
167
+ }
168
+
169
+ // Every time the touch/mouse moves
170
+ handlePanResponderMove = (e, gestureState) => {
171
+ // Keep track of how far we've moved in total (dx and dy)
172
+ this.setState({
173
+ offsetTop: gestureState.dy,
174
+ offsetLeft: gestureState.dx,
175
+ })
176
+ }
177
+
178
+ // When the touch/mouse is lifted
179
+ handlePanResponderEnd = (e, gestureState) => {
180
+ const {initialTop, initialLeft, initialWidth, initialHeight, draggingTL, draggingTM, draggingTR, draggingML, draggingMM, draggingMR, draggingBL, draggingBM, draggingBR } = this.state
181
+
182
+ const state = {
183
+ draggingTL: false,
184
+ draggingTM: false,
185
+ draggingTR: false,
186
+ draggingML: false,
187
+ draggingMM: false,
188
+ draggingMR: false,
189
+ draggingBL: false,
190
+ draggingBM: false,
191
+ draggingBR: false,
192
+ offsetTop: 0,
193
+ offsetLeft: 0,
194
+ }
195
+
196
+ state.initialTop = initialTop + ((draggingTL || draggingTM || draggingTR || draggingMM) ? gestureState.dy : 0)
197
+ state.initialLeft = initialLeft + ((draggingTL || draggingML || draggingBL || draggingMM) ? gestureState.dx : 0)
198
+ state.initialWidth = initialWidth + ((draggingTL || draggingML || draggingBL) ? - gestureState.dx : (draggingTM || draggingMM || draggingBM) ? 0 : gestureState.dx)
199
+ state.initialHeight = initialHeight + ((draggingTL || draggingTM || draggingTR) ? - gestureState.dy : (draggingML || draggingMM || draggingMR) ? 0 : gestureState.dy)
200
+
201
+ if (state.initialWidth > this.props.initialWidth) {
202
+ state.initialWidth = this.props.initialWidth
203
+ }
204
+ if (state.initialWidth < this.props.minWidth) {
205
+ state.initialWidth = this.props.minWidth
206
+ }
207
+ if (state.initialHeight > this.props.initialHeight) {
208
+ state.initialHeight = this.props.initialHeight
209
+ }
210
+ if (state.initialHeight < this.props.minHeight) {
211
+ state.initialHeight = this.props.minHeight
212
+ }
213
+
214
+ this.setState(state)
215
+ this.props.onLayoutChanged(state.initialTop, state.initialLeft, state.initialWidth, state.initialHeight)
216
+ }
217
+ }
218
+
219
+ export default ImageCropOverlay;
@@ -0,0 +1,83 @@
1
+ import React, { useState, useEffect,useMemo,useRef } from '$react';
2
+ import { View, StyleSheet,ImageBackground} from 'react-native';
3
+ import theme from "$theme";
4
+ import ActivityIndicator from "$ecomponents/ActivityIndicator";
5
+ import Label from "$ecomponents/Label";
6
+ import PropTypes from "prop-types";
7
+ import { isNonNullString,defaultStr,defaultObj } from '$cutils';
8
+ import Button from "$ecomponents/Button";
9
+ import Dialog from "$ecomponents/Dialog";
10
+ import ExpoImageManipulator from './ExpoImageManipulator';
11
+ import ImageCropOverlay from './ImageCropOverlay';
12
+
13
+
14
+ /***@see : https://docs.expo.dev/versions/latest/sdk/bar-code-scanner/ */
15
+ export default function ImageCropperComponent({src,testID,onCancel,dialogProps}) {
16
+ testID = defaultStr(testID,"RN_ImageCropperComponent");
17
+ const [visible,setVisible] = useState(true);
18
+ dialogProps = Object.assign({},dialogProps);
19
+ const prevVisible = React.usePrevious(visible);
20
+ const cancelRef = React.useRef(false);
21
+ const cancel = ()=>{
22
+ cancelRef.current = true;
23
+ setVisible(false);
24
+ }
25
+ useEffect(()=>{
26
+ if(prevVisible === visible) return;
27
+ if(prevVisible && !visible && cancelRef.current && typeof onCancel =="function"){
28
+ onCancel();
29
+ }
30
+ cancelRef.current = false;
31
+ },[visible]);
32
+ return <Dialog
33
+ fullPage
34
+ actions={[]}
35
+ title = {`Rogner l'image`}
36
+ {...dialogProps}
37
+ onBackActionPress={cancel}
38
+ visible = {visible}
39
+ >
40
+ <ImageBackground
41
+ resizeMode="contain"
42
+ style={styles.imageBackground}
43
+ source={{ uri : src }}
44
+ >
45
+ <ImageCropOverlay onLayoutChanged={(top, left, width, height) => {
46
+ console.log(top,lef,width,height," is to lefff")
47
+ }} initialWidth={50} initialHeight={50}
48
+ initialTop={0} initialLeft={0}
49
+ minHeight={100} minWidth={100}
50
+ />
51
+ </ImageBackground>
52
+ </Dialog>;
53
+ }
54
+ const styles = StyleSheet.create({
55
+ center : {
56
+ justifyContent : "center",
57
+ alignItems : "center",
58
+ flexDirection : "column",
59
+ flex : 1,
60
+ },
61
+ row : {
62
+ flexDirection : "row",
63
+ justifyContent : "center",
64
+ alignItems : "center",
65
+ flexWrap :"wrap",
66
+ },
67
+ imageBackground : {
68
+ flex : 1,
69
+ width : "100%",
70
+ height : "100%",
71
+ justifyContent: 'center',
72
+ padding: 20, alignItems: 'center',
73
+ }
74
+ });
75
+
76
+ /***
77
+ @see : https://docs.expo.dev/versions/latest/sdk/camera-next
78
+ */
79
+ ImageCropperComponent.propTypes = {
80
+ onScan : PropTypes.func,
81
+ onGrantAccess : PropTypes.func, //lorsque la permission est allouée
82
+ onDenyAccess : PropTypes.func, //lorsque la permission est refusée
83
+ }
@@ -6,7 +6,6 @@ import {defaultObj} from "$cutils";
6
6
 
7
7
  const ImageEditorComponent = React.forwardRef((props,ref)=>{
8
8
  let {source,uri,onSuccess,imageUri,lockAspectRatio,dialogProps,onDismiss,visible,imageProps,...rest} = props;
9
- const isMounted = React.useIsMounted();
10
9
  const [context] = React.useState({});
11
10
  imageProps = defaultObj(imageProps);
12
11
  dialogProps = defaultObj(dialogProps);
@@ -3,15 +3,15 @@ import Menu from "$ecomponents/Menu";
3
3
  import Avatar from "$ecomponents/Avatar";
4
4
  import {isDecimal,setQueryParams,isValidURL,defaultDecimal,defaultStr as defaultString,isDataURL,isPromise,defaultBool,isObj,isNonNullString} from "$cutils";
5
5
  import notify from "$enotify";
6
- let maxWidthDiff = 150, maxHeightDiff = 150;
6
+ let maxWidthDiff = 100, maxHeightDiff = 100;
7
7
  import {StyleSheet} from "react-native";
8
8
  import React from "$react";
9
9
  import PropTypes from "prop-types";
10
10
  import {isMobileNative} from "$cplatform";
11
+ import {uniqid} from "$cutils";
11
12
  //import Signature from "$ecomponents/Signature";
12
13
  import Label from "$ecomponents/Label";
13
- //import Editor from "./Editor";
14
- import {Component as CameraComponent} from "$emedia/camera";
14
+ //import Cropper from "./Cropper";
15
15
 
16
16
  import {pickImage,nonZeroMin,canTakePhoto,takePhoto} from "$emedia";
17
17
  import addPhoto from "$eassets/add_photo.png";
@@ -48,11 +48,8 @@ export const getUri = (src,onlySting)=>{
48
48
 
49
49
  export default function ImageComponent(props){
50
50
  const [src,setSrc] = React.useState(defaultVal(props.src));
51
+ const [cropWindowProp,setCropWindowProp] = React.useState(null);
51
52
  const prevSrc = React.usePrevious(src);
52
- /*const [editorProps,setEditorProps] = React.useState({
53
- visible : false,
54
- options : {}
55
- })*/
56
53
  const [isDrawing,setIsDrawing] = React.useState(false);
57
54
  let {disabled,onMount,defaultSource,editable,onUnmount,label,text,labelProps,readOnly,beforeRemove,
58
55
  onChange,draw,round,drawText,drawLabel,rounded,defaultSrc,
@@ -114,8 +111,8 @@ export default function ImageComponent(props){
114
111
  if(!imageWidth && !imageHeight){
115
112
  imageWidth = imageHeight = rest.size;
116
113
  }
117
- let cropWidth = nonZeroMin(cropProps.width,width)
118
- let cropHeight = nonZeroMin(cropProps.height,height);
114
+ let cropWidth = nonZeroMin(cropProps.width,imageWidth,width,size)
115
+ let cropHeight = nonZeroMin(cropProps.height,imageHeight,height,size);
119
116
  if(!cropWidth) cropWidth = undefined;
120
117
  if(!cropHeight) cropHeight = undefined;
121
118
 
@@ -125,24 +122,26 @@ export default function ImageComponent(props){
125
122
  if(cropWidth || cropHeight){
126
123
  canCrop = true;
127
124
  if(cropWidth) opts.width = cropWidth;
128
- else if(cropHeight) opts.height = cropHeight;
125
+ if(cropHeight) opts.height = cropHeight;
129
126
  }
130
127
  opts.allowsEditing = canCrop;
131
- return opts;
128
+ return {...cropProps,...opts};
132
129
  }
133
130
  const handlePickedImage = (image,opts)=>{
134
131
  opts = defaultObj(opts);
135
132
  if(!isDataURL(image.dataURL)){
136
133
  return notify.error(`Le fichier sélectionné est une image non valide`);
137
134
  }
138
- let diffWidth = image.width - cropWidth - maxWidthDiff,
139
- diffHeight = image.height - cropHeight - maxHeightDiff;
140
- let canCrop = isMobileNative()? false : ((width && diffWidth > 0) || (height && diffHeight > 0)? true : false);
141
135
  const imageSrc = pickUri ? image.uri : image.dataURL;
142
- if(canCrop){
143
- return context.cropImage({source:image,uri:image.dataURL,...opts}).then((props)=>{
144
- setSrc(imageSrc)
145
- });
136
+ if(imageSrc){
137
+ const diffWidth = image.width - cropWidth - maxWidthDiff,diffHeight = image.height - cropHeight - maxHeightDiff;
138
+ const canCrop = isMobileNative()? false : ((diffWidth > 0) || (diffHeight > 0)? true : false);
139
+ if(canCrop){
140
+ const cProps = getCropProps(opts);
141
+ return context.cropImage({...cProps,source:image,uri:image.dataURL,src:imageSrc}).then((props)=>{
142
+ setSrc(imageSrc)
143
+ });
144
+ }
146
145
  }
147
146
  pickedImageRef.current = image;
148
147
  setSrc(imageSrc);
@@ -170,6 +169,11 @@ export default function ImageComponent(props){
170
169
  },[src]),
171
170
  cropImage : (props)=>{
172
171
  return Promise.resolve(props);
172
+ if(!isMobileNative()){
173
+ return new Promise((resolve,reject)=>{
174
+ setCropWindowProp(props);
175
+ });
176
+ }
173
177
  return new Promise((resolve,reject)=>{
174
178
  console.log({...editorProps,visible:true,...props},"is editor props");
175
179
  setEditorProps({...editorProps,visible:true,...props})
@@ -263,6 +267,11 @@ export default function ImageComponent(props){
263
267
  const _label = withLabel !== false ? defaultString(label) : "";
264
268
  const isDisabled = menuItems.length > 0 ? true : false;
265
269
  return <View testID={testID+"_FagmentContainer"}>
270
+ {false && src && !isMobileNative() && isObj(cropWindowProp) && Object.size(cropWindowProp,true) ? <Cropper
271
+ src={src}
272
+ {...cropWindowProp}
273
+ key = {uniqid("crop-image")}
274
+ /> : null}
266
275
  {!createSignatureOnly ? (<Menu
267
276
  {...menuProps}
268
277
  disabled = {isDisabled}
@@ -1,21 +1,22 @@
1
1
  module.exports = {
2
2
  "@fto-consult/expo-ui": {
3
- "name": "@fto-consult/expo-ui",
4
- "version": "8.46.2",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/borispipo/expo-ui.git"
8
- },
9
- "homepage": "https://github.com/borispipo/expo-ui#readme"
3
+ "version": "8.46.3",
4
+ "url": "https://github.com/borispipo/expo-ui#readme",
5
+ "license": "ISC"
10
6
  },
11
7
  "@babel/plugin-proposal-export-namespace-from": {
12
8
  "version": "7.18.9",
13
9
  "url": "https://babel.dev/docs/en/next/babel-plugin-proposal-export-namespace-from",
14
10
  "license": "MIT"
15
11
  },
16
- "@emotion/react": {
17
- "version": "11.11.4",
18
- "url": "https://github.com/emotion-js/emotion/tree/main/packages/react",
12
+ "@emotion/native": {
13
+ "version": "11.11.0",
14
+ "url": "https://emotion.sh",
15
+ "license": "MIT"
16
+ },
17
+ "@expo/html-elements": {
18
+ "version": "0.9.1",
19
+ "url": "https://github.com/expo/expo/tree/main/packages/html-elements",
19
20
  "license": "MIT"
20
21
  },
21
22
  "@expo/metro-config": {
@@ -23,28 +24,49 @@ module.exports = {
23
24
  "url": "https://github.com/expo/expo.git",
24
25
  "license": "MIT"
25
26
  },
27
+ "@expo/vector-icons": {
28
+ "version": "14.0.0",
29
+ "url": "https://expo.github.io/vector-icons",
30
+ "license": "MIT"
31
+ },
26
32
  "@expo/webpack-config": {
27
33
  "version": "19.0.1",
28
34
  "url": "https://github.com/expo/expo-webpack-integrations/tree/main/packages/webpack-config#readme",
29
35
  "license": "MIT"
30
36
  },
31
- "@faker-js/faker": {
32
- "version": "8.4.1",
33
- "url": "https://github.com/faker-js/faker.git",
37
+ "@pchmn/expo-material3-theme": {
38
+ "version": "1.3.2",
39
+ "url": "https://github.com/pchmn/expo-material3-theme#readme",
34
40
  "license": "MIT"
35
41
  },
36
- "@fto-consult/common": {
37
- "version": "4.36.0",
38
- "url": "https://github.com/borispipo/common#readme",
39
- "license": "ISC"
42
+ "@react-native-community/netinfo": {
43
+ "version": "11.1.0",
44
+ "url": "https://github.com/react-native-netinfo/react-native-netinfo#readme",
45
+ "license": "MIT"
46
+ },
47
+ "@react-native/assets-registry": {
48
+ "version": "0.74.0",
49
+ "url": "https://github.com/facebook/react-native/tree/HEAD/packages/assets#readme",
50
+ "license": "MIT"
40
51
  },
41
- "@fto-consult/node-utils": {
42
- "version": "1.7.1",
52
+ "@react-navigation/native": {
53
+ "version": "6.1.10",
54
+ "url": "https://reactnavigation.org",
43
55
  "license": "MIT"
44
56
  },
45
- "apexcharts": {
46
- "version": "3.47.0",
47
- "url": "https://apexcharts.com",
57
+ "@react-navigation/native-stack": {
58
+ "version": "6.9.18",
59
+ "url": "https://github.com/software-mansion/react-native-screens#readme",
60
+ "license": "MIT"
61
+ },
62
+ "@react-navigation/stack": {
63
+ "version": "6.3.21",
64
+ "url": "https://reactnavigation.org/docs/stack-navigator/",
65
+ "license": "MIT"
66
+ },
67
+ "@shopify/flash-list": {
68
+ "version": "1.6.3",
69
+ "url": "https://shopify.github.io/flash-list/",
48
70
  "license": "MIT"
49
71
  },
50
72
  "babel-plugin-inline-dotenv": {
@@ -57,122 +79,108 @@ module.exports = {
57
79
  "url": "https://github.com/tleunen/babel-plugin-module-resolver.git",
58
80
  "license": "MIT"
59
81
  },
60
- "crypto-browserify": {
61
- "version": "3.12.0",
62
- "url": "https://github.com/crypto-browserify/crypto-browserify",
82
+ "expo": {
83
+ "version": "50.0.11",
84
+ "url": "https://github.com/expo/expo/tree/main/packages/expo",
63
85
  "license": "MIT"
64
86
  },
65
- "file-saver": {
66
- "version": "2.0.5",
67
- "url": "https://github.com/eligrey/FileSaver.js#readme",
87
+ "expo-camera": {
88
+ "version": "14.0.6",
89
+ "url": "https://docs.expo.dev/versions/latest/sdk/camera/",
68
90
  "license": "MIT"
69
91
  },
70
- "google-libphonenumber": {
71
- "version": "3.2.34",
72
- "url": "https://ruimarinho.github.io/google-libphonenumber/",
73
- "license": "(MIT AND Apache-2.0)"
74
- },
75
- "html2canvas": {
76
- "version": "1.4.1",
77
- "url": "https://html2canvas.hertzen.com",
92
+ "expo-clipboard": {
93
+ "version": "5.0.1",
94
+ "url": "https://docs.expo.dev/versions/latest/sdk/clipboard",
78
95
  "license": "MIT"
79
96
  },
80
- "htmlparser2-without-node-native": {
81
- "version": "3.9.2",
82
- "url": "git://github.com/fb55/htmlparser2.git",
97
+ "expo-font": {
98
+ "version": "11.10.3",
99
+ "url": "https://docs.expo.dev/versions/latest/sdk/font/",
83
100
  "license": "MIT"
84
101
  },
85
- "is-plain-obj": {
86
- "version": "4.1.0",
102
+ "expo-image-picker": {
103
+ "version": "14.7.1",
104
+ "url": "https://docs.expo.dev/versions/latest/sdk/imagepicker/",
87
105
  "license": "MIT"
88
106
  },
89
- "jsbarcode": {
90
- "version": "3.11.6",
91
- "url": "https://github.com/lindell/JsBarcode#readme",
107
+ "expo-linking": {
108
+ "version": "6.2.2",
109
+ "url": "https://docs.expo.dev/versions/latest/sdk/linking",
92
110
  "license": "MIT"
93
111
  },
94
- "prop-types": {
95
- "version": "15.8.1",
96
- "url": "https://facebook.github.io/react/",
112
+ "expo-sharing": {
113
+ "version": "11.10.0",
114
+ "url": "https://docs.expo.dev/versions/latest/sdk/sharing/",
97
115
  "license": "MIT"
98
116
  },
99
- "react-content-loader": {
100
- "version": "6.2.1",
101
- "url": "https://github.com/danilowoz/react-content-loader",
117
+ "expo-sqlite": {
118
+ "version": "13.3.0",
119
+ "url": "https://docs.expo.dev/versions/latest/sdk/sqlite/",
102
120
  "license": "MIT"
103
121
  },
104
- "react-dom": {
105
- "version": "18.2.0",
106
- "url": "https://reactjs.org/",
122
+ "expo-status-bar": {
123
+ "version": "1.11.1",
124
+ "url": "https://docs.expo.dev/versions/latest/sdk/status-bar/",
107
125
  "license": "MIT"
108
126
  },
109
- "react-native-big-list": {
110
- "version": "1.6.1",
111
- "url": "https://marcocesarato.github.io/react-native-big-list-docs/",
112
- "license": "GPL-3.0-or-later"
113
- },
114
- "react-native-iphone-x-helper": {
115
- "version": "1.3.1",
116
- "url": "https://github.com/ptelad/react-native-iphone-x-helper#readme",
127
+ "expo-system-ui": {
128
+ "version": "2.9.3",
129
+ "url": "https://docs.expo.dev/versions/latest/sdk/system-ui",
117
130
  "license": "MIT"
118
131
  },
119
- "react-native-mime-types": {
120
- "version": "2.5.0",
132
+ "expo-web-browser": {
133
+ "version": "12.8.2",
134
+ "url": "https://docs.expo.dev/versions/latest/sdk/webbrowser/",
121
135
  "license": "MIT"
122
136
  },
123
- "react-native-paper": {
124
- "version": "5.12.3",
125
- "url": "https://callstack.github.io/react-native-paper",
137
+ "react": {
138
+ "version": "18.2.0",
139
+ "url": "https://reactjs.org/",
126
140
  "license": "MIT"
127
141
  },
128
- "react-native-paper-dates": {
129
- "version": "0.22.3",
130
- "url": "https://github.com/web-ridge/react-native-paper-dates#readme",
142
+ "react-native": {
143
+ "version": "0.73.4",
144
+ "url": "https://reactnative.dev/",
131
145
  "license": "MIT"
132
146
  },
133
- "react-native-web": {
134
- "version": "0.19.10",
135
- "url": "git://github.com/necolas/react-native-web.git",
147
+ "react-native-gesture-handler": {
148
+ "version": "2.14.1",
149
+ "url": "https://github.com/software-mansion/react-native-gesture-handler#readme",
136
150
  "license": "MIT"
137
151
  },
138
- "react-virtuoso": {
139
- "version": "4.7.2",
140
- "url": "https://virtuoso.dev/",
152
+ "react-native-get-random-values": {
153
+ "version": "1.8.0",
141
154
  "license": "MIT"
142
155
  },
143
- "readable-stream": {
144
- "version": "4.5.2",
145
- "url": "https://github.com/nodejs/readable-stream",
156
+ "react-native-reanimated": {
157
+ "version": "3.6.2",
158
+ "url": "https://github.com/software-mansion/react-native-reanimated#readme",
146
159
  "license": "MIT"
147
160
  },
148
- "sanitize-filename": {
149
- "version": "1.6.3",
150
- "url": "git@github.com:parshap/node-sanitize-filename.git",
151
- "license": "WTFPL OR ISC"
152
- },
153
- "sharp-cli": {
154
- "version": "2.1.1",
155
- "url": "https://github.com/vseventer/sharp-cli",
161
+ "react-native-safe-area-context": {
162
+ "version": "4.8.2",
163
+ "url": "https://github.com/th3rdwave/react-native-safe-area-context#readme",
156
164
  "license": "MIT"
157
165
  },
158
- "stream-browserify": {
159
- "version": "3.0.0",
160
- "url": "https://github.com/browserify/stream-browserify",
166
+ "react-native-screens": {
167
+ "version": "3.29.0",
168
+ "url": "https://github.com/software-mansion/react-native-screens#readme",
161
169
  "license": "MIT"
162
170
  },
163
- "tippy.js": {
164
- "version": "6.3.7",
165
- "url": "https://atomiks.github.io/tippyjs/",
171
+ "react-native-svg": {
172
+ "version": "14.1.0",
173
+ "url": "https://github.com/react-native-community/react-native-svg",
166
174
  "license": "MIT"
167
175
  },
168
- "vm": {
169
- "version": "0.1.0",
170
- "url": "https://github.com/DiegoRBaquero/node-vm#readme",
176
+ "react-native-view-shot": {
177
+ "version": "3.8.0",
178
+ "url": "https://github.com/gre/react-native-view-shot",
171
179
  "license": "MIT"
172
180
  },
173
- "xlsx": {
174
- "version": "0.18.5",
175
- "url": "https://sheetjs.com/",
176
- "license": "Apache-2.0"
181
+ "react-native-webview": {
182
+ "version": "13.6.4",
183
+ "url": "https://github.com/react-native-webview/react-native-webview#readme",
184
+ "license": "MIT"
177
185
  }
178
186
  };