Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 80 additions & 78 deletions src/components/modals/ExerciseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
KeyboardAvoidingView,
Platform,
Alert,
SafeAreaView,
} from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Ionicons as Icon } from "@expo/vector-icons";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { colors } from "../../theme/colors";
Expand Down Expand Up @@ -118,6 +118,7 @@ const ExerciseModal: React.FC<ExerciseModalProps> = ({
renderContentOnly = false,
isCompleted = false,
}) => {
const insets = useSafeAreaInsets();
const [searchTerm, setSearchTerm] = useState("");
const [selectedCategory, setSelectedCategory] = useState("전체");
const [currentMode, setCurrentMode] = useState<"add" | "edit" | "detail">(
Expand Down Expand Up @@ -1449,7 +1450,7 @@ const getExerciseDisplayName = React.useCallback(
: [];

const content = fullScreen ? (
<SafeAreaView style={styles.fullScreenContainer}>
<SafeAreaView style={styles.fullScreenContainer} edges={["top"]}>
<View style={styles.fullScreenContent}>
{currentMode === "add" ? (
<KeyboardAvoidingView
Expand Down Expand Up @@ -2099,79 +2100,9 @@ const getExerciseDisplayName = React.useCallback(
styles.footer,
hasSequenceControls && styles.footerExtended,
allSetsCompleted && !isCompleted && styles.footerWithFeedback,
{ paddingBottom: Math.max(insets.bottom, 34) },
]}
>
{hasSequenceControls && (
<View style={styles.sequenceControlRow}>
<TouchableOpacity
style={[
styles.sequenceControlButton,
!hasPrevSequence && styles.sequenceControlButtonDisabled,
]}
onPress={() => handleSequenceNavigatePress("prev")}
disabled={!hasPrevSequence}
>
<Text
style={[
styles.sequenceControlText,
!hasPrevSequence && styles.sequenceControlTextDisabled,
]}
>
이전 운동
</Text>
</TouchableOpacity>
{!isCompleted && (
<TouchableOpacity
style={[styles.sequenceControlButton, styles.sequenceTimerButton]}
onPress={toggleWorkoutTimer}
>
<Text style={styles.sequenceTimerLabel}>타이머</Text>
<Text
style={[
styles.sequenceTimerValue,
!isWorkoutTimerRunning && styles.sequenceTimerValuePaused,
]}
>
{formatWorkoutTimer(workoutTimerSeconds)}
</Text>
</TouchableOpacity>
)}
<TouchableOpacity
style={[
styles.sequenceControlButton,
!canNextExercise && styles.sequenceControlButtonDisabled,
]}
onPress={() => handleSequenceNavigatePress("next")}
disabled={!canNextExercise}
>
<Text
style={[
styles.sequenceControlText,
!canNextExercise && styles.sequenceControlTextDisabled,
]}
>
다음 운동
</Text>
</TouchableOpacity>
</View>
)}
<TouchableOpacity
style={[
styles.endWorkoutBtn,
!canFinishWorkout && styles.endWorkoutBtnDisabled,
]}
onPress={handleSave}
disabled={!canFinishWorkout}
>
<Text
style={[
styles.endWorkoutBtnText,
!canFinishWorkout && styles.endWorkoutBtnTextDisabled,
]}
>
운동 끝내기
</Text>
</TouchableOpacity>
{allSetsCompleted && !isCompleted && (() => {
const currentExerciseName = getExerciseDisplayName(
selectedExercise || exerciseData || { name: "" }
Expand Down Expand Up @@ -2305,6 +2236,77 @@ const getExerciseDisplayName = React.useCallback(
</View>
);
})()}
{hasSequenceControls && (
<View style={styles.sequenceControlRow}>
<TouchableOpacity
style={[
styles.sequenceControlButton,
!hasPrevSequence && styles.sequenceControlButtonDisabled,
]}
onPress={() => handleSequenceNavigatePress("prev")}
disabled={!hasPrevSequence}
>
<Text
style={[
styles.sequenceControlText,
!hasPrevSequence && styles.sequenceControlTextDisabled,
]}
>
이전 운동
</Text>
</TouchableOpacity>
{!isCompleted && (
<TouchableOpacity
style={[styles.sequenceControlButton, styles.sequenceTimerButton]}
onPress={toggleWorkoutTimer}
>
<Text style={styles.sequenceTimerLabel}>타이머</Text>
<Text
style={[
styles.sequenceTimerValue,
!isWorkoutTimerRunning && styles.sequenceTimerValuePaused,
]}
>
{formatWorkoutTimer(workoutTimerSeconds)}
</Text>
</TouchableOpacity>
)}
<TouchableOpacity
style={[
styles.sequenceControlButton,
!canNextExercise && styles.sequenceControlButtonDisabled,
]}
onPress={() => handleSequenceNavigatePress("next")}
disabled={!canNextExercise}
>
<Text
style={[
styles.sequenceControlText,
!canNextExercise && styles.sequenceControlTextDisabled,
]}
>
다음 운동
</Text>
</TouchableOpacity>
</View>
)}
<TouchableOpacity
style={[
styles.endWorkoutBtn,
!canFinishWorkout && styles.endWorkoutBtnDisabled,
]}
onPress={handleSave}
disabled={!canFinishWorkout}
>
<Text
style={[
styles.endWorkoutBtnText,
!canFinishWorkout && styles.endWorkoutBtnTextDisabled,
]}
>
운동 끝내기
</Text>
</TouchableOpacity>
</View>
</View>
)}
Expand Down Expand Up @@ -3219,9 +3221,9 @@ const styles = StyleSheet.create({
elevation: 3,
},
feedbackSection: {
marginTop: 12,
marginTop: 0,
marginHorizontal: 0,
marginBottom: 0,
marginBottom: 12,
},
feedbackTitle: {
fontSize: 18,
Expand Down Expand Up @@ -3827,16 +3829,16 @@ const styles = StyleSheet.create({
},
footer: {
paddingHorizontal: 20,
paddingBottom: 8,
paddingTop: 8,
paddingBottom: 0,
paddingTop: 16,
backgroundColor: "#2a2a2a",
},
footerExtended: {
flexDirection: "column",
gap: 6,
},
footerWithFeedback: {
paddingBottom: 16,
paddingBottom: 0,
},
saveExerciseBtn: {
backgroundColor: "#e3ff7c",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 수정을 검토한 결과, 몇 가지 잠재적인 버그 및 개선점이 발견되었습니다.

  1. 안전 영역 인셋: useSafeAreaInsets를 추가함으로써 paddingBottom에 동적으로 값을 적용하는 것은 좋은 접근입니다. 그러나 이 값이 paddingBottom: Math.max(insets.bottom, 34)로 설정되어 있어, 경우에 따라 34가 항상 적용될 수 있습니다. 즉, 괜찮은 안전 영역 설정이 있는 상황에서도 불필요하게 많은 여백이 생길 수 있습니다. 이를 위해 조건부 로직을 추가하여 모바일 기기에서 더 나은 사용자 경험을 제공하면 좋겠습니다.

  2. 스타일 변경의 영향: footerfeedbackSection의 패딩 크기를 변경했는데, 이는 전반적인 디자인에 영향을 줄 수 있습니다. 변경된 디자인이 예상되는 사용자 인터페이스에 적절한지 확인할 필요가 있습니다. 디자이너와의 협업이 필요합니다.

  3. 불필요한 코드 주석: 수정된 코드 블록에서 큰 코드 블록(주석 처리된 이전 코드)의 주석을 제거하는 것이 좋습니다. 버전 히스토리에서 추적할 수 있는 위해 불필요한 주석은 코드 가독성을 해칠 수 있습니다.

  4. 메모리 누수 위험: React.useCallback으로 감싸진 getExerciseDisplayName이 제대로 종속성을 관리하는지 확인이 필요합니다. 이 함수의 종속성이 변경되었을 때 내부 구현이 호출되지 않는다면, 메모리 누수나 잘못된 동작을 초래할 수 있습니다.

이러한 사항을 고려해주시기 바랍니다.

Expand Down
25 changes: 20 additions & 5 deletions src/screens/analysis/AnalysisScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,7 @@ const AnalysisScreen = ({ navigation }: any) => {
// 랜덤 코멘트 로드
const loadRandomScoreComment = useCallback(async () => {
try {
console.log("[ANALYSIS][COMMENT] 코멘트 로드 시작");
setScoreCommentLoading(true);
setScoreComment(null);

Expand All @@ -1907,21 +1908,35 @@ const AnalysisScreen = ({ navigation }: any) => {
];
const randomIndex = Math.floor(Math.random() * commentTypes.length);
const selectedCommentAPI = commentTypes[randomIndex];
const apiType = randomIndex === 0 ? "daily" : randomIndex === 1 ? "weekly" : "monthly";

console.log("[ANALYSIS] 코멘트 API 선택:", randomIndex === 0 ? "daily" : randomIndex === 1 ? "weekly" : "monthly");
console.log("[ANALYSIS][COMMENT] 코멘트 API 선택:", apiType, "index:", randomIndex);

const comment = await selectedCommentAPI();
console.log("[ANALYSIS][COMMENT] API 호출 완료, 반환된 comment:", comment);
console.log("[ANALYSIS][COMMENT] comment 타입:", typeof comment);
console.log("[ANALYSIS][COMMENT] comment가 truthy인가?", !!comment);
console.log("[ANALYSIS][COMMENT] comment가 문자열인가?", typeof comment === 'string');
console.log("[ANALYSIS][COMMENT] comment 길이:", comment ? (typeof comment === 'string' ? comment.length : 'N/A') : 0);

if (comment) {
setScoreComment(comment);
console.log("[ANALYSIS] 코멘트 로드 성공:", comment);
console.log("[ANALYSIS][COMMENT] 코멘트 로드 성공, state에 설정:", comment);
} else {
console.log("[ANALYSIS] 코멘트 없음");
console.log("[ANALYSIS][COMMENT] 코멘트 없음 (null 또는 빈 값)");
}
} catch (error) {
console.error("[ANALYSIS] 코멘트 로드 실패:", error);
} catch (error: any) {
console.error("[ANALYSIS][COMMENT] 코멘트 로드 실패:", {
message: error?.message,
status: error?.status,
data: error?.data,
stack: error?.stack,
error: error,
});
setScoreComment(null);
} finally {
setScoreCommentLoading(false);
console.log("[ANALYSIS][COMMENT] 코멘트 로드 완료, loading: false");
}
}, []);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 리뷰 코멘트

  1. 에러 핸들링: catch 블록에서 에러 객체의 구조를 많이 파악하고 있습니다. 그러나 error가 항상 message, status, data, stack 속성을 가질 필요가 없습니다. 이로 인해 불필요한 속성을 접근하려 할 경우 undefined 접근 에러가 발생할 수 있습니다. 이를 피하기 위해서는 객체에 대한 타입 체크를 해주는 것이 좋습니다.

  2. 코멘트의 형식: typeof comment가 문자열일 경우에만 길이를 출력하도록 하고 있습니다. 그러나, comment가 객체일 경우(예: { text: '코멘트' })도 있을 수 있습니다. 기본적으로 comment의 형식을 명확히 하여 이러한 사례를 고려해야 합니다.

  3. 리팩토링 가능성: apiType을 할당하는 로직이 조건문을 사용하여 이루어지고 있습니다. commentTypes 배열이 늘어날 경우 코드가 복잡해질 수 있으니, 좀 더 명확하게 매핑을 할 수 있는 방법으로 리팩토링하는 것을 권장합니다. 예를 들어, 배열의 각 인덱스에 직접적으로 매핑을 할 수도 있습니다.

  4. 의미 전달: 로그 메시지는 잘 작성되어 있지만, 너무 상세한 로그가 포함되어 있습니다. 각 로그 지점에서 필요한 정보만 담도록 조정하는 것이 좋습니다.

  5. 비동기 처리가 완료될 때 로딩 상태를 업데이트하는 방법: finally 블록에서 로딩 상태를 업데이트하는 것은 바람직합니다. 그러나, 이 방법이 UI 상태를 일관되게 유지해야 할 경우, 사용자가 결과를 인지하기에 충분한 시간 차가 발생할 수 있습니다.

결론적으로, 코드가 개념적으로 잘 작성되어 있지만, 에러 핸들링과 코드 유지관리를 위한 리팩토링이 필요하며, 함수의 안정성을 높이는 데 중점을 두는 것이 좋습니다.

Expand Down
8 changes: 4 additions & 4 deletions src/screens/chatbot/ChatbotScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ const ChatbotScreen = ({ navigation }: any) => {
<View
style={[
styles.chatinputContainer,
{ paddingBottom: insets.bottom > 0 ? insets.bottom : 10 },
{ paddingBottom: insets.bottom > 0 ? Math.max(insets.bottom, 4) : 4 },
]}
>
<TextInput
Expand Down Expand Up @@ -631,7 +631,7 @@ const styles = StyleSheet.create({
},
scrollViewContent: {
flexGrow: 1,
paddingBottom: 20,
paddingBottom: 10,
},
mainContent: {
flex: 1,
Expand Down Expand Up @@ -800,7 +800,7 @@ const styles = StyleSheet.create({
borderRadius: 12,
},
messagesContainer: {
paddingBottom: 20,
paddingBottom: 10,
},
message: {
maxWidth: "80%",
Expand Down Expand Up @@ -831,7 +831,7 @@ const styles = StyleSheet.create({
chatinputContainer: {
flexDirection: "row",
paddingHorizontal: 10,
paddingTop: 10,
paddingTop: 8,
backgroundColor: NEW_COLORS.card_bg,
borderTopWidth: 1,
borderTopColor: NEW_COLORS.separator,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치에서 몇 가지 우려사항이 있습니다:

  1. 패딩 값 변경: paddingBottom 값을 20에서 10으로 변경하는 것은 UI에 영향을 줄 수 있습니다. 만약 기존의 디자인과 비해 너무 좁아진다면 사용자 경험에 부정적인 영향을 미칠 수 있습니다. UI/UX 디자이너와 논의하여 확인하는 것이 좋습니다.
  2. Math.max 사용: Math.max(insets.bottom, 4)를 사용하는 것은 하단 패딩이 너무 작아지는 것을 방지하는 좋은 방법입니다. 그러나, 이 값이 고정되도록 하여 다른 패딩이나 디자인 요소와의 조화를 고려하는 것이 중요합니다.
  3. 하드코딩된 숫자: 여러 곳에서 하드코딩된 숫자(예: 10, 4)가 사용되고 있습니다. 이러한 값을 상수로 정의하여 코드의 유지보수성을 높이는 것이 좋습니다.

코드에서의 변경사항을 좀 더 이해하고, UI 테스트를 통해 불편함이 없는지 확실히 확인해보는 것이 필요합니다.

Expand Down
85 changes: 83 additions & 2 deletions src/screens/exercise/ExerciseScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6386,10 +6386,91 @@ const StretchDetailContent = ({

// 문자열 형태의 설명
if (typeof instructions === "string" && instructions.length > 0) {
const lines = instructions
// 운동 설명과 동일한 방식으로 Step 처리
const stepMatches: Array<{ number: string; content: string }> =
[];

// 먼저 줄바꿈으로 분리
const instructionLines = instructions
.split("\n")
.filter((line) => line.trim());

instructionLines.forEach((line) => {
// 한 줄에 여러 Step이 쉼표로 구분되어 있을 수 있음
// "Step: 1 ..., Step:2 ..., Step:3 ..." 형식 처리

// Step: 또는 ,Step: 패턴으로 분리
const stepParts = line
.split(/(?:^|,\s*)Step:/i)
.filter((part) => part.trim());
const foundSteps: Array<{
stepNum: number;
description: string;
}> = [];

stepParts.forEach((part) => {
// Step: 다음에 오는 숫자와 설명 추출
const stepMatch = part.match(/^\s*(\d+)\s*(.+)$/);
if (stepMatch) {
const stepNum = parseInt(stepMatch[1], 10);
let description = stepMatch[2].trim();
// 끝의 쉼표, 점, 공백 제거
description = description.replace(/[,\.\s]+$/, "").trim();

if (description) {
foundSteps.push({ stepNum, description });
}
}
});

// Step: 패턴으로 분리되지 않은 경우, 정규식으로 다시 시도
if (foundSteps.length === 0) {
const stepRegex = /Step:\s*(\d+)\s*([^,]+?)(?=,\s*Step:|$)/gi;
let match;

while ((match = stepRegex.exec(line)) !== null) {
const stepNum = parseInt(match[1], 10);
let description = match[2].trim();
description = description.replace(/[,\.\s]+$/, "").trim();

if (description) {
foundSteps.push({ stepNum, description });
}
}
}

// 찾은 Step들을 stepMatches에 추가
foundSteps.forEach(({ stepNum, description }) => {
stepMatches.push({
number: stepNum.toString(),
content: description,
});
});
});

// Step 패턴이 있으면 Step 기준으로 렌더링
if (stepMatches.length > 0) {
return stepMatches.map((step, idx) => {
return (
<View key={idx}>
<View style={styles.stretchDetailStep}>
<Text style={styles.stretchDetailStepNumber}>
{step.number}
</Text>
<Text style={styles.stretchDetailStepTitle}>
{step.content}
</Text>
</View>
</View>
);
});
}

// Step 패턴이 없으면 기존대로 줄바꿈으로 분리
const fallbackLines = instructions
.split("\n")
.filter((line) => line.trim());
return lines.map((line: string, idx: number) => (
return fallbackLines.map((line: string, idx: number) => (
<Text key={idx} style={styles.stretchDetailStepText}>
{line.trim()}
</Text>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 검토 피드백

  1. 정규식 처리 신뢰성:

    • 두 개의 단계에서 stepMatches를 찾는 방식 사용. 첫 번째 패턴에서 아무것도 찾지 못할 경우 두 번째 정규식을 사용합니다. 하지만 정규식이 완전하게 모든 경우를 처리할 수 있을지 의문입니다. 예를 들어, Step: 1 Some Description, Step: 2 Another Description. 형식이 아닌 경우에 대한 충분한 테스트가 필요합니다.
  2. 성능 문제:

    • 문자열을 처리할 때 split, filter, map을 반복적으로 사용하는 방식은 큰 규모의 문자열에서 성능 저하를 일으킬 수 있습니다. 가능한 한 작업을 결합하여 호출 횟수를 줄이는 것이 좋습니다.
  3. 에러 처리 부족:

    • parseInt 사용 시 유효한 숫자가 아닌 경우에 대한 에러 처리가 없습니다. 숫자가 아닌 문자열이 입력될 경우 NaN이 발생할 수 있습니다. 추가적인 검증 로직이 필요합니다.
  4. 반복 코드:

    • 두 가지 방식으로 문자열을 처리하는 부분에 중복 코드가 존재합니다. description을 trimming하고, 쉼표 및 점을 제거하는 코드가 두 번 나타납니다. 이 부분을 함수로 추출하여 중복을 줄이는 것이 좋습니다.
  5. 전역 상태 의존성 확인 필요:

    • styles가 이전 코드와 어떻게 관련되어 있는지 명확하지 않습니다. styles가 유효한지 확인하고, 필요한 경우 어떤 값이 무엇을 위해 필요한지 주석을 추가하는 것이 좋습니다.
  6. 키 값에 대한 경고:

    • List나 반복적으로 jsx를 사용하는 경우, key prop에 배열의 인덱스를 사용하는 것은 권장되지 않습니다. 데이터가 변경될 때 리렌더링 성능 문제가 생길 수 있으니, 유일한 식별자 사용을 고려해야 합니다.
  7. 상태 관리 미비:

    • 이 코드 조각에서 렌더링하는 부분에 상태 변화가 필요한지 확인해야 합니다. 예를 들어, stepMatches 배열이 상태에 따라 변화할 수 있는 부분인지 검토해주세요.

이 코드는 전반적으로 좋은 접근법이지만 위에서 언급한 대로 몇 가지 수정 사항과 개선 사항이 필요합니다. 그렇지 않으면 문제를 일으킬 가능성이 있습니다.

Expand Down
Loading