Skip to content

Commit 903fd92

Browse files
authored
merge: 게이미피케이션 페이지, 데이터 구성 (#28)
merge: 게이미피케이션 페이지, 데이터 구성 (#28)
2 parents 788f121 + c1dbb2f commit 903fd92

File tree

16 files changed

+395
-8
lines changed

16 files changed

+395
-8
lines changed

packages/react-native/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
88
android:maxSdkVersion="32" />
99
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
10-
10+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
1111
<application
1212
android:name=".MainApplication"
1313
android:label="@string/app_name"

packages/react-native/ios/Podfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ setup_permissions([
2828
'Camera',
2929
# 'Contacts',
3030
# 'FaceID',
31-
# 'LocationAccuracy',
32-
# 'LocationAlways',
33-
# 'LocationWhenInUse',
31+
'LocationAccuracy',
32+
'LocationAlways',
33+
'LocationWhenInUse',
3434
# 'MediaLibrary',
3535
# 'Microphone',
3636
# 'Motion',

packages/react-native/ios/Podfile.lock

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,27 @@ PODS:
960960
- Yoga
961961
- react-native-date-picker (5.0.4):
962962
- React-Core
963+
- react-native-geolocation (3.4.0):
964+
- DoubleConversion
965+
- glog
966+
- hermes-engine
967+
- RCT-Folly (= 2024.01.01.00)
968+
- RCTRequired
969+
- RCTTypeSafety
970+
- React-Codegen
971+
- React-Core
972+
- React-debug
973+
- React-Fabric
974+
- React-featureflags
975+
- React-graphics
976+
- React-ImageManager
977+
- React-NativeModulesApple
978+
- React-RCTFabric
979+
- React-rendererdebug
980+
- React-utils
981+
- ReactCommon/turbomodule/bridging
982+
- ReactCommon/turbomodule/core
983+
- Yoga
963984
- react-native-image-picker (7.1.2):
964985
- DoubleConversion
965986
- glog
@@ -1350,6 +1371,7 @@ DEPENDENCIES:
13501371
- React-Mapbuffer (from `../../../node_modules/react-native/ReactCommon`)
13511372
- "react-native-cameraroll (from `../../../node_modules/@react-native-camera-roll/camera-roll`)"
13521373
- react-native-date-picker (from `../../../node_modules/react-native-date-picker`)
1374+
- "react-native-geolocation (from `../../../node_modules/@react-native-community/geolocation`)"
13531375
- react-native-image-picker (from `../../../node_modules/react-native-image-picker`)
13541376
- react-native-pager-view (from `../../../node_modules/react-native-pager-view`)
13551377
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
@@ -1458,6 +1480,8 @@ EXTERNAL SOURCES:
14581480
:path: "../../../node_modules/@react-native-camera-roll/camera-roll"
14591481
react-native-date-picker:
14601482
:path: "../../../node_modules/react-native-date-picker"
1483+
react-native-geolocation:
1484+
:path: "../../../node_modules/@react-native-community/geolocation"
14611485
react-native-image-picker:
14621486
:path: "../../../node_modules/react-native-image-picker"
14631487
react-native-pager-view:
@@ -1563,6 +1587,7 @@ SPEC CHECKSUMS:
15631587
React-Mapbuffer: 9f68550e7c6839d01411ac8896aea5c868eff63a
15641588
react-native-cameraroll: a9138c165c9975da773d26945591d313992c799b
15651589
react-native-date-picker: 6891317e850deae5b53d51355226e07a495aba61
1590+
react-native-geolocation: 67d909c955daedea3f8c30d912f004f806edff64
15661591
react-native-image-picker: c3afe5472ef870d98a4b28415fc0b928161ee5f7
15671592
react-native-pager-view: f848f89049a8e888d38f10ff31588eb63292a95f
15681593
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
@@ -1592,14 +1617,14 @@ SPEC CHECKSUMS:
15921617
ReactCommon: f00e436b3925a7ae44dfa294b43ef360fbd8ccc4
15931618
RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a
15941619
RNGestureHandler: 8dbcccada4a7e702e7dec9338c251b1cf393c960
1595-
RNPermissions: 4da8c626e4ac9d71c1a199d500d52dd54da62e38
1620+
RNPermissions: 4eb47e412214ddbc0fc5cc76fd441f456da95df7
15961621
RNReanimated: 7e6fc1e80f412285a16ac3879b9e4672ffa91cef
15971622
RNScreens: 5aeecbb09aa7285379b6e9f3c8a3c859bb16401c
15981623
RNSVG: cb24fb322de8c1ebf59904e7aca0447bb8dbed5a
15991624
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
16001625
VisionCamera: 412fec057156eb5fe84a44351e8ed71a072c1228
16011626
Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c
16021627

1603-
PODFILE CHECKSUM: 18c90cc9391298febe533b3a6a27e59b23d9fec0
1628+
PODFILE CHECKSUM: d41c31c821cf0e01297a2147be1556d600f1dadc
16041629

16051630
COCOAPODS: 1.15.2

packages/react-native/ios/SPOTClient/Info.plist

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
<key>NSPhotoLibraryUsageDescription</key>
1010
<string>$(PRODUCT_NAME) needs access to your Photo Library.</string>
1111

12+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
13+
<string>$(PRODUCT_NAME) needs access to your Location to play location quiz.</string>
14+
15+
<key>NSLocationAlwaysUsageDescription</key>
16+
<string>$(PRODUCT_NAME) needs access to your Location to play location quiz.</string>
17+
1218
<key>CFBundleDevelopmentRegion</key>
1319
<string>en</string>
1420
<key>CFBundleDisplayName</key>
@@ -39,7 +45,7 @@
3945
<true/>
4046
</dict>
4147
<key>NSLocationWhenInUseUsageDescription</key>
42-
<string></string>
48+
<string>$(PRODUCT_NAME) needs access to your Location to play location quiz.</string>
4349
<key>UIAppFonts</key>
4450
<array>
4551
<string>fonts/Pretendard-Bold.otf</string>

packages/react-native/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@gorhom/bottom-sheet": "^4.6.4",
2020
"@react-native-async-storage/async-storage": "^1.24.0",
2121
"@react-native-camera-roll/camera-roll": "^7.8.1",
22+
"@react-native-community/geolocation": "^3.4.0",
2223
"@react-navigation/bottom-tabs": "^6.6.0",
2324
"@react-navigation/material-top-tabs": "^6.6.14",
2425
"@react-navigation/native": "^6.1.17",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
2+
3+
export interface Location {
4+
latitude?: number;
5+
longitude?: number;
6+
}
7+
8+
export interface QuizzesResponse {
9+
title: string;
10+
location: string;
11+
region: string;
12+
image: string;
13+
}
14+
15+
interface UseQuizzesQueryParams {
16+
location?: Location;
17+
}
18+
19+
const mockQuizzes = [
20+
{
21+
title: '도깨비',
22+
location: '주문진 방파제',
23+
region: '강원도 강릉',
24+
image:
25+
'https://t1.daumcdn.net/news/202406/27/poctan/20240627172416746baii.jpg',
26+
},
27+
{
28+
title: '도깨비2',
29+
location: '주문한 방파제',
30+
region: '강원도 강릉',
31+
image:
32+
'https://t1.daumcdn.net/news/202406/27/poctan/20240627172416746baii.jpg',
33+
},
34+
] as QuizzesResponse[];
35+
36+
export default function useQuizzesQuery({ location }: UseQuizzesQueryParams) {
37+
return useSuspenseQuery({
38+
queryKey: ['Quizzes', location],
39+
queryFn: async () => {
40+
if (!location) {
41+
return null;
42+
}
43+
44+
return mockQuizzes;
45+
},
46+
});
47+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Geolocation, {
2+
GeolocationResponse,
3+
} from '@react-native-community/geolocation';
4+
import { Alert, Linking, Platform } from 'react-native';
5+
import {
6+
check,
7+
PERMISSIONS,
8+
RESULTS,
9+
PermissionStatus,
10+
request,
11+
} from 'react-native-permissions';
12+
13+
export default function useGeolocation() {
14+
const checkLocationPermission = (result: PermissionStatus) => {
15+
switch (result) {
16+
case RESULTS.GRANTED:
17+
case RESULTS.LIMITED:
18+
return 'granted';
19+
20+
default:
21+
Alert.alert(
22+
'위치 정보 권한이 있어야 사용할 수 있어요.',
23+
'설정페이지로 이동하시겠어요?',
24+
[
25+
{
26+
text: '취소',
27+
},
28+
{
29+
text: '확인',
30+
onPress: async () => {
31+
await Linking.openSettings();
32+
},
33+
},
34+
],
35+
);
36+
return 'denied';
37+
}
38+
};
39+
40+
const hasLocationPermission = async () => {
41+
if (Platform.OS === 'ios') {
42+
const result = await request(PERMISSIONS.IOS.LOCATION_ALWAYS);
43+
const permission = await check(PERMISSIONS.IOS.LOCATION_ALWAYS);
44+
return checkLocationPermission(permission) === 'granted';
45+
}
46+
await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
47+
const permission = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
48+
return checkLocationPermission(permission) === 'granted';
49+
};
50+
51+
const getGeolocation = async () => {
52+
const hasPermission = await hasLocationPermission();
53+
if (!hasPermission) {
54+
return null;
55+
}
56+
57+
const result = new Promise<GeolocationResponse>((resolve) => {
58+
Geolocation.getCurrentPosition((pos) => resolve(pos));
59+
});
60+
return result;
61+
};
62+
63+
return { getGeolocation };
64+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useEffect, useState } from 'react';
2+
import useGeolocation from '@/hooks/useGeolocation';
3+
import useQuizzesQuery, { Location } from '@/apis/queries/useQuizzesQuery';
4+
import QuizLoading from './Gamification/QuizLoading';
5+
import QuizSlider from './Gamification/QuizSlider';
6+
import withSuspense from '@/components/HOC/withSuspense';
7+
8+
export default withSuspense(
9+
function Gamification() {
10+
const { getGeolocation } = useGeolocation();
11+
const [location, setLocation] = useState<Location>();
12+
const { data } = useQuizzesQuery({ location });
13+
14+
useEffect(() => {
15+
getGeolocation().then((res) => {
16+
setLocation({
17+
latitude: res?.coords.latitude,
18+
longitude: res?.coords.longitude,
19+
});
20+
});
21+
});
22+
23+
if (!data) {
24+
return <QuizLoading />;
25+
}
26+
27+
return <QuizSlider quizListData={data} />;
28+
},
29+
{
30+
fallback: <QuizLoading />,
31+
},
32+
);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Dimensions, Image, TouchableOpacity, View } from 'react-native';
2+
import { Font } from 'design-system';
3+
import { QuizzesResponse } from '@/apis/queries/useQuizzesQuery';
4+
5+
interface QuizCardProps {
6+
quizData: QuizzesResponse;
7+
}
8+
9+
const { width: fullWidth } = Dimensions.get('window');
10+
11+
export const QUIZ_CARD_SIZE = (fullWidth * 80) / 100;
12+
13+
export default function QuizCard({ quizData }: QuizCardProps) {
14+
return (
15+
<View
16+
style={{
17+
display: 'flex',
18+
width: QUIZ_CARD_SIZE,
19+
}}
20+
>
21+
<Image
22+
source={{
23+
uri: quizData.image,
24+
}}
25+
style={{
26+
width: QUIZ_CARD_SIZE,
27+
height: QUIZ_CARD_SIZE,
28+
borderTopLeftRadius: 25,
29+
borderTopRightRadius: 25,
30+
}}
31+
/>
32+
<View className="bg-[#191919] rounded-b-3xl p-3">
33+
<View className="flex gap-1">
34+
<View className="justify-center items-center gap-1 flex flex-col">
35+
<Font.Bold type="title1" color="white">
36+
{quizData.title}
37+
</Font.Bold>
38+
<View>
39+
<Font.Bold type="body2" color="white">
40+
{quizData.location}
41+
</Font.Bold>
42+
</View>
43+
<View>
44+
<Font.Bold type="ui-text" color="white">
45+
{quizData.region}
46+
</Font.Bold>
47+
</View>
48+
</View>
49+
<View className="flex gap-3 flex-row justify-center py-3">
50+
<TouchableOpacity className="bg-SPOT-red justify-center items-center px-6 py-1.5 rounded-lg">
51+
<Font.Bold type="body3" color="white">
52+
퀴즈 풀기
53+
</Font.Bold>
54+
</TouchableOpacity>
55+
<TouchableOpacity className="bg-Button-gray justify-center items-center px-6 py-1.5 rounded-lg">
56+
<Font.Bold type="body3" color="white">
57+
Spot! 필터
58+
</Font.Bold>
59+
</TouchableOpacity>
60+
</View>
61+
</View>
62+
</View>
63+
</View>
64+
);
65+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { View } from 'react-native';
2+
import { Font } from 'design-system';
3+
import Header from '@/components/common/Header';
4+
import BackGroundGradient from '@/layouts/BackGroundGradient';
5+
6+
export default function QuizLoading() {
7+
return (
8+
<BackGroundGradient withoutScroll>
9+
<Header type="logo" />
10+
<View className="flex-1 p-4 justify-center items-center gap-8">
11+
<View>
12+
<Font.Bold color="white" type="title1">
13+
현재 위치 주변
14+
</Font.Bold>
15+
<Font.Bold color="white" type="title1">
16+
촬영지(Spot!)를
17+
</Font.Bold>
18+
<Font.Bold color="white" type="title1">
19+
검색하고 있습니다...
20+
</Font.Bold>
21+
</View>
22+
<View className="p-3 flex justify-center items-start gap-4">
23+
<Font.Bold type="body2" color="white">
24+
Q. 지역 배지는 어떻게 받을 수 있나요?
25+
</Font.Bold>
26+
<View>
27+
<Font.Bold type="body3" color="white">
28+
해당 Spot에 직접 방문해서 ..
29+
</Font.Bold>
30+
<View className="mt-2">
31+
<Font.Bold type="body3" color="white">
32+
1. 퀴즈 풀고 배지 받기
33+
</Font.Bold>
34+
<Font.Bold type="body3" color="white">
35+
2. Spot! 필터 사용해서 촬영한 사진 저장하고 배지 받기
36+
</Font.Bold>
37+
</View>
38+
</View>
39+
</View>
40+
</View>
41+
</BackGroundGradient>
42+
);
43+
}

0 commit comments

Comments
 (0)