feat(ui): UI 화면 골격 + store + composite/maskSplit hook (v0-plan Task 9/10/11)
새 의존성: - expo-image-picker ~56.0.13 (PhotoCapture) - expo-media-library ~56.0.6 (CaptureResult 저장) - expo-file-system ~56.0.7 (generateRegionMask 파일 저장 — Mac 구현 시) 새 파일: - src/store/useAppStore.ts: zustand store (photoUri/maskUri/pose/mode + reset) - src/features/segmentation/index.ts: SegmentationModule TS wrapper (Mac Swift 빌드 대기) - src/features/composite/shaders.ts: Skia RuntimeEffect 셰이더 소스 (마스크 흰색 → 카메라, 그 외 → 베이스) - src/features/composite/CompositeView.tsx: 합성 컴포넌트 골격 (Skia v2 API Mac 검증 대기) - src/features/composite/useCameraFrameUri.ts: 카메라 프레임 URI hook 골격 (vision-camera v5 API Mac 검증 대기) - src/features/maskSplit/generateRegionMask.ts: region 마스크 생성 골격 (Skia v2 API Mac 검증 대기) - src/features/maskSplit/useModeMask.ts: mode 전환 시 마스크 자동 재생성 hook + cleanup race 처리 - src/screens/OnboardingScreen.tsx: 3장 슬라이드 (정상 동작) - src/screens/PhotoCaptureScreen.tsx: 갤러리 선택 + 자세 검증 (PoseModule Mac 빌드 후 동작) - src/screens/CaptureResultScreen.tsx: 결과 + 저장 + 공유 (expo-media-library) - src/screens/LiveFittingScreen.tsx: 모드 전환 + 핀치/팬 + Composite 골격 (Camera는 v5 API Mac 검증 대기) - App.tsx: 4화면 state-based navigation (onboarding → photo → live → result) + GestureHandlerRootView 감싸기 핵심 결정: - vision-camera v5는 v4와 완전 다른 Nitro 기반 (takeSnapshot 미존재 / usePhotoOutput/usePreviewOutput hook 패턴). v0-plan v4 코드 그대로 옮길 수 없어 Camera 부분 골격 + Mac TODO 명시. - Skia v2 child shader API도 v1과 다를 가능성 → CompositeView 골격 + Mac TODO. - 모드 전환/핀치 줌/네비게이션/store/검증 로직은 골격 단계에서 정상 동작. 검증 (Windows 가능 범위): - npx tsc --noEmit: 무에러 - npm test: 6 suites / 17 tests passed (UI 골격 추가가 기존 테스트 영향 없음 확인) Mac에서 채울 부분 (정리): - vision-camera v5 Camera + photoOutput + previewOutput 통합 - Skia v2 RuntimeEffect child shader 패턴 (ImageShader / useShader) - expo-file-system v56 writeAsStringAsync(base64) 동작 확인 - generateRegionMask 본 구현 (Surface.MakeOffscreen + clipRect + encode PNG) - 실기기 manual test 5종 (각 화면 전환 + 핀치 줌 + 캡쳐 + 저장 + 공유) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
64
App.tsx
64
App.tsx
@@ -1,20 +1,56 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { OnboardingScreen } from './src/screens/OnboardingScreen';
|
||||
import { PhotoCaptureScreen } from './src/screens/PhotoCaptureScreen';
|
||||
import { LiveFittingScreen } from './src/screens/LiveFittingScreen';
|
||||
import { CaptureResultScreen } from './src/screens/CaptureResultScreen';
|
||||
import { useAppStore } from './src/store/useAppStore';
|
||||
import { detectPose } from './src/features/pose';
|
||||
|
||||
type Screen = 'onboarding' | 'photo' | 'live' | 'result';
|
||||
|
||||
export default function App(): ReactElement {
|
||||
const [screen, setScreen] = useState<Screen>('onboarding');
|
||||
const [capturedUri, setCapturedUri] = useState<string>('');
|
||||
const setPhoto = useAppStore((s) => s.setPhoto);
|
||||
const setPose = useAppStore((s) => s.setPose);
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Open up App.tsx to start working on your app!</Text>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<StatusBar style="auto" />
|
||||
</View>
|
||||
{screen === 'onboarding' && (
|
||||
<OnboardingScreen onDone={() => setScreen('photo')} />
|
||||
)}
|
||||
{screen === 'photo' && (
|
||||
<PhotoCaptureScreen
|
||||
onPhotoReady={async (uri) => {
|
||||
setPhoto(uri);
|
||||
try {
|
||||
const pose = await detectPose(uri);
|
||||
setPose(pose);
|
||||
} catch {
|
||||
// Mac에서 PoseModule 빌드 전까지는 pose 없이 진행 (LiveFitting에서 mask 생성 실패)
|
||||
}
|
||||
setScreen('live');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{screen === 'live' && (
|
||||
<LiveFittingScreen
|
||||
onCapture={(uri) => {
|
||||
setCapturedUri(uri);
|
||||
setScreen('result');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{screen === 'result' && (
|
||||
<CaptureResultScreen
|
||||
imageUri={capturedUri}
|
||||
onRetry={() => setScreen('live')}
|
||||
/>
|
||||
)}
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user