Skip to content

분석 녹음 파일 업로드 화면 구현#137

Merged
HamBeomJoon merged 20 commits into
developfrom
feat/#115-analytical-recording-file-upload
May 24, 2026
Merged

분석 녹음 파일 업로드 화면 구현#137
HamBeomJoon merged 20 commits into
developfrom
feat/#115-analytical-recording-file-upload

Conversation

@HamBeomJoon
Copy link
Copy Markdown
Contributor

@HamBeomJoon HamBeomJoon commented May 23, 2026

📌 작업 내용

  • 발표 분석 플로우 신규 구현

    • 발표 일정 입력, 발표 상황 선택, 대본 입력/업로드, 음성 파일 업로드, 분석 대기, 리포트 진입 화면 추가
    • 분석 플로우 상태/이벤트/ViewModel 구성
    • 파일 업로드 실패 및 재시도 화면 추가
  • 홈 화면에서 분석 진입 액션 추가

    • 플로팅 메뉴를 통해 음성 녹음 분석 / 파일 업로드 분석 진입 UI 추가
    • 분석 feature 모듈을 앱 및 홈 네비게이션에 연결
  • 분석 모듈 추가

    • feature:analysis:api, feature:analysis:impl 모듈 생성
    • AnalysisNavKey.Create 기반 네비게이션 엔트리 추가
  • 파일 업로드 및 미리보기 UX 추가

    • 대본 파일 업로드 UI 추가
    • 오디오 파일 업로드 UI 추가
    • 업로드 진행 상태 표시
    • 업로드된 오디오 재생/일시정지/seek 지원
  • 발표 녹음 분석 API 연동

    • /recording/analyze API 연동을 추가했습니다.
    • 발표 제목, 날짜, 발표 유형, 목적, 스타일, 청중, 대본, 대본 파일, 음성 파일을 multipart form-data로 전송하도록 구현했습니다.
    • 서버 enum 값에 맞춰 type, purpose, style, audience 요청 값을 매핑했습니다.
    • 발표 분석 응답 DTO와 도메인 모델을 추가하고 Repository/UseCase까지 연결했습니다.
  • 파일 업로드 안정성 개선

    • Android content:// Uri를 API 업로드에 사용할 수 있도록 앱 cacheDir 임시 파일로 복사하는 AnalysisFileCache를 추가했습니다.
    • ViewModel에서 ApplicationContext 의존성을 제거하고 파일 캐시 처리를 별도 helper로 분리했습니다.
    • 임시 파일 생성 후 복사 실패 시 잔여 파일이 남지 않도록 삭제 처리를 추가했습니다.
    • scriptFilePath, audioFilePath, recordingFilePath 빈 문자열 방어를 추가했습니다.
    • 대용량 음성/대본 파일 업로드 시 readBytes()로 전체 파일을 메모리에 올리지 않도록 Ktor ChannelProvider 기반 스트리밍 업로드로 변경했습니다.

🧩 관련 이슈


📸 스크린샷

2026-05-24.00.28.02.mov

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 발표 녹음 분석 기능 추가 - 발표 일정, 상황(카테고리/목적/스타일/청중), 스크립트, 오디오 파일 입력을 통한 분석 플로우
    • 홈 화면에 음성 녹음 및 파일 업로드 분석 메뉴 추가
  • UI 개선

    • 디자인 시스템 컴포넌트 업데이트 (아코디언 구분선, 탭 리플 효과, 날짜 선택기)
  • 기타

    • 발표 분석 카테고리 및 옵션 값 업데이트

Review Change Stack

* **feat: 발표 분석 프로세스 단계별 UI 및 로직 구현**
    * 새로운 발표 분석을 위한 `AnalysisNavKey` 및 탐색 그래프(`AnalysisEntryBuilder`)를 추가했습니다.
    * `AnalysisFlowViewModel`을 통해 5단계의 분석 흐름(일정 입력 -> 상황 설정 -> 대본 입력 -> 음성 업로드 -> 분석 중)을 관리하도록 구현했습니다.
    * **일정 추가**: 발표 제목 및 `PrezelDatePicker`를 이용한 날짜 선택 기능을 구현했습니다.
    * **상황 설정**: `PrezelAccordion`과 칩/카드 UI를 사용하여 발표 유형, 목적, 스타일, 청중을 선택하는 기능을 추가했습니다.
    * **대본 입력**: `.txt` 파일 업로드와 직접 입력(`PrezelTextArea`) 방식을 모두 지원합니다.
    * **음성 업로드**: 오디오 파일(`mp3`, `wav`) 선택 및 업로드 상태를 표시하는 카드 UI를 추가했습니다.
    * **분석 중**: 가상의 딜레이와 함께 분석 진행 상태를 보여주는 로딩 화면을 구현했습니다.

* **feat: 홈 화면 내 분석 진입점 추가**
    * 홈 화면에 `PrezelFloatingMenu`(FAB)를 도입하여 '음성 녹음' 및 '파일 업로드' 분석 모드로 진입할 수 있는 인터페이스를 추가했습니다.
    * FAB 확장 시 배경 오버레이 처리를 추가하여 사용성을 개선했습니다.

* **refactor: 로그인 및 내비게이션 구조 단순화**
    * `LoginViewModel`에서 실제 카카오 로그인 로직을 임시로 제거하고, 로그인 클릭 시 바로 홈 화면으로 이동하도록 흐름을 단순화했습니다.
    * `feature:login:impl` 모듈의 불필요한 의존성(`AuthManager`, `LoginUseCase`)을 정리했습니다.

* **build: 모듈 의존성 및 리소스 구성**
    * `feature:analysis` api/impl 모듈을 신규 생성하고 `app` 모듈 및 관련 기능 모듈에 의존성을 연결했습니다.
    * 분석 단계별 아이콘, 문자열 리소스 및 공통 레이아웃(`AnalysisStepLayout`)을 정의했습니다.
*   **feat: 대본 파일 업로드 기능 개선 및 지원 형식 확대**
    *   대본 파일 지원 형식을 기존 `.txt`에서 `pdf`, `.txt`로 확대하고 관련 안내 문구를 수정했습니다.
    *   `ActivityResultContracts.OpenDocument`를 사용하여 다양한 문서 타입(`pdf`, `text/*` 등)을 선택할 수 있도록 개선했습니다.
    *   파일 업로드 화면을 `StatusView`와 커스텀 벡터 아이콘(`feature_analysis_impl_no_script`)을 사용하는 UI로 개편했습니다.
    *   `ContentResolver`를 통해 URI로부터 실제 파일명을 추출하도록 `toFileName` 확장 함수를 개선했습니다.

*   **refactor: 발표 일정 및 입력 유효성 로직 수정**
    *   발표 제목 입력 시 공백 제외 최소 2자 이상 입력해야 다음 단계로 이동 가능하도록 검증 로직을 강화했습니다.
    *   발표 날짜 표시 형식을 `YYYY.MM.DD`에서 `YYYY년 MM월 DD일`로 변경하고 관련 파싱 로직을 업데이트했습니다.

*   **style: 디자인 시스템 컴포넌트 동작 및 UI 세부 조정**
    *   `PrezelTabs`: 탭 클릭 시 리플 효과(Ripple)가 발생하지 않도록 `NoRippleInteractionSource`를 적용했습니다.
    *   `PrezelAccordion`: 아코디언이 확장(`expanded`)된 상태에서는 하단 구분선(`showDivider`)이 노출되지 않도록 수정했습니다.
    *   `DayCell`: 데이트 피커 내 날짜 선택 시 리플 효과를 제거했습니다.
    *   `PresentationScheduleScreen`: 날짜 선택 필드의 상호작용 소스를 초기화하여 클릭 영역 동작을 개선했습니다.
…al-recording-file-upload

# Conflicts:
#	Prezel/feature/login/impl/src/main/java/com/team/prezel/feature/login/impl/LoginScreen.kt
#	Prezel/feature/login/impl/src/main/java/com/team/prezel/feature/login/impl/LoginViewModel.kt
#	Prezel/feature/login/impl/src/main/java/com/team/prezel/feature/login/impl/contract/LoginUiEffect.kt
#	Prezel/feature/login/impl/src/main/java/com/team/prezel/feature/login/impl/navigation/LoginEntryBuilder.kt
* **feat: 파일 인식 실패 화면 추가 및 탐색 로직 구현**
    * 음성 및 대본 파일 인식 실패 시 표시할 `FileRecognitionFailedScreen`, `ScriptFileRecognitionFailedScreen`을 추가했습니다.
    * `AnalysisFlowStep`에 실패 상태(`FILE_RECOGNITION_FAILED`, `SCRIPT_FILE_RECOGNITION_FAILED`)를 추가하고, 재시도(`RetryFileUpload`) 인텐트 처리를 구현했습니다.

* **feat: 대본 및 음성 파일 업로드 진행 상태 표시 구현**
    * 파일 선택 시 즉시 완료되지 않고 시뮬레이션된 프로그레스 바를 표시하도록 개선했습니다 (`animateFloatAsState` 사용).
    * `ScriptInputScreen` 및 `AudioUploadScreen`에서 파일 업로드 중 진행률(%)과 상태 바를 시각화했습니다.

* **refactor: 분석 설정(Situation) 인텐트 및 모델 구조 개선**
    * 개별 데이터 클래스로 분리되어 있던 카테고리, 목적, 스타일, 청중 선택 인텐트를 `SelectSituationOption` 추상화된 sealed interface 구조로 통합했습니다.
    * `PresentationSituationScreen` 내 아코디언 컴포넌트들을 하위 함수로 분리하여 가독성을 높였습니다.

* **refactor: 디자인 시스템 컴포넌트 최신화 및 UI 코드 정리**
    * `AnalysisStepLayout`의 버튼 영역을 `PrezelButtonArea`의 최신 API(`mainButton`, `subButton` 슬롯 방식)에 맞게 리팩터링했습니다.
    * `PrezelChip`의 파라미터명을 최신 디자인 시스템 사양(`interaction` -> `state`)에 맞게 수정했습니다.
    * `PresentationScheduleScreen`에서 날짜 선택 필드와 텍스트 필드 로직을 별도 컴포넌트로 추출했습니다.

* **fix: 지원 파일 형식 수정 및 리소스 추가**
    * 대본 파일 형식을 `txt`로 제한하고, 음성 파일 형식을 `m4a`, `mp4`, `mp3` 위주로 조정했습니다.
    * 인식 실패 화면에서 사용할 에러 아이콘(`feature_analysis_impl_error_voice.xml`)을 추가했습니다.

* **etc: 개발용 임시 코드 추가**
    * `SplashViewModel`에서 로그인 여부와 관계없이 홈으로 바로 이동하도록 임시 로직을 추가했습니다.
    * `LoginEntryBuilder`에서 `AuthManager` 의존성을 주입받도록 수정했습니다.
…al-recording-file-upload

# Conflicts:
#	Prezel/settings.gradle.kts
- 기존 `AnalyzingScreen`을 `AnalysisLoadingScreen`으로 변경하고 Lottie 애니메이션 적용
- 분석 완료 후 표시할 `AnalysisReportScreen` 추가 및 `AnalysisFlowStep.REPORT` 단계 정의
- `AudioUploadScreen`에 `MediaPlayer` 기반의 오디오 재생 및 탐색(Seek) 로직 구현
- `ScriptInputScreen`과 `AudioUploadScreen`의 파일 업로드 표시 로직을 공통 컴포넌트인 `FileUploader`로 리팩터링
- `AnalysisFlowViewModel` 내 단계 전환 로직에 `REPORT` 단계 추가 및 백버튼 동작 수정
- 분석 완료 시 홈으로 이동하던 `AnalysisEntryBuilder` 로직을 화면 내 상태 변화로 처리하도록 변경
- `SplashViewModel`에서 홈 화면으로 즉시 이동하던 임시 코드 제거
- 실제 로그인 상태를 확인하고 로딩 상태를 업데이트하는 로직 활성화
@HamBeomJoon HamBeomJoon self-assigned this May 23, 2026
@HamBeomJoon HamBeomJoon added the ✨ feat 새로운 기능 추가 또는 기존 기능 확장 label May 23, 2026
@HamBeomJoon HamBeomJoon requested a review from moondev03 as a code owner May 23, 2026 01:57
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

분석 기능 모듈(api/impl)이 추가되어 발표 일정→상황→스크립트→오디오 업로드→분석→리포트 흐름을 구현합니다. ViewModel 상태 머신, UI 화면들, 파일 캐시, 네트워킹 계층, 유스케이스 및 Home 화면 통합이 포함되며, Category/Purpose/Style/Audience 열거형 값이 업데이트됩니다.

Changes

분석 기능 모듈 및 UI 흐름 구현

Layer / File(s) Summary
분석 기능 모듈 등록 및 네비게이션
Prezel/settings.gradle.kts, Prezel/feature/analysis/api/build.gradle.kts, Prezel/feature/analysis/impl/build.gradle.kts, Prezel/app/build.gradle.kts, Prezel/feature/analysis/api/src/main/java/com/team/prezel/feature/analysis/api/AnalysisNavKey.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt, Prezel/feature/home/impl/build.gradle.kts, Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt
:feature:analysis:api/impl 모듈이 Gradle에 등록되고, AnalysisNavKey.Create에 대한 Entry 빌더가 설정되어 Home에서 분석 화면으로 라우팅됩니다.
분석 상태/의도/효과 계약 정의
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiIntent.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiEffect.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/model/AnalysisUiMessage.kt
AnalysisFlowUiState에서 단계/폼/결과를 관리하고, AnalysisFlowUiIntent로 사용자 이벤트를 정의하며, AnalysisFlowUiEffect로 네비게이션/메시지를 처리합니다.
발표 일정·상황 입력 UI
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/schedule/PresentationScheduleScreen.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/situation/PresentationSituationScreen.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/situation/SituationOptions.kt
제목/날짜 입력, 카테고리/목적/스타일/청중을 아코디언과 칩으로 선택하는 UI가 구현됩니다.
스크립트·오디오 업로드 및 분석 UI
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/script/ScriptInputScreen.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/audio/AudioUploadScreen.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/AnalysisLoadingScreen.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/AnalysisReportScreen.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/FileRecognitionFailedScreen.kt
파일 업로드/직접 입력으로 스크립트를 입력하고, 오디오를 선택해 재생하며, 분석 로딩과 결과/실패 화면을 표시합니다.
분석 단계 공통 레이아웃 및 지원 유틸
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/component/AnalysisStepLayout.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/component/AnalysisFileNameExt.kt
AnalysisStepLayout이 앱바/진행 바/콘텐츠/버튼 영역을 표준화하고, toFileName() 유틸이 URI에서 파일명을 추출합니다.
분석 ViewModel 및 상태 머신
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt
의도 처리, 단계 검증/전환, 분석 실행, 오류 복구를 구현하는 상태 머신으로, 파일 캐싱과 유스케이스 호출을 조율합니다.
분석 라우팅 및 상태 구독 화면
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisScreen.kt
ViewModel을 주입받아 상태를 수집하고 효과를 구독하며, 단계별로 내부 화면을 조건부 렌더링합니다.
파일 캐시 및 오류 처리
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCache.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCacheImpl.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandler.kt, Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/di/AnalysisModule.kt
URI를 앱 캐시 디렉터리로 복사하는 파일 캐시와 분석 오류를 UI 동작으로 변환하는 실패 핸들러, Hilt DI 바인딩이 구현됩니다.
네트워크·저장소·유스케이스 계층
Prezel/core/network/src/main/java/com/team/prezel/core/network/service/PracticeService.kt, Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSource.kt, Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSourceImpl.kt, Prezel/core/network/src/main/java/com/team/prezel/core/network/model/practice/PresentationRecordingAnalysisResponse.kt, Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PracticeRepositoryImpl.kt, Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/practice/PracticeRepository.kt, Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/practice/AnalyzePresentationRecordingUseCase.kt, Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PracticeMapper.kt
분석 API 엔드포인트, 멀티파트 데이터소스 구현, 응답 DTO, 저장소 메서드, 유스케이스, 응답 매퍼가 추가되어 분석 요청/응답을 완성합니다.
오류 매핑 및 AppError 확장
Prezel/core/common/src/main/kotlin/com/team/prezel/core/common/error/AppError.kt, Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt
SCRIPT_FILE_RECOGNITION_FAILED 오류 상수가 추가되고, 서버 오류 매핑이 갱신됩니다.
분석 결과 도메인 모델
Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationRecordingAnalysisResult.kt
발표 분석 결과, 성장 그래프, 예상 질문 데이터 클래스가 도메인 모델로 정의됩니다.
Home 화면 분석 메뉴 통합
Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt, Prezel/feature/home/impl/src/main/res/values/strings.xml
FAB 확장 메뉴에 음성 녹음/파일 업로드 분석 항목이 추가되어 분석 기능으로의 네비게이션을 제공합니다.
분석 기능 리소스
Prezel/feature/analysis/impl/src/main/res/values/strings.xml, Prezel/feature/analysis/impl/src/main/res/drawable/*
분석 화면 UI 문자열과 오류/상태 아이콘이 리소스로 추가됩니다.
디자인시스템 컴포넌트 리플 효과 수정
Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelAccordion.kt, Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/datepicker/config/DayCell.kt, Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/navigations/PrezelTabs.kt
아코디언 펼침 상태, 날짜셀, 탭에서 리플 표시 조건이 업데이트됩니다.

Category/Purpose/Style/Audience 열거형 값 변경

Layer / File(s) Summary
열거형 상수 정의 변경
Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Category.kt, Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Purpose.kt, Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Style.kt, Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Audience.kt
Category(PERSUASION/REPORT → OFFER/WORK), Purpose, Style, Audience 열거형 상수가 변경됩니다.
History UI 열거형 매핑 업데이트
Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/HistoryViewModel.kt, Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/HistoryScreen.kt, Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/component/HistoryItemList.kt, Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/component/HistoryPresentationCard.kt
샘플 데이터와 labelResId 매핑이 새로운 열거값으로 갱신됩니다.
Home 화면 및 Preview 열거형 업데이트
Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeViewModel.kt, Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/body/PresentationSheet.kt, Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt
HomeViewModel 샘플, Preview 데이터, 카테고리 라벨/배경 리소스 매핑이 새로운 열거값으로 업데이트됩니다.
Terms ViewModel 오류 매핑 조정
Prezel/feature/terms/impl/src/main/java/com/team/prezel/feature/terms/impl/TermsViewModel.kt
AppError 매핑에서 VOICE_RECOGNITION_FAILED 명시적 케이스가 제거되고 폴백으로 처리됩니다.

Possibly related issues

  • Team-Prezel/Prezel-Android#116: 이 PR의 분석 흐름(AudioUploadScreen, ViewModel, 파일 캐시, 네트워킹 지원) 구현은 해당 이슈에서 요청된 "음성 파일 업로드" 분석 화면을 직접 구현합니다.

Possibly related PRs

  • Team-Prezel/Prezel-Android#136: 메인 PR의 AudioUploadScreenFileUploader 컴포넌트의 상태 모델과 재생·seek UI를 직접 렌더링하므로, 해당 PR의 FileUploader 구현과 코드 레벨로 강하게 맞물려 있습니다.

  • Team-Prezel/Prezel-Android#100: 메인 PR이 HistoryViewModel/HistoryPresentationCard의 Category/Purpose/Style/Audience 상수 매핑과 프리뷰 데이터를 업데이트하는 부분이 해당 PR이 만든 히스토리 UI 경로와 직접 겹칩니다.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AudioUploadScreen.kt`:
- Line 53: Add WAV MIME types to the audio filter and update the user-facing
format hint: modify the AUDIO_FILE_MIME_TYPES array in AudioUploadScreen.kt to
include "audio/wav" and "audio/x-wav", and update the localization key
feature_analysis_impl_audio_file_format (or the UI text that references it) to
mention WAV alongside m4a/mp4/mp3 so the OpenDocument chooser allows WAV files
and the guidance string reflects WAV support.

In `@Prezel/feature/analysis/impl/src/main/res/values/strings.xml`:
- Around line 46-55: Update the user-facing file-format strings so they match
the actual supported formats: change the resource
feature_analysis_impl_script_file_format to list all supported script formats
(not just "txt") and change feature_analysis_impl_audio_file_format to include
"wav" in addition to "m4a, mp4, mp3" so the displayed guidance accurately
reflects supported inputs.

In
`@Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt`:
- Around line 19-25: The two FAB callbacks in HomeScreen
(navigateToFileUploadAnalysis and navigateToVoiceRecordingAnalysis) both call
navigator.navigate(AnalysisNavKey.Create) so the routing can't distinguish
intent; update routing to pass distinct navigation keys or parameters (e.g., add
separate AnalysisNavKey variants or include a mode/argument on
AnalysisNavKey.Create) and change the two callbacks to call the appropriate
key/value so navigator.navigate receives a unique identifier for file-upload vs
voice-recording; search for HomeScreen, navigateToFileUploadAnalysis,
navigateToVoiceRecordingAnalysis, AnalysisNavKey.Create, and navigator.navigate
to apply the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ecd4e1f8-eb42-4941-a317-cfddee234518

📥 Commits

Reviewing files that changed from the base of the PR and between c4e2455 and a325dff.

📒 Files selected for processing (33)
  • Prezel/app/build.gradle.kts
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelAccordion.kt
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/datepicker/config/DayCell.kt
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/navigations/PrezelTabs.kt
  • Prezel/feature/analysis/api/build.gradle.kts
  • Prezel/feature/analysis/api/src/main/java/com/team/prezel/feature/analysis/api/AnalysisNavKey.kt
  • Prezel/feature/analysis/impl/build.gradle.kts
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisLoadingScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisReportScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AudioUploadScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/FileRecognitionFailedScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/PresentationScheduleScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/PresentationSituationScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/ScriptInputScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/SituationOptions.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/component/AnalysisStepLayout.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/component/AnalysisUploadComponents.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiEffect.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiIntent.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt
  • Prezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_error_voice.xml
  • Prezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_no_script.xml
  • Prezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_no_voice.xml
  • Prezel/feature/analysis/impl/src/main/res/values/strings.xml
  • Prezel/feature/analysis/impl/src/test/java/com/team/prezel/feature/analysis/impl/.gitkeep
  • Prezel/feature/home/impl/build.gradle.kts
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt
  • Prezel/feature/home/impl/src/main/res/values/strings.xml
  • Prezel/settings.gradle.kts

Comment thread Prezel/feature/analysis/impl/src/main/res/values/strings.xml
- `feature:analysis:impl` 모듈 내 화면 컴포넌트들을 도메인별 패키지(`audio`, `result`, `schedule`, `script`, `situation`)로 분리하여 재구성
- `PresentationSituationScreen`의 아코디언 UI에 선택된 옵션 표시 기능 추가 및 카드 디자인 개선
- `AnalysisStepLayout` 내 버튼 영역의 배경 표시 설정 및 제목 텍스트 스타일을 `title2Bold`로 변경
- `AnalysisFlowUiIntent`에 상황 설정 옵션 선택을 위한 헬퍼 함수 추가 및 `AnalysisScreen` 내 인텐트 전달 로직 단순화
- `strings.xml` 내 분석 관련 문구 및 상황 설정 옵션 명칭(차분한, 편안한 등)을 기획안에 맞춰 수정
- `MediaPlayer` 생성 시 `uri` 처리를 `toUri()`를 사용하도록 개선하여 안정성 확보
- `core:model`의 `Category` enum 순서 조정 및 상황 설정 관련 리소스 매핑 로직 업데이트
- `AnalyzePresentationRecordingUseCase` 추가 및 도메인 모델 정의
- `PracticeRepository` 및 구현체에 발표 녹음 분석 로직(`analyzePresentationRecording`) 추가
- `PracticeRemoteDataSource` 및 Ktor 기반 멀티파트 데이터 전송 로직 구현
- 발표 분석 요청/응답을 위한 네트워크 DTO(`PresentationRecordingAnalysisResponse` 등) 추가
- `AnalysisFlowViewModel`에서 파일 캐시 복사 및 분석 API 호출 로직 구현
- 분석 중 로딩 화면 및 결과 데이터 상태 관리 추가
- `PracticeRepositoryImplTest`에 발표 분석 시나리오 테스트 케이스 추가
- `AnalysisFailureHandler`를 추가하여 에러 유형별(인증, 서버, 네트워크 등) 처리 로직 구현
- 분석 실패 시 재시도 또는 스낵바 메시지 표시를 위한 `AnalysisFailureAction` 및 `AnalysisUiMessage` 정의
- `AnalysisFlowViewModel`에서 분석 요청 로직을 `PresentationAnalysisRequest` 데이터 클래스로 캡슐화하고 리팩터링
- 분석 실패 대응 테스트 코드(`AnalysisFailureHandlerTest`) 추가
- `AppErrorExt`에서 `FILE_UPLOAD_FAILED` 에러를 `INVALID_REQUEST`로 매핑하도록 수정 및 관련 테스트 추가
- 에러 메시지 표시를 위한 문자열 리소스 및 `AnalysisFlowUiEffect.ShowMessage` 추가
- `AnalysisFileCache` 인터페이스 및 `AnalysisFileCacheImpl` 구현체 추가
- `AnalysisFlowViewModel` 내부에 존재하던 파일 복사 로직을 `AnalysisFileCache`로 분리
- Content Uri로부터 임시 파일을 생성하고 앱 캐시 디렉토리로 복사하는 기능 모듈화
- `PresentationAnalysisRequest`를 `PresentationAnalysisSubmission`으로 네이밍 변경
- Hilt를 사용하여 `AnalysisFileCache` 의존성 주입 설정 추가 (`AnalysisModule`)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
Prezel/core/data/src/test/java/com/team/prezel/core/data/error/AppErrorExtTest.kt (1)

13-26: ⚡ Quick win

변경된 에러 매핑 2건도 테스트에 포함해 주세요.

현재 테스트는 FILE_UPLOAD_FAILED만 검증하고 있어, 이번 변경에 포함된 SENTENCE_NOT_FOUND, TERMS_NOT_FOUND 매핑 회귀를 놓치기 쉽습니다.

테스트 보강 예시
 class AppErrorExtTest {
     `@Test`
     fun `지원하지 않는 파일 형식 에러는 잘못된 요청으로 변환한다`() {
         val result = Result
             .failure<Unit>(
                 ApiException(
                     status = 400,
                     errorCode = ServerErrorCode.FILE_UPLOAD_FAILED,
                     message = "지원하지 않는 오디오 파일 형식입니다.",
                 ),
             ).mapDomainFailure()

         val exception = assertIs<AppException>(result.exceptionOrNull())
         assertEquals(AppError.INVALID_REQUEST, exception.error)
         assertEquals("지원하지 않는 오디오 파일 형식입니다.", exception.message)
     }
+
+    `@Test`
+    fun `문장 없음 에러는 서버 에러로 변환한다`() {
+        val result = Result
+            .failure<Unit>(
+                ApiException(
+                    status = 500,
+                    errorCode = ServerErrorCode.SENTENCE_NOT_FOUND,
+                    message = "문장을 찾을 수 없습니다.",
+                ),
+            ).mapDomainFailure()
+
+        val exception = assertIs<AppException>(result.exceptionOrNull())
+        assertEquals(AppError.SERVER_ERROR, exception.error)
+    }
+
+    `@Test`
+    fun `약관 없음 에러는 not found로 변환한다`() {
+        val result = Result
+            .failure<Unit>(
+                ApiException(
+                    status = 404,
+                    errorCode = ServerErrorCode.TERMS_NOT_FOUND,
+                    message = "약관을 찾을 수 없습니다.",
+                ),
+            ).mapDomainFailure()
+
+        val exception = assertIs<AppException>(result.exceptionOrNull())
+        assertEquals(AppError.NOT_FOUND, exception.error)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/core/data/src/test/java/com/team/prezel/core/data/error/AppErrorExtTest.kt`
around lines 13 - 26, Add two additional assertions in the same test (`지원하지 않는
파일 형식 에러는 잘못된 요청으로 변환한다`) to cover the new error mappings: create
Result.failure<Unit>(ApiException(..., errorCode =
ServerErrorCode.SENTENCE_NOT_FOUND, ...)).mapDomainFailure() and assert the
resulting exception is an AppException with the expected AppError (e.g.,
AppError.NOT_FOUND) and the original message; repeat the same for
ServerErrorCode.TERMS_NOT_FOUND. Use the same helpers used in the test
(Result.failure, ApiException, mapDomainFailure(), assertIs<AppException>(),
assertEquals(...)) so the new assertions mirror the existing FILE_UPLOAD_FAILED
check.
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/AnalysisLoadingScreen.kt (1)

24-27: ⚡ Quick win

LaunchedEffect 키에 콜백을 포함해 stale 호출을 방지해 주세요

Line 25의 LaunchedEffect(Unit)는 재구성 중 onFinished 참조가 바뀌어도 기존 람다를 호출할 수 있습니다. 콜백 자체를 key로 두는 편이 안전합니다.

수정 예시
-    if (onFinished != null) {
-        LaunchedEffect(Unit) {
+    if (onFinished != null) {
+        LaunchedEffect(onFinished) {
             delay(ANALYSIS_LOADING_DURATION_MILLIS)
             onFinished()
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/AnalysisLoadingScreen.kt`
around lines 24 - 27, LaunchedEffect(Unit) can call a stale onFinished after
recomposition; change the LaunchedEffect key to onFinished so the effect
restarts whenever the callback reference changes and will always invoke the
current lambda, i.e., wrap the delay(ANALYSIS_LOADING_DURATION_MILLIS) +
onFinished() inside LaunchedEffect(onFinished) (retaining
ANALYSIS_LOADING_DURATION_MILLIS and delay) and keep the existing null-check for
onFinished.
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt (1)

41-45: ⚡ Quick win

FILE/AUDIO 업로드 URI 검증이 null만 검사합니다

  • 현재 OpenDocument 취소/클리어 흐름은 null로 전달되므로 단계 진행이 열리지 않습니다.
  • 다만 다른 입력 경로에서 ""(빈 문자열)가 들어올 경우 통과 가능하니 isNullOrBlank()로 방어하는 게 더 안전합니다.
수정 예시
-                ScriptInputType.FILE_UPLOAD -> form.scriptFileUri != null
+                ScriptInputType.FILE_UPLOAD -> !form.scriptFileUri.isNullOrBlank()
                 ScriptInputType.DIRECT_INPUT -> form.script.isNotBlank()
             }
 
-            AnalysisFlowStep.AUDIO_UPLOAD -> form.audioFileUri != null
+            AnalysisFlowStep.AUDIO_UPLOAD -> !form.audioFileUri.isNullOrBlank()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt`
around lines 41 - 45, The check for upload URIs only guards against null which
lets empty strings slip through; update the predicates to use isNullOrBlank()
for file/audio URIs so cancelled/cleared flows (which may pass null) and
empty-string cases both fail. Specifically, replace usages like
form.audioFileUri != null and form.scriptFileUri != null (and any checks tied to
ScriptInputType.FILE_UPLOAD and AnalysisFlowStep.AUDIO_UPLOAD) with
isNullOrBlank() negation (e.g., !form.audioFileUri.isNullOrBlank()) so the step
gating is robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSourceImpl.kt`:
- Around line 61-71: The code only checks scriptFilePath for null but not for
empty/blank, so creating File(File("")) can fail; update the scriptFile
initialization to guard against blank values (e.g., replace val scriptFile =
scriptFilePath?.let(::File) with a check using takeIf or isNullOrBlank like
scriptFilePath?.takeIf(String::isNotBlank)?.let(::File)) so scriptFile remains
null for empty paths and the subsequent MultiPartFormDataContent
append("scriptFile", ...) block is skipped safely.
- Around line 60-89: The multipart builder currently loads whole files into
memory via file.readBytes() for scriptFile and audio (see append keys
"scriptFile" and "audio") and also constructs File("") when scriptFilePath is
empty; update PracticeRemoteDataSourceImpl to (1) validate audioFilePath and
scriptFilePath for null/blank before creating File objects (avoid creating
File("") and guard empty audio path), and (2) replace readBytes() usages with a
streaming/content-provider approach supported by Ktor multipart (use an
InputStream/ByteReadChannel provider or Ktor's InputProvider when appending
multipart parts) so the file is streamed instead of fully read into memory;
ensure headers (ContentType/ContentDisposition) are preserved when switching to
the streaming append.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandler.kt`:
- Around line 21-35: The current when(error) in the AnalysisFailureHandler
returns RetryFileUpload only for audio; add a branch for the script-recognition
error so script upload failures trigger the correct recovery flow: map
AppError.SCRIPT_FILE_RECOGNITION_FAILED (the enum/constant) to
AnalysisFailureAction.RetryFileUpload(uploadType = AnalysisUploadType.SCRIPT)
alongside the existing audio mapping in the same when expression so script
failures route to the SCRIPT retry flow.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`:
- Around line 57-59: The guard in moveNext() currently treats
AnalysisFlowStep.ANALYZING as an exception and allows a path to REPORT, which
lets duplicate taps or delayed events jump to the report before analysis
finishes; update moveNext() to treat ANALYZING as non-movable (remove the
special-case exception) so that if currentState.step ==
AnalysisFlowStep.ANALYZING the method returns early (or check
!currentState.canMoveNext || currentState.step == AnalysisFlowStep.ANALYZING
then return), and ensure any explicit transition logic that moves ANALYZING ->
REPORT (the code handling that transition) only runs after the analysis
completion callback/state change; apply the same change to the similar block
handling steps in the other method referenced (the block around the ANALYZING ->
REPORT transition).

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCacheImpl.kt`:
- Around line 40-45: When creating the temp file `target` in
AnalysisFileCacheImpl (the block that calls File.createTempFile and then
context.contentResolver.openInputStream(uri).use { ... target.outputStream().use
{ input.copyTo(output) } }), ensure `target` is deleted if any exception occurs
during opening/copying; wrap the input/output copy in a try/catch/finally (or
use runCatching { ... }.onFailure { target.delete() }) so that on failure you
call `target.delete()` (and optionally log) to avoid leaving residual files in
context.cacheDir.

---

Nitpick comments:
In
`@Prezel/core/data/src/test/java/com/team/prezel/core/data/error/AppErrorExtTest.kt`:
- Around line 13-26: Add two additional assertions in the same test (`지원하지 않는 파일
형식 에러는 잘못된 요청으로 변환한다`) to cover the new error mappings: create
Result.failure<Unit>(ApiException(..., errorCode =
ServerErrorCode.SENTENCE_NOT_FOUND, ...)).mapDomainFailure() and assert the
resulting exception is an AppException with the expected AppError (e.g.,
AppError.NOT_FOUND) and the original message; repeat the same for
ServerErrorCode.TERMS_NOT_FOUND. Use the same helpers used in the test
(Result.failure, ApiException, mapDomainFailure(), assertIs<AppException>(),
assertEquals(...)) so the new assertions mirror the existing FILE_UPLOAD_FAILED
check.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt`:
- Around line 41-45: The check for upload URIs only guards against null which
lets empty strings slip through; update the predicates to use isNullOrBlank()
for file/audio URIs so cancelled/cleared flows (which may pass null) and
empty-string cases both fail. Specifically, replace usages like
form.audioFileUri != null and form.scriptFileUri != null (and any checks tied to
ScriptInputType.FILE_UPLOAD and AnalysisFlowStep.AUDIO_UPLOAD) with
isNullOrBlank() negation (e.g., !form.audioFileUri.isNullOrBlank()) so the step
gating is robust.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/AnalysisLoadingScreen.kt`:
- Around line 24-27: LaunchedEffect(Unit) can call a stale onFinished after
recomposition; change the LaunchedEffect key to onFinished so the effect
restarts whenever the callback reference changes and will always invoke the
current lambda, i.e., wrap the delay(ANALYSIS_LOADING_DURATION_MILLIS) +
onFinished() inside LaunchedEffect(onFinished) (retaining
ANALYSIS_LOADING_DURATION_MILLIS and delay) and keep the existing null-check for
onFinished.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aeba7d0a-0e89-44a2-afa1-a7da7b5508e4

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce18c7 and 1fd8c75.

📒 Files selected for processing (25)
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PracticeRepositoryImpl.kt
  • Prezel/core/data/src/test/java/com/team/prezel/core/data/error/AppErrorExtTest.kt
  • Prezel/core/data/src/test/java/com/team/prezel/core/data/repository/practice/PracticeRepositoryImplTest.kt
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/practice/PracticeRepository.kt
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/practice/AnalyzePresentationRecordingUseCase.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationRecordingAnalysisResult.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSource.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSourceImpl.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/model/practice/PresentationAnalysisRequest.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/model/practice/PresentationRecordingAnalysisResponse.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/service/PracticeService.kt
  • Prezel/feature/analysis/impl/build.gradle.kts
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandler.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCache.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCacheImpl.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiEffect.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/di/AnalysisModule.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/model/AnalysisUiMessage.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/AnalysisLoadingScreen.kt
  • Prezel/feature/analysis/impl/src/main/res/values/strings.xml
  • Prezel/feature/analysis/impl/src/test/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandlerTest.kt
✅ Files skipped from review due to trivial changes (3)
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/model/AnalysisUiMessage.kt
  • Prezel/feature/analysis/impl/src/main/res/values/strings.xml
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationRecordingAnalysisResult.kt

- `PracticeRemoteDataSourceImpl`에서 `scriptFilePath`가 빈 문자열인 경우 파일로 처리하지 않도록 개선
- `AnalysisFileCacheImpl`에서 파일 복사 중 예외 발생 시 생성 중이던 임시 파일을 삭제하도록 수정
- 서버의 `FILE_IS_EMPTY` 에러를 도메인의 `SCRIPT_FILE_RECOGNITION_FAILED` 에러로 매핑하는 로직 추가
- `AnalysisFailureHandler`에 스크립트 인식 실패 시 재업로드를 유도하는 액션 추가
- 스크립트 파일 인식 실패 및 에러 변환 로직에 대한 단위 테스트 추가
- 대용량 파일 업로드 시 메모리 효율을 위해 `readBytes()` 대신 `ChannelProvider`를 사용하도록 변경
- `PracticeRemoteDataSourceImpl`에서 파일 경로에 대한 유효성 검사(`require`) 추가
- `AnalysisFlowViewModel`에서 분석 중(`ANALYZING`) 단계의 불필요한 이동 로직 제거 및 상태 전이 방식 개선
- 파일 인식 실패 또는 리포트 단계에서 잘못된 단계로 전환되지 않도록 방어 로직 적용
- `AnalysisFlowUiState`: `scriptFileUri` 및 `audioFileUri` 유효성 검사 시 `isNullOrBlank()`를 사용하여 빈 값 체크 강화
- `AppErrorExtTest`: `VOICE_ANALYSIS_FAILED` 에러가 `AppError.SERVER_ERROR`로 올바르게 매핑되는지 확인하는 테스트 추가
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt (2)

82-93: 🏗️ Heavy lift

분석 중 뒤로 가기 시 진행 중인 코루틴이 취소되지 않습니다.

moveBack()에서 ANALYZING -> AUDIO_UPLOAD로 전환할 때, Line 87에서 시작된 분석 코루틴은 계속 실행됩니다. 사용자가 뒤로 가서 새로운 분석을 시작하면, 이전 분석이 완료될 때 예기치 않은 상태 업데이트가 발생할 수 있습니다.

🔧 제안: 분석 Job을 추적하고 필요 시 취소
+    private var analysisJob: Job? = null
+
     private fun analyzePresentation() {
         val submission = currentState.form.toPresentationAnalysisSubmissionOrNull() ?: return
 
         updateState { copy(step = AnalysisFlowStep.ANALYZING) }
 
-        viewModelScope.launch {
+        analysisJob?.cancel()
+        analysisJob = viewModelScope.launch {
             submission
                 .analyzePresentationRecording()
                 .onSuccess(::handleAnalysisSuccess)
                 .onFailure { throwable -> handleAnalysisFailure(throwable.toAnalysisFailureAction()) }
         }
     }

moveBack()에서 ANALYZING 상태일 때 Job을 취소:

     private fun moveBack() {
+        if (currentState.step == AnalysisFlowStep.ANALYZING) {
+            analysisJob?.cancel()
+        }
         val previousStep = when (currentState.step) {

Also applies to: 184-194

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`
around lines 82 - 93, The analyzePresentation coroutine launched in
analyzePresentation() is not tracked so it continues after moveBack(); fix by
storing the Job returned from viewModelScope.launch in a property (e.g.,
analysisJob) when calling viewModelScope.launch { ... } and use that Job to
cancel the work when the user navigates back: in moveBack() check if current
step == AnalysisFlowStep.ANALYZING and call analysisJob?.cancel() (and clear the
property), ensuring cancellation happens before you update state to
AUDIO_UPLOAD; keep existing handlers
(handleAnalysisSuccess/handleAnalysisFailure) but make them cancellation-safe
(ignore or no-op on CancellationException) so a cancelled job does not perform
unexpected state updates.

234-236: 💤 Low value

scriptFileUri에도 빈 문자열 방어 처리 권장

Line 234의 scripttakeIf(String::isNotBlank)로 빈 문자열을 null로 변환하지만, Line 235의 scriptFileUri는 입력 타입만 체크합니다. canMoveNext에서 이미 isNullOrBlank() 검증이 있지만, 일관성과 방어적 프로그래밍을 위해 동일한 처리를 추가하면 좋습니다.

♻️ 제안 수정
-        scriptFileUri = scriptFileUri.takeIf { scriptInputType == ScriptInputType.FILE_UPLOAD },
+        scriptFileUri = scriptFileUri.takeIf { scriptInputType == ScriptInputType.FILE_UPLOAD && !it.isNullOrBlank() },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`
around lines 234 - 236, The assignment of scriptFileUri should also defensively
convert blank strings to null like script does: update the expression that sets
scriptFileUri (currently using scriptFileUri.takeIf { scriptInputType ==
ScriptInputType.FILE_UPLOAD }) to only keep non-blank values when
scriptInputType == ScriptInputType.FILE_UPLOAD; reference the scriptFileUri
variable and ScriptInputType.FILE_UPLOAD and ensure this matches the
blank-checking behavior used for script (and keeps canMoveNext's isNullOrBlank
expectations).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`:
- Around line 82-93: The analyzePresentation coroutine launched in
analyzePresentation() is not tracked so it continues after moveBack(); fix by
storing the Job returned from viewModelScope.launch in a property (e.g.,
analysisJob) when calling viewModelScope.launch { ... } and use that Job to
cancel the work when the user navigates back: in moveBack() check if current
step == AnalysisFlowStep.ANALYZING and call analysisJob?.cancel() (and clear the
property), ensuring cancellation happens before you update state to
AUDIO_UPLOAD; keep existing handlers
(handleAnalysisSuccess/handleAnalysisFailure) but make them cancellation-safe
(ignore or no-op on CancellationException) so a cancelled job does not perform
unexpected state updates.
- Around line 234-236: The assignment of scriptFileUri should also defensively
convert blank strings to null like script does: update the expression that sets
scriptFileUri (currently using scriptFileUri.takeIf { scriptInputType ==
ScriptInputType.FILE_UPLOAD }) to only keep non-blank values when
scriptInputType == ScriptInputType.FILE_UPLOAD; reference the scriptFileUri
variable and ScriptInputType.FILE_UPLOAD and ensure this matches the
blank-checking behavior used for script (and keeps canMoveNext's isNullOrBlank
expectations).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 409701d5-97d4-44dd-b739-549a4deebf80

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd8c75 and b8fc011.

📒 Files selected for processing (10)
  • Prezel/core/common/src/main/kotlin/com/team/prezel/core/common/error/AppError.kt
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.kt
  • Prezel/core/data/src/test/java/com/team/prezel/core/data/error/AppErrorExtTest.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSourceImpl.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandler.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCacheImpl.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.kt
  • Prezel/feature/analysis/impl/src/test/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandlerTest.kt
  • Prezel/feature/terms/impl/src/main/java/com/team/prezel/feature/terms/impl/TermsViewModel.kt

- `AnalysisFlowUiState`: `scriptFileUri` 및 `audioFileUri` 유효성 검사 시 `isNullOrBlank()`를 사용하여 빈 값 체크 강화
- `AppErrorExtTest`: `VOICE_ANALYSIS_FAILED` 에러가 `AppError.SERVER_ERROR`로 올바르게 매핑되는지 확인하는 테스트 추가
- `PresentationAnalysisRequest.kt` 내 선언된 Enum 클래스들을 제거하고 `String` 타입으로 대체
- `PracticeRemoteDataSource` 인터페이스 및 구현체의 파라미터 타입을 `String`으로 변경
- `PracticeMapper`에서 도메인 모델을 `String` 타입의 요청 값으로 매핑하도록 수정
- `AnalysisFlowViewModel`에서 스크립트 파일 URI 처리 시 Null 또는 공백 체크 로직 강화
- `TermsViewModel` 내 에러 처리 로직의 불필요한 Case 제거 및 `else` 분기 추가
- `AnalysisEntryBuilder` 내 불필요한 지역 변수 제거 및 람다식 정리
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt (1)

87-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

분석 요청 중복/경합으로 이전 응답이 현재 화면 상태를 덮어쓸 수 있습니다.

Line 87-92에서 in-flight Job 추적이 없고, Line 190에서 분석 중 뒤로가기가 허용됩니다. 이 상태에서 먼저 시작한 요청이 늦게 완료되면 Line 121-127에서 REPORT로 강제 전환될 수 있습니다.

🔧 제안 수정
 import androidx.lifecycle.viewModelScope
@@
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.Job
@@
 ) : BaseViewModel<AnalysisFlowUiState, AnalysisFlowUiIntent, AnalysisFlowUiEffect>(AnalysisFlowUiState()) {
+    private var analyzeJob: Job? = null
+
@@
     private fun analyzePresentation() {
         val submission = currentState.form.toPresentationAnalysisSubmissionOrNull() ?: return
 
         updateState { copy(step = AnalysisFlowStep.ANALYZING) }
 
-        viewModelScope.launch {
+        analyzeJob?.cancel()
+        analyzeJob = viewModelScope.launch {
             submission
                 .analyzePresentationRecording()
-                .onSuccess(::handleAnalysisSuccess)
+                .onSuccess { result ->
+                    if (currentState.step == AnalysisFlowStep.ANALYZING) {
+                        handleAnalysisSuccess(result)
+                    }
+                }
                 .onFailure { throwable -> handleAnalysisFailure(throwable.toAnalysisFailureAction()) }
         }
     }
@@
     private fun moveBack() {
+        if (currentState.step == AnalysisFlowStep.ANALYZING) {
+            analyzeJob?.cancel()
+        }
         val previousStep = when (currentState.step) {

Also applies to: 121-127, 190-200

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`
around lines 87 - 92, Avoid race conditions by tracking and invalidating
in-flight analysis requests: add a cancellable Job or a monotonically increasing
analysisRequestId as a ViewModel field and, before calling viewModelScope.launch
{ submission.analyzePresentationRecording() ... }, cancel the previous Job or
capture the new requestId; then in the success/failure callbacks
(handleAnalysisSuccess / handleAnalysisFailure via onSuccess/onFailure) verify
the captured requestId matches the current one (or that the Job wasn't
cancelled) before mutating UI state. Also update the back-navigation handling
(the code that allows navigating back while analysis is running) to
increment/clear the requestId or cancel the Job so any earlier responses are
ignored and do not force state transitions (e.g., to REPORT). Ensure
submission.analyzePresentationRecording() responses are gated by this
token/cancel logic and that toAnalysisFailureAction() handling also checks it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`:
- Around line 234-236: 현재 로직은 script를 입력 타입과 무관하게 보존하고 scriptFileUri는
FILE_UPLOAD일 때만 설정해 텍스트 스크립트와 파일이 동시에 전송될 수 있으니, script를 FILE_UPLOAD인 경우 비우고(또는
null로) 텍스트는 scriptInputType이 FILE_UPLOAD이 아닐 때만 유지하도록 변경하세요; 즉
AnalysisFlowViewModel의 해당 할당에서 script는 script.takeIf { scriptInputType !=
ScriptInputType.FILE_UPLOAD && it.isNotBlank() }로 조건을 추가하고 scriptFileUri는 기존처럼
scriptInputType == ScriptInputType.FILE_UPLOAD && !it.isNullOrBlank() 조건을 유지해 중복
전송을 방지하세요.

In
`@Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/component/HistoryPresentationCard.kt`:
- Line 159: The Category.WORK mapping in HistoryPresentationCard is using
R.string.feature_history_impl_category_report which is inconsistent with the
analysis screen; update the Category.WORK branch in the when/mapper inside
HistoryPresentationCard to use the same string resource the analysis screen uses
for the WORK (업무/비즈니스) label (replace
R.string.feature_history_impl_category_report with the correct WORK resource
used elsewhere, e.g. feature_history_impl_category_work or the shared work label
resource) so the enum displays consistently across screens.

---

Outside diff comments:
In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`:
- Around line 87-92: Avoid race conditions by tracking and invalidating
in-flight analysis requests: add a cancellable Job or a monotonically increasing
analysisRequestId as a ViewModel field and, before calling viewModelScope.launch
{ submission.analyzePresentationRecording() ... }, cancel the previous Job or
capture the new requestId; then in the success/failure callbacks
(handleAnalysisSuccess / handleAnalysisFailure via onSuccess/onFailure) verify
the captured requestId matches the current one (or that the Job wasn't
cancelled) before mutating UI state. Also update the back-navigation handling
(the code that allows navigating back while analysis is running) to
increment/clear the requestId or cancel the Job so any earlier responses are
ignored and do not force state transitions (e.g., to REPORT). Ensure
submission.analyzePresentationRecording() responses are gated by this
token/cancel logic and that toAnalysisFailureAction() handling also checks it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8064cde0-e24e-4b72-bc6e-2411e0599bb2

📥 Commits

Reviewing files that changed from the base of the PR and between b8fc011 and 396c225.

📒 Files selected for processing (25)
  • Prezel/app/build.gradle.kts
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PracticeMapper.kt
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PracticeRepositoryImpl.kt
  • Prezel/core/data/src/test/java/com/team/prezel/core/data/repository/practice/PracticeRepositoryImplTest.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Audience.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Category.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Purpose.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Style.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSource.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PracticeRemoteDataSourceImpl.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/audio/AudioUploadScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/component/AnalysisFileNameExt.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/situation/PresentationSituationScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/situation/SituationOptions.kt
  • Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/HistoryScreen.kt
  • Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/HistoryViewModel.kt
  • Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/component/HistoryItemList.kt
  • Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/component/HistoryPresentationCard.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeViewModel.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/body/PresentationSheet.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.kt
  • Prezel/feature/terms/impl/src/main/java/com/team/prezel/feature/terms/impl/TermsViewModel.kt
💤 Files with no reviewable changes (1)
  • Prezel/core/data/src/test/java/com/team/prezel/core/data/repository/practice/PracticeRepositoryImplTest.kt
✅ Files skipped from review due to trivial changes (1)
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/Purpose.kt

- `strings.xml` 내 발표 관련 칩(Category, Purpose, Style, Audience)의 리소스 ID를 도메인 모델 키워드에 맞춰 변경
- `HistoryPresentationCard`에서 변경된 리소스 ID를 참조하도록 수정
- `AnalysisFlowViewModel`에서 분석 요청 시 중복 요청 방지 및 취소 로직 추가 (`analyzeJob` 관리)
- 분석 중 뒤로 가기 시 진행 중인 분석 작업(`Job`)을 취소하도록 개선
- `PresentationAnalysisSubmission` 생성 시 스크립트 입력 타입(`ScriptInputType`)에 따른 데이터 매핑 로직 정교화
- `aspectRatio(1f)`를 추가하여 선택 옵션 아이템이 정방형 비율을 유지하도록 수정
- 아이템 내부 `Column`에 `fillMaxSize`를 적용하여 가용 영역을 전체로 확장
- 고정 높이 `Spacer`를 가변 가중치(`weight(1f)`)로 변경하여 내부 요소 배치 최적화
@HamBeomJoon HamBeomJoon merged commit 66275e0 into develop May 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat 새로운 기능 추가 또는 기존 기능 확장

Projects

None yet

Development

Successfully merging this pull request may close these issues.

분석 녹음 화면 구현 (음성 녹음)

2 participants