Commit 71e7f49b by tungnq

IMPORTANT : Đã bổ sung camera vào màn profile

parent f5753f42
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:name=".MainApplication"
......
......@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your Camera.</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
......
......@@ -68,6 +68,7 @@
"react-native-tab-view": "^3.0.1",
"react-native-touch-id": "^4.4.1",
"react-native-vector-icons": "^8.1.0",
"react-native-vision-camera": "^4.7.2",
"react-native-webview": "^13.12.2",
"react-redux": "^7.2.4",
"redux": "^4.1.0",
......
import React from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native';
import { Camera } from 'react-native-vision-camera';
const CameraScreen = ({
avatarUri,
openCamera, // () => void
showCamera, // boolean
cameraRef, // React.RefObject<Camera>
device, // CameraDevice | undefined
onToggleCameraPosition, // () => void (thay cho setUseFront)
onTakePhoto, // () => void
onCloseCamera, // () => void (thay cho setShowCamera(false))
}) => {
return (
<View style={{ flex: 1 }}>
{/* Avatar */}
<View style={{ alignItems: 'center', marginTop: 16 }}>
{avatarUri ? (
<Image source={{ uri: avatarUri }} style={{ width: 120, height: 120, borderRadius: 60 }} />
) : (
<View style={{ width: 120, height: 120, borderRadius: 60, backgroundColor: '#eee' }} />
)}
<TouchableOpacity onPress={openCamera} style={styles.pickBtn}>
<Text style={styles.pickBtnText}>Chn nh</Text>
</TouchableOpacity>
</View>
{/* Overlay Camera */}
{showCamera && (
<View style={styles.overlay}>
{device ? (
<>
<Camera
ref={cameraRef}
style={StyleSheet.absoluteFill}
device={device}
isActive={showCamera}
photo // cần để dùng takePhoto()
/>
<View style={styles.controls}>
<TouchableOpacity style={styles.smallBtn} onPress={onToggleCameraPosition}>
<Text style={styles.btnText}>Đổi cam</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.shutter} onPress={onTakePhoto} />
<TouchableOpacity style={styles.smallBtn} onPress={onCloseCamera}>
<Text style={styles.btnText}>Đóng</Text>
</TouchableOpacity>
</View>
</>
) : (
<View style={[styles.overlay, styles.center]}>
<Text style={{ color: '#fff' }}>Không tìm thy camera</Text>
<TouchableOpacity style={[styles.smallBtn, { marginTop: 16 }]} onPress={onCloseCamera}>
<Text style={styles.btnText}>Đóng</Text>
</TouchableOpacity>
</View>
)}
</View>
)}
</View>
);
};
export default CameraScreen;
const styles = StyleSheet.create({
overlay: {
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'black',
},
controls: {
position: 'absolute', bottom: 30, left: 0, right: 0,
flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center',
},
shutter: {
width: 68, height: 68, borderRadius: 34,
borderWidth: 4, borderColor: '#fff',
backgroundColor: 'rgba(255,255,255,0.2)',
},
smallBtn: {
paddingHorizontal: 12, paddingVertical: 8,
borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.2)',
},
btnText: { color: '#fff', fontWeight: '600' },
pickBtn: {
marginTop: 12, padding: 8, backgroundColor: '#2e6ef7', borderRadius: 8,
},
pickBtnText: { color: '#fff', fontWeight: '600' },
center: { justifyContent: 'center', alignItems: 'center' },
});
import React, {useState} from 'react';
import {Text, View, StyleSheet} from 'react-native';
import React, { useRef, useState } from 'react';
import { Platform } from 'react-native';
import ProfileView from './view';
import { useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
const Profile = props => {
const [user, setUser] = useState({
const Profile = () => {
// --- mock profile ---
const [user] = useState({
name: 'Phạm Minh Quân',
role: 'Trợ lý bộ môn thuộc trường',
code: '80578',
......@@ -24,7 +26,7 @@ const Profile = props => {
workCommencementDate: '20/03/2025',
});
// States cho các TextField trong form
// --- form states ---
const [phoneNumber, setPhoneNumber] = useState('');
const [oldTeacherCode, setOldTeacherCode] = useState('');
const [workPlace, setWorkPlace] = useState('');
......@@ -58,16 +60,45 @@ const Profile = props => {
const [salaryIncreaseMilestone, setSalaryIncreaseMilestone] = useState('');
const [lecturerTitle, setLecturerTitle] = useState('');
// --- radio group ---
const [selectedValue2, setSelectedValue2] = useState('1');
const options2 = [
{label: 'Cơ chế', value: '1'},
{label: 'Biên hữu', value: '2'},
{ label: 'Cơ chế', value: '1' },
{ label: 'Biên hữu', value: '2' },
];
const onValueChange2 = value => {
setSelectedValue2(value);
const onValueChange2 = (v) => setSelectedValue2(v);
// --- camera logic (container) ---
const [showCamera, setShowCamera] = useState(false);
const [avatarUri, setAvatarUri] = useState(null);
const [useFront, setUseFront] = useState(false);
const cameraRef = useRef(null);
const device = useCameraDevice(useFront ? 'front' : 'back');
const { hasPermission, requestPermission } = useCameraPermission();
const openCamera = async () => {
if (!hasPermission) {
const ok = await requestPermission();
if (!ok) return;
}
setShowCamera(true);
};
const onTakePhoto = async () => {
if (!cameraRef.current) return;
const photo = await cameraRef.current.takePhoto({
qualityPrioritization: 'balanced',
flash: 'off',
});
const uri = Platform.OS === 'android' ? 'file://' + photo.path : photo.path;
setAvatarUri(uri);
setShowCamera(false);
};
const onToggleCameraPosition = () => setUseFront((v) => !v);
const onCloseCamera = () => setShowCamera(false);
// --- save handler ---
const handleSave = () => {
const formData = {
phoneNumber,
......@@ -103,84 +134,68 @@ const Profile = props => {
currentSalaryCoefficient,
salaryIncreaseMilestone,
lecturerTitle,
avatarUri,
};
console.log('Profile Form Data:', formData);
// TODO: Implement save logic
// TODO: call API save
};
// ❗ Chỉ MỘT return: truyền mọi thứ xuống View
return (
<ProfileView
// profile
dataProfile={user}
// camera props (UI sẽ dùng để hiển thị)
avatarUri={avatarUri}
onPressSelectImage={openCamera}
showCamera={showCamera}
cameraRef={cameraRef}
device={device}
onToggleCameraPosition={onToggleCameraPosition}
onTakePhoto={onTakePhoto}
onCloseCamera={onCloseCamera}
// radio
selectedValue2={selectedValue2}
options2={options2}
onValueChange2={onValueChange2}
// TextField states
phoneNumber={phoneNumber}
setPhoneNumber={setPhoneNumber}
oldTeacherCode={oldTeacherCode}
setOldTeacherCode={setOldTeacherCode}
workPlace={workPlace}
setWorkPlace={setWorkPlace}
position={position}
setPosition={setPosition}
extraDuty={extraDuty}
setExtraDuty={setExtraDuty}
laborType={laborType}
setLaborType={setLaborType}
employmentType={employmentType}
setEmploymentType={setEmploymentType}
appointmentDecision={appointmentDecision}
setAppointmentDecision={setAppointmentDecision}
appointmentDate={appointmentDate}
setAppointmentDate={setAppointmentDate}
issuingAgency={issuingAgency}
setIssuingAgency={setIssuingAgency}
jobBeforeRecruitment={jobBeforeRecruitment}
setJobBeforeRecruitment={setJobBeforeRecruitment}
allowanceSeniorityDate={allowanceSeniorityDate}
setAllowanceSeniorityDate={setAllowanceSeniorityDate}
workAllowancePercent={workAllowancePercent}
setWorkAllowancePercent={setWorkAllowancePercent}
teacherAllowancePercent={teacherAllowancePercent}
setTeacherAllowancePercent={setTeacherAllowancePercent}
teacherType={teacherType}
setTeacherType={setTeacherType}
academicUnit={academicUnit}
setAcademicUnit={setAcademicUnit}
subjectDepartment={subjectDepartment}
setSubjectDepartment={setSubjectDepartment}
employmentStatus={employmentStatus}
setEmploymentStatus={setEmploymentStatus}
workStartDate={workStartDate}
setWorkStartDate={setWorkStartDate}
currentContractType={currentContractType}
setCurrentContractType={setCurrentContractType}
contractStartDate={contractStartDate}
setContractStartDate={setContractStartDate}
contractEndDate={contractEndDate}
setContractEndDate={setContractEndDate}
currentContractNumber={currentContractNumber}
setCurrentContractNumber={setCurrentContractNumber}
contractEffectiveDate={contractEffectiveDate}
setContractEffectiveDate={setContractEffectiveDate}
fullTimeWorkStartDate={fullTimeWorkStartDate}
setFullTimeWorkStartDate={setFullTimeWorkStartDate}
lastClassificationDate={lastClassificationDate}
setLastClassificationDate={setLastClassificationDate}
currentWorkAllowancePercent={currentWorkAllowancePercent}
setCurrentWorkAllowancePercent={setCurrentWorkAllowancePercent}
salaryGrade={salaryGrade}
setSalaryGrade={setSalaryGrade}
currentSalaryLevel={currentSalaryLevel}
setCurrentSalaryLevel={setCurrentSalaryLevel}
currentSalaryCoefficient={currentSalaryCoefficient}
setCurrentSalaryCoefficient={setCurrentSalaryCoefficient}
salaryIncreaseMilestone={salaryIncreaseMilestone}
setSalaryIncreaseMilestone={setSalaryIncreaseMilestone}
lecturerTitle={lecturerTitle}
setLecturerTitle={setLecturerTitle}
// Handlers
// form states
phoneNumber={phoneNumber} setPhoneNumber={setPhoneNumber}
oldTeacherCode={oldTeacherCode} setOldTeacherCode={setOldTeacherCode}
workPlace={workPlace} setWorkPlace={setWorkPlace}
position={position} setPosition={setPosition}
extraDuty={extraDuty} setExtraDuty={setExtraDuty}
laborType={laborType} setLaborType={setLaborType}
employmentType={employmentType} setEmploymentType={setEmploymentType}
appointmentDecision={appointmentDecision} setAppointmentDecision={setAppointmentDecision}
appointmentDate={appointmentDate} setAppointmentDate={setAppointmentDate}
issuingAgency={issuingAgency} setIssuingAgency={setIssuingAgency}
jobBeforeRecruitment={jobBeforeRecruitment} setJobBeforeRecruitment={setJobBeforeRecruitment}
allowanceSeniorityDate={allowanceSeniorityDate} setAllowanceSeniorityDate={setAllowanceSeniorityDate}
workAllowancePercent={workAllowancePercent} setWorkAllowancePercent={setWorkAllowancePercent}
teacherAllowancePercent={teacherAllowancePercent} setTeacherAllowancePercent={setTeacherAllowancePercent}
teacherType={teacherType} setTeacherType={setTeacherType}
academicUnit={academicUnit} setAcademicUnit={setAcademicUnit}
subjectDepartment={subjectDepartment} setSubjectDepartment={setSubjectDepartment}
employmentStatus={employmentStatus} setEmploymentStatus={setEmploymentStatus}
workStartDate={workStartDate} setWorkStartDate={setWorkStartDate}
currentContractType={currentContractType} setCurrentContractType={setCurrentContractType}
contractStartDate={contractStartDate} setContractStartDate={setContractStartDate}
contractEndDate={contractEndDate} setContractEndDate={setContractEndDate}
currentContractNumber={currentContractNumber} setCurrentContractNumber={setCurrentContractNumber}
contractEffectiveDate={contractEffectiveDate} setContractEffectiveDate={setContractEffectiveDate}
fullTimeWorkStartDate={fullTimeWorkStartDate} setFullTimeWorkStartDate={setFullTimeWorkStartDate}
lastClassificationDate={lastClassificationDate} setLastClassificationDate={setLastClassificationDate}
currentWorkAllowancePercent={currentWorkAllowancePercent} setCurrentWorkAllowancePercent={setCurrentWorkAllowancePercent}
salaryGrade={salaryGrade} setSalaryGrade={setSalaryGrade}
currentSalaryLevel={currentSalaryLevel} setCurrentSalaryLevel={setCurrentSalaryLevel}
currentSalaryCoefficient={currentSalaryCoefficient} setCurrentSalaryCoefficient={setCurrentSalaryCoefficient}
salaryIncreaseMilestone={salaryIncreaseMilestone} setSalaryIncreaseMilestone={setSalaryIncreaseMilestone}
lecturerTitle={lecturerTitle} setLecturerTitle={setLecturerTitle}
// actions
onSave={handleSave}
/>
);
......
......@@ -59,5 +59,13 @@ const styles = StyleSheet.create({
sizedBox: {
width: 15,
},
overlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'black' },
controls: { position: 'absolute', bottom: 30, left: 0, right: 0, flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' },
shutter: { width: 68, height: 68, borderRadius: 34, borderWidth: 4, borderColor: '#fff', backgroundColor: 'rgba(255,255,255,0.2)' },
smallBtn: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.2)' },
btnText: { color: '#fff', fontWeight: '600' },
absoluteFill: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 },
center: { justifyContent: 'center', alignItems: 'center' },
});
export default styles;
......@@ -7361,6 +7361,11 @@ react-native-vector-icons@^8.1.0:
prop-types "^15.7.2"
yargs "^16.1.1"
react-native-vision-camera@^4.7.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/react-native-vision-camera/-/react-native-vision-camera-4.7.2.tgz#e4bd8f3d893a3f5fa9d8224ce28b8ef00d171bc1"
integrity sha512-C+5PvlSunN6I4aYplSask+v3jfhgduZumIVw6H6lG+Afpf8boIcG3uFSsSfVgj+hxI7fx6qM6bsciEhzgxEUYg==
react-native-webview@^13.12.2:
version "13.15.0"
resolved "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment