From f57c8ec91b49a4efd979dd43529430e44cc41769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Garrido?= <57329376+RGarrido03@users.noreply.github.com> Date: Sat, 24 May 2025 15:53:23 +0100 Subject: [PATCH 01/13] move shopping list skeleton outside Co-authored-by: Violeta Ramos --- app/(tabs)/shopping-list.tsx | 34 ++-------------- components/skeletons/ShoppingListSkeleton.tsx | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 31 deletions(-) create mode 100644 components/skeletons/ShoppingListSkeleton.tsx diff --git a/app/(tabs)/shopping-list.tsx b/app/(tabs)/shopping-list.tsx index 7c688a7..753fb8d 100644 --- a/app/(tabs)/shopping-list.tsx +++ b/app/(tabs)/shopping-list.tsx @@ -1,5 +1,5 @@ import { useCallback, useMemo, useRef, useState } from "react"; -import { TouchableOpacity, useColorScheme, View } from "react-native"; +import { TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { EllipsisVertical, @@ -21,7 +21,6 @@ import { useLazyQuery, useMutation, useQuery } from "@apollo/client"; import { ItemType, Status } from "@/graphql/graphql"; import { Button } from "@/components/Button"; import NoShoppingCard from "@/assets/images/lucide/shopping-cart.svg"; -import { Skeleton } from "moti/skeleton"; import { gql } from "@/graphql/gql"; import { useAuth } from "@/hooks/useAuth"; import { ThemedIcon } from "@/components/ThemedIcon"; @@ -32,6 +31,7 @@ import { BottomSheetModal } from "@gorhom/bottom-sheet"; import LottieView from "lottie-react-native"; import SparkleIcon from "@/assets/lottie/sparkle.json"; import { RawBottomSheet } from "@/components/RawBottomSheet"; +import ShoppingListSkeleton from "@/components/skeletons/ShoppingListSkeleton"; export const GET_PRODUCTS_LIST = gql(` query CurrentSupermarketListWithProducts($userId: String!) { @@ -112,7 +112,6 @@ const ListScreen = () => { const router = useRouter(); const itemBackgroundColor = useThemeColor("background"); const iconBackgroundColor = useThemeColor("text"); - const theme = useColorScheme(); const { toast, toastOnError } = useToast(); const rawBottomSheetRef = useRef(null); const voiceBottomSheetRef = useRef(null); @@ -283,34 +282,7 @@ const ListScreen = () => { ]); if (loading) { - return ( - - - {[1, 2, 3, 4, 5].map((category) => ( - - - - - {[1, 4, 8].map((product) => ( - - ))} - - - ))} - - - ); + return ; } if (error) diff --git a/components/skeletons/ShoppingListSkeleton.tsx b/components/skeletons/ShoppingListSkeleton.tsx new file mode 100644 index 0000000..ea07f6b --- /dev/null +++ b/components/skeletons/ShoppingListSkeleton.tsx @@ -0,0 +1,39 @@ +import { useColorScheme, View } from "react-native"; +import { Skeleton } from "moti/skeleton"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +const ShoppingListSkeleton = () => { + const insets = useSafeAreaInsets(); + const theme = useColorScheme(); + + return ( + + + {[1, 2, 3, 4, 5].map((category) => ( + + + + + {[1, 4, 8].map((product) => ( + + ))} + + + ))} + + + ); +}; + +export default ShoppingListSkeleton; From 5884c22848dec126510c74fd6bd79bd3c793ef24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Garrido?= <57329376+RGarrido03@users.noreply.github.com> Date: Sat, 24 May 2025 16:31:33 +0100 Subject: [PATCH 02/13] Move recommendations button to new file Co-authored-by: Violeta Ramos --- app/(tabs)/shopping-list.tsx | 106 +++--------------------- components/Header.tsx | 32 ++++--- components/RecommendationButton.tsx | 124 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 107 deletions(-) create mode 100644 components/RecommendationButton.tsx diff --git a/app/(tabs)/shopping-list.tsx b/app/(tabs)/shopping-list.tsx index 753fb8d..5b384c7 100644 --- a/app/(tabs)/shopping-list.tsx +++ b/app/(tabs)/shopping-list.tsx @@ -1,24 +1,16 @@ import { useCallback, useMemo, useRef, useState } from "react"; import { TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { - EllipsisVertical, - Mic, - NotebookPen, - Pencil, - Search, - Sparkles, -} from "lucide-react-native"; +import { EllipsisVertical, Mic, NotebookPen, Pencil, Search, Sparkles } from "lucide-react-native"; import ListTitle from "@/components/ui/list/listTitle"; import List from "@/components/ui/list/list"; import { useFocusEffect, useRouter } from "expo-router"; import { ThemedText } from "@/components/ThemedText"; import Header from "@/components/Header"; import themeConfig from "@/tailwind.config"; -import { useBackground } from "@/hooks/useBackground"; import { useThemeColor } from "@/hooks/useThemeColor"; -import { useLazyQuery, useMutation, useQuery } from "@apollo/client"; -import { ItemType, Status } from "@/graphql/graphql"; +import { useMutation, useQuery } from "@apollo/client"; +import { Status } from "@/graphql/graphql"; import { Button } from "@/components/Button"; import NoShoppingCard from "@/assets/images/lucide/shopping-cart.svg"; import { gql } from "@/graphql/gql"; @@ -28,10 +20,9 @@ import { useToast } from "@/hooks/useToast"; import { LegendList } from "@legendapp/list"; import { VoiceBottomSheet } from "@/components/VoiceBottomSheet"; import { BottomSheetModal } from "@gorhom/bottom-sheet"; -import LottieView from "lottie-react-native"; -import SparkleIcon from "@/assets/lottie/sparkle.json"; import { RawBottomSheet } from "@/components/RawBottomSheet"; import ShoppingListSkeleton from "@/components/skeletons/ShoppingListSkeleton"; +import RecommendationButton from "@/components/RecommendationButton"; export const GET_PRODUCTS_LIST = gql(` query CurrentSupermarketListWithProducts($userId: String!) { @@ -76,24 +67,6 @@ export const CREATE_LIST = gql(` } `); -const GET_RECOMMENDATIONS = gql(` - query GetRecommendations($userId: String!) { - rawListByUserAndAi(userId: $userId, k: 5) { - ean - name - genericName - quantity - images - brandName - blurhash - supermarkets { - price - id - } - } - } -`); - const UPSERT_PRODUCT_FROM_LIST = gql(` mutation UpsertProductFromList($models: [ListProductInput!]!) { upsertProductFromList(models: $models) { @@ -108,14 +81,12 @@ const UPSERT_PRODUCT_FROM_LIST = gql(` const ListScreen = () => { const { id } = useAuth(); const insets = useSafeAreaInsets(); - const { setState: setBackground } = useBackground(); const router = useRouter(); const itemBackgroundColor = useThemeColor("background"); const iconBackgroundColor = useThemeColor("text"); - const { toast, toastOnError } = useToast(); + const { toastOnError } = useToast(); const rawBottomSheetRef = useRef(null); const voiceBottomSheetRef = useRef(null); - const lottieRef = useRef(null); const { error, data, loading, refetch } = useQuery(GET_PRODUCTS_LIST, { variables: { @@ -132,12 +103,6 @@ const ListScreen = () => { ], }); - const [getRecommendations] = useLazyQuery(GET_RECOMMENDATIONS, { - variables: { - userId: id!, - }, - }); - const [upsertProductFromList] = useMutation(UPSERT_PRODUCT_FROM_LIST, { refetchQueries: [ { @@ -231,56 +196,6 @@ const ListScreen = () => { [data?.user?.actualList?._id, upsertProductFromList, toastOnError], ); - const handleRecommendationPress = useCallback(async () => { - if (!data?.user?.actualList?._id) return; - - setBackground(true); - lottieRef.current?.play(); - - const result = await getRecommendations(); - if (result.error) { - toast({ - title: "Oops! Something went wrong", - text: result.error.message, - }); - } - if (!result.data?.rawListByUserAndAi) { - toast({ - title: "Oops! Something went wrong", - text: "No recommendations found", - }); - } - - await upsertProductFromList({ - onError: toastOnError, - variables: { - models: - result.data?.rawListByUserAndAi.map((product) => ({ - id_composite: { - listId: data?.user?.actualList?._id ?? "", - productEan: product.ean, - }, - quantity: 1, - supermarket_id: product?.supermarkets?.reduce( - (min, current) => (current.price < min.price ? current : min), - product?.supermarkets?.[0], - ).id, - type: ItemType.Ai, - })) ?? [], - }, - }); - setBackground(false); - lottieRef.current?.pause(); - lottieRef.current?.reset(); - }, [ - data?.user?.actualList?._id, - getRecommendations, - setBackground, - toast, - toastOnError, - upsertProductFromList, - ]); - if (loading) { return ; } @@ -340,11 +255,12 @@ const ListScreen = () => { onPress: () => rawBottomSheetRef.current?.present(), }, { icon: Mic, onPress: () => voiceBottomSheetRef.current?.present() }, - { - icon: SparkleIcon, - onPress: handleRecommendationPress, - iconRef: lottieRef, - }, + () => ( + + ), ]} /> diff --git a/components/Header.tsx b/components/Header.tsx index f246c5b..08fcd30 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -5,7 +5,7 @@ import { ArrowLeft, LucideIcon } from "lucide-react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useNavigation } from "expo-router"; import LottieView, { AnimationObject } from "lottie-react-native"; -import React from "react"; +import React, { FC } from "react"; import { SvgProps } from "react-native-svg"; interface IconProps { @@ -25,10 +25,14 @@ export type HeaderAction = { interface HeaderProps { title?: string; leading?: HeaderAction | true; - trailing?: HeaderAction[]; + trailing?: (HeaderAction | FC)[]; enableInsets?: boolean; } +const isReactFC = (item: any): item is React.FC => { + return typeof item === "function"; +}; + const Header = ({ title, leading, trailing, enableInsets }: HeaderProps) => { const insets = useSafeAreaInsets(); const navigation = useNavigation(); @@ -77,16 +81,20 @@ const Header = ({ title, leading, trailing, enableInsets }: HeaderProps) => { )} {trailing && trailing.length > 0 && ( - {trailing?.map((action, index) => ( -