Version 2 : permissions caméra

This commit is contained in:
Valentin Duflot 2026-05-07 12:55:15 +02:00
parent a9260a1fee
commit bdfca474cc
4 changed files with 301 additions and 34 deletions

View file

@ -1,43 +1,219 @@
import { useState } from 'react';
import { Text, TextInput, View } from 'react-native';
//npx expo install expo-location expo-camera
export default function Home() {
const users = [
"alice",
"bob",
"charlie"
]
import {
CameraView,
useCameraPermissions,
} from "expo-camera";
const variable = true;
import React, { useRef, useState } from "react";
import {
ActivityIndicator,
Alert,
Image,
Linking,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
const [nom, setNom] = useState('');
export default function App() {
const cameraRef = useRef(null);
const [statusText, setStatusText] = useState("Permission non demandée");
const [loading, setLoading] = useState(false);
return (
<View style={{ flex: 1, padding: 16, gap: 12 }}>
{variable && <Text>Test</Text>}
{variable ? <Text>Vrai</Text> : <Text>Faux</Text>}
{users.map(user => {
return <Text key={user}>{user}</Text>
})}
const [cameraVisible, setCameraVisible] = useState(false);
const [photoUri, setPhotoUri] = useState(null);
<View style={{ height: 1, borderWidth: 1 }} />
const [permission, requestPermission] =
useCameraPermissions();
<Text style={{ fontSize: 18, fontWeight: '600' }}>
Saisie + bouton "Valider"
</Text>
const openSettings = () => {
Linking.openSettings();
};
<Text>Valeur validée : {nom || '(vide)'}</Text>
const openCamera = async () => {
try {
setLoading(true);
setStatusText("Demande de permission caméra...");
<TextInput
value={nom}
onChangeText={setNom}
placeholder="Taper un nom "
style={{ borderWidth: 1, padding: 12, borderRadius: 10 }}
autoCapitalize="none"
/>
const result = await requestPermission();
</View>
if (!result.granted) {
if (result.canAskAgain === false) {
Alert.alert(
"Permission bloquée",
"La caméra doit être activée dans les paramètres.",
[
{ text: "Annuler", style: "cancel" },
{ text: "Paramètres", onPress: openSettings },
]
);
} else {
Alert.alert(
"Permission refusée",
"Impossible d'utiliser la caméra."
);
}
);
setStatusText("Permission caméra refusée");
return;
}
setPhotoUri(null);
setCameraVisible(true);
setStatusText("Caméra autorisée");
} catch (e) {
setStatusText("Erreur lors de la demande caméra");
} finally {
setLoading(false);
}
};
const takePhoto = async () => {
try {
if (!cameraRef.current) {
Alert.alert("Erreur", "La caméra n'est pas encore prête.");
return;
}
setLoading(true);
setStatusText("Prise de photo...");
const photo = await cameraRef.current.takePictureAsync();
if (!photo || !photo.uri) {
Alert.alert("Erreur", "Aucune photo obtenue.");
return;
}
setPhotoUri(photo.uri);
setCameraVisible(false);
setStatusText("Photo prise");
} catch (e) {
setStatusText("Erreur lors de la prise de photo");
} finally {
setLoading(false);
}
};
const closeCamera = () => {
setCameraVisible(false);
setStatusText("Caméra fermée");
};
if (cameraVisible) {
return (
<View style={styles.cameraContainer}>
<CameraView
ref={cameraRef}
style={styles.camera}
facing="back"
/>
<View style={styles.cameraControls}>
<TouchableOpacity style={styles.secondaryButton} onPress={closeCamera}>
<Text style={styles.buttonText}>Fermer</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={takePhoto}>
<Text style={styles.buttonText}>Prendre une photo</Text>
</TouchableOpacity>
</View>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>TP permissions Expo</Text>
<TouchableOpacity style={styles.button} onPress={openCamera}>
<Text style={styles.buttonText}>Ouvrir la caméra</Text>
</TouchableOpacity>
{loading ? <ActivityIndicator size="large" /> : null}
<Text style={styles.status}>{statusText}</Text>
{photoUri ? (
<View style={styles.photoBox}>
<Text style={styles.subtitle}>Photo prise :</Text>
<Image source={{ uri: photoUri }} style={styles.photo} />
</View>
) : null}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 20,
backgroundColor: "#fff",
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 20,
textAlign: "center",
},
subtitle: {
fontWeight: "bold",
marginBottom: 10,
},
button: {
backgroundColor: "#2563eb",
padding: 14,
borderRadius: 8,
marginBottom: 12,
},
secondaryButton: {
backgroundColor: "#666",
padding: 14,
borderRadius: 8,
marginBottom: 12,
},
buttonText: {
color: "#fff",
textAlign: "center",
fontWeight: "bold",
},
status: {
textAlign: "center",
marginVertical: 20,
},
box: {
padding: 16,
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 8,
marginBottom: 20,
},
photoBox: {
marginTop: 10,
alignItems: "center",
},
photo: {
width: 250,
height: 250,
borderRadius: 8,
borderWidth: 1,
borderColor: "#ccc",
},
cameraContainer: {
flex: 1,
backgroundColor: "#000",
},
camera: {
flex: 1,
},
cameraControls: {
position: "absolute",
bottom: 40,
left: 20,
right: 20,
},
});

87
package-lock.json generated
View file

@ -9,15 +9,19 @@
"version": "1.0.0",
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"expo": "~54.0.33",
"expo-camera": "~17.0.10",
"expo-constants": "~18.0.13",
"expo-font": "~14.0.11",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-image-picker": "~17.0.10",
"expo-linking": "~8.0.11",
"expo-location": "~19.0.8",
"expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
@ -2756,6 +2760,18 @@
}
}
},
"node_modules/@react-native-async-storage/async-storage": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
"integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
"license": "MIT",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.81.5",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz",
@ -6062,6 +6078,26 @@
"react-native": "*"
}
},
"node_modules/expo-camera": {
"version": "17.0.10",
"resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-17.0.10.tgz",
"integrity": "sha512-w1RBw83mAGVk4BPPwNrCZyFop0VLiVSRE3c2V9onWbdFwonpRhzmB4drygG8YOUTl1H3wQvALJHyMPTbgsK1Jg==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*",
"react-native-web": "*"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/expo-constants": {
"version": "18.0.13",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz",
@ -6126,6 +6162,27 @@
}
}
},
"node_modules/expo-image-loader": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz",
"integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-image-picker": {
"version": "17.0.10",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-17.0.10.tgz",
"integrity": "sha512-a2xrowp2trmvXyUWgX3O6Q2rZaa2C59AqivKI7+bm+wLvMfTEbZgldLX4rEJJhM8xtmEDTNU+lzjtObwzBRGaw==",
"license": "MIT",
"dependencies": {
"expo-image-loader": "~6.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-keep-awake": {
"version": "15.0.8",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz",
@ -6150,6 +6207,15 @@
"react-native": "*"
}
},
"node_modules/expo-location": {
"version": "19.0.8",
"resolved": "https://registry.npmjs.org/expo-location/-/expo-location-19.0.8.tgz",
"integrity": "sha512-H/FI75VuJ1coodJbbMu82pf+Zjess8X8Xkiv9Bv58ZgPKS/2ztjC1YO1/XMcGz7+s9DrbLuMIw22dFuP4HqneA==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "3.0.24",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz",
@ -7829,6 +7895,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@ -8807,6 +8882,18 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-options": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"license": "MIT",
"dependencies": {
"is-plain-obj": "^2.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",

View file

@ -12,15 +12,19 @@
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"expo": "~54.0.33",
"expo-camera": "~17.0.10",
"expo-constants": "~18.0.13",
"expo-font": "~14.0.11",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-image-picker": "~17.0.10",
"expo-linking": "~8.0.11",
"expo-location": "~19.0.8",
"expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
@ -31,11 +35,11 @@
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-worklets": "0.5.1",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0"
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "~19.1.0",

View file

@ -1,7 +1,7 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"strict": false,
"paths": {
"@/*": [
"./*"