Skip to content

Feature: 최종 기능 구현 및 QA#56

Merged
Monixc merged 45 commits into
mainfrom
feature/records-detail
Sep 22, 2025
Merged

Feature: 최종 기능 구현 및 QA#56
Monixc merged 45 commits into
mainfrom
feature/records-detail

Conversation

@Monixc

@Monixc Monixc commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

작업 내용

참고 사항

…nimated, react-native-worklets 패키지 업데이트
@Monixc Monixc requested a review from ksy90101 as a code owner September 22, 2025 04:22
@vercel

vercel Bot commented Sep 22, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
runova-web Ignored Ignored Sep 22, 2025 4:22am

@Monixc Monixc merged commit 7836a82 into main Sep 22, 2025
3 checks passed
@Monixc Monixc deleted the feature/records-detail branch September 22, 2025 04:23
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @Monixc, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 사용자 경험을 크게 향상시키는 여러 핵심 기능을 도입합니다. 런닝 기록을 상세히 확인하고, 나만의 인증 사진을 꾸며 공유할 수 있는 기능을 제공하며, 코스 런닝 중에는 실시간 음성 내비게이션으로 경로 안내를 받을 수 있습니다. 또한, 홈 화면의 시각적 요소를 강화하고, 전반적인 앱 성능과 안정성을 개선하기 위한 다양한 코드 최적화 및 디버깅 로깅 제거 작업이 포함되었습니다.

Highlights

  • 런닝 기록 상세 페이지 및 사진 꾸미기 기능 추가: 사용자가 런닝 기록을 상세하게 확인하고, 런닝 후 촬영한 인증 사진에 통계, 경로, 타임스탬프 등을 추가하여 꾸밀 수 있는 기능이 추가되었습니다. 꾸민 사진은 저장하거나 공유할 수 있습니다.
  • 실시간 런닝 내비게이션 및 음성 안내: 코스 런닝 중 실시간으로 경로 이탈 여부를 검증하고, 다음 회전 지점까지의 거리와 방향을 음성으로 안내하는 내비게이션 기능이 구현되었습니다.
  • 홈 화면 동적 이미지 및 위치 정보 개선: 홈 화면에 동적으로 로드되는 런닝 아트 이미지가 추가되었으며, 현재 위치를 기반으로 주소를 표시하도록 헤더가 개선되었습니다.
  • 카메라 및 미디어 라이브러리 권한 처리 강화: 인증 사진 촬영 및 저장을 위해 iOS Info.plist 및 Android Manifest에 필요한 권한 설명과 설정이 추가되었고, react-native-vision-camera 라이브러리 설정이 적용되었습니다.
  • 성능 및 디버깅 개선: 앱 전반에 걸쳐 불필요한 console.log 호출이 제거되어 런타임 성능이 향상되었으며, react-native-reanimatedrunOnJS를 활용하여 지도 제스처 처리의 성능과 안정성이 개선되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

이 PR은 인증사진 촬영 및 꾸미기, 런닝 기록 상세 조회, 턴바이턴 내비게이션 등 다양한 최종 기능들을 구현하고 있습니다. 전반적으로 코드 품질이 높고, 기능 구현이 체계적으로 이루어졌습니다. 특히 제스처 핸들링 리팩토링과 react-native-reanimatedrunOnJS를 활용한 성능 개선은 매우 훌륭합니다. 다만, 몇 가지 개선점을 제안합니다. 스타일 가이드에 따라 매직 넘버와 하드코딩된 문자열들을 상수로 분리하고, run.ts 스토어의 상태 초기화 로직에서 발견된 잠재적인 버그를 수정하는 것이 좋겠습니다. 또한, 일부 새로운 컴포넌트에서 하드코딩된 색상 값을 테마나 상수 파일로 옮겨 일관성을 높일 수 있습니다. 자세한 내용은 각 파일에 남긴 코멘트를 참고해주세요.

Comment thread src/store/run.ts
Comment on lines +294 to +295
// isTracking이 true면 일시정지 상태이므로 상태 보존
if (state.isTracking) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

resetRunState 함수의 로직이 변경되어, isTrackingfalse일 때 (즉, 런닝이 일시정지되었을 때) 런닝 상태가 완전히 초기화될 위험이 있습니다. 이전 로직처럼 state.startTime의 존재 여부로 런닝 진행 상태를 판단해야 합니다. 현재 로직은 사용자가 런닝을 잠시 멈추었을 때 기록이 사라지는 심각한 버그를 유발할 수 있습니다.

Suggested change
// isTracking이 true면 일시정지 상태이므로 상태 보존
if (state.isTracking) {
// 런닝이 진행 중(시작되었거나 일시정지 상태)이면 상태를 보존
if (state.startTime) {

stats: {
distance: runningStats.distance,
calories: runningStats.calories,
pace: parseFloat(runningStats.pace.replace(':', '.')),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

parseFloat(runningStats.pace.replace(':', '.')) 로직은 페이스 값을 문자열에서 숫자로 다시 변환하는 과정이 불안정해 보입니다. runningStats.pace"5'30\""와 같은 형식의 문자열일 수 있어 replace(':', '.')가 의도대로 동작하지 않을 수 있습니다. formatRunningStats 함수에서 포맷팅되기 전의 원본 숫자 페이스 값(routeStats.pace)을 사용하는 것이 더 안전하고 명확합니다.

Suggested change
pace: parseFloat(runningStats.pace.replace(':', '.')),
pace: routeStats.pace,

Comment thread app.config.js
Comment on lines +21 to +26
NSCameraUsageDescription:
'인증사진 촬영을 위해 카메라 접근이 필요합니다.',
NSMicrophoneUsageDescription:
'비디오 촬영을 위해 마이크 접근이 필요합니다.',
NSPhotoLibraryUsageDescription:
'인증사진을 갤러리에 저장하기 위해 사진 라이브러리 접근이 필요합니다.',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

권한 요청 문구가 여러 곳에서 중복으로 사용되고 있습니다. 예를 들어 '인증사진 촬영을 위해 카메라 접근이 필요합니다.' 문구가 NSCameraUsageDescriptionreact-native-vision-camera 플러그인 설정에 모두 하드코딩되어 있습니다. 스타일 가이드(144라인)1에 따라, 이러한 문자열들을 별도의 상수 파일로 분리하여 관리하면 유지보수성과 코드의 일관성을 높일 수 있습니다.

Style Guide References

Footnotes

  1. 상수는 하드코딩하지 말고 constants.ts 또는 theme.ts에 관리해야 합니다.

Comment thread src/components/Camera.tsx
Comment on lines +58 to +62
title: '카메라 권한',
message: '사진 촬영을 위해 카메라 권한이 필요합니다.',
buttonNeutral: '나중에',
buttonNegative: '취소',
buttonPositive: '확인',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

PermissionsAndroid.request에 사용된 title, message 등의 문자열들이 하드코딩되어 있습니다. 스타일 가이드(144라인)1에 따라 이러한 문자열들은 상수 파일로 분리하여 관리하는 것이 좋습니다. 이를 통해 코드의 가독성과 유지보수성을 높일 수 있습니다.

Style Guide References

Footnotes

  1. 상수는 하드코딩하지 말고 constants.ts 또는 theme.ts에 관리해야 합니다.

Comment on lines +40 to +99
const starCount = Math.floor(Math.random() * 100) + 200;
const newStars: Star[] = [];

for (let i = 0; i < starCount; i++) {
let x, y;

if (Math.random() < 0.3) {
const lineProgress = Math.random();
const lineWidth = 150;
const centerX = lineProgress * screenWidth;
const centerY = lineProgress * screenHeight * 0.7 + screenHeight * 0.15;

x = centerX + (Math.random() - 0.5) * lineWidth;
y = centerY + (Math.random() - 0.5) * lineWidth * 0.3;
} else {
x = Math.random() * screenWidth;
y = Math.random() * screenHeight;
}

newStars.push({
id: i,
x: Math.max(0, Math.min(screenWidth, x)),
y: Math.max(0, Math.min(screenHeight, y)),
size: Math.random() * 1.7 + 0.3,
animatedValue: new Animated.Value(Math.random() * 0.6 + 0.3),
});
}

setStars(newStars);

newStars.forEach((star) => {
const createTwinkleAnimation = () => {
const minOpacity = Math.random() * 0.2 + 0.1;
const maxOpacity = Math.random() * 0.4 + 0.6;

return Animated.sequence([
Animated.timing(star.animatedValue, {
toValue: maxOpacity,
duration: Math.random() * 3000 + 2000,
useNativeDriver: true,
}),
Animated.timing(star.animatedValue, {
toValue: minOpacity,
duration: Math.random() * 3000 + 2000,
useNativeDriver: true,
}),
]);
};

const startAnimation = () => {
createTwinkleAnimation().start(() => {
setTimeout(() => {
startAnimation();
}, Math.random() * 2000);
});
};

setTimeout(() => {
startAnimation();
}, Math.random() * 5000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

컴포넌트 내부에 200, 100, 0.3, 150, 3000 등 여러 매직 넘버가 사용되었습니다. 스타일 가이드(145라인)1에서는 매직 넘버를 이름 있는 상수로 치환하도록 권장하고 있습니다. 각 숫자의 의미를 명확히 나타내는 상수로 정의하면 코드의 가독성과 유지보수성이 크게 향상됩니다.

예시:

const STAR_COUNT_BASE = 200;
const STAR_COUNT_RANDOM_FACTOR = 100;
const GALAXY_PROBABILITY = 0.3;

Style Guide References

Footnotes

  1. 매직 넘버(예: 200, 0.5 등)는 이름 있는 상수로 치환해야 합니다.

Comment thread src/hooks/useRunModals.ts

type Props = {
navigation: NativeStackNavigationProp<TabParamList, 'RunTab'>;
navigation: any; // RootStackParamList와 TabParamList 모두 지원

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

navigation prop의 타입을 any로 지정하셨습니다. 스타일 가이드(18라인)1에서는 any 타입 사용을 금지하고 있습니다. RunTabStackParamListTabParamList를 포함하는 더 구체적인 내비게이션 타입을 정의하거나 제네릭을 사용하여 타입을 명시하는 것이 좋습니다.

Suggested change
navigation: any; // RootStackParamList와 TabParamList 모두 지원
navigation: NativeStackNavigationProp<any>; // RootStackParamList와 TabParamList 모두 지원

Style Guide References

Footnotes

  1. any는 금지합니다. 불가피할 경우 unknown을 우선 사용합니다.

Comment on lines +117 to +156
// 인증 에러나 네트워크 에러 시 기본 이미지 사용
const fallbackImages = [
require('@/assets/bear.png'),
require('@/assets/chick.png'),
require('@/assets/dog.png'),
require('@/assets/woman.png'),
];

const newImages: FloatingImageData[] = [];
const maxAttempts = 500;

fallbackImages.forEach((image: any, index: number) => {
const count = 1;

for (let i = 0; i < count; i++) {
let attempts = 0;
let validPosition = false;
let newImage: FloatingImageData | null = null;

while (attempts < maxAttempts && !validPosition) {
newImage = {
id: index * 10 + i,
source: image,
x: Math.random() * (screenWidth - 100),
y: Math.random() * (screenHeight / 2 - 250) + 150,
size: Math.random() * 60 + 100,
rotation: Math.random() * 360,
};

if (isValidPosition(newImage, newImages)) {
validPosition = true;
newImages.push(newImage);
}
attempts++;
}
}
});

setFloatingImages(newImages);
setFloatingImages(newImages);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

API 호출 실패 시 실행되는 catch 블록의 폴백 로직이 API 성공 시의 로직과 매우 유사하게 중복되고 있습니다. 이미지 소스 배열을 생성하는 부분만 다릅니다. 이 중복을 제거하기 위해 이미지 배열을 생성하는 로직을 분리하고, 이미지 배치 로직을 공통 함수로 추출하면 코드의 유지보수성이 향상될 것입니다.

const finalPathData = routePathData || getDefaultPathData();

if (!routePathData) {
console.log('📊 [RouteRenderer] routePathData가 없음, 테스트 경로 사용');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

개발 중 사용된 것으로 보이는 console.log가 남아있습니다. 최종 코드에서는 제거하는 것이 좋습니다.

Comment on lines +33 to +49
const saveToGallery = async () => {
try {
// 미디어 라이브러리 권한 요청
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
Alert.alert('권한 필요', '갤러리에 저장하려면 권한이 필요합니다.');
return false;
}

// 갤러리에 저장
const asset = await MediaLibrary.createAssetAsync(photoUri);

return true;
} catch (error) {
return false;
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

saveToGallery 함수가 정의되어 있지만 컴포넌트 내에서 호출되는 곳이 없습니다. 사용되지 않는 코드는 혼란을 줄 수 있으므로 제거하는 것을 고려해 보세요.

Comment on lines +43 to +48
// 유턴은 더 강한 색상으로 표시
if (instruction.scale === '유턴') {
return ['#f59e0b', '#d97706', '#b45309']; // 주황색 그라데이션
}

return ['#3b82f6', '#2563eb', '#1d4ed8']; // 파란색 그라데이션

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

배경색으로 사용되는 색상 값들이 하드코딩되어 있습니다. 스타일 가이드(98라인)1에 따라 이러한 색상들은 테마 또는 상수 파일(src/constants/colors.ts 등)에서 관리하는 것이 좋습니다. 이를 통해 앱 전체의 색상 일관성을 유지하고 관리를 용이하게 할 수 있습니다.

Style Guide References

Footnotes

  1. 재사용 가능한 색상, 폰트, spacing은 theme 또는 constants 파일로 관리합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant