added qr scaning and finishing touches

This commit is contained in:
David Katrinka 2026-01-09 21:10:18 +01:00
parent bc347c3f05
commit 2cf7f5dc7b
8 changed files with 153 additions and 9 deletions

View File

@ -9,7 +9,11 @@
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
"supportsTablet": true,
"infoPlist": {
"NSCameraUsageDescription": "$(PRODUCT_NAME) needs access to your Camera."
},
"bundleIdentifier": "com.anonymous.ai-questions-app"
},
"android": {
"adaptiveIcon": {
@ -19,7 +23,9 @@
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
"predictiveBackGestureEnabled": false,
"permissions": ["android.permission.CAMERA"],
"package": "com.anonymous.ai_questions_app"
},
"web": {
"output": "static",
@ -39,7 +45,14 @@
}
}
],
"expo-secure-store"
"expo-secure-store",
[
"react-native-vision-camera",
{
"cameraPermissionText": "$(PRODUCT_NAME) needs access to your Camera.",
"enableCodeScanner": true
}
]
],
"experiments": {
"typedRoutes": true,

View File

@ -49,7 +49,7 @@ export default function TestsScreen() {
selectedValue={category ? `${category}` : undefined}
onValueChange={handleChangeCategory}
placeholder="Select category" />
<Button>
<Button onPress={() => router.push('/qr')}>
<ButtonText><FontAwesome5 name="qrcode" size={18} /></ButtonText>
</Button>
</View>

104
app/qr.tsx Normal file
View File

@ -0,0 +1,104 @@
import { useStartTestMutation } from "@/api/userTests";
import { Button, ButtonText } from "@/components/ui/button";
import { ThemedText } from "@/components/ui/themed-text";
import getErrorAxiosMessage from "@/utils/get-error-axios-message";
import { router, Stack, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import { useToast } from "react-native-toast-notifications";
import {
Camera,
useCameraDevice,
useCameraPermission,
useCodeScanner,
} from "react-native-vision-camera";
const QrScreen = () => {
const device = useCameraDevice("back");
const { hasPermission, requestPermission } = useCameraPermission();
const toast = useToast();
const [scanned, setScanned] = useState(false);
const { mutate, isPending } = useStartTestMutation();
useFocusEffect(
useCallback(() => {
setScanned(false);
}, [])
);
const handleStartTest = (id: number) => {
mutate(
{
test_id: id,
},
{
onSuccess: (data) => {
router.push(`/user-tests/doing/${data.id}`);
},
onError: (error) => {
toast.show(getErrorAxiosMessage(error), { type: "danger" });
},
}
);
};
const codeScanner = useCodeScanner({
codeTypes: ["qr", "ean-13"],
onCodeScanned: (codes) => {
if (scanned) return;
if (!codes || codes.length === 0) return;
const scannedValue = codes[0].value;
if (scannedValue) {
const id = parseInt(scannedValue, 10);
setScanned(true);
toast.show("Going to test", { type: "normal" });
router.push(`/tests/${id}`);
// handleStartTest(id);
} else {
toast.show("Invalid QR code", { type: "warning" });
}
},
});
if (!hasPermission) {
return (
<View style={styles.center}>
<Stack.Screen options={{ title: "Scan QR Code" }} />
<ThemedText>No camera permission</ThemedText>
<Button onPress={requestPermission} className="mt-2">
<ButtonText>Grant Permission</ButtonText>
</Button>
</View>
);
}
if (!device) {
return (
<View style={styles.center}>
<ThemedText>No camera device found</ThemedText>
</View>
);
}
return (
<>
<Stack.Screen options={{ title: "Scan QR Code" }} />
<Camera
codeScanner={codeScanner}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
/>
</>
);
};
const styles = StyleSheet.create({
center: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
});
export default QrScreen;

View File

@ -252,7 +252,7 @@ const DoingUserTestScreen = () => {
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "flex-start",
width: "100%"
},
top: {
width: "100%",

View File

@ -106,7 +106,7 @@ const Answer = ({
{isTextType ? (
<FontAwesome5 name="quote-left" />
) : (
<ThemedText darkColor="#fff" lightColor="#fff">
<ThemedText darkColor="#000" lightColor="#fff">
{answer.id}
</ThemedText>
)}

View File

@ -33,12 +33,13 @@ const CustomSelect = ({
return options;
}, [options, noneOption]);
const backgroundColor = useThemeColor({ }, "background");
const metaColor = useThemeColor({}, "meta");
return (
<Select className={className} onValueChange={onValueChange} selectedValue={selectedValue} defaultValue={defaultValue}>
<SelectTrigger className="justify-between" variant="outline" size="md" style={{ backgroundColor }}>
<SelectInput placeholder={placeholder} />
<FontAwesome5 className="mr-3" name="chevron-down" />
<FontAwesome5 style={{color: metaColor}} className="mr-3" name="chevron-down" />
</SelectTrigger>
<SelectPortal>
<SelectBackdrop />

25
package-lock.json generated
View File

@ -47,6 +47,7 @@
"react-native-svg": "^15.15.1",
"react-native-toast-notifications": "^3.4.0",
"react-native-vector-icons": "^10.3.0",
"react-native-vision-camera": "^4.7.3",
"react-native-web": "~0.21.0",
"react-native-worklets": "^0.5.2",
"react-stately": "^3.43.0",
@ -14687,6 +14688,30 @@
"node": ">=10"
}
},
"node_modules/react-native-vision-camera": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/react-native-vision-camera/-/react-native-vision-camera-4.7.3.tgz",
"integrity": "sha512-g1/neOyjSqn1kaAa2FxI/qp5KzNvPcF0bnQw6NntfbxH6tm0+8WFZszlgb5OV+iYlB6lFUztCbDtyz5IpL47OA==",
"license": "MIT",
"peerDependencies": {
"@shopify/react-native-skia": "*",
"react": "*",
"react-native": "*",
"react-native-reanimated": "*",
"react-native-worklets-core": "*"
},
"peerDependenciesMeta": {
"@shopify/react-native-skia": {
"optional": true
},
"react-native-reanimated": {
"optional": true
},
"react-native-worklets-core": {
"optional": true
}
}
},
"node_modules/react-native-web": {
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",

View File

@ -5,8 +5,8 @@
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint"
},
@ -50,6 +50,7 @@
"react-native-svg": "^15.15.1",
"react-native-toast-notifications": "^3.4.0",
"react-native-vector-icons": "^10.3.0",
"react-native-vision-camera": "^4.7.3",
"react-native-web": "~0.21.0",
"react-native-worklets": "^0.5.2",
"react-stately": "^3.43.0",