@fto-consult/expo-ui 7.10.0 → 7.12.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/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@fto-consult/expo-ui",
|
3
|
-
"version": "7.
|
3
|
+
"version": "7.12.0",
|
4
4
|
"description": "Bibliothèque de composants UI Expo,react-native",
|
5
5
|
"scripts": {
|
6
6
|
"clear-npx-cache": "npx clear-npx-cache",
|
@@ -72,7 +72,7 @@
|
|
72
72
|
"@expo/vector-icons": "^13.0.0",
|
73
73
|
"@faker-js/faker": "^8.0.2",
|
74
74
|
"@fto-consult/common": "^3.75.2",
|
75
|
-
"@fto-consult/electron-gen": "^2.
|
75
|
+
"@fto-consult/electron-gen": "^2.2.0",
|
76
76
|
"@pchmn/expo-material3-theme": "^1.3.1",
|
77
77
|
"@react-native-async-storage/async-storage": "1.18.2",
|
78
78
|
"@react-native-community/datetimepicker": "7.2.0",
|
@@ -113,7 +113,7 @@
|
|
113
113
|
"react-native-get-random-values": "~1.9.0",
|
114
114
|
"react-native-iphone-x-helper": "^1.3.1",
|
115
115
|
"react-native-mime-types": "^2.4.0",
|
116
|
-
"react-native-paper": "^
|
116
|
+
"react-native-paper": "^4.12.6",
|
117
117
|
"react-native-paper-dates": "^0.21.7",
|
118
118
|
"react-native-reanimated": "~3.3.0",
|
119
119
|
"react-native-safe-area-context": "4.6.3",
|
@@ -158,7 +158,7 @@ const ButtonComponent = React.forwardRef((prs,ref) => {
|
|
158
158
|
borderColor = Colors.isValid(style.borderColor)? style.borderColor : isCancelButton ? theme.styles.onError : Colors.isValid(borderColor)? borderColor : undefined;
|
159
159
|
}
|
160
160
|
if(theme.isDark() && !hasElevation){
|
161
|
-
textColor = white;
|
161
|
+
//textColor = white;
|
162
162
|
}
|
163
163
|
|
164
164
|
const rippleColor = Colors.setAlpha(textColor,0.32);
|
@@ -54,7 +54,7 @@ export default function ImageComponent(props){
|
|
54
54
|
options : {}
|
55
55
|
})*/
|
56
56
|
const [isDrawing,setIsDrawing] = React.useState(false);
|
57
|
-
let {disabled,onMount,defaultSource,onUnmount,label,text,labelProps,readOnly,beforeRemove,
|
57
|
+
let {disabled,onMount,defaultSource,editable,onUnmount,label,text,labelProps,readOnly,beforeRemove,
|
58
58
|
onChange,draw,round,drawText,drawLabel,rounded,defaultSrc,
|
59
59
|
createSignatureOnly,pickImageProps,width,height,cropProps,size,resizeProps,containerProps,
|
60
60
|
menuProps,pickUri,drawProps,imageProps,length,testID,...rest} = props;
|
@@ -75,6 +75,9 @@ export default function ImageComponent(props){
|
|
75
75
|
if(disabled){
|
76
76
|
readOnly = true;
|
77
77
|
}
|
78
|
+
if(editable ===false){
|
79
|
+
readOnly = true;
|
80
|
+
}
|
78
81
|
React.useEffect(()=>{
|
79
82
|
if(src == props.src) return;
|
80
83
|
setSrc(props.src);
|
@@ -308,6 +311,7 @@ ImageComponent.propTypes = {
|
|
308
311
|
menuProps : PropTypes.object, ///les props du menu d'édition du composant,
|
309
312
|
readOnly : PropTypes.bool,
|
310
313
|
disabled: PropTypes.bool,
|
314
|
+
editable : PropTypes.bool,//si la source de l'image peut être modifiée, via le menu Sélectionner une image ou prendre une photo en fonction de la plateforme
|
311
315
|
pickUri : PropTypes.bool,////si l'uri sera retournée lorsqu'on pick l'image en lieu et place du dataURL
|
312
316
|
imageProps : PropTypes.object, ///les props supplémentaires du composant Image
|
313
317
|
draw : PropTypes.bool, //si l'on peut déssiner une image
|
@@ -0,0 +1,478 @@
|
|
1
|
+
import * as FileSaver from "$efile-system/utils/FileSaver";
|
2
|
+
import {isElectron} from "$cplatform";
|
3
|
+
import notify from "$cnotify";
|
4
|
+
import Preloader from "$preloader";
|
5
|
+
import {defaultStr,isNonNullString,defaultObj,isDataURL,getFileExtension,isPromise} from "$cutils";
|
6
|
+
import {HStack} from "$ecomponents/Stack";
|
7
|
+
import Label from "$ecomponents/Label";
|
8
|
+
import DialogProvider from "$ecomponents/Form/FormData/DialogProvider";
|
9
|
+
import session from "$session";
|
10
|
+
import Image from "$ecomponents/Image";
|
11
|
+
import View from "$ecomponents/View";
|
12
|
+
import theme from "$theme";
|
13
|
+
import APP from "$capp/instance";
|
14
|
+
import useContext from "$econtext";
|
15
|
+
import Button from "$ecomponents/Button";
|
16
|
+
import {ProgressBar} from "react-native-paper";
|
17
|
+
|
18
|
+
export const events = {
|
19
|
+
RECORDING_STARTED : "RECORDING_STARTED",
|
20
|
+
RECORDING_PAUSED : "RECORDING_PAUSED",
|
21
|
+
RECORDING_STOPED : "RECORDING_STOPRED",
|
22
|
+
RECORDING_STATUS : "RECORDING_STATUS",
|
23
|
+
}
|
24
|
+
import {useState,useEffect,useMemo,useRef} from "react";
|
25
|
+
|
26
|
+
const startSessionKey = "desktop-capturer-session";
|
27
|
+
const actionsSessionKey = "desktop-capturer-actions";
|
28
|
+
|
29
|
+
export const canRecord = x=> isElectron()? true : typeof navigator !=="undefined" && window?.navigator && (navigator?.mediaDevices) && typeof navigator?.mediaDevices?.getDisplayMedia === 'function';
|
30
|
+
|
31
|
+
export const updateSystemTray = x => false;
|
32
|
+
|
33
|
+
export function getUserMedia(constraints) {
|
34
|
+
// if Promise-based API is available, use it
|
35
|
+
if ((navigator?.mediaDevices && typeof navigator?.mediaDevices?.getUserMedia =="function")) {
|
36
|
+
return navigator.mediaDevices.getUserMedia(constraints);
|
37
|
+
}
|
38
|
+
// otherwise try falling back to old, possibly prefixed API...
|
39
|
+
const legacyApi = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
40
|
+
if (legacyApi) {
|
41
|
+
return new Promise(function (resolve, reject) {
|
42
|
+
legacyApi.bind(navigator)(constraints, resolve, reject);
|
43
|
+
});
|
44
|
+
}
|
45
|
+
return Promise.reject({status:false,msg:"user media not available"})
|
46
|
+
}
|
47
|
+
export const getAudioConstraint = x=>{
|
48
|
+
return {audio: true,echoCancellation:{exact: true},noiseSuppression:{exact:true}};
|
49
|
+
}
|
50
|
+
export async function getUserMediaAsync(constraints,video) {
|
51
|
+
try {
|
52
|
+
const stream = await (video === false ? getUserMedia(constraints): navigator.mediaDevices.getDisplayMedia(constraints));
|
53
|
+
return stream;
|
54
|
+
} catch (e) {
|
55
|
+
console.error('navigator.getUserMedia error:', e);
|
56
|
+
}
|
57
|
+
return null;
|
58
|
+
}
|
59
|
+
export function handleUserMediaError(e) {
|
60
|
+
console.error(e," stream recording error");
|
61
|
+
notify.error(e);
|
62
|
+
}
|
63
|
+
|
64
|
+
export function getSupportedMimeTypes(mediaTypes,filter) {
|
65
|
+
if(isNonNullString(mediaTypes)){
|
66
|
+
mediaTypes = mediaTypes.split(",");
|
67
|
+
}
|
68
|
+
mediaTypes = Array.isArray(mediaTypes)? mediaTypes : isNonNullString(mediaTypes)? mediaTypes.split(",") : [];
|
69
|
+
filter = typeof filter =="function"? filter : x=>true;
|
70
|
+
if (!mediaTypes.length) mediaTypes.push(...['video', 'audio'])
|
71
|
+
const FILE_EXTENSIONS = ['webm', 'ogg', 'mp4', 'x-matroska']
|
72
|
+
const CODECS = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h.265', 'h264', 'h.264', 'opus']
|
73
|
+
return [...new Set(
|
74
|
+
FILE_EXTENSIONS.flatMap(ext =>
|
75
|
+
CODECS.flatMap(codec =>
|
76
|
+
mediaTypes.flatMap(mediaType => [
|
77
|
+
`${mediaType}/${ext};codecs:${codec}`,
|
78
|
+
`${mediaType}/${ext};codecs=${codec}`,
|
79
|
+
`${mediaType}/${ext};codecs:${codec.toUpperCase()}`,
|
80
|
+
`${mediaType}/${ext};codecs=${codec.toUpperCase()}`,
|
81
|
+
`${mediaType}/${ext}`,
|
82
|
+
]),
|
83
|
+
),
|
84
|
+
),
|
85
|
+
)].filter(variation => MediaRecorder.isTypeSupported(variation) && filter(variation))
|
86
|
+
}
|
87
|
+
const recordingKeys = ['isRecording','isPaused','isInactive'];
|
88
|
+
function mainDesktopCapturer (){
|
89
|
+
let mimeType = "video/webm;codecs=vp9";
|
90
|
+
let recordingOptions = {};
|
91
|
+
var recorder;
|
92
|
+
let blobs = [];
|
93
|
+
const electronDesktopCapturer = isElectron() && typeof window?.ELECTRON !=="undefined" && typeof window?.ELECTRON?.desktopCapturer =="object" && ELECTRON?.desktopCapturer || {};
|
94
|
+
const getRecordingStatus = ()=>{
|
95
|
+
const ret = {}
|
96
|
+
if(recorder){
|
97
|
+
recordingKeys.map((v)=>{
|
98
|
+
if(recorder){
|
99
|
+
ret[v] = recorder.state == v.toLowerCase().split("is")[1]? true : false
|
100
|
+
} else {
|
101
|
+
ret[v] = false;
|
102
|
+
}
|
103
|
+
});
|
104
|
+
} else if(electronDesktopCapturer?.getRecordingStatus){
|
105
|
+
return electronDesktopCapturer?.getRecordingStatus();
|
106
|
+
}
|
107
|
+
return ret;
|
108
|
+
};
|
109
|
+
function handleStream(stream,opts) {
|
110
|
+
opts = Object.assign({},opts);
|
111
|
+
recorder = new MediaRecorder(stream, { mimeType});
|
112
|
+
blobs = [];
|
113
|
+
recorder.ondataavailable = function(event) {
|
114
|
+
if(event.data.size > 0){
|
115
|
+
blobs.push(event.data);
|
116
|
+
}
|
117
|
+
updateSystemTray();
|
118
|
+
};
|
119
|
+
recorder.onstop = function(event){
|
120
|
+
updateSystemTray();
|
121
|
+
if(!blobs.length) return false;
|
122
|
+
const opts = defaultObj(recordingOptions);
|
123
|
+
let {fileName} = opts;
|
124
|
+
fileName = defaultStr(fileName,"video-"+APP.getName()+"-"+(new Date().toFormat("dd-mm-yyyy HHMM"))).trim();
|
125
|
+
fileName = (fileName.rtrim(getFileExtension(fileName,false)))+".webm";
|
126
|
+
return FileSaver.save({content:new Blob(blobs, {type: mimeType}),mimeType,fileName}).then(({path,fileName})=>{
|
127
|
+
if(isNonNullString(path) || isNonNullString(fileName)){
|
128
|
+
notify.info(`Vidéo sauvegardée ${isNonNullString(path)?` à l'emplacement [${path}]`:` avec comme nom de fichier ${fileName}`}`);
|
129
|
+
}
|
130
|
+
}).catch(notify.error).finally(()=>{
|
131
|
+
recorder = undefined;
|
132
|
+
blobs = [];
|
133
|
+
});
|
134
|
+
}
|
135
|
+
recorder.start(1000);
|
136
|
+
updateSystemTray();
|
137
|
+
}
|
138
|
+
|
139
|
+
function startRecording(opts) {
|
140
|
+
if(!canRecord()){
|
141
|
+
return Promise.reject({stauts:false,isRecording:false,msg:"unable to get user media, get user media is not a function"})
|
142
|
+
}
|
143
|
+
if(recorder){
|
144
|
+
recorder.stop();
|
145
|
+
}
|
146
|
+
recorder = undefined;
|
147
|
+
opts = defaultObj(opts)
|
148
|
+
if(isNonNullString(opts.mimeType)){
|
149
|
+
const mimeTypes = getSupportedMimeTypes(x=>!x.startsWith("audio/"))
|
150
|
+
if(mimeTypes.includes(opts.mimeType)){
|
151
|
+
mimeType = opts.mimeType;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
const timer = typeof opts.timer =="number"? opts.timer : 0;
|
155
|
+
const cb = (e)=>{
|
156
|
+
setTimeout(()=>{
|
157
|
+
const status = desktopCapturer.getRecordingStatus();
|
158
|
+
APP.trigger(events.RECORDING_STATUS,status);
|
159
|
+
},timer*1000+1000);
|
160
|
+
}
|
161
|
+
if(typeof electronDesktopCapturer?.startRecording ==='function'){
|
162
|
+
try {
|
163
|
+
const eRecorder = electronDesktopCapturer.startRecording({...opts,mimeType,updateSystemTray,handleUserMediaError})
|
164
|
+
return Promise.resolve(eRecorder).then(cb).catch(notify.error);
|
165
|
+
} catch(e){
|
166
|
+
notify.error(e);
|
167
|
+
return Promise.reject(e);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
opts.video = defaultObj(opts.video);
|
171
|
+
const audio = isBool(opts.audio) && !opts.audio ? false : defaultObj(opts.audio);
|
172
|
+
const video = {
|
173
|
+
...opts.video,
|
174
|
+
mediaSource: "screen"
|
175
|
+
}
|
176
|
+
recordingOptions = opts;
|
177
|
+
return new Promise((resolve,reject)=>{
|
178
|
+
if(audio){
|
179
|
+
(async() => {
|
180
|
+
const audioStream = await getUserMediaAsync(getAudioConstraint(),false)
|
181
|
+
const videoStream = await getUserMediaAsync({audio:false,video})
|
182
|
+
if(audioStream && videoStream){
|
183
|
+
const combinedStream = new MediaStream([...videoStream.getVideoTracks(), ...audioStream.getAudioTracks()])
|
184
|
+
handleStream(combinedStream,opts)
|
185
|
+
}
|
186
|
+
resolve({isRecording:true});
|
187
|
+
})();
|
188
|
+
} else {
|
189
|
+
return getUserMediaSync({audio:false,video}).then((stream)=>{
|
190
|
+
handleStream(stream,opts);
|
191
|
+
cb();
|
192
|
+
resolve({isRecording:true})
|
193
|
+
}).catch(handleUserMediaError);
|
194
|
+
}
|
195
|
+
return resolve({isRecording:false});
|
196
|
+
})
|
197
|
+
}
|
198
|
+
|
199
|
+
function pauseRecording(){
|
200
|
+
if(electronDesktopCapturer?.pauseRecording){
|
201
|
+
return electronDesktopCapturer?.pauseRecording();
|
202
|
+
}
|
203
|
+
if(!recorder || !getRecordingStatus().isRecording) return;
|
204
|
+
recorder.pause();
|
205
|
+
updateSystemTray();
|
206
|
+
return true;
|
207
|
+
}
|
208
|
+
function resumeRecording(){
|
209
|
+
if(electronDesktopCapturer?.resumeRecording) return electronDesktopCapturer.resumeRecording();
|
210
|
+
if(!recorder || !getRecordingStatus().isPaused) return;
|
211
|
+
recorder.resume();
|
212
|
+
updateSystemTray();
|
213
|
+
return true;
|
214
|
+
}
|
215
|
+
function stopRecording(opts) {
|
216
|
+
setTimeout(()=>{
|
217
|
+
const status = desktopCapturer.getRecordingStatus();
|
218
|
+
APP.trigger(events.RECORDING_STATUS,status);
|
219
|
+
},1000);
|
220
|
+
if(electronDesktopCapturer.stopRecording) return electronDesktopCapturer.stopRecording();
|
221
|
+
if(!recorder) return false;
|
222
|
+
let s = getRecordingStatus();
|
223
|
+
if(!s.isPaused && !s.isRecording){
|
224
|
+
recorder = undefined;
|
225
|
+
return false;
|
226
|
+
}
|
227
|
+
if(recorder){
|
228
|
+
let s = getRecordingStatus();
|
229
|
+
if(s.isRecording || s.isPaused){
|
230
|
+
recorder.stop();
|
231
|
+
}
|
232
|
+
}
|
233
|
+
recorder = undefined;
|
234
|
+
return true;
|
235
|
+
}
|
236
|
+
return {
|
237
|
+
canRecord,
|
238
|
+
isUserMediaAvailable:canRecord,
|
239
|
+
getRecordingStatus,
|
240
|
+
startRecording,
|
241
|
+
pauseRecording,
|
242
|
+
resumeRecording,
|
243
|
+
getAudioConstraint,
|
244
|
+
stopRecording,
|
245
|
+
getSupportedMimeTypes,
|
246
|
+
electron : electronDesktopCapturer,
|
247
|
+
isElectron : typeof electronDesktopCapturer.getRecordingStatus ==="function",
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
export const looopForTimer = (timer)=>{
|
252
|
+
return new Promise((resolve,reject)=>{
|
253
|
+
let timerCB = undefined;
|
254
|
+
timer = typeof timer =='number'? timer : 3000;
|
255
|
+
const loopCB = ()=>{
|
256
|
+
clearTimeout(timerCB);
|
257
|
+
const d = Math.ceil(timer/1000);
|
258
|
+
const testID = "RN_PreloaderLooper";
|
259
|
+
if(timer >= 1000){
|
260
|
+
Preloader.open({
|
261
|
+
content : <HStack testID={testID}>
|
262
|
+
<Label>Début capture dans</Label>
|
263
|
+
<Label textBold fontSize={40}>{" "+(d).formatNumber()+" "}</Label>
|
264
|
+
<Label>seconde{d>1 &&"s"}</Label>
|
265
|
+
</HStack>
|
266
|
+
})
|
267
|
+
timer-=1000;
|
268
|
+
timerCB = setTimeout(loopCB,900);
|
269
|
+
return;
|
270
|
+
}
|
271
|
+
Preloader.close();
|
272
|
+
resolve();
|
273
|
+
}
|
274
|
+
return loopCB();
|
275
|
+
})
|
276
|
+
}
|
277
|
+
const desktopCapturer = mainDesktopCapturer();
|
278
|
+
|
279
|
+
export async function handleCapture(){
|
280
|
+
if(!canRecord()){
|
281
|
+
const message = "Impossible de faire des enregistrements vidéo sur ce type de périférique";
|
282
|
+
notify.error(message);
|
283
|
+
return Promise.reject({message});
|
284
|
+
}
|
285
|
+
const {isRecording,isPaused} = desktopCapturer.getRecordingStatus();
|
286
|
+
let fields = {},title = "Capture d'écran vidéo en cours", yes = null, no = "Annuler";
|
287
|
+
const mimeTypes = getSupportedMimeTypes("video");
|
288
|
+
let onSuccess = undefined;
|
289
|
+
const sKey = !isPaused && !isRecording ? startSessionKey : actionsSessionKey;
|
290
|
+
const data = Object.assign({},session.get(sKey));
|
291
|
+
if(!isPaused && !isRecording){
|
292
|
+
let sources = null;
|
293
|
+
const {electron} = desktopCapturer;
|
294
|
+
const screenAccess = typeof electron.getScreenAccess =="function" && electron.getScreenAccess() || true;
|
295
|
+
if(!screenAccess){
|
296
|
+
const msg = `Le partage d'écran n'est pas activé sur votre système, merci d'activer le partage d'écran dans les paramètres systèmes`;
|
297
|
+
notify.error(msg);
|
298
|
+
return Promise.reject({message:msg});
|
299
|
+
}
|
300
|
+
if(typeof electron.getSources =='function'){
|
301
|
+
sources = await electron.getSources();
|
302
|
+
}
|
303
|
+
title = "Effectuer une capture d'écran vidéo";
|
304
|
+
fields = {
|
305
|
+
source : Array.isArray(sources)? {
|
306
|
+
text : "Sélectionner la source de l'écran à capturer",
|
307
|
+
type : "select",
|
308
|
+
required : true,
|
309
|
+
items : sources,
|
310
|
+
itemValue : ({item,index})=>item.id,
|
311
|
+
//compare : (a,b)=> a?.id === b?.id,
|
312
|
+
listProps : {itemHeight:200},
|
313
|
+
itemHeight : 250,
|
314
|
+
renderItem : ({item,index})=>{
|
315
|
+
if(!isObj(item) || !isDataURL(item.thumbnailURL)) return null;
|
316
|
+
return <View style={[theme.styles.w100,theme.styles.alignItemsFlexStart]} testID = {`RNViewSource_${item.id}`}>
|
317
|
+
<Label textBold primary>{item.name}</Label>
|
318
|
+
<Image editable={false} width = {250} rounded = {false} src={item.thumbnailURL}/>
|
319
|
+
</View>
|
320
|
+
},
|
321
|
+
renderText : ({item,index})=>{
|
322
|
+
return `${item?.name}`;
|
323
|
+
}
|
324
|
+
} : undefined,
|
325
|
+
audio : {
|
326
|
+
text : "Enregistrer le son",
|
327
|
+
type : 'switch',
|
328
|
+
defaultValue : true,
|
329
|
+
checkedValue : true,
|
330
|
+
uncheckedValue : false,
|
331
|
+
},
|
332
|
+
timer : {
|
333
|
+
text : 'Délai d\'attente en secondes',
|
334
|
+
type : 'number',
|
335
|
+
format : 'number',
|
336
|
+
defaultValue : !isElectron() ? 0 : 3,
|
337
|
+
},
|
338
|
+
mimeType : {
|
339
|
+
text : 'Format de la vidéo',
|
340
|
+
type : 'select',
|
341
|
+
items : mimeTypes,
|
342
|
+
defaultValue : mimeTypes[0],
|
343
|
+
itemValue : ({item,index})=>item,
|
344
|
+
renderText : ({item,index}) => item,
|
345
|
+
renderItem : ({item,index}) => item,
|
346
|
+
},
|
347
|
+
showPreloaderOnScreenCapture : electron?.isElectron ? {
|
348
|
+
text : "Afficher la progression",
|
349
|
+
tooltip : "Afficher la progression au niveau de l'icone de l'application",
|
350
|
+
defaultValue : 1,
|
351
|
+
} : undefined
|
352
|
+
}
|
353
|
+
yes = {
|
354
|
+
text : 'Capturer',
|
355
|
+
icon : "record"
|
356
|
+
}
|
357
|
+
onSuccess = ({data})=>{
|
358
|
+
const timer = Math.ceil(typeof data.timer =="number"? data.timer : 0);
|
359
|
+
if(timer > 0){
|
360
|
+
return looopForTimer(timer*1000).then(()=>{
|
361
|
+
desktopCapturer.startRecording(data);
|
362
|
+
});
|
363
|
+
}
|
364
|
+
desktopCapturer.startRecording(data);
|
365
|
+
}
|
366
|
+
} else {
|
367
|
+
const type = isRecording || isPaused ? "radio" : undefined;
|
368
|
+
fields = {
|
369
|
+
action : {
|
370
|
+
text : 'Que voulez vous faire?',
|
371
|
+
type : 'select',
|
372
|
+
items : [
|
373
|
+
isRecording? {
|
374
|
+
code :'pauseRecording',
|
375
|
+
label:'Mettre la capture en pause',
|
376
|
+
type,
|
377
|
+
} : undefined,
|
378
|
+
isPaused ? {
|
379
|
+
code :"resumeRecording",
|
380
|
+
label:'Reprendre la capture vidéo',
|
381
|
+
type,
|
382
|
+
} : undefined,
|
383
|
+
{
|
384
|
+
code:'stopRecording',
|
385
|
+
label:'Arréter la capture vidéo',
|
386
|
+
},
|
387
|
+
],
|
388
|
+
defaultValue : isPaused?'resumeRecording' : 'stopRecording',
|
389
|
+
multiple : false,
|
390
|
+
required : true,
|
391
|
+
}
|
392
|
+
}
|
393
|
+
yes = {
|
394
|
+
text : "Exécuter",
|
395
|
+
icon : "play",
|
396
|
+
}
|
397
|
+
no = {
|
398
|
+
text : "Annuler",
|
399
|
+
icon :"cancel",
|
400
|
+
}
|
401
|
+
onSuccess = ({data})=>{
|
402
|
+
if(typeof desktopCapturer[data.action] ==='function'){
|
403
|
+
return desktopCapturer[data.action]();
|
404
|
+
}
|
405
|
+
}
|
406
|
+
}
|
407
|
+
return DialogProvider.open({
|
408
|
+
title,
|
409
|
+
actions : [yes],
|
410
|
+
onSuccess : ({data,...rest})=>{
|
411
|
+
if(onSuccess) onSuccess({data,...rest});
|
412
|
+
DialogProvider.close();
|
413
|
+
session.set(sKey,data);
|
414
|
+
},
|
415
|
+
data,
|
416
|
+
fields,
|
417
|
+
});
|
418
|
+
}
|
419
|
+
|
420
|
+
export const useRecordingStatus = ()=>{
|
421
|
+
const [status,setStatus] = useState(desktopCapturer.getRecordingStatus());
|
422
|
+
const timerRef = useRef(null);
|
423
|
+
const {isPaused,isRecording,isInactive} = status;
|
424
|
+
const mRecord = canRecord();
|
425
|
+
useEffect(()=>{
|
426
|
+
if(!mRecord) return ()=>{};
|
427
|
+
if(isInactive){
|
428
|
+
clearInterval(timerRef.current);
|
429
|
+
timerRef.current = null;
|
430
|
+
} else {
|
431
|
+
timerRef.current = setInterval(()=>{
|
432
|
+
const nStatus = desktopCapturer.getRecordingStatus();
|
433
|
+
if(nStatus.isPaused !== status.isPaused || nStatus.isInactive !== status.isInactive || nStatus.isRecording !== status.isRecording){
|
434
|
+
setStatus({...nStatus});
|
435
|
+
}
|
436
|
+
},1000);
|
437
|
+
}
|
438
|
+
return ()=>{
|
439
|
+
clearInterval(timerRef.current);
|
440
|
+
timerRef.current = null;
|
441
|
+
}
|
442
|
+
},[isPaused,isRecording,isInactive]);
|
443
|
+
useEffect(()=>{
|
444
|
+
return ()=>{
|
445
|
+
clearInterval(timerRef.current);
|
446
|
+
timerRef.current = null;
|
447
|
+
}
|
448
|
+
},[]);
|
449
|
+
return status;
|
450
|
+
}
|
451
|
+
|
452
|
+
/**** le bouton permettant d'exécuter la fonction de recording de l'application */
|
453
|
+
export const RecordingButton = function({onPress,testID,...props}){
|
454
|
+
const {desktopCapturer:dParams} = useContext();
|
455
|
+
const {isRecording,isPaused,isInactive} = useRecordingStatus();
|
456
|
+
const canCaptureDesktop = !canRecord()? false : typeof dParams =="function"? !!dParams() : typeof dParams =="boolean"? dParams : true;
|
457
|
+
if(!canCaptureDesktop) return null;
|
458
|
+
testID = defaultStr(props.testID,"RNScreenRecordStatusButton")
|
459
|
+
const color = theme.Colors.setAlpha(theme.colors.text,theme.ALPHA);
|
460
|
+
const buttonContainerProps = {style:[theme.styles.w100,theme.styles.alignItemsFlexStart]}
|
461
|
+
return <View testID={testID+"_Container"} {...buttonContainerProps}>
|
462
|
+
<Button containerProps = {buttonContainerProps} {...props} style={[{color},buttonContainerProps.style,props.style]} upperCase={false} onPress = {(...args)=>{
|
463
|
+
if(onPress && onPress(...args) === false) return;
|
464
|
+
handleCapture();
|
465
|
+
}}
|
466
|
+
icon={isPaused ? "material-pause-presentation":isRecording ? "record-rec":"record"} iconProps={{color}} testID={testID}
|
467
|
+
>
|
468
|
+
{isPaused ? "Capture vidéo en pause" : isRecording ? "Capture vidéo en cours ..." :"Faire une capture vidéo" }
|
469
|
+
</Button>
|
470
|
+
{isPaused || isRecording ? <ProgressBar
|
471
|
+
indeterminate = {isRecording || undefined}
|
472
|
+
color = {theme.colors.primary}
|
473
|
+
progress = {isPaused ? 50 : undefined}
|
474
|
+
/> : null}
|
475
|
+
</View>
|
476
|
+
}
|
477
|
+
|
478
|
+
export default desktopCapturer;
|
@@ -7,6 +7,7 @@ import {defaultObj,sortBy,defaultStr,isObj} from "$cutils";
|
|
7
7
|
import appConfig from "$capp/config";
|
8
8
|
import useContext from "$econtext/hooks";
|
9
9
|
import {useMemo,useEffect,useRef} from "react";
|
10
|
+
import {RecordingButton} from "$expo-ui/desktopCapturer";
|
10
11
|
///les items du drawer
|
11
12
|
import { screenName as aboutScreenName} from "$escreens/Help/About";
|
12
13
|
import theme from "$theme";
|
@@ -94,7 +95,20 @@ const useGetItems = (options)=>{
|
|
94
95
|
if(isObj(item) && isNonNullString(item.drawerSection) && (item.drawerSection.trim()) in items){
|
95
96
|
items[item.drawerSection.trim()].items.push(item);
|
96
97
|
}
|
97
|
-
})
|
98
|
+
});
|
99
|
+
const captureSide = {
|
100
|
+
label : <RecordingButton/>,
|
101
|
+
style : [theme.styles.noPadding],
|
102
|
+
labelProps : {style : [{flexShrink : 1},theme.styles.alignItemsFlexStart,theme.styles.w100]}
|
103
|
+
};
|
104
|
+
let hasCapture = false;
|
105
|
+
if(!hasCapture){
|
106
|
+
if(isObj(items.admin) && Array.isArray(items.admin.items)){
|
107
|
+
items.admin = Object.clone(items.admin);
|
108
|
+
items.admin.items.push(captureSide);
|
109
|
+
hasCapture = true;
|
110
|
+
}
|
111
|
+
}
|
98
112
|
if(handleHelp){
|
99
113
|
const dHelp = isObj(items.help)? Object.clone(items.help) : {};
|
100
114
|
items.help = {
|
@@ -105,6 +119,10 @@ const useGetItems = (options)=>{
|
|
105
119
|
...dHelp,
|
106
120
|
items : Array.isArray(dHelp.items)? dHelp.items : [],
|
107
121
|
};
|
122
|
+
if(!hasCapture){
|
123
|
+
items.help.items.push(captureSide);
|
124
|
+
hasCapture = true;
|
125
|
+
}
|
108
126
|
items.help.items.push({
|
109
127
|
icon : 'help',
|
110
128
|
label : 'A propos de '+APP.getName(),
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module.exports = {
|
2
2
|
"@fto-consult/expo-ui": {
|
3
3
|
"name": "@fto-consult/expo-ui",
|
4
|
-
"version": "7.
|
4
|
+
"version": "7.11.0",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
7
7
|
"url": "git+https://github.com/borispipo/expo-ui.git"
|
@@ -54,7 +54,7 @@ module.exports = {
|
|
54
54
|
"license": "ISC"
|
55
55
|
},
|
56
56
|
"@fto-consult/electron-gen": {
|
57
|
-
"version": "
|
57
|
+
"version": "2.2.0",
|
58
58
|
"license": "ISC"
|
59
59
|
},
|
60
60
|
"@pchmn/expo-material3-theme": {
|
@@ -253,6 +253,11 @@ module.exports = {
|
|
253
253
|
"version": "1.9.0",
|
254
254
|
"license": "MIT"
|
255
255
|
},
|
256
|
+
"react-native-indicators": {
|
257
|
+
"version": "0.17.0",
|
258
|
+
"url": "git://github.com/n4kz/react-native-indicators.git",
|
259
|
+
"license": "BSD-3-Clause"
|
260
|
+
},
|
256
261
|
"react-native-iphone-x-helper": {
|
257
262
|
"version": "1.3.1",
|
258
263
|
"url": "https://github.com/ptelad/react-native-iphone-x-helper#readme",
|
@@ -263,7 +268,7 @@ module.exports = {
|
|
263
268
|
"license": "MIT"
|
264
269
|
},
|
265
270
|
"react-native-paper": {
|
266
|
-
"version": "
|
271
|
+
"version": "4.12.6",
|
267
272
|
"url": "https://callstack.github.io/react-native-paper",
|
268
273
|
"license": "MIT"
|
269
274
|
},
|