feature
deskripsi: - new component camera
This commit is contained in:
8
app.json
8
app.json
@@ -38,6 +38,14 @@
|
|||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"expo-camera",
|
||||||
|
{
|
||||||
|
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
|
||||||
|
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone",
|
||||||
|
"recordAudioAndroid": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
|
|||||||
@@ -3,23 +3,22 @@ import {
|
|||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
SelectCustom,
|
SelectCustom,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextCustom,
|
|
||||||
TextInputCustom,
|
TextInputCustom,
|
||||||
ViewWrapper,
|
ViewWrapper,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { StyleSheet, Text } from "react-native";
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
export default function ProfileEdit() {
|
export default function ProfileEdit() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
|
|
||||||
const [nama, setNama] = useState("Bagas Banuna");
|
const [data, setData] = useState({
|
||||||
const [email, setEmail] = useState("bagasbanuna@gmail.com");
|
nama: "Bagas Banuna",
|
||||||
const [alamat, setAlamat] = useState("Bandar Lampung");
|
email: "bagasbanuna@gmail.com",
|
||||||
|
alamat: "Jember",
|
||||||
const [selectedValue, setSelectedValue] = useState<string | number>("");
|
selectedValue: "",
|
||||||
|
});
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{ label: "React", value: "react" },
|
{ label: "React", value: "react" },
|
||||||
@@ -33,15 +32,24 @@ export default function ProfileEdit() {
|
|||||||
{ label: "SvelteKit", value: "sveltekit" },
|
{ label: "SvelteKit", value: "sveltekit" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
console.log({
|
||||||
|
nama: data.nama,
|
||||||
|
email: data.email,
|
||||||
|
alamat: data.alamat,
|
||||||
|
selectedValue: data.selectedValue,
|
||||||
|
});
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper
|
<ViewWrapper
|
||||||
bottomBarComponent={
|
bottomBarComponent={
|
||||||
<ButtonCustom
|
<ButtonCustom
|
||||||
disabled={!nama || !email || !alamat || !selectedValue}
|
disabled={
|
||||||
onPress={() => {
|
!data.nama || !data.email || !data.alamat || !data.selectedValue
|
||||||
console.log("data >>", nama, email, alamat, selectedValue);
|
}
|
||||||
router.back();
|
onPress={handleSave}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Simpan
|
Simpan
|
||||||
</ButtonCustom>
|
</ButtonCustom>
|
||||||
@@ -52,37 +60,36 @@ export default function ProfileEdit() {
|
|||||||
label="Framework"
|
label="Framework"
|
||||||
placeholder="Pilih framework favoritmu"
|
placeholder="Pilih framework favoritmu"
|
||||||
data={options}
|
data={options}
|
||||||
value={selectedValue}
|
value={data.selectedValue}
|
||||||
onChange={setSelectedValue}
|
onChange={(value) => {
|
||||||
|
setData({ ...(data as any), selectedValue: value });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/* {selectedValue && (
|
|
||||||
<Text style={styles.result}>Terpilih: {selectedValue}</Text>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
<TextInputCustom
|
<TextInputCustom
|
||||||
label="Nama"
|
label="Nama"
|
||||||
placeholder="Nama"
|
placeholder="Nama"
|
||||||
value={nama}
|
value={data.nama}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setNama(text);
|
setData({ ...data, nama: text });
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<TextInputCustom
|
<TextInputCustom
|
||||||
label="Email"
|
label="Email"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
value={email}
|
value={data.email}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setEmail(text);
|
setData({ ...data, email: text });
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<TextInputCustom
|
<TextInputCustom
|
||||||
label="Alamat"
|
label="Alamat"
|
||||||
placeholder="Alamat"
|
placeholder="Alamat"
|
||||||
value={alamat}
|
value={data.alamat}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setAlamat(text);
|
setData({ ...data, alamat: text });
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|||||||
168
app/(application)/profile/[id]/take-picture.tsx
Normal file
168
app/(application)/profile/[id]/take-picture.tsx
Normal 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",
|
||||||
|
},
|
||||||
|
});
|
||||||
190
app/(application)/profile/[id]/take-picture2.tsx
Normal file
190
app/(application)/profile/[id]/take-picture2.tsx
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,11 +1,41 @@
|
|||||||
import { Text, View } from "react-native";
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
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 UpdatePhotoBackground() {
|
export default function UpdateBackgroundProfile() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
return (
|
return (
|
||||||
<View>
|
<ViewWrapper
|
||||||
<Text>Update Photo Background {id}</Text>
|
bottomBarComponent={
|
||||||
</View>
|
<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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,41 @@
|
|||||||
import { Text, View } from "react-native";
|
import { BaseBox, ButtonCustom } from "@/components";
|
||||||
import { useLocalSearchParams } from "expo-router";
|
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() {
|
export default function UpdatePhotoProfile() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
return (
|
return (
|
||||||
<View>
|
<ViewWrapper
|
||||||
<Text>Update Photo Profile {id}</Text>
|
bottomBarComponent={
|
||||||
</View>
|
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ export default function ProfileLayout() {
|
|||||||
name="[id]/update-background"
|
name="[id]/update-background"
|
||||||
options={{ title: "Update Latar Belakang" }}
|
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>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
8
bun.lock
8
bun.lock
@@ -13,10 +13,12 @@
|
|||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
"expo": "53.0.13",
|
"expo": "53.0.13",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~14.1.5",
|
||||||
|
"expo-camera": "~16.1.10",
|
||||||
"expo-constants": "~17.1.6",
|
"expo-constants": "~17.1.6",
|
||||||
"expo-font": "~13.3.1",
|
"expo-font": "~13.3.1",
|
||||||
"expo-haptics": "~14.1.4",
|
"expo-haptics": "~14.1.4",
|
||||||
"expo-image": "~2.3.0",
|
"expo-image": "~2.3.0",
|
||||||
|
"expo-image-picker": "~16.1.4",
|
||||||
"expo-linking": "~7.1.5",
|
"expo-linking": "~7.1.5",
|
||||||
"expo-router": "~5.1.1",
|
"expo-router": "~5.1.1",
|
||||||
"expo-splash-screen": "~0.30.9",
|
"expo-splash-screen": "~0.30.9",
|
||||||
@@ -811,6 +813,8 @@
|
|||||||
|
|
||||||
"expo-blur": ["expo-blur@14.1.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CCLJHxN4eoAl06ESKT3CbMasJ98WsjF9ZQEJnuxtDb9ffrYbZ+g9ru84fukjNUOTtc8A8yXE5z8NgY1l0OMrmQ=="],
|
"expo-blur": ["expo-blur@14.1.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CCLJHxN4eoAl06ESKT3CbMasJ98WsjF9ZQEJnuxtDb9ffrYbZ+g9ru84fukjNUOTtc8A8yXE5z8NgY1l0OMrmQ=="],
|
||||||
|
|
||||||
|
"expo-camera": ["expo-camera@16.1.10", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-qoRJeSwPmMbuu0VfnQTC+q79Kt2SqTWColEImgithL9u0qUQcC55U89IfhZk55Hpt6f1DgKuDzUOG5oY+snSWg=="],
|
||||||
|
|
||||||
"expo-constants": ["expo-constants@17.1.6", "", { "dependencies": { "@expo/config": "~11.0.9", "@expo/env": "~1.0.5" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-q5mLvJiLtPcaZ7t2diSOlQ2AyxIO8YMVEJsEfI/ExkGj15JrflNQ7CALEW6IF/uNae/76qI/XcjEuuAyjdaCNw=="],
|
"expo-constants": ["expo-constants@17.1.6", "", { "dependencies": { "@expo/config": "~11.0.9", "@expo/env": "~1.0.5" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-q5mLvJiLtPcaZ7t2diSOlQ2AyxIO8YMVEJsEfI/ExkGj15JrflNQ7CALEW6IF/uNae/76qI/XcjEuuAyjdaCNw=="],
|
||||||
|
|
||||||
"expo-file-system": ["expo-file-system@18.1.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-SyaWg+HitScLuyEeSG9gMSDT0hIxbM9jiZjSBP9l9zMnwZjmQwsusE6+7qGiddxJzdOhTP4YGUfvEzeeS0YL3Q=="],
|
"expo-file-system": ["expo-file-system@18.1.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-SyaWg+HitScLuyEeSG9gMSDT0hIxbM9jiZjSBP9l9zMnwZjmQwsusE6+7qGiddxJzdOhTP4YGUfvEzeeS0YL3Q=="],
|
||||||
@@ -821,6 +825,10 @@
|
|||||||
|
|
||||||
"expo-image": ["expo-image@2.3.0", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-muL8OSbgCskQJsyqenKPNULWXwRm5BY2ruS6WMo/EzFyI3iXI/0mXgb2J/NXUa8xCEYxSyoGkGZFyCBvGY1ofA=="],
|
"expo-image": ["expo-image@2.3.0", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-muL8OSbgCskQJsyqenKPNULWXwRm5BY2ruS6WMo/EzFyI3iXI/0mXgb2J/NXUa8xCEYxSyoGkGZFyCBvGY1ofA=="],
|
||||||
|
|
||||||
|
"expo-image-loader": ["expo-image-loader@5.1.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-sEBx3zDQIODWbB5JwzE7ZL5FJD+DK3LVLWBVJy6VzsqIA6nDEnSFnsnWyCfCTSvbGigMATs1lgkC2nz3Jpve1Q=="],
|
||||||
|
|
||||||
|
"expo-image-picker": ["expo-image-picker@16.1.4", "", { "dependencies": { "expo-image-loader": "~5.1.0" }, "peerDependencies": { "expo": "*" } }, "sha512-bTmmxtw1AohUT+HxEBn2vYwdeOrj1CLpMXKjvi9FKSoSbpcarT4xxI0z7YyGwDGHbrJqyyic3I9TTdP2J2b4YA=="],
|
||||||
|
|
||||||
"expo-keep-awake": ["expo-keep-awake@14.1.4", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA=="],
|
"expo-keep-awake": ["expo-keep-awake@14.1.4", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA=="],
|
||||||
|
|
||||||
"expo-linking": ["expo-linking@7.1.5", "", { "dependencies": { "expo-constants": "~17.1.6", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-8g20zOpROW78bF+bLI4a3ZWj4ntLgM0rCewKycPL0jk9WGvBrBtFtwwADJgOiV1EurNp3lcquerXGlWS+SOQyA=="],
|
"expo-linking": ["expo-linking@7.1.5", "", { "dependencies": { "expo-constants": "~17.1.6", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-8g20zOpROW78bF+bLI4a3ZWj4ntLgM0rCewKycPL0jk9WGvBrBtFtwwADJgOiV1EurNp3lcquerXGlWS+SOQyA=="],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// components/Button/Button.tsx
|
// components/Button/Button.tsx
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Text, TouchableOpacity } from "react-native";
|
import { StyleProp, Text, TouchableOpacity, ViewStyle } from "react-native";
|
||||||
import buttonStyles from "./buttonCustomStyles";
|
import buttonStyles from "./buttonCustomStyles";
|
||||||
import { radiusMap } from "@/constants/radius-value";
|
import { radiusMap } from "@/constants/radius-value";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
@@ -21,6 +21,7 @@ interface ButtonProps {
|
|||||||
radius?: RadiusType; // ← bisa string enum atau number
|
radius?: RadiusType; // ← bisa string enum atau number
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
iconLeft?: React.ReactNode;
|
iconLeft?: React.ReactNode;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonCustom: React.FC<ButtonProps> = ({
|
const ButtonCustom: React.FC<ButtonProps> = ({
|
||||||
@@ -32,6 +33,7 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
|||||||
radius = "full", // default md
|
radius = "full", // default md
|
||||||
disabled = false,
|
disabled = false,
|
||||||
iconLeft,
|
iconLeft,
|
||||||
|
style,
|
||||||
}) => {
|
}) => {
|
||||||
const borderRadius =
|
const borderRadius =
|
||||||
typeof radius === "number" ? radius : radiusMap[radius ?? "md"]; // fallback ke 'md'
|
typeof radius === "number" ? radius : radiusMap[radius ?? "md"]; // fallback ke 'md'
|
||||||
@@ -44,7 +46,7 @@ const ButtonCustom: React.FC<ButtonProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.button, disabled && styles.disabled]}
|
style={[styles.button, disabled && styles.disabled, style]}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
|
|||||||
21
components/Button/ButtonUpload.tsx
Normal file
21
components/Button/ButtonUpload.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { GStyles } from "@/styles/global-styles";
|
||||||
|
import { Feather } from "@expo/vector-icons";
|
||||||
|
import React from "react";
|
||||||
|
import ButtonCustom from "./ButtonCustom";
|
||||||
|
|
||||||
|
interface ButtonUploadProps {
|
||||||
|
title?: string;
|
||||||
|
onPress: () => void;
|
||||||
|
}
|
||||||
|
export default function ButtonUpload({ onPress, title = "Upload" }: ButtonUploadProps) {
|
||||||
|
return (
|
||||||
|
<ButtonCustom
|
||||||
|
onPress={onPress}
|
||||||
|
iconLeft={<Feather name="upload" size={20} color={MainColor.black} />}
|
||||||
|
style={GStyles.buttonCentered50Percent}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</ButtonCustom>
|
||||||
|
);
|
||||||
|
}
|
||||||
0
components/Camera/CameraCustom.tsx
Normal file
0
components/Camera/CameraCustom.tsx
Normal file
6
constants/dummy-image-value.ts
Normal file
6
constants/dummy-image-value.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const DUMMY_IMAGE = {
|
||||||
|
avatar: require("@/assets/images/dummy/dummy-avatar.png"),
|
||||||
|
background: require("@/assets/images/dummy/dummy-image-background.jpg"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DUMMY_IMAGE;
|
||||||
@@ -20,10 +20,12 @@
|
|||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
"expo": "53.0.13",
|
"expo": "53.0.13",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~14.1.5",
|
||||||
|
"expo-camera": "~16.1.10",
|
||||||
"expo-constants": "~17.1.6",
|
"expo-constants": "~17.1.6",
|
||||||
"expo-font": "~13.3.1",
|
"expo-font": "~13.3.1",
|
||||||
"expo-haptics": "~14.1.4",
|
"expo-haptics": "~14.1.4",
|
||||||
"expo-image": "~2.3.0",
|
"expo-image": "~2.3.0",
|
||||||
|
"expo-image-picker": "~16.1.4",
|
||||||
"expo-linking": "~7.1.5",
|
"expo-linking": "~7.1.5",
|
||||||
"expo-router": "~5.1.1",
|
"expo-router": "~5.1.1",
|
||||||
"expo-splash-screen": "~0.30.9",
|
"expo-splash-screen": "~0.30.9",
|
||||||
|
|||||||
@@ -155,4 +155,10 @@ export const GStyles = StyleSheet.create({
|
|||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
},
|
},
|
||||||
// =============== BOTTOM BAR =============== //
|
// =============== BOTTOM BAR =============== //
|
||||||
|
|
||||||
|
// =============== BUTTON =============== //
|
||||||
|
buttonCentered50Percent: {
|
||||||
|
width: "50%",
|
||||||
|
alignSelf: "center",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,5 +13,5 @@
|
|||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".expo/types/**/*.ts",
|
".expo/types/**/*.ts",
|
||||||
"expo-env.d.ts"
|
"expo-env.d.ts"
|
||||||
, "components/Button/ButtonCustom" ]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user