image picker fix, working order
This commit is contained in:
parent
3aeffb3585
commit
443c48cb01
4
.env
4
.env
@ -4,3 +4,7 @@ EXPO_PUBLIC_APPWRITE_DATABASE_ID=677bd04b002bbdbe424f
|
||||
EXPO_PUBLIC_APPWRITE_USERS_COLLECTION_ID=679289990026ad25c429
|
||||
EXPO_PUBLIC_APPWRITE_CARS_COLLECTION_ID=67aa22430023baf8dfcb
|
||||
EXPO_PUBLIC_APPWRITE_BUCKET_ID=67aa2598002da47bee85
|
||||
|
||||
|
||||
EXPO_PUBLIC_WORLDPAY_API_URL=https://api.worldpay.com/v1
|
||||
EXPO_PUBLIC_WORLDPAY_KEY=your_worldpay_key_here
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
import { View, Text, SafeAreaView, ScrollView, Image, TouchableOpacity } from "react-native";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View, Text, SafeAreaView, ScrollView, Image, TouchableOpacity, Dimensions } from "react-native";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { useLocalSearchParams, router } from "expo-router";
|
||||
import icons from "@/constants/icons";
|
||||
import { getCar } from "@/lib/database";
|
||||
import { useGlobalContext } from '@/lib/globalProvider';
|
||||
import Carousel, { ICarouselInstance } from 'react-native-reanimated-carousel';
|
||||
|
||||
const CarDetails = () => {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [carData, setCarData] = useState<any>(null);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const { user } = useGlobalContext();
|
||||
const width = Dimensions.get('window').width;
|
||||
const carouselRef = useRef<ICarouselInstance>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCar = async () => {
|
||||
@ -30,24 +36,64 @@ const CarDetails = () => {
|
||||
contentContainerClassName="pb-32"
|
||||
>
|
||||
{/* Header */}
|
||||
<View className="relative">
|
||||
<View className="relative h-72">
|
||||
<Carousel
|
||||
ref={carouselRef}
|
||||
loop
|
||||
width={width}
|
||||
height={288}
|
||||
data={carData.images || []}
|
||||
scrollAnimationDuration={1000}
|
||||
onProgressChange={(_, absoluteProgress) => {
|
||||
setActiveIndex(Math.round(absoluteProgress));
|
||||
}}
|
||||
renderItem={({ item }) => (
|
||||
<Image
|
||||
source={{ uri: carData.images?.[0] }}
|
||||
className="w-full h-72"
|
||||
source={{ uri: item as string }}
|
||||
className="w-full h-full"
|
||||
resizeMode="cover"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Navigation Arrows */}
|
||||
{carData.images?.length > 1 && (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
onPress={() => carouselRef.current?.scrollTo ({ index: activeIndex - 1 })}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/50 rounded-full p-2 z-10"
|
||||
>
|
||||
<Image
|
||||
source={icons.backArrow}
|
||||
className="w-6 h-6"
|
||||
tintColor="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => carouselRef.current?.scrollTo({ index: activeIndex + 1 })}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/50 rounded-full p-2 z-10"
|
||||
>
|
||||
<Image
|
||||
source={icons.backArrow}
|
||||
className="w-6 h-6 rotate-180"
|
||||
tintColor="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Back Button */}
|
||||
<TouchableOpacity
|
||||
onPress={() => router.back()}
|
||||
className="absolute top-5 left-5 bg-white/90 p-2 rounded-full"
|
||||
className="absolute top-5 left-5 bg-white/90 p-2 rounded-full z-10"
|
||||
>
|
||||
<Image source={icons.backArrow} className="size-6" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Favorite Button */}
|
||||
<TouchableOpacity
|
||||
className="absolute top-5 right-5 bg-white/90 p-2 rounded-full"
|
||||
className="absolute top-5 right-5 bg-white/90 p-2 rounded-full z-10"
|
||||
>
|
||||
<Image source={icons.heart} className="size-6" />
|
||||
</TouchableOpacity>
|
||||
|
||||
@ -33,12 +33,13 @@ export const CarForm = () => {
|
||||
price: Number(formData.price),
|
||||
year: Number(formData.year),
|
||||
images: images,
|
||||
postedBy: user.$id
|
||||
postedBy: user.$id,
|
||||
featured: false
|
||||
});
|
||||
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error('Error creating car:', error);
|
||||
console.error('Error creating car listing:', error);
|
||||
alert('Failed to create car listing');
|
||||
}
|
||||
};
|
||||
@ -47,15 +48,11 @@ export const CarForm = () => {
|
||||
<ScrollView className="p-4">
|
||||
<Text className="text-lg font-bold mb-4">Add New Car</Text>
|
||||
|
||||
{/* Images */}
|
||||
<View className="mb-4">
|
||||
<ImagePickerComponent
|
||||
onImageSelected={(url) => setImages([...images, url])}
|
||||
multiple
|
||||
onImageSelected={setImages}
|
||||
images={images}
|
||||
/>
|
||||
<Text className="text-sm text-gray-500 mt-2">
|
||||
{images.length} images selected
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Basic Info */}
|
||||
@ -115,7 +112,7 @@ export const CarForm = () => {
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Fuel Type Picker */}
|
||||
{/* Pickers */}
|
||||
<View className="border border-gray-300 rounded-lg mb-4 px-2">
|
||||
<Picker
|
||||
selectedValue={formData.fuelType}
|
||||
@ -127,7 +124,6 @@ export const CarForm = () => {
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
{/* Transmission Picker */}
|
||||
<View className="border border-gray-300 rounded-lg mb-4 px-2">
|
||||
<Picker
|
||||
selectedValue={formData.transmission}
|
||||
@ -139,6 +135,7 @@ export const CarForm = () => {
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
{/* Submit Button */}
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit}
|
||||
className="bg-blue-500 p-4 rounded-full"
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import React from 'react';
|
||||
import { View, TouchableOpacity, Image, Text } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, TouchableOpacity, Image, Text, ScrollView } from 'react-native';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import { uploadImage } from '@/lib/appwrite';
|
||||
import icons from '@/constants/icons';
|
||||
|
||||
interface Props {
|
||||
onImageSelected: (url: string) => void;
|
||||
multiple?: boolean;
|
||||
onImageSelected: (url: string[]) => void;
|
||||
images: string[];
|
||||
}
|
||||
|
||||
export const ImagePickerComponent = ({ onImageSelected, multiple = false }: Props) => {
|
||||
export const ImagePickerComponent = ({ onImageSelected, images }: Props) => {
|
||||
const pickImage = async () => {
|
||||
if (images.length >= 5) {
|
||||
alert('Maximum 5 images allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
|
||||
if (status !== 'granted') {
|
||||
@ -19,27 +24,24 @@ export const ImagePickerComponent = ({ onImageSelected, multiple = false }: Prop
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
mediaTypes: ['images'],
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 1,
|
||||
allowsMultipleSelection: multiple,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
try {
|
||||
// Convert image to blob
|
||||
const response = await fetch(result.assets[0].uri);
|
||||
const blob = await response.blob();
|
||||
|
||||
// Upload to Appwrite
|
||||
const imageUrl = await uploadImage({
|
||||
uri: result.assets[0].uri,
|
||||
type: 'image/jpeg',
|
||||
name: 'image.jpg',
|
||||
size: blob.size
|
||||
});
|
||||
onImageSelected(imageUrl);
|
||||
onImageSelected([...images, imageUrl]);
|
||||
} catch (error) {
|
||||
console.error('Error uploading image:', error);
|
||||
alert('Failed to upload image');
|
||||
@ -47,7 +49,36 @@ export const ImagePickerComponent = ({ onImageSelected, multiple = false }: Prop
|
||||
}
|
||||
};
|
||||
|
||||
const removeImage = (index: number) => {
|
||||
const newImages = [...images];
|
||||
newImages.splice(index, 1);
|
||||
onImageSelected(newImages);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
className="mb-4"
|
||||
>
|
||||
{images.map((image, index) => (
|
||||
<View key={index} className="relative mr-2">
|
||||
<Image
|
||||
source={{ uri: image }}
|
||||
className="w-24 h-24 rounded-lg"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => removeImage(index)}
|
||||
className="absolute -top-2 -right-2 bg-red-500 rounded-full p-1"
|
||||
>
|
||||
<Text className="text-white text-xs">✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
{images.length < 5 && (
|
||||
<TouchableOpacity
|
||||
onPress={pickImage}
|
||||
className="flex items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-4"
|
||||
@ -56,8 +87,11 @@ export const ImagePickerComponent = ({ onImageSelected, multiple = false }: Prop
|
||||
source={icons.dumbell}
|
||||
className="w-8 h-8 mb-2"
|
||||
/>
|
||||
{/* change dumbell to camera */}
|
||||
<Text className="text-gray-500">Upload Image</Text>
|
||||
<Text className="text-gray-500">
|
||||
Upload Image ({images.length}/5)
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@ -23,6 +23,8 @@ export const config = {
|
||||
usersId: process.env.EXPO_PUBLIC_APPWRITE_USERS_COLLECTION_ID,
|
||||
carId: process.env.EXPO_PUBLIC_APPWRITE_CARS_COLLECTION_ID,
|
||||
bucketId: process.env.EXPO_PUBLIC_APPWRITE_BUCKET_ID,
|
||||
worldpayApiUrl: process.env.NODE_ENV === 'development' ? 'https://api.worldpay.com/v1' : process.env.EXPO_PUBLIC_WORLDPAY_API_URL,
|
||||
worldpayKey: process.env.NODE_ENV === 'development' ? 'your_worldpay_key_here' : process.env.EXPO_PUBLIC_WORLDPAY_KEY,
|
||||
}
|
||||
|
||||
export const client = new Client();
|
||||
|
||||
4
notes.md
4
notes.md
@ -1,2 +1,4 @@
|
||||
splash screen not showing
|
||||
database seed 2:32
|
||||
|
||||
## next to implement
|
||||
in-app payment, develop on separate branch
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@ -32,6 +32,7 @@
|
||||
"react-native-appwrite": "^0.6.0",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-reanimated-carousel": "^4.0.2",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
@ -15092,6 +15093,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated-carousel": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated-carousel/-/react-native-reanimated-carousel-4.0.2.tgz",
|
||||
"integrity": "sha512-vNpCfPlFoOVKHd+oB7B0luoJswp+nyz0NdJD8+LCrf25JiNQXfM22RSJhLaksBHqk3fm8R4fKWPNcfy5w7wL1Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-native": ">=0.70.3",
|
||||
"react-native-gesture-handler": ">=2.9.0",
|
||||
"react-native-reanimated": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-picker/picker": "2.9.0",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"expo": "~52.0.23",
|
||||
@ -23,6 +24,7 @@
|
||||
"expo-constants": "~17.0.3",
|
||||
"expo-font": "~13.0.2",
|
||||
"expo-haptics": "~14.0.0",
|
||||
"expo-image-picker": "~16.0.5",
|
||||
"expo-linking": "~7.0.3",
|
||||
"expo-router": "~4.0.15",
|
||||
"expo-splash-screen": "~0.29.18",
|
||||
@ -37,15 +39,14 @@
|
||||
"react-native-appwrite": "^0.6.0",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-reanimated-carousel": "^4.0.2",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"use-debounce": "^10.0.4",
|
||||
"expo-image-picker": "~16.0.5",
|
||||
"@react-native-picker/picker": "2.9.0"
|
||||
"use-debounce": "^10.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user