Files
Genarrative/apps/mobile-shell/src/shell/QrScannerOverlay.tsx
T
kdletters 387a1c26e3 完成全量检查整改
清理已跟踪原始日志与个人本地配置
修复前后端有效门禁、测试和构建问题
补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束
收紧图片编辑器状态、附件与媒体引用测试
排除已下线旧创作入口测试并清理 warning
2026-07-17 16:56:46 +08:00

147 lines
3.2 KiB
TypeScript

import {
type BarcodeScanningResult,
Camera,
CameraView,
} from 'expo-camera';
import { useCallback, useEffect, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import {
cancelQrCodeScan,
completeQrCodeScan,
failQrCodeScan,
subscribeQrScannerState,
} from '../host-bridge/scanner';
function logQrScannerPermissionFailure(_error: unknown) {
console.warn('mobile QR scanner permission request failed');
}
export function QrScannerOverlay() {
const [isActive, setIsActive] = useState(false);
const [requestKey, setRequestKey] = useState(0);
const [hasPermission, setHasPermission] = useState(false);
const [scanCompleted, setScanCompleted] = useState(false);
useEffect(() => subscribeQrScannerState((state) => {
setIsActive(state.active);
setRequestKey(state.requestKey);
}), []);
useEffect(() => {
if (!isActive) {
setHasPermission(false);
setScanCompleted(false);
return;
}
let disposed = false;
setHasPermission(false);
setScanCompleted(false);
void Camera.requestCameraPermissionsAsync()
.then((permission) => {
if (disposed) {
return;
}
if (!permission.granted) {
failQrCodeScan();
return;
}
setHasPermission(true);
})
.catch((error: unknown) => {
if (!disposed) {
logQrScannerPermissionFailure(error);
failQrCodeScan();
}
});
return () => {
disposed = true;
};
}, [isActive, requestKey]);
const handleBarcodeScanned = useCallback(
(result: BarcodeScanningResult) => {
if (scanCompleted || result.type !== 'qr') {
return;
}
if (completeQrCodeScan(result.data)) {
setScanCompleted(true);
}
},
[scanCompleted],
);
if (!isActive) {
return null;
}
return (
<View style={styles.root}>
{hasPermission ? (
<CameraView
style={styles.camera}
facing="back"
onBarcodeScanned={scanCompleted ? undefined : handleBarcodeScanned}
barcodeScannerSettings={{
barcodeTypes: ['qr'],
}}
/>
) : null}
<View pointerEvents="none" style={styles.frame} />
<Pressable
accessibilityRole="button"
accessibilityLabel="关闭扫码"
onPress={cancelQrCodeScan}
style={styles.closeButton}
>
<Text style={styles.closeButtonText}>关闭</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
root: {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
backgroundColor: '#0c0a09',
},
camera: {
flex: 1,
},
frame: {
position: 'absolute',
top: '22%',
right: 42,
bottom: '22%',
left: 42,
borderWidth: 2,
borderColor: '#fffdf9',
borderRadius: 8,
},
closeButton: {
position: 'absolute',
top: 20,
right: 20,
minWidth: 76,
minHeight: 40,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
backgroundColor: '#fffdf9',
paddingHorizontal: 16,
},
closeButtonText: {
color: '#211a16',
fontSize: 15,
fontWeight: '700',
lineHeight: 20,
},
});