From 1f9e20152ab6529c08e8b1080b32220dda4617b8 Mon Sep 17 00:00:00 2001 From: mrevanzak Date: Sat, 25 Nov 2023 12:03:39 +0700 Subject: [PATCH] feat(expo/listing): image upload loading --- apps/expo/package.json | 5 +- apps/expo/src/app/(app)/(tabs)/listing.tsx | 76 +++++++++-------- apps/expo/src/utils/supabase.ts | 22 +++++ package.json | 1 + pnpm-lock.yaml | 94 ++++++++++++++++++++++ 5 files changed, 161 insertions(+), 37 deletions(-) diff --git a/apps/expo/package.json b/apps/expo/package.json index 3f69ee1..dbdde35 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -5,7 +5,7 @@ "main": "index.tsx", "scripts": { "clean": "git clean -xdf .expo .turbo node_modules", - "dev": "expo start --android", + "dev": "expo start", "dev:android": "expo start --android", "dev:ios": "expo start --ios", "lint": "eslint .", @@ -26,7 +26,6 @@ "@trpc/client": "next", "@trpc/react-query": "next", "@trpc/server": "next", - "base64-arraybuffer": "^1.0.2", "expo": "^49.0.13", "expo-auth-session": "~5.0.2", "expo-clipboard": "~4.3.1", @@ -50,6 +49,8 @@ "react-native-screens": "~3.22.1", "react-native-ui-lib": "^7.9.1", "superjson": "1.13.1", + "tus-js-client": "^3.1.1", + "use-tus": "^0.7.3", "zod": "^3.21.4", "zustand": "^4.4.6" }, diff --git a/apps/expo/src/app/(app)/(tabs)/listing.tsx b/apps/expo/src/app/(app)/(tabs)/listing.tsx index cefff0d..ee7d901 100644 --- a/apps/expo/src/app/(app)/(tabs)/listing.tsx +++ b/apps/expo/src/app/(app)/(tabs)/listing.tsx @@ -1,7 +1,8 @@ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { ActivityIndicator, Alert } from "react-native"; import { AnimatedImage, + AnimatedScanner, BorderRadiuses, Button, KeyboardAwareScrollView, @@ -9,19 +10,18 @@ import { View, } from "react-native-ui-lib"; import Constants from "expo-constants"; -import * as FileSystem from "expo-file-system"; import * as ImagePicker from "expo-image-picker"; import { useRouter } from "expo-router"; import Input from "@/components/forms/Input"; import Picker from "@/components/forms/Picker"; import { api } from "@/utils/api"; import colors from "@/utils/colors"; -import { storageClient } from "@/utils/supabase"; +import { storageClient, uploadOptions } from "@/utils/supabase"; import { useUser } from "@clerk/clerk-expo"; import { MaterialCommunityIcons } from "@expo/vector-icons"; import { zodResolver } from "@hookform/resolvers/zod"; -import { decode } from "base64-arraybuffer"; import { FormProvider, useForm } from "react-hook-form"; +import { useTus } from "use-tus"; import { z } from "zod"; const schema = z.object({ @@ -34,28 +34,30 @@ const schema = z.object({ export default function UploadProductScreen() { const router = useRouter(); - const [image, setImage] = useState(); - const [uploading, setUploading] = useState(false); + const [image, setImage] = useState(); + const [uploadProggres, setUploadProgress] = useState(0); + const [isUploading, setIsUploading] = useState(false); const { user } = useUser(); const { data } = api.category.getCategories.useQuery({ partial: true }); - const { mutate } = api.product.addProduct.useMutation({}); + const { mutate } = api.product.addProduct.useMutation(); + const { setUpload } = useTus({ autoStart: true }); const methods = useForm>({ resolver: zodResolver(schema), }); const { handleSubmit, reset } = methods; const onSubmit = handleSubmit(async (data) => { - setUploading(true); + setIsUploading(true); const filePath = `${user?.id}/${data.name}.png`; - await onUpload(filePath); - const imgUrl = storageClient.from("products").getPublicUrl(filePath, { - transform: { - quality: 50, - }, - }); + const { error } = await onUpload(filePath); + if (error) { + Alert.alert("Gagal mengupload gambar"); + return; + } + const imgUrl = storageClient.from("products").getPublicUrl(filePath); mutate( { ...data, @@ -63,42 +65,45 @@ export default function UploadProductScreen() { }, { onSuccess: () => { - setUploading(false); reset(); setImage(undefined); + setIsUploading(false); router.push("/home"); }, }, ); }); - const onUpload = async (filePath: string) => { - if (!image) return; - const base64 = await FileSystem.readAsStringAsync(image, { - encoding: "base64", - }); - - const { error } = await storageClient - .from("products") - .upload(filePath, decode(base64), { - contentType: "image/png", + const onUpload = useCallback( + (filePath: string) => { + return new Promise<{ error?: Error }>((resolve) => { + setUpload(image, { + ...uploadOptions("products", filePath), + onProgress(bytesSent, bytesTotal) { + setUploadProgress((bytesSent / bytesTotal) * 100); + }, + onSuccess() { + resolve({ error: undefined }); + }, + onError(error) { + setIsUploading(false); + resolve({ error }); + }, + }); }); - if (error) { - console.log(error); - return; - } - }; + }, + [image, setUpload], + ); const onSelectImage = async () => { const options: ImagePicker.ImagePickerOptions = { mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, - quality: 1, }; const result = await ImagePicker.launchImageLibraryAsync(options); if (!result.canceled) { - setImage(result.assets[0]?.uri); + setImage(result); } }; @@ -133,10 +138,10 @@ export default function UploadProductScreen() { Upload Gambar - {image ? ( + {image?.assets ? ( <> } /> + {isUploading && }