diff --git a/apps/friends-react-native/src/app/assets/icons/kakaotalk.svg b/apps/friends-react-native/src/app/assets/icons/kakaotalk.svg
new file mode 100644
index 0000000..9c6b81e
--- /dev/null
+++ b/apps/friends-react-native/src/app/assets/icons/kakaotalk.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/friends-react-native/src/app/assets/icons/user-hashtag.svg b/apps/friends-react-native/src/app/assets/icons/user-hashtag.svg
new file mode 100644
index 0000000..67dbf17
--- /dev/null
+++ b/apps/friends-react-native/src/app/assets/icons/user-hashtag.svg
@@ -0,0 +1,12 @@
+
diff --git a/apps/friends-react-native/src/app/components/Icons/Kakaotalk.tsx b/apps/friends-react-native/src/app/components/Icons/Kakaotalk.tsx
new file mode 100644
index 0000000..07fc5cd
--- /dev/null
+++ b/apps/friends-react-native/src/app/components/Icons/Kakaotalk.tsx
@@ -0,0 +1,4 @@
+import Icon from '../../assets/icons/kakaotalk.svg';
+import { createIconComponent } from './_createIconComponent';
+
+export const KakaotalkIcon = createIconComponent(Icon);
diff --git a/apps/friends-react-native/src/app/components/Icons/UserHashtagIcon.tsx b/apps/friends-react-native/src/app/components/Icons/UserHashtagIcon.tsx
new file mode 100644
index 0000000..beb24b0
--- /dev/null
+++ b/apps/friends-react-native/src/app/components/Icons/UserHashtagIcon.tsx
@@ -0,0 +1,4 @@
+import Icon from '../../assets/icons/user-hashtag.svg';
+import { createIconComponent } from './_createIconComponent';
+
+export const UserHashtagIcon = createIconComponent(Icon);
diff --git a/apps/friends-react-native/src/app/contexts/ServiceContext.ts b/apps/friends-react-native/src/app/contexts/ServiceContext.ts
index 5420615..ba08095 100644
--- a/apps/friends-react-native/src/app/contexts/ServiceContext.ts
+++ b/apps/friends-react-native/src/app/contexts/ServiceContext.ts
@@ -5,6 +5,7 @@ import { ColorService } from '../../usecases/colorService';
import { CourseBookService } from '../../usecases/courseBookService';
import { FriendService } from '../../usecases/friendService';
import { TimetableViewService } from '../../usecases/timetableViewService';
+import { NativeEventService } from '../../usecases/nativeEventService';
type ServiceContext = {
timetableViewService: TimetableViewService;
@@ -12,7 +13,9 @@ type ServiceContext = {
friendService: FriendService;
courseBookService: CourseBookService;
assetService: AssetService;
+ nativeEventService: NativeEventService;
};
+
export const serviceContext = createContext(null);
export const useServiceContext = () => {
const context = useContext(serviceContext);
diff --git a/apps/friends-react-native/src/app/queries/useFriends.ts b/apps/friends-react-native/src/app/queries/useFriends.ts
index 7046b81..728a5fa 100644
--- a/apps/friends-react-native/src/app/queries/useFriends.ts
+++ b/apps/friends-react-native/src/app/queries/useFriends.ts
@@ -1,6 +1,7 @@
-import { useQuery } from '@tanstack/react-query';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useServiceContext } from '../contexts/ServiceContext';
+import { FriendId } from '../../entities/friend';
export const useFriends = (req: { state: 'REQUESTED' | 'REQUESTING' | 'ACTIVE' }) => {
const { friendService } = useServiceContext();
@@ -9,3 +10,22 @@ export const useFriends = (req: { state: 'REQUESTED' | 'REQUESTING' | 'ACTIVE' }
queryFn: ({ queryKey: [, params] }) => friendService.listFriends(params),
});
};
+
+export const useAcceptFriend = () => {
+ const queryClient = useQueryClient();
+ const { friendService } = useServiceContext();
+ return useMutation({
+ mutationFn: (req: { type: 'NICKNAME'; friendId: FriendId } | { type: 'KAKAO'; requestToken: string }) =>
+ friendService.acceptFriend(req),
+ onSuccess: () => queryClient.invalidateQueries(),
+ });
+};
+
+export const useDeclineFriend = () => {
+ const queryClient = useQueryClient();
+ const { friendService } = useServiceContext();
+ return useMutation({
+ mutationFn: (friendId: FriendId) => friendService.declineFriend({ friendId }),
+ onSuccess: () => queryClient.invalidateQueries(),
+ });
+};
diff --git a/apps/friends-react-native/src/app/queries/useRequestFriendToken.ts b/apps/friends-react-native/src/app/queries/useRequestFriendToken.ts
new file mode 100644
index 0000000..b0ffd17
--- /dev/null
+++ b/apps/friends-react-native/src/app/queries/useRequestFriendToken.ts
@@ -0,0 +1,12 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { useServiceContext } from '../contexts/ServiceContext';
+
+export const useRequestFriendToken = () => {
+ const { friendService } = useServiceContext();
+
+ return useQuery({
+ queryKey: ['requestFriendToken'] as const,
+ queryFn: () => friendService.generateToken(),
+ });
+};
diff --git a/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/ManageFriendsDrawerContentRequestedList/index.tsx b/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/ManageFriendsDrawerContentRequestedList/index.tsx
index 70f94de..d0b5345 100644
--- a/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/ManageFriendsDrawerContentRequestedList/index.tsx
+++ b/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/ManageFriendsDrawerContentRequestedList/index.tsx
@@ -1,12 +1,10 @@
-import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Alert, FlatList, StyleSheet, View } from 'react-native';
-import { FriendId } from '../../../../../entities/friend';
import { Button } from '../../../../components/Button';
import { EmptyView } from '../../../../components/EmptyView';
import { Typography } from '../../../../components/Typography';
import { useServiceContext } from '../../../../contexts/ServiceContext';
-import { useFriends } from '../../../../queries/useFriends';
+import { useAcceptFriend, useDeclineFriend, useFriends } from '../../../../queries/useFriends';
export const ManageFriendsDrawerContentRequestedList = () => {
const { friendService } = useServiceContext();
@@ -36,7 +34,12 @@ export const ManageFriendsDrawerContentRequestedList = () => {
@@ -55,24 +58,6 @@ const Empty = () => {
);
};
-const useAcceptFriend = () => {
- const queryClient = useQueryClient();
- const { friendService } = useServiceContext();
- return useMutation({
- mutationFn: (friendId: FriendId) => friendService.acceptFriend({ friendId }),
- onSuccess: () => queryClient.invalidateQueries(),
- });
-};
-
-const useDeclineFriend = () => {
- const queryClient = useQueryClient();
- const { friendService } = useServiceContext();
- return useMutation({
- mutationFn: (friendId: FriendId) => friendService.declineFriend({ friendId }),
- onSuccess: () => queryClient.invalidateQueries(),
- });
-};
-
const styles = StyleSheet.create({
item: {
height: 40,
diff --git a/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/index.tsx b/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/index.tsx
index 9f75d37..44d4a28 100644
--- a/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/index.tsx
+++ b/apps/friends-react-native/src/app/screens/MainScreen/ManageFriendsDrawerContent/index.tsx
@@ -67,7 +67,7 @@ export const ManageFriendsDrawerContent = ({ onClose }: Props) => {
dispatch({ type: 'setAddFriendModalOpen', isOpen: true })}
+ onPress={() => dispatch({ type: 'setRequestFriendModalOpen', isOpen: true })}
>
친구 추가하기
diff --git a/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/RequestFriendsMethodList/index.tsx b/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/RequestFriendsMethodList/index.tsx
new file mode 100644
index 0000000..7387b9d
--- /dev/null
+++ b/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/RequestFriendsMethodList/index.tsx
@@ -0,0 +1,80 @@
+import { StyleSheet, View } from 'react-native';
+
+import { Typography } from '../../../../components/Typography';
+import { TouchableOpacity } from 'react-native-gesture-handler';
+import { UserHashtagIcon } from '../../../../components/Icons/UserHashtagIcon';
+import { useThemeContext } from '../../../../contexts/ThemeContext';
+import { KakaotalkIcon } from '../../../../components/Icons/Kakaotalk';
+import { RequestFriendModalStep, useMainScreenContext } from '../..';
+import { useRequestFriendToken } from '../../../../queries/useRequestFriendToken';
+import { useServiceContext } from '../../../../contexts/ServiceContext';
+
+export const RequestFriendsMethodList = () => {
+ const { dispatch } = useMainScreenContext();
+ const { nativeEventService } = useServiceContext();
+ const { data } = useRequestFriendToken();
+ const iconColor = useThemeContext((theme) => theme.color.text.default);
+
+ const setRequestFriendModalStep = (step: RequestFriendModalStep) =>
+ dispatch({
+ type: 'setRequestFriendModalStep',
+ requestFriendModalStep: step,
+ });
+
+ const requestFriendWithKakao = () => {
+ const parameters = {
+ requestToken: data!.requestToken,
+ };
+
+ nativeEventService.sendEventToNative({
+ type: 'add-friend-kakao',
+ parameters,
+ });
+
+ dispatch({
+ type: 'setRequestFriendModalOpen',
+ isOpen: false,
+ });
+ };
+
+ return (
+ <>
+
+
+
+ 카카오톡으로 친구 초대
+
+
+
+ setRequestFriendModalStep('REQUEST_WITH_NICKNAME')}>
+
+ 닉네임으로 친구 초대
+
+
+ >
+ );
+};
+
+const styles = StyleSheet.create({
+ sheetContent: { paddingBottom: 20 },
+ sheetItem: {
+ height: 50,
+ paddingVertical: 10,
+ display: 'flex',
+ flexDirection: 'row',
+ gap: 25,
+ alignItems: 'center',
+ },
+});
diff --git a/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/RequestFriendsWithNickname/index.tsx b/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/RequestFriendsWithNickname/index.tsx
new file mode 100644
index 0000000..4d61ab9
--- /dev/null
+++ b/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/RequestFriendsWithNickname/index.tsx
@@ -0,0 +1,90 @@
+import { Alert } from 'react-native';
+import { StyleSheet, View } from 'react-native';
+
+import { get } from '../../../../../utils/get';
+import { BottomSheet } from '../../../../components/BottomSheet';
+import { WarningIcon } from '../../../../components/Icons/WarningIcon';
+import { Input } from '../../../../components/Input';
+import { Typography } from '../../../../components/Typography';
+import { COLORS } from '../../../../styles/colors';
+import { useMainScreenContext, useRequestFriend } from '../..';
+import { useServiceContext } from '../../../../contexts/ServiceContext';
+import { useThemeContext } from '../../../../contexts/ThemeContext';
+
+export const RequestFriendsWithNickname = () => {
+ const { requestFriendModalNickname, dispatch } = useMainScreenContext();
+ const { friendService } = useServiceContext();
+ const guideEnabledColor = useThemeContext((data) => data.color.text.guide);
+ const { mutate: request } = useRequestFriend();
+
+ const isValid = friendService.isValidNicknameTag(requestFriendModalNickname);
+ const guideMessageState = requestFriendModalNickname === '' ? 'disabled' : isValid ? 'hidden' : 'enabled';
+
+ const closeAddFriendModal = () => dispatch({ type: 'setRequestFriendModalOpen', isOpen: false });
+
+ return (
+
+
+ request(requestFriendModalNickname, {
+ onSuccess: () => {
+ Alert.alert('친구에게 요청을 보냈습니다.');
+ closeAddFriendModal();
+ },
+ onError: (err) => {
+ const displayMessage = get(err, ['displayMessage']);
+ Alert.alert(displayMessage ? `${displayMessage}` : '오류가 발생했습니다.');
+ },
+ }),
+ disabled: !isValid,
+ }}
+ />
+
+ 추가하고 싶은 친구의 닉네임
+
+ dispatch({ type: 'setRequestFriendModalNickname', nickname: e })}
+ placeholder="예) 홍길동#1234"
+ />
+
+ {guideMessageState !== 'hidden' &&
+ (() => {
+ const color = { enabled: guideEnabledColor, disabled: COLORS.gray40 }[guideMessageState];
+ return (
+ <>
+
+
+ 닉네임 전체를 입력하세요
+
+ >
+ );
+ })()}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ questionIconButton: { flexDirection: 'row', alignItems: 'center', gap: 6 },
+ questionIcon: { color: COLORS.gray30 },
+ modalContent: { paddingBottom: 30 },
+ inputDescription: { marginTop: 30, fontSize: 14 },
+ input: { marginTop: 15 },
+ guide: {
+ marginTop: 7,
+ display: 'flex',
+ flexDirection: 'row',
+ gap: 1,
+ alignItems: 'center',
+ height: 12,
+ },
+ guideText: { fontSize: 12 },
+ hamburgerWrapper: { position: 'relative' },
+ hamburgerNotificationDot: { position: 'absolute', top: 5, right: -1 },
+});
diff --git a/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/index.tsx b/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/index.tsx
new file mode 100644
index 0000000..828c0c8
--- /dev/null
+++ b/apps/friends-react-native/src/app/screens/MainScreen/RequestFriendsBottomSheetContent/index.tsx
@@ -0,0 +1,16 @@
+import { useMainScreenContext } from '..';
+import { BottomSheet } from '../../../components/BottomSheet';
+import { RequestFriendsMethodList } from './RequestFriendsMethodList';
+import { RequestFriendsWithNickname } from './RequestFriendsWithNickname';
+
+export const RequestFriendsBottomSheetContent = () => {
+ const { isRequestFriendModalOpen, requestFriendModalStep, dispatch } = useMainScreenContext();
+
+ const closeAddFriendModal = () => dispatch({ type: 'setRequestFriendModalOpen', isOpen: false });
+
+ return (
+
+ {requestFriendModalStep === 'METHOD_LIST' ? : }
+
+ );
+};
diff --git a/apps/friends-react-native/src/app/screens/MainScreen/index.tsx b/apps/friends-react-native/src/app/screens/MainScreen/index.tsx
index 28f0672..bceaa56 100644
--- a/apps/friends-react-native/src/app/screens/MainScreen/index.tsx
+++ b/apps/friends-react-native/src/app/screens/MainScreen/index.tsx
@@ -1,44 +1,44 @@
import { createDrawerNavigator, DrawerContentComponentProps, DrawerHeaderProps } from '@react-navigation/drawer';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createContext, Dispatch, useContext, useEffect, useMemo, useReducer } from 'react';
-import { Alert, TouchableOpacity } from 'react-native';
+import { TouchableOpacity } from 'react-native';
import { StyleSheet, View } from 'react-native';
import { CourseBook } from '../../../entities/courseBook';
import { ClientFeature } from '../../../entities/feature';
import { FriendId } from '../../../entities/friend';
import { Nickname } from '../../../entities/user';
-import { get } from '../../../utils/get';
import { AppBar } from '../../components/Appbar';
-import { BottomSheet } from '../../components/BottomSheet';
import { HamburgerIcon } from '../../components/Icons/HamburgerIcon';
import { QuestionIcon } from '../../components/Icons/QuestionIcon';
import { UserPlusIcon } from '../../components/Icons/UserPlusIcon';
-import { WarningIcon } from '../../components/Icons/WarningIcon';
-import { Input } from '../../components/Input';
import { NotificationDot } from '../../components/NotificationDot';
-import { Typography } from '../../components/Typography';
import { useFeatureContext } from '../../contexts/FeatureContext';
import { useServiceContext } from '../../contexts/ServiceContext';
import { useThemeContext } from '../../contexts/ThemeContext';
import { useFriendCourseBooks } from '../../queries/useFriendCourseBooks';
-import { useFriends } from '../../queries/useFriends';
+import { useAcceptFriend, useFriends } from '../../queries/useFriends';
import { COLORS } from '../../styles/colors';
import { FriendTimetable } from './FriendTimetable';
import { ManageFriendsDrawerContent } from './ManageFriendsDrawerContent';
+import { RequestFriendsBottomSheetContent } from './RequestFriendsBottomSheetContent';
+
+export type RequestFriendModalStep = 'METHOD_LIST' | 'REQUEST_WITH_NICKNAME';
type MainScreenState = {
selectedFriendId: FriendId | undefined;
selectedCourseBook: CourseBook | undefined;
- isAddFriendModalOpen: boolean;
- addFriendModalNickname: string;
+ isRequestFriendModalOpen: boolean;
+ requestFriendModalStep: RequestFriendModalStep;
+ requestFriendModalNickname: string;
isGuideModalOpen: boolean;
};
type MainScreenAction =
| { type: 'setFriend'; friendId: FriendId | undefined }
| { type: 'setCourseBook'; courseBook: CourseBook }
- | { type: 'setAddFriendModalOpen'; isOpen: boolean }
- | { type: 'setAddFriendModalNickname'; nickname: string }
+ | { type: 'setRequestFriendModalOpen'; isOpen: boolean }
+ | { type: 'setRequestFriendModalStep'; requestFriendModalStep: RequestFriendModalStep }
+ | { type: 'setRequestFriendModalNickname'; nickname: string }
| { type: 'setGuideModalOpen'; isOpen: boolean };
type MainScreenContext = MainScreenState & { dispatch: Dispatch };
const mainScreenReducer = (state: MainScreenState, action: MainScreenAction): MainScreenState => {
@@ -47,15 +47,22 @@ const mainScreenReducer = (state: MainScreenState, action: MainScreenAction): Ma
return { ...state, selectedFriendId: action.friendId, selectedCourseBook: undefined };
case 'setCourseBook':
return { ...state, selectedCourseBook: action.courseBook };
- case 'setAddFriendModalOpen':
+ case 'setRequestFriendModalOpen':
return action.isOpen
- ? { ...state, isAddFriendModalOpen: true }
- : { ...state, isAddFriendModalOpen: false, addFriendModalNickname: '' };
- case 'setAddFriendModalNickname':
- if (!state.isAddFriendModalOpen) throw new Error();
- return { ...state, addFriendModalNickname: action.nickname };
+ ? { ...state, isRequestFriendModalOpen: true }
+ : {
+ ...state,
+ isRequestFriendModalOpen: false,
+ requestFriendModalNickname: '',
+ requestFriendModalStep: 'METHOD_LIST',
+ };
+ case 'setRequestFriendModalNickname':
+ if (!state.isRequestFriendModalOpen) throw new Error();
+ return { ...state, requestFriendModalNickname: action.nickname };
case 'setGuideModalOpen':
return { ...state, isGuideModalOpen: action.isOpen };
+ case 'setRequestFriendModalStep':
+ return { ...state, requestFriendModalStep: action.requestFriendModalStep };
}
};
const mainScreenContext = createContext(null);
@@ -70,14 +77,19 @@ export const MainScreen = () => {
const [state, dispatch] = useReducer(mainScreenReducer, {
selectedFriendId: undefined,
selectedCourseBook: undefined,
- isAddFriendModalOpen: false,
- addFriendModalNickname: '',
+ isRequestFriendModalOpen: false,
+ requestFriendModalStep: 'METHOD_LIST',
+ requestFriendModalNickname: '',
isGuideModalOpen: false,
});
+ const { nativeEventService } = useServiceContext();
const { clientFeatures } = useFeatureContext();
const { data: friends } = useFriends({ state: 'ACTIVE' });
+ const { mutate: acceptFriend } = useAcceptFriend();
+
+ const eventEmitter = nativeEventService.getEventEmitter();
useEffect(() => {
if (!clientFeatures.includes(ClientFeature.ASYNC_STORAGE)) return;
@@ -96,6 +108,31 @@ export const MainScreen = () => {
.catch(() => null);
}, [state.selectedFriendId, clientFeatures, friends]);
+ useEffect(() => {
+ const parameters = { eventType: 'add-friend-kakao' };
+
+ const listener = eventEmitter.addListener('add-friend-kakao', (event) => {
+ acceptFriend({
+ type: 'KAKAO',
+ requestToken: event.requstToken,
+ });
+ });
+
+ nativeEventService.sendEventToNative({
+ type: 'register',
+ parameters,
+ });
+
+ return () => {
+ listener.remove();
+
+ nativeEventService.sendEventToNative({
+ type: 'deregister',
+ parameters,
+ });
+ };
+ }, [eventEmitter, nativeEventService, acceptFriend]);
+
const backgroundColor = useThemeContext((data) => data.color.bg.default);
const selectedFriendIdWithDefault = state.selectedFriendId ?? friends?.at(0)?.friendId;
const { data: courseBooks } = useFriendCourseBooks(selectedFriendIdWithDefault);
@@ -107,16 +144,18 @@ export const MainScreen = () => {
() => ({
selectedFriendId: selectedFriendIdWithDefault,
selectedCourseBook: selectedCourseBookWithDefault,
- isAddFriendModalOpen: state.isAddFriendModalOpen,
- addFriendModalNickname: state.addFriendModalNickname,
+ isRequestFriendModalOpen: state.isRequestFriendModalOpen,
+ requestFriendModalStep: state.requestFriendModalStep,
+ requestFriendModalNickname: state.requestFriendModalNickname,
isGuideModalOpen: state.isGuideModalOpen,
dispatch,
}),
[
selectedFriendIdWithDefault,
selectedCourseBookWithDefault,
- state.isAddFriendModalOpen,
- state.addFriendModalNickname,
+ state.isRequestFriendModalOpen,
+ state.requestFriendModalStep,
+ state.requestFriendModalNickname,
state.isGuideModalOpen,
],
)}
@@ -132,18 +171,12 @@ export const MainScreen = () => {
};
const Header = ({ navigation }: DrawerHeaderProps) => {
- const { addFriendModalNickname, isAddFriendModalOpen, dispatch } = useMainScreenContext();
- const { friendService } = useServiceContext();
- const { mutate: request } = useRequestFriend();
- const guideEnabledColor = useThemeContext((data) => data.color.text.guide);
+ const { dispatch } = useMainScreenContext();
const { data: requestedFriends } = useFriends({ state: 'REQUESTED' });
const isRequestedFriendExist = requestedFriends && requestedFriends.length !== 0;
- const isValid = friendService.isValidNicknameTag(addFriendModalNickname);
- const guideMessageState = addFriendModalNickname === '' ? 'disabled' : isValid ? 'hidden' : 'enabled';
- const openAddFriendModal = () => dispatch({ type: 'setAddFriendModalOpen', isOpen: true });
- const closeAddFriendModal = () => dispatch({ type: 'setAddFriendModalOpen', isOpen: false });
+ const openAddFriendModal = () => dispatch({ type: 'setRequestFriendModalOpen', isOpen: true });
const openGuideModal = () => dispatch({ type: 'setGuideModalOpen', isOpen: true });
return (
@@ -170,52 +203,7 @@ const Header = ({ navigation }: DrawerHeaderProps) => {
}
/>
-
-
-
- request(addFriendModalNickname, {
- onSuccess: () => {
- Alert.alert('친구에게 요청을 보냈습니다.');
- closeAddFriendModal();
- },
- onError: (err) => {
- const displayMessage = get(err, ['displayMessage']);
- Alert.alert(displayMessage ? `${displayMessage}` : '오류가 발생했습니다.');
- },
- }),
- disabled: !isValid,
- }}
- />
-
- 추가하고 싶은 친구의 닉네임
-
- dispatch({ type: 'setAddFriendModalNickname', nickname: e })}
- placeholder="예) 홍길동#1234"
- />
-
- {guideMessageState !== 'hidden' &&
- (() => {
- const color = { enabled: guideEnabledColor, disabled: COLORS.gray40 }[guideMessageState];
- return (
- <>
-
-
- 닉네임 전체를 입력하세요
-
- >
- );
- })()}
-
-
-
+
>
);
};
@@ -224,7 +212,7 @@ const DrawerContent = ({ navigation }: DrawerContentComponentProps) => {
return navigation.closeDrawer()} />;
};
-const useRequestFriend = () => {
+export const useRequestFriend = () => {
const { friendService } = useServiceContext();
const queryClient = useQueryClient();
diff --git a/apps/friends-react-native/src/infrastructures/createFriendRepository.ts b/apps/friends-react-native/src/infrastructures/createFriendRepository.ts
index 6f8baba..2bedf8f 100644
--- a/apps/friends-react-native/src/infrastructures/createFriendRepository.ts
+++ b/apps/friends-react-native/src/infrastructures/createFriendRepository.ts
@@ -9,6 +9,7 @@ export const createFriendRepository = (apiClient: ApiClient): FriendRepository =
apiClient.get>>(`/v1/friends?state=${state}`),
requestFriend: ({ nickname }) => apiClient.post('/v1/friends', { nickname }),
acceptFriend: ({ friendId }) => apiClient.post(`/v1/friends/${friendId}/accept`),
+ acceptFriendWithKakao: ({ requestToken }) => apiClient.post(`/v1/friends/accept-link/${requestToken}`),
declineFriend: ({ friendId }) => apiClient.post(`/v1/friends/${friendId}/decline`),
deleteFriend: ({ friendId }) => apiClient.delete(`/v1/friends/${friendId}`),
getFriendPrimaryTable: ({ friendId, semester, year }) =>
@@ -16,5 +17,6 @@ export const createFriendRepository = (apiClient: ApiClient): FriendRepository =
getFriendCourseBooks: ({ friendId }) => apiClient.get(`/v1/friends/${friendId}/coursebooks`),
patchFriendDisplayName: ({ friendId, displayName }) =>
apiClient.patch(`/v1/friends/${friendId}/display-name`, { displayName }),
+ generateToken: () => apiClient.get<{ requestToken: string }>('/v1/friends/generate-link'),
};
};
diff --git a/apps/friends-react-native/src/infrastructures/createFriendService.ts b/apps/friends-react-native/src/infrastructures/createFriendService.ts
index b3af6ff..e1f6a6e 100644
--- a/apps/friends-react-native/src/infrastructures/createFriendService.ts
+++ b/apps/friends-react-native/src/infrastructures/createFriendService.ts
@@ -11,13 +11,17 @@ export const createFriendService = ({
friendRepository
.listFriends(req)
.then((res) => res.content.map((c) => ({ friendId: c.id, ...c.nickname, displayName: c.displayName }))),
- acceptFriend: (req) => friendRepository.acceptFriend(req),
+ acceptFriend: (req) =>
+ req.type === 'NICKNAME'
+ ? friendRepository.acceptFriend({ friendId: req.friendId })
+ : friendRepository.acceptFriendWithKakao({ requestToken: req.requestToken }),
declineFriend: (req) => friendRepository.declineFriend(req),
deleteFriend: (req) => friendRepository.deleteFriend(req),
requestFriend: (req) => friendRepository.requestFriend(req),
getFriendPrimaryTable: (req) => friendRepository.getFriendPrimaryTable(req),
getFriendCourseBooks: (req) => friendRepository.getFriendCourseBooks(req),
patchFriendDisplayName: (req) => friendRepository.patchFriendDisplayName(req),
+ generateToken: () => friendRepository.generateToken(),
formatNickname: (req, options = { type: 'default' }) => {
const displayName = req.displayName;
diff --git a/apps/friends-react-native/src/infrastructures/createNativeEventService.ts b/apps/friends-react-native/src/infrastructures/createNativeEventService.ts
new file mode 100644
index 0000000..2ec1542
--- /dev/null
+++ b/apps/friends-react-native/src/infrastructures/createNativeEventService.ts
@@ -0,0 +1,13 @@
+import { NativeEventEmitter, NativeModules } from 'react-native';
+import { NativeEvent, NativeEventService } from '../usecases/nativeEventService';
+
+export const createNativeEventService = (): NativeEventService => {
+ const eventEmitter = new NativeEventEmitter(NativeModules.RNEventEmitter);
+
+ return {
+ getEventEmitter: () => eventEmitter,
+ sendEventToNative: (event: NativeEvent) => {
+ NativeModules.RNEventEmitter.sendEventToNative(event.type, event.parameters);
+ },
+ };
+};
diff --git a/apps/friends-react-native/src/main.tsx b/apps/friends-react-native/src/main.tsx
index fe4d550..ca8beb0 100644
--- a/apps/friends-react-native/src/main.tsx
+++ b/apps/friends-react-native/src/main.tsx
@@ -18,6 +18,7 @@ import { createFetchClient } from './infrastructures/createFetchClient';
import { createFriendRepository } from './infrastructures/createFriendRepository';
import { createFriendService } from './infrastructures/createFriendService';
import { createTimetableViewService } from './infrastructures/createTimetableViewService';
+import { createNativeEventService } from './infrastructures/createNativeEventService';
type ExternalProps = {
'x-access-token': string;
@@ -48,10 +49,11 @@ export const Main = ({
const colorService = createColorService({ repositories: [createColorRepository({ clients: [fetchClient] })] });
const friendService = createFriendService({ repositories: [friendRepository] });
const courseBookService = createCourseBookService();
+ const nativeEventService = createNativeEventService();
const serviceValue = useMemo(
- () => ({ timetableViewService, colorService, friendService, courseBookService, assetService }),
- [timetableViewService, colorService, friendService, courseBookService, assetService],
+ () => ({ timetableViewService, colorService, friendService, courseBookService, assetService, nativeEventService }),
+ [timetableViewService, colorService, friendService, courseBookService, assetService, nativeEventService],
);
const themeValue = useMemo(() => getThemeValues(theme), [theme]);
diff --git a/apps/friends-react-native/src/repositories/friendRepository.ts b/apps/friends-react-native/src/repositories/friendRepository.ts
index b2c52e2..75a654b 100644
--- a/apps/friends-react-native/src/repositories/friendRepository.ts
+++ b/apps/friends-react-native/src/repositories/friendRepository.ts
@@ -23,6 +23,8 @@ export type FriendRepository = {
acceptFriend: (req: { friendId: FriendId }) => Promise;
+ acceptFriendWithKakao: (req: { requestToken: string }) => Promise;
+
declineFriend: (req: { friendId: FriendId }) => Promise;
deleteFriend: (req: { friendId: FriendId }) => Promise;
@@ -32,4 +34,6 @@ export type FriendRepository = {
getFriendCourseBooks: (req: { friendId: FriendId }) => Promise;
patchFriendDisplayName: (req: { friendId: FriendId; displayName: DisplayName }) => Promise;
+
+ generateToken: () => Promise<{ requestToken: string }>;
};
diff --git a/apps/friends-react-native/src/types/native.d.ts b/apps/friends-react-native/src/types/native.d.ts
new file mode 100644
index 0000000..9c83fdb
--- /dev/null
+++ b/apps/friends-react-native/src/types/native.d.ts
@@ -0,0 +1,11 @@
+import { NativeModule } from 'react-native';
+
+declare module 'react-native' {
+ interface NativeModulesStatic {
+ RNEventEmitter: NativeModule & {
+ sendEventToNative: (name: string, parameters?: Record) => void;
+ };
+ }
+}
+
+export {};
diff --git a/apps/friends-react-native/src/usecases/friendService.ts b/apps/friends-react-native/src/usecases/friendService.ts
index 7bd2c6b..3ece1a6 100644
--- a/apps/friends-react-native/src/usecases/friendService.ts
+++ b/apps/friends-react-native/src/usecases/friendService.ts
@@ -10,12 +10,15 @@ export type FriendService = {
state: 'ACTIVE' | 'REQUESTED' | 'REQUESTING';
}) => Promise<{ friendId: FriendId; nickname: Nickname; tag: NicknameTag; displayName?: DisplayName }[]>;
requestFriend: (req: { nickname: Nickname }) => Promise;
- acceptFriend: (req: { friendId: FriendId }) => Promise;
+ acceptFriend: (
+ req: { type: 'NICKNAME'; friendId: FriendId } | { type: 'KAKAO'; requestToken: string },
+ ) => Promise;
declineFriend: (req: { friendId: FriendId }) => Promise;
deleteFriend: (req: { friendId: FriendId }) => Promise;
getFriendPrimaryTable: (req: { friendId: FriendId; semester: Semester; year: Year }) => Promise;
getFriendCourseBooks: (req: { friendId: FriendId }) => Promise;
patchFriendDisplayName: (req: { friendId: FriendId; displayName: DisplayName }) => Promise;
+ generateToken: () => Promise<{ requestToken: string }>;
formatNickname: (
req: { nickname: Nickname; tag: NicknameTag; displayName?: DisplayName },
diff --git a/apps/friends-react-native/src/usecases/nativeEventService.ts b/apps/friends-react-native/src/usecases/nativeEventService.ts
new file mode 100644
index 0000000..163414e
--- /dev/null
+++ b/apps/friends-react-native/src/usecases/nativeEventService.ts
@@ -0,0 +1,10 @@
+import { NativeEventEmitter } from 'react-native';
+
+export type NativeEvent =
+ | { type: 'register' | 'deregister'; parameters: { eventType: string } }
+ | { type: 'add-friend-kakao'; parameters: { requestToken: string } };
+
+export type NativeEventService = {
+ getEventEmitter: () => NativeEventEmitter;
+ sendEventToNative: (event: NativeEvent) => void;
+};