diff --git a/ios/zipbap/Info.plist b/ios/zipbap/Info.plist index 0949aac..a23ea89 100644 --- a/ios/zipbap/Info.plist +++ b/ios/zipbap/Info.plist @@ -38,7 +38,7 @@ CFBundleVersion - 19 + 22 ITSAppUsesNonExemptEncryption KAKAO_APP_KEY diff --git a/src/features/feed/api/useToggleBookmarkMutation.ts b/src/features/feed/api/useToggleBookmarkMutation.ts index f93acce..f877e90 100644 --- a/src/features/feed/api/useToggleBookmarkMutation.ts +++ b/src/features/feed/api/useToggleBookmarkMutation.ts @@ -6,7 +6,6 @@ import { useUserStore } from '@shared/store'; export const useToggleBookmarkMutation = () => { const { user } = useUserStore(); const queryClient = useQueryClient(); - console.log(user?.id); return useMutation({ mutationFn: async ({ recipeId, isBookmarked }: { recipeId: string; isBookmarked: boolean }) => { diff --git a/src/features/feed/ui/FeedModalImageViewer.tsx b/src/features/feed/ui/FeedModalImageViewer.tsx index c8fa193..f6c20e9 100644 --- a/src/features/feed/ui/FeedModalImageViewer.tsx +++ b/src/features/feed/ui/FeedModalImageViewer.tsx @@ -37,7 +37,6 @@ const FeedModalImageViewer = ({ visible, onClose, item }: Props) => { step {item.turn.toString().padStart(2, '0')} - {item.description} {item.description} diff --git a/src/features/recipe/model/useRecipeCreateForm.ts b/src/features/recipe/model/useRecipeCreateForm.ts index 80680ac..c28b1ef 100644 --- a/src/features/recipe/model/useRecipeCreateForm.ts +++ b/src/features/recipe/model/useRecipeCreateForm.ts @@ -96,7 +96,6 @@ export const useRecipeCreateForm = () => { // 임시 저장 const tempSaveMutation = useMutation({ mutationFn: async (recipe: RecipeDetail) => { - console.log(recipe.id); const res = await apiInstance.put(`/recipes/${recipe.id}/temp`, recipe); return res.data; }, diff --git a/src/features/user/ui/FollowItem.tsx b/src/features/user/ui/FollowItem.tsx index 6ad7147..bb0f835 100644 --- a/src/features/user/ui/FollowItem.tsx +++ b/src/features/user/ui/FollowItem.tsx @@ -33,7 +33,6 @@ const FollowItem = ({ user, navigation }: Props) => { ); }; - console.log(isFollowing); return ( { } }, [feedDetail]); - // prefetch recipe orders image - useEffect(() => { - if (!feedDetail) return; - - const prefetchStepImages = async () => { - for (const order of feedDetail.cookingOrders) { - if (order.image) { - const cachePath = await Image.getCachePathAsync(order.image); + // prefetch image + const imagesToPrefetch = useMemo(() => { + if (!feedDetail) return []; - if (!cachePath) await Image.prefetch(order.image); - } - } - }; - - prefetchStepImages(); + return [ + feedDetail.thumbnail, + feedDetail.profileImage, + ...feedDetail.cookingOrders.map(order => order.image), + ]; }, [feedDetail]); + usePrefetchImages(imagesToPrefetch); + // Skeleton ui if (!feedDetail || !feedId || isLoading) return ; diff --git a/src/pages/recipe/ui/MyRecipe.tsx b/src/pages/recipe/ui/MyRecipe.tsx index 7088c3e..3a4dbcd 100644 --- a/src/pages/recipe/ui/MyRecipe.tsx +++ b/src/pages/recipe/ui/MyRecipe.tsx @@ -1,9 +1,9 @@ import { useQuery } from '@tanstack/react-query'; -import { Image } from 'expo-image'; -import React, { useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { View, FlatList } from 'react-native'; import { Portal } from 'react-native-portalize'; import loginVideo from '@/assets/video/emptyScreenVideo.mp4'; +import { usePrefetchImages } from '@/src/shared/lib/usePrefetchImages'; import { RecipeItemSkeleton } from '@features/recipe'; import { EmptyStateUsingVideo } from '@features/user'; import { useCategories } from '@entities/category'; @@ -37,24 +37,9 @@ const MyRecipe: React.FC = ({ navigation }) => { // recipes const recipeList: Recipe[] = useMemo(() => recipes?.result || [], [recipes]); - // prefetch image - useEffect(() => { - if (!recipeList) return; - const prefetchMyrecipeImage = async () => { - for (const recipe of recipeList) { - if (!recipe.thumbnail) return; - - const cachePath = await Image.getCachePathAsync(recipe.thumbnail); - - // cache miss - if (!cachePath) { - await Image.prefetch(recipe.thumbnail); - } - } - }; - - prefetchMyrecipeImage(); - }, [recipeList]); + // prefetch recipe thumbnail + const thumbnailUrls = useMemo(() => recipeList.map(recipe => recipe.thumbnail), [recipeList]); + usePrefetchImages(thumbnailUrls); // filtered recipes const filteredRecipes = recipeList.filter(recipe => { diff --git a/src/pages/recipe/ui/RecipeCreate.tsx b/src/pages/recipe/ui/RecipeCreate.tsx index fbdf333..62f207e 100644 --- a/src/pages/recipe/ui/RecipeCreate.tsx +++ b/src/pages/recipe/ui/RecipeCreate.tsx @@ -1,9 +1,9 @@ -import { Image } from 'expo-image'; -import React, { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { View, FlatList, TouchableOpacity } from 'react-native'; import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable'; import PlusIcon from '@/assets/img/recipe/plus-float.svg'; import loginVideo from '@/assets/video/emptyScreenVideo.mp4'; +import { usePrefetchImages } from '@/src/shared/lib/usePrefetchImages'; import { EmptyStateUsingVideo } from '@features/user'; import { useRecipeListQuery, ArticleView, DetailDeleteComponent, Recipe } from '@entities/recipe'; import { useRecipeTypeStore } from '@shared/store'; @@ -13,7 +13,7 @@ interface MainPageProps { navigation: RootNavigationProp<'Main'>; } -const RecipeCreate: React.FC = ({ navigation }) => { +const RecipeCreate = ({ navigation }: MainPageProps) => { // recipe type const { recipeType } = useRecipeTypeStore(); @@ -22,22 +22,9 @@ const RecipeCreate: React.FC = ({ navigation }) => { const recipeList = useMemo(() => (data || []) as Recipe[], [data]); const isRecipeListEmpty = recipeList.length === 0; - // prefetch image - useEffect(() => { - if (isRecipeListEmpty) return; - const perfetchImage = async () => { - for (const recipe of recipeList) { - if (!recipe.thumbnail) return; - - const cachePath = await Image.getCachePathAsync(recipe.thumbnail); - - if (!cachePath) { - await Image.prefetch(recipe.thumbnail); - } - } - }; - perfetchImage(); - }, [isRecipeListEmpty, recipeList]); + // prefetch thumbnail + const thumbnailUrls = useMemo(() => recipeList.map(recipe => recipe.thumbnail), [recipeList]); + usePrefetchImages(thumbnailUrls); // navigate const navigateToRecipeCreateForm = () => { diff --git a/src/pages/recipe/ui/RecipeDetail.tsx b/src/pages/recipe/ui/RecipeDetail.tsx index 0859d4c..8230e4e 100644 --- a/src/pages/recipe/ui/RecipeDetail.tsx +++ b/src/pages/recipe/ui/RecipeDetail.tsx @@ -1,7 +1,8 @@ import { Image } from 'expo-image'; -import React, { useEffect } from 'react'; +import React, { useMemo } from 'react'; import { Text, View, ScrollView } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { usePrefetchImages } from '@/src/shared/lib/usePrefetchImages'; import { RecipeDetailSection, RecipeStepsArticleViewType, @@ -43,22 +44,11 @@ const RecipeDetail = ({ navigation, route }: RecipeDetailProps) => { // recipe list const { data: detailRecipe, isLoading } = useRecipeDetailQuery(recipeId); - // prefetch: recipe orders image - useEffect(() => { - if (!detailRecipe) return; - - const prefetchStepImages = async () => { - for (const order of detailRecipe.cookingOrders) { - if (order.image) { - const cachePath = await Image.getCachePathAsync(order.image); - - if (!cachePath) await Image.prefetch(order.image); - } - } - }; - - prefetchStepImages(); - }, [detailRecipe]); + const thumbnailUrls = useMemo( + () => detailRecipe?.cookingOrders.map(order => order.image) || [], + [detailRecipe], + ); + usePrefetchImages(thumbnailUrls); // category const { categoryValue } = useCategories(); @@ -142,7 +132,7 @@ const RecipeDetail = ({ navigation, route }: RecipeDetailProps) => { {detailRecipe?.ingredientInfo} } - subTitle="레시피 소개" + subTitle="재료 소개" /> {/* 레시피 영상 */} {detailRecipe?.video && ( diff --git a/src/pages/user/ui/ProfileEdit.tsx b/src/pages/user/ui/ProfileEdit.tsx index eec2512..9beab8d 100644 --- a/src/pages/user/ui/ProfileEdit.tsx +++ b/src/pages/user/ui/ProfileEdit.tsx @@ -16,7 +16,6 @@ import { const ProfileEdit = ({ navigation, route }: ProfileEditProps) => { const { userId } = route.params; - console.log(userId); const { user } = useUserStore(); diff --git a/src/pages/user/ui/Secession.tsx b/src/pages/user/ui/Secession.tsx index 3341de7..cf48c70 100644 --- a/src/pages/user/ui/Secession.tsx +++ b/src/pages/user/ui/Secession.tsx @@ -9,7 +9,6 @@ import { defaultShadow, ModalHeader } from '@shared/ui'; const Secession = ({ navigation, route }: SecessionProps) => { const { userId } = route.params; - console.log(userId); const insets = useSafeAreaInsets(); const [confirmText, setConfirmText] = useState(''); const [isChecked, setIsChecked] = useState(false); diff --git a/src/pages/user/ui/UserSettingBottomSheet.tsx b/src/pages/user/ui/UserSettingBottomSheet.tsx index de6b3a2..c9aa0b2 100644 --- a/src/pages/user/ui/UserSettingBottomSheet.tsx +++ b/src/pages/user/ui/UserSettingBottomSheet.tsx @@ -31,7 +31,6 @@ const UserSettingBottomSheet = ({ const handleCatagorySave = () => { // 카테고리 저장 로직 bottomSheetClose(); - console.log(2); }; console.log(pushAll); diff --git a/src/shared/lib/usePrefetchImages.ts b/src/shared/lib/usePrefetchImages.ts new file mode 100644 index 0000000..0fc2c98 --- /dev/null +++ b/src/shared/lib/usePrefetchImages.ts @@ -0,0 +1,34 @@ +import { Image } from 'expo-image'; +import { useEffect, useMemo } from 'react'; + +type PrefetchableImages = string | undefined | null; + +const CHUNK_SIZE = 10; + +export const usePrefetchImages = (imageUrls: readonly PrefetchableImages[]) => { + const stringifiedUrls = useMemo(() => JSON.stringify(imageUrls), [imageUrls]); + + useEffect(() => { + const urls: PrefetchableImages[] = JSON.parse(stringifiedUrls); + const prefetchUrls = urls.filter((url): url is string => !!url); + + if (prefetchUrls.length === 0) return; + + const executePrefetchInChunks = async () => { + for (let i = 0; i < prefetchUrls.length; i += CHUNK_SIZE) { + const chunk = prefetchUrls.slice(i, i + CHUNK_SIZE); + + const prefetchTasks = chunk.map(async url => { + const cachePath = await Image.getCachePathAsync(url); + if (!cachePath) { + return Image.prefetch(url); + } + }); + + await Promise.allSettled(prefetchTasks); + } + }; + + executePrefetchInChunks(); + }, [stringifiedUrls]); +};