From c6233c7e929a57b6735be8e4cddae9d7da2d8ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:33:56 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[SZ-553]feat:=20=ED=88=AC=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=EB=B7=B0=20=EB=93=9C=EB=9E=98=EA=B7=B8=EC=95=A4?= =?UTF-8?q?=EB=93=9C=EB=A1=AD=20=EB=8F=99=EC=9E=91=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/index.jsx | 30 ++++++++++++------- .../todayView/dailyTodos/DailyTodos.tsx | 20 ++++++++++--- .../dailyTodos/dailyTodo/DailyTodo.jsx | 19 +----------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/app/(tabs)/index.jsx b/app/(tabs)/index.jsx index 5784ead..58447a4 100644 --- a/app/(tabs)/index.jsx +++ b/app/(tabs)/index.jsx @@ -28,6 +28,8 @@ import { scale, verticalScale } from 'react-native-size-matters'; const TodayView = () => { const { i18n } = useTranslation(); const { userId } = useContext(LoginContext); + const [isDragging, setIsDragging] = React.useState(false); + const getLoadingText = () => { if (i18n.language === 'ko') { return `투두`; @@ -44,7 +46,12 @@ const TodayView = () => { return ( - + setIsDragging(true)} + onDragEnd={() => setIsDragging(false)} + /> ); }; @@ -56,14 +63,14 @@ const TodayView = () => { - - + + { renderItem={renderCategoriesTodo} keyExtractor={item => item.id} contentContainerStyle={styles.flatList} + scrollEnabled={!isDragging} /> - - + + diff --git a/components/todayView/dailyTodos/DailyTodos.tsx b/components/todayView/dailyTodos/DailyTodos.tsx index d33cf12..7fe9d59 100644 --- a/components/todayView/dailyTodos/DailyTodos.tsx +++ b/components/todayView/dailyTodos/DailyTodos.tsx @@ -19,12 +19,14 @@ import { Icon } from '@ui-kitten/components'; import React, { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { StyleSheet, TextInput, View } from 'react-native'; -import DraggableFlatList from 'react-native-draggable-flatlist'; +import DraggableFlatList, { + ScaleDecorator, +} from 'react-native-draggable-flatlist'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import DailyTodo from './dailyTodo/DailyTodo'; import { moderateScale, scale, verticalScale } from 'react-native-size-matters'; -const DailyTodos = ({ todosData, categoryId }) => { +const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { const { userId } = useContext(LoginContext); const { selectedDate } = useContext(DateContext); const { t } = useTranslation(); @@ -36,6 +38,11 @@ const DailyTodos = ({ todosData, categoryId }) => { selectedDate, ); + const handleDragEndWithCallback = ({ data, from, to }) => { + handleDragEnd({ data, from, to }); + onDragEnd?.(); + }; + const { isTextInputOpen, activatedCategoryId, setTextInputOpen } = useContext(TextInputContext); @@ -51,7 +58,11 @@ const DailyTodos = ({ todosData, categoryId }) => { }; const renderTodo = ({ item, drag, isActive }) => { - return ; + return ( + + + + ); }; return ( @@ -60,7 +71,8 @@ const DailyTodos = ({ todosData, categoryId }) => { onDragStart?.()} keyExtractor={item => item.id.toString()} onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)} scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE} diff --git a/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx b/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx index 7418620..c7b3d39 100644 --- a/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx +++ b/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx @@ -1,10 +1,8 @@ -import SubTodoGenerateModal from '@/components/SubTodoGenerateModal'; import { TextInputContext } from '@/contexts/textInputContext'; import '@/locales/index'; import { useContext } from 'react'; import { View } from 'react-native'; import useModal from '../../../../hooks/common/useModal'; -import GeneratedSubTodoList from './generatedSubTodoList/GeneratedSubTodoList'; import SubTodoInfo from './subTodoInfo/SubTodoInfo'; import SubTodoList from './subTodoList/SubTodoList'; import TodoListItem from './todoListItem/TodoListItem'; @@ -18,15 +16,10 @@ const DailyTodo = ({ item, drag, isActive }) => { setIsSubTodoToggleActivated, subTodoInputActivated, setSubTodoInputActivated, - generatedSubTodos, - setGeneratedSubTodos, } = useDailyTodo(); const { setTextInputOpen } = useContext(TextInputContext); - const { - isVisible: isSubTodoGenerateModalVisible, - setIsVisible: setIsSubTodoGenerateModalVisible, - } = useModal(); + const { setIsVisible: setIsSubTodoGenerateModalVisible } = useModal(); const { setIsVisible: setIsTodoModalVisible } = useModal(); @@ -62,16 +55,6 @@ const DailyTodo = ({ item, drag, isActive }) => { setSubTodoInputActivated={setSubTodoInputActivated} /> ) : null} - - ); }; From e1518830c6d748509bf9cde06bcdeb00d21cc3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:00:58 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[SZ-553]feat:=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 캘린더 컴포넌트 캐싱 - fetch 데이터 캐싱 --- components/WeeklyCalendar.jsx | 299 +++++++++++++++++--------------- hooks/api/useCategoriesQuery.js | 2 + hooks/api/useInboxTodoQuery.js | 2 + hooks/api/useTodoQuery.js | 2 + 4 files changed, 169 insertions(+), 136 deletions(-) diff --git a/components/WeeklyCalendar.jsx b/components/WeeklyCalendar.jsx index da98a56..8ec5f0d 100644 --- a/components/WeeklyCalendar.jsx +++ b/components/WeeklyCalendar.jsx @@ -13,79 +13,179 @@ import { Icon, Layout, Text, useTheme } from '@ui-kitten/components'; import { View } from 'react-native'; import moment from 'moment'; import 'moment/locale/ko'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { + useContext, + useEffect, + useState, + useMemo, + useCallback, + memo, +} from 'react'; import { useTranslation } from 'react-i18next'; import { TouchableOpacity } from 'react-native'; import fontStyles from '../theme/fontStyles'; import { moderateScale, scale, verticalScale } from 'react-native-size-matters'; import useTodosQuery from '@/hooks/api/useTodoQuery'; -const WeeklyCalendar = () => { +// 날짜 변환 딕셔너리를 컴포넌트 외부로 이동 +const convertDictionary = { + 월: 'Mon', + 화: 'Tue', + 수: 'Wed', + 목: 'Thu', + 금: 'Fri', + 토: 'Sat', + 일: 'Sun', +}; + +// 날짜 아이템 컴포넌트 분리 및 메모이제이션 +const DayItem = memo( + ({ date, selectedDate, theme, todos, onDateSelect, userId, i18n }) => { + const convertWeekDates = useCallback( + convertedDate => { + if (i18n.language === 'ko') { + return convertedDate.format('ddd'); + } + return convertDictionary[convertedDate.format('ddd')]; + }, + [i18n.language], + ); + + const todoCount = useMemo( + () => + todos.filter( + todo => + isTodoIncludedInTodayView(todo.date, date.format('YYYY-MM-DD')) && + !todo.isCompleted, + ).length, + [todos, date], + ); + + const handlePress = useCallback(() => { + handleLogEvent(WEEKLYCALENDAR_DAYITEM_CLICK_EVENT, { + time: new Date().toISOString(), + userId, + day: date.format('YYYY-MM-DD'), + }); + onDateSelect(date); + }, [date, userId, onDateSelect]); + + return ( + + + {convertWeekDates(date)} + + + + + {todoCount} + + + + {date.format('D')} + + + + ); + }, +); + +const WeeklyCalendar = memo(() => { const { selectedDate, setSelectedDate } = useContext(DateContext); - const [currentDate, setcurrentDate] = useState(moment()); + const [currentDate, setCurrentDate] = useState(moment()); const theme = useTheme(); const { userId } = useContext(LoginContext); - const todos = useTodosQuery().data; - // const [todos, setTodos] = useState(fedchedTodos.data); - const getWeekDates = date => { + const { data: todos = [] } = useTodosQuery(); + const { i18n } = useTranslation(); + + // 주간 날짜 계산 메모이제이션 + const getWeekDates = useCallback(date => { const start = date.clone().startOf('ISOWeek'); - const r = Array.from({ length: 7 }, (_, i) => + return Array.from({ length: 7 }, (_, i) => moment(convertGmtToKst(new Date(start.clone().add(i, 'days')))), ); - return r; - }; - const [weekDates, setwWeekDates] = useState(getWeekDates(currentDate)); - const { i18n } = useTranslation(); + }, []); + + const weekDates = useMemo( + () => getWeekDates(currentDate), + [currentDate, getWeekDates], + ); - const navigateWeek = direction => { - setcurrentDate(prevDate => + const navigateWeek = useCallback(direction => { + setCurrentDate(prevDate => direction === 'next' ? prevDate.clone().add(7, 'd') : prevDate.clone().subtract(7, 'd'), ); - }; - useEffect(() => { - setwWeekDates(getWeekDates(currentDate)); - }, [currentDate]); - useEffect(() => { - moment().isBetween(weekDates[0], weekDates[6]) - ? setSelectedDate(currentDate) - : setSelectedDate(weekDates[0]); - }, [weekDates, setSelectedDate, currentDate]); + }, []); - const handleDateSelect = date => { - setSelectedDate(date); - }; + const handleDateSelect = useCallback( + date => { + setSelectedDate(date); + }, + [setSelectedDate], + ); - const convertMonthAndYear = date => { + const convertMonthAndYear = useCallback(date => { return date.format('yyyy.MM'); - }; + }, []); - const convertDictionary = { - 월: 'Mon', - 화: 'Tue', - 수: 'Wed', - 목: 'Thu', - 금: 'Fri', - 토: 'Sat', - 일: 'Sun', - }; + const convertCalendarType = useCallback(() => { + return i18n.language === 'ko' ? '주' : 'W'; + }, [i18n.language]); - const convertWeekDates = date => { - if (i18n.language === 'ko') { - return date.format('ddd'); - } else if (i18n.language === 'en') { - return convertDictionary[date.format('ddd')]; - } - }; + // 주간 이동 버튼 핸들러 + const handleNavigateWeek = useCallback( + direction => { + handleLogEvent(WEEKLYCALENDAR_NAVIGATEWEEK_CLICK_EVENT, { + time: new Date().toISOString(), + userId, + week: selectedDate.format('YYYY-MM-DD'), + direction, + }); + navigateWeek(direction); + }, + [navigateWeek, userId, selectedDate], + ); - const convertCalendarType = () => { - if (i18n.language === 'ko') { - return '주'; - } else if (i18n.language === 'en') { - return 'W'; + useEffect(() => { + if (moment().isBetween(weekDates[0], weekDates[6])) { + setSelectedDate(currentDate); + } else { + setSelectedDate(weekDates[0]); } - }; + }, [weekDates, setSelectedDate, currentDate]); return ( @@ -98,35 +198,14 @@ const WeeklyCalendar = () => { - { - handleLogEvent(WEEKLYCALENDAR_NAVIGATEWEEK_CLICK_EVENT, { - time: new Date().toISOString(), - userId: userId, - week: selectedDate.format('YYYY-MM-DD'), - direction: 'prev', - }); - navigateWeek('prev'); - }} - > + handleNavigateWeek('prev')}> - { - navigateWeek('next'); - - handleLogEvent(WEEKLYCALENDAR_NAVIGATEWEEK_CLICK_EVENT, { - time: new Date().toISOString(), - userId: userId, - week: selectedDate.format('YYYY-MM-DD'), - direction: 'next', - }); - }} - > + handleNavigateWeek('next')}> { {weekDates.map((date, index) => ( - - - {convertWeekDates(date)} - - { - handleLogEvent(WEEKLYCALENDAR_DAYITEM_CLICK_EVENT, { - time: new Date().toISOString(), - userId: userId, - day: date.format('YYYY-MM-DD'), - }); - handleDateSelect(date); - }} - style={{ - ...styles.touchBox, - backgroundColor: date.isSame(selectedDate, 'day') - ? theme.Blue01 - : 'transparent', - }} - > - - - { - todos.filter( - todo => - isTodoIncludedInTodayView( - todo.date, - date.format('YYYY-MM-DD'), - ) && !todo.isCompleted, - ).length - } - - - - {date.format('D')} - - - + ))} ); -}; +}); +// 스타일은 동일하게 유지 const styles = StyleSheet.create({ background: { display: 'flex', diff --git a/hooks/api/useCategoriesQuery.js b/hooks/api/useCategoriesQuery.js index 5f00f42..ae285eb 100644 --- a/hooks/api/useCategoriesQuery.js +++ b/hooks/api/useCategoriesQuery.js @@ -15,6 +15,8 @@ const useCategoriesQuery = userId => { suspense: true, refetchInterval: 60000, refetchIntervalInBackground: true, + cacheTime: 180000, + staleTime: 30000, }); }; diff --git a/hooks/api/useInboxTodoQuery.js b/hooks/api/useInboxTodoQuery.js index faa94a6..a29a4d2 100644 --- a/hooks/api/useInboxTodoQuery.js +++ b/hooks/api/useInboxTodoQuery.js @@ -14,6 +14,8 @@ const useInboxTodoQuery = userId => { queryKey: [INBOX_QUERY_KEY], queryFn: () => fetcher(userId), suspense: true, + cacheTime: 180000, + staleTime: 30000, }); }; diff --git a/hooks/api/useTodoQuery.js b/hooks/api/useTodoQuery.js index 63a760d..3e5f6f6 100644 --- a/hooks/api/useTodoQuery.js +++ b/hooks/api/useTodoQuery.js @@ -16,6 +16,8 @@ const useTodosQuery = userId => { suspense: true, refetchInterval: 60000, refetchIntervalInBackground: true, + cacheTime: 180000, + staleTime: 30000, }); }; From 6c3e085c6cbae9d1b823aeece03db04ce79b33fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:18:56 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[SZ-553]:feat:=20=EC=BA=98=EB=A6=B0?= =?UTF-8?q?=EB=8D=94=EC=97=90=20=EB=82=A8=EC=9D=80=20=ED=88=AC=EB=91=90=20?= =?UTF-8?q?=EA=B0=AF=EC=88=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/WeeklyCalendar.jsx | 48 ++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/components/WeeklyCalendar.jsx b/components/WeeklyCalendar.jsx index 8ec5f0d..28671ea 100644 --- a/components/WeeklyCalendar.jsx +++ b/components/WeeklyCalendar.jsx @@ -26,6 +26,7 @@ import { TouchableOpacity } from 'react-native'; import fontStyles from '../theme/fontStyles'; import { moderateScale, scale, verticalScale } from 'react-native-size-matters'; import useTodosQuery from '@/hooks/api/useTodoQuery'; +import useCategoriesQuery from '@/hooks/api/useCategoriesQuery'; // 날짜 변환 딕셔너리를 컴포넌트 외부로 이동 const convertDictionary = { @@ -40,7 +41,7 @@ const convertDictionary = { // 날짜 아이템 컴포넌트 분리 및 메모이제이션 const DayItem = memo( - ({ date, selectedDate, theme, todos, onDateSelect, userId, i18n }) => { + ({ date, selectedDate, theme, todoCount, onDateSelect, userId, i18n }) => { const convertWeekDates = useCallback( convertedDate => { if (i18n.language === 'ko') { @@ -51,16 +52,6 @@ const DayItem = memo( [i18n.language], ); - const todoCount = useMemo( - () => - todos.filter( - todo => - isTodoIncludedInTodayView(todo.date, date.format('YYYY-MM-DD')) && - !todo.isCompleted, - ).length, - [todos, date], - ); - const handlePress = useCallback(() => { handleLogEvent(WEEKLYCALENDAR_DAYITEM_CLICK_EVENT, { time: new Date().toISOString(), @@ -127,6 +118,7 @@ const WeeklyCalendar = memo(() => { const theme = useTheme(); const { userId } = useContext(LoginContext); const { data: todos = [] } = useTodosQuery(); + const { data: categories = [] } = useCategoriesQuery(); const { i18n } = useTranslation(); // 주간 날짜 계산 메모이제이션 @@ -187,6 +179,38 @@ const WeeklyCalendar = memo(() => { } }, [weekDates, setSelectedDate, currentDate]); + // 일주일치 할일 필터링을 위한 메모이제이션 추가 + const filteredWeekTodos = useMemo(() => { + const startDate = weekDates[0].format('YYYY-MM-DD'); + const endDate = weekDates[6].format('YYYY-MM-DD'); + + return todos.filter(todo => { + // 카테고리 체크 + const isCategoryValid = categories.some( + category => category.id === todo.categoryId, + ); + // 날짜가 해당 주에 포함되는지 체크 + const isDateInRange = moment(todo.date).isBetween( + startDate, + endDate, + 'day', + '[]', + ); + // 완료되지 않은 할일만 필터링 + return !todo.isCompleted && isCategoryValid && isDateInRange; + }); + }, [todos, categories, weekDates]); + + // DayItem 컴포넌트에 전달할 할일 개수 계산 함수 + const getTodoCountForDate = useCallback( + date => { + return filteredWeekTodos.filter(todo => + isTodoIncludedInTodayView(todo.date, date.format('YYYY-MM-DD')), + ).length; + }, + [filteredWeekTodos], + ); + return ( @@ -234,7 +258,7 @@ const WeeklyCalendar = memo(() => { date={date} selectedDate={selectedDate} theme={theme} - todos={todos} + todoCount={getTodoCountForDate(date)} onDateSelect={handleDateSelect} userId={userId} i18n={i18n} From 6902b1bcefc69179e71c4e1dc383a59d999919b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 03:23:46 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[SZ-553]feat:=20optimistic=20ui=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/molecules/EditableTextField.tsx | 53 +++++--- .../todoMoreMenu/useTodoMoreMenu.tsx | 4 +- .../dailyTodo/todoListItem/useTodoListItem.ts | 16 ++- hooks/api/useTodoMutations.js | 55 -------- hooks/api/useTodoMutations.ts | 126 ++++++++++++++++++ hooks/api/useTodoQuery.js | 2 +- 6 files changed, 171 insertions(+), 85 deletions(-) delete mode 100644 hooks/api/useTodoMutations.js create mode 100644 hooks/api/useTodoMutations.ts diff --git a/components/common/molecules/EditableTextField.tsx b/components/common/molecules/EditableTextField.tsx index 3d6aa46..255fa4b 100644 --- a/components/common/molecules/EditableTextField.tsx +++ b/components/common/molecules/EditableTextField.tsx @@ -1,8 +1,9 @@ import { Text } from '@ui-kitten/components'; -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native'; import { Todo } from '../../../types/todo'; import { moderateScale, scale } from 'react-native-size-matters'; +import { memo } from 'react'; interface EditableListItemTitleProps { isEditing: boolean; @@ -12,26 +13,36 @@ interface EditableListItemTitleProps { textStyles?: StyleProp | null; } -const EditableTextField = ({ - isEditing, - handleSubmitEditing, - item, - inputStyles = styles.input, - textStyles = styles.text, -}: EditableListItemTitleProps) => { - const [content, setContent] = useState(item.content); - return isEditing ? ( - handleSubmitEditing(content)} - autoFocus={true} - style={inputStyles} - /> - ) : ( - {item.content} - ); -}; +const EditableTextField = memo( + ({ + isEditing, + handleSubmitEditing, + item, + inputStyles = styles.input, + textStyles = styles.text, + }: EditableListItemTitleProps) => { + const [content, setContent] = useState(item.content); + + const onSubmit = useCallback(() => { + handleSubmitEditing(content); + }, [content, handleSubmitEditing]); + + const onChangeText = useCallback((text: string) => { + setContent(text); + }, []); + console.log(content); + return isEditing ? ( + + ) : ( + {content} + ); + }, +); export default EditableTextField; diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx index dea7a21..5178b46 100644 --- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx +++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx @@ -38,8 +38,8 @@ const useTodoMoreMenu = ({ const updatedTodo = { date: kstDate, - todo_id: item.id, - category_id: selectedCategory, + todoId: item.id, + categoryId: selectedCategory, }; updateTodoDate(updatedTodo); }; diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts index cb309ca..98383f0 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts @@ -1,6 +1,6 @@ import { TextInputContext } from '@/contexts/textInputContext'; import { useTheme } from '@ui-kitten/components'; -import { useContext } from 'react'; +import { useContext, useCallback } from 'react'; import { LoginContext } from '../../../../../contexts/LoginContext'; import { useTodoUpdateMutation } from '../../../../../hooks/api/useTodoMutations'; import { @@ -48,6 +48,7 @@ const useTodoListItem = ({ }); }; + // eslint-disable-next-line react-hooks/exhaustive-deps const handleTodoContentUpdate = content => { const updatedData = { todoId: item.id, @@ -56,11 +57,14 @@ const useTodoListItem = ({ updateTodo(updatedData); }; - const handleTodoListItemSubmitEditing = content => { - handleTodoContentUpdate(content); - setIsEditing(false); - setTodayTextInputOpen(false); - }; + const handleTodoListItemSubmitEditing = useCallback( + (content: string) => { + handleTodoContentUpdate(content); + setTodayTextInputOpen(false); + setIsEditing(false); + }, + [handleTodoContentUpdate, setTodayTextInputOpen, setIsEditing], + ); const handleGenerateIconPress = () => { setIsSubTodoGenerateModalVisible(true); diff --git a/hooks/api/useTodoMutations.js b/hooks/api/useTodoMutations.js deleted file mode 100644 index 3ef1b5a..0000000 --- a/hooks/api/useTodoMutations.js +++ /dev/null @@ -1,55 +0,0 @@ -import { api as Api } from '@/utils/api'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { INBOX_QUERY_KEY } from './useInboxTodoQuery'; -import { TODO_QUERY_KEY } from './useTodoQuery'; - -// 생성 (Add Todo) -const addTodoFetcher = async todoData => { - const data = await Api.addTodo(todoData); - return data; -}; - -export const useTodoAddMutation = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: addTodoFetcher, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] }); - queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] }); - }, - }); -}; - -// 수정 (Update Todo) -const updateTodoFetcher = async updatedData => { - const data = await Api.updateTodo(updatedData); - return data; -}; - -export const useTodoUpdateMutation = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: updateTodoFetcher, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] }); - queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] }); - }, - }); -}; - -// 삭제 (Delete Todo) -const deleteTodoFetcher = async todoId => { - const data = await Api.deleteTodo(todoId.todoId); - return data; -}; - -export const useTodoDeleteMutation = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: deleteTodoFetcher, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] }); - queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] }); - }, - }); -}; diff --git a/hooks/api/useTodoMutations.ts b/hooks/api/useTodoMutations.ts new file mode 100644 index 0000000..dac84f7 --- /dev/null +++ b/hooks/api/useTodoMutations.ts @@ -0,0 +1,126 @@ +import { api as Api } from '@/utils/api'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { INBOX_QUERY_KEY } from './useInboxTodoQuery'; +import { TODO_QUERY_KEY } from './useTodoQuery'; +import { Todo } from '@/types/todo'; + +const addTodoFetcher = async (todoData: Omit) => { + const data = await Api.addTodo(todoData); + return data; +}; + +export const useTodoAddMutation = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: addTodoFetcher, + onMutate: async newTodo => { + await queryClient.cancelQueries({ queryKey: [TODO_QUERY_KEY] }); + await queryClient.cancelQueries({ queryKey: [INBOX_QUERY_KEY] }); + + const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]); + const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]); + + const optimisticTodo: Todo = { + ...newTodo, + id: Date.now(), + children: [], + }; + + queryClient.setQueryData([TODO_QUERY_KEY], old => [ + ...(old || []), + optimisticTodo, + ]); + queryClient.setQueryData([INBOX_QUERY_KEY], old => [ + ...(old || []), + optimisticTodo, + ]); + + return { previousTodos, previousInbox }; + }, + onError: (_err, _newTodo, context) => { + queryClient.setQueryData([TODO_QUERY_KEY], context?.previousTodos); + queryClient.setQueryData([INBOX_QUERY_KEY], context?.previousInbox); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] }); + queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] }); + }, + }); +}; + +const updateTodoFetcher = async ( + updatedData: Partial & { todoId: number }, +) => { + const data = await Api.updateTodo(updatedData); + return data; +}; + +export const useTodoUpdateMutation = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: updateTodoFetcher, + onMutate: async updatedTodo => { + await queryClient.cancelQueries({ queryKey: [TODO_QUERY_KEY] }); + await queryClient.cancelQueries({ queryKey: [INBOX_QUERY_KEY] }); + + const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]); + const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]); + + const updateTodo = (old: Todo[] | undefined) => + old?.map(todo => { + if (todo.id === updatedTodo.todoId) { + return { ...todo, ...updatedTodo }; + } + return todo; + }) || []; + + queryClient.setQueryData([TODO_QUERY_KEY], updateTodo); + queryClient.setQueryData([INBOX_QUERY_KEY], updateTodo); + + return { previousTodos, previousInbox }; + }, + onError: (_err, _updatedTodo, context) => { + queryClient.setQueryData([TODO_QUERY_KEY], context?.previousTodos); + queryClient.setQueryData([INBOX_QUERY_KEY], context?.previousInbox); + }, + onSuccess: () => { + queryClient.refetchQueries({ queryKey: [TODO_QUERY_KEY] }); + queryClient.refetchQueries({ queryKey: [INBOX_QUERY_KEY] }); + }, + }); +}; + +const deleteTodoFetcher = async ({ todoId }: { todoId: number }) => { + const data = await Api.deleteTodo(todoId); + return data; +}; + +export const useTodoDeleteMutation = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: deleteTodoFetcher, + onMutate: async ({ todoId }) => { + await queryClient.cancelQueries({ queryKey: [TODO_QUERY_KEY] }); + await queryClient.cancelQueries({ queryKey: [INBOX_QUERY_KEY] }); + + const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]); + const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]); + + const deleteTodo = (old: Todo[] | undefined) => + old?.filter(todo => todo.id !== todoId) || []; + + queryClient.setQueryData([TODO_QUERY_KEY], deleteTodo); + queryClient.setQueryData([INBOX_QUERY_KEY], deleteTodo); + + return { previousTodos, previousInbox }; + }, + onError: (_err, _variables, context) => { + queryClient.setQueryData([TODO_QUERY_KEY], context?.previousTodos); + queryClient.setQueryData([INBOX_QUERY_KEY], context?.previousInbox); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [TODO_QUERY_KEY] }); + queryClient.invalidateQueries({ queryKey: [INBOX_QUERY_KEY] }); + }, + }); +}; diff --git a/hooks/api/useTodoQuery.js b/hooks/api/useTodoQuery.js index 3e5f6f6..5081d57 100644 --- a/hooks/api/useTodoQuery.js +++ b/hooks/api/useTodoQuery.js @@ -1,4 +1,3 @@ -// useTodosQuery.js import { api as Api } from '@/utils/api'; import { useQuery } from '@tanstack/react-query'; @@ -18,6 +17,7 @@ const useTodosQuery = userId => { refetchIntervalInBackground: true, cacheTime: 180000, staleTime: 30000, + keepPreviousData: true, }); }; From 8272c4627c5ae8deb82ff770cabbe71b0687aa9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:27:26 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[SZ-553]feat:=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/_layout.jsx | 60 ++++---- components/categoryView/CategoryMainItem.jsx | 11 +- .../common/molecules/EditableTextField.tsx | 1 + .../inboxView/inboxTodos/InboxTodos.tsx | 8 +- .../inboxTodos/inboxTodo/InboxTodo.jsx | 8 +- .../inboxTodo/subTodoList/InboxSubTodo.jsx | 6 +- .../inboxTodo/todoListItem/useTodoListItem.ts | 6 +- .../todayView/dailyTodos/DailyTodos.tsx | 8 +- .../dailyTodos/dailyTodo/DailyTodo.jsx | 24 +-- .../dailyTodo/subTodoInfo/SubTodoInfo.tsx | 1 + .../dailyTodo/subTodoList/DailySubTodo.jsx | 6 +- .../dailyTodo/todoListItem/TodoListItem.tsx | 139 +++++++++--------- .../todoMoreMenu/TodoMoreMenu.tsx | 6 +- .../todoMoreMenu/useTodoMoreMenu.tsx | 12 +- .../dailyTodo/todoListItem/useTodoListItem.ts | 21 +-- contexts/textInputContext.js | 31 ---- contexts/textInputStore.js | 17 +++ contexts/todoEditStore.ts | 18 +++ 18 files changed, 178 insertions(+), 205 deletions(-) delete mode 100644 contexts/textInputContext.js create mode 100644 contexts/textInputStore.js create mode 100644 contexts/todoEditStore.ts diff --git a/app/(tabs)/_layout.jsx b/app/(tabs)/_layout.jsx index bee5029..cd7d929 100644 --- a/app/(tabs)/_layout.jsx +++ b/app/(tabs)/_layout.jsx @@ -1,4 +1,3 @@ -import TextInputProvider from '@/contexts/textInputContext'; import '@/locales/index'; import { Tabs } from 'expo-router'; import React from 'react'; @@ -19,38 +18,35 @@ const inboxIcon = ({ color, size }) => ( const TabLayout = () => { const { t } = useTranslation(); return ( - - + - - - - + /> + + ); }; diff --git a/components/categoryView/CategoryMainItem.jsx b/components/categoryView/CategoryMainItem.jsx index e26fbba..30fab8a 100644 --- a/components/categoryView/CategoryMainItem.jsx +++ b/components/categoryView/CategoryMainItem.jsx @@ -1,7 +1,6 @@ -import { TextInputContext } from '@/contexts/textInputContext'; +import useTextInputStore from '@/contexts/textInputStore'; import colors from '@/theme/theme.json'; import { Icon } from '@ui-kitten/components'; -import { useContext } from 'react'; import { Pressable, StyleSheet, View } from 'react-native'; import { scale, verticalScale } from 'react-native-size-matters'; import CategoryButton from './CategoryButton'; @@ -12,7 +11,13 @@ const CategoryMainItem = ({ item, isToday = true }) => { setTodayActivatedCategoryId, setInboxTextInputOpen, setInboxActivatedCategoryId, - } = useContext(TextInputContext); + } = useTextInputStore(state => ({ + setTodayTextInputOpen: state.setTodayTextInputOpen, + setTodayActivatedCategoryId: state.setTodayActivatedCategoryId, + setInboxTextInputOpen: state.setInboxTextInputOpen, + setInboxActivatedCategoryId: state.setInboxActivatedCategoryId, + })); + const handlePress = () => { if (isToday) { setTodayTextInputOpen(true); diff --git a/components/common/molecules/EditableTextField.tsx b/components/common/molecules/EditableTextField.tsx index 255fa4b..76e5754 100644 --- a/components/common/molecules/EditableTextField.tsx +++ b/components/common/molecules/EditableTextField.tsx @@ -37,6 +37,7 @@ const EditableTextField = memo( onChangeText={onChangeText} onSubmitEditing={onSubmit} style={inputStyles} + autoFocus={true} /> ) : ( {content} diff --git a/components/inboxView/inboxTodos/InboxTodos.tsx b/components/inboxView/inboxTodos/InboxTodos.tsx index 088076e..66d4160 100644 --- a/components/inboxView/inboxTodos/InboxTodos.tsx +++ b/components/inboxView/inboxTodos/InboxTodos.tsx @@ -1,5 +1,5 @@ import { LoginContext } from '@/contexts/LoginContext'; -import { TextInputContext } from '@/contexts/textInputContext'; +import useTextInputStore from '@/contexts/textInputStore'; import { useTodoUpdateMutation } from '@/hooks/api/useTodoMutations'; import useCreateInboxTodo from '@/hooks/todo/useCreateInboxTodo'; import useFilteredInboxTodos from '@/hooks/todo/useFilteredInboxTodo'; @@ -42,7 +42,11 @@ const InboxTodos = ({ todosData, categoryId }) => { isInboxTextInputOpen, inboxActivatedCategoryId, setInboxTextInputOpen, - } = useContext(TextInputContext); + } = useTextInputStore(state => ({ + isInboxTextInputOpen: state.isInboxTextInputOpen, + inboxActivatedCategoryId: state.inboxActivatedCategoryId, + setInboxTextInputOpen: state.setInboxTextInputOpen, + })); const { input, setInput, handleSubmit } = useCreateInboxTodo( userId, categoryId, diff --git a/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx b/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx index 4082d04..ba0e1c3 100644 --- a/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx +++ b/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx @@ -1,14 +1,14 @@ import SubTodoGenerateModal from '@/components/SubTodoGenerateModal'; import GeneratedSubTodoList from '@/components/todayView/dailyTodos/dailyTodo/generatedSubTodoList/GeneratedSubTodoList'; -import { TextInputContext } from '@/contexts/textInputContext'; import '@/locales/index'; -import React, { useContext } from 'react'; +import React from 'react'; import { View } from 'react-native'; import useModal from '../../../../hooks/common/useModal'; import SubTodoInfo from './subTodoInfo/SubTodoInfo'; import SubTodoList from './subTodoList/SubTodoList'; import TodoListItem from './todoListItem/TodoListItem'; import useInboxTodo from './useInboxTodo'; +import useTextInputStore from '@/contexts/textInputStore'; const InboxTodo = ({ item, drag, isActive }) => { const { @@ -22,7 +22,9 @@ const InboxTodo = ({ item, drag, isActive }) => { setGeneratedSubTodos, } = useInboxTodo(); - const { setInboxTextInputOpen } = useContext(TextInputContext); + const { setInboxTextInputOpen } = useTextInputStore( + state => state.setInboxTextInputOpen, + ); const { isVisible: isSubTodoGenerateModalVisible, diff --git a/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx b/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx index 226b07c..236cfa3 100644 --- a/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx +++ b/components/inboxView/inboxTodos/inboxTodo/subTodoList/InboxSubTodo.jsx @@ -1,5 +1,5 @@ import { LoginContext } from '@/contexts/LoginContext'; -import { TextInputContext } from '@/contexts/textInputContext'; +import useTextInputStore from '@/contexts/textInputStore'; import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations'; import { handleLogEvent, @@ -17,7 +17,9 @@ const InboxSubTodo = ({ item }) => { const theme = useTheme(); const { mutate: updateSubTodo } = useSubTodoUpdateMutation(); const { userId } = useContext(LoginContext); - const { setInboxTextInputOpen } = useContext(TextInputContext); + const { setInboxTextInputOpen } = useTextInputStore( + state => state.setInboxTextInputOpen, + ); const handleEdit = () => { setIsEditing(true); diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts b/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts index b454cc4..50f4044 100644 --- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts +++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts @@ -1,4 +1,4 @@ -import { TextInputContext } from '@/contexts/textInputContext'; +import useTextInputStore from '@/contexts/textInputStore'; import { useTheme } from '@ui-kitten/components'; import { useContext } from 'react'; import { LoginContext } from '../../../../../contexts/LoginContext'; @@ -19,7 +19,9 @@ const useTodoListItem = ({ const { mutate: updateTodo } = useTodoUpdateMutation(); const { userId } = useContext(LoginContext); const theme = useTheme(); - const { setInboxTextInputOpen } = useContext(TextInputContext); + const { setInboxTextInputOpen } = useTextInputStore( + state => state.setInboxTextInputOpen, + ); const checkIconlogPressEvent = () => { handleLogEvent(DAILYTODO_TODOCOMPLETE_CLICK_EVENT, { diff --git a/components/todayView/dailyTodos/DailyTodos.tsx b/components/todayView/dailyTodos/DailyTodos.tsx index af5e588..ad0b4de 100644 --- a/components/todayView/dailyTodos/DailyTodos.tsx +++ b/components/todayView/dailyTodos/DailyTodos.tsx @@ -1,6 +1,6 @@ import { DateContext } from '@/contexts/DateContext'; import { LoginContext } from '@/contexts/LoginContext'; -import { TextInputContext } from '@/contexts/textInputContext'; +import useTextInputStore from '@/contexts/textInputStore'; import useCreateTodo from '@/hooks/todo/useCreateTodo'; import useFilteredTodos from '@/hooks/todo/useFilteredTodo'; import useHandleDrag from '@/hooks/todo/useHandleDrag'; @@ -47,7 +47,11 @@ const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { isTodayTextInputOpen, todayActivatedCategoryId, setTodayTextInputOpen, - } = useContext(TextInputContext); + } = useTextInputStore(state => ({ + isTodayTextInputOpen: state.isTodayTextInputOpen, + todayActivatedCategoryId: state.todayActivatedCategoryId, + setTodayTextInputOpen: state.setTodayTextInputOpen, + })); const handleDragEnd = useHandleDrag(); diff --git a/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx b/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx index 64501f8..d6c1a75 100644 --- a/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx +++ b/components/todayView/dailyTodos/dailyTodo/DailyTodo.jsx @@ -1,8 +1,5 @@ -import { TextInputContext } from '@/contexts/textInputContext'; import '@/locales/index'; -import { useContext } from 'react'; import { View } from 'react-native'; -import useModal from '../../../../hooks/common/useModal'; import SubTodoInfo from './subTodoInfo/SubTodoInfo'; import SubTodoList from './subTodoList/SubTodoList'; import TodoListItem from './todoListItem/TodoListItem'; @@ -10,37 +7,18 @@ import useDailyTodo from './useDailyTodo'; const DailyTodo = ({ item, drag, isActive, categoryId }) => { const { - isEditing, - setIsEditing, isSubtodoToggleActivated, setIsSubTodoToggleActivated, subTodoInputActivated, setSubTodoInputActivated, } = useDailyTodo(); - const { setIsVisible: setIsSubTodoGenerateModalVisible } = useModal(); - const { setTodayTextInputOpen } = useContext(TextInputContext); - - const { setIsVisible: setIsTodoModalVisible } = useModal(); - - const handleEdit = () => { - setIsEditing(true); - setTodayTextInputOpen(false); - setIsTodoModalVisible(false); - }; - return ( 0} - onEdit={handleEdit} + item={item} setSubTodoInputActivated={setSubTodoInputActivated} categoryId={categoryId} /> diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx b/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx index c3c3e62..d13629e 100644 --- a/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx +++ b/components/todayView/dailyTodos/dailyTodo/subTodoInfo/SubTodoInfo.tsx @@ -48,6 +48,7 @@ const SubTodoInfo: React.FC = ({ const styles = StyleSheet.create({ container: { paddingTop: 0, + paddingRight: scale(2), }, bottomContainer: { flexDirection: 'row', diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx b/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx index ddb437e..b5b5cb8 100644 --- a/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx +++ b/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx @@ -1,6 +1,6 @@ import EditableTextField from '@/components/common/molecules/EditableTextField'; import { LoginContext } from '@/contexts/LoginContext'; -import { TextInputContext } from '@/contexts/textInputContext'; +import useTextInputStore from '@/contexts/textInputStore'; import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations'; import getIconFillColor from '@/utils/getIconFillColor'; import { @@ -18,7 +18,9 @@ const DailySubTodo = ({ item }) => { const [isEditing, setIsEditing] = useState(false); const { mutate: updateSubTodo } = useSubTodoUpdateMutation(); const { userId } = useContext(LoginContext); - const { setTodayTextInputOpen } = useContext(TextInputContext); + const { setTodayTextInputOpen } = useTextInputStore( + state => state.setTodayTextInputOpen, + ); const handleEdit = () => { setIsEditing(true); diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx index a16e3ba..52decc0 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx @@ -1,6 +1,6 @@ import getIconFillColor from '@/utils/getIconFillColor'; import { Icon, ListItem, Text } from '@ui-kitten/components'; -import React from 'react'; +import React, { useCallback } from 'react'; import { Pressable, StyleSheet, View } from 'react-native'; import { RenderItemParams } from 'react-native-draggable-flatlist'; import { moderateScale, scale, verticalScale } from 'react-native-size-matters'; @@ -24,80 +24,73 @@ interface TodoListItemProps extends RenderItemParams { categoryId: number; } -const TodoListItem: React.FC = ({ - item, - drag, +const TodoListItem: React.FC = React.memo( + ({ item, drag, isActive, setSubTodoInputActivated, categoryId }) => { + const { + isEditing, + setIsEditing, + theme, + handleCheckIconPress, + handleTodoListItemPress, + handleTodoListItemSubmitEditing, + } = useTodoListItem({ + item, + }); - isActive, - isEditing, - setIsEditing, - setIsSubTodoGenerateModalVisible, - onEdit, - setSubTodoInputActivated, - categoryId, -}) => { - const { - theme, - handleCheckIconPress, - handleTodoListItemPress, - handleTodoListItemSubmitEditing, - } = useTodoListItem({ - item, - isEditing, - setIsEditing, - setIsSubTodoGenerateModalVisible, - }); + const accessoryLeft = useCallback( + (props?) => { + return ( + + + + ); + }, + [handleCheckIconPress, item.isCompleted], + ); - const accessoryLeft = (props?) => { - return ( - handleCheckIconPress()} - style={styles.touchableCheck} - > - { + return ( + - - ); - }; + ); + }, [item, setSubTodoInputActivated, categoryId, setIsEditing]); - const accessoryRight = () => { - return ( - + const title = useCallback( + () => ( + + + {item.dueTime && ( + + {item.dueTime.split(':').slice(0, 2).join(':')} + + )} + + ), + [isEditing, handleTodoListItemSubmitEditing, item, theme], ); - }; - const title = () => ( - - - {item.dueTime && ( - - {item.dueTime.split(':').slice(0, 2).join(':')} - - )} - - ); - - return ( - <> + return ( = ({ disabled={isActive} title={title} /> - - ); -}; + ); + }, +); const styles = StyleSheet.create({ checkIcon: { @@ -132,4 +125,4 @@ const styles = StyleSheet.create({ }, }); -export default TodoListItem; +export default React.memo(TodoListItem); diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx index f5e308a..533712b 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx @@ -49,11 +49,10 @@ const MenuIconButton = onPress => ( ); const TodoMoreMenu = ({ - setIsSubTodoGenerateModalVisible, - onEdit, item, setSubTodoInputActivated, categoryId, + setIsEditing, }) => { const { handleEditPress, @@ -61,11 +60,10 @@ const TodoMoreMenu = ({ handleCreateSubTodoPress: handleAddSubTodoPress, handlePutTodoToInboxPress, } = useTodoMoreMenu({ - setIsSubTodoGenerateModalVisible, - onEdit, item, setSubTodoInputActivated, categoryId, + setIsEditing, }); const [visible, setVisible] = useState(false); const { t } = useTranslation(); diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx index 7578ca8..b720696 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx @@ -16,10 +16,9 @@ import { useContext } from 'react'; const useTodoMoreMenu = ({ item, - onEdit = () => {}, - setIsSubTodoGenerateModalVisible, setSubTodoInputActivated, categoryId, + setIsEditing, }) => { const { userId } = useContext(LoginContext); @@ -37,7 +36,7 @@ const useTodoMoreMenu = ({ const updatedTodo = { date: kstDate, - todo_id: item.id, + todoId: item.id, category_id: categoryId, }; updateTodoDate(updatedTodo); @@ -49,7 +48,7 @@ const useTodoMoreMenu = ({ userId: userId, todoId: item.id, }); - onEdit(); + setIsEditing(true); }; const handleDeletePress = () => { @@ -69,10 +68,6 @@ const useTodoMoreMenu = ({ }); }; - const handleGenerateSubTodoPress = () => { - setIsSubTodoGenerateModalVisible(true); - }; - const handleCreateSubTodoPress = () => { handleLogEvent(TODOMODAL_CREATESUBTODO_CLICK_EVENT, { time: new Date().toISOString(), @@ -95,7 +90,6 @@ const useTodoMoreMenu = ({ handleEditPress, handleDeletePress, handleChaneDatePress, - handleGenerateSubTodoPress, handleCreateSubTodoPress, handlePutTodoToInboxPress, }; diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts index 98383f0..eddcf38 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts @@ -1,6 +1,5 @@ -import { TextInputContext } from '@/contexts/textInputContext'; import { useTheme } from '@ui-kitten/components'; -import { useContext, useCallback } from 'react'; +import { useContext, useCallback, useState } from 'react'; import { LoginContext } from '../../../../../contexts/LoginContext'; import { useTodoUpdateMutation } from '../../../../../hooks/api/useTodoMutations'; import { @@ -10,16 +9,11 @@ import { handleLogEvent, } from '../../../../../utils/logEvent'; -const useTodoListItem = ({ - item, - isEditing, - setIsEditing, - setIsSubTodoGenerateModalVisible, -}) => { +const useTodoListItem = ({ item }) => { const { mutate: updateTodo } = useTodoUpdateMutation(); const { userId } = useContext(LoginContext); const theme = useTheme(); - const { setTodayTextInputOpen } = useContext(TextInputContext); + const [isEditing, setIsEditing] = useState(false); const checkIconlogPressEvent = () => { handleLogEvent(DAILYTODO_TODOCOMPLETE_CLICK_EVENT, { @@ -60,16 +54,10 @@ const useTodoListItem = ({ const handleTodoListItemSubmitEditing = useCallback( (content: string) => { handleTodoContentUpdate(content); - setTodayTextInputOpen(false); setIsEditing(false); }, - [handleTodoContentUpdate, setTodayTextInputOpen, setIsEditing], + [handleTodoContentUpdate, setIsEditing], ); - - const handleGenerateIconPress = () => { - setIsSubTodoGenerateModalVisible(true); - }; - const handleSettingIconPress = () => { handleLogEvent(DAILYTODO_MEATBALLMENU_CLICK_EVENT, { time: new Date().toISOString(), @@ -85,7 +73,6 @@ const useTodoListItem = ({ setIsEditing, handleTodoListItemPress, handleTodoListItemSubmitEditing, - handleGenerateIconPress, handleSettingIconPress, }; }; diff --git a/contexts/textInputContext.js b/contexts/textInputContext.js deleted file mode 100644 index bfc925c..0000000 --- a/contexts/textInputContext.js +++ /dev/null @@ -1,31 +0,0 @@ -import { createContext, useState } from 'react'; - -export const TextInputContext = createContext(); - -const TextInputProvider = ({ children }) => { - const [isTodayTextInputOpen, setTodayTextInputOpen] = useState(false); - const [todayActivatedCategoryId, setTodayActivatedCategoryId] = - useState(null); - const [isInboxTextInputOpen, setInboxTextInputOpen] = useState(false); - const [inboxActivatedCategoryId, setInboxActivatedCategoryId] = - useState(null); - - return ( - - {children} - - ); -}; - -export default TextInputProvider; diff --git a/contexts/textInputStore.js b/contexts/textInputStore.js new file mode 100644 index 0000000..8f6d0be --- /dev/null +++ b/contexts/textInputStore.js @@ -0,0 +1,17 @@ +import { create } from 'zustand'; + +const useTextInputStore = create(set => ({ + isTodayTextInputOpen: false, + setTodayTextInputOpen: isOpen => set({ isTodayTextInputOpen: isOpen }), + + todayActivatedCategoryId: null, + setTodayActivatedCategoryId: id => set({ todayActivatedCategoryId: id }), + + isInboxTextInputOpen: false, + setInboxTextInputOpen: isOpen => set({ isInboxTextInputOpen: isOpen }), + + inboxActivatedCategoryId: null, + setInboxActivatedCategoryId: id => set({ inboxActivatedCategoryId: id }), +})); + +export default useTextInputStore; diff --git a/contexts/todoEditStore.ts b/contexts/todoEditStore.ts new file mode 100644 index 0000000..d36b15e --- /dev/null +++ b/contexts/todoEditStore.ts @@ -0,0 +1,18 @@ +import { create } from 'zustand'; + +interface TodoEditStore { + isEditing: boolean; + editingTodoId: number | null; + isSubTodoModalVisible: boolean; + setIsEditing: (isEditing: boolean) => void; + setEditingTodoId: (todoId: number | null) => void; +} + +export const useTodoEditStore = create(set => ({ + isEditing: false, + editingTodoId: null, + isSubTodoModalVisible: false, + isTodayTextInputOpen: false, + setIsEditing: isEditing => set({ isEditing }), + setEditingTodoId: todoId => set({ editingTodoId: todoId }), +})); From 0c145a0f1ab3a1aad09a50c3e756c7a7fe830403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:46:53 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[SZ-553]feat:=20=ED=88=AC=EB=91=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20optimistic=20ui=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/molecules/EditableTextField.tsx | 33 +++++++++++++++---- .../dailyTodo/todoListItem/TodoListItem.tsx | 5 ++- .../dailyTodo/todoListItem/useTodoListItem.ts | 19 +---------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/components/common/molecules/EditableTextField.tsx b/components/common/molecules/EditableTextField.tsx index 76e5754..c44e7cd 100644 --- a/components/common/molecules/EditableTextField.tsx +++ b/components/common/molecules/EditableTextField.tsx @@ -4,38 +4,57 @@ import { StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native'; import { Todo } from '../../../types/todo'; import { moderateScale, scale } from 'react-native-size-matters'; import { memo } from 'react'; +import { useTodoUpdateMutation } from '@/hooks/api/useTodoMutations'; +import { + NativeSyntheticEvent, + TextInputSubmitEditingEventData, +} from 'react-native'; interface EditableListItemTitleProps { isEditing: boolean; - handleSubmitEditing: (content) => void; + setIsEditing: (isEditing: boolean) => void; item: Todo; inputStyles?: StyleProp | null; textStyles?: StyleProp | null; } +const handleTodoContentUpdate = (content, item, updateTodo) => { + const updatedData = { + todoId: item.id, + content: content, + }; + updateTodo(updatedData); +}; + const EditableTextField = memo( ({ isEditing, - handleSubmitEditing, + setIsEditing, item, inputStyles = styles.input, textStyles = styles.text, }: EditableListItemTitleProps) => { const [content, setContent] = useState(item.content); + const { mutate: updateTodo } = useTodoUpdateMutation(); - const onSubmit = useCallback(() => { - handleSubmitEditing(content); - }, [content, handleSubmitEditing]); + const handleTodoListItemSubmitEditing = useCallback( + async (e: NativeSyntheticEvent) => { + handleTodoContentUpdate(e.nativeEvent.text, item, updateTodo); + await new Promise(resolve => setTimeout(resolve, 100)); // 0.1초 대기 + setIsEditing(false); + }, + [item, setIsEditing, updateTodo], + ); const onChangeText = useCallback((text: string) => { setContent(text); }, []); - console.log(content); + console.log(content, ' ', item.content); return isEditing ? ( await handleTodoListItemSubmitEditing(e)} style={inputStyles} autoFocus={true} /> diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx index 52decc0..d4a9b03 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/TodoListItem.tsx @@ -32,7 +32,6 @@ const TodoListItem: React.FC = React.memo( theme, handleCheckIconPress, handleTodoListItemPress, - handleTodoListItemSubmitEditing, } = useTodoListItem({ item, }); @@ -72,7 +71,7 @@ const TodoListItem: React.FC = React.memo( {item.dueTime && ( @@ -87,7 +86,7 @@ const TodoListItem: React.FC = React.memo( )} ), - [isEditing, handleTodoListItemSubmitEditing, item, theme], + [isEditing, item, setIsEditing, theme], ); return ( diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts index eddcf38..fd794c0 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/useTodoListItem.ts @@ -1,5 +1,5 @@ import { useTheme } from '@ui-kitten/components'; -import { useContext, useCallback, useState } from 'react'; +import { useContext, useState } from 'react'; import { LoginContext } from '../../../../../contexts/LoginContext'; import { useTodoUpdateMutation } from '../../../../../hooks/api/useTodoMutations'; import { @@ -42,22 +42,6 @@ const useTodoListItem = ({ item }) => { }); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - const handleTodoContentUpdate = content => { - const updatedData = { - todoId: item.id, - content: content, - }; - updateTodo(updatedData); - }; - - const handleTodoListItemSubmitEditing = useCallback( - (content: string) => { - handleTodoContentUpdate(content); - setIsEditing(false); - }, - [handleTodoContentUpdate, setIsEditing], - ); const handleSettingIconPress = () => { handleLogEvent(DAILYTODO_MEATBALLMENU_CLICK_EVENT, { time: new Date().toISOString(), @@ -72,7 +56,6 @@ const useTodoListItem = ({ item }) => { isEditing, setIsEditing, handleTodoListItemPress, - handleTodoListItemSubmitEditing, handleSettingIconPress, }; }; From 68a5aafa3c440e4a19600f271327c3536c0fde9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 05:22:21 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[SZ-553]feat:=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=EC=95=A4=EB=93=9C=EB=A1=AD=20=EC=98=B5=ED=8B=B0?= =?UTF-8?q?=EB=AF=B8=EC=8A=A4=ED=8B=B1=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/todayView/TextInput.tsx | 64 +++++++++ components/todayView/TodoList.tsx | 56 ++++++++ .../todayView/dailyTodos/DailyTodos.tsx | 130 +++++------------- hooks/api/useTodoMutations.ts | 43 +++++- hooks/todo/useFilteredTodo.js | 19 ++- hooks/todo/useHandleDrag.js | 6 +- 6 files changed, 204 insertions(+), 114 deletions(-) create mode 100644 components/todayView/TextInput.tsx create mode 100644 components/todayView/TodoList.tsx diff --git a/components/todayView/TextInput.tsx b/components/todayView/TextInput.tsx new file mode 100644 index 0000000..3473971 --- /dev/null +++ b/components/todayView/TextInput.tsx @@ -0,0 +1,64 @@ +import React, { memo } from 'react'; +import { TextInput as RNTextInput, View, StyleSheet } from 'react-native'; +import { Icon } from '@ui-kitten/components'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import colors from '@/theme/theme.json'; +import { useTranslation } from 'react-i18next'; + +interface TodoInputProps { + input: string; + setInput: (text: string) => void; + onSubmit: () => void; +} + +const TodoInput = memo(({ input, setInput, onSubmit }: TodoInputProps) => { + const { t } = useTranslation(); + + return ( + + + + + + + ); +}); + +export default TodoInput; + +const styles = StyleSheet.create({ + inputWrapper: { + width: '100%', + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.Gray02, + backgroundColor: 'white', + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: scale(7), + paddingVertical: verticalScale(8), + }, + checkIcon: { + width: scale(24), + height: verticalScale(24), + }, + textInput: { + flex: 1, + backgroundColor: colors.Gray02, + borderRadius: moderateScale(4), + paddingVertical: verticalScale(8), + paddingHorizontal: scale(10), + }, +}); diff --git a/components/todayView/TodoList.tsx b/components/todayView/TodoList.tsx new file mode 100644 index 0000000..d7a11a2 --- /dev/null +++ b/components/todayView/TodoList.tsx @@ -0,0 +1,56 @@ +import { + DEFAULT_SCROLL_EVENT_THROTTLE, + handleScroll, +} from '@/utils/handleScroll'; +import { TODAYVIEW_SCROLL_EVENT } from '@/utils/logEvent'; +import React, { memo } from 'react'; +import { StyleSheet } from 'react-native'; +import DraggableFlatList from 'react-native-draggable-flatlist'; +import { Todo } from '@/types/todo'; + +const TodoList = memo( + ({ + todos, + renderItem, + onDragEnd, + onDragStart, + userId, + setTodos, + }: { + todos: Todo[]; + renderItem: (info: { + item: Todo; + drag: () => void; + isActive: boolean; + }) => React.ReactNode; + onDragEnd: (params: { from: number; to: number; data: Todo[] }) => void; + onDragStart: () => void; + userId: string; + setTodos: (todos: Todo[]) => void; + }) => ( + { + onDragEnd({ from, to, data }); + setTodos(data); + }} + onDragBegin={onDragStart} + keyExtractor={item => item.id.toString()} + onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)} + scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE} + simultaneousHandlers={[]} + activationDistance={20} + containerStyle={styles.flatListContainer} + extraData={todos} + /> + ), +); + +export default TodoList; + +const styles = StyleSheet.create({ + flatListContainer: { + flex: 1, + }, +}); diff --git a/components/todayView/dailyTodos/DailyTodos.tsx b/components/todayView/dailyTodos/DailyTodos.tsx index ad0b4de..d7ba23c 100644 --- a/components/todayView/dailyTodos/DailyTodos.tsx +++ b/components/todayView/dailyTodos/DailyTodos.tsx @@ -5,67 +5,56 @@ import useCreateTodo from '@/hooks/todo/useCreateTodo'; import useFilteredTodos from '@/hooks/todo/useFilteredTodo'; import useHandleDrag from '@/hooks/todo/useHandleDrag'; import '@/locales/index'; -import colors from '@/theme/theme.json'; -import { - DEFAULT_SCROLL_EVENT_THROTTLE, - handleScroll, -} from '@/utils/handleScroll'; import { handleLogEvent, - TODAYVIEW_SCROLL_EVENT, TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, } from '@/utils/logEvent'; -import { Icon } from '@ui-kitten/components'; -import React, { useContext } from 'react'; -import { useTranslation } from 'react-i18next'; -import { StyleSheet, TextInput, View } from 'react-native'; -import DraggableFlatList, { - ScaleDecorator, -} from 'react-native-draggable-flatlist'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { ScaleDecorator } from 'react-native-draggable-flatlist'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { moderateScale, scale, verticalScale } from 'react-native-size-matters'; import DailyTodo from './dailyTodo/DailyTodo'; +import TodoList from '../TodoList'; +import TodoInput from '../TextInput'; const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { const { userId } = useContext(LoginContext); const { selectedDate } = useContext(DateContext); - const { t } = useTranslation(); - const currentTodos = useFilteredTodos(todosData, categoryId, selectedDate); + const [todos, setTodos] = useState(currentTodos); const { input, setInput, handleSubmit } = useCreateTodo( userId, categoryId, selectedDate, ); - const handleDragEndWithCallback = ({ data, from, to }) => { - handleDragEnd({ data, from, to }); - onDragEnd?.(); - }; - const { isTodayTextInputOpen, todayActivatedCategoryId, setTodayTextInputOpen, - } = useTextInputStore(state => ({ - isTodayTextInputOpen: state.isTodayTextInputOpen, - todayActivatedCategoryId: state.todayActivatedCategoryId, - setTodayTextInputOpen: state.setTodayTextInputOpen, - })); + } = useTextInputStore(); const handleDragEnd = useHandleDrag(); - const handleInputSubmit = () => { + const handleInputSubmit = useCallback(() => { handleLogEvent(TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, { time: new Date().toISOString(), - userId: userId, + userId, }); handleSubmit(); setTodayTextInputOpen(false); - }; + }, [userId, handleSubmit, setTodayTextInputOpen]); + + const handleDragEndWithCallback = useCallback( + ({ from, to, data }) => { + handleDragEnd({ from, to, data }); + onDragEnd?.(); + }, + [handleDragEnd, onDragEnd], + ); - const renderTodo = ({ item, drag, isActive }) => { - return ( + const renderTodo = useCallback( + ({ item, drag, isActive }) => ( { categoryId={categoryId} /> - ); - }; + ), + [categoryId], + ); + + useEffect(() => { + setTodos(currentTodos); + }, [currentTodos]); return ( - onDragStart?.()} - keyExtractor={item => item.id.toString()} - onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)} - scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE} - simultaneousHandlers={[]} - activationDistance={20} - containerStyle={styles.flatListContainer} + onDragStart={onDragStart} + userId={userId} /> {isTodayTextInputOpen && categoryId === todayActivatedCategoryId && ( - - - - - - + )} @@ -125,40 +103,6 @@ const styles = StyleSheet.create({ flex: 1, backgroundColor: 'white', }, - flatListContainer: { - flex: 1, - }, - keyboardAvoidingView: { - width: '100%', - position: 'absolute', - bottom: 0, - backgroundColor: 'white', - }, - inputWrapper: { - width: '100%', - borderTopWidth: StyleSheet.hairlineWidth, - borderTopColor: colors.Gray02, - backgroundColor: 'white', - }, - inputContainer: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: scale(7), - paddingVertical: verticalScale(8), - }, - checkIcon: { - width: scale(24), - height: verticalScale(24), - }, - textInput: { - flex: 1, - backgroundColor: colors.Gray02, - borderRadius: moderateScale(4), - marginLeft: scale(4), - paddingHorizontal: scale(8), - height: scale(24), - fontSize: moderateScale(14), - }, }); export default DailyTodos; diff --git a/hooks/api/useTodoMutations.ts b/hooks/api/useTodoMutations.ts index dac84f7..c7ccddb 100644 --- a/hooks/api/useTodoMutations.ts +++ b/hooks/api/useTodoMutations.ts @@ -49,7 +49,10 @@ export const useTodoAddMutation = () => { }; const updateTodoFetcher = async ( - updatedData: Partial & { todoId: number }, + updatedData: Partial & { + todoId: number; + patchRank?: { prevId: number | null }; + }, ) => { const data = await Api.updateTodo(updatedData); return data; @@ -66,16 +69,44 @@ export const useTodoUpdateMutation = () => { const previousTodos = queryClient.getQueryData([TODO_QUERY_KEY]); const previousInbox = queryClient.getQueryData([INBOX_QUERY_KEY]); - const updateTodo = (old: Todo[] | undefined) => - old?.map(todo => { + const updateTodoOrder = (old: Todo[] | undefined) => { + if (!old) return []; + + if ('patchRank' in updatedTodo) { + const movedTodo = old.find(todo => todo.id === updatedTodo.todoId); + if (!movedTodo) return old; + + const filteredTodos = old.filter( + todo => todo.id !== updatedTodo.todoId, + ); + + if (updatedTodo.patchRank.prevId === null) { + return [movedTodo, ...filteredTodos]; + } + + const targetIndex = filteredTodos.findIndex( + todo => todo.id === updatedTodo.patchRank!.prevId, + ); + + if (targetIndex === -1) return old; + + return [ + ...filteredTodos.slice(0, targetIndex + 1), + movedTodo, + ...filteredTodos.slice(targetIndex + 1), + ]; + } + + return old.map(todo => { if (todo.id === updatedTodo.todoId) { return { ...todo, ...updatedTodo }; } return todo; - }) || []; + }); + }; - queryClient.setQueryData([TODO_QUERY_KEY], updateTodo); - queryClient.setQueryData([INBOX_QUERY_KEY], updateTodo); + queryClient.setQueryData([TODO_QUERY_KEY], updateTodoOrder); + queryClient.setQueryData([INBOX_QUERY_KEY], updateTodoOrder); return { previousTodos, previousInbox }; }, diff --git a/hooks/todo/useFilteredTodo.js b/hooks/todo/useFilteredTodo.js index b6d9261..6d41cb3 100644 --- a/hooks/todo/useFilteredTodo.js +++ b/hooks/todo/useFilteredTodo.js @@ -1,17 +1,14 @@ -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; const useFilteredTodos = (todos, selectedCategory, selectedDate) => { - const [filteredTodos, setFilteredTodos] = useState([]); + const filteredTodos = useMemo(() => { + if (!todos) return []; - useEffect(() => { - if (todos) { - const filtered = todos.filter( - todo => - todo.categoryId === selectedCategory && - todo.date === selectedDate.format('YYYY-MM-DD'), - ); - setFilteredTodos(filtered); - } + return todos.filter( + todo => + todo.categoryId === selectedCategory && + todo.date === selectedDate.format('YYYY-MM-DD'), + ); }, [todos, selectedCategory, selectedDate]); return filteredTodos; diff --git a/hooks/todo/useHandleDrag.js b/hooks/todo/useHandleDrag.js index 8e28dec..d2bc410 100644 --- a/hooks/todo/useHandleDrag.js +++ b/hooks/todo/useHandleDrag.js @@ -5,9 +5,7 @@ const useHandleDrag = () => { const handleDragEnd = ({ from, to, data: newData }) => { if (!newData || newData.length === 0) return; - if (from === to) { - return; - } + if (from === to) return; let prevTodoId = null; let nextTodoId = null; @@ -22,7 +20,7 @@ const useHandleDrag = () => { } const updatedData = { - todo_id: newData[to].id, + todoId: newData[to].id, patchRank: { prevId: prevTodoId, nextId: nextTodoId, From 67f0cffcaa84150c4abe6e256ee6fa945bd864e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:24:14 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[SZ-553]feat:=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=8B=9C=20=EA=B2=80=EC=9D=80=ED=99=94=EB=A9=B4=20=EA=B9=9C?= =?UTF-8?q?=EC=A7=9D=EC=9D=B4=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/index.jsx | 4 +-- .../common/molecules/EditableTextField.tsx | 24 +++++++++++++++-- .../CalendarBottomSheet.tsx | 20 ++------------ .../SubTodoGenerateBottomSheet.tsx | 26 ++----------------- 4 files changed, 28 insertions(+), 46 deletions(-) diff --git a/app/(tabs)/index.jsx b/app/(tabs)/index.jsx index 58447a4..1cf6acf 100644 --- a/app/(tabs)/index.jsx +++ b/app/(tabs)/index.jsx @@ -87,9 +87,9 @@ const TodayView = () => { scrollEnabled={!isDragging} /> - - + + diff --git a/components/common/molecules/EditableTextField.tsx b/components/common/molecules/EditableTextField.tsx index c44e7cd..5b1bf29 100644 --- a/components/common/molecules/EditableTextField.tsx +++ b/components/common/molecules/EditableTextField.tsx @@ -1,5 +1,11 @@ import { Text } from '@ui-kitten/components'; -import React, { useState, useCallback } from 'react'; +import React, { + useState, + useCallback, + useContext, + useEffect, + useRef, +} from 'react'; import { StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native'; import { Todo } from '../../../types/todo'; import { moderateScale, scale } from 'react-native-size-matters'; @@ -9,6 +15,7 @@ import { NativeSyntheticEvent, TextInputSubmitEditingEventData, } from 'react-native'; +import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider'; interface EditableListItemTitleProps { isEditing: boolean; @@ -36,6 +43,18 @@ const EditableTextField = memo( }: EditableListItemTitleProps) => { const [content, setContent] = useState(item.content); const { mutate: updateTodo } = useTodoUpdateMutation(); + const { bottomSheetRef } = useContext(AIBottomSheetContext); + const inputRef = useRef(null); + + useEffect(() => { + if (isEditing) { + bottomSheetRef.current?.close(); + const timer = setTimeout(() => { + inputRef.current?.focus(); + }, 100); + return () => clearTimeout(timer); + } + }, [isEditing, bottomSheetRef]); const handleTodoListItemSubmitEditing = useCallback( async (e: NativeSyntheticEvent) => { @@ -49,14 +68,15 @@ const EditableTextField = memo( const onChangeText = useCallback((text: string) => { setContent(text); }, []); - console.log(content, ' ', item.content); return isEditing ? ( await handleTodoListItemSubmitEditing(e)} style={inputStyles} autoFocus={true} + onFocus={() => bottomSheetRef.current?.close()} /> ) : ( {content} diff --git a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx index 7c1638a..262123f 100644 --- a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx +++ b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx @@ -1,9 +1,6 @@ import { View, Text, Pressable, StyleSheet } from 'react-native'; -import React, { useCallback, useContext, useMemo } from 'react'; -import BottomSheet, { - BottomSheetBackdrop, - BottomSheetView, -} from '@gorhom/bottom-sheet'; +import React, { useContext, useMemo } from 'react'; +import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet'; import { DateContext } from '@/contexts/DateContext'; import { CalendarBottomSheetContext } from '@/contexts/CalendarBottomSheetProvider'; import { @@ -99,25 +96,12 @@ const CalendarBottomSheet = ({ isTodo, item }) => { startDayOfWeek: 1, }); - const renderBackdrop = useCallback( - props => ( - - ), - [], - ); - return ( { const { bottomSheetRef } = useContext(AIBottomSheetContext); - const renderBackdrop = useCallback( - props => ( - - ), - [], - ); - useEffect(() => { if (generatedSubTodos.length > 0) { setSelectedIndexes(generatedSubTodos.map((_, index) => index)); @@ -220,7 +199,6 @@ const SubTodoGenerateBottomSheet = ({ item }) => { snapPoints={snapPoints} index={-1} enablePanDownToClose={true} - backdropComponent={renderBackdrop} > {renderContent()} From 541c89484d8f1a4623cf8e19ce75b2c85255cd18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:38:12 +0900 Subject: [PATCH 09/12] =?UTF-8?q?[SZ-553]feat:=20=ED=88=AC=EB=91=90=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=8B=9C=20=EC=83=9D=EC=84=B1=ED=9B=84=20?= =?UTF-8?q?=ED=8F=AC=EC=BB=A4=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/index.jsx | 9 +- components/todayView/TodoList.tsx | 97 ++++++++++++------- .../todayView/dailyTodos/DailyTodos.tsx | 4 + 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/app/(tabs)/index.jsx b/app/(tabs)/index.jsx index 1cf6acf..7facb97 100644 --- a/app/(tabs)/index.jsx +++ b/app/(tabs)/index.jsx @@ -42,9 +42,11 @@ const TodayView = () => { userId: userId, }); - const renderCategoriesTodo = ({ item }) => { + const renderCategoriesTodo = ({ item, index }) => { + const isLastItem = index === categoriesData.length - 1; + return ( - + void; - isActive: boolean; - }) => React.ReactNode; - onDragEnd: (params: { from: number; to: number; data: Todo[] }) => void; - onDragStart: () => void; - userId: string; - setTodos: (todos: Todo[]) => void; - }) => ( - { - onDragEnd({ from, to, data }); - setTodos(data); - }} - onDragBegin={onDragStart} - keyExtractor={item => item.id.toString()} - onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)} - scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE} - simultaneousHandlers={[]} - activationDistance={20} - containerStyle={styles.flatListContainer} - extraData={todos} - /> + forwardRef< + FlatList, + { + todos: Todo[]; + renderItem: (info: { + item: Todo; + drag: () => void; + isActive: boolean; + }) => React.ReactNode; + onDragEnd: (params: { from: number; to: number; data: Todo[] }) => void; + onDragStart: () => void; + userId: string; + setTodos: (todos: Todo[]) => void; + } + >( + ( + { + todos, + renderItem, + onDragEnd, + onDragStart, + userId, + setTodos, + }: { + todos: Todo[]; + renderItem: (info: { + item: Todo; + drag: () => void; + isActive: boolean; + }) => React.ReactNode; + onDragEnd: (params: { from: number; to: number; data: Todo[] }) => void; + onDragStart: () => void; + userId: string; + setTodos: (todos: Todo[]) => void; + }, + ref, + ) => { + return ( + { + onDragEnd({ from, to, data }); + setTodos(data); + }} + onDragBegin={onDragStart} + keyExtractor={item => item.id.toString()} + onScroll={() => handleScroll(TODAYVIEW_SCROLL_EVENT, userId)} + scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE} + simultaneousHandlers={[]} + activationDistance={20} + containerStyle={styles.flatListContainer} + extraData={todos} + /> + ); + }, ), ); diff --git a/components/todayView/dailyTodos/DailyTodos.tsx b/components/todayView/dailyTodos/DailyTodos.tsx index d7ba23c..48a63d4 100644 --- a/components/todayView/dailyTodos/DailyTodos.tsx +++ b/components/todayView/dailyTodos/DailyTodos.tsx @@ -36,6 +36,8 @@ const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { const handleDragEnd = useHandleDrag(); + const todoListRef = React.useRef(null); + const handleInputSubmit = useCallback(() => { handleLogEvent(TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, { time: new Date().toISOString(), @@ -43,6 +45,7 @@ const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { }); handleSubmit(); setTodayTextInputOpen(false); + todoListRef.current?.scrollToEnd(); }, [userId, handleSubmit, setTodayTextInputOpen]); const handleDragEndWithCallback = useCallback( @@ -75,6 +78,7 @@ const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { Date: Sat, 23 Nov 2024 09:04:20 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[SZ-553]feat:=20=EC=B5=9C=EC=A2=85=20?= =?UTF-8?q?=EB=A7=88=EB=AC=B4=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/categoryView/categoryListView.jsx | 2 + app/settingsView/settingsAccountView.tsx | 1 + app/settingsView/settingsView.jsx | 6 +- .../common/molecules/EditableTextField.tsx | 19 +++- .../molecules/InboxEditableTextField.tsx | 49 ++++++++++ .../inboxView/inboxTodos/InboxTodos.tsx | 91 +++++-------------- .../inboxTodos/inboxTodo/InboxTodo.jsx | 26 +----- .../inboxTodo/todoListItem/TodoListItem.tsx | 27 ++---- .../todoMoreMenu/TodoMoreMenu.tsx | 15 +-- .../todoMoreMenu/useTodoMoreMenu.tsx | 6 -- .../inboxTodo/todoListItem/useTodoListItem.ts | 14 +-- .../todayView/dailyTodos/DailyTodos.tsx | 6 ++ .../CalendarBottomSheet.tsx | 2 +- .../dailyTodo/subTodoList/DailySubTodo.jsx | 14 +-- .../dailyTodo/subTodoList/SubTodoList.tsx | 10 +- .../todoMoreMenu/TodoMoreMenu.tsx | 1 - .../SubTodoGenerateBottomSheet.tsx | 18 +++- contexts/LoginContext.js | 3 + contexts/listRefStore.ts | 18 ++++ hooks/auth/useLogin.js | 4 +- 20 files changed, 176 insertions(+), 156 deletions(-) create mode 100644 components/common/molecules/InboxEditableTextField.tsx create mode 100644 contexts/listRefStore.ts diff --git a/app/categoryView/categoryListView.jsx b/app/categoryView/categoryListView.jsx index 9890adb..0bca343 100644 --- a/app/categoryView/categoryListView.jsx +++ b/app/categoryView/categoryListView.jsx @@ -49,6 +49,8 @@ const styles = StyleSheet.create({ }, list: { backgroundColor: 'white', + paddingHorizontal: scale(20), + flex: 1, }, }); diff --git a/app/settingsView/settingsAccountView.tsx b/app/settingsView/settingsAccountView.tsx index 18dc24d..a77cd8f 100644 --- a/app/settingsView/settingsAccountView.tsx +++ b/app/settingsView/settingsAccountView.tsx @@ -1,5 +1,6 @@ import '@/locales/index'; import { IndexPath, Layout, Menu, MenuItem } from '@ui-kitten/components'; +import React from 'react'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { SafeAreaView, StyleSheet } from 'react-native'; diff --git a/app/settingsView/settingsView.jsx b/app/settingsView/settingsView.jsx index 89c2c59..54354fd 100644 --- a/app/settingsView/settingsView.jsx +++ b/app/settingsView/settingsView.jsx @@ -8,7 +8,7 @@ import { useTheme, } from '@ui-kitten/components'; import { useRouter } from 'expo-router'; -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; import ConfirmModal from '@/components/common/molecules/ConfirmModal'; import { api as Api } from '@/utils/api'; @@ -25,6 +25,7 @@ import { } from 'react-native'; import { heightPercentage, widthPercentage } from '@/utils/responsiveSize'; import fontStyles from '@/theme/fontStyles'; +import { LoginContext } from '@/contexts/LoginContext'; const privacyPolicyUrl = 'https://swm-onestep.github.io/posts/%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8-copy/'; const termsOfServiceUrl = @@ -45,6 +46,7 @@ const renderCurrentStatus = currentStatus => () => ( ); const SettingsView = () => { + const { userName } = useContext(LoginContext); const { t } = useTranslation(); const theme = useTheme(); const { clear: clearStorage } = useStorage(); @@ -175,7 +177,7 @@ const SettingsView = () => { {t('views.settingsView.greeting')} - user + {userName} diff --git a/components/common/molecules/EditableTextField.tsx b/components/common/molecules/EditableTextField.tsx index 5b1bf29..982b20f 100644 --- a/components/common/molecules/EditableTextField.tsx +++ b/components/common/molecules/EditableTextField.tsx @@ -16,6 +16,7 @@ import { TextInputSubmitEditingEventData, } from 'react-native'; import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider'; +import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations'; interface EditableListItemTitleProps { isEditing: boolean; @@ -23,6 +24,10 @@ interface EditableListItemTitleProps { item: Todo; inputStyles?: StyleProp | null; textStyles?: StyleProp | null; + isTodo?: boolean; + handleSubmitEditing?: ( + e: NativeSyntheticEvent, + ) => void; } const handleTodoContentUpdate = (content, item, updateTodo) => { @@ -40,9 +45,11 @@ const EditableTextField = memo( item, inputStyles = styles.input, textStyles = styles.text, + isTodo = true, }: EditableListItemTitleProps) => { const [content, setContent] = useState(item.content); const { mutate: updateTodo } = useTodoUpdateMutation(); + const { mutate: updateSubTodo } = useSubTodoUpdateMutation(); const { bottomSheetRef } = useContext(AIBottomSheetContext); const inputRef = useRef(null); @@ -58,11 +65,19 @@ const EditableTextField = memo( const handleTodoListItemSubmitEditing = useCallback( async (e: NativeSyntheticEvent) => { - handleTodoContentUpdate(e.nativeEvent.text, item, updateTodo); + if (isTodo) { + handleTodoContentUpdate(e.nativeEvent.text, item, updateTodo); + } else { + const updatedData = { + subtodoId: item.id, + content: e.nativeEvent.text, + }; + updateSubTodo(updatedData); + } await new Promise(resolve => setTimeout(resolve, 100)); // 0.1초 대기 setIsEditing(false); }, - [item, setIsEditing, updateTodo], + [item, setIsEditing, updateTodo, updateSubTodo, isTodo], ); const onChangeText = useCallback((text: string) => { diff --git a/components/common/molecules/InboxEditableTextField.tsx b/components/common/molecules/InboxEditableTextField.tsx new file mode 100644 index 0000000..3d6aa46 --- /dev/null +++ b/components/common/molecules/InboxEditableTextField.tsx @@ -0,0 +1,49 @@ +import { Text } from '@ui-kitten/components'; +import React, { useState } from 'react'; +import { StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native'; +import { Todo } from '../../../types/todo'; +import { moderateScale, scale } from 'react-native-size-matters'; + +interface EditableListItemTitleProps { + isEditing: boolean; + handleSubmitEditing: (content) => void; + item: Todo; + inputStyles?: StyleProp | null; + textStyles?: StyleProp | null; +} + +const EditableTextField = ({ + isEditing, + handleSubmitEditing, + item, + inputStyles = styles.input, + textStyles = styles.text, +}: EditableListItemTitleProps) => { + const [content, setContent] = useState(item.content); + return isEditing ? ( + handleSubmitEditing(content)} + autoFocus={true} + style={inputStyles} + /> + ) : ( + {item.content} + ); +}; + +export default EditableTextField; + +const styles = StyleSheet.create({ + text: { + paddingLeft: scale(8), + fontSize: moderateScale(14), + }, + input: { + justifyContent: 'center', + alignItems: 'center', + paddingLeft: scale(8), + fontSize: moderateScale(14), + }, +}); diff --git a/components/inboxView/inboxTodos/InboxTodos.tsx b/components/inboxView/inboxTodos/InboxTodos.tsx index 66d4160..8899780 100644 --- a/components/inboxView/inboxTodos/InboxTodos.tsx +++ b/components/inboxView/inboxTodos/InboxTodos.tsx @@ -1,6 +1,5 @@ import { LoginContext } from '@/contexts/LoginContext'; import useTextInputStore from '@/contexts/textInputStore'; -import { useTodoUpdateMutation } from '@/hooks/api/useTodoMutations'; import useCreateInboxTodo from '@/hooks/todo/useCreateInboxTodo'; import useFilteredInboxTodos from '@/hooks/todo/useFilteredInboxTodo'; import '@/locales/index'; @@ -14,30 +13,19 @@ import { INBOXVIEW_SCROLL_EVENT, TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, } from '@/utils/logEvent'; -import { Icon } from '@ui-kitten/components'; -import { LexoRank } from 'lexorank'; + import React, { Fragment, useContext } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - KeyboardAvoidingView, - StyleSheet, - TextInput, - View, -} from 'react-native'; -import DraggableFlatList, { - ScaleDecorator, -} from 'react-native-draggable-flatlist'; -import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { KeyboardAccessoryView } from 'react-native-keyboard-accessory'; +import { KeyboardAvoidingView, StyleSheet } from 'react-native'; + +import { FlatList, GestureHandlerRootView } from 'react-native-gesture-handler'; import { moderateScale, scale, verticalScale } from 'react-native-size-matters'; import InboxTodo from './inboxTodo/InboxTodo'; +import TodoInput from '@/components/todayView/TextInput'; const InboxTodos = ({ todosData, categoryId }) => { - const { t } = useTranslation(); const { userId } = useContext(LoginContext); const inboxCurrentTodos = useFilteredInboxTodos(todosData, categoryId); - const { mutate: updateInboxTodo } = useTodoUpdateMutation(); const { isInboxTextInputOpen, inboxActivatedCategoryId, @@ -52,24 +40,10 @@ const InboxTodos = ({ todosData, categoryId }) => { categoryId, ); - const renderTodo = ({ item, drag, isActive }) => { - return ( - - - - ); + const renderTodo = ({ item }) => { + return ; }; - const handleDragEnd = async ({ data }) => { - const updatedData = data.map(item => { - return { - todoId: item.id, - order: LexoRank.parse(item.order).toString(), - }; - }); - updateInboxTodo(updatedData); - }; - //TODO: order 순서 생각해보니까 이제 서버에서 하잖아? 일단 전체 투두에서 계속 마지막으로 붙이는 식으로 구현 const handleInputSubmit = async () => { handleLogEvent(TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, { time: new Date().toISOString(), @@ -81,42 +55,27 @@ const InboxTodos = ({ todosData, categoryId }) => { }; return ( - + - renderTodo({ item })} keyExtractor={item => item.id.toString()} onScroll={() => handleScroll(INBOXVIEW_SCROLL_EVENT, userId)} scrollEventThrottle={DEFAULT_SCROLL_EVENT_THROTTLE} /> {isInboxTextInputOpen && categoryId === inboxActivatedCategoryId ? ( - - - - - - + ) : null} @@ -127,21 +86,19 @@ const styles = StyleSheet.create({ container: { flex: 1, }, - keyboardInputContainer: { - backgroundColor: 'white', - borderTopWidth: 0, - }, - mainContainer: { + keyboardAvoidingView: { flex: 1, backgroundColor: 'white', }, flatListContainer: { flex: 1, }, - keyboardAvoidingView: { - width: '100%', - position: 'absolute', - bottom: 0, + keyboardInputContainer: { + backgroundColor: 'white', + borderTopWidth: 0, + }, + mainContainer: { + flex: 1, backgroundColor: 'white', }, inputWrapper: { diff --git a/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx b/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx index ba0e1c3..031fa4d 100644 --- a/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx +++ b/components/inboxView/inboxTodos/inboxTodo/InboxTodo.jsx @@ -1,5 +1,3 @@ -import SubTodoGenerateModal from '@/components/SubTodoGenerateModal'; -import GeneratedSubTodoList from '@/components/todayView/dailyTodos/dailyTodo/generatedSubTodoList/GeneratedSubTodoList'; import '@/locales/index'; import React from 'react'; import { View } from 'react-native'; @@ -10,7 +8,7 @@ import TodoListItem from './todoListItem/TodoListItem'; import useInboxTodo from './useInboxTodo'; import useTextInputStore from '@/contexts/textInputStore'; -const InboxTodo = ({ item, drag, isActive }) => { +const InboxTodo = ({ item }) => { const { isEditing, setIsEditing, @@ -18,19 +16,12 @@ const InboxTodo = ({ item, drag, isActive }) => { setIsSubTodoToggleActivated, subTodoInputActivated, setSubTodoInputActivated, - generatedSubTodos, - setGeneratedSubTodos, } = useInboxTodo(); - const { setInboxTextInputOpen } = useTextInputStore( + const setInboxTextInputOpen = useTextInputStore( state => state.setInboxTextInputOpen, ); - const { - isVisible: isSubTodoGenerateModalVisible, - setIsVisible: setIsSubTodoGenerateModalVisible, - } = useModal(); - const { setIsVisible: setIsTodoModalVisible } = useModal(); const handleEdit = () => { @@ -43,11 +34,8 @@ const InboxTodo = ({ item, drag, isActive }) => { 0} onEdit={handleEdit} @@ -65,16 +53,6 @@ const InboxTodo = ({ item, drag, isActive }) => { setSubTodoInputActivated={setSubTodoInputActivated} /> ) : null} - - ); }; diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx index d325777..197439c 100644 --- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx +++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/TodoListItem.tsx @@ -1,5 +1,4 @@ -import EditableTextField from '@/components/common/molecules/EditableTextField'; -import { heightPercentage, widthPercentage } from '@/utils/responsiveSize'; +import EditableTextField from '@/components/common/molecules/InboxEditableTextField'; import { Icon, ListItem } from '@ui-kitten/components'; import React from 'react'; import { Pressable, StyleSheet, Text } from 'react-native'; @@ -7,14 +6,11 @@ import { RenderItemParams } from 'react-native-draggable-flatlist'; import { Todo } from '../../../../../types/todo'; import TodoMoreMenu from './todoMoreMenu/TodoMoreMenu'; import useTodoListItem from './useTodoListItem'; +import { scale, verticalScale } from 'react-native-size-matters'; interface TodoListItemProps extends RenderItemParams { isEditing: boolean; setIsEditing: React.Dispatch>; - setIsSubTodoGenerateModalVisible: React.Dispatch< - React.SetStateAction - >; - setIsTodoModalVisible: React.Dispatch>; onEdit: () => void; // eslint-disable-next-line @typescript-eslint/no-explicit-any bottomSheetRef: React.MutableRefObject; @@ -30,7 +26,6 @@ const TodoListItem: React.FC = ({ isActive, isEditing, setIsEditing, - setIsSubTodoGenerateModalVisible, onEdit, setSubTodoInputActivated, }) => { @@ -44,7 +39,6 @@ const TodoListItem: React.FC = ({ item, isEditing, setIsEditing, - setIsSubTodoGenerateModalVisible, }); const accessoryLeft = (props?) => { @@ -63,7 +57,6 @@ const TodoListItem: React.FC = ({ const accessoryRight = () => { return ( = ({ onLongPress={drag} disabled={isActive} title={title} + style={{ paddingHorizontal: 0 }} /> ); @@ -107,8 +101,7 @@ const styles = StyleSheet.create({ container: { flexDirection: 'row', justifyContent: 'space-between', - paddingBottom: heightPercentage(8), - paddingTop: heightPercentage(8), + paddingVertical: verticalScale(10), marginRight: 0, paddingRight: 0, }, @@ -119,13 +112,13 @@ const styles = StyleSheet.create({ flex: 1, }, checkIcon: { - width: widthPercentage(20), - height: heightPercentage(20), + width: scale(20), + height: verticalScale(20), }, settingIcon: { - width: widthPercentage(20), - height: heightPercentage(20), - marginRight: widthPercentage(4), + width: scale(20), + height: verticalScale(20), + marginRight: scale(4), }, touchableCheck: { paddingTop: 0, @@ -133,7 +126,7 @@ const styles = StyleSheet.create({ }, text: { paddingTop: 0, - paddingLeft: widthPercentage(4), + paddingLeft: scale(4), }, input: { paddingTop: 0, diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx index 4246f11..7321beb 100644 --- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx +++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx @@ -46,25 +46,19 @@ const MenuIconButton = onPress => ( ); -const TodoMoreMenu = ({ - setIsSubTodoGenerateModalVisible, - onEdit, - item, - setSubTodoInputActivated, -}) => { +const TodoMoreMenu = ({ onEdit, item, setSubTodoInputActivated }) => { const { handleEditPress, handleDeletePress, - handleGenerateSubTodoPress, handleCreateSubTodoPress: handleAddSubTodoPress, // eslint-disable-next-line @typescript-eslint/no-unused-vars handlePutTodoToInboxPress, } = useTodoMoreMenu({ - setIsSubTodoGenerateModalVisible, onEdit, item, setSubTodoInputActivated, }); + const [visible, setVisible] = useState(false); const { t } = useTranslation(); const { openBottomSheet } = useContext(CalendarBottomSheetContext); @@ -119,12 +113,12 @@ const TodoMoreMenu = ({ disabled={item.children.length > 0} accessoryLeft={GenerateSubtodoIcon} title= 0} + disabled={true} titleText={t('components.todoMoreMenu.createSubTodoWithAi')} /> onPress={() => { setSelectedTodo(item); - handleGenerateSubTodoPress(); + setVisible(false); }} style={styles.middleMenuItem} /> @@ -136,6 +130,7 @@ const TodoMoreMenu = ({ /> onPress={() => { handleAddSubTodoPress(); + setVisible(false); }} style={styles.middleMenuItem} /> diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx index 5178b46..614952b 100644 --- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx +++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/useTodoMoreMenu.tsx @@ -18,7 +18,6 @@ import { useContext } from 'react'; const useTodoMoreMenu = ({ item, onEdit = () => {}, - setIsSubTodoGenerateModalVisible, setSubTodoInputActivated, }) => { const { selectedCategory } = useContext(CategoryContext); @@ -70,10 +69,6 @@ const useTodoMoreMenu = ({ }); }; - const handleGenerateSubTodoPress = () => { - setIsSubTodoGenerateModalVisible(true); - }; - const handleCreateSubTodoPress = () => { handleLogEvent(TODOMODAL_CREATESUBTODO_CLICK_EVENT, { time: new Date().toISOString(), @@ -96,7 +91,6 @@ const useTodoMoreMenu = ({ handleEditPress, handleDeletePress, handleChaneDatePress, - handleGenerateSubTodoPress, handleCreateSubTodoPress, handlePutTodoToInboxPress, }; diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts b/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts index 50f4044..d831e8f 100644 --- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts +++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/useTodoListItem.ts @@ -10,16 +10,11 @@ import { handleLogEvent, } from '../../../../../utils/logEvent'; -const useTodoListItem = ({ - item, - isEditing, - setIsEditing, - setIsSubTodoGenerateModalVisible, -}) => { +const useTodoListItem = ({ item, isEditing, setIsEditing }) => { const { mutate: updateTodo } = useTodoUpdateMutation(); const { userId } = useContext(LoginContext); const theme = useTheme(); - const { setInboxTextInputOpen } = useTextInputStore( + const setInboxTextInputOpen = useTextInputStore( state => state.setInboxTextInputOpen, ); @@ -64,10 +59,6 @@ const useTodoListItem = ({ setInboxTextInputOpen(false); }; - const handleGenerateIconPress = () => { - setIsSubTodoGenerateModalVisible(true); - }; - const handleSettingIconPress = () => { handleLogEvent(DAILYTODO_MEATBALLMENU_CLICK_EVENT, { time: new Date().toISOString(), @@ -83,7 +74,6 @@ const useTodoListItem = ({ setIsEditing, handleTodoListItemPress, handleTodoListItemSubmitEditing, - handleGenerateIconPress, handleSettingIconPress, }; }; diff --git a/components/todayView/dailyTodos/DailyTodos.tsx b/components/todayView/dailyTodos/DailyTodos.tsx index 48a63d4..8ee1069 100644 --- a/components/todayView/dailyTodos/DailyTodos.tsx +++ b/components/todayView/dailyTodos/DailyTodos.tsx @@ -16,6 +16,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import DailyTodo from './dailyTodo/DailyTodo'; import TodoList from '../TodoList'; import TodoInput from '../TextInput'; +import useListRefStore from '@/contexts/listRefStore'; const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { const { userId } = useContext(LoginContext); @@ -36,8 +37,13 @@ const DailyTodos = ({ todosData, categoryId, onDragStart, onDragEnd }) => { const handleDragEnd = useHandleDrag(); + const { setTodoListRef } = useListRefStore(); const todoListRef = React.useRef(null); + useEffect(() => { + setTodoListRef(todoListRef); + }, [setTodoListRef]); + const handleInputSubmit = useCallback(() => { handleLogEvent(TODAYVIEW_TEXTINPUT_SUBMIT_EVENT, { time: new Date().toISOString(), diff --git a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx index 262123f..8f8facd 100644 --- a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx +++ b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx @@ -26,7 +26,7 @@ const CalendarBottomSheet = ({ isTodo, item }) => { ); const { mutate: updateTodoDate } = useTodoUpdateMutation(); const { mutate: updateSubTodoDate } = useSubTodoUpdateMutation(); - const snapPoints = useMemo(() => ['75%'], []); + const snapPoints = useMemo(() => ['90%'], []); const { i18n } = useTranslation(); const handleDateUpdate = date => { diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx b/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx index b5b5cb8..1c16016 100644 --- a/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx +++ b/components/todayView/dailyTodos/dailyTodo/subTodoList/DailySubTodo.jsx @@ -18,7 +18,7 @@ const DailySubTodo = ({ item }) => { const [isEditing, setIsEditing] = useState(false); const { mutate: updateSubTodo } = useSubTodoUpdateMutation(); const { userId } = useContext(LoginContext); - const { setTodayTextInputOpen } = useTextInputStore( + const setTodayTextInputOpen = useTextInputStore( state => state.setTodayTextInputOpen, ); @@ -36,15 +36,6 @@ const DailySubTodo = ({ item }) => { updateSubTodo(updatedData); }; - const handleSubTodoUpdate = content => { - const updatedData = { - subtodoId: item.id, - content: content, - }; - updateSubTodo(updatedData); - setIsEditing(false); - }; - const handleCheckIconPress = () => { handleLogEvent(DAILYTODO_SUBTODOCOMPLETE_CLICK_EVENT, { time: new Date().toISOString(), @@ -77,10 +68,11 @@ const DailySubTodo = ({ item }) => { <> ); diff --git a/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx b/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx index 451ccae..2fe560f 100644 --- a/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx +++ b/components/todayView/dailyTodos/dailyTodo/subTodoList/SubTodoList.tsx @@ -1,6 +1,6 @@ import colors from '@/theme/theme.json'; import { Icon, List } from '@ui-kitten/components'; -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { StyleSheet, TextInput, View } from 'react-native'; import { Todo } from '../../../../../types/todo'; @@ -27,6 +27,13 @@ const SubTodoList: React.FC = ({ const { handleSubtodoSubmit, subTodoInput, setSubtodoInput } = useSubTodoList( { item, setSubTodoInputActivated }, ); + const inputRef = useRef(null); + + useEffect(() => { + if (subTodoInputActivated) { + inputRef.current?.focus(); + } + }, [subTodoInputActivated]); return ( = ({ fill={colors.Gray02} /> { diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx index 533712b..c6ff9bc 100644 --- a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx +++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx @@ -71,7 +71,6 @@ const TodoMoreMenu = ({ const setSelectedTodo = useTodoStore(state => state.setSelectedTodo); const { openBottomSheet: openAIBottomSheet } = useContext(AIBottomSheetContext); - useContext(AIBottomSheetContext); const toggleMenu = useCallback(() => { setVisible(true); diff --git a/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx b/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx index 685d9a3..44514e9 100644 --- a/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx +++ b/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx @@ -20,6 +20,7 @@ import axios from 'axios'; import { API_PATH } from '@/utils/config'; import * as Sentry from '@sentry/react-native'; import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider'; +import useListRefStore from '@/contexts/listRefStore'; const SubTodoGenerateBottomSheet = ({ item }) => { const { accessToken } = useContext(LoginContext); @@ -29,6 +30,7 @@ const SubTodoGenerateBottomSheet = ({ item }) => { const theme = useTheme(); const { t } = useTranslation(); const { mutate: addSubTodo } = useSubTodoAddMutation(); + const { todoListRef } = useListRefStore(); const snapPoints = useMemo(() => ['75%'], []); @@ -74,7 +76,21 @@ const SubTodoGenerateBottomSheet = ({ item }) => { date: generatedSubTodos[index].date, todoId: generatedSubTodos[index].todo, })); - addSubTodo({ todoData: newSubTodos }); + + addSubTodo( + { todoData: newSubTodos }, + { + onSuccess: () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + if (todoListRef?.current) { + todoListRef.current?.scrollToEnd({ animated: true }); + } + }); + }); + }, + }, + ); handleClose(); }; diff --git a/contexts/LoginContext.js b/contexts/LoginContext.js index b5929bd..31d5d82 100755 --- a/contexts/LoginContext.js +++ b/contexts/LoginContext.js @@ -10,6 +10,7 @@ const LoginProvider = ({ children }) => { const [accessToken, setAccessToken] = useState(); const [refreshToken, setRefreshToken] = useState(); const [loginType, setLoginType] = useState(); + const [userName, setUserName] = useState(); return ( { setRefreshToken, loginType, setLoginType, + userName, + setUserName, }} > {children} diff --git a/contexts/listRefStore.ts b/contexts/listRefStore.ts new file mode 100644 index 0000000..66d0e34 --- /dev/null +++ b/contexts/listRefStore.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { create } from 'zustand'; + +type ListRefStore = { + categoryListRef: React.RefObject | null; + setCategoryListRef: (ref: React.RefObject) => void; + todoListRef: React.RefObject | null; + setTodoListRef: (ref: React.RefObject) => void; +}; + +const useListRefStore = create(set => ({ + categoryListRef: null, + setCategoryListRef: ref => set({ categoryListRef: ref }), + todoListRef: null, + setTodoListRef: ref => set({ todoListRef: ref }), +})); + +export default useListRefStore; diff --git a/hooks/auth/useLogin.js b/hooks/auth/useLogin.js index 18c817d..3f5de38 100644 --- a/hooks/auth/useLogin.js +++ b/hooks/auth/useLogin.js @@ -17,7 +17,8 @@ const useLogin = () => { const storage = useStorage(); const { deviceToken } = useDeviceToken(); const router = useRouter(); - const { setIsLoggedIn, setUserId, setAccessToken } = useContext(LoginContext); + const { setIsLoggedIn, setUserId, setAccessToken, setUserName } = + useContext(LoginContext); const { mutate: addCategory } = useCategoryAddMutation(); const { t } = useTranslation(); @@ -42,6 +43,7 @@ const useLogin = () => { const user = await Api.getUserInfo(); await setAsyncStorageLoginInfo(jwtTokenData, user); setUserId(jwtTokenData.userId); + setUserName(jwtTokenData.email); setIsLoggedIn(true); if (jwtTokenData.isNew) { handleAddCategory({ categoryName: t('views.categoryAddView.init') }); From b895ba627c1252580435e9bc6b089e770dcffc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sat, 23 Nov 2024 09:21:35 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[SZ-553]fix:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.config.js | 4 ++-- app/categoryView/categoryListView.jsx | 1 - hooks/auth/useGoogleAuth.js | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app.config.js b/app.config.js index d9eca1a..0596a1f 100644 --- a/app.config.js +++ b/app.config.js @@ -76,7 +76,7 @@ const expoConfig = { backgroundColor: '#ffffff', }, package: 'com.safezone.onestep', - versionCode: 7, + versionCode: 5, softwareKeyboardLayoutMode: 'pan', }, web: { @@ -125,7 +125,7 @@ const expoConfig = { ], './plugins/withStaticFrameworks', ], - runtimeVersion: '1.0.1', + runtimeVersion: '1.0.0', updates: { url: 'https://u.expo.dev/63f6bbd9-1594-44b3-b161-0e0051413ef0', }, diff --git a/app/categoryView/categoryListView.jsx b/app/categoryView/categoryListView.jsx index 0bca343..d610f79 100644 --- a/app/categoryView/categoryListView.jsx +++ b/app/categoryView/categoryListView.jsx @@ -44,7 +44,6 @@ const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', - paddingHorizontal: scale(20), backgroundColor: 'white', }, list: { diff --git a/hooks/auth/useGoogleAuth.js b/hooks/auth/useGoogleAuth.js index 0ccd86d..6ae5f4a 100644 --- a/hooks/auth/useGoogleAuth.js +++ b/hooks/auth/useGoogleAuth.js @@ -1,5 +1,4 @@ import { api as Api } from '@/utils/api'; -import messaging from '@react-native-firebase/messaging'; import * as Google from 'expo-auth-session/providers/google'; import { useEffect, useState } from 'react'; import { Platform } from 'react-native'; @@ -45,9 +44,6 @@ const useGoogleAuth = () => { useEffect(() => { getClientId(); handleLocalToken(); - (async () => { - await messaging().requestPermission(); - })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From 575374e96d8489b79e58a2f4ab9584828ba82f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Mon, 20 Jan 2025 04:09:04 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[SZ-553]fix:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 중복 컴포넌트로 삭제. --- components/InboxSubTodo.jsx | 93 ------------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 components/InboxSubTodo.jsx diff --git a/components/InboxSubTodo.jsx b/components/InboxSubTodo.jsx deleted file mode 100644 index 2a1033d..0000000 --- a/components/InboxSubTodo.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import { TextInputContext } from '@/contexts/textInputContext'; -import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations'; -import { Icon, Input, ListItem } from '@ui-kitten/components'; -import React, { useContext, useState } from 'react'; -import { Text, TouchableOpacity } from 'react-native'; -import { useTheme } from 'react-native-elements'; -import TodoModal from './TodoModal'; - -const InboxSubTodo = ({ item }) => { - const [isEditing, setIsEditing] = useState(false); - const [content, setContent] = useState(item.content); - const theme = useTheme(); - const [modalVisible, setModalVisible] = useState(false); - const { mutate: updateInboxSubTodo } = useSubTodoUpdateMutation(); - const { setInboxTextInputOpen } = useContext(TextInputContext); - - const handleEdit = () => { - setIsEditing(true); - setInboxTextInputOpen(false); - setModalVisible(false); - }; - - const handleInboxSubTodoUpdate = () => { - const updatedData = { - todoId: item.id, - content: content, - }; - updateInboxSubTodo(updatedData); - }; - - const outlineIcon = props => { - return ( - setModalVisible(true)}> - - - ); - }; - - const settingIcon = props => { - return ( - setModalVisible(true)}> - - - ); - }; - - return ( - <> - setContent(value)} - onSubmitEditing={() => { - handleInboxSubTodoUpdate(); - setIsEditing(false); - setInboxTextInputOpen(true); - }} - autoFocus={true} - /> - ) : ( - {item.content} - ) - } - key={item.id} - accessoryLeft={props => outlineIcon(props)} - accessoryRight={props => settingIcon(props)} - onPress={() => setModalVisible(true)} - style={{ paddingLeft: 40 }} - /> - - - ); -}; - -export default InboxSubTodo;