From 13c738f1fbd44b641df42ddf077882fbab8e6e91 Mon Sep 17 00:00:00 2001 From: dioo1461 Date: Wed, 4 Feb 2026 17:24:43 +0900 Subject: [PATCH 1/9] =?UTF-8?q?fix:=20=EC=98=A4=EB=8A=98=EC=9D=98=20?= =?UTF-8?q?=EA=B3=B5=EB=B6=80=20=EA=B8=B0=EB=A1=9D=EC=9D=B4=200=EC=B4=88?= =?UTF-8?q?=EC=9D=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=EB=A5=BC=20?= =?UTF-8?q?=EA=B3=B5=EB=B6=80=20=EC=8B=9C=EC=9E=91=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/StudyDataHydrator.tsx | 7 +------ src/features/study/api/mutations.ts | 9 ++------- src/layout/StackableScreenLayout/index.tsx | 2 +- src/utils/Time/index.ts | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/StudyDataHydrator.tsx b/src/StudyDataHydrator.tsx index cedf5297..a1c166fb 100644 --- a/src/StudyDataHydrator.tsx +++ b/src/StudyDataHydrator.tsx @@ -36,13 +36,8 @@ const StudyDataHydrator = ({ children }: PropsWithChildren) => { const todayRecord = todayStudyRecords.find( record => record.categoryId === studyStateData.categoryId, ); - if (todayRecord == null) { - // eslint-disable-next-line no-console - console.error('Today study record must be defined when starting study session'); - return; - } - const recordedStudyTime = todayRecord.time; + const recordedStudyTime = todayRecord?.time ?? Time.fromMilliseconds(0); const progressiveStudyTime = Time.fromMilliseconds( new Date().getTime() - studyStateData.startTime.getTime(), ); diff --git a/src/features/study/api/mutations.ts b/src/features/study/api/mutations.ts index bae43475..74e4d90e 100644 --- a/src/features/study/api/mutations.ts +++ b/src/features/study/api/mutations.ts @@ -26,6 +26,7 @@ export const useStartStudyMutation = () => { studyStatByDateQueryOptions(new Date()), ); // TODO: 24시간이 넘어가는 공부시간에 대해 어떻게 처리할 것인지? + // TODO: StudyDataHydrator와 중복되는 로직 리팩토링 const category = studyDataUtils.identifyCategory({ categories: categories.data, categoryId: response.data.categoryId, @@ -35,13 +36,7 @@ export const useStartStudyMutation = () => { const todayRecord = todayStudyRecords.find( record => record.categoryId === response.data.categoryId, ); - if (todayRecord == null) { - // eslint-disable-next-line no-console - console.error('Today study record must be defined when starting study session'); - return; - } - - const recordedStudyTime = todayRecord.time; + const recordedStudyTime = todayRecord?.time ?? Time.fromMilliseconds(0); const progressiveStudyTime = Time.fromMilliseconds( new Date().getTime() - response.data.startTime.getTime(), ); diff --git a/src/layout/StackableScreenLayout/index.tsx b/src/layout/StackableScreenLayout/index.tsx index ca716b36..b6f3653c 100644 --- a/src/layout/StackableScreenLayout/index.tsx +++ b/src/layout/StackableScreenLayout/index.tsx @@ -24,7 +24,7 @@ const StackableScreenLayout = ({ headerElementColor = theme.color.primary[500], header, children, - safeAreas, + safeAreas = 'top', ...screenContainerProps }: StackableScreenProps) => { const navigation = useStackNavigation(); diff --git a/src/utils/Time/index.ts b/src/utils/Time/index.ts index f0c9b5dd..e054dffa 100644 --- a/src/utils/Time/index.ts +++ b/src/utils/Time/index.ts @@ -65,7 +65,7 @@ export class Time { const diff = totalMilliseconds1 - totalMilliseconds2; if (diff < 0) { // eslint-disable-next-line no-console - console.warn('Resulting time is negative. Returning zero time.'); + console.log('[Time] Resulting time is negative. Returning zero time.'); return Time.fromMilliseconds(0); } return Time.fromMilliseconds(diff); From 9cb07ccad46a29a8276e7ffa521006fcea4f31e2 Mon Sep 17 00:00:00 2001 From: dioo1461 Date: Wed, 4 Feb 2026 18:16:46 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=EB=B3=84=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EC=83=89=EC=83=81=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=ED=95=A0=EB=8B=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/groups/hooks/useGroupColorKey.ts | 47 +++++++++++++++++ .../components/JoinedGroupPanel/index.tsx | 51 +++++++++++-------- .../hooks/useGroupChatBootstrap.ts | 4 +- .../hooks/useGroupChatRealtime.ts | 4 +- src/screens/group/GroupChatScreen/index.tsx | 6 +-- src/utils/asyncStorage/index.ts | 3 +- 6 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 src/features/groups/hooks/useGroupColorKey.ts diff --git a/src/features/groups/hooks/useGroupColorKey.ts b/src/features/groups/hooks/useGroupColorKey.ts new file mode 100644 index 00000000..e9fd7bda --- /dev/null +++ b/src/features/groups/hooks/useGroupColorKey.ts @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; + +import type { GroupColorKey } from '@/constants/groupColors'; +import { GROUP_COLORS } from '@/constants/groupColors'; +import { useProfileQuery } from '@/features/user/api/queries'; +import { ASYNC_KEYS, asyncStorage } from '@/utils/asyncStorage'; + +const GROUP_COLOR_KEYS = Object.keys(GROUP_COLORS) as GroupColorKey[]; + +export const useGroupColorKey = (groupId: number | null): GroupColorKey | null => { + const profileData = useProfileQuery().data; + const userId = profileData?.id; + + const [colorKey, setColorKey] = useState(null); + + useEffect(() => { + if (userId == null) return; + let cancelled = false; + if (groupId == null) { + setColorKey(null); + return; + } + + (async () => { + const stored = await asyncStorage.get(ASYNC_KEYS.groupColor(userId, groupId)); + if (stored && isGroupColorKey(stored)) { + if (!cancelled) setColorKey(stored); + return; + } + const newKey = pickRandomGroupColorKey(); + await asyncStorage.set(ASYNC_KEYS.groupColor(userId, groupId), newKey); + if (!cancelled) setColorKey(newKey); + })(); + + return () => { + cancelled = true; + }; + }, [userId, groupId]); + + return colorKey; +}; + +const pickRandomGroupColorKey = (): GroupColorKey => + GROUP_COLOR_KEYS[Math.floor(Math.random() * GROUP_COLOR_KEYS.length)]; + +const isGroupColorKey = (v: string): v is GroupColorKey => + (GROUP_COLOR_KEYS as readonly string[]).includes(v); \ No newline at end of file diff --git a/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx b/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx index c485559c..d5298b10 100644 --- a/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx +++ b/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx @@ -2,6 +2,8 @@ import { GroupButton } from '@features/groups/ui'; import { Button, Icon, Panel } from '@/components'; import { useGroupsQuery } from '@/features/groups/api/queries'; +import { useGroupColorKey } from '@/features/groups/hooks/useGroupColorKey'; +import type { GetGroupsResponse } from '@/features/groups/model/groups'; import { useStackNavigation } from '@/hooks'; import { useTranslatedText } from '@/hooks/useTranslatedText'; import { theme } from '@/theme'; @@ -14,34 +16,14 @@ const JoinedGroupPanel = () => { const tCreateNewGroup = useTranslatedText({ tKey: 'groups.create-new-group' }); const navigation = useStackNavigation(); const { data, isPending } = useGroupsQuery(); - + if (!data || isPending) return null; return ( {data.length ? - data.map(group => { - const leaderImageUrl = - group.profiles.find(profile => profile.role === 'LEADER')?.imageUrl ?? null; - const otherImageUrls = group.profiles - .filter(profile => profile.role !== 'LEADER') - .map(profile => profile.imageUrl); - return ( - - ); - }) + data.map(group => ) : } { ); }; +const GroupListItem = ({ group }: { group: GetGroupsResponse['data'][number] }) => { + const groupColorKey = useGroupColorKey(group.groupId); + if (!groupColorKey) return null; + + const leaderImageUrl = + group.profiles.find(p => p.role === 'LEADER')?.imageUrl ?? null; + + const otherImageUrls = group.profiles + .filter(p => p.role !== 'LEADER') + .map(p => p.imageUrl); + + return ( + + ); +}; + export default JoinedGroupPanel; diff --git a/src/screens/group/GroupChatScreen/hooks/useGroupChatBootstrap.ts b/src/screens/group/GroupChatScreen/hooks/useGroupChatBootstrap.ts index ce95ffe7..9a193efd 100644 --- a/src/screens/group/GroupChatScreen/hooks/useGroupChatBootstrap.ts +++ b/src/screens/group/GroupChatScreen/hooks/useGroupChatBootstrap.ts @@ -51,7 +51,7 @@ export const useGroupChatBootstrap = ({ await loadMoreLocal(); const cursor = - (await asyncStorage.get(ASYNC_KEYS.groupChatCursor(chatRoomId))) ?? undefined; + (await asyncStorage.get(ASYNC_KEYS.groupChatCursor(myUserId, chatRoomId))) ?? undefined; const chatData = await queryClient.ensureQueryData( chatRoomInfoAfterCursorQueryOptions(chatRoomId, cursor), @@ -71,7 +71,7 @@ export const useGroupChatBootstrap = ({ const entities = fetchedChats.map(chat => chatObjToEntity(chat, chatRoomId)); await storeChats(entities); const recentChatId = fetchedChats[fetchedChats.length - 1].chatId; - asyncStorage.set(ASYNC_KEYS.groupChatCursor(chatRoomId), recentChatId); + asyncStorage.set(ASYNC_KEYS.groupChatCursor(myUserId, chatRoomId), recentChatId); sendReadCursor({ roomId: chatRoomId, type: 'READ', content: recentChatId }); } diff --git a/src/screens/group/GroupChatScreen/hooks/useGroupChatRealtime.ts b/src/screens/group/GroupChatScreen/hooks/useGroupChatRealtime.ts index a659d344..9b2a98fe 100644 --- a/src/screens/group/GroupChatScreen/hooks/useGroupChatRealtime.ts +++ b/src/screens/group/GroupChatScreen/hooks/useGroupChatRealtime.ts @@ -13,7 +13,7 @@ import { chatObjToEntity } from '../utils'; type Params = { isConnected: boolean; chatRoomId: string; - myUserId: number | null; + myUserId: number; memberMap: Map; tUnknown: string; @@ -55,7 +55,7 @@ export const useGroupChatRealtime = ({ }; await storeChats([chatObjToEntity(newChat, chatRoomId)]); - await asyncStorage.set(ASYNC_KEYS.groupChatCursor(chatRoomId), message.id); + await asyncStorage.set(ASYNC_KEYS.groupChatCursor(myUserId, chatRoomId), message.id); sendReadCursor({ roomId: chatRoomId, type: 'READ', content: message.id }); if (message.sender !== myUserId) { diff --git a/src/screens/group/GroupChatScreen/index.tsx b/src/screens/group/GroupChatScreen/index.tsx index 86005f6d..4d458ec9 100644 --- a/src/screens/group/GroupChatScreen/index.tsx +++ b/src/screens/group/GroupChatScreen/index.tsx @@ -37,7 +37,7 @@ const GroupChatScreen = () => { const { loadMoreChats, storeChats } = useChatDB({ chatRoomId, - userId: profileData?.id ?? null, + userId: profileData?.id ?? -1, }); const [chats, setChats] = useState([]); @@ -50,14 +50,14 @@ const GroupChatScreen = () => { loadMoreChats, storeChats, setChats, - myUserId: profileData?.id ?? null, + myUserId: profileData?.id ?? -1, sendReadCursor, }); useGroupChatRealtime({ isConnected, chatRoomId, - myUserId: profileData?.id ?? null, + myUserId: profileData?.id ?? -1, memberMap, tUnknown, storeChats, diff --git a/src/utils/asyncStorage/index.ts b/src/utils/asyncStorage/index.ts index 882b23e4..0676b5c3 100644 --- a/src/utils/asyncStorage/index.ts +++ b/src/utils/asyncStorage/index.ts @@ -14,5 +14,6 @@ export const asyncStorage: AsyncStorage = { export const ASYNC_KEYS = { language: 'language', - groupChatCursor: (chatRoomId: string) => `groupChat:${chatRoomId}`, + groupChatCursor: (userId: number, chatRoomId: string) => `groupChat:${userId}-${chatRoomId}`, + groupColor: (userId: number, groupId: number) => `groupColor:${userId}-${groupId}`, }; \ No newline at end of file From ebf783e30293d913638f2f2f417ac8ca6c96f849 Mon Sep 17 00:00:00 2001 From: dioo1461 Date: Wed, 4 Feb 2026 18:21:31 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=EA=B7=B8=EB=A3=B9=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=ED=99=94=EB=A9=B4=EB=93=A4=EC=9D=B4=20groupColorKe?= =?UTF-8?q?y=EB=A5=BC=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/group/GroupRankingScreen/index.tsx | 2 +- src/screens/group/GroupSettingScreen/index.tsx | 2 +- src/screens/group/groupDetail/GroupDetailScreen/index.tsx | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/screens/group/GroupRankingScreen/index.tsx b/src/screens/group/GroupRankingScreen/index.tsx index 07bfc871..5f0b4d30 100644 --- a/src/screens/group/GroupRankingScreen/index.tsx +++ b/src/screens/group/GroupRankingScreen/index.tsx @@ -28,7 +28,7 @@ const GroupRankingScreen = () => { return ( diff --git a/src/screens/group/GroupSettingScreen/index.tsx b/src/screens/group/GroupSettingScreen/index.tsx index b2afea7c..f29ae6d8 100644 --- a/src/screens/group/GroupSettingScreen/index.tsx +++ b/src/screens/group/GroupSettingScreen/index.tsx @@ -17,7 +17,7 @@ const GroupSettingScreen = () => { return ( diff --git a/src/screens/group/groupDetail/GroupDetailScreen/index.tsx b/src/screens/group/groupDetail/GroupDetailScreen/index.tsx index b869d773..7199b684 100644 --- a/src/screens/group/groupDetail/GroupDetailScreen/index.tsx +++ b/src/screens/group/groupDetail/GroupDetailScreen/index.tsx @@ -1,4 +1,4 @@ -import { useFocusEffect } from '@react-navigation/native'; +import { useFocusEffect, useRoute } from '@react-navigation/native'; import type { Day } from 'date-fns'; import { useCallback, useMemo, useState } from 'react'; @@ -16,7 +16,7 @@ import GroupInfoPanel from '../components/GroupInfoPanel'; import TodaysGoalPanel from '../components/TodaysGoalPanel'; const GroupDetailScreen = () => { - const groupColor = GROUP_COLORS[theme.color.blue[300]]; + const groupColor = GROUP_COLORS[useStackRoute<'GroupDetail'>().params.groupColorKey]; const { data: categories } = useCategoriesQuery(); const { params } = useStackRoute<'GroupDetail'>(); @@ -57,8 +57,8 @@ const GroupDetailScreen = () => { return ( Date: Wed, 4 Feb 2026 18:37:06 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EA=B7=B8=EB=A3=B9=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EB=B3=80=EA=B2=BD=20=EB=B2=84=ED=8A=BC=20=EB=A7=88?= =?UTF-8?q?=ED=81=AC=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/icons/icon-palette.svg | 3 ++ src/components/Icon/IconSet.tsx | 37 ++++++++++--------- src/locales/ko/groupDetail.json | 2 +- .../DashBoardButtonGroup/index.tsx | 18 ++++----- 4 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 assets/icons/icon-palette.svg diff --git a/assets/icons/icon-palette.svg b/assets/icons/icon-palette.svg new file mode 100644 index 00000000..179eee68 --- /dev/null +++ b/assets/icons/icon-palette.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Icon/IconSet.tsx b/src/components/Icon/IconSet.tsx index 270ebdca..a6f09ae9 100644 --- a/src/components/Icon/IconSet.tsx +++ b/src/components/Icon/IconSet.tsx @@ -32,6 +32,7 @@ import NotiOffOutline from '@icons/icon-noti-off-outline.svg' import SendRight from '@icons/icon-send-right.svg' import TrashBin from '@icons/icon-trash-bin.svg' import Camera from '@icons/icon-camera.svg' +import Palette from '@icons/icon-palette.svg' import type { Icon, MultiIcon } from '@/types/icon' import { SvgProps } from 'react-native-svg' @@ -86,6 +87,7 @@ type IconSetComponent = { SendRight: Icon; TrashBin: Icon; Camera: Icon; + Palette: Icon; } const IconSet: IconSetComponent = { @@ -106,23 +108,24 @@ const IconSet: IconSetComponent = { DogFootprint: withDefaultProps(DogFootprint, { color: theme.color.red[100] }), ArrowLeft: withDefaultProps(ArrowLeft, { color: COLOR_GRAY }), Edit: withDefaultProps(Edit, { color: COLOR_GRAY }), - Study: withDefaultProps(Study, { color: theme.color.neutral['white'] }), - LockFilled: withDefaultProps(LockFilled, { color: theme.color.neutral[500] }), - LockOutline: withDefaultProps(LockOutline, { color: theme.color.neutral[500] }), - UnlockFilled: withDefaultProps(UnlockFilled, { color: theme.color.neutral[500] }), - Invite: withDefaultProps(Invite, { color: theme.color.neutral[500] }), - Graph: withDefaultProps(Graph, { color: theme.color.neutral[500] }), - Ranking: withDefaultProps(Ranking, { color: theme.color.neutral[500] }), - Chat: withDefaultProps(Chat, { color: theme.color.neutral[500] }), - More: withDefaultProps(More, { color: theme.color.neutral[500] }), - Person: withDefaultProps(Person, { color: theme.color.neutral[500] }), - DoorOpened: withDefaultProps(DoorOpened, { color: theme.color.neutral[500] }), - Setting: withDefaultProps(Setting, { color: theme.color.neutral[500] }), - NotiOnFilled: withDefaultProps(NotiOnFilled, { color: theme.color.neutral[500] }), - NotiOffOutline: withDefaultProps(NotiOffOutline, { color: theme.color.neutral[500] }), - SendRight: withDefaultProps(SendRight, { color: theme.color.neutral[500] }), - TrashBin: withDefaultProps(TrashBin, { color: theme.color.neutral[500] }), - Camera: withDefaultProps(Camera, { color: theme.color.neutral['white'] }), + Study: withDefaultProps(Study, { color: COLOR_WHITE }), + LockFilled: withDefaultProps(LockFilled, { color: COLOR_GRAY }), + LockOutline: withDefaultProps(LockOutline, { color: COLOR_GRAY }), + UnlockFilled: withDefaultProps(UnlockFilled, { color: COLOR_GRAY }), + Invite: withDefaultProps(Invite, { color: COLOR_GRAY }), + Graph: withDefaultProps(Graph, { color: COLOR_GRAY }), + Ranking: withDefaultProps(Ranking, { color: COLOR_GRAY }), + Chat: withDefaultProps(Chat, { color: COLOR_GRAY }), + More: withDefaultProps(More, { color: COLOR_GRAY }), + Person: withDefaultProps(Person, { color: COLOR_GRAY }), + DoorOpened: withDefaultProps(DoorOpened, { color: COLOR_GRAY }), + Setting: withDefaultProps(Setting, { color: COLOR_GRAY }), + NotiOnFilled: withDefaultProps(NotiOnFilled, { color: COLOR_GRAY }), + NotiOffOutline: withDefaultProps(NotiOffOutline, { color: COLOR_GRAY }), + SendRight: withDefaultProps(SendRight, { color: COLOR_GRAY }), + TrashBin: withDefaultProps(TrashBin, { color: COLOR_GRAY }), + Camera: withDefaultProps(Camera, { color: COLOR_WHITE }), + Palette: withDefaultProps(Palette, { color: COLOR_GRAY }), }; export default IconSet; diff --git a/src/locales/ko/groupDetail.json b/src/locales/ko/groupDetail.json index 0e3a16f6..da32917a 100644 --- a/src/locales/ko/groupDetail.json +++ b/src/locales/ko/groupDetail.json @@ -3,7 +3,7 @@ "public-group": "공개 그룹", "private-group": "비공개 그룹", "ranking": "랭킹", - "statistics": "통계", + "color": "색상", "chat": "채팅", "setting": "설정", "member-list": "멤버 목록", diff --git a/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx b/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx index 27a518f7..3dc7febc 100644 --- a/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx +++ b/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx @@ -33,7 +33,7 @@ const DashBoardButtonGroup = ({ disabled, }: DashBoardButtonGroupProps) => { const tRanking = useTranslatedText({ tKey: 'groupDetail.ranking' }); - const tStatistics = useTranslatedText({ tKey: 'groupDetail.statistics' }); + const tColor = useTranslatedText({ tKey: 'groupDetail.color' }); const tSetting = useTranslatedText({ tKey: 'groupDetail.setting' }); const tCanUseAfterJoining = useTranslatedText({ tKey: 'groupDetail.can-use-after-joining' }); const navigation = useStackNavigation(); @@ -52,7 +52,7 @@ const DashBoardButtonGroup = ({ }); }; - const handleStatisticsPress = () => { + const handleColorPress = () => { if (disabled) { Toast.show(tCanUseAfterJoining); return; @@ -92,19 +92,19 @@ const DashBoardButtonGroup = ({ onPress={handleRankingPress} title={tRanking} /> - + {isHost && Date: Wed, 4 Feb 2026 20:02:32 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20CategoryProgressItem=EC=97=90=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EC=83=89=EC=83=81=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/hooks/useCategoryColor.ts | 39 +++++++++ .../StudyBottomSheet/core/GoalItemList.tsx | 84 ++++++++++++------- .../components/SelectedDateStat/index.tsx | 54 +++++++----- 3 files changed, 127 insertions(+), 50 deletions(-) create mode 100644 src/features/category/hooks/useCategoryColor.ts diff --git a/src/features/category/hooks/useCategoryColor.ts b/src/features/category/hooks/useCategoryColor.ts new file mode 100644 index 00000000..f45f9659 --- /dev/null +++ b/src/features/category/hooks/useCategoryColor.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react'; + +import { type GroupColorKey } from '@/constants/groupColors'; +import { useGroupsQuery } from '@/features/groups/api/queries'; +import { useProfileQuery } from '@/features/user/api/queries'; +import { theme } from '@/theme'; +import { ASYNC_KEYS, asyncStorage } from '@/utils/asyncStorage'; + +import { useCategoriesQuery } from '../api/queries'; + +const DEFAULT_COLOR_KEY: GroupColorKey = theme.color.primary[500]; + +export const useCategoryColor = (categoryId: number) => { + const [colorKey, setColorKey] = useState(DEFAULT_COLOR_KEY); + const { data: groupsData } = useGroupsQuery(); + const { data: categoriesData } = useCategoriesQuery(); + const { data: profileData } = useProfileQuery(); + + useEffect(() => { + if (groupsData == null || categoriesData == null || profileData == null) { + setColorKey(DEFAULT_COLOR_KEY); + return; + } + + (async () => { + const matchingGroup = groupsData.find((group) => group.categoryIds.includes(categoryId)); + if (matchingGroup) { + const groupColorKey = await asyncStorage.get( + ASYNC_KEYS.groupColor(profileData.id, matchingGroup.groupId), + ) as GroupColorKey | null; + setColorKey(groupColorKey ?? DEFAULT_COLOR_KEY); + } else { + setColorKey(DEFAULT_COLOR_KEY); + } + })(); + }, [categoryId, groupsData, categoriesData, profileData]); + + return colorKey; +}; diff --git a/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx b/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx index 0f285c85..25c346cd 100644 --- a/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx +++ b/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx @@ -2,10 +2,13 @@ import { startOfDay } from 'date-fns'; import type { GroupColorKey } from '@/constants/groupColors'; import { useCategoriesQuery } from '@/features/category/api/queries'; +import { useCategoryColor } from '@/features/category/hooks/useCategoryColor'; +import type { GetCategoriesResponse } from '@/features/category/model'; import type { DAY_MASK_BY_UTC_CODE } from '@/features/category/model/types'; import { CategoryProgressItem } from '@/features/category/ui'; import { useStartStudyMutation } from '@/features/study/api/mutations'; import { useStudyStatByDateQuery } from '@/features/study/api/queries'; +import type { GetStudyStatByDateResponse } from '@/features/study/model'; import { formatDateToHMS, getDayByUTCCode } from '@/utils/date'; import { Time } from '@/utils/Time'; @@ -22,8 +25,6 @@ const GoalItemList = ({ }: GoalItemListProps) => { const { data: categoryData, isPending: isCategoryDataPending } = useCategoriesQuery(); const { data: studyData, isPending: isStudyDataPending } = useStudyStatByDateQuery(selectedDate); - - const { startStudyMutate } = useStartStudyMutation(); if (!categoryData || isCategoryDataPending) return null; if (!studyData || isStudyDataPending) return null; @@ -37,38 +38,61 @@ const GoalItemList = ({ getDayByUTCCode(selectedDate.getDay() as keyof typeof DAY_MASK_BY_UTC_CODE) ]; }) - .map(category => { - const onStudyButtonPress = () => { - startStudyMutate({ - categoryId: category.id, - // TODO: 임시 카테고리 기능 구현 후 수정 필요 - temporaryName: null, - startTime: formatDateToHMS(new Date()), - }, { - onSuccess: () => { - setBottomSheetEnabled(false); - }, - }); - }; - const studyTime = - studyData.find(stat => stat.categoryId === category.id)?.time ?? Time.fromSeconds(0); + .map(category => + ); + + return ( + categoryNodes.length ? categoryNodes : + ); +}; - return ( - - ); +const GoalItemListElement = ({ + category, + studyData, + setBottomSheetEnabled, + selectedDate, +}: { + category: GetCategoriesResponse['data'][number]; + studyData: GetStudyStatByDateResponse['data']; + setBottomSheetEnabled: (enabled: boolean) => void; + selectedDate: Date; +}) => { + const { startStudyMutate } = useStartStudyMutation(); + const categoryColor = useCategoryColor(category.id); + + const onStudyButtonPress = () => { + startStudyMutate({ + categoryId: category.id, + // TODO: 임시 카테고리 기능 구현 후 수정 필요 + temporaryName: null, + startTime: formatDateToHMS(new Date()), + }, { + onSuccess: () => { + setBottomSheetEnabled(false); + }, }); + }; + const studyTime = studyData.find( + stat => stat.categoryId === category.id, + )?.time ?? Time.fromSeconds(0); return ( - categoryNodes.length ? categoryNodes : + ); }; diff --git a/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx b/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx index a7798ce9..5b15849a 100644 --- a/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx +++ b/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx @@ -2,8 +2,11 @@ import { View } from 'react-native'; import { TText } from '@/components'; import { useCategoriesQuery } from '@/features/category/api/queries'; +import { useCategoryColor } from '@/features/category/hooks/useCategoryColor'; +import type { GetCategoriesResponse } from '@/features/category/model'; import CategoryProgressItem from '@/features/category/ui/CategoryProgressItem'; import { useStudyStatByDateQuery } from '@/features/study/api/queries'; +import type { GetStudyStatByDateResponse } from '@/features/study/model'; import { studyDataUtils } from '@/store/studySlice'; import { theme } from '@/theme'; import { Time } from '@/utils/Time'; @@ -46,28 +49,39 @@ const SelectedDateStat = ({ selectedDate }: SelectedDateStatProps) => { }), }} /> - { - studyStatData.map(studyStat => { - const categoryInfo = (studyStat.categoryId) ? - studyDataUtils.getCategoryById(categoryData, studyStat.categoryId) - : studyDataUtils.getCategoryByTemporaryName(categoryData, studyStat.name!); - return ( - // TODO: groupColorKey 설정하기 - - ); - }) - } + {studyStatData.map(studyStat => + )} ); }; +const Item = ({ + studyStat, + categoryData, +}: { + studyStat: GetStudyStatByDateResponse['data'][number]; + categoryData: GetCategoriesResponse['data']; +}) => { + const categoryInfo = (studyStat.categoryId) ? + studyDataUtils.getCategoryById(categoryData, studyStat.categoryId) + : studyDataUtils.getCategoryByTemporaryName(categoryData, studyStat.name!); + const categoryColor = useCategoryColor(categoryInfo.id); + + return ( + + ); +}; + export default SelectedDateStat; \ No newline at end of file From 53eba0ed1458ad9c302be5391d0719909ae4d702 Mon Sep 17 00:00:00 2001 From: dioo1461 Date: Wed, 4 Feb 2026 20:50:16 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EA=B7=B8=EB=A3=B9=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/hooks/useCategoryColor.ts | 36 +++------- src/features/groups/hooks/useGroupColorKey.ts | 69 ++++++++++--------- .../components/JoinedGroupPanel/index.tsx | 2 +- .../StudyBottomSheet/core/GoalItemList.tsx | 3 +- .../components/SelectedDateStat/index.tsx | 3 +- .../groupDetail/GroupDetailScreen/index.tsx | 9 +-- .../ColorPickItem/index.style.ts | 23 +++++++ .../ColorPickItem/index.tsx | 30 ++++++++ .../GroupColorPickerDialog/index.style.ts | 8 +++ .../GroupColorPickerDialog/index.tsx | 37 ++++++++++ .../DashBoardButtonGroup/index.tsx | 44 ++++++------ 11 files changed, 172 insertions(+), 92 deletions(-) create mode 100644 src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts create mode 100644 src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx create mode 100644 src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts create mode 100644 src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx diff --git a/src/features/category/hooks/useCategoryColor.ts b/src/features/category/hooks/useCategoryColor.ts index f45f9659..303e0432 100644 --- a/src/features/category/hooks/useCategoryColor.ts +++ b/src/features/category/hooks/useCategoryColor.ts @@ -1,39 +1,19 @@ -import { useEffect, useState } from 'react'; import { type GroupColorKey } from '@/constants/groupColors'; import { useGroupsQuery } from '@/features/groups/api/queries'; -import { useProfileQuery } from '@/features/user/api/queries'; +import { useGroupColorKey } from '@/features/groups/hooks/useGroupColorKey'; import { theme } from '@/theme'; -import { ASYNC_KEYS, asyncStorage } from '@/utils/asyncStorage'; - -import { useCategoriesQuery } from '../api/queries'; const DEFAULT_COLOR_KEY: GroupColorKey = theme.color.primary[500]; export const useCategoryColor = (categoryId: number) => { - const [colorKey, setColorKey] = useState(DEFAULT_COLOR_KEY); const { data: groupsData } = useGroupsQuery(); - const { data: categoriesData } = useCategoriesQuery(); - const { data: profileData } = useProfileQuery(); - useEffect(() => { - if (groupsData == null || categoriesData == null || profileData == null) { - setColorKey(DEFAULT_COLOR_KEY); - return; - } + const matchingGroupId = + groupsData?.find((g) => g.categoryIds.includes(categoryId))?.groupId ?? null; + + const { colorKey: groupColor } = useGroupColorKey(matchingGroupId); + const colorKey = groupColor ?? DEFAULT_COLOR_KEY; - (async () => { - const matchingGroup = groupsData.find((group) => group.categoryIds.includes(categoryId)); - if (matchingGroup) { - const groupColorKey = await asyncStorage.get( - ASYNC_KEYS.groupColor(profileData.id, matchingGroup.groupId), - ) as GroupColorKey | null; - setColorKey(groupColorKey ?? DEFAULT_COLOR_KEY); - } else { - setColorKey(DEFAULT_COLOR_KEY); - } - })(); - }, [categoryId, groupsData, categoriesData, profileData]); - - return colorKey; -}; + return { colorKey }; +}; \ No newline at end of file diff --git a/src/features/groups/hooks/useGroupColorKey.ts b/src/features/groups/hooks/useGroupColorKey.ts index e9fd7bda..4e546ea8 100644 --- a/src/features/groups/hooks/useGroupColorKey.ts +++ b/src/features/groups/hooks/useGroupColorKey.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import type { GroupColorKey } from '@/constants/groupColors'; import { GROUP_COLORS } from '@/constants/groupColors'; @@ -7,41 +7,44 @@ import { ASYNC_KEYS, asyncStorage } from '@/utils/asyncStorage'; const GROUP_COLOR_KEYS = Object.keys(GROUP_COLORS) as GroupColorKey[]; -export const useGroupColorKey = (groupId: number | null): GroupColorKey | null => { - const profileData = useProfileQuery().data; +const pickRandomGroupColorKey = (): GroupColorKey => + GROUP_COLOR_KEYS[Math.floor(Math.random() * GROUP_COLOR_KEYS.length)]; + +const isGroupColorKey = (v: string): v is GroupColorKey => + (GROUP_COLOR_KEYS as readonly string[]).includes(v); + +export const useGroupColorKey = (groupId: number | null) => { + const { data: profileData } = useProfileQuery(); const userId = profileData?.id; + const qc = useQueryClient(); - const [colorKey, setColorKey] = useState(null); - - useEffect(() => { - if (userId == null) return; - let cancelled = false; - if (groupId == null) { - setColorKey(null); - return; - } - - (async () => { - const stored = await asyncStorage.get(ASYNC_KEYS.groupColor(userId, groupId)); - if (stored && isGroupColorKey(stored)) { - if (!cancelled) setColorKey(stored); - return; - } - const newKey = pickRandomGroupColorKey(); - await asyncStorage.set(ASYNC_KEYS.groupColor(userId, groupId), newKey); - if (!cancelled) setColorKey(newKey); - })(); + const queryKey = ['groupColor', userId, groupId]; - return () => { - cancelled = true; - }; - }, [userId, groupId]); + const { data: colorKey } = useQuery({ + queryKey, + enabled: userId != null && groupId != null, + queryFn: async () => { + if (userId == null || groupId == null) return null; - return colorKey; -}; + const storageKey = ASYNC_KEYS.groupColor(userId, groupId); + const stored = await asyncStorage.get(storageKey); -const pickRandomGroupColorKey = (): GroupColorKey => - GROUP_COLOR_KEYS[Math.floor(Math.random() * GROUP_COLOR_KEYS.length)]; + if (stored && isGroupColorKey(stored)) return stored; -const isGroupColorKey = (v: string): v is GroupColorKey => - (GROUP_COLOR_KEYS as readonly string[]).includes(v); \ No newline at end of file + const newKey = pickRandomGroupColorKey(); + await asyncStorage.set(storageKey, newKey); + return newKey; + }, + staleTime: Infinity, + gcTime: Infinity, + }); + + const updateColorKey = async (next: GroupColorKey) => { + if (userId == null || groupId == null) return; + + qc.setQueryData(queryKey, next); + await asyncStorage.set(ASYNC_KEYS.groupColor(userId, groupId), next); + }; + + return { colorKey: colorKey ?? null, updateColorKey }; +}; diff --git a/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx b/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx index d5298b10..e51f0d3b 100644 --- a/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx +++ b/src/screens/bottomTab/GroupsScreen/components/JoinedGroupPanel/index.tsx @@ -38,7 +38,7 @@ const JoinedGroupPanel = () => { }; const GroupListItem = ({ group }: { group: GetGroupsResponse['data'][number] }) => { - const groupColorKey = useGroupColorKey(group.groupId); + const { colorKey: groupColorKey } = useGroupColorKey(group.groupId); if (!groupColorKey) return null; const leaderImageUrl = diff --git a/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx b/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx index 25c346cd..b7bcf499 100644 --- a/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx +++ b/src/screens/bottomTab/HomeScreen/components/StudyBottomSheet/core/GoalItemList.tsx @@ -1,6 +1,5 @@ import { startOfDay } from 'date-fns'; -import type { GroupColorKey } from '@/constants/groupColors'; import { useCategoriesQuery } from '@/features/category/api/queries'; import { useCategoryColor } from '@/features/category/hooks/useCategoryColor'; import type { GetCategoriesResponse } from '@/features/category/model'; @@ -64,7 +63,7 @@ const GoalItemListElement = ({ selectedDate: Date; }) => { const { startStudyMutate } = useStartStudyMutation(); - const categoryColor = useCategoryColor(category.id); + const { colorKey: categoryColor } = useCategoryColor(category.id); const onStudyButtonPress = () => { startStudyMutate({ diff --git a/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx b/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx index 5b15849a..2bbce398 100644 --- a/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx +++ b/src/screens/bottomTab/StatsScreen/components/SelectedDateStat/index.tsx @@ -8,7 +8,6 @@ import CategoryProgressItem from '@/features/category/ui/CategoryProgressItem'; import { useStudyStatByDateQuery } from '@/features/study/api/queries'; import type { GetStudyStatByDateResponse } from '@/features/study/model'; import { studyDataUtils } from '@/store/studySlice'; -import { theme } from '@/theme'; import { Time } from '@/utils/Time'; import { SELECTED_DATE_HEADER_TITLE_TEXT_STYLE, TOTAL_STUDY_TIME_TEXT_STYLE } from './index.style'; @@ -69,7 +68,7 @@ const Item = ({ const categoryInfo = (studyStat.categoryId) ? studyDataUtils.getCategoryById(categoryData, studyStat.categoryId) : studyDataUtils.getCategoryByTemporaryName(categoryData, studyStat.name!); - const categoryColor = useCategoryColor(categoryInfo.id); + const { colorKey: categoryColor } = useCategoryColor(categoryInfo.id); return ( { - const groupColor = GROUP_COLORS[useStackRoute<'GroupDetail'>().params.groupColorKey]; - const { data: categories } = useCategoriesQuery(); const { params } = useStackRoute<'GroupDetail'>(); + const { colorKey } = useGroupColorKey(params.groupId); + const groupColor = colorKey ? GROUP_COLORS[colorKey] : null; const { data, isPending } = useGroupDetailQuery(params.groupId); const [chatCount, setChatCount] = useState(null); @@ -54,6 +54,7 @@ const GroupDetailScreen = () => { // TODO: Fallback UI if (isPending || !data) return null; + if (!groupColor) return null; return ( StyleSheet.create({ + container: { + width: 50, + height: 50, + borderRadius: theme.radius['max'], + borderWidth: selected ? 3 : 1, + borderColor: theme.color.neutral[700], + }, + colorDisplay: { + flex: 1, + padding: 8, + backgroundColor: colorKey, + borderRadius: theme.radius['max'], + }, +}); \ No newline at end of file diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx new file mode 100644 index 00000000..61f138c8 --- /dev/null +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.tsx @@ -0,0 +1,30 @@ +import { Pressable, View } from 'react-native'; + +import type { GroupColorKey } from '@/constants/groupColors'; + +import { createStyles } from './index.style'; + +interface ColorPickItemProps { + colorKey: GroupColorKey; + isSelected: boolean; + onSelect: (colorKey: GroupColorKey) => void; +} + +const ColorPickItem = ({ + colorKey, + isSelected, + onSelect, +}: ColorPickItemProps) => { + const styles = createStyles(isSelected, colorKey); + + return ( + onSelect(colorKey)} + style={styles.container} + > + + + ); +}; + +export default ColorPickItem; diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts new file mode 100644 index 00000000..841eac50 --- /dev/null +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts @@ -0,0 +1,8 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + gap: 12, + }, +}); \ No newline at end of file diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx new file mode 100644 index 00000000..3c83cf06 --- /dev/null +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx @@ -0,0 +1,37 @@ +import { View } from 'react-native'; + +import { Dialog } from '@/components'; +import { GROUP_COLORS, type GroupColorKey } from '@/constants/groupColors'; + +import ColorPickItem from './ColorPickItem'; +import { styles } from './index.style'; + +interface GroupColorPickerProps { + groupColorKey: GroupColorKey; + onSelectColor: (groupColorKey: GroupColorKey) => void; +} + +const GroupColorPickerDialog = ({ + groupColorKey, + onSelectColor, +}: GroupColorPickerProps) => ( + + {({ closeDialog }) => ( + + {Object.keys(GROUP_COLORS).map(key => ( + { + onSelectColor(selectedColorKey); + closeDialog(); + }} + /> + ))} + + )} + +); + +export default GroupColorPickerDialog; \ No newline at end of file diff --git a/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx b/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx index 3dc7febc..40dffe29 100644 --- a/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx +++ b/src/screens/group/groupDetail/components/GroupInfoPanel/DashBoardButtonGroup/index.tsx @@ -3,10 +3,14 @@ import { View } from 'react-native'; import { Icon, Toast } from '@/components'; import type { GroupColorKey } from '@/constants/groupColors'; import { GROUP_COLORS } from '@/constants/groupColors'; +import { useGroupColorKey } from '@/features/groups/hooks/useGroupColorKey'; import type { GroupMember } from '@/features/groups/model'; +import { useProfileQuery } from '@/features/user/api/queries'; import { useStackNavigation } from '@/hooks/useStackNavigation'; import { useTranslatedText } from '@/hooks/useTranslatedText'; +import { dialogService } from '@/utils/dialog'; +import GroupColorPickerDialog from '../../GroupColorPickerDialog'; import DashBoardButton from '../DashBoardButton'; import ChatButton from '../DashBoardButton/ChatButton'; import { createStyles } from './index.style'; @@ -22,15 +26,9 @@ interface DashBoardButtonGroupProps { disabled: boolean; } +// eslint-disable-next-line max-lines-per-function const DashBoardButtonGroup = ({ - groupName, - groupColorKey, - chatCount, - isHost, - chatRoomId, - members, - groupId, - disabled, + groupName, groupColorKey, chatCount, isHost, chatRoomId, members, groupId, disabled, }: DashBoardButtonGroupProps) => { const tRanking = useTranslatedText({ tKey: 'groupDetail.ranking' }); const tColor = useTranslatedText({ tKey: 'groupDetail.color' }); @@ -40,16 +38,17 @@ const DashBoardButtonGroup = ({ const styles = createStyles(groupColorKey, disabled); const buttonPrimaryColor = GROUP_COLORS[groupColorKey].medium; + + const { updateColorKey } = useGroupColorKey(groupId); + const { data: profileData } = useProfileQuery(); + if (!profileData) return null; const handleRankingPress = () => { if (disabled) { Toast.show(tCanUseAfterJoining); return; } - navigation.navigate('GroupRanking', { - groupId, - groupColorKey, - }); + navigation.navigate('GroupRanking', { groupId, groupColorKey }); }; const handleColorPress = () => { @@ -57,6 +56,15 @@ const DashBoardButtonGroup = ({ Toast.show(tCanUseAfterJoining); return; } + dialogService.add({ + content: ( + { + updateColorKey(selectedColorKey); + }} + />), + }); }; const handleChatPress = () => { @@ -64,12 +72,7 @@ const DashBoardButtonGroup = ({ Toast.show(tCanUseAfterJoining); return; } - navigation.navigate('GroupChat', { - chatRoomId, - groupName, - groupColorKey, - members, - }); + navigation.navigate('GroupChat', { chatRoomId, groupName, groupColorKey, members }); }; const handleSettingsPress = () => { @@ -77,10 +80,7 @@ const DashBoardButtonGroup = ({ Toast.show(tCanUseAfterJoining); return; } - navigation.navigate('GroupSetting', { - groupId, - groupColorKey, - }); + navigation.navigate('GroupSetting', { groupId, groupColorKey }); }; return ( From 0561f71ea4e78901c9a993ccfdc06305c6e0f42b Mon Sep 17 00:00:00 2001 From: dioo1461 Date: Wed, 4 Feb 2026 20:59:14 +0900 Subject: [PATCH 7/9] =?UTF-8?q?style:=20Color=20Picker=20UI=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locales/ko/groupDetail.json | 3 +- .../ColorPickItem/index.style.ts | 12 +++---- .../GroupColorPickerDialog/index.style.ts | 14 ++++++++ .../GroupColorPickerDialog/index.tsx | 32 +++++++++++-------- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/locales/ko/groupDetail.json b/src/locales/ko/groupDetail.json index da32917a..118e320a 100644 --- a/src/locales/ko/groupDetail.json +++ b/src/locales/ko/groupDetail.json @@ -21,5 +21,6 @@ "cannot-join-your-group": "본인이 속한 그룹에는 가입할 수 없어요.", "group-join-success": "그룹 가입이 완료되었습니다.", "group-join-request-sent": "그룹 참여 요청이 전송되었습니다.", - "failed-to-join-group": "그룹 가입에 실패했습니다. 다시 시도해주세요." + "failed-to-join-group": "그룹 가입에 실패했습니다. 다시 시도해주세요.", + "group-color-picker-title": "그룹 테마 색상 선택" } \ No newline at end of file diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts index 7df0ce70..ca9540b3 100644 --- a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; -import type { GroupColorKey } from '@/constants/groupColors'; +import { GROUP_COLORS, type GroupColorKey } from '@/constants/groupColors'; import { theme } from '@/theme'; export const createStyles = ( @@ -8,16 +8,16 @@ export const createStyles = ( colorKey: GroupColorKey, ) => StyleSheet.create({ container: { - width: 50, - height: 50, + width: 40, + height: 40, borderRadius: theme.radius['max'], - borderWidth: selected ? 3 : 1, - borderColor: theme.color.neutral[700], + borderWidth: selected ? 3 : 0, + borderColor: GROUP_COLORS[colorKey].strong, }, colorDisplay: { flex: 1, padding: 8, - backgroundColor: colorKey, + backgroundColor: GROUP_COLORS[colorKey].medium, borderRadius: theme.radius['max'], }, }); \ No newline at end of file diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts index 841eac50..073e7dd8 100644 --- a/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.style.ts @@ -1,8 +1,22 @@ import { StyleSheet } from 'react-native'; +import type { TextProps } from '@/components/Text'; +import type { GroupColorKey } from '@/constants/groupColors'; +import { GROUP_COLORS } from '@/constants/groupColors'; + export const styles = StyleSheet.create({ container: { + alignItems: 'center', + justifyContent: 'center', + gap: 24, + }, + pickerContainer: { flexDirection: 'row', gap: 12, }, +}); + +export const PICKER_TITLE_TEXT_STYLE = (groupColorKey: GroupColorKey): TextProps => ({ + font: 't1', + color: GROUP_COLORS[groupColorKey].strong, }); \ No newline at end of file diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx index 3c83cf06..dafa2fff 100644 --- a/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/index.tsx @@ -1,10 +1,10 @@ import { View } from 'react-native'; -import { Dialog } from '@/components'; +import { Dialog, TText } from '@/components'; import { GROUP_COLORS, type GroupColorKey } from '@/constants/groupColors'; import ColorPickItem from './ColorPickItem'; -import { styles } from './index.style'; +import { PICKER_TITLE_TEXT_STYLE, styles } from './index.style'; interface GroupColorPickerProps { groupColorKey: GroupColorKey; @@ -18,17 +18,23 @@ const GroupColorPickerDialog = ({ {({ closeDialog }) => ( - {Object.keys(GROUP_COLORS).map(key => ( - { - onSelectColor(selectedColorKey); - closeDialog(); - }} - /> - ))} + + + {Object.keys(GROUP_COLORS).map(key => ( + { + onSelectColor(selectedColorKey); + closeDialog(); + }} + /> + ))} + )} From ae1e3db1215cd82ba8e32a10ee95838ef3382e8b Mon Sep 17 00:00:00 2001 From: dioo1461 Date: Wed, 4 Feb 2026 23:34:18 +0900 Subject: [PATCH 8/9] =?UTF-8?q?style:=20=EA=B7=B8=EB=A3=B9=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=ED=99=94=EB=A9=B4=EC=97=90=20safeArea:=20'topAndBo?= =?UTF-8?q?ttom'=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/group/GroupChatScreen/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/group/GroupChatScreen/index.tsx b/src/screens/group/GroupChatScreen/index.tsx index 4d458ec9..1428d006 100644 --- a/src/screens/group/GroupChatScreen/index.tsx +++ b/src/screens/group/GroupChatScreen/index.tsx @@ -23,7 +23,6 @@ import { useGroupMemberMap } from './hooks/useGroupMemberMap'; // TODO: 채팅방 진입 시 마지막으로 본 채팅으로 스크롤 이동 // TODO: 윈도우에서 채팅 시간이 YYYY.MM.DD 형식으로 나오는 문제 해결 // TODO: 채팅 읽음 커서 기능구현 -// eslint-disable-next-line max-lines-per-function const GroupChatScreen = () => { const tUnknown = useTranslatedText({ tKey: 'general.unknown' }); const { params } = useStackRoute<'GroupChat'>(); @@ -90,6 +89,7 @@ const GroupChatScreen = () => { /> } keyboardControllerType='keyboardAvoidingView' + safeAreas='topAndBottom' title={groupName} > Date: Fri, 6 Feb 2026 14:46:46 +0900 Subject: [PATCH 9/9] =?UTF-8?q?style:=20=EC=83=89=EC=83=81=20=ED=94=BC?= =?UTF-8?q?=EC=BB=A4=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupColorPickerDialog/ColorPickItem/index.style.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts index ca9540b3..47cdc93b 100644 --- a/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts +++ b/src/screens/group/groupDetail/components/GroupColorPickerDialog/ColorPickItem/index.style.ts @@ -12,11 +12,11 @@ export const createStyles = ( height: 40, borderRadius: theme.radius['max'], borderWidth: selected ? 3 : 0, - borderColor: GROUP_COLORS[colorKey].strong, + borderColor: GROUP_COLORS[colorKey].medium, }, colorDisplay: { flex: 1, - padding: 8, + margin: 4, backgroundColor: GROUP_COLORS[colorKey].medium, borderRadius: theme.radius['max'], },