From 194ef3848dd5e3bb7d746ac321d0ac99e02a5cf8 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 14:22:01 +0900
Subject: [PATCH 1/3] =?UTF-8?q?[SZ-539]fix:=20=ED=97=A4=EB=8D=94=EC=97=90?=
=?UTF-8?q?=20=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20=EA=B0=92=EA=B9=8C?=
=?UTF-8?q?=EC=A7=80=20=EB=93=A4=EC=96=B4=EA=B0=80=EB=8A=94=20=EA=B2=83=20?=
=?UTF-8?q?=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
utils/api.js | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/utils/api.js b/utils/api.js
index b431bb0..59d3525 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -113,8 +113,10 @@ class Api {
}
try {
+ const { method, data, ...restOptions } = options;
+
const headers = {
- ...options,
+ ...restOptions,
'Content-Type': 'application/json',
};
if (this.accessToken) {
@@ -122,9 +124,9 @@ class Api {
}
const response = await this.axiosInstance.request({
url,
- method: options.method,
+ method,
headers,
- data: options.data,
+ data,
});
return response.data;
} catch (e) {
From 6599c9be37b5672c1a94e4cbf22b02fa42a87c80 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 14:22:24 +0900
Subject: [PATCH 2/3] =?UTF-8?q?[SZ-539]fix:=20=EC=95=88=EC=93=B0=EB=8A=94?=
=?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
hooks/api/useApi.js | 199 --------------------------------------------
1 file changed, 199 deletions(-)
delete mode 100644 hooks/api/useApi.js
diff --git a/hooks/api/useApi.js b/hooks/api/useApi.js
deleted file mode 100644
index 0c9e3db..0000000
--- a/hooks/api/useApi.js
+++ /dev/null
@@ -1,199 +0,0 @@
-import { useState, useEffect } from 'react';
-import AsyncStorage from '@react-native-async-storage/async-storage';
-import axios from 'axios';
-import { router } from 'expo-router';
-import { API_PATH } from '@/utils/config';
-import * as Sentry from '@sentry/react-native';
-
-const TOKEN_INVALID_OR_EXPIRED_MESSAGE = 'Token is invalid or expired';
-const TOKEN_INVALID_TYPE_MESSAGE = 'Given token not valid for any token type';
-
-export function useApi() {
- const [accessToken, setAccessToken] = useState(null);
- useEffect(() => {
- AsyncStorage.getItem('accessToken').then(token => {
- setAccessToken(token);
- });
- }, []);
-
- const metadata = () => {
- return {
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${accessToken}`,
- },
- };
- };
-
- const handleRequest = async request => {
- try {
- const response = await request();
- return response.data;
- } catch (err) {
- if (
- (err.response?.status === 401 &&
- err.response?.data?.detail === TOKEN_INVALID_OR_EXPIRED_MESSAGE) ||
- err.response?.data?.detail === TOKEN_INVALID_TYPE_MESSAGE
- ) {
- try {
- const refreshToken = await AsyncStorage.getItem('refreshToken');
- const responseData = await axios.post(API_PATH.renew, {
- refresh: refreshToken,
- access: accessToken,
- });
- await AsyncStorage.setItem('accessToken', responseData.data.access);
- setAccessToken(responseData.data.access);
- const secondRequest = await request();
- return secondRequest.data;
- } catch (refreshError) {
- Sentry.captureException(refreshError);
-
- if (refreshError.response?.status === 401) {
- await AsyncStorage.multiRemove([
- 'accessToken',
- 'refreshToken',
- 'userId',
- ]);
- router.replace('');
- } else {
- throw refreshError;
- }
- }
- }
- Sentry.captureException(err);
- throw err;
- }
- };
-
- const fetchTodos = userId => {
- return handleRequest(() =>
- axios.get(`${API_PATH.todos}?user_id=${userId}`, metadata()),
- );
- };
-
- const addTodo = todoData => {
- return handleRequest(() =>
- axios.post(API_PATH.todos, todoData, metadata()),
- );
- };
-
- const deleteTodo = ({ todoId }) => {
- return handleRequest(() =>
- axios.request({
- url: API_PATH.todos,
- method: 'DELETE',
- ...metadata(),
- data: { todo_id: todoId },
- }),
- );
- };
-
- const updateTodo = ({ updateData }) => {
- return handleRequest(() =>
- axios.patch(API_PATH.todos, updateData, metadata()),
- );
- };
-
- const verifyToken = token => {
- return handleRequest(() => axios.post(API_PATH.verify, { token }));
- };
-
- const renewToken = refreshToken => {
- return handleRequest(() =>
- axios.post(API_PATH.renew, {
- refresh: refreshToken,
- access: accessToken,
- }),
- );
- };
-
- const googleLogin = tokenData => {
- return handleRequest(() => axios.post(API_PATH.login, tokenData));
- };
-
- const getUserInfo = () => {
- return handleRequest(() => axios.get(API_PATH.user, metadata()));
- };
-
- const getCategory = userId => {
- return handleRequest(() =>
- axios.get(`${API_PATH.categories}?user_id=${userId}`, metadata()),
- );
- };
-
- const addCategory = categoryData => {
- return handleRequest(() =>
- axios.post(API_PATH.categories, categoryData, metadata()),
- );
- };
-
- const updateCategory = ({ updatedData }) => {
- return handleRequest(() =>
- axios.patch(API_PATH.categories, updatedData, metadata()),
- );
- };
-
- const deleteCategory = ({ categoryId }) => {
- return handleRequest(() =>
- axios.request({
- url: API_PATH.categories,
- method: 'DELETE',
- ...metadata(),
- data: { category_id: categoryId },
- }),
- );
- };
-
- const addSubTodo = subTodoData => {
- return handleRequest(() =>
- axios.post(API_PATH.subTodos, subTodoData, metadata()),
- );
- };
-
- const updateSubTodo = ({ updatedData }) => {
- return handleRequest(() =>
- axios.patch(API_PATH.subTodos, updatedData, metadata()),
- );
- };
-
- const deleteSubTodo = ({ subTodoId }) => {
- return handleRequest(() =>
- axios.request({
- url: API_PATH.subTodos,
- method: 'DELETE',
- ...metadata(),
- data: { subtodoId: subTodoId },
- }),
- );
- };
-
- const getInboxTodo = userId => {
- return handleRequest(() =>
- axios.get(`${API_PATH.inbox}?user_id=${userId}`, metadata()),
- );
- };
-
- const getAndroidClientId = () => {
- return handleRequest(() => axios.get(API_PATH.android));
- };
-
- return {
- fetchTodos,
- addTodo,
- deleteTodo,
- updateTodo,
- verifyToken,
- renewToken,
- googleLogin,
- getUserInfo,
- getCategory,
- addCategory,
- updateCategory,
- deleteCategory,
- addSubTodo,
- updateSubTodo,
- deleteSubTodo,
- getInboxTodo,
- getAndroidClientId,
- };
-}
From aceb5c7ffa921d250d979de1918a620d66c45f1f 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 14:23:20 +0900
Subject: [PATCH 3/3] =?UTF-8?q?[SZ-539]feat:=20AI=EC=83=9D=EC=84=B1=20bott?=
=?UTF-8?q?omsheet=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/(tabs)/index.jsx | 59 ++--
.../todoMoreMenu/TodoMoreMenu.tsx | 3 +-
.../CalendarBottomSheet.tsx | 35 +-
.../todoMoreMenu/TodoMoreMenu.tsx | 10 +-
.../SubTodoGenerateBottomSheet.tsx | 324 ++++++++++++++++++
contexts/AIBottomSheetProvider.js | 29 ++
contexts/CalendarBottomSheetProvider.js | 9 +-
7 files changed, 435 insertions(+), 34 deletions(-)
create mode 100644 components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx
create mode 100644 contexts/AIBottomSheetProvider.js
diff --git a/app/(tabs)/index.jsx b/app/(tabs)/index.jsx
index 822307e..5784ead 100644
--- a/app/(tabs)/index.jsx
+++ b/app/(tabs)/index.jsx
@@ -2,7 +2,9 @@ import CategoryMainItem from '@/components/categoryView/CategoryMainItem';
import LoadingSpinner from '@/components/common/molecules/LoadingSpinner';
import CalendarBottomSheet from '@/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet';
import DailyTodos from '@/components/todayView/dailyTodos/DailyTodos';
+import SubTodoGenerateBottomSheet from '@/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet';
import WeeklyCalendar from '@/components/WeeklyCalendar';
+import AIBottomSheetProvider from '@/contexts/AIBottomSheetProvider';
import CalendarBottomSheetProvider from '@/contexts/CalendarBottomSheetProvider';
import CategoryProvider from '@/contexts/CategoryContext';
import DateProvider from '@/contexts/DateContext';
@@ -48,37 +50,40 @@ const TodayView = () => {
};
const { data: categoriesData } = useCategoriesQuery(userId);
const { data: todosData } = useTodosQuery(userId);
- const { selectedTodo } = useTodoStore();
+ const selectedTodo = useTodoStore(state => state.selectedTodo);
return (
-
-
-
-
-
-
- }
- >
- item.id}
- contentContainerStyle={styles.flatList}
- />
-
-
-
-
+
+
+
+
+
+
+
+ }
+ >
+ item.id}
+ contentContainerStyle={styles.flatList}
+ />
+
+
+
+
+
+
diff --git a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
index da25e6c..4231d4b 100644
--- a/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
+++ b/components/inboxView/inboxTodos/inboxTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
@@ -68,7 +68,7 @@ const TodoMoreMenu = ({
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const { openBottomSheet } = useContext(CalendarBottomSheetContext);
- const { setSelectedTodo } = useTodoStore();
+ const setSelectedTodo = useTodoStore(state => state.setSelectedTodo);
const toggleMenu = useCallback(() => {
setVisible(true);
@@ -121,6 +121,7 @@ const TodoMoreMenu = ({
titleText={t('components.todoMoreMenu.createSubTodoWithAi')}
/>
onPress={() => {
+ setSelectedTodo(item);
handleGenerateSubTodoPress();
}}
style={styles.middleMenuItem}
diff --git a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx
index dd1c6ec..7c1638a 100644
--- a/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx
+++ b/components/todayView/dailyTodos/calendarBottomSheet/CalendarBottomSheet.tsx
@@ -1,4 +1,4 @@
-import { View, Text } from 'react-native';
+import { View, Text, Pressable, StyleSheet } from 'react-native';
import React, { useCallback, useContext, useMemo } from 'react';
import BottomSheet, {
BottomSheetBackdrop,
@@ -11,6 +11,7 @@ import {
Calendar,
I18nConfig,
NativeDateService,
+ Icon,
} from '@ui-kitten/components';
import { convertGmtToKst } from '@/utils/convertTimezone';
import { useTodoUpdateMutation } from '@/hooks/api/useTodoMutations';
@@ -18,11 +19,14 @@ import { useSubTodoUpdateMutation } from '@/hooks/api/useSubTodoMutations';
import { t } from 'i18next';
import { useTranslation } from 'react-i18next';
import { scale, verticalScale } from 'react-native-size-matters';
+import fontStyles from '@/theme/fontStyles';
const CalendarBottomSheet = ({ isTodo, item }) => {
const { selectedDate } = useContext(DateContext);
const [calendarDate, setCalendarDate] = React.useState(selectedDate.toDate());
- const { bottomSheetRef } = useContext(CalendarBottomSheetContext);
+ const { bottomSheetRef, closeBottomSheet } = useContext(
+ CalendarBottomSheetContext,
+ );
const { mutate: updateTodoDate } = useTodoUpdateMutation();
const { mutate: updateSubTodoDate } = useSubTodoUpdateMutation();
const snapPoints = useMemo(() => ['75%'], []);
@@ -129,6 +133,14 @@ const CalendarBottomSheet = ({ isTodo, item }) => {
backgroundColor: 'white',
}}
>
+
+
+ {t('components.todoMoreMenu.changeDate')}
+
+
+
+
+
{
};
export default CalendarBottomSheet;
+
+const styles = StyleSheet.create({
+ topContainer: {
+ backgroundColor: 'white',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginVertical: verticalScale(15),
+ paddingHorizontal: scale(16),
+ },
+
+ icon: {
+ width: scale(20),
+ height: verticalScale(20),
+ },
+ titleText: {
+ fontSize: fontStyles.Subtitle.S1.B_130.fontSize,
+ fontFamily: fontStyles.Subtitle.S1.B_130.fontFamily,
+ },
+});
diff --git a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
index 0b4b19b..49fc2ce 100644
--- a/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
+++ b/components/todayView/dailyTodos/dailyTodo/todoListItem/todoMoreMenu/TodoMoreMenu.tsx
@@ -21,6 +21,7 @@ import theme from '@/theme/theme.json';
import useTodoMoreMenu from './useTodoMoreMenu';
import { CalendarBottomSheetContext } from '@/contexts/CalendarBottomSheetProvider';
import useTodoStore from '@/contexts/TodoStore';
+import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider';
const TodoText = ({ titleText, disabled = false }) => {
return (
@@ -56,7 +57,6 @@ const TodoMoreMenu = ({
const {
handleEditPress,
handleDeletePress,
- handleGenerateSubTodoPress,
handleCreateSubTodoPress: handleAddSubTodoPress,
handlePutTodoToInboxPress,
} = useTodoMoreMenu({
@@ -68,7 +68,10 @@ const TodoMoreMenu = ({
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const { openBottomSheet } = useContext(CalendarBottomSheetContext);
- const { setSelectedTodo } = useTodoStore();
+ const setSelectedTodo = useTodoStore(state => state.setSelectedTodo);
+ const { openBottomSheet: openAIBottomSheet } =
+ useContext(AIBottomSheetContext);
+ useContext(AIBottomSheetContext);
const toggleMenu = useCallback(() => {
setVisible(true);
@@ -123,7 +126,8 @@ const TodoMoreMenu = ({
titleText={t('components.todoMoreMenu.createSubTodoWithAi')}
/>
onPress={() => {
- handleGenerateSubTodoPress();
+ setSelectedTodo(item);
+ openAIBottomSheet();
setVisible(false);
}}
style={styles.middleMenuItem}
diff --git a/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx b/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx
new file mode 100644
index 0000000..723cd94
--- /dev/null
+++ b/components/todayView/dailyTodos/subtodoGenerateBottomSheet/SubTodoGenerateBottomSheet.tsx
@@ -0,0 +1,324 @@
+import React, {
+ useCallback,
+ useContext,
+ useMemo,
+ useState,
+ useEffect,
+} from 'react';
+import { View, StyleSheet, Pressable } from 'react-native';
+import BottomSheet, {
+ BottomSheetBackdrop,
+ BottomSheetView,
+} from '@gorhom/bottom-sheet';
+import { LoginContext } from '@/contexts/LoginContext';
+import {
+ Button,
+ Text,
+ List,
+ ListItem,
+ useTheme,
+ Icon,
+} from '@ui-kitten/components';
+import { useTranslation } from 'react-i18next';
+import { scale, verticalScale, moderateScale } from 'react-native-size-matters';
+import { IconButton } from '@/components/common/molecules/IconButton';
+import LoadingSpinner from '@/components/common/molecules/LoadingSpinner';
+import fontStyles from '@/theme/fontStyles';
+import { useSubTodoAddMutation } from '@/hooks/api/useSubTodoMutations';
+import axios from 'axios';
+import { API_PATH } from '@/utils/config';
+import * as Sentry from '@sentry/react-native';
+import { AIBottomSheetContext } from '@/contexts/AIBottomSheetProvider';
+
+const SubTodoGenerateBottomSheet = ({ item }) => {
+ const { accessToken } = useContext(LoginContext);
+ const [isLoading, setIsLoading] = useState(false);
+ const [generatedSubTodos, setGeneratedSubTodos] = useState([]);
+ const [selectedIndexes, setSelectedIndexes] = useState([]);
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { mutate: addSubTodo } = useSubTodoAddMutation();
+
+ const snapPoints = useMemo(() => ['75%'], []);
+
+ const { bottomSheetRef } = useContext(AIBottomSheetContext);
+
+ const renderBackdrop = useCallback(
+ props => (
+
+ ),
+ [],
+ );
+
+ useEffect(() => {
+ if (generatedSubTodos.length > 0) {
+ setSelectedIndexes(generatedSubTodos.map((_, index) => index));
+ bottomSheetRef.current?.snapToIndex(0);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [generatedSubTodos]);
+
+ const handleClose = () => {
+ bottomSheetRef.current?.close();
+ setGeneratedSubTodos([]);
+ setSelectedIndexes([]);
+ setIsLoading(false);
+ };
+
+ const handleGenerateSubTodos = () => {
+ setIsLoading(true);
+ axios
+ .get(`${API_PATH.recommend}?todo_id=${item.id}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ })
+ .then(response => {
+ setIsLoading(false);
+ setGeneratedSubTodos(response.data.children);
+ })
+ .catch(error => {
+ setIsLoading(false);
+ Sentry.captureException(error);
+ });
+ };
+
+ const handleApplySelection = () => {
+ const newSubTodos = selectedIndexes.map(index => ({
+ content: generatedSubTodos[index].content,
+ date: generatedSubTodos[index].date,
+ todoId: generatedSubTodos[index].todo,
+ }));
+ addSubTodo({ todoData: newSubTodos });
+ handleClose();
+ };
+
+ const handleToggleSelection = index => {
+ setSelectedIndexes(prev =>
+ prev.includes(index) ? prev.filter(i => i !== index) : [...prev, index],
+ );
+ };
+
+ const renderSubTodoItemTitle = (evaProps, subTodoItem) => (
+
+ {subTodoItem.content}
+
+ );
+
+ const renderSubTodoItemAccessoryRight = (props, index) => (
+ handleToggleSelection(index)}
+ fill={
+ selectedIndexes.includes(index)
+ ? theme['color-primary-500']
+ : theme['text-basic-color']
+ }
+ iconName="done-all-outline"
+ {...props}
+ />
+ );
+
+ const renderSubTodoItem = ({ subTodoItem, index }) => (
+ renderSubTodoItemAccessoryRight(props, index)}
+ title={props => renderSubTodoItemTitle(props, subTodoItem)}
+ />
+ );
+
+ const renderInitialView = () => (
+
+
+
+ {t('components.todoMoreMenu.createSubTodoWithAi')}
+
+
+
+
+
+
+
+
+ {item?.content}
+
+
+ {t('components.subTodoGenerateModal.askCreateSubTodo')}
+
+
+
+
+
+
+
+ );
+
+ const renderGeneratedList = () => (
+
+
+
+ {t('components.todoMoreMenu.createSubTodoWithAi')}
+
+
+
+
+
+
+
+ renderSubTodoItem({ subTodoItem: generatedTodoItem, index })
+ }
+ style={styles.list}
+ />
+
+
+
+ );
+
+ const renderContent = () => {
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return generatedSubTodos.length === 0
+ ? renderInitialView()
+ : renderGeneratedList();
+ };
+
+ return (
+
+
+ {renderContent()}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ contentContainer: {
+ flex: 1,
+ padding: scale(20),
+ borderTopLeftRadius: scale(20),
+ borderTopRightRadius: scale(20),
+ },
+ initialContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ textContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 8,
+ },
+ titleText: {
+ height: verticalScale(23),
+ textAlign: 'center',
+ },
+ descriptionText: {
+ height: verticalScale(23),
+ textAlign: 'center',
+ },
+ buttonContainer: {
+ height: verticalScale(52),
+ width: '100%',
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ gap: 12,
+ },
+ button: {
+ flex: 1,
+ height: verticalScale(44),
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: scale(12),
+ },
+ loadingContainer: {
+ height: verticalScale(165),
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ listContainer: {
+ flex: 1,
+ height: '100%',
+ },
+ listWrapper: {
+ flex: 1,
+ flexDirection: 'column',
+ },
+ list: {
+ flex: 1,
+ backgroundColor: 'white',
+ },
+ listItem: {},
+ titleStyle: {
+ fontSize: moderateScale(13),
+ },
+ applyButton: {
+ marginTop: 16,
+ height: verticalScale(44),
+ borderRadius: scale(12),
+ },
+ topContainer: {
+ height: verticalScale(50),
+ width: '100%',
+ backgroundColor: 'white',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ icon: {
+ width: scale(20),
+ height: verticalScale(20),
+ },
+ bottomSheetTitleText: {
+ fontSize: fontStyles.Subtitle.S1.B_130.fontSize,
+ fontFamily: fontStyles.Subtitle.S1.B_130.fontFamily,
+ },
+ middleContainer: {
+ flex: 1,
+ display: 'flex',
+ justifyContent: 'space-between',
+ },
+});
+
+export default SubTodoGenerateBottomSheet;
diff --git a/contexts/AIBottomSheetProvider.js b/contexts/AIBottomSheetProvider.js
new file mode 100644
index 0000000..83a3275
--- /dev/null
+++ b/contexts/AIBottomSheetProvider.js
@@ -0,0 +1,29 @@
+import { createContext, useRef } from 'react';
+
+export const AIBottomSheetContext = createContext(null);
+
+const AIBottomSheetProvider = ({ children }) => {
+ const bottomSheetRef = useRef(null);
+
+ const openBottomSheet = () => {
+ if (bottomSheetRef.current) {
+ bottomSheetRef.current.expand();
+ }
+ };
+
+ const closeBottomSheet = () => {
+ if (bottomSheetRef.current) {
+ bottomSheetRef.current.close();
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default AIBottomSheetProvider;
diff --git a/contexts/CalendarBottomSheetProvider.js b/contexts/CalendarBottomSheetProvider.js
index 33a725b..09aeb5f 100644
--- a/contexts/CalendarBottomSheetProvider.js
+++ b/contexts/CalendarBottomSheetProvider.js
@@ -10,9 +10,16 @@ const CalendarBottomSheetProvider = ({ children }) => {
bottomSheetRef.current.expand();
}
};
+
+ const closeBottomSheet = () => {
+ if (bottomSheetRef.current) {
+ bottomSheetRef.current.close();
+ }
+ };
+
return (
{children}