Commit 7b119d30 by tungnq

feat: Nâng cấp màn hình hồ sơ với chọn ảnh & chụp ảnh

Tích hợp image picker cho phép chọn ảnh từ thư viện.

Thêm chức năng camera để chụp ảnh trực tiếp trong ứng dụng.

Tải nhanh ảnh gần đây từ Camera Roll để chọn tức thời.

Cập nhật style cho cụm điều khiển camera và khung xem trước ảnh.

Cải thiện trải nghiệm: quản lý hiển thị tab (ẩn/hiện) khi mở camera và khi xem trước ảnh.
parent a658561e
......@@ -2,7 +2,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
......
......@@ -9,6 +9,9 @@ const images = {
icGallery: require('./images/icGallery.png'),
icNoData: require('./icon/icon_png/icon_no_data.png'),
icMenuEdit: require('./icon/icon_png/menuEdit.png'),
icSwitchCamera: require('./icon/icon_png/icon_switch_camera.png'),
icClose: require('./icon/icon_png/icon_close.png'),
icTakePhoto: require('./icon/icon_png/icon_take_photo.png'),
//HomeScreen
icLichDay:require('./icon/lich_day.png'),
icBaoBu: require('./icon/bao_bu.png'),
......
......@@ -56,4 +56,4 @@ export const TRASHEMAIL = 'TRASHEMAIL';
export const LISTROLLCALL = 'LISTROLLCALL';
export const LISTSTUDENTROLLCALL = 'LISTSTUDENTROLLCALL';
export const DETAILROLLCALL = 'DETAILROLLCALL';
export const DETAILROLLCALL = 'DETAILROLLCALL';
\ No newline at end of file
import React, {useEffect, useState} from 'react';
import {DeviceEventEmitter, Image, View} from 'react-native';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {useFocusEffect} from '@react-navigation/native';
import i18n from '../helper/i18/i18n';
import {connect} from 'react-redux';
import R from '../assets/R';
......@@ -15,36 +16,46 @@ const Tab = createBottomTabNavigator();
const TabNavigator = props => {
const [reload, setReload] = useState(false);
const [hideTabBar, setHideTabBar] = useState(false);
useEffect(() => {
let setLanguage = DeviceEventEmitter.addListener('setLanguage', value => {
setReload(!reload);
});
let hideTabs = DeviceEventEmitter.addListener('hideTabs', shouldHide => {
setHideTabBar(shouldHide);
});
return () => {
setLanguage.remove();
hideTabs.remove();
};
}, []);
return (
<Tab.Navigator
initialRouteName="Screen5"
screenOptions={{headerShown: false}}
screenOptions={{
headerShown: false,
tabBarStyle: hideTabBar
? {display: 'none'}
: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.29,
shadowRadius: 2,
elevation: 7,
justifyContent: 'center',
},
}}
tabBarOptions={{
showIcon: true,
showLabel: true,
activeTintColor: R.colors.main,
style: {
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.29,
shadowRadius: 2,
elevation: 7,
justifyContent: 'center',
},
}}>
<Tab.Screen
name="HomeScreen1"
......@@ -59,7 +70,7 @@ const TabNavigator = props => {
),
}}
/>
<Tab.Screen
<Tab.Screen
name="HomeScreen145"
component={DrawerNavigator}
options={{
......
import React, { useRef, useState } from 'react';
import { Platform } from 'react-native';
import React, {useRef, useState, useEffect} from 'react';
import {Platform, DeviceEventEmitter, Alert, PermissionsAndroid} from 'react-native';
import {launchImageLibrary} from 'react-native-image-picker';
import {CameraRoll} from '@react-native-camera-roll/camera-roll';
import ProfileView from './view';
import { useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
import {useCameraDevice, useCameraPermission} from 'react-native-vision-camera';
const Profile = () => {
// --- mock profile ---
const Profile = (props) => {
const [user] = useState({
name: 'Phạm Minh Quân',
role: 'Trợ lý bộ môn thuộc trường',
......@@ -26,7 +27,6 @@ const Profile = () => {
workCommencementDate: '20/03/2025',
});
// --- form states ---
const [phoneNumber, setPhoneNumber] = useState('');
const [oldTeacherCode, setOldTeacherCode] = useState('');
const [workPlace, setWorkPlace] = useState('');
......@@ -63,25 +63,95 @@ const Profile = () => {
// --- 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 = (v) => setSelectedValue2(v);
const onValueChange2 = v => setSelectedValue2(v);
// --- camera logic (container) ---
const [showCamera, setShowCamera] = useState(false);
const [showImageSourceModal, setShowImageSourceModal] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const [avatarUri, setAvatarUri] = useState(null);
const [previewUri, setPreviewUri] = useState(null);
const [recentPhotos, setRecentPhotos] = useState([]);
const [useFront, setUseFront] = useState(false);
const cameraRef = useRef(null);
const device = useCameraDevice(useFront ? 'front' : 'back');
const { hasPermission, requestPermission } = useCameraPermission();
const {hasPermission, requestPermission} = useCameraPermission();
// Load recent photos for gallery preview
const loadRecentPhotos = async () => {
try {
if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
return;
}
}
const photos = await CameraRoll.getPhotos({
first: 4, // Get 4 recent photos for preview
assetType: 'Photos',
});
setRecentPhotos(photos.edges.map(edge => edge.node.image.uri));
} catch (error) {
console.log('Error loading recent photos:', error);
}
};
useEffect(() => {
loadRecentPhotos();
}, []);
const openImageSourceModal = async () => {
// Go directly to camera instead of showing modal
if (!hasPermission) {
const ok = await requestPermission();
if (!ok) return;
}
setShowCamera(true);
DeviceEventEmitter.emit('hideTabs', true);
};
const closeImageSourceModal = () => {
setShowImageSourceModal(false);
};
const openCamera = async () => {
if (!hasPermission) {
const ok = await requestPermission();
if (!ok) return;
}
setShowImageSourceModal(false);
setShowCamera(true);
DeviceEventEmitter.emit('hideTabs', true);
};
const openGallery = () => {
const options = {
mediaType: 'photo',
includeBase64: false,
maxHeight: 2000,
maxWidth: 2000,
};
launchImageLibrary(options, response => {
if (response.didCancel || response.error) {
return;
}
if (response.assets && response.assets[0]) {
const imageUri = response.assets[0].uri;
setPreviewUri(imageUri);
setShowImageSourceModal(false);
setShowPreview(true);
DeviceEventEmitter.emit('hideTabs', true);
}
});
};
const onTakePhoto = async () => {
......@@ -91,12 +161,38 @@ const Profile = () => {
flash: 'off',
});
const uri = Platform.OS === 'android' ? 'file://' + photo.path : photo.path;
setAvatarUri(uri);
setPreviewUri(uri);
setShowCamera(false);
setShowPreview(true);
// Keep tabs hidden for preview
};
const onToggleCameraPosition = () => setUseFront(v => !v);
const onCloseCamera = () => {
setShowCamera(false);
// Show tab bar when camera closes
DeviceEventEmitter.emit('hideTabs', false);
};
const onConfirmPhoto = () => {
setAvatarUri(previewUri);
setPreviewUri(null);
setShowPreview(false);
DeviceEventEmitter.emit('hideTabs', false);
};
const onRetakePhoto = () => {
setPreviewUri(null);
setShowPreview(false);
DeviceEventEmitter.emit('hideTabs', false);
};
const onToggleCameraPosition = () => setUseFront((v) => !v);
const onCloseCamera = () => setShowCamera(false);
const onClosePreview = () => {
setPreviewUri(null);
setShowPreview(false);
DeviceEventEmitter.emit('hideTabs', false);
};
// --- save handler ---
const handleSave = () => {
......@@ -145,56 +241,97 @@ const Profile = () => {
<ProfileView
// profile
dataProfile={user}
// camera props (UI sẽ dùng để hiển thị)
avatarUri={avatarUri}
onPressSelectImage={openCamera}
onPressSelectImage={openImageSourceModal}
showCamera={showCamera}
cameraRef={cameraRef}
device={device}
onToggleCameraPosition={onToggleCameraPosition}
onTakePhoto={onTakePhoto}
onCloseCamera={onCloseCamera}
// image source modal
showImageSourceModal={showImageSourceModal}
onCloseImageSourceModal={closeImageSourceModal}
onSelectCamera={openCamera}
onSelectGallery={openGallery}
// preview
showPreview={showPreview}
previewUri={previewUri}
onConfirmPhoto={onConfirmPhoto}
onRetakePhoto={onRetakePhoto}
onClosePreview={onClosePreview}
// gallery preview
recentPhotos={recentPhotos}
// radio
selectedValue2={selectedValue2}
options2={options2}
onValueChange2={onValueChange2}
// 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}
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}
/>
......
import {StyleSheet, Text, View} from 'react-native';
import {StyleSheet, Text, View, Dimensions} from 'react-native';
import R from '../../assets/R';
const {width, height} = Dimensions.get('window');
const widthLibary = width / 7;
const heightLibary = height / 14;
const styles = StyleSheet.create({
container: {
flex: 1,
......@@ -59,13 +62,212 @@ const styles = StyleSheet.create({
sizedBox: {
width: 15,
},
topLeft: {
position: 'absolute',
top: 15,
left: 15,
},
topRight: {
position: 'absolute',
top: 15,
right: 15,
},
bottomLeft: {
position: 'absolute',
bottom: heightLibary,
left: widthLibary,
},
bottomRight: {
position: 'absolute',
bottom: 15,
right: 15,
},
shutter: {
position: 'absolute',
bottom: 50,
left: '40%',
width: 68,
height: 68,
borderRadius: 34,
borderWidth: 4,
borderColor: '#fff',
backgroundColor: 'rgba(255,255,255,0.2)',
},
overlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'black',
},
smallBtn: {
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
backgroundColor: 'transparent',
},
btnText: {color: '#fff', fontWeight: '600'},
absoluteFill: {position: 'absolute', top: 0, left: 0, right: 0, bottom: 0},
center: {justifyContent: 'center', alignItems: 'center'},
// Gallery Preview Styles
galleryPreview: {
width: 60,
height: 60,
borderRadius: 8,
backgroundColor: 'rgba(0, 0, 0, 0.3)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
galleryGrid: {
width: '100%',
height: '100%',
flexDirection: 'row',
flexWrap: 'wrap',
borderRadius: 6,
overflow: 'hidden',
},
galleryThumbnail: {
width: '50%',
height: '50%',
borderWidth: 0.5,
borderColor: 'rgba(255, 255, 255, 0.2)',
},
// Image Source Modal Styles
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'flex-end',
},
modalContainer: {
backgroundColor: R.colors.white,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
paddingHorizontal: 20,
paddingVertical: 30,
minHeight: 250,
},
modalTitle: {
fontSize: R.sizes.lg,
fontFamily: R.fonts.fontMedium,
fontWeight: '600',
color: R.colors.black,
textAlign: 'center',
marginBottom: 30,
},
modalOption: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 15,
paddingHorizontal: 20,
backgroundColor: R.colors.grayLight || '#f5f5f5',
borderRadius: 10,
marginBottom: 15,
},
modalIcon: {
width: 24,
height: 24,
marginRight: 15,
tintColor: R.colors.blue,
},
modalOptionText: {
fontSize: R.sizes.md,
fontFamily: R.fonts.fontMedium,
fontWeight: '500',
color: R.colors.black,
},
modalCancelButton: {
paddingVertical: 15,
alignItems: 'center',
marginTop: 10,
},
modalCancelText: {
fontSize: R.sizes.md,
fontFamily: R.fonts.fontMedium,
fontWeight: '500',
color: R.colors.gray,
},
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' },
// Preview Modal Styles
previewContainer: {
flex: 1,
backgroundColor: R.colors.black,
},
previewHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingVertical: 15,
paddingTop: 50,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
},
previewCloseButton: {
padding: 10,
},
previewCloseIcon: {
width: 24,
height: 24,
tintColor: R.colors.white,
},
previewTitle: {
fontSize: R.sizes.lg,
fontFamily: R.fonts.fontMedium,
fontWeight: '600',
color: R.colors.white,
},
previewSpacer: {
width: 44,
},
previewImageContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
previewImage: {
width: '100%',
height: '100%',
},
previewActions: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingHorizontal: 40,
paddingVertical: 30,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
},
previewRetakeButton: {
paddingVertical: 12,
paddingHorizontal: 30,
borderRadius: 25,
borderWidth: 2,
borderColor: R.colors.white,
backgroundColor: 'transparent',
},
previewRetakeText: {
fontSize: R.sizes.md,
fontFamily: R.fonts.fontMedium,
fontWeight: '600',
color: R.colors.white,
textAlign: 'center',
},
previewConfirmButton: {
paddingVertical: 12,
paddingHorizontal: 30,
borderRadius: 25,
backgroundColor: R.colors.blue,
},
previewConfirmText: {
fontSize: R.sizes.md,
fontFamily: R.fonts.fontMedium,
fontWeight: '600',
color: R.colors.white,
textAlign: 'center',
},
});
export default styles;
......@@ -5,6 +5,7 @@ import {
Image,
ScrollView,
TouchableOpacity,
Modal,
} from 'react-native';
import styles from './style';
import Header from '../../components/Header/Header';
......@@ -12,13 +13,11 @@ import R from '../../assets/R';
import Button from '../../components/Button';
import TextField from '../../components/Input/TextField';
import RadioGroup from '../../components/RadioButton/RadioGroup';
import {
Camera,
} from 'react-native-vision-camera'
import {Camera} from 'react-native-vision-camera';
const ProfileView = props => {
const {
dataProfile,
// camera
avatarUri,
onPressSelectImage,
......@@ -29,6 +28,21 @@ const ProfileView = props => {
onTakePhoto,
onCloseCamera,
// image source modal
showImageSourceModal,
onCloseImageSourceModal,
onSelectCamera,
onSelectGallery,
// preview
showPreview,
previewUri,
onConfirmPhoto,
onRetakePhoto,
onClosePreview,
// gallery preview
recentPhotos,
selectedValue2,
options2,
onValueChange2,
......@@ -101,9 +115,6 @@ const ProfileView = props => {
onSave,
} = props;
const renderButtonCamera = () => {
return (
<Button
......@@ -122,17 +133,69 @@ const ProfileView = props => {
);
};
const renderPreviewModal = () => {
return (
<Modal
visible={showPreview}
transparent={false}
animationType="slide"
onRequestClose={onClosePreview}>
<View style={styles.previewContainer}>
<View style={styles.previewHeader}>
<TouchableOpacity
style={styles.previewCloseButton}
onPress={onClosePreview}>
<Image
source={R.images.icClose}
style={styles.previewCloseIcon}
/>
</TouchableOpacity>
<Text style={styles.previewTitle}>Xem trước nh</Text>
<View style={styles.previewSpacer} />
</View>
<View style={styles.previewImageContainer}>
{previewUri && (
<Image
source={{uri: previewUri}}
style={styles.previewImage}
resizeMode="contain"
/>
)}
</View>
<View style={styles.previewActions}>
<TouchableOpacity
style={styles.previewRetakeButton}
onPress={onRetakePhoto}>
<Text style={styles.previewRetakeText}>Chp li</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.previewConfirmButton}
onPress={onConfirmPhoto}>
<Text style={styles.previewConfirmText}>Xác nhn</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
};
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} />
)}
{avatarUri ? (
<Image source={{uri: avatarUri}} style={styles.image} />
) : (
<Image
source={R.images.iconCamera}
style={[styles.image, {width: 25, height: 25}]}
/>
)}
</View>
<View style={styles.containerButton}>{renderButtonCamera()}</View>
</View>
......@@ -690,6 +753,10 @@ const ProfileView = props => {
</View>
</ScrollView>
{/* Image Source Selection Modal */}
{/* Preview Modal */}
{renderPreviewModal()}
{/* Overlay Camera - chỉ UI, mọi handler/state nhận từ props */}
{showCamera && (
......@@ -703,27 +770,66 @@ const ProfileView = props => {
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>
<TouchableOpacity
style={styles.topLeft}
onPress={onToggleCameraPosition}>
<Image
source={R.images.icSwitchCamera}
tintColor={R.colors.white}
style={{width: 30, height: 30}}
/>
</TouchableOpacity>
<TouchableOpacity style={styles.topRight} onPress={onCloseCamera}>
<Image
source={R.images.icClose}
tintColor={R.colors.white}
style={{width: 30, height: 30}}
/>
</TouchableOpacity>
<TouchableOpacity style={styles.bottomLeft} onPress={onSelectGallery}>
<View style={styles.galleryPreview}>
{recentPhotos.length > 0 ? (
<View style={styles.galleryGrid}>
{recentPhotos.slice(0, 4).map((photoUri, index) => (
<Image
key={index}
source={{uri: photoUri}}
style={styles.galleryThumbnail}
/>
))}
</View>
) : (
<Image
source={R.images.icTakePhoto}
tintColor={R.colors.white}
style={{width: 30, height: 30}}
/>
)}
</View>
</TouchableOpacity>
<TouchableOpacity style={styles.shutter} onPress={onTakePhoto}>
{/* <Image
source={R.images.icTakePhoto}
tintColor={R.colors.white}
style={{width: 24, height: 24}}
/> */}
</TouchableOpacity>
</>
) : (
<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={{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>
);
};
......
......@@ -7116,6 +7116,11 @@ react-native-image-crop-picker@^0.36.2:
resolved "https://registry.npmjs.org/react-native-image-crop-picker/-/react-native-image-crop-picker-0.36.4.tgz"
integrity sha512-FOWkYbCEh78V5/aK9HqMSvRnQJtelGwj0UOu1zhE49gO6e4YoKKNBvA15jweAMM/kPA+omDXBIgJaruonoEXGA==
react-native-image-picker@^8.2.1:
version "8.2.1"
resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-8.2.1.tgz#1ac7826563cbaa5d5298d9f2acc53c69805e5393"
integrity sha512-FBeGYJGFDjMdGCcyubDJgBAPCQ4L1D3hwLXyUU91jY9ahOZMTbluceVvRmrEKqnDPFJ0gF1NVhJ0nr1nROFLdg==
react-native-indicators@^0.17.0:
version "0.17.0"
resolved "https://registry.npmjs.org/react-native-indicators/-/react-native-indicators-0.17.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