From f16f0aac35fb4b2062672254059bd3afcad6f6e9 Mon Sep 17 00:00:00 2001 From: a06246 Date: Mon, 15 Dec 2025 15:13:47 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=9A=B4=EB=8F=99=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20ui=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ExerciseSetItem.tsx | 273 ++++--- src/components/modals/ExerciseModal.tsx | 826 +++++++++++---------- src/screens/analysis/CalendarScreen.tsx | 189 ++++- src/screens/auth/KakaoOnboardingScreen.tsx | 20 +- src/screens/auth/SignupScreen.tsx | 20 +- src/screens/chatbot/ChatbotScreen.tsx | 711 +++++++++--------- src/screens/diet/DietScreen.tsx | 39 + src/screens/exercise/ExerciseScreen.tsx | 23 + src/screens/main/HomeScreen.tsx | 34 +- src/utils/eventBus.ts | 9 + 10 files changed, 1267 insertions(+), 877 deletions(-) diff --git a/src/components/ExerciseSetItem.tsx b/src/components/ExerciseSetItem.tsx index e3bfec4..1ec290f 100644 --- a/src/components/ExerciseSetItem.tsx +++ b/src/components/ExerciseSetItem.tsx @@ -18,7 +18,6 @@ interface ExerciseSetItemProps { onPressRemove: () => void; onWeightChange?: (weight: number) => void; onRepsChange?: (reps: number) => void; - onOrderChange?: (order: number) => void; } const ExerciseSetItem: React.FC = ({ @@ -31,7 +30,6 @@ const ExerciseSetItem: React.FC = ({ onPressRemove, onWeightChange, onRepsChange, - onOrderChange, }) => { const isHighlighted = isActive || isCompleted; @@ -49,36 +47,12 @@ const ExerciseSetItem: React.FC = ({ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > - + - {/* 세트 번호 - 편집 가능 */} - {onOrderChange ? ( - - { - const num = parseInt(text) || 1; - onOrderChange(num); - }} - keyboardType="numeric" - editable={!isCompleted} - /> - - 세트 - - - ) : ( + {/* 세트 번호 */} + = ({ > {order ?? 1}세트 - )} + {/* 무게 */} {onWeightChange ? ( - { - const num = parseInt(text) || 0; - onWeightChange(num); - }} - keyboardType="numeric" - editable={!isCompleted} - /> - - kg - + + { + const num = parseFloat(text) || 0; + onWeightChange(num); + }} + keyboardType="decimal-pad" + editable={!isCompleted} + placeholder="0" + placeholderTextColor="#999" + /> + + kg + + ) : ( = ({ {/* 횟수 */} {onRepsChange ? ( - { - const num = parseInt(text) || 0; - onRepsChange(num); - }} - keyboardType="numeric" - editable={!isCompleted} - /> - - 회 - + + { + const num = parseInt(text) || 0; + onRepsChange(num); + }} + keyboardType="number-pad" + editable={!isCompleted} + placeholder="0" + placeholderTextColor="#999" + /> + + 회 + + ) : ( = ({ )} - {/* 체크박스 */} + {/* 완료 버튼 */} - {isCompleted && ( - - )} - {!isCompleted && ( - + {isCompleted ? ( + + ) : ( + )} @@ -185,26 +166,32 @@ const styles = StyleSheet.create({ container: { flexDirection: "row", alignItems: "center", - paddingVertical: 10, - paddingHorizontal: 18, - marginHorizontal: 12, - marginBottom: 8, - borderRadius: 12, + justifyContent: "space-between", + paddingVertical: 16, + paddingHorizontal: 16, + marginHorizontal: 0, + marginBottom: 12, + borderRadius: 16, position: "relative", + borderWidth: 1.5, shadowColor: "#000", shadowOffset: { width: 0, - height: 1, + height: 2, }, - shadowOpacity: 0.1, - shadowRadius: 2, + shadowOpacity: 0.08, + shadowRadius: 4, elevation: 2, + gap: 12, + width: "100%", }, containerDefault: { backgroundColor: "#FFFFFF", + borderColor: "#E8E8E8", }, containerActive: { - backgroundColor: "#E8FF8A", + backgroundColor: "#F0F9F0", + borderColor: "#4CAF50", }, deleteButton: { position: "absolute", @@ -213,67 +200,78 @@ const styles = StyleSheet.create({ zIndex: 1, }, deleteButtonCircle: { - width: 24, - height: 24, - borderRadius: 12, - backgroundColor: "#CCCCCC", + width: 28, + height: 28, + borderRadius: 14, + backgroundColor: "#FF5252", justifyContent: "center", alignItems: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 3, + elevation: 3, }, - setNumber: { - fontSize: 14, - fontWeight: "400", - minWidth: 50, - marginRight: 12, - }, - orderContainer: { - flexDirection: "row", - alignItems: "center", - minWidth: 60, - marginRight: 12, - }, - orderInput: { - fontSize: 14, - fontWeight: "400", - minWidth: 30, - textAlign: "right", - padding: 0, + setNumberContainer: { + width: 60, + justifyContent: "center", + alignItems: "flex-start", }, - orderUnit: { - fontSize: 14, - fontWeight: "400", - marginLeft: 4, + setNumber: { + fontSize: 15, + fontWeight: "600", + textAlign: "left", }, weight: { - fontSize: 14, - fontWeight: "400", + fontSize: 15, + fontWeight: "600", flex: 1, - marginRight: 12, + maxWidth: "35%", + textAlign: "right", }, reps: { - fontSize: 14, - fontWeight: "400", + fontSize: 15, + fontWeight: "600", flex: 1, - marginRight: 12, + maxWidth: "35%", + textAlign: "right", }, valueContainer: { flex: 1, + maxWidth: "35%", + }, + inputWrapper: { flexDirection: "row", alignItems: "center", - marginRight: 12, + justifyContent: "flex-end", + backgroundColor: "#F8F8F8", + borderRadius: 10, + paddingHorizontal: 12, + paddingVertical: 10, + borderWidth: 1, + borderColor: "#E0E0E0", + width: "100%", }, valueInput: { - flex: 1, - fontSize: 14, - fontWeight: "400", + fontSize: 16, + fontWeight: "600", textAlign: "right", - minWidth: 40, + width: 50, padding: 0, + color: "#000000", + }, + valueInputDefault: { + color: "#000000", + }, + valueInputActive: { + color: "#4CAF50", }, unit: { fontSize: 14, - fontWeight: "400", - marginLeft: 4, + fontWeight: "600", + marginLeft: 6, + color: "#666666", + width: 24, }, textDefault: { color: "#000000", @@ -281,21 +279,18 @@ const styles = StyleSheet.create({ textActive: { color: "#000000", }, - checkbox: { - width: 32, - height: 32, - borderRadius: 8, - borderWidth: 1, + completeButton: { + width: 50, + height: 50, justifyContent: "center", alignItems: "center", + flexShrink: 0, }, - checkboxDefault: { - backgroundColor: "#FFFFFF", - borderColor: "#E0E0E0", + completeButtonDefault: { + backgroundColor: "transparent", }, - checkboxCompleted: { - backgroundColor: "#FFFFFF", - borderColor: "#000000", + completeButtonActive: { + backgroundColor: "transparent", }, }); diff --git a/src/components/modals/ExerciseModal.tsx b/src/components/modals/ExerciseModal.tsx index d749b4f..782222e 100644 --- a/src/components/modals/ExerciseModal.tsx +++ b/src/components/modals/ExerciseModal.tsx @@ -320,6 +320,7 @@ const ExerciseModal: React.FC = ({ ] ); const prefetchedInstructionUrlsRef = useRef>(new Set()); + const detailScrollViewRef = useRef(null); // 이미지 로드 실패 추적 (URL을 키로 사용) const [failedImageUrls, setFailedImageUrls] = useState>(new Set()); // 운동별 피드백 상태 관리 (운동 이름을 키로 사용) @@ -633,6 +634,10 @@ const ExerciseModal: React.FC = ({ (direction: "prev" | "next") => { cacheCurrentExerciseSets(); persistCurrentExerciseState(); + // 다음 버튼을 눌렀을 때 스크롤을 맨 위로 + if (direction === "next") { + detailScrollViewRef.current?.scrollTo({ y: 0, animated: true }); + } onSequenceNavigate?.(direction); }, [cacheCurrentExerciseSets, persistCurrentExerciseState, onSequenceNavigate] @@ -1758,7 +1763,7 @@ const getExerciseDisplayName = React.useCallback( }} style={styles.backBtnTop} > - + {getExerciseDisplayName( @@ -1766,34 +1771,6 @@ const getExerciseDisplayName = React.useCallback( )} - {!isCompleted && ( - setShowExerciseListModal(true)} - activeOpacity={0.85} - > - - - )} - {mode !== "edit" && !isCompleted && ( - - - - {formatWorkoutTimer(workoutTimerSeconds)} - - - )} @@ -1812,42 +1789,15 @@ const getExerciseDisplayName = React.useCallback( - {!isCompleted && ( - setShowExerciseListModal(true)} - activeOpacity={0.85} - > - - - )} - {mode !== "edit" && !isCompleted && ( - - - - {formatWorkoutTimer(workoutTimerSeconds)} - - - )} - + )} - + )} {/* 오른쪽 버튼: 운동이 2개 이상이고, 현재가 마지막이 아니면 표시 */} @@ -1953,7 +1903,7 @@ const getExerciseDisplayName = React.useCallback( } }} > - + )} @@ -1970,7 +1920,7 @@ const getExerciseDisplayName = React.useCallback( {showInstructionsSection && ( @@ -2055,16 +2005,6 @@ const getExerciseDisplayName = React.useCallback( - - - - - - {sets.map((set) => ( - handleOrderChange(set.id, order) - } onWeightChange={(weight) => handleSetChange(set.id, "weight", weight) } @@ -2091,6 +2028,16 @@ const getExerciseDisplayName = React.useCallback( /> ))} + + + + 세트 추가 + + @@ -2156,82 +2103,90 @@ const getExerciseDisplayName = React.useCallback( }); }; + const hasFeedback = currentFeedback.feedback !== null; + return ( - 이 운동 어땠나요? - - handleIntensityClick("heavy")} - > - - 무거워요 - - - handleIntensityClick("light")} - > - - 가벼워요 - - - handleFeedbackClick("like")} - > - + + 난이도는 어땠나요? + + handleIntensityClick("heavy")} + > + + 무거워요 + + + handleIntensityClick("light")} + > + + 가벼워요 + + + + + + )} + + {/* 이 운동 어땠나요 섹션 */} + + 이 운동 어땠나요? + + handleFeedbackClick("like")} > - 좋아요 - - - handleFeedbackClick("dislike")} - > - + + handleFeedbackClick("dislike")} > - 싫어요 - - + + + ); @@ -2246,27 +2201,34 @@ const getExerciseDisplayName = React.useCallback( onPress={() => handleSequenceNavigatePress("prev")} disabled={!hasPrevSequence} > + - 이전 운동 + 이전 {!isCompleted && ( - 타이머 - + + {formatWorkoutTimer(workoutTimerSeconds)} @@ -2285,28 +2247,27 @@ const getExerciseDisplayName = React.useCallback( !canNextExercise && styles.sequenceControlTextDisabled, ]} > - 다음 운동 + 다음 + )} - - - 운동 끝내기 - - + + 운동 끝내기 + + + )} )} @@ -2322,10 +2283,10 @@ const getExerciseDisplayName = React.useCallback( > {!fullScreen && ( - + - 종목 추가 + 종목 추가 @@ -2630,16 +2591,6 @@ const getExerciseDisplayName = React.useCallback( - - - - - - {sets.map((set) => ( - handleOrderChange(set.id, order) - } onWeightChange={(weight) => handleSetChange(set.id, "weight", weight) } @@ -2666,6 +2614,16 @@ const getExerciseDisplayName = React.useCallback( /> ))} + + + + 세트 추가 + + @@ -2676,23 +2634,16 @@ const getExerciseDisplayName = React.useCallback( allSetsCompleted && !isCompleted && styles.footerWithFeedback, ]} > - - - 운동 끝내기 - - + + 운동 끝내기 + + + )} {allSetsCompleted && !isCompleted && (() => { const currentExerciseName = getExerciseDisplayName( selectedExercise || exerciseData || { name: "" } @@ -2746,82 +2697,90 @@ const getExerciseDisplayName = React.useCallback( }); }; + const hasFeedback = currentFeedback.feedback !== null; + return ( - 이 운동 어땠나요? - - handleIntensityClick("heavy")} - > - - 무거워요 - - - handleIntensityClick("light")} - > - - 가벼워요 - - - handleFeedbackClick("like")} - > - + + 난이도는 어땠나요? + + handleIntensityClick("heavy")} + > + + 무거워요 + + + handleIntensityClick("light")} + > + + 가벼워요 + + + + + + )} + + {/* 이 운동 어땠나요 섹션 */} + + 이 운동 어땠나요? + + handleFeedbackClick("like")} > - 좋아요 - - - handleFeedbackClick("dislike")} - > - + + handleFeedbackClick("dislike")} > - 싫어요 - - + + + ); @@ -2950,21 +2909,21 @@ const styles = StyleSheet.create({ backgroundColor: "#2a2a2a", borderTopLeftRadius: 20, borderTopRightRadius: 20, - height: "70%", // 고정 비율 높이 상향 + height: "85%", // 고정 비율 높이 상향 minHeight: 0, overflow: "hidden", }, fullScreenContainer: { flex: 1, - backgroundColor: "#0c0c0c", + backgroundColor: "#ffffff", }, fullScreenContent: { flex: 1, - backgroundColor: "#0c0c0c", + backgroundColor: "#ffffff", }, fullScreenHeader: { paddingHorizontal: 20, - backgroundColor: "#0c0c0c", + backgroundColor: "#ffffff", paddingTop: 16, paddingBottom: 2, }, @@ -2992,7 +2951,7 @@ const styles = StyleSheet.create({ flex: 1, fontSize: 18, fontWeight: "600", - color: "#ffffff", + color: "#000000", textAlign: "center", }, detailHeaderActions: { @@ -3022,7 +2981,7 @@ const styles = StyleSheet.create({ }, exerciseDetailModal: { flex: 1, - backgroundColor: "#0c0c0c", + backgroundColor: "#ffffff", }, detailKeyboardAvoider: { flex: 1, @@ -3033,7 +2992,15 @@ const styles = StyleSheet.create({ alignItems: "center", padding: 20, borderBottomWidth: 1, - borderBottomColor: "#404040", + borderBottomColor: "#e8e8e8", + backgroundColor: "#ffffff", + }, + addExerciseModalHeader: { + backgroundColor: "#252525", + borderBottomColor: "#3a3a3a", + }, + addExerciseModalTitle: { + color: "#ffffff", }, headerLeft: { width: 52, @@ -3052,14 +3019,14 @@ const styles = StyleSheet.create({ width: 36, height: 36, borderRadius: 18, - backgroundColor: "rgba(255,255,255,0.14)", + backgroundColor: "#f5f5f5", justifyContent: "center", alignItems: "center", }, modalTitle: { fontSize: 18, fontWeight: "600", - color: "#ffffff", + color: "#000000", flex: 1, textAlign: "center", }, @@ -3084,22 +3051,48 @@ const styles = StyleSheet.create({ timerBadge: { flexDirection: "row", alignItems: "center", - gap: 4, - backgroundColor: "rgba(255,255,255,0.1)", - borderRadius: 18, - paddingHorizontal: 10, - paddingVertical: 6, - borderWidth: 1, - borderColor: "rgba(255,255,255,0.16)", + gap: 6, + backgroundColor: "#000000", + borderRadius: 20, + paddingHorizontal: 14, + paddingVertical: 8, + borderWidth: 0, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, }, timerBadgePaused: { - backgroundColor: "rgba(255,255,255,0.04)", - borderColor: "rgba(255,255,255,0.08)", + backgroundColor: "#f5f5f5", + borderWidth: 1, + borderColor: "#e0e0e0", }, timerBadgeText: { color: "#ffffff", - fontSize: 13, - fontWeight: "600", + fontSize: 14, + fontWeight: "700", + letterSpacing: 0.5, + }, + timerBadgeTextPaused: { + color: "#666666", + }, + timerBadgeInline: { + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 12, + paddingVertical: 6, + backgroundColor: "#f0f9f0", + borderRadius: 20, + minWidth: 80, + borderWidth: 1, + borderColor: "#4CAF50", + }, + timerBadgeTextInline: { + color: "#4CAF50", + fontSize: 16, + fontWeight: "700", + letterSpacing: 0.5, }, methodBtn: { paddingHorizontal: 10, @@ -3116,13 +3109,15 @@ const styles = StyleSheet.create({ marginHorizontal: 20, marginTop: 0, marginBottom: 24, - backgroundColor: "#333333", - borderRadius: 10, + backgroundColor: "#f8f8f8", + borderRadius: 12, padding: 16, + borderWidth: 1, + borderColor: "#e8e8e8", }, instructionTitle: { - fontSize: 13, - color: "#ffffff", + fontSize: 14, + color: "#000000", fontWeight: "600", marginBottom: 0, }, @@ -3133,9 +3128,9 @@ const styles = StyleSheet.create({ gap: 8, }, instructionText: { - fontSize: 12, - color: "#cccccc", - lineHeight: 17, + fontSize: 13, + color: "#666666", + lineHeight: 20, }, instructionList: { marginTop: 12, @@ -3145,8 +3140,8 @@ const styles = StyleSheet.create({ marginBottom: 16, }, instructionNumber: { - fontSize: 12, - color: "#ffffff", + fontSize: 13, + color: "#000000", fontWeight: "600", marginRight: 8, minWidth: 20, @@ -3156,13 +3151,14 @@ const styles = StyleSheet.create({ }, exerciseImageContainer: { position: "relative", - width: "100%", height: 270, marginTop: 0, marginBottom: 24, - backgroundColor: "#1a1a1a", + marginHorizontal: 20, + backgroundColor: "#f8f8f8", justifyContent: "center", alignItems: "center", + borderRadius: 16, }, exerciseImageLarge: { width: "100%", @@ -3179,87 +3175,148 @@ const styles = StyleSheet.create({ left: 12, top: "50%", marginTop: -16, - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: "rgba(0, 0, 0, 0.5)", + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: "rgba(255, 255, 255, 0.9)", justifyContent: "center", alignItems: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, }, exerciseImageNavRight: { position: "absolute", right: 12, top: "50%", marginTop: -16, - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: "rgba(0, 0, 0, 0.5)", + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: "rgba(255, 255, 255, 0.9)", justifyContent: "center", alignItems: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, }, loadingText: { - color: "#666666", + color: "#999999", fontSize: 14, }, addSetButtonWrapper: { alignItems: "center", - marginTop: 0, - marginBottom: 12, + marginTop: 12, + marginBottom: 16, + paddingHorizontal: 0, }, - addSetCircleButton: { - width: 32, - height: 32, - borderRadius: 16, - backgroundColor: "#e3ff7c", - justifyContent: "center", + addSetButton: { + flexDirection: "row", alignItems: "center", - shadowColor: "#000", - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.2, - shadowRadius: 4, - elevation: 3, + justifyContent: "center", + backgroundColor: "#f0f9f0", + borderRadius: 12, + paddingVertical: 14, + paddingHorizontal: 20, + borderWidth: 1.5, + borderColor: "#4CAF50", + borderStyle: "dashed", + width: "100%", + gap: 8, + }, + addSetButtonText: { + color: "#4CAF50", + fontSize: 16, + fontWeight: "700", }, feedbackSection: { marginTop: 0, marginHorizontal: 0, marginBottom: 12, }, - feedbackTitle: { - fontSize: 18, - fontWeight: "600", - color: "#ffffff", - marginBottom: 16, - textAlign: "center", + intensitySection: { + marginBottom: 24, }, - feedbackButtonsRow: { + intensityTitleRow: { flexDirection: "row", - flexWrap: "wrap", + alignItems: "center", justifyContent: "space-between", + gap: 16, }, - feedbackButton: { - width: "48%", - backgroundColor: "#ffffff", - borderWidth: 1, - borderColor: "#000000", - borderRadius: 8, - paddingVertical: 14, + intensityTitle: { + fontSize: 16, + fontWeight: "600", + color: "#000000", + marginBottom: 0, + flex: 1, + }, + intensityButtonsRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + flexWrap: "nowrap", + }, + intensityButton: { + backgroundColor: "#f8f8f8", + borderWidth: 1.5, + borderColor: "#e0e0e0", + borderRadius: 10, + paddingVertical: 10, + paddingHorizontal: 16, alignItems: "center", justifyContent: "center", - marginBottom: 12, + minWidth: 80, }, - feedbackButtonSelected: { - backgroundColor: "#d6ff4b", - borderColor: "#d6ff4b", + intensityButtonSelected: { + backgroundColor: "#4CAF50", + borderColor: "#4CAF50", }, - feedbackButtonText: { - color: "#000000", - fontSize: 14, - fontWeight: "500", + intensityButtonText: { + color: "#666666", + fontSize: 12, + fontWeight: "600", }, - feedbackButtonTextSelected: { - color: "#000000", + intensityButtonTextSelected: { + color: "#ffffff", + fontSize: 12, + fontWeight: "700", + }, + feedbackTitleRow: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + gap: 16, + }, + feedbackTitle: { + fontSize: 16, fontWeight: "600", + color: "#000000", + marginBottom: 0, + flex: 1, + }, + feedbackButtonsRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + flexWrap: "nowrap", + }, + feedbackIconButton: { + width: 44, + height: 44, + borderRadius: 22, + backgroundColor: "#f8f8f8", + borderWidth: 2, + borderColor: "#e0e0e0", + alignItems: "center", + justifyContent: "center", + }, + feedbackIconButtonSelected: { + backgroundColor: "#4CAF50", + borderColor: "#4CAF50", }, exerciseListOverlay: { flex: 1, @@ -3388,22 +3445,33 @@ const styles = StyleSheet.create({ borderBottomColor: "#f0f0f0", }, endWorkoutBtn: { - backgroundColor: "#404040", - paddingVertical: 10, - borderRadius: 12, + backgroundColor: "#000000", + paddingVertical: 18, + borderRadius: 14, alignItems: "center", + justifyContent: "center", + width: "100%", + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 8, + elevation: 5, + marginTop: 12, }, endWorkoutBtnDisabled: { - backgroundColor: "#2a2a2a", - opacity: 0.5, + backgroundColor: "#e8e8e8", + opacity: 1, + shadowOpacity: 0, + elevation: 0, }, endWorkoutBtnText: { color: "#ffffff", - fontSize: 16, - fontWeight: "600", + fontSize: 17, + fontWeight: "700", + letterSpacing: 0.3, }, endWorkoutBtnTextDisabled: { - color: "#888888", + color: "#999999", }, instructionScroll: { maxHeight: 260, @@ -3669,6 +3737,7 @@ const styles = StyleSheet.create({ paddingBottom: 0, flex: 1, minHeight: 0, + width: "100%", }, setsHeader: { flexDirection: "row", @@ -3696,6 +3765,7 @@ const styles = StyleSheet.create({ marginTop: 0, paddingTop: 0, paddingBottom: 12, + width: "100%", }, setControlBtn: { width: 32, @@ -3831,7 +3901,9 @@ const styles = StyleSheet.create({ paddingHorizontal: 20, paddingBottom: 0, paddingTop: 16, - backgroundColor: "#2a2a2a", + backgroundColor: "#ffffff", + borderTopWidth: 1, + borderTopColor: "#e8e8e8", }, footerExtended: { flexDirection: "column", @@ -3863,23 +3935,25 @@ const styles = StyleSheet.create({ commentLabel: { fontSize: 14, fontWeight: "600", - color: "#ffffff", + color: "#000000", }, commentInputWrapper: { - backgroundColor: "#1f1f1f", + backgroundColor: "#f8f8f8", borderRadius: 12, padding: 16, gap: 8, + borderWidth: 1, + borderColor: "#e8e8e8", }, commentInput: { minHeight: 60, - color: "#ffffff", + color: "#000000", fontSize: 14, lineHeight: 20, }, commentCounter: { fontSize: 12, - color: "#888888", + color: "#999999", textAlign: "right", }, sequenceControlContainer: { @@ -3896,39 +3970,45 @@ const styles = StyleSheet.create({ sequenceControlButton: { flex: 1, borderRadius: 12, - backgroundColor: "#272727", - paddingVertical: 8, + backgroundColor: "#f8f8f8", + paddingVertical: 14, alignItems: "center", justifyContent: "center", + borderWidth: 1, + borderColor: "#e8e8e8", + flexDirection: "row", + minHeight: 48, }, sequenceControlButtonDisabled: { - backgroundColor: "#1a1a1a", - opacity: 0.4, + backgroundColor: "#f5f5f5", + opacity: 0.5, + borderColor: "#e0e0e0", }, sequenceControlText: { - color: "#ffffff", - fontSize: 14, + color: "#000000", + fontSize: 15, fontWeight: "600", }, sequenceControlTextDisabled: { - color: "#888888", + color: "#cccccc", }, sequenceTimerButton: { - gap: 6, - paddingVertical: 10, - }, - sequenceTimerLabel: { - color: "#bbbbbb", - fontSize: 12, - fontWeight: "500", + flex: 1, + borderRadius: 12, + backgroundColor: "#f0f9f0", + paddingVertical: 14, + alignItems: "center", + justifyContent: "center", + borderWidth: 1.5, + borderColor: "#4CAF50", + flexDirection: "row", + minHeight: 48, }, - sequenceTimerValue: { - color: "#ffffff", + sequenceTimerText: { + color: "#4CAF50", fontSize: 16, fontWeight: "700", - }, - sequenceTimerValuePaused: { - color: "#ffb84d", + letterSpacing: 0.5, }, commentSendButton: { backgroundColor: "#e3ff7c", diff --git a/src/screens/analysis/CalendarScreen.tsx b/src/screens/analysis/CalendarScreen.tsx index 2576cf5..f24455f 100644 --- a/src/screens/analysis/CalendarScreen.tsx +++ b/src/screens/analysis/CalendarScreen.tsx @@ -11,8 +11,9 @@ import { Ionicons as Icon } from '@expo/vector-icons'; import {colors} from '../../theme/colors'; import {useDate} from '../../contexts/DateContext'; import {fetchWeeklyProgress, fetchMonthlyProgress} from '../../utils/exerciseApi'; -import {mealAPI} from '../../services'; +import {mealAPI, homeAPI} from '../../services'; import type {DailyProgressWeekItem, NutritionGoal, DailyMealsResponse} from '../../types'; +import {eventBus} from '../../utils/eventBus'; const CalendarScreen = ({navigation}: any) => { const [monthBase, setMonthBase] = useState(new Date()); @@ -35,7 +36,82 @@ const CalendarScreen = ({navigation}: any) => { const loadWeeklyProgress = async () => { try { const data = await fetchWeeklyProgress(); - setWeeklyProgress(Array.isArray(data) ? data : []); + + // 이번 주의 날짜 범위 계산 (일~토) + const today = new Date(); + const getStartOfWeek = (d: Date) => { + const n = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + const diff = n.getDay(); + n.setDate(n.getDate() - diff); + return n; + }; + const startOfWeek = getStartOfWeek(today); + + // 이번 주의 각 날짜에 대해 칼로리 데이터 가져오기 + const weekDates = Array.from({ length: 7 }).map((_, i) => { + const d = new Date( + startOfWeek.getFullYear(), + startOfWeek.getMonth(), + startOfWeek.getDate() + i + ); + return formatDateToString(d); + }); + + // 각 날짜에 대해 영양성분 요약 조회 (병렬 처리) + const nutritionPromises = weekDates.map(async (date) => { + try { + const summary = await mealAPI.getNutritionSummary(date); + return { date, calories: summary.calories || 0 }; + } catch (error) { + console.error(`영양성분 조회 실패 (${date}):`, error); + return { date, calories: 0 }; + } + }); + + // 각 날짜에 대해 운동 시간 조회 (병렬 처리) + const workoutTimePromises = weekDates.map(async (date) => { + try { + const progress = await homeAPI.getTodayProgress(date); + return { date, totalSeconds: progress.totalExerciseSeconds || 0 }; + } catch (error) { + console.error(`운동 시간 조회 실패 (${date}):`, error); + return { date, totalSeconds: 0 }; + } + }); + + const [nutritionResults, workoutTimeResults] = await Promise.all([ + Promise.all(nutritionPromises), + Promise.all(workoutTimePromises), + ]); + + // 기존 데이터와 병합 (칼로리 및 운동 시간 데이터 업데이트) + let updatedData: DailyProgressWeekItem[] = []; + + if (Array.isArray(data) && data.length > 0) { + updatedData = weekDates.map((date) => { + const existingItem = data.find((item) => item.date === date); + const nutritionItem = nutritionResults.find((item) => item.date === date); + const workoutItem = workoutTimeResults.find((item) => item.date === date); + + return { + date, + exerciseRate: existingItem?.exerciseRate ?? 0, + totalCalorie: nutritionItem?.calories ?? existingItem?.totalCalorie ?? 0, + // 운동 시간은 백엔드에서 exerciseRate로 계산되지만, 최신 데이터를 위해 재조회 + }; + }); + } else { + updatedData = weekDates.map((date) => { + const nutritionItem = nutritionResults.find((item) => item.date === date); + return { + date, + exerciseRate: 0, + totalCalorie: nutritionItem?.calories ?? 0, + }; + }); + } + + setWeeklyProgress(updatedData); } catch (e) { console.error('주간 진행률 로드 실패:', e); setWeeklyProgress([]); @@ -47,7 +123,71 @@ const CalendarScreen = ({navigation}: any) => { try { const yearMonth = `${year}-${String(month + 1).padStart(2, '0')}`; const data = await fetchMonthlyProgress(yearMonth); - setMonthlyProgress(Array.isArray(data) ? data : []); + + // 해당 월의 모든 날짜 계산 + const firstOfMonth = new Date(year, month, 1); + const nextMonth = new Date(year, month + 1, 1); + const daysInMonth = Math.round((nextMonth.getTime() - firstOfMonth.getTime()) / (1000 * 60 * 60 * 24)); + const monthDates = Array.from({ length: daysInMonth }).map((_, i) => { + const d = new Date(year, month, i + 1); + return formatDateToString(d); + }); + + // 각 날짜에 대해 영양성분 요약 조회 (병렬 처리) + const nutritionPromises = monthDates.map(async (date) => { + try { + const summary = await mealAPI.getNutritionSummary(date); + return { date, calories: summary.calories || 0 }; + } catch (error) { + console.error(`영양성분 조회 실패 (${date}):`, error); + return { date, calories: 0 }; + } + }); + + // 각 날짜에 대해 운동 시간 조회 (병렬 처리) + const workoutTimePromises = monthDates.map(async (date) => { + try { + const progress = await homeAPI.getTodayProgress(date); + return { date, totalSeconds: progress.totalExerciseSeconds || 0 }; + } catch (error) { + console.error(`운동 시간 조회 실패 (${date}):`, error); + return { date, totalSeconds: 0 }; + } + }); + + const [nutritionResults, workoutTimeResults] = await Promise.all([ + Promise.all(nutritionPromises), + Promise.all(workoutTimePromises), + ]); + + // 기존 데이터와 병합 (칼로리 및 운동 시간 데이터 업데이트) + let updatedData: DailyProgressWeekItem[] = []; + + if (Array.isArray(data) && data.length > 0) { + updatedData = monthDates.map((date) => { + const existingItem = data.find((item) => item.date === date); + const nutritionItem = nutritionResults.find((item) => item.date === date); + const workoutItem = workoutTimeResults.find((item) => item.date === date); + + return { + date, + exerciseRate: existingItem?.exerciseRate ?? 0, + totalCalorie: nutritionItem?.calories ?? existingItem?.totalCalorie ?? 0, + // 운동 시간은 백엔드에서 exerciseRate로 계산되지만, 최신 데이터를 위해 재조회 + }; + }); + } else { + updatedData = monthDates.map((date) => { + const nutritionItem = nutritionResults.find((item) => item.date === date); + return { + date, + exerciseRate: 0, + totalCalorie: nutritionItem?.calories ?? 0, + }; + }); + } + + setMonthlyProgress(updatedData); } catch (e) { console.error('월별 진행률 로드 실패:', e); setMonthlyProgress([]); @@ -120,6 +260,49 @@ const CalendarScreen = ({navigation}: any) => { return unsubscribe; }, [navigation]); + // 식사 삭제 이벤트 리스너 + useEffect(() => { + const unsubscribe = eventBus.on("mealDeleted", () => { + console.log("[CALENDAR] 식사 삭제 이벤트 수신, 캘린더 데이터 새로고침"); + // 주간/월간 진행률과 오늘 식단 데이터 새로고침 + loadWeeklyProgress(); + loadMonthlyProgress(monthBase.getFullYear(), monthBase.getMonth()); + fetchTodayMeals(); + }); + + return () => { + unsubscribe?.(); + }; + }, [monthBase]); + + // 운동 저장/삭제 이벤트 리스너 + useEffect(() => { + const unsubscribeSaved = eventBus.on("workoutSessionSaved", () => { + console.log("[CALENDAR] 운동 저장 이벤트 수신, 캘린더 데이터 새로고침"); + // 주간/월간 진행률 새로고침 (운동 시간 업데이트) + // 약간의 지연을 두어 백엔드 데이터 업데이트 대기 + setTimeout(() => { + loadWeeklyProgress(); + loadMonthlyProgress(monthBase.getFullYear(), monthBase.getMonth()); + }, 500); + }); + + const unsubscribeDeleted = eventBus.on("workoutSessionDeleted", () => { + console.log("[CALENDAR] 운동 삭제 이벤트 수신, 캘린더 데이터 새로고침"); + // 주간/월간 진행률 새로고침 (운동 시간 업데이트) + // 약간의 지연을 두어 백엔드 데이터 업데이트 대기 + setTimeout(() => { + loadWeeklyProgress(); + loadMonthlyProgress(monthBase.getFullYear(), monthBase.getMonth()); + }, 500); + }); + + return () => { + unsubscribeSaved?.(); + unsubscribeDeleted?.(); + }; + }, [monthBase]); + // 식단 데이터 const meals = [ { diff --git a/src/screens/auth/KakaoOnboardingScreen.tsx b/src/screens/auth/KakaoOnboardingScreen.tsx index 590a1c1..3069d1a 100644 --- a/src/screens/auth/KakaoOnboardingScreen.tsx +++ b/src/screens/auth/KakaoOnboardingScreen.tsx @@ -14,7 +14,7 @@ import { SafeAreaView, } from "react-native"; import { Picker } from "@react-native-picker/picker"; -import { authAPI } from "../../services"; +import { authAPI, mealAPI } from "../../services"; const healthGoalOptions = [ { label: "벌크업", value: "BULK" }, @@ -184,6 +184,24 @@ const KakaoOnboardingScreen = ({ navigation }: any) => { const response = await authAPI.submitOnboarding(onboardingData); + // 기본 칼로리 목표 2000으로 설정 + try { + const today = new Date(); + const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; + await mealAPI.setNutritionGoal({ + targetCalories: 2000, + targetCarbs: 0, + targetProtein: 0, + targetFat: 0, + goalType: 'MANUAL', + date: todayStr, + }); + console.log('✅ 기본 칼로리 목표 2000 설정 완료'); + } catch (goalError) { + console.error('기본 칼로리 목표 설정 실패:', goalError); + // 칼로리 목표 설정 실패해도 온보딩은 성공으로 처리 + } + setLoading(false); // 200 응답 (온보딩 완료) → Alert 없이 바로 홈으로 이동 diff --git a/src/screens/auth/SignupScreen.tsx b/src/screens/auth/SignupScreen.tsx index c06b231..fd52cca 100644 --- a/src/screens/auth/SignupScreen.tsx +++ b/src/screens/auth/SignupScreen.tsx @@ -14,7 +14,7 @@ import { Linking, } from 'react-native'; import {Picker} from '@react-native-picker/picker'; -import {authAPI} from '../../services'; +import {authAPI, mealAPI} from '../../services'; const SignupScreen = ({navigation}: any) => { // 회원가입 단계 관리 (1~5단계) @@ -374,6 +374,24 @@ const SignupScreen = ({navigation}: any) => { const response = await authAPI.signup(signupData); if (response.success) { + // 기본 칼로리 목표 2000으로 설정 + try { + const today = new Date(); + const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; + await mealAPI.setNutritionGoal({ + targetCalories: 2000, + targetCarbs: 0, + targetProtein: 0, + targetFat: 0, + goalType: 'MANUAL', + date: todayStr, + }); + console.log('✅ 기본 칼로리 목표 2000 설정 완료'); + } catch (goalError) { + console.error('기본 칼로리 목표 설정 실패:', goalError); + // 칼로리 목표 설정 실패해도 회원가입은 성공으로 처리 + } + setShowCompleteScreen(true); } else { const errorMessage = response.message || '회원가입에 실패했습니다'; diff --git a/src/screens/chatbot/ChatbotScreen.tsx b/src/screens/chatbot/ChatbotScreen.tsx index 1bec95e..5e92ad5 100644 --- a/src/screens/chatbot/ChatbotScreen.tsx +++ b/src/screens/chatbot/ChatbotScreen.tsx @@ -12,6 +12,7 @@ import { Alert, ActivityIndicator, Animated, + Dimensions, } from "react-native"; import { SafeAreaView, @@ -29,11 +30,15 @@ interface Message { text: string; } +const { width } = Dimensions.get("window"); + const ChatbotScreen = ({ navigation }: any) => { const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); const [isLoading, setIsLoading] = useState(false); const [userId, setUserId] = useState(null); + const scrollViewRef = useRef(null); + const fadeAnim = useRef(new Animated.Value(0)).current; const insets = useSafeAreaInsets(); @@ -58,6 +63,13 @@ const ChatbotScreen = ({ navigation }: any) => { loadSettings(); loadUserId(); loadMembershipInfo(); + + // 페이드 인 애니메이션 + Animated.timing(fadeAnim, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }).start(); }, []); // ✅ 챗봇 히스토리 로드 @@ -214,6 +226,11 @@ const ChatbotScreen = ({ navigation }: any) => { setMessages((prev) => [...prev, { type: "user", text: userMessage }]); setIsLoading(true); + // 메시지 추가 후 스크롤 + setTimeout(() => { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }, 100); + try { const botResponse = await chatAPI.sendMessage( userId, @@ -223,6 +240,11 @@ const ChatbotScreen = ({ navigation }: any) => { ); setMessages((prev) => [...prev, { type: "bot", text: botResponse }]); + + // 봇 응답 후 스크롤 + setTimeout(() => { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }, 100); await loadMembershipInfo(); } catch (error: any) { @@ -251,15 +273,6 @@ const ChatbotScreen = ({ navigation }: any) => { } setMessages((prev) => [...prev, { type: "bot", text: errorMessage }]); - - if ( - error.message?.includes("로그인") || - error.message?.includes("인증") - ) { - setTimeout(() => { - navigation.replace("Login"); - }, 1500); - } } finally { setIsLoading(false); } @@ -361,28 +374,22 @@ const ChatbotScreen = ({ navigation }: any) => { } }; - const renderPremiumBadge = () => { - if (membershipType === "PREMIUM") { - return ( - - - 프리미엄 무제한 - - ); - } - return null; - }; - return ( {!isInTab && ( - navigation.goBack()}> - + navigation.goBack()} + style={styles.headerButton} + > + - AI 챗봇 - setIsSettingsModalOpen(true)}> - + AI 코치 + setIsSettingsModalOpen(true)} + style={styles.headerButton} + > + )} @@ -392,98 +399,92 @@ const ChatbotScreen = ({ navigation }: any) => { behavior={Platform.OS === "ios" ? "padding" : "height"} keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 0} > - {/* ✅ ScrollView로 전체 감싸기 */} - - + { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }} + > {messages.length === 0 && !showHistory ? ( - <> - - - 안녕하세요! - 어떻게 도와드릴까요? - - - setIsSettingsModalOpen(true)} + + + - - 채팅 설정 - + 🤖 + - - - {renderPremiumBadge()} - - - - {getModeText()} · {getStyleText()} - + + 안녕하세요! + + AI 코치가 도와드리겠습니다 + + + {membershipType === "PREMIUM" && ( + + + 프리미엄 무제한 - + )} - - - 🤖 - + + + + {getModeText()} · {getStyleText()} + handleQuickSelect("exercise")} activeOpacity={0.7} > - 🏋️ - 운동 추천 + 💪 + 운동 추천 handleQuickSelect("food")} activeOpacity={0.7} > - 🍗 - 식단 추천 + 🍎 + 식단 추천 handleQuickSelect("plan")} activeOpacity={0.7} > - 📅 - 계획 수립 + 📋 + 계획 수립 @@ -493,60 +494,49 @@ const ChatbotScreen = ({ navigation }: any) => { onPress={handleShowHistory} disabled={isLoadingHistory} > - + {isLoadingHistory ? "불러오는 중..." : "이전 대화 보기"} - + ) : ( <> - {renderPremiumBadge()} - - - - {getModeText()} · {getStyleText()} - + + {membershipType === "PREMIUM" && ( + + + + )} + + + + {getModeText()} · {getStyleText()} + + + + + + + + + setIsSettingsModalOpen(true)} + > + + - - - - - - setIsSettingsModalOpen(true)} - > - - {isLoadingHistory && ( - - - - 대화 기록 불러오는 중... - + + + 대화 기록 불러오는 중... )} @@ -555,46 +545,57 @@ const ChatbotScreen = ({ navigation }: any) => { {msg.type === "bot" && ( - 🤖 + + 🤖 + )} + {msg.type === "user" ? ( {msg.text} ) : ( - + {msg.text} )} + {msg.type === "user" && ( - + )} ))} + {isLoading && ( - + - 🤖 + + 🤖 + - + - - - + + + @@ -602,34 +603,34 @@ const ChatbotScreen = ({ navigation }: any) => { )} - - + + 0 ? Math.max(insets.bottom, 4) : 4) + : (insets.bottom > 0 ? Math.max(insets.bottom, 8) : 8) }, ]} > { ); }; -const NEW_COLORS = { - background: "#1a1a1a", - text: "#f0f0f0", - text_secondary: "#a0a0a0", - accent: "#e3ff7c", - card_bg: "#252525", - separator: "#3a3a3a", - delete_color: "#ff6b6b", +const COLORS = { + background: "#0a0a0a", + surface: "#151515", + surfaceLight: "#1f1f1f", + text: "#ffffff", + textSecondary: "#888888", + primary: "#e3ff7c", + primaryDark: "#d4f05a", + border: "#2a2a2a", + premium: "#FFD700", }; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: NEW_COLORS.background, + backgroundColor: COLORS.background, }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", - paddingVertical: 16, paddingHorizontal: 20, + paddingVertical: 16, borderBottomWidth: 1, - borderBottomColor: NEW_COLORS.separator, + borderBottomColor: COLORS.border, }, - backIcon: { - fontSize: 24, - color: NEW_COLORS.text, - fontWeight: "bold", + headerButton: { + padding: 4, }, headerTitle: { - fontSize: 18, - fontWeight: "600", - color: NEW_COLORS.text, + fontSize: 20, + fontWeight: "700", + color: COLORS.text, + letterSpacing: -0.5, }, keyboardView: { flex: 1, }, + content: { + flex: 1, + }, scrollView: { flex: 1, }, scrollViewContent: { flexGrow: 1, - paddingBottom: 10, + paddingBottom: 20, }, - mainContent: { + emptyState: { flex: 1, - padding: 20, - }, - welcomeHeader: { alignItems: "center", - marginTop: 20, + justifyContent: "center", + paddingHorizontal: 24, + paddingTop: 40, + paddingBottom: 20, + }, + avatarContainer: { + marginBottom: 32, }, - welcomeSection: { + avatarGradient: { + width: 120, + height: 120, + borderRadius: 60, + justifyContent: "center", alignItems: "center", + shadowColor: COLORS.primary, + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.4, + shadowRadius: 20, + elevation: 10, + }, + avatarEmoji: { + fontSize: 60, }, - title: { + greetingTitle: { fontSize: 32, - fontWeight: "700", - color: NEW_COLORS.text, + fontWeight: "800", + color: COLORS.text, marginBottom: 8, - letterSpacing: -0.5, + letterSpacing: -1, }, - subtitle: { - fontSize: 18, - color: NEW_COLORS.text_secondary, + greetingSubtitle: { + fontSize: 16, + color: COLORS.textSecondary, + marginBottom: 24, fontWeight: "400", }, - settingsButton: { + premiumBadge: { flexDirection: "row", alignItems: "center", - gap: 8, - marginTop: 16, - paddingHorizontal: 20, - paddingVertical: 10, - backgroundColor: NEW_COLORS.card_bg, + gap: 6, + paddingHorizontal: 14, + paddingVertical: 8, + backgroundColor: `${COLORS.premium}15`, borderRadius: 20, + marginBottom: 16, borderWidth: 1, - borderColor: NEW_COLORS.accent, + borderColor: `${COLORS.premium}30`, }, - settingsButtonText: { - fontSize: 14, + premiumText: { + fontSize: 13, + color: COLORS.premium, fontWeight: "600", - color: NEW_COLORS.accent, }, - badgeContainer: { - flexDirection: "row", + premiumBadgeSmall: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: `${COLORS.premium}20`, justifyContent: "center", alignItems: "center", - flexWrap: "wrap", - gap: 12, - marginTop: 16, - }, - premiumBadge: { - flexDirection: "row", - alignItems: "center", - gap: 6, - paddingHorizontal: 12, - paddingVertical: 6, - backgroundColor: "#FFD70020", - borderRadius: 12, + marginRight: 8, }, - premiumBadgeText: { - fontSize: 12, - color: "#FFD700", - fontWeight: "600", - }, - currentSettingsBadge: { + settingsInfo: { flexDirection: "row", alignItems: "center", gap: 6, paddingHorizontal: 16, - paddingVertical: 8, - backgroundColor: `${NEW_COLORS.accent}20`, - borderRadius: 16, - }, - currentSettingsBadgeText: { - fontSize: 12, - color: NEW_COLORS.accent, - fontWeight: "500", - }, - botImageContainer: { - alignItems: "center", - marginVertical: 40, - padding: 20, - }, - botImageGradient: { - width: 140, - height: 140, - borderRadius: 70, - justifyContent: "center", - alignItems: "center", - borderWidth: 2, - borderColor: NEW_COLORS.accent, + paddingVertical: 10, + backgroundColor: `${COLORS.primary}15`, + borderRadius: 20, + marginBottom: 32, + borderWidth: 1, + borderColor: `${COLORS.primary}30`, }, - botEmoji: { - fontSize: 80, + settingsText: { + fontSize: 13, + color: COLORS.primary, + fontWeight: "600", }, quickActions: { - flexDirection: "row", - justifyContent: "space-around", + width: "100%", gap: 12, + marginBottom: 32, }, - actionBtn: { - flex: 1, + quickActionCard: { borderRadius: 20, overflow: "hidden", - shadowColor: NEW_COLORS.accent, + shadowColor: COLORS.primary, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 6, + shadowRadius: 12, + elevation: 8, }, - actionBtnGradient: { - padding: 20, + quickActionGradient: { + padding: 24, alignItems: "center", justifyContent: "center", minHeight: 100, }, - actionIcon: { + quickActionEmoji: { fontSize: 36, marginBottom: 8, }, - actionText: { - fontSize: 14, + quickActionText: { + fontSize: 16, fontWeight: "700", color: "#000000", letterSpacing: 0.3, @@ -826,208 +819,218 @@ const styles = StyleSheet.create({ historyButton: { flexDirection: "row", alignItems: "center", - justifyContent: "center", gap: 8, - marginTop: 32, paddingVertical: 14, paddingHorizontal: 24, - backgroundColor: NEW_COLORS.card_bg, + backgroundColor: COLORS.surface, borderRadius: 24, borderWidth: 1, - borderColor: NEW_COLORS.separator, - shadowColor: "#000", - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 2, + borderColor: COLORS.border, }, historyButtonText: { fontSize: 15, fontWeight: "600", - color: NEW_COLORS.accent, + color: COLORS.primary, }, - loadingHistory: { + chatHeader: { flexDirection: "row", + justifyContent: "space-between", alignItems: "center", - justifyContent: "center", - padding: 12, - gap: 8, - }, - loadingHistoryText: { - fontSize: 14, - color: NEW_COLORS.text_secondary, + paddingHorizontal: 20, + paddingVertical: 16, + borderBottomWidth: 1, + borderBottomColor: COLORS.border, }, - chatHeader: { + chatHeaderLeft: { flexDirection: "row", - justifyContent: "space-between", alignItems: "center", - paddingBottom: 12, - marginBottom: 8, }, - currentSettingsInline: { + chatHeaderRight: { + flexDirection: "row", + gap: 12, + }, + settingsBadge: { flexDirection: "row", alignItems: "center", gap: 6, paddingHorizontal: 12, paddingVertical: 6, - backgroundColor: NEW_COLORS.card_bg, - borderRadius: 12, + backgroundColor: COLORS.surface, + borderRadius: 16, + borderWidth: 1, + borderColor: COLORS.border, }, - currentSettingsInlineText: { - fontSize: 11, - color: NEW_COLORS.text_secondary, + settingsBadgeText: { + fontSize: 12, + color: COLORS.textSecondary, + fontWeight: "500", }, - newChatButton: { - padding: 8, - backgroundColor: NEW_COLORS.card_bg, - borderRadius: 12, + headerIconButton: { + padding: 6, }, - settingsButtonSmall: { - padding: 8, - backgroundColor: NEW_COLORS.card_bg, - borderRadius: 12, + loadingContainer: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + padding: 16, + gap: 8, + }, + loadingText: { + fontSize: 14, + color: COLORS.textSecondary, }, messagesContainer: { - paddingBottom: 10, + paddingHorizontal: 20, + paddingTop: 20, }, - messageWrapper: { + messageRow: { flexDirection: "row", alignItems: "flex-end", - marginBottom: 16, - gap: 8, + marginBottom: 20, + gap: 10, }, - userMessageWrapper: { + userMessageRow: { justifyContent: "flex-end", }, - botMessageWrapper: { + botMessageRow: { justifyContent: "flex-start", }, botAvatar: { width: 36, height: 36, borderRadius: 18, - backgroundColor: NEW_COLORS.card_bg, + overflow: "hidden", + marginBottom: 4, + }, + botAvatarGradient: { + width: "100%", + height: "100%", justifyContent: "center", alignItems: "center", - borderWidth: 2, - borderColor: NEW_COLORS.accent, }, botAvatarEmoji: { - fontSize: 20, + fontSize: 18, }, userAvatar: { width: 36, height: 36, borderRadius: 18, - backgroundColor: NEW_COLORS.accent, + backgroundColor: `${COLORS.primary}20`, justifyContent: "center", alignItems: "center", + marginBottom: 4, + borderWidth: 2, + borderColor: `${COLORS.primary}40`, }, - message: { - maxWidth: "75%", - paddingHorizontal: 16, - paddingVertical: 12, + messageBubble: { + maxWidth: width * 0.75, + paddingHorizontal: 18, + paddingVertical: 14, borderRadius: 20, }, - userMessage: { + userBubble: { borderBottomRightRadius: 4, - shadowColor: NEW_COLORS.accent, - shadowOffset: { width: 0, height: 2 }, + shadowColor: COLORS.primary, + shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 4, + shadowRadius: 12, + elevation: 6, }, - botMessage: { - backgroundColor: NEW_COLORS.card_bg, + botBubble: { + backgroundColor: COLORS.surface, borderBottomLeftRadius: 4, borderWidth: 1, - borderColor: NEW_COLORS.separator, + borderColor: COLORS.border, shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 2, + shadowOpacity: 0.2, + shadowRadius: 8, + elevation: 3, }, - loadingMessage: { - paddingVertical: 16, + typingBubble: { + paddingVertical: 18, + paddingHorizontal: 20, }, userMessageText: { color: "#000000", - fontSize: 15, - lineHeight: 22, + fontSize: 16, + lineHeight: 24, fontWeight: "500", + letterSpacing: 0.2, }, botMessageText: { - color: NEW_COLORS.text, - fontSize: 15, - lineHeight: 22, + color: COLORS.text, + fontSize: 16, + lineHeight: 24, fontWeight: "400", + letterSpacing: 0.1, }, typingIndicator: { flexDirection: "row", alignItems: "center", - gap: 4, + gap: 6, }, - dot: { - width: 8, - height: 8, - borderRadius: 4, - backgroundColor: NEW_COLORS.text_secondary, + typingDot: { + width: 10, + height: 10, + borderRadius: 5, + backgroundColor: COLORS.primary, }, - dot1: { + typingDot1: { opacity: 0.4, }, - dot2: { - opacity: 0.6, + typingDot2: { + opacity: 0.7, }, - dot3: { - opacity: 0.8, + typingDot3: { + opacity: 1, }, - chatinputContainer: { - paddingHorizontal: 16, - paddingTop: 4, - backgroundColor: NEW_COLORS.background, + inputContainer: { + paddingHorizontal: 20, + paddingTop: 12, + backgroundColor: COLORS.background, borderTopWidth: 1, - borderTopColor: NEW_COLORS.separator, + borderTopColor: COLORS.border, }, inputWrapper: { flexDirection: "row", alignItems: "flex-end", - gap: 10, - backgroundColor: NEW_COLORS.card_bg, + gap: 12, + backgroundColor: COLORS.surface, borderRadius: 28, - paddingHorizontal: 4, - paddingVertical: 4, - borderWidth: 1, - borderColor: NEW_COLORS.separator, + paddingHorizontal: 6, + paddingVertical: 6, + borderWidth: 1.5, + borderColor: COLORS.border, }, - messageInput: { + input: { flex: 1, backgroundColor: "transparent", - borderRadius: 24, + borderRadius: 22, paddingHorizontal: 18, paddingVertical: 12, - fontSize: 15, - color: NEW_COLORS.text, - maxHeight: 100, + fontSize: 16, + color: COLORS.text, + maxHeight: 120, minHeight: 44, + lineHeight: 22, }, - sendBtn: { + sendButton: { width: 44, height: 44, borderRadius: 22, - backgroundColor: NEW_COLORS.accent, + backgroundColor: COLORS.primary, justifyContent: "center", alignItems: "center", - shadowColor: NEW_COLORS.accent, - shadowOffset: { width: 0, height: 2 }, + shadowColor: COLORS.primary, + shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.4, - shadowRadius: 6, - elevation: 5, + shadowRadius: 8, + elevation: 6, }, - sendBtnDisabled: { - backgroundColor: NEW_COLORS.separator, - opacity: 0.5, + sendButtonDisabled: { + backgroundColor: COLORS.border, + opacity: 0.4, shadowOpacity: 0, elevation: 0, }, diff --git a/src/screens/diet/DietScreen.tsx b/src/screens/diet/DietScreen.tsx index 74d9dd5..f4b0d28 100644 --- a/src/screens/diet/DietScreen.tsx +++ b/src/screens/diet/DietScreen.tsx @@ -15,6 +15,7 @@ import {colors} from '../../theme/colors'; import {useDate} from '../../contexts/DateContext'; import {mealAPI, recommendedMealAPI, homeAPI} from '../../services'; import {useFocusEffect} from '@react-navigation/native'; +import {eventBus} from '../../utils/eventBus'; // 진행률 API 호출 제거 // import {fetchWeeklyProgress, fetchMonthlyProgress} from '../../utils/exerciseApi'; import type {DailyMealsResponse, DailyMeal, NutritionGoal, DailyProgressWeekItem, AddMealRequest} from '../../types'; @@ -458,9 +459,47 @@ const DietScreen = ({navigation, route}: any) => { await mealAPI.deleteMeal(mealId); Alert.alert('성공', '식사가 삭제되었습니다.'); + const dateStr = formatDateToString(dateToFetch); + // 삭제 후 데이터 새로고침 await fetchDailyMeals(dateToFetch); await loadRecommendedMealsForDate(dateToFetch); + + // 달력 칼로리 데이터 새로고침 + if (showMonthView) { + // 월간 달력인 경우 해당 월의 모든 날짜 + const year = monthBase.getFullYear(); + const month = monthBase.getMonth() + 1; + const firstOfMonth = new Date(year, month - 1, 1); + const nextMonth = new Date(year, month, 1); + const daysInMonth = Math.round((nextMonth.getTime() - firstOfMonth.getTime()) / (1000 * 60 * 60 * 24)); + const monthDates = Array.from({ length: daysInMonth }).map((_, i) => { + const d = new Date(year, month - 1, i + 1); + return formatDateToString(d); + }); + await loadCalendarCalories(monthDates); + } else { + // 주간 달력인 경우 이번 주 7일 + const getStartOfWeek = (d: Date) => { + const n = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + const diff = n.getDay(); + n.setDate(n.getDate() - diff); + return n; + }; + const startOfWeek = getStartOfWeek(dateToFetch); + const weekDates = Array.from({ length: 7 }).map((_, i) => { + const d = new Date( + startOfWeek.getFullYear(), + startOfWeek.getMonth(), + startOfWeek.getDate() + i + ); + return formatDateToString(d); + }); + await loadCalendarCalories(weekDates); + } + + // eventBus로 식사 삭제 이벤트 발생 (캘린더와 홈 화면 새로고침용) + eventBus.emit('mealDeleted', { date: dateStr, mealId }); } catch (error: any) { console.error('식사 삭제 실패:', error); let errorMessage = '식사 삭제에 실패했습니다.'; diff --git a/src/screens/exercise/ExerciseScreen.tsx b/src/screens/exercise/ExerciseScreen.tsx index 8818f7c..23b6446 100644 --- a/src/screens/exercise/ExerciseScreen.tsx +++ b/src/screens/exercise/ExerciseScreen.tsx @@ -2125,6 +2125,16 @@ const ExerciseScreen = ({ navigation }: any) => { loadGoalData(); loadTodayProgress(); // 오늘 진행률 로드 (게이지 업데이트) + // eventBus로 운동 저장 이벤트 발생 (캘린더 새로고침용) + const workoutDate = selectedDate + ? `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}` + : null; + eventBus.emit("workoutSessionSaved", { + sessionId: response.sessionId, + exerciseName: trimmedTitle, + workoutDate: workoutDate, + }); + // 운동 제목 저장 후 주간 진행률을 다시 가져와서 게이지 업데이트 // 서버에서 exerciseRate 계산에 시간이 걸릴 수 있으므로 여러 번 재시도 const retryLoadProgress = async ( @@ -3579,6 +3589,12 @@ const ExerciseScreen = ({ navigation }: any) => { style: "destructive", onPress: async () => { try { + // 삭제할 운동 정보 저장 (이벤트 발생용) + const deletedActivity = allActivities.find((activity) => activity.id === workoutId); + const workoutDate = selectedDate + ? `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}` + : null; + // 서버 API 호출 (sessionId가 있으면) if (sessionId) { await deleteWorkoutSession(sessionId); @@ -3589,6 +3605,13 @@ const ExerciseScreen = ({ navigation }: any) => { setAllActivities((prev) => prev.filter((activity) => activity.id !== workoutId) ); + + // eventBus로 운동 삭제 이벤트 발생 (캘린더 새로고침용) + eventBus.emit("workoutSessionDeleted", { + sessionId: sessionId || null, + exerciseName: deletedActivity?.title || null, + workoutDate: workoutDate, + }); } catch (e) { console.error("[WORKOUT][DELETE] 삭제 실패:", e); Alert.alert("오류", "운동 삭제 중 오류가 발생했습니다."); diff --git a/src/screens/main/HomeScreen.tsx b/src/screens/main/HomeScreen.tsx index 00d1d97..64b8b96 100644 --- a/src/screens/main/HomeScreen.tsx +++ b/src/screens/main/HomeScreen.tsx @@ -737,7 +737,10 @@ const HomeScreen = ({ navigation }: any) => { try { // 현재 시간 확인 (한국 시간대) const now = new Date(); - const koreaTime = new Date(now.toLocaleString("en-US", { timeZone: "Asia/Seoul" })); + // 한국 시간대로 변환 (UTC+9) + const koreaTimeOffset = 9 * 60; // 한국은 UTC+9 + const utcTime = now.getTime() + (now.getTimezoneOffset() * 60 * 1000); + const koreaTime = new Date(utcTime + (koreaTimeOffset * 60 * 1000)); const hours = koreaTime.getHours(); const minutes = koreaTime.getMinutes(); const currentTime = hours * 60 + minutes; // 분 단위로 변환 @@ -748,25 +751,29 @@ const HomeScreen = ({ navigation }: any) => { let targetMealType: "BREAKFAST" | "LUNCH" | "DINNER" | null = null; let mealTypeName = ""; - // 00:00 - 4:00: 아침 - if (currentTime >= 0 && currentTime < 4 * 60) { + // 00:00 - 10:00: 아침 + if (currentTime >= 0 && currentTime < 10 * 60) { targetMealType = "BREAKFAST"; mealTypeName = "아침"; + console.log('[HOME] 시간대 판단: 아침 (0 <=', currentTime, '< 600)'); } - // 4:00 - 18:00: 점심 - else if (currentTime >= 4 * 60 && currentTime < 18 * 60) { + // 10:00 - 18:00: 점심 + else if (currentTime >= 10 * 60 && currentTime < 18 * 60) { targetMealType = "LUNCH"; mealTypeName = "점심"; + console.log('[HOME] 시간대 판단: 점심 (600 <=', currentTime, '< 1080)'); } // 18:00 - 24:00: 저녁 else if (currentTime >= 18 * 60 && currentTime < 24 * 60) { targetMealType = "DINNER"; mealTypeName = "저녁"; + console.log('[HOME] 시간대 판단: 저녁 (1080 <=', currentTime, '< 1440)'); } - // 00:00 - 4:00 범위가 아니면 아침 (새벽 시간대) + // 예외 처리 (현재는 발생하지 않지만 안전을 위해) else { targetMealType = "BREAKFAST"; mealTypeName = "아침"; + console.log('[HOME] 시간대 판단: 예외 처리 - 아침으로 설정 (currentTime:', currentTime, ')'); } const today = new Date(); @@ -1008,6 +1015,21 @@ const HomeScreen = ({ navigation }: any) => { }; }, []); + // 식사 삭제 이벤트 리스너 + useEffect(() => { + const unsubscribe = eventBus.on("mealDeleted", () => { + console.log("[HOME] 식사 삭제 이벤트 수신, 식단 데이터 새로고침"); + // 식단 추천 데이터 새로고침 + loadMealRecommendation(); + // 주간 진행률 데이터 새로고침 (캘린더 칼로리 업데이트) + loadWeeklyProgress(); + }); + + return () => { + unsubscribe?.(); + }; + }, []); + // 위젯 편집 화면에서 돌아올 때 위젯 순서 다시 불러오기 useEffect(() => { const unsubscribe = navigation.addListener("focus", () => { diff --git a/src/utils/eventBus.ts b/src/utils/eventBus.ts index d5ed160..3f0285b 100644 --- a/src/utils/eventBus.ts +++ b/src/utils/eventBus.ts @@ -4,9 +4,18 @@ type EventMap = { exerciseName?: string | null; workoutDate?: string | null; }; + workoutSessionSaved: { + sessionId?: string | number | null; + exerciseName?: string | null; + workoutDate?: string | null; + }; inbodyUpdated: { measurementDate?: string | null; }; + mealDeleted: { + date?: string | null; + mealId?: number | null; + }; }; type EventKey = keyof EventMap; From a9c07e038d6e7c2cc7a03a1f8c8f5c494268d5ad Mon Sep 17 00:00:00 2001 From: a06246 Date: Mon, 15 Dec 2025 15:17:31 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=B1=97=EB=B4=87=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/chatbot/ChatbotScreen.tsx | 786 ++++++++++---------------- 1 file changed, 305 insertions(+), 481 deletions(-) diff --git a/src/screens/chatbot/ChatbotScreen.tsx b/src/screens/chatbot/ChatbotScreen.tsx index 5e92ad5..86b0fd8 100644 --- a/src/screens/chatbot/ChatbotScreen.tsx +++ b/src/screens/chatbot/ChatbotScreen.tsx @@ -1,5 +1,5 @@ // src/screens/chatbot/ChatbotScreen.tsx -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect } from "react"; import { View, Text, @@ -11,14 +11,11 @@ import { Platform, Alert, ActivityIndicator, - Animated, - Dimensions, } from "react-native"; import { SafeAreaView, useSafeAreaInsets, } from "react-native-safe-area-context"; -import { LinearGradient } from "expo-linear-gradient"; import { Ionicons as Icon } from "@expo/vector-icons"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { chatAPI, ChatHistoryItem } from "../../services/chatAPI"; @@ -30,15 +27,11 @@ interface Message { text: string; } -const { width } = Dimensions.get("window"); - const ChatbotScreen = ({ navigation }: any) => { const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); const [isLoading, setIsLoading] = useState(false); const [userId, setUserId] = useState(null); - const scrollViewRef = useRef(null); - const fadeAnim = useRef(new Animated.Value(0)).current; const insets = useSafeAreaInsets(); @@ -63,13 +56,6 @@ const ChatbotScreen = ({ navigation }: any) => { loadSettings(); loadUserId(); loadMembershipInfo(); - - // 페이드 인 애니메이션 - Animated.timing(fadeAnim, { - toValue: 1, - duration: 500, - useNativeDriver: true, - }).start(); }, []); // ✅ 챗봇 히스토리 로드 @@ -226,11 +212,6 @@ const ChatbotScreen = ({ navigation }: any) => { setMessages((prev) => [...prev, { type: "user", text: userMessage }]); setIsLoading(true); - // 메시지 추가 후 스크롤 - setTimeout(() => { - scrollViewRef.current?.scrollToEnd({ animated: true }); - }, 100); - try { const botResponse = await chatAPI.sendMessage( userId, @@ -240,11 +221,6 @@ const ChatbotScreen = ({ navigation }: any) => { ); setMessages((prev) => [...prev, { type: "bot", text: botResponse }]); - - // 봇 응답 후 스크롤 - setTimeout(() => { - scrollViewRef.current?.scrollToEnd({ animated: true }); - }, 100); await loadMembershipInfo(); } catch (error: any) { @@ -273,6 +249,15 @@ const ChatbotScreen = ({ navigation }: any) => { } setMessages((prev) => [...prev, { type: "bot", text: errorMessage }]); + + if ( + error.message?.includes("로그인") || + error.message?.includes("인증") + ) { + setTimeout(() => { + navigation.replace("Login"); + }, 1500); + } } finally { setIsLoading(false); } @@ -374,22 +359,28 @@ const ChatbotScreen = ({ navigation }: any) => { } }; + const renderPremiumBadge = () => { + if (membershipType === "PREMIUM") { + return ( + + + 프리미엄 무제한 + + ); + } + return null; + }; + return ( {!isInTab && ( - navigation.goBack()} - style={styles.headerButton} - > - + navigation.goBack()}> + - AI 코치 - setIsSettingsModalOpen(true)} - style={styles.headerButton} - > - + AI 챗봇 + setIsSettingsModalOpen(true)}> + )} @@ -399,93 +390,76 @@ const ChatbotScreen = ({ navigation }: any) => { behavior={Platform.OS === "ios" ? "padding" : "height"} keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 0} > - - { - scrollViewRef.current?.scrollToEnd({ animated: true }); - }} - > + {messages.length === 0 && !showHistory ? ( - - - + + + 안녕하세요! + 어떻게 도와드릴까요? + + + setIsSettingsModalOpen(true)} > - 🤖 - + + 채팅 설정 + - - 안녕하세요! - - AI 코치가 도와드리겠습니다 - - - {membershipType === "PREMIUM" && ( - - - 프리미엄 무제한 + + + {renderPremiumBadge()} + + + + {getModeText()} · {getStyleText()} + - )} + - - - - {getModeText()} · {getStyleText()} - + + 🤖 handleQuickSelect("exercise")} - activeOpacity={0.7} > - - 💪 - 운동 추천 - + 🏋️ + 운동 추천 handleQuickSelect("food")} - activeOpacity={0.7} > - - 🍎 - 식단 추천 - + 🍗 + 식단 추천 handleQuickSelect("plan")} - activeOpacity={0.7} > - - 📋 - 계획 수립 - + 📅 + 계획 수립 @@ -494,49 +468,60 @@ const ChatbotScreen = ({ navigation }: any) => { onPress={handleShowHistory} disabled={isLoadingHistory} > - + {isLoadingHistory ? "불러오는 중..." : "이전 대화 보기"} - + ) : ( <> - - {membershipType === "PREMIUM" && ( - - - - )} - - - - {getModeText()} · {getStyleText()} - - - - - - - - - setIsSettingsModalOpen(true)} - > - - + {renderPremiumBadge()} + + + + {getModeText()} · {getStyleText()} + + + + + + + setIsSettingsModalOpen(true)} + > + + {isLoadingHistory && ( - - - 대화 기록 불러오는 중... + + + + 대화 기록 불러오는 중... + )} @@ -545,103 +530,51 @@ const ChatbotScreen = ({ navigation }: any) => { - {msg.type === "bot" && ( - - - 🤖 - - - )} - - {msg.type === "user" ? ( - - {msg.text} - - ) : ( - - {msg.text} - - )} - - {msg.type === "user" && ( - - - - )} + + {msg.text} + ))} - {isLoading && ( - - - - 🤖 - - - - - - - - - + + ... )} )} - - + + 0 ? Math.max(insets.bottom, 8) : 8) - }, + styles.chatinputContainer, + { paddingBottom: insets.bottom > 0 ? Math.max(insets.bottom, 4) : 4 }, ]} > - - - - {isLoading ? ( - - ) : ( - - )} - - + + + + @@ -656,383 +589,274 @@ const ChatbotScreen = ({ navigation }: any) => { ); }; -const COLORS = { - background: "#0a0a0a", - surface: "#151515", - surfaceLight: "#1f1f1f", - text: "#ffffff", - textSecondary: "#888888", - primary: "#e3ff7c", - primaryDark: "#d4f05a", - border: "#2a2a2a", - premium: "#FFD700", +const NEW_COLORS = { + background: "#1a1a1a", + text: "#f0f0f0", + text_secondary: "#a0a0a0", + accent: "#e3ff7c", + card_bg: "#252525", + separator: "#3a3a3a", + delete_color: "#ff6b6b", }; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: COLORS.background, + backgroundColor: NEW_COLORS.background, }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", - paddingHorizontal: 20, paddingVertical: 16, + paddingHorizontal: 20, borderBottomWidth: 1, - borderBottomColor: COLORS.border, + borderBottomColor: NEW_COLORS.separator, }, - headerButton: { - padding: 4, + backIcon: { + fontSize: 24, + color: NEW_COLORS.text, + fontWeight: "bold", }, headerTitle: { - fontSize: 20, - fontWeight: "700", - color: COLORS.text, - letterSpacing: -0.5, + fontSize: 18, + fontWeight: "600", + color: NEW_COLORS.text, }, keyboardView: { flex: 1, }, - content: { - flex: 1, - }, scrollView: { flex: 1, }, scrollViewContent: { flexGrow: 1, - paddingBottom: 20, + paddingBottom: 10, }, - emptyState: { + mainContent: { flex: 1, - alignItems: "center", - justifyContent: "center", - paddingHorizontal: 24, - paddingTop: 40, - paddingBottom: 20, + padding: 20, }, - avatarContainer: { - marginBottom: 32, - }, - avatarGradient: { - width: 120, - height: 120, - borderRadius: 60, - justifyContent: "center", + welcomeHeader: { alignItems: "center", - shadowColor: COLORS.primary, - shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.4, - shadowRadius: 20, - elevation: 10, + marginTop: 20, }, - avatarEmoji: { - fontSize: 60, + welcomeSection: { + alignItems: "center", }, - greetingTitle: { + title: { fontSize: 32, - fontWeight: "800", - color: COLORS.text, + fontWeight: "bold", + color: NEW_COLORS.text, marginBottom: 8, - letterSpacing: -1, }, - greetingSubtitle: { - fontSize: 16, - color: COLORS.textSecondary, - marginBottom: 24, - fontWeight: "400", + subtitle: { + fontSize: 18, + color: NEW_COLORS.text_secondary, }, - premiumBadge: { + settingsButton: { flexDirection: "row", alignItems: "center", - gap: 6, - paddingHorizontal: 14, - paddingVertical: 8, - backgroundColor: `${COLORS.premium}15`, + gap: 8, + marginTop: 16, + paddingHorizontal: 20, + paddingVertical: 10, + backgroundColor: NEW_COLORS.card_bg, borderRadius: 20, - marginBottom: 16, borderWidth: 1, - borderColor: `${COLORS.premium}30`, + borderColor: NEW_COLORS.accent, }, - premiumText: { - fontSize: 13, - color: COLORS.premium, + settingsButtonText: { + fontSize: 14, fontWeight: "600", + color: NEW_COLORS.accent, }, - premiumBadgeSmall: { - width: 24, - height: 24, - borderRadius: 12, - backgroundColor: `${COLORS.premium}20`, + badgeContainer: { + flexDirection: "row", justifyContent: "center", alignItems: "center", - marginRight: 8, + flexWrap: "wrap", + gap: 12, + marginTop: 16, }, - settingsInfo: { + premiumBadge: { flexDirection: "row", alignItems: "center", gap: 6, - paddingHorizontal: 16, - paddingVertical: 10, - backgroundColor: `${COLORS.primary}15`, - borderRadius: 20, - marginBottom: 32, - borderWidth: 1, - borderColor: `${COLORS.primary}30`, + paddingHorizontal: 12, + paddingVertical: 6, + backgroundColor: "#FFD70020", + borderRadius: 12, }, - settingsText: { - fontSize: 13, - color: COLORS.primary, + premiumBadgeText: { + fontSize: 12, + color: "#FFD700", fontWeight: "600", }, + currentSettingsBadge: { + flexDirection: "row", + alignItems: "center", + gap: 6, + paddingHorizontal: 16, + paddingVertical: 8, + backgroundColor: `${NEW_COLORS.accent}20`, + borderRadius: 16, + }, + currentSettingsBadgeText: { + fontSize: 12, + color: NEW_COLORS.accent, + fontWeight: "500", + }, + botImageContainer: { + alignItems: "center", + marginVertical: 40, + }, + botEmoji: { + fontSize: 120, + }, quickActions: { - width: "100%", + flexDirection: "row", + justifyContent: "space-around", gap: 12, - marginBottom: 32, }, - quickActionCard: { + actionBtn: { + flex: 1, + backgroundColor: NEW_COLORS.card_bg, + padding: 20, borderRadius: 20, - overflow: "hidden", - shadowColor: COLORS.primary, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 12, - elevation: 8, - }, - quickActionGradient: { - padding: 24, alignItems: "center", - justifyContent: "center", - minHeight: 100, + shadowColor: "#000000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, }, - quickActionEmoji: { - fontSize: 36, + actionIcon: { + fontSize: 32, marginBottom: 8, }, - quickActionText: { - fontSize: 16, - fontWeight: "700", - color: "#000000", - letterSpacing: 0.3, + actionText: { + fontSize: 14, + fontWeight: "600", + color: NEW_COLORS.text, }, historyButton: { flexDirection: "row", alignItems: "center", + justifyContent: "center", gap: 8, + marginTop: 32, paddingVertical: 14, paddingHorizontal: 24, - backgroundColor: COLORS.surface, + backgroundColor: NEW_COLORS.card_bg, borderRadius: 24, borderWidth: 1, - borderColor: COLORS.border, + borderColor: NEW_COLORS.separator, }, historyButtonText: { fontSize: 15, fontWeight: "600", - color: COLORS.primary, + color: NEW_COLORS.accent, }, - chatHeader: { + loadingHistory: { flexDirection: "row", - justifyContent: "space-between", alignItems: "center", - paddingHorizontal: 20, - paddingVertical: 16, - borderBottomWidth: 1, - borderBottomColor: COLORS.border, + justifyContent: "center", + padding: 12, + gap: 8, }, - chatHeaderLeft: { - flexDirection: "row", - alignItems: "center", + loadingHistoryText: { + fontSize: 14, + color: NEW_COLORS.text_secondary, }, - chatHeaderRight: { + chatHeader: { flexDirection: "row", - gap: 12, + justifyContent: "space-between", + alignItems: "center", + paddingBottom: 12, + marginBottom: 8, }, - settingsBadge: { + currentSettingsInline: { flexDirection: "row", alignItems: "center", gap: 6, paddingHorizontal: 12, paddingVertical: 6, - backgroundColor: COLORS.surface, - borderRadius: 16, - borderWidth: 1, - borderColor: COLORS.border, - }, - settingsBadgeText: { - fontSize: 12, - color: COLORS.textSecondary, - fontWeight: "500", + backgroundColor: NEW_COLORS.card_bg, + borderRadius: 12, }, - headerIconButton: { - padding: 6, + currentSettingsInlineText: { + fontSize: 11, + color: NEW_COLORS.text_secondary, }, - loadingContainer: { - flexDirection: "row", - alignItems: "center", - justifyContent: "center", - padding: 16, - gap: 8, + newChatButton: { + padding: 8, + backgroundColor: NEW_COLORS.card_bg, + borderRadius: 12, }, - loadingText: { - fontSize: 14, - color: COLORS.textSecondary, + settingsButtonSmall: { + padding: 8, + backgroundColor: NEW_COLORS.card_bg, + borderRadius: 12, }, messagesContainer: { - paddingHorizontal: 20, - paddingTop: 20, - }, - messageRow: { - flexDirection: "row", - alignItems: "flex-end", - marginBottom: 20, - gap: 10, - }, - userMessageRow: { - justifyContent: "flex-end", - }, - botMessageRow: { - justifyContent: "flex-start", - }, - botAvatar: { - width: 36, - height: 36, - borderRadius: 18, - overflow: "hidden", - marginBottom: 4, - }, - botAvatarGradient: { - width: "100%", - height: "100%", - justifyContent: "center", - alignItems: "center", + paddingBottom: 10, }, - botAvatarEmoji: { - fontSize: 18, - }, - userAvatar: { - width: 36, - height: 36, - borderRadius: 18, - backgroundColor: `${COLORS.primary}20`, - justifyContent: "center", - alignItems: "center", - marginBottom: 4, - borderWidth: 2, - borderColor: `${COLORS.primary}40`, - }, - messageBubble: { - maxWidth: width * 0.75, - paddingHorizontal: 18, - paddingVertical: 14, + message: { + maxWidth: "80%", + padding: 12, borderRadius: 20, + marginBottom: 12, }, - userBubble: { - borderBottomRightRadius: 4, - shadowColor: COLORS.primary, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 12, - elevation: 6, - }, - botBubble: { - backgroundColor: COLORS.surface, - borderBottomLeftRadius: 4, - borderWidth: 1, - borderColor: COLORS.border, - shadowColor: "#000", - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.2, - shadowRadius: 8, - elevation: 3, + userMessage: { + alignSelf: "flex-end", + backgroundColor: NEW_COLORS.accent, }, - typingBubble: { - paddingVertical: 18, - paddingHorizontal: 20, + botMessage: { + alignSelf: "flex-start", + backgroundColor: NEW_COLORS.card_bg, }, userMessageText: { color: "#000000", fontSize: 16, - lineHeight: 24, - fontWeight: "500", - letterSpacing: 0.2, }, botMessageText: { - color: COLORS.text, + color: NEW_COLORS.text, fontSize: 16, - lineHeight: 24, - fontWeight: "400", - letterSpacing: 0.1, - }, - typingIndicator: { - flexDirection: "row", - alignItems: "center", - gap: 6, }, - typingDot: { - width: 10, - height: 10, - borderRadius: 5, - backgroundColor: COLORS.primary, - }, - typingDot1: { - opacity: 0.4, - }, - typingDot2: { - opacity: 0.7, - }, - typingDot3: { - opacity: 1, - }, - inputContainer: { - paddingHorizontal: 20, - paddingTop: 12, - backgroundColor: COLORS.background, - borderTopWidth: 1, - borderTopColor: COLORS.border, + loadingText: { + color: NEW_COLORS.text_secondary, + fontSize: 16, }, - inputWrapper: { + chatinputContainer: { flexDirection: "row", - alignItems: "flex-end", + paddingHorizontal: 10, + paddingTop: 8, + backgroundColor: NEW_COLORS.card_bg, + borderTopWidth: 1, + borderTopColor: NEW_COLORS.separator, gap: 12, - backgroundColor: COLORS.surface, - borderRadius: 28, - paddingHorizontal: 6, - paddingVertical: 6, - borderWidth: 1.5, - borderColor: COLORS.border, }, - input: { + messageInput: { flex: 1, - backgroundColor: "transparent", - borderRadius: 22, - paddingHorizontal: 18, + backgroundColor: NEW_COLORS.background, + borderRadius: 24, + paddingHorizontal: 20, paddingVertical: 12, fontSize: 16, - color: COLORS.text, - maxHeight: 120, - minHeight: 44, - lineHeight: 22, + color: NEW_COLORS.text, }, - sendButton: { - width: 44, - height: 44, - borderRadius: 22, - backgroundColor: COLORS.primary, + sendBtn: { + width: 48, + height: 48, + borderRadius: 24, + backgroundColor: NEW_COLORS.accent, justifyContent: "center", alignItems: "center", - shadowColor: COLORS.primary, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.4, - shadowRadius: 8, - elevation: 6, }, - sendButtonDisabled: { - backgroundColor: COLORS.border, - opacity: 0.4, - shadowOpacity: 0, - elevation: 0, + sendIcon: { + color: "#000000", + fontSize: 20, }, });