From ccaaab57f8ee955ff45cc3c6a381f9c4a77f00e4 Mon Sep 17 00:00:00 2001 From: Jihyeong Date: Sun, 13 Oct 2024 23:54:34 +0900 Subject: [PATCH] =?UTF-8?q?Feat/#60=20=EB=A6=AC=EB=B7=B0=20=ED=9A=8C?= =?UTF-8?q?=EC=9D=98=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: tabBar가 보이는 현상을 수정합니다. * refactor: 지우기 로직을 제거합니다. * refactor: 배경을 그라데이션으로 변경합니다. * refactor: 배경을 그라데이션으로 변경합니다. * fix: 화면 밖으로 벗어나는 현상을 수정합니다. --- app/(app)/project/detail.tsx | 13 ++-- app/(app)/project/review/create.tsx | 76 +++++++++++++------ app/(app)/project/review/select.tsx | 6 +- .../project/ProjectDetail/index.tsx | 13 ++-- src/components/project/ProjectItem/index.tsx | 74 +++++++++--------- .../review/CreateReviewList/index.tsx | 37 ++++----- src/constants/service.ts | 3 + 7 files changed, 119 insertions(+), 103 deletions(-) diff --git a/app/(app)/project/detail.tsx b/app/(app)/project/detail.tsx index bd05805..e13d66e 100644 --- a/app/(app)/project/detail.tsx +++ b/app/(app)/project/detail.tsx @@ -1,4 +1,4 @@ -import { Link, useLocalSearchParams } from 'expo-router'; +import { useLocalSearchParams, useRouter } from 'expo-router'; import { Suspense } from 'react'; import { MOCK_PROJECT_DETAIL } from '@/__mock__/project'; @@ -16,6 +16,7 @@ type InnerProps = { function Inner({ id }: InnerProps) { const data = MOCK_PROJECT_DETAIL; + const router = useRouter(); return ( @@ -24,13 +25,9 @@ function Inner({ id }: InnerProps) { backgroundColor={color.Background.Alternative} title={data.name} left={ - - - - - + router.replace({ pathname: PROJECT_URLS.MAIN })}> + + } right={ diff --git a/app/(app)/project/review/create.tsx b/app/(app)/project/review/create.tsx index a763569..5f1c202 100644 --- a/app/(app)/project/review/create.tsx +++ b/app/(app)/project/review/create.tsx @@ -1,8 +1,14 @@ import styled from '@emotion/native'; import dayjs from 'dayjs'; +import { LinearGradient } from 'expo-linear-gradient'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { Suspense, useCallback, useState } from 'react'; import { View } from 'react-native'; +import Animated, { + useAnimatedScrollHandler, + useAnimatedStyle, + useSharedValue, +} from 'react-native-reanimated'; import type { QuestionType } from '@/apis/questionnaire/type'; import SolidButton from '@/components/common/button/SolidButton'; @@ -11,17 +17,18 @@ import CustomLayout from '@/components/common/custom-layout'; import ConfirmModal from '@/components/common/modal/ConfirmModal'; import Typography from '@/components/common/typography'; import CreateReviewList from '@/components/review/CreateReviewList'; -import { REVIEW_NAVIGATIONS, REVIEW_URLS } from '@/constants'; +import { COMPONENT_SIZE, REVIEW_NAVIGATIONS, REVIEW_URLS } from '@/constants'; import type { EngCategoryType } from '@/enums/categoryEnum'; import useGetCategory from '@/hooks/queries/useGetCategory'; import { color } from '@/styles/theme'; -import { distributeItems } from '@/utils'; +import { distributeItems, getSize } from '@/utils'; type WrapperProps = { id: string; categories: EngCategoryType[]; }; +const GRADIENT_COLRORS = ['#FFE58E', '#AAC5FF']; const CREATE_REVIEW_LENGTH = 20; function assignQuestionsToCategories(categoriesData: QuestionType[]): QuestionType[] { @@ -59,6 +66,7 @@ function Create() { } function Wrapper({ id, categories }: WrapperProps) { + const router = useRouter(); const { data: { allQuestions }, } = useGetCategory(categories); @@ -69,23 +77,6 @@ function Wrapper({ id, categories }: WrapperProps) { assignQuestionsToCategories(allQuestions) ); - const removeQuestion = useCallback( - (categoryType: EngCategoryType, questionId: number) => { - const updatedQuestions = selectedQuestions.map((category) => { - if (category.categoryType === categoryType) { - return { - ...category, - questions: category.questions.filter((question) => question.questionId !== questionId), - }; - } - return category; - }); - - setSelectedQuestions(updatedQuestions); - }, - [selectedQuestions] - ); - const getRandomNewQuestion = useCallback( (categoryType: EngCategoryType, questionId: number) => { const selectedCategory = selectedQuestions.find( @@ -129,8 +120,25 @@ function Wrapper({ id, categories }: WrapperProps) { }, [selectedQuestions, allQuestions] ); + const scrollX = useSharedValue(0); - const router = useRouter(); + // 스크롤 이벤트 핸들러로 scrollX 값을 업데이트 + const scrollHandler = useAnimatedScrollHandler({ + onScroll: (event) => { + scrollX.value = event.contentOffset.x; + }, + }); + + // 그라데이션 배경을 스크롤에 따라 움직이도록 스타일 정의 + const animatedGradientStyle = useAnimatedStyle(() => { + return { + transform: [ + { + translateX: scrollX.value * -0.2, // 배경 이동을 더 부드럽게 조정 + }, + ], + }; + }); return ( @@ -170,10 +178,26 @@ function Wrapper({ id, categories }: WrapperProps) { + + + + @@ -192,15 +216,19 @@ const S = { ListWrapper: styled.View` flex-grow: 1; justify-content: center; - background-color: ${({ theme }) => theme.color.Background.Alternative}; `, ButtonWrapper: styled.View` padding: 12px 20px 52px; - background-color: ${({ theme }) => theme.color.Background.Alternative}; `, GuideTextWrapper: styled.View` padding: 20px; - background-color: ${({ theme }) => theme.color.Background.Normal}; + `, + GradientBackground: styled(Animated.View)` + position: absolute; + top: 1px; + z-index: -1; + width: ${getSize.deviceWidth * 2}px; + height: ${getSize.deviceHeight - 55 - COMPONENT_SIZE.STATUSBAR}px; `, }; diff --git a/app/(app)/project/review/select.tsx b/app/(app)/project/review/select.tsx index 651fd4f..ad6a1b8 100644 --- a/app/(app)/project/review/select.tsx +++ b/app/(app)/project/review/select.tsx @@ -51,11 +51,7 @@ function Select() { title='나의 설문지' left={ - router.canGoBack() - ? router.back() - : router.replace({ pathname: PROJECT_URLS.DETAIL, params: { id } }) - }> + onPress={() => router.replace({ pathname: PROJECT_URLS.DETAIL, params: { id } })}> } diff --git a/src/components/project/ProjectDetail/index.tsx b/src/components/project/ProjectDetail/index.tsx index 0435120..8cb38e4 100644 --- a/src/components/project/ProjectDetail/index.tsx +++ b/src/components/project/ProjectDetail/index.tsx @@ -1,5 +1,5 @@ import { AntDesign } from '@expo/vector-icons'; -import { Link } from 'expo-router'; +import { useRouter } from 'expo-router'; import { ScrollView } from 'react-native'; import SolidButton from '@/components/common/button/SolidButton'; @@ -35,6 +35,7 @@ type Props = { }; function ProjectDetail({ id, data }: Props) { + const router = useRouter(); return ( - - 설문지 만들기 - + router.navigate({ pathname: REVIEW_URLS.SELECT, params: { id } })} + full> + 설문지 만들기 + ); diff --git a/src/components/project/ProjectItem/index.tsx b/src/components/project/ProjectItem/index.tsx index c19d668..1dec490 100644 --- a/src/components/project/ProjectItem/index.tsx +++ b/src/components/project/ProjectItem/index.tsx @@ -1,4 +1,4 @@ -import { Link } from 'expo-router'; +import { useRouter } from 'expo-router'; import SlideBar from '@/components/common/slide-bar'; import Typography from '@/components/common/typography'; @@ -10,43 +10,43 @@ import { color } from '@/styles/theme'; import * as S from './style'; function ProjectItem({ name, member_num, profile, review_count, id }: ProjectItemType) { + const router = useRouter(); return ( - - - - - - 프로젝트 - - - - - {name} - - - {review_count} / {member_num} - - - - - - - + + router.replace({ + pathname: PROJECT_URLS.DETAIL, + params: { id }, + }) + }> + + + + 프로젝트 + + + + + {name} + + + {review_count} / {member_num} + + + + + + ); } diff --git a/src/components/review/CreateReviewList/index.tsx b/src/components/review/CreateReviewList/index.tsx index 634bb94..df29a05 100644 --- a/src/components/review/CreateReviewList/index.tsx +++ b/src/components/review/CreateReviewList/index.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/native'; -import { Ionicons } from '@expo/vector-icons'; -import { ScrollView } from 'react-native'; +import type { ScrollHandlerProcessed } from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import type { QuestionnaireType, QuestionType } from '@/apis/questionnaire/type'; import ResetIcon from '@/components/common/icon/reset-icon'; @@ -17,28 +17,20 @@ import { color } from '@/styles/theme'; import { getSize } from '@/utils'; type Props = { + scrollHandler: ScrollHandlerProcessed>; selectedQuestions: QuestionType[]; - removeQuestion: (categoryType: EngCategoryType, id: number) => void; getRandomNewQuestion: (categoryType: EngCategoryType, questionId: number) => void; }; type ReviewItemProps = { onResetClick: () => void; - onDeleteClick: () => void; listLength: number; categoryType: EngCategoryType; question: QuestionnaireType; index: number; }; -function ReviewItem({ - onResetClick, - onDeleteClick, - question, - categoryType, - listLength, - index, -}: ReviewItemProps) { +function ReviewItem({ onResetClick, question, categoryType, listLength, index }: ReviewItemProps) { return ( @@ -60,13 +52,6 @@ function ReviewItem({ fill={color.Label.Alternative} /> - - - @@ -87,7 +72,7 @@ function ReviewItem({ ); } -function CreateReviewList({ selectedQuestions, removeQuestion, getRandomNewQuestion }: Props) { +function CreateReviewList({ scrollHandler, selectedQuestions, getRandomNewQuestion }: Props) { const list = selectedQuestions.flatMap((category) => category.questions.map((question) => ({ categoryType: category.categoryType, @@ -97,17 +82,23 @@ function CreateReviewList({ selectedQuestions, removeQuestion, getRandomNewQuest return ( - i * (getSize.screenWidth - 20) + )} + scrollEventThrottle={16} horizontal> {list.map(({ categoryType, question }, index) => ( getRandomNewQuestion(categoryType, question.questionId)} - onDeleteClick={() => removeQuestion(categoryType, question.questionId)} key={question.questionId} listLength={list.length} categoryType={categoryType} @@ -115,7 +106,7 @@ function CreateReviewList({ selectedQuestions, removeQuestion, getRandomNewQuest index={index + 1} /> ))} - + ); } diff --git a/src/constants/service.ts b/src/constants/service.ts index 9dc804a..1b80df2 100644 --- a/src/constants/service.ts +++ b/src/constants/service.ts @@ -1,3 +1,5 @@ +import { StatusBar } from 'react-native'; + export const SCREEN_SIZE = { WEB_WIDTH: 375, WEB_HEIGHT: 812, @@ -6,4 +8,5 @@ export const SCREEN_SIZE = { export const COMPONENT_SIZE = { HEADER_NAV: 64, BOTTOM_NAV: 86, + STATUSBAR: (StatusBar.currentHeight = 0), } as const;