deskripsi:
- new component camera
This commit is contained in:
2025-07-04 15:36:09 +08:00
parent 17e6208aae
commit b54693caa7
15 changed files with 528 additions and 42 deletions

View File

@@ -3,23 +3,22 @@ import {
ButtonCustom,
SelectCustom,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { StyleSheet, Text } from "react-native";
import { StyleSheet } from "react-native";
export default function ProfileEdit() {
const { id } = useLocalSearchParams();
const [nama, setNama] = useState("Bagas Banuna");
const [email, setEmail] = useState("bagasbanuna@gmail.com");
const [alamat, setAlamat] = useState("Bandar Lampung");
const [selectedValue, setSelectedValue] = useState<string | number>("");
const [data, setData] = useState({
nama: "Bagas Banuna",
email: "bagasbanuna@gmail.com",
alamat: "Jember",
selectedValue: "",
});
const options = [
{ label: "React", value: "react" },
@@ -33,15 +32,24 @@ export default function ProfileEdit() {
{ label: "SvelteKit", value: "sveltekit" },
];
const handleSave = () => {
console.log({
nama: data.nama,
email: data.email,
alamat: data.alamat,
selectedValue: data.selectedValue,
});
router.back();
};
return (
<ViewWrapper
bottomBarComponent={
<ButtonCustom
disabled={!nama || !email || !alamat || !selectedValue}
onPress={() => {
console.log("data >>", nama, email, alamat, selectedValue);
router.back();
}}
disabled={
!data.nama || !data.email || !data.alamat || !data.selectedValue
}
onPress={handleSave}
>
Simpan
</ButtonCustom>
@@ -52,37 +60,36 @@ export default function ProfileEdit() {
label="Framework"
placeholder="Pilih framework favoritmu"
data={options}
value={selectedValue}
onChange={setSelectedValue}
value={data.selectedValue}
onChange={(value) => {
setData({ ...(data as any), selectedValue: value });
}}
/>
{/* {selectedValue && (
<Text style={styles.result}>Terpilih: {selectedValue}</Text>
)} */}
<TextInputCustom
label="Nama"
placeholder="Nama"
value={nama}
value={data.nama}
onChangeText={(text) => {
setNama(text);
setData({ ...data, nama: text });
}}
required
/>
<TextInputCustom
label="Email"
placeholder="Email"
value={email}
value={data.email}
onChangeText={(text) => {
setEmail(text);
setData({ ...data, email: text });
}}
required
/>
<TextInputCustom
label="Alamat"
placeholder="Alamat"
value={alamat}
value={data.alamat}
onChangeText={(text) => {
setAlamat(text);
setData({ ...data, alamat: text });
}}
required
/>

View File

@@ -0,0 +1,168 @@
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
} from "@/components";
import AntDesign from "@expo/vector-icons/AntDesign";
import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
import { CameraType, CameraView, useCameraPermissions } from "expo-camera";
import { Image } from "expo-image";
import * as ImagePicker from "expo-image-picker";
import { router } from "expo-router";
import { useRef, useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
export default function TakePictureProfile() {
const [permission, requestPermission] = useCameraPermissions();
const ref = useRef<CameraView>(null);
const [uri, setUri] = useState<string | null>(null);
const [facing, setFacing] = useState<CameraType>("back");
if (!permission?.granted) {
return (
<View style={styles.container}>
<Text style={{ textAlign: "center" }}>
We need your permission to use the camera
</Text>
<Pressable onPress={requestPermission}>
<Text style={{ color: "blue", marginTop: 10 }}>Grant permission</Text>
</Pressable>
</View>
);
}
const takePicture = async () => {
const photo = await ref.current?.takePictureAsync();
setUri(photo?.uri || null);
};
const pickImage = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.canceled) {
setUri(result.assets[0].uri);
}
};
const toggleFacing = () => {
setFacing((prev) => (prev === "back" ? "front" : "back"));
};
const renderPicture = () => {
return (
<View>
<Image
source={uri ? uri : null}
contentFit="contain"
style={{ width: 340, aspectRatio: 1 }}
/>
<Spacing />
<StackCustom>
<ButtonCustom onPress={() => setUri(null)} title="Foto ulang" />
<ButtonCustom
onPress={() => {
console.log("Update foto");
router.back();
}}
title="Update Foto"
/>
</StackCustom>
</View>
);
};
const renderCameraUI = () => {
return (
<View style={styles.cameraOverlay}>
<View style={styles.shutterContainer}>
<Pressable onPress={toggleFacing}>
<FontAwesome6 name="rotate-left" size={32} color="white" />
</Pressable>
<Pressable onPress={takePicture}>
{({ pressed }) => (
<View
style={[
styles.shutterBtn,
{
opacity: pressed ? 0.5 : 1,
},
]}
>
<View style={styles.shutterBtnInner} />
</View>
)}
</Pressable>
<Pressable onPress={pickImage}>
<AntDesign name="folderopen" size={32} color="white" />
</Pressable>
</View>
</View>
);
};
return (
<>
{uri ? (
<ViewWrapper>
<View style={styles.container}>{renderPicture()}</View>
</ViewWrapper>
) : (
<>
<CameraView
style={styles.camera}
ref={ref}
facing={facing}
responsiveOrientationWhenOrientationLocked
/>
{renderCameraUI()}
</>
)}
</>
);
}
const styles = StyleSheet.create({
container: {
justifyContent: "center",
alignItems: "center",
},
camera: {
flex: 1,
width: "100%",
},
cameraOverlay: {
...StyleSheet.absoluteFillObject,
justifyContent: "flex-end",
padding: 44,
},
shutterContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
shutterBtn: {
backgroundColor: "transparent",
borderWidth: 5,
borderColor: "white",
width: 85,
height: 85,
borderRadius: 45,
alignItems: "center",
justifyContent: "center",
},
shutterBtnInner: {
width: 70,
height: 70,
borderRadius: 50,
backgroundColor: "white",
},
});

View File

@@ -0,0 +1,190 @@
// COMPONENT : Jika ingin uoload gambar dan video gunakan component ini
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
} from "@/components";
import AntDesign from "@expo/vector-icons/AntDesign";
import Feather from "@expo/vector-icons/Feather";
import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
import {
CameraMode,
CameraType,
CameraView,
useCameraPermissions,
} from "expo-camera";
import { Image } from "expo-image";
import { router } from "expo-router";
import { useRef, useState } from "react";
import { Button, Pressable, StyleSheet, Text, View } from "react-native";
export default function TakePictureProfile2() {
const [permission, requestPermission] = useCameraPermissions();
const ref = useRef<CameraView>(null);
const [uri, setUri] = useState<string | null>(null);
const [mode, setMode] = useState<CameraMode>("picture");
const [facing, setFacing] = useState<CameraType>("back");
const [recording, setRecording] = useState(false);
if (!permission?.granted) {
return (
<View style={styles.container}>
<Text style={{ textAlign: "center" }}>
We need your permission to use the camera
</Text>
<Button onPress={requestPermission} title="Grant permission" />
</View>
);
}
const takePicture = async () => {
const photo = await ref.current?.takePictureAsync();
setUri(photo?.uri || null);
};
const recordVideo = async () => {
if (recording) {
setRecording(false);
ref.current?.stopRecording();
return;
}
setRecording(true);
const video = await ref.current?.recordAsync();
console.log({ video });
};
const toggleMode = () => {
setMode((prev) => (prev === "picture" ? "video" : "picture"));
};
const toggleFacing = () => {
setFacing((prev) => (prev === "back" ? "front" : "back"));
};
const renderPicture = () => {
console.log("renderPicture", uri);
return (
<View>
<Image
source={uri ? uri : null}
contentFit="contain"
style={{ width: 340, aspectRatio: 1 }}
/>
<Spacing />
<StackCustom>
<ButtonCustom onPress={() => setUri(null)} title="Foto ulang" />
<ButtonCustom
onPress={() => {
console.log("Update foto");
router.back();
}}
title="Update Foto"
/>
</StackCustom>
</View>
);
};
const renderCameraUI = () => {
return (
<View style={styles.cameraOverlay}>
<View style={styles.shutterContainer}>
<Pressable onPress={toggleMode}>
{mode === "picture" ? (
<AntDesign name="picture" size={32} color="white" />
) : (
<Feather name="video" size={32} color="white" />
)}
</Pressable>
<Pressable onPress={mode === "picture" ? takePicture : recordVideo}>
{({ pressed }) => (
<View
style={[
styles.shutterBtn,
{
opacity: pressed ? 0.5 : 1,
},
]}
>
<View
style={[
styles.shutterBtnInner,
{
backgroundColor: mode === "picture" ? "white" : "red",
},
]}
/>
</View>
)}
</Pressable>
<Pressable onPress={toggleFacing}>
<FontAwesome6 name="rotate-left" size={32} color="white" />
</Pressable>
</View>
</View>
);
};
return (
<>
{uri ? (
<ViewWrapper>
<View style={styles.container}>{renderPicture()}</View>
</ViewWrapper>
) : (
<>
<CameraView
style={styles.camera}
ref={ref}
mode={mode}
facing={facing}
mute={false}
responsiveOrientationWhenOrientationLocked
/>
{renderCameraUI()}
</>
)}
</>
);
}
const styles = StyleSheet.create({
container: {
justifyContent: "center",
alignItems: "center",
},
camera: {
flex: 1,
width: "100%",
},
cameraOverlay: {
...StyleSheet.absoluteFillObject,
justifyContent: "flex-end",
padding: 44,
},
shutterContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
shutterBtn: {
backgroundColor: "transparent",
borderWidth: 5,
borderColor: "white",
width: 85,
height: 85,
borderRadius: 45,
alignItems: "center",
justifyContent: "center",
},
shutterBtnInner: {
width: 70,
height: 70,
borderRadius: 50,
},
});

View File

@@ -1,11 +1,41 @@
import { Text, View } from "react-native";
import { useLocalSearchParams } from "expo-router";
/* eslint-disable @typescript-eslint/no-unused-vars */
import { BaseBox, ButtonCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import ButtonUpload from "@/components/Button/ButtonUpload";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { router, useLocalSearchParams } from "expo-router";
import { Image } from "react-native";
export default function UpdatePhotoBackground() {
const { id } = useLocalSearchParams();
return (
<View>
<Text>Update Photo Background {id}</Text>
</View>
)
}
export default function UpdateBackgroundProfile() {
const { id } = useLocalSearchParams();
return (
<ViewWrapper
bottomBarComponent={
<ButtonCustom
onPress={() => {
console.log("Simpan foto");
router.back();
}}
>
Simpan
</ButtonCustom>
}
>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
<Image
source={DUMMY_IMAGE.background}
resizeMode="cover"
style={{ width: "100%", height: "100%", borderRadius: 10 }}
/>
</BaseBox>
<ButtonUpload
onPress={() =>
router.navigate("/(application)/profile/[id]/take-picture")
}
/>
</ViewWrapper>
);
}

View File

@@ -1,11 +1,41 @@
import { Text, View } from "react-native";
import { useLocalSearchParams } from "expo-router";
import { BaseBox, ButtonCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import ButtonUpload from "@/components/Button/ButtonUpload";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { router, useLocalSearchParams } from "expo-router";
import { Image } from "react-native";
export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams();
return (
<View>
<Text>Update Photo Profile {id}</Text>
</View>
<ViewWrapper
bottomBarComponent={
<ButtonCustom
onPress={() => {
console.log("Simpan foto");
router.back();
}}
>
Simpan
</ButtonCustom>
}
>
<BaseBox
style={{ alignItems: "center", justifyContent: "center", height: 250 }}
>
<Image
source={DUMMY_IMAGE.avatar}
resizeMode="cover"
style={{ width: 200, height: 200 }}
/>
</BaseBox>
<ButtonUpload
onPress={() => {
console.log("ID >>", id);
router.navigate("/(application)/profile/[id]/take-picture");
}}
/>
</ViewWrapper>
);
}

View File

@@ -24,6 +24,14 @@ export default function ProfileLayout() {
name="[id]/update-background"
options={{ title: "Update Latar Belakang" }}
/>
<Stack.Screen
name="[id]/take-picture"
options={{ title: "Ambil Foto" }}
/>
<Stack.Screen
name="[id]/take-picture2"
options={{ title: "Ambil Foto 2" }}
/>
</Stack>
</>
);