Skip to content

feat: 필터 체험하기 기능 추가 #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/react-native/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ android {
applicationId "com.spotclient"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2
versionName "1.0.1"
versionCode 3
versionName "1.1.0"
manifestPlaceholders=[KAKAO_APP_KEY:KAKAO_APP_KEY]
resValue "string", "KAKAO_APP_KEY", KAKAO_APP_KEY
resValue "string", "CODEPUSH_DEPLOYMENT_KEY",CODEPUSH_DEPLOYMENT_KEY
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/codePush.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ appcenter codepush release \
-a rlfehd2013/SPOT \
-c ./CodePush \
-d Production \
-t 1.0.1 \
-t 1.1.0 \
``

rm -rf CodePush
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export default function useDetailQuery({
const result = await authAxios.get<ServerResponse<DetailResponse>>(
`/api/spot/${id}?workId=${workId}`,
);
const originOverview = result.data.result.overview;
result.data.result.overview = originOverview.replace('<br>', '');
result.data.result.overview = originOverview.replace('</br>', '');
return result.data.result;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface MySpotResponse {
region: Region;
city: City;
workId: number;
workName: string;
posterUrl: string;
isLiked: boolean;
likeCount: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface QuizzesResponse {
region: Region;
city: City;
imageUrl: string;
filterImage: string;
}

interface UseQuizzesQueryParams {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions packages/react-native/src/components/camera/CheckPhoto.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { forwardRef } from 'react';
import { PhotoFile } from 'react-native-vision-camera';
import {
Dimensions,
Image,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import { Font } from 'design-system';
import ViewShot from 'react-native-view-shot';
import DownloadIcon from '@/assets/DownloadIcon';

const { width } = Dimensions.get('window');

interface CheckPhotoProps {
filterUrl: string;
savePhoto: () => Promise<void>;
resetPhoto: () => void;
photo: PhotoFile;
}

export default forwardRef<ViewShot, CheckPhotoProps>(function CheckPhoto(
{ filterUrl, savePhoto, resetPhoto, photo },
captureRef,
) {
return (
<>
<View style={{ width, height: (4 * width) / 3, position: 'relative' }}>
<ViewShot
ref={captureRef}
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Image
source={{ uri: `file://${photo.path}` }}
style={StyleSheet.absoluteFill}
/>
<Image
source={
typeof filterUrl === 'string' ? { uri: filterUrl } : filterUrl
}
style={{
width,
height: (4 * width) / 3,
}}
/>
</ViewShot>
</View>
<View className="absolute bottom-14 flex-row items-center justify-between w-full px-8">
<View className="w-20" />
<View className="w-20">
<TouchableOpacity
onPress={savePhoto}
className="items-center justify-center rounded-full bg-SPOT-white w-20 h-20"
>
<DownloadIcon width={60} height={60} />
</TouchableOpacity>
</View>
<View className="w-20">
<TouchableOpacity
onPress={resetPhoto}
className="bg-SPOT-white p-3 rounded-lg items-center justify-center"
>
<Font.Bold type="body2" color="black">
다시찍기
</Font.Bold>
</TouchableOpacity>
</View>
</View>
</>
);
});
137 changes: 137 additions & 0 deletions packages/react-native/src/components/camera/FilterCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { useRef } from 'react';
import { Image, Dimensions, TouchableOpacity } from 'react-native';
import Carousel, { ICarouselInstance } from 'react-native-reanimated-carousel';
import Animated, {
interpolate,
useAnimatedStyle,
} from 'react-native-reanimated';
import FILTER_PATHS from '@/constants/FILTER_PATHS';

const { width } = Dimensions.get('window');

const ITEM_WIDTH = width / 5; // 화면에 5개의 요소가 보이게 설정

interface FilterCarouselProps {
filterIndex: number;
takePhoto: () => Promise<void>;
handleSnap: (index: number) => void;
}

interface FilterItemProps {
item: (typeof FILTER_PATHS)[number];
animationValue: Animated.SharedValue<number>;
onPress: () => void;
}

function FilterItem({ item, animationValue, onPress }: FilterItemProps) {
const animatedStyle = useAnimatedStyle(() => {
const scale = interpolate(
animationValue.value,
[0, 1, 2, 3, 4],
[0.8, 0.8, 1.1, 0.8, 0.8],
);
const translateX = interpolate(
animationValue.value,
[0, 1, 2, 3, 4],
[0, -ITEM_WIDTH * 0.1, 0, ITEM_WIDTH * 0.1, 0],
);

return {
transform: [{ scale }, { translateX }],
};
});

return (
<Animated.View
style={[
{
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
borderRadius: 15,
},
animatedStyle,
]}
>
<TouchableOpacity onPress={onPress}>
<Image
source={item}
style={{
width: ITEM_WIDTH,
height: ITEM_WIDTH,
resizeMode: 'cover',
}}
/>
</TouchableOpacity>
</Animated.View>
);
}

// Carousel은 실제 index보다 2가 작게 시작합니다.
// 따라서 handleSnap으로 실질 인덱스와 맞출 때는 2크게 설정되어야 합니다.

function FilterCarousel({
filterIndex,
takePhoto,
handleSnap,
}: FilterCarouselProps) {
const carouselRef = useRef<ICarouselInstance>(null);
const experienceFilterLength = FILTER_PATHS.length;

const snapToIndex = (index: number) => {
const moveMent = Math.abs(filterIndex - index);
const isLoop = moveMent >= experienceFilterLength - 2;
const vector = filterIndex - index > 0 ? 1 : -1;

if (carouselRef.current) {
carouselRef.current.scrollTo({
count: isLoop
? vector * (experienceFilterLength - moveMent)
: index - filterIndex,
animated: true,
});
}
};

const handleClickImage = (index: number) => {
if (index === filterIndex) {
takePhoto();
return;
}
snapToIndex(index);
};

return (
<Carousel
ref={carouselRef}
snapEnabled
pagingEnabled
loop
width={ITEM_WIDTH}
height={ITEM_WIDTH}
style={{
width,
height: ITEM_WIDTH,
}}
defaultIndex={
filterIndex - 2 >= 0
? filterIndex - 2
: experienceFilterLength - 2 + filterIndex
}
scrollAnimationDuration={500}
data={FILTER_PATHS}
onSnapToItem={(index) => {
handleSnap((index + 2) % experienceFilterLength);
}}
renderItem={({ item, animationValue, index }) => (
<FilterItem
item={item}
animationValue={animationValue}
onPress={() => handleClickImage(index)}
/>
)}
/>
);
}

export default FilterCarousel;
77 changes: 77 additions & 0 deletions packages/react-native/src/components/camera/SpotCamera.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Dimensions,
Image,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import { Camera } from 'react-native-vision-camera';
import { forwardRef } from 'react';
import useCamera from '@/hooks/useCamera';
import ChangeIcon from '@/assets/ChangeIcon';

const { width } = Dimensions.get('window');
interface SpotCameraProps {
filterUrl: string;
takePhoto: () => Promise<void>;
hideButton?: boolean;
}

export default forwardRef<Camera, SpotCameraProps>(function SpotCamera(
{ filterUrl, takePhoto, hideButton },
cameraRef,
) {
const { device, hasPermission, changeCameraPosition } = useCamera();

if (!device || !hasPermission) return null;
return (
<>
<View
style={{
width,
height: (4 * width) / 3,
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Camera
ref={cameraRef}
style={StyleSheet.absoluteFill}
device={device}
isActive
photo
enableZoomGesture
audio={false}
/>
<Image
source={
typeof filterUrl === 'string' ? { uri: filterUrl } : filterUrl
}
style={{
width,
height: (4 * width) / 3,
}}
/>
</View>
{!hideButton && (
<View className="absolute items-center justify-between flex-row bottom-0 pb-16 w-full px-10 pt-10">
<TouchableOpacity
onPress={changeCameraPosition}
className="items-center justify-center w-[52px] h-[52px] rounded-full bg-SPOT-black/50"
>
<ChangeIcon />
</TouchableOpacity>
<TouchableOpacity
onPress={takePhoto}
className="items-center justify-center"
>
<View className="absolute bg-white w-[80px] h-[80px] rounded-full" />
<View className="absolute bg-white w-[72px] h-[72px] rounded-full border-[3px] border-SPOT-black" />
</TouchableOpacity>
<View className="w-[52px] h-[52px]" />
</View>
)}
</>
);
});
5 changes: 3 additions & 2 deletions packages/react-native/src/components/common/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function Default({ data, size = 260 }: CardProps) {
contentId,
id,
workId,
workName,
} = data;

const [cardLike, setCardLike] = useState<CardLike>({
Expand Down Expand Up @@ -107,8 +108,8 @@ function Default({ data, size = 260 }: CardProps) {
{name}
</Font.Bold>
<View className="mt-1">
<Font type="body3" color="white">
{getDisplayRegion({ locationEnum: region, cityEnum: city })}
<Font type="body3" color="white" ellipsis>
{`${getDisplayRegion({ locationEnum: region, cityEnum: city })} • ${workName}`}
</Font>
</View>
</View>
Expand Down
Loading
Loading