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"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your Camera.</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"react-native-tab-view": "^3.0.1", "react-native-tab-view": "^3.0.1",
"react-native-touch-id": "^4.4.1", "react-native-touch-id": "^4.4.1",
"react-native-vector-icons": "^8.1.0", "react-native-vector-icons": "^8.1.0",
"react-native-vision-camera": "^4.7.2",
"react-native-webview": "^13.12.2", "react-native-webview": "^13.12.2",
"react-redux": "^7.2.4", "react-redux": "^7.2.4",
"redux": "^4.1.0", "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 React, { useRef, useState } from 'react';
import {Text, View, StyleSheet} from 'react-native'; import { Platform } from 'react-native';
import ProfileView from './view'; import ProfileView from './view';
import { useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
const Profile = props => { const Profile = () => {
const [user, setUser] = useState({ // --- mock profile ---
const [user] = useState({
name: 'Phạm Minh Quân', name: 'Phạm Minh Quân',
role: 'Trợ lý bộ môn thuộc trường', role: 'Trợ lý bộ môn thuộc trường',
code: '80578', code: '80578',
...@@ -24,7 +26,7 @@ const Profile = props => { ...@@ -24,7 +26,7 @@ const Profile = props => {
workCommencementDate: '20/03/2025', workCommencementDate: '20/03/2025',
}); });
// States cho các TextField trong form // --- form states ---
const [phoneNumber, setPhoneNumber] = useState(''); const [phoneNumber, setPhoneNumber] = useState('');
const [oldTeacherCode, setOldTeacherCode] = useState(''); const [oldTeacherCode, setOldTeacherCode] = useState('');
const [workPlace, setWorkPlace] = useState(''); const [workPlace, setWorkPlace] = useState('');
...@@ -58,16 +60,45 @@ const Profile = props => { ...@@ -58,16 +60,45 @@ const Profile = props => {
const [salaryIncreaseMilestone, setSalaryIncreaseMilestone] = useState(''); const [salaryIncreaseMilestone, setSalaryIncreaseMilestone] = useState('');
const [lecturerTitle, setLecturerTitle] = useState(''); const [lecturerTitle, setLecturerTitle] = useState('');
// --- radio group ---
const [selectedValue2, setSelectedValue2] = useState('1'); const [selectedValue2, setSelectedValue2] = useState('1');
const options2 = [ const options2 = [
{label: 'Cơ chế', value: '1'}, { label: 'Cơ chế', value: '1' },
{label: 'Biên hữu', value: '2'}, { label: 'Biên hữu', value: '2' },
]; ];
const onValueChange2 = (v) => setSelectedValue2(v);
const onValueChange2 = value => { // --- camera logic (container) ---
setSelectedValue2(value); 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 handleSave = () => {
const formData = { const formData = {
phoneNumber, phoneNumber,
...@@ -103,84 +134,68 @@ const Profile = props => { ...@@ -103,84 +134,68 @@ const Profile = props => {
currentSalaryCoefficient, currentSalaryCoefficient,
salaryIncreaseMilestone, salaryIncreaseMilestone,
lecturerTitle, lecturerTitle,
avatarUri,
}; };
console.log('Profile Form Data:', formData); 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 ( return (
<ProfileView <ProfileView
// profile
dataProfile={user} 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} selectedValue2={selectedValue2}
options2={options2} options2={options2}
onValueChange2={onValueChange2} onValueChange2={onValueChange2}
// TextField states
phoneNumber={phoneNumber} // form states
setPhoneNumber={setPhoneNumber} phoneNumber={phoneNumber} setPhoneNumber={setPhoneNumber}
oldTeacherCode={oldTeacherCode} oldTeacherCode={oldTeacherCode} setOldTeacherCode={setOldTeacherCode}
setOldTeacherCode={setOldTeacherCode} workPlace={workPlace} setWorkPlace={setWorkPlace}
workPlace={workPlace} position={position} setPosition={setPosition}
setWorkPlace={setWorkPlace} extraDuty={extraDuty} setExtraDuty={setExtraDuty}
position={position} laborType={laborType} setLaborType={setLaborType}
setPosition={setPosition} employmentType={employmentType} setEmploymentType={setEmploymentType}
extraDuty={extraDuty} appointmentDecision={appointmentDecision} setAppointmentDecision={setAppointmentDecision}
setExtraDuty={setExtraDuty} appointmentDate={appointmentDate} setAppointmentDate={setAppointmentDate}
laborType={laborType} issuingAgency={issuingAgency} setIssuingAgency={setIssuingAgency}
setLaborType={setLaborType} jobBeforeRecruitment={jobBeforeRecruitment} setJobBeforeRecruitment={setJobBeforeRecruitment}
employmentType={employmentType} allowanceSeniorityDate={allowanceSeniorityDate} setAllowanceSeniorityDate={setAllowanceSeniorityDate}
setEmploymentType={setEmploymentType} workAllowancePercent={workAllowancePercent} setWorkAllowancePercent={setWorkAllowancePercent}
appointmentDecision={appointmentDecision} teacherAllowancePercent={teacherAllowancePercent} setTeacherAllowancePercent={setTeacherAllowancePercent}
setAppointmentDecision={setAppointmentDecision} teacherType={teacherType} setTeacherType={setTeacherType}
appointmentDate={appointmentDate} academicUnit={academicUnit} setAcademicUnit={setAcademicUnit}
setAppointmentDate={setAppointmentDate} subjectDepartment={subjectDepartment} setSubjectDepartment={setSubjectDepartment}
issuingAgency={issuingAgency} employmentStatus={employmentStatus} setEmploymentStatus={setEmploymentStatus}
setIssuingAgency={setIssuingAgency} workStartDate={workStartDate} setWorkStartDate={setWorkStartDate}
jobBeforeRecruitment={jobBeforeRecruitment} currentContractType={currentContractType} setCurrentContractType={setCurrentContractType}
setJobBeforeRecruitment={setJobBeforeRecruitment} contractStartDate={contractStartDate} setContractStartDate={setContractStartDate}
allowanceSeniorityDate={allowanceSeniorityDate} contractEndDate={contractEndDate} setContractEndDate={setContractEndDate}
setAllowanceSeniorityDate={setAllowanceSeniorityDate} currentContractNumber={currentContractNumber} setCurrentContractNumber={setCurrentContractNumber}
workAllowancePercent={workAllowancePercent} contractEffectiveDate={contractEffectiveDate} setContractEffectiveDate={setContractEffectiveDate}
setWorkAllowancePercent={setWorkAllowancePercent} fullTimeWorkStartDate={fullTimeWorkStartDate} setFullTimeWorkStartDate={setFullTimeWorkStartDate}
teacherAllowancePercent={teacherAllowancePercent} lastClassificationDate={lastClassificationDate} setLastClassificationDate={setLastClassificationDate}
setTeacherAllowancePercent={setTeacherAllowancePercent} currentWorkAllowancePercent={currentWorkAllowancePercent} setCurrentWorkAllowancePercent={setCurrentWorkAllowancePercent}
teacherType={teacherType} salaryGrade={salaryGrade} setSalaryGrade={setSalaryGrade}
setTeacherType={setTeacherType} currentSalaryLevel={currentSalaryLevel} setCurrentSalaryLevel={setCurrentSalaryLevel}
academicUnit={academicUnit} currentSalaryCoefficient={currentSalaryCoefficient} setCurrentSalaryCoefficient={setCurrentSalaryCoefficient}
setAcademicUnit={setAcademicUnit} salaryIncreaseMilestone={salaryIncreaseMilestone} setSalaryIncreaseMilestone={setSalaryIncreaseMilestone}
subjectDepartment={subjectDepartment} lecturerTitle={lecturerTitle} setLecturerTitle={setLecturerTitle}
setSubjectDepartment={setSubjectDepartment}
employmentStatus={employmentStatus} // actions
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
onSave={handleSave} onSave={handleSave}
/> />
); );
......
...@@ -59,5 +59,13 @@ const styles = StyleSheet.create({ ...@@ -59,5 +59,13 @@ const styles = StyleSheet.create({
sizedBox: { sizedBox: {
width: 15, 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; export default styles;
...@@ -2,22 +2,33 @@ import React from 'react'; ...@@ -2,22 +2,33 @@ import React from 'react';
import { import {
Text, Text,
View, View,
TouchableOpacity,
StyleSheet,
Image, Image,
ScrollView, ScrollView,
TouchableOpacity,
} from 'react-native'; } from 'react-native';
import styles from './style'; import styles from './style';
import Header from '../../components/Header/Header'; import Header from '../../components/Header/Header';
import R from '../../assets/R'; import R from '../../assets/R';
import Button from '../../components/Button'; import Button from '../../components/Button';
import TextField from '../../components/Input/TextField'; import TextField from '../../components/Input/TextField';
import RadioButton from '../../components/RadioButton/RadioButton';
import RadioGroup from '../../components/RadioButton/RadioGroup'; import RadioGroup from '../../components/RadioButton/RadioGroup';
import {
Camera,
} from 'react-native-vision-camera'
const ProfileView = props => { const ProfileView = props => {
const { const {
dataProfile, dataProfile,
// camera
avatarUri,
onPressSelectImage,
showCamera,
cameraRef,
device,
onToggleCameraPosition,
onTakePhoto,
onCloseCamera,
selectedValue2, selectedValue2,
options2, options2,
onValueChange2, onValueChange2,
...@@ -90,22 +101,14 @@ const ProfileView = props => { ...@@ -90,22 +101,14 @@ const ProfileView = props => {
onSave, onSave,
} = props; } = props;
console.log(dataProfile?.name);
const renderButtonCamera = () => {
return ( return (
<View style={styles.container}>
<Header title={'Hồ sơ giảng viên'} isBack />
<ScrollView showsVerticalScrollIndicator={false} vertical>
<View style={styles.body}>
<View style={styles.headerBody}>
<View style={styles.headerLeft}>
<View style={styles.boxCamera}>
<View style={styles.containerImage}>
<Image source={R.images.iconCamera} style={styles.image} />
</View>
<View style={styles.containerButton}>
<Button <Button
title="Chọn ảnh" title="Chọn ảnh"
onPress={() => {}} onPress={onPressSelectImage}
backgroundColor={R.colors.blue} backgroundColor={R.colors.blue}
textColor={R.colors.white} textColor={R.colors.white}
height={30} height={30}
...@@ -116,7 +119,22 @@ const ProfileView = props => { ...@@ -116,7 +119,22 @@ const ProfileView = props => {
paddingHorizontal={15} paddingHorizontal={15}
paddingVertical={3} paddingVertical={3}
/> />
);
};
const renderHeaderBody = () => {
return (
<View style={styles.headerBody}>
<View style={styles.headerLeft}>
<View style={styles.boxCamera}>
<View style={styles.containerImage}>
{avatarUri ? (
<Image source={{ uri: avatarUri }} style={styles.image} />
) : (
<Image source={R.images.iconCamera} style={styles.image} />
)}
</View> </View>
<View style={styles.containerButton}>{renderButtonCamera()}</View>
</View> </View>
</View> </View>
...@@ -134,8 +152,7 @@ const ProfileView = props => { ...@@ -134,8 +152,7 @@ const ProfileView = props => {
]}> ]}>
{dataProfile?.name || 'Không có dữ liệu'} {dataProfile?.name || 'Không có dữ liệu'}
</Text> </Text>
<Text <Text style={[styles.text, {fontSize: R.fontsize.fontsSizeSubTitle}]}>
style={[styles.text, {fontSize: R.fontsize.fontsSizeSubTitle}]}>
{dataProfile?.role || 'Không có dữ liệu'} {dataProfile?.role || 'Không có dữ liệu'}
</Text> </Text>
<Text <Text
...@@ -235,6 +252,11 @@ const ProfileView = props => { ...@@ -235,6 +252,11 @@ const ProfileView = props => {
</Text> </Text>
</View> </View>
</View> </View>
);
};
const renderBody = () => {
return (
<View style={styles.bodyProfile}> <View style={styles.bodyProfile}>
<View style={styles.containerInput}> <View style={styles.containerInput}>
<TextField <TextField
...@@ -655,8 +677,53 @@ const ProfileView = props => { ...@@ -655,8 +677,53 @@ const ProfileView = props => {
fontSizeTitle={R.sizes.sm} fontSizeTitle={R.sizes.sm}
/> />
</View> </View>
);
};
return (
<View style={styles.container}>
<Header title={'Hồ sơ giảng viên'} isBack />
<ScrollView showsVerticalScrollIndicator={false} vertical>
<View style={styles.body}>
{renderHeaderBody()}
{renderBody()}
</View> </View>
</ScrollView> </ScrollView>
{/* Overlay Camera - chỉ UI, mọi handler/state nhận từ props */}
{showCamera && (
<View style={styles.overlay}>
{device ? (
<>
<Camera
ref={cameraRef}
style={styles.absoluteFill}
device={device}
isActive={showCamera}
photo
/>
<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> </View>
); );
}; };
......
...@@ -7361,6 +7361,11 @@ react-native-vector-icons@^8.1.0: ...@@ -7361,6 +7361,11 @@ react-native-vector-icons@^8.1.0:
prop-types "^15.7.2" prop-types "^15.7.2"
yargs "^16.1.1" 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: react-native-webview@^13.12.2:
version "13.15.0" version "13.15.0"
resolved "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz" 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