Skip to content

Commit 2e1b1db

Browse files
authored
Merge pull request #145 from team-ppointer/feat/native/problem-#141
[Feat/native/#141] 문제 풀이 Flow 구현
2 parents b8ca31a + 7caf848 commit 2e1b1db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3235
-362
lines changed

apps/native/App.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { NavigationContainer, DefaultTheme, Theme } from '@react-navigation/nati
44
import { StatusBar } from 'expo-status-bar';
55
import { SafeAreaProvider } from 'react-native-safe-area-context';
66
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
7-
import RootNavigator from './src/navigation/RootNavigator';
8-
import { colors } from './src/theme/tokens';
9-
import './src/app/providers/global.css';
10-
import './src/app/providers/api';
11-
import { LoadingScreen } from '@/components/common/LoadingScreen';
12-
import { useLoadAssets } from '@/hooks/useAssets';
7+
import RootNavigator from '@navigation/RootNavigator';
8+
import { colors } from '@theme/tokens';
9+
import '@/app/providers/global.css';
10+
import '@/app/providers/api';
11+
import { LoadingScreen } from '@components/common';
12+
import { useLoadAssets } from '@hooks';
13+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
1314

1415
const queryClient = new QueryClient();
1516

@@ -40,12 +41,14 @@ export default function App() {
4041

4142
return (
4243
<QueryClientProvider client={queryClient}>
43-
<SafeAreaProvider>
44-
<NavigationContainer theme={navigationTheme} linking={linking}>
45-
<StatusBar style='dark' />
46-
<RootNavigator />
47-
</NavigationContainer>
48-
</SafeAreaProvider>
44+
<GestureHandlerRootView style={{ flex: 1 }}>
45+
<SafeAreaProvider>
46+
<NavigationContainer theme={navigationTheme} linking={linking}>
47+
<StatusBar style='dark' />
48+
<RootNavigator />
49+
</NavigationContainer>
50+
</SafeAreaProvider>
51+
</GestureHandlerRootView>
4952
</QueryClientProvider>
5053
);
5154
}

apps/native/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"dependencies": {
1515
"@expo/vector-icons": "^15.0.3",
16+
"@gorhom/bottom-sheet": "^5.2.7",
1617
"@react-native-community/datetimepicker": "^8.5.1",
1718
"@react-navigation/bottom-tabs": "^7.4.0",
1819
"@react-navigation/elements": "^2.6.3",
@@ -24,6 +25,7 @@
2425
"expo": "~54.0.25",
2526
"expo-asset": "^12.0.10",
2627
"expo-constants": "~18.0.10",
28+
"expo-file-system": "^19.0.19",
2729
"expo-font": "~14.0.9",
2830
"expo-haptics": "~15.0.7",
2931
"expo-image": "~3.0.10",
@@ -45,11 +47,12 @@
4547
"react-native": "0.81.5",
4648
"react-native-css-interop": "^0.2.1",
4749
"react-native-gesture-handler": "~2.28.0",
48-
"react-native-reanimated": "~3.17.5",
50+
"react-native-reanimated": "~4.1.5",
4951
"react-native-safe-area-context": "~5.4.0",
5052
"react-native-screens": "~4.16.0",
5153
"react-native-svg": "^15.15.0",
5254
"react-native-web": "~0.21.0",
55+
"react-native-webview": "^13.16.0",
5356
"react-native-worklets": "0.5.1",
5457
"zustand": "^5.0.8"
5558
},

apps/native/src/apis/controller/study/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import postAnswer from './postAnswer';
2+
import postPointing from './postPointing';
13
import useGetMonthlyPublish from './useGetMonthlyPublish';
24
import useGetProblem from './useGetProblem';
35
import useGetPublishDetail from './useGetPublishDetail';
46
import useGetWeeklyProgress from './useGetWeeklyProgress';
57
import useGetWeeklyPublish from './useGetWeeklyPublish';
68

79
export {
10+
postAnswer,
11+
postPointing,
812
useGetMonthlyPublish,
913
useGetProblem,
1014
useGetPublishDetail,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { client } from '@apis';
2+
3+
const postAnswer = async (
4+
publishId: number,
5+
problemId: number | null,
6+
submitAnswer: number | null
7+
) => {
8+
const body: {
9+
publishId: number;
10+
problemId?: number;
11+
submitAnswer?: number;
12+
} = {
13+
publishId,
14+
...(submitAnswer !== null && submitAnswer !== undefined ? { submitAnswer } : {}),
15+
};
16+
17+
if (problemId !== null) {
18+
body.problemId = problemId;
19+
} else {
20+
throw new Error('problemId 반드시 입력하세요.');
21+
}
22+
23+
return await client.POST('/api/student/study/submit/answer', {
24+
body,
25+
});
26+
};
27+
28+
export default postAnswer;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { client } from '@apis';
2+
3+
const postPointing = async (pointingId: number, isUnderstood: boolean) => {
4+
return await client.POST('/api/student/study/submit/pointing', {
5+
body: {
6+
pointingId,
7+
isUnderstood,
8+
},
9+
});
10+
};
11+
12+
export default postPointing;

apps/native/src/apis/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { client, TanstackQueryClient } from './client';
2+
import authMiddleware from './authMiddleware';
23

3-
export { client, TanstackQueryClient };
4+
export { client, TanstackQueryClient, authMiddleware };
45

56
// controllers
67
export * from './controller/auth';
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { client } from '@/apis';
2-
import authMiddleware from '@/apis/authMiddleware';
1+
import { client, authMiddleware } from '@apis';
32

43
client.use(authMiddleware);
5-
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { ActivityIndicator, Text, View } from 'react-native';
22

3-
import { colors } from '@/theme/tokens';
3+
import { colors } from '@theme/tokens';
44

55
type Props = {
66
label?: string;
77
};
88

9-
export const LoadingScreen = ({ label = '잠시만 기다려 주세요.' }: Props) => {
9+
const LoadingScreen = ({ label = '잠시만 기다려 주세요.' }: Props) => {
1010
return (
1111
<View className='flex-1 items-center justify-center bg-gray-100 px-12'>
1212
<ActivityIndicator size='large' color={colors['primary-500']} />
1313
<Text className='mt-6 text-center text-[16px] text-gray-800'>{label}</Text>
1414
</View>
1515
);
1616
};
17+
18+
export default LoadingScreen;

apps/native/src/components/common/NotificationItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { colors } from '@/theme/tokens';
1+
import { colors } from '@theme/tokens';
22
import { BookOpenText, LucideIcon, Megaphone, MessageCircleMore } from 'lucide-react-native';
33
import React from 'react';
44
import { Pressable, Text, View } from 'react-native';
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { useEffect, useMemo, useRef, useState } from 'react';
2+
import { Animated, Pressable, Text, View, ViewProps } from 'react-native';
3+
4+
type SegmentedControlProps = {
5+
options: string[];
6+
selectedIndex: number;
7+
onChange: (index: number) => void;
8+
containerProps?: ViewProps;
9+
};
10+
11+
const SegmentedControl = ({
12+
options,
13+
selectedIndex,
14+
onChange,
15+
containerProps,
16+
}: SegmentedControlProps) => {
17+
const translateValue = useRef(new Animated.Value(selectedIndex)).current;
18+
const [containerWidth, setContainerWidth] = useState(0);
19+
20+
useEffect(() => {
21+
Animated.spring(translateValue, {
22+
toValue: selectedIndex,
23+
useNativeDriver: true,
24+
tension: 200,
25+
friction: 20,
26+
}).start();
27+
}, [selectedIndex, translateValue]);
28+
29+
const { segmentWidth, indicatorOffset } = useMemo(() => {
30+
const horizontalPadding = 8; // p-[4px] on container
31+
const contentWidth = Math.max(containerWidth - horizontalPadding, 0);
32+
return {
33+
segmentWidth: contentWidth > 0 ? contentWidth / options.length : 0,
34+
indicatorOffset: horizontalPadding / 2,
35+
};
36+
}, [containerWidth, options.length]);
37+
38+
const indicatorStyle = useMemo(
39+
() => ({
40+
width: segmentWidth,
41+
transform: [{ translateX: Animated.multiply(translateValue, segmentWidth || 0) }],
42+
}),
43+
[segmentWidth, translateValue]
44+
);
45+
46+
return (
47+
<View
48+
className='my-[10px] flex-row items-center rounded-full bg-gray-300 p-[4px]'
49+
onLayout={(event) => setContainerWidth(event.nativeEvent.layout.width)}
50+
{...containerProps}>
51+
<Animated.View
52+
pointerEvents='none'
53+
className='absolute h-full rounded-full bg-gray-800 shadow-[0px_1px_4px_0px_rgba(12,12,13,0.05),0px_1px_4px_0px_rgba(12,12,13,0.10)]'
54+
style={[{ left: indicatorOffset }, indicatorStyle]}
55+
/>
56+
{options.map((option, index) => {
57+
const isSelected = index === selectedIndex;
58+
return (
59+
<Pressable key={option} className='z-10 flex-1 py-[8px]' onPress={() => onChange(index)}>
60+
<Text
61+
className={`text-center ${isSelected ? 'text-14b text-white' : 'text-13m text-black'}`}>
62+
{option}
63+
</Text>
64+
</Pressable>
65+
);
66+
})}
67+
</View>
68+
);
69+
};
70+
71+
export default SegmentedControl;

0 commit comments

Comments
 (0)