Skip to content

분석 녹음 화면 구현 (음성 직접 녹음)#142

Merged
HamBeomJoon merged 18 commits into
developfrom
feat/#116-analysis-voice-record
May 30, 2026
Merged

분석 녹음 화면 구현 (음성 직접 녹음)#142
HamBeomJoon merged 18 commits into
developfrom
feat/#116-analysis-voice-record

Conversation

@HamBeomJoon
Copy link
Copy Markdown
Contributor

@HamBeomJoon HamBeomJoon commented May 29, 2026

📌 작업 내용

  • 발표 분석 플로우에 음성 녹음 입력 경로를 추가했습니다.
  • 분석 시작 타입을 음성 녹음 / 음성 파일 업로드로 구분하고, 각 입력 방식에 맞게 재시도 및 뒤로가기 동작을 정리했습니다.
  • 음성 녹음 화면을 신규 구현했습니다.
    • 녹음 시작, 일시정지, 재개, 정지, 재녹음 흐름
    • 녹음 완료 후 기존 대본 로드
  • 분석 플로우 ViewModel 구조를 정리하고, step 전환 / nav key / retry 동작을 일관되게 처리하도록 개선했습니다.
  • 실패 케이스를 구분했습니다.
    • 음성 인식 실패
    • 파일 인식 실패
    • 대본 파일 인식 실패
    • 분석 실패
  • 대본 txt 파일 업로드 후 음성 녹음 화면 진입 시 업로드한 대본이 표시되도록 반영했습니다.
  • status bar 스타일을 분석 플로우 상태에 따라 동적으로 제어했습니다.
    • 음성 녹음 진입/녹음 중: bgMedium
    • 녹음 완료: bgRegular
    • 대본 zoom in: bgRegular
  • 전역 GlobalEvent 기반 status bar 스타일 변경 이벤트를 추가했습니다.
  • 분석 리포트 재분석 플로우와 분석 결과 화면 진입 경로를 연동했습니다.

## 🧩 관련 이슈 - close #116

📸 스크린샷


Summary by CodeRabbit

  • 신규 기능

    • 녹음 일시정지/재개 지원 및 실시간 파형·볼륨 표시
    • 대본 파일 업로드 옵션 추가
    • 재분석(리애널라이즈), 재녹음 및 대본 재작성 흐름 추가
    • 분석 단계별 내비게이션(중간 단계 이동) 및 리포트 새로고침 키
  • 개선

    • 음성 녹음 UI/컨트롤·상태바 스타일 동적 관리 강화
    • 권한/오류 안내 메시지 및 실패 화면·문구 개선

Review Change Stack

- 분석 플로우의 새로운 단계로 `VOICE_RECORDING` 단계 추가
- 음성 녹음 화면(`VoiceRecordingScreen`) 및 녹음 권한 핸들러 구현
- `AudioSessionState`에 `PausedRecording` 상태 추가 및 관련 일시정지/재개 로직 구현
- `RecordingAudioController` 및 `MediaRecorderSession`에 녹음 일시정지(`pause`), 재개(`resume`) 기능 추가
- 분석 데이터 제출 시 녹음된 파일 경로(`recordingFilePath`)를 포함하도록 개선
- 녹음 상태 관련 다국어 문자열 및 UI 에러 메시지 정의
- 연습 녹음 기능(`PracticeRecordingViewModel`)에 일시정지 상태 대응 로직 반영
- GlobalEvent에 `ChangeEdgeToEdgeStatusBarStyle`, `ResetEdgeToEdgeStatusBarStyle` 이벤트 추가
- GlobalEventBus에 비동기 컨텍스트 외에서 사용 가능한 `tryEmit` 메서드 추가
- `PrezelApp`에서 전역 이벤트를 수집하여 상태바 배경색 및 아이콘 스타일을 동적으로 변경하는 로직 구현
- 상태바 스타일을 위한 `EdgeToEdgeStatusBarStyle` 열거형 정의 (DEFAULT, BG_REGULAR, BG_MEDIUM)
- Activity 확장 함수를 통해 Window 인셋 컨트롤러 및 상태바 색상 설정 로직 추가
- 음성 녹음 화면(`VoiceRecordingScreen.kt`)에서 대규모 UI 컴포넌트들을 별도 파일로 분리
    - `VoiceRecordingButtonArea.kt`: 녹음 상태별 버튼 영역 분리
    - `VoiceRecordingContent.kt`: 스크립트 및 녹음 상태 표시 영역 분리
    - `VoiceRecordingStateProperties.kt`: `AudioSessionState` 기반 확장 프로퍼티 정리
    - `VoiceRecordingStatusBarStyle.kt`: 상태바 스타일 제어 로직 분리
- `AnalysisFlowViewModel` 내 UI 상태 변경 및 분석 요청 로직 개선
    - `reduceFormOrNull` 확장 함수를 통해 `AnalysisFlowUiIntent`에 따른 `AnalysisForm` 갱신 로직 간소화
    - `analyzePresentationRecording` 로직을 별도 함수로 추출하고 파일 캐시 처리 방식 개선
    - 스크립트 건너뛰기(`skipScript`) 및 파일 업로드 재시도 로직 보완
- `MediaRecordingAudioController` 내 `AudioSessionEffect` 전송 방식을 `emit` 확장 함수로 통일
- `ScriptInputScreen` 내 직접 입력 모드에서 텍스트 필드 하단 여백(`Spacer`) 추가 및 미리보기 케이스 추가
- `AnalysisScreen` 내 스낵바 표시 및 녹음 권한 핸들러 호출 로직 구조 개선 (컴포넌트 분리)
…-voice-record

# Conflicts:
#	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/contract/AnalysisFlowUiState.kt
- `AnalysisFlowViewModel`의 분석 로직을 개선하고 결과 화면 이동 방식을 `NavigateToReport` Effect 전송으로 변경
- `AnalyzePresentationRecordingUseCase` 사용을 중단하고 `AnalyzePresentationUseCase`로 단일화
- 음성 녹음 화면에 `PrezelVoiceChrome` 컴포넌트를 적용하여 시각적 피드백 강화
- `AudioSessionState`를 `VoiceChromeStatus`로 변환하는 확장 함수 추가
- `AnalysisFlowStep`에서 `REPORT` 단계를 제거하고 화면 전환 흐름 최적화
- `PrezelVoiceChrome` 컴포넌트가 부모 너비에 맞게 확장되도록 수정
- 일시정지 상태에 대한 시스템 문자열 리소스 수정 (`일시정지됨` -> `일시정지됐어요`)
- `AnalysisFlowViewModel`의 분석 로직을 개선하고 결과 화면 이동 방식을 `NavigateToReport` Effect 전송으로 변경
- `AnalyzePresentationRecordingUseCase` 사용을 중단하고 `AnalyzePresentationUseCase`로 단일화
- 음성 녹음 화면에 `PrezelVoiceChrome` 컴포넌트를 적용하여 시각적 피드백 강화
- `AudioSessionState`를 `VoiceChromeStatus`로 변환하는 확장 함수 추가
- `AnalysisFlowStep`에서 `REPORT` 단계를 제거하고 화면 전환 흐름 최적화
- `PrezelVoiceChrome` 컴포넌트가 부모 너비에 맞게 확장되도록 수정
- 일시정지 상태에 대한 시스템 문자열 리소스 수정 (`일시정지됨` -> `일시정지됐어요`)
- 분석 프로세스의 각 단계(`Schedule`, `Situation`, `Script`, `Recording` 등)에 대한 세분화된 `AnalysisNavKey` 정의
- 기존 분석 기록을 바탕으로 다시 녹음하거나 스크립트를 수정하여 재분석하는 기능 추가
- `AnalysisFlowViewModel`에서 단계별 상태 관리 및 `NavigateToStep` 이펙트 처리 로직 구현
- `PresentationRepository` 및 원격 데이터 소스에 대본과 음성 파일을 포함한 재분석 API(`reAnalyzePresentation`) 연동
- 리포트 화면에서 재녹음 및 스크립트 수정 화면으로 이동하는 네비게이션 로직 연결
- `AnalysisRoute`를 도입하여 `ViewModelStoreOwner`를 수동으로 관리하고 분석 flow 내 상태 유지 개선
- 분석 진행률(progress) 계산 로직을 변경된 단계에 맞춰 업데이트
- `AnalysisFlowViewModel` 내부에 혼재되어 있던 폼 매핑 및 상태 감소 로직을 `AnalysisFormMapper.kt`와 `AnalysisBackStateReducer.kt`로 분리
- `AnalysisEntryBuilder`에서 중복되는 네비게이션 엔트리 생성 로직을 `analysisEntry` 확장 함수로 공통화
- 녹음 및 오디오 세션 제어 로직을 별도 함수로 추출하여 `AnalysisFlowViewModel` 코드 간소화
- 뒤로 가기 동작 시 상태별 리소스 해제 및 데이터 초기화 로직 개선
- ViewModelStoreOwner 조회 실패 시 에러 메시지 한글화 및 가독성 개선
- `AnalysisNavKey`에 `AnalysisStartType` 필드를 추가하여 진입 경로 구분
- 시작 타입에 따라 오디오 입력 단계(`AUDIO_UPLOAD` 또는 `VOICE_RECORDING`)를 동적으로 결정하도록 개선
- `AnalysisFlowViewModel`에서 `startType`에 따른 단계 전환 및 재시도 로직 반영
- 홈 화면에서 파일 업로드와 음성 녹음 분석 진입 시 각각의 `startType` 전달하도록 수정
- 분석 재녹음 시 `FetchPresentationScriptDetailUseCase`를 사용하여 기존 대본을 자동으로 로드하도록 개선
- `AnalysisUiMessage`에 `SCRIPT_LOAD_FAILED` 에러 타입 및 문자열 리소스 추가
- 음성 녹음 화면에서 대본 확대/축소(`ZoomIn`, `ZoomOut`) 기능 및 UI 연동
- 대본 확대 시 상단 바 노출 여부 및 상태 표시줄 스타일 동적 변경 로직 추가
- `ReportNavKey`에 `refreshKey`를 추가하여 분석 완료 후 리포트로 이동 시 화면 갱신 강제
- 분석 리포트 화면에서 다이얼로그 확인 클릭 시 상태 초기화 로직 보완
- `AnalysisFlowUiState`의 뒤로가기 시 녹음 리셋 판단 로직 수정
- `VOICE_ANALYSIS_FAILED` 에러 타입 추가 및 서버 에러 코드 매핑
- 분석 실패 시 재시도 처리를 위한 `ANALYSIS_FAILED` 단계 및 화면 추가
- `AnalysisFileCache`에 URI로부터 텍스트를 읽어오는 `readTextFromUri` 메서드 구현
- `AnalysisFlowViewModel`에서 대본 파일 선택 시 텍스트 내용을 자동으로 불러오도록 개선
- 음성 인식 및 분석 실패 시나리오에 따른 내비게이션 및 에러 메시지 세분화
- 분석 프로세스 중 뒤로 가기 시 오디오 컨트롤러 리셋 로직 개선
- 분석 실패(error_analysis) 및 음성 인식 오류 관련 벡터 그래픽 리소스 추가 및 수정
- `AnalysisScreen`에서 대본 확장 여부와 녹음 상태에 따라 `EdgeToEdgeStatusBarStyle`을 결정하도록 개선
- `VoiceRecordingScreen`의 대본 확장 상태(`isScriptExpanded`)를 상위 컴포넌트로 전달하기 위한 콜백 추가
- `VoiceRecordingStatusBarStyle`에서 불필요한 `onDispose` 스타일 초기화 로직 제거
- `PrezelApp`의 메인 컨테이너 `Box`에 `fillMaxSize` 수정자 적용
- 녹음 가이드 스낵바 표시 로직을 별도 컴포저블에서 `AnalysisScreen` 내 `LaunchedEffect`로 통합
@HamBeomJoon HamBeomJoon self-assigned this May 29, 2026
@HamBeomJoon HamBeomJoon requested a review from moondev03 as a code owner May 29, 2026 14:11
@HamBeomJoon HamBeomJoon added the ✨ feat 새로운 기능 추가 또는 기존 기능 확장 label May 29, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Warning

Review limit reached

@HamBeomJoon, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 42 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 14234a6e-5d95-4c23-b23a-ab8f92ac4bd2

📥 Commits

Reviewing files that changed from the base of the PR and between 1372a52 and 58b619b.

📒 Files selected for processing (2)
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.kt
  • Prezel/feature/analysis/impl/src/main/res/values/strings.xml
📝 Walkthrough

워크스루

분석 플로우에 음성 녹음 단계를 추가하고 오디오 세션/컨트롤을 확장했으며, 네비게이션·뷰모델·네트워크·UI(녹음 화면, 파형, 권한 처리)·리소스가 함께 갱신되었습니다.

변경 사항

음성 녹음 및 재분석 통합

Layer / File(s) Summary
통합 PR 변경 전체
전체 범위 (see review stack)
오디오 코어(녹음/일시정지/재개, maxAmplitude), 오디오 컨트롤러(recordingVolumes, pause/resume), AudioSessionState 확장(PausedRecording), GlobalEvent(상태바 스타일 이벤트) 및 GlobalEventBus.tryEmit, Presentation re-analyze 파라미터(script, scriptFilePath), Analysis 네비게이션/상태/인텐트/이펙트 확장, AnalysisFlowViewModel 기능 확장(녹음 수집/제어/재분석), 음성 녹음 UI(VoiceRecording* 컴포저블), AnalysisFormMapper 및 AnalysisFileCache 텍스트 읽기, 네트워크 multipart 변경, 리포트 네비게이션 연동, 디자인 시스템(voice chrome/wave)과 문자열/resource 업데이트, 빌드 스크립트·.gitignore 소소 변경을 포함한 통합 변경입니다.

가능한 관련 PR:

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)

280-308: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

runCatching에서 CancellationException을 재던져 취소 시 UI 실패 전환을 막기

moveBack()으로 ANALYZING 중 analyzeJob?.cancel()이 발생해도 analyzePresentationRecording()/reAnalyzePresentationRecording()runCatching { ... getOrThrow() }CancellationExceptionResult.failure로 만들고, 상위 onFailure { throwable -> handleAnalysisFailure(throwable.toAnalysisFailureAction()) } 흐름을 타면서 toAnalysisFailureAction()error=nullAnalysisUiMessage.UNKNOWN_FAILED(ShowMessage)를 반환해 step이 잘못 변경될 수 있습니다(취소를 실패로 취급).

🐛 취소 예외 재던지기 제안
+import kotlinx.coroutines.CancellationException
             ).getOrThrow()
-        }
+        }.onFailure { if (it is CancellationException) throw it }

동일한 수정이 reAnalyzePresentationRecording에도 적용돼야 합니다.

🤖 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 280 - 308, The runCatching inside
PresentationAnalysisSubmission.analyzePresentationRecording (and likewise
reAnalyzePresentationRecording) is currently catching CancellationException and
converting it to Result.failure, causing cancellations to be treated as
failures; update the runCatching block to detect a CancellationException thrown
from inside (e.g., from analyzePresentationUseCase or file operations) and
rethrow it immediately (propagate cancellation) before letting runCatching wrap
other exceptions so cancellation short-circuits and does not drive
handleAnalysisFailure/toAnalysisFailureAction flows.
🧹 Nitpick comments (3)
Prezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.kt (1)

44-52: ⚡ Quick win

null 검사 추가를 권장합니다.

recorder!!.pause()recorder!!.resume()에서 null-assertion 연산자(!!)를 사용하면, recorder가 null일 때 NullPointerException이 발생합니다. runCatching으로 감싸져 있지만, NPE는 MediaRecorder API 호출 실패보다 명확한 에러 메시지를 제공하지 못합니다.

recorder가 null인 경우 명시적으로 처리하는 것이 더 안전합니다.

🛡️ Null 검사를 추가한 개선안
 override fun pause(): Result<Unit> =
     runCatching {
-        recorder!!.pause()
+        recorder?.pause() ?: error("Recorder not initialized")
     }

 override fun resume(): Result<Unit> =
     runCatching {
-        recorder!!.resume()
+        recorder?.resume() ?: error("Recorder not initialized")
     }
🤖 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/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.kt`
around lines 44 - 52, The pause() and resume() implementations use the non-null
assertion on recorder (recorder!!.pause()/recorder!!.resume()), which can throw
an NPE; update both methods in MediaRecorderSession (pause and resume) to
explicitly check recorder for null and return a failed Result with a clear error
(or Result.failure(IllegalStateException/appropriate exception)) when recorder
is null, otherwise call recorder.pause()/recorder.resume() inside runCatching as
before so callers receive a meaningful error instead of an NPE.
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.kt (1)

62-115: ⚡ Quick win

isScriptExpanded 상태가 이중으로 관리되어 동기화가 깨질 수 있습니다.

이 컴포저블이 rememberSaveable로 자체 isScriptExpanded를 보유하면서 onScriptExpandedChange로 부모(AnalysisScreen)에도 동일 상태를 전파합니다. 부모는 rememberSaveable(uiState.step)로 step 변경 시 false로 초기화하지만, 이 화면의 로컬 상태는 step 키가 없어 그대로 복원됩니다. VOICE_RECORDING 단계를 벗어났다가 재진입하는 경로(재녹음 등)에서 로컬은 true, 부모는 false로 어긋나 상태바 스타일/스크립트 확장 UI가 불일치할 수 있습니다.

상태를 부모로 끌어올려(isScriptExpanded를 파라미터로 전달) 단일 출처로 만드는 것을 권장합니다.

♻️ 상태 호이스팅 제안
 `@Composable`
 private fun VoiceRecordingScreen(
     script: String,
     recordingState: AudioSessionState,
     recordingVolumes: ImmutableList<Float>,
     analyzeEnabled: Boolean,
+    isScriptExpanded: Boolean,
     onClickRecordingControl: () -> Unit,
     onStopRecording: () -> Unit,
     onResetRecording: () -> Unit,
     onAnalyze: () -> Unit,
     onScriptExpandedChange: (Boolean) -> Unit,
     onBack: () -> Unit,
 ) {
-    var isScriptExpanded by rememberSaveable { mutableStateOf(false) }
-
     Column(

토글 시에는 onScriptExpandedChange(!isScriptExpanded)만 호출하도록 변경하고, 상위 VoiceRecordingScreen(uiState, ...) 오버로드 및 AnalysisScreen에서 isScriptExpanded를 함께 전달하세요.

🤖 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/recording/VoiceRecordingScreen.kt`
around lines 62 - 115, VoiceRecordingScreen currently keeps local
rememberSaveable isScriptExpanded while also calling onScriptExpandedChange
which causes duplicated, unsynced state; remove the local rememberSaveable
isScriptExpanded and instead accept isScriptExpanded as a parameter of
VoiceRecordingScreen, update the internal VoiceRecordingContent call to use that
passed-in isScriptExpanded, and change the toggle lambda to call
onScriptExpandedChange(!isScriptExpanded) only; update the caller (e.g.,
AnalysisScreen or any overload of VoiceRecordingScreen) to own the
rememberSaveable state and pass it down so there is a single source of truth.
Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt (1)

310-333: ⚡ Quick win

오디오/스크립트 경로 해석 로직이 두 함수에 중복됩니다.

analyzePresentationRecordingreAnalyzePresentationRecording에서 audioFilePath/scriptFile 계산이 동일하게 반복됩니다. 공통 헬퍼로 추출하면 향후 분기 위험과 중복을 줄일 수 있습니다.

♻️ 경로 해석 헬퍼 추출 제안
private suspend fun PresentationAnalysisSubmission.resolveAudioFilePath(): String? =
    audioFileUri?.let { uri ->
        analysisFileCache.copyUriToCache(uriString = uri, prefix = "audio").absolutePath
    } ?: recordingFilePath

private suspend fun PresentationAnalysisSubmission.resolveScriptFilePath(): String? =
    scriptFileUri?.let { uri ->
        analysisFileCache.copyUriToCache(uriString = uri, prefix = "script").absolutePath
    }

두 함수에서 동일하게 사용:

-            val audioFilePath = audioFileUri
-                ?.let { uri ->
-                    val audioFile = analysisFileCache.copyUriToCache(uriString = uri, prefix = "audio")
-                    audioFile.absolutePath
-                }
-                ?: recordingFilePath
-            val scriptFile = scriptFileUri?.let { uri ->
-                analysisFileCache.copyUriToCache(uriString = uri, prefix = "script")
-            }
+            val audioFilePath = resolveAudioFilePath()
+            val scriptFilePath = resolveScriptFilePath()
🤖 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 310 - 333, Duplicate audio/script path resolution exists in
analyzePresentationRecording and reAnalyzePresentationRecording; extract helpers
on PresentationAnalysisSubmission to centralize logic. Add suspend extension
functions resolveAudioFilePath() and resolveScriptFilePath() that use
audioFileUri, scriptFileUri, recordingFilePath and
analysisFileCache.copyUriToCache(prefix = "audio"/"script") to return the
computed paths, then replace the inline logic in analyzePresentationRecording
and reAnalyzePresentationRecording to call these helpers and pass the returned
paths into reAnalyzePresentationUseCase / analyzePresentationUseCase.
🤖 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/app/src/main/java/com/team/prezel/ui/PrezelApp.kt`:
- Around line 196-205: The current applyEdgeToEdgeStatusBarStyle() always sets
insetsController.isAppearanceLightStatusBars = true which forces dark icons;
change it to compute isAppearanceLightStatusBars based on the selected style
and/or current theme/background brightness (e.g., for BG_REGULAR and BG_MEDIUM
in dark theme set false, for DEFAULT decide from window decor background or
luminance of the computed statusBarColor). Update the block around
WindowCompat.getInsetsController(window, view) and the statusBarColor
calculation to compute a boolean (derived from style, bgRegular/bgMedium colors
or theme.isLight) and assign that to
insetsController.isAppearanceLightStatusBars instead of hardcoding true.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStateProperties.kt`:
- Around line 74-78: The Int.toTimerText() function uses String.format without
an explicit Locale which can produce locale-dependent output; update the format
call in Int.toTimerText to pass a fixed locale (e.g., Locale.US or Locale.ROOT)
so the "%02d:%02d" rendering is stable across locales by invoking the format
overload that accepts a Locale.

---

Outside diff comments:
In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`:
- Around line 280-308: The runCatching inside
PresentationAnalysisSubmission.analyzePresentationRecording (and likewise
reAnalyzePresentationRecording) is currently catching CancellationException and
converting it to Result.failure, causing cancellations to be treated as
failures; update the runCatching block to detect a CancellationException thrown
from inside (e.g., from analyzePresentationUseCase or file operations) and
rethrow it immediately (propagate cancellation) before letting runCatching wrap
other exceptions so cancellation short-circuits and does not drive
handleAnalysisFailure/toAnalysisFailureAction flows.

---

Nitpick comments:
In
`@Prezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.kt`:
- Around line 44-52: The pause() and resume() implementations use the non-null
assertion on recorder (recorder!!.pause()/recorder!!.resume()), which can throw
an NPE; update both methods in MediaRecorderSession (pause and resume) to
explicitly check recorder for null and return a failed Result with a clear error
(or Result.failure(IllegalStateException/appropriate exception)) when recorder
is null, otherwise call recorder.pause()/recorder.resume() inside runCatching as
before so callers receive a meaningful error instead of an NPE.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.kt`:
- Around line 310-333: Duplicate audio/script path resolution exists in
analyzePresentationRecording and reAnalyzePresentationRecording; extract helpers
on PresentationAnalysisSubmission to centralize logic. Add suspend extension
functions resolveAudioFilePath() and resolveScriptFilePath() that use
audioFileUri, scriptFileUri, recordingFilePath and
analysisFileCache.copyUriToCache(prefix = "audio"/"script") to return the
computed paths, then replace the inline logic in analyzePresentationRecording
and reAnalyzePresentationRecording to call these helpers and pass the returned
paths into reAnalyzePresentationUseCase / analyzePresentationUseCase.

In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.kt`:
- Around line 62-115: VoiceRecordingScreen currently keeps local
rememberSaveable isScriptExpanded while also calling onScriptExpandedChange
which causes duplicated, unsynced state; remove the local rememberSaveable
isScriptExpanded and instead accept isScriptExpanded as a parameter of
VoiceRecordingScreen, update the internal VoiceRecordingContent call to use that
passed-in isScriptExpanded, and change the toggle lambda to call
onScriptExpandedChange(!isScriptExpanded) only; update the caller (e.g.,
AnalysisScreen or any overload of VoiceRecordingScreen) to own the
rememberSaveable state and pass it down so there is a single source of truth.
🪄 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: 651044c0-e45a-4169-b8ce-68bd2cd5b815

📥 Commits

Reviewing files that changed from the base of the PR and between c7dc878 and d786791.

📒 Files selected for processing (60)
  • Prezel/.gitignore
  • Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
  • Prezel/core/audio/build.gradle.kts
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/AudioRecorderSession.kt
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/AudioSessionState.kt
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.kt
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecordingAudioController.kt
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/RecordingAudioController.kt
  • Prezel/core/common/src/main/kotlin/com/team/prezel/core/common/error/AppError.kt
  • Prezel/core/common/src/main/kotlin/com/team/prezel/core/common/event/GlobalEvent.kt
  • Prezel/core/common/src/main/kotlin/com/team/prezel/core/common/event/GlobalEventBus.kt
  • Prezel/core/common/src/main/kotlin/com/team/prezel/core/common/event/GlobalEventBusImpl.kt
  • 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/PresentationRepositoryImpl.kt
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/voice/PrezelVoiceChrome.kt
  • Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/voice/PrezelVoiceChromeWave.kt
  • Prezel/core/designsystem/src/main/res/values/strings.xml
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/ReAnalyzePresentationUseCase.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.kt
  • 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/AnalysisBackStateReducer.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/AnalysisFormMapper.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/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/model/AnalysisUiMessage.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/recording/RecordAudioPermission.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingButtonArea.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingContent.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStateProperties.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStatusBarStyle.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/script/ScriptInputScreen.kt
  • Prezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_error_analysis.xml
  • Prezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_error_voice.xml
  • Prezel/feature/analysis/impl/src/main/res/values/strings.xml
  • Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/navigation/HistoryEntryBuilder.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt
  • Prezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/PracticeRecordingViewModel.kt
  • Prezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/RecordAudioPermission.kt
  • Prezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/component/PracticeRecordingContent.kt
  • Prezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/component/PracticeRecordingControl.kt
  • Prezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/contract/PracticeRecordingUiState.kt
  • Prezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.kt
  • Prezel/feature/report/impl/build.gradle.kts
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt

Comment thread Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
- `MediaRecorderSession`의 `pause`, `resume` 메서드에서 안전한 호출(Safe Call) 적용 및 초기화 예외 처리 추가
- `AnalysisFlowViewModel` 내 파일 경로 해결 로직을 `resolveAudioFilePath`, `resolveScriptFilePath`로 분리하여 코드 중복 제거
- 분석 요청 처리 시 `CancellationException` 발생 시 코루틴 취소가 정상적으로 전파되도록 수정
- `VoiceRecordingScreen`의 스크립트 확장 상태(`isScriptExpanded`)를 상위 컴포넌트에서 제어하도록 끌어올리기(State Hoisting) 수행
- `String.format` 사용 시 `Locale.US`를 명시하여 지역 설정에 따른 포맷 오동작 방지
- `MainActivity`에서 테마 설정을 라이트 모드(`isDarkTheme = false`)로 고정
…-voice-record

# Conflicts:
#	Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
#	Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt
- `PresentationRemoteDataSourceImpl`에서 불필요한 `toAudioMultipart` 확장 함수와 중복된 `appendAudioPart` 선언 제거
- `AnalysisFlowViewModel` 내 파일 경로 해결 로직(`resolveAudioFilePath`, `resolveScriptFilePath`)을 외부 private 함수로 분리하고 `AnalysisFileCache` 의존성을 명시적으로 전달하도록 수정
- `fetchPresentationDetailUseCase` 결과 처리 시 `detail.analysisSummary`를 참조하도록 데이터 접근 로직 보정
- `AnalysisScreen`의 가독성 향상을 위해 `AnalysisFormInputStepContent` 컴포저블을 분리하고 단계별 렌더링 로직 최적화
- `PrezelApp.kt`에서 불필요한 레이아웃 중복 코드 및 잘못된 위치의 `EdgeToEdgeStatusBarBackground` 호출부 정리
- `VoiceRecordingStatusBarStyle`: `DisposableEffect`를 추가하여 컴포저블이 해제될 때 `ResetEdgeToEdgeStatusBarStyle` 이벤트를 발생시켜 상태바 스타일을 초기화하도록 개선
- `AnalysisScreen`: 상태바 스타일 적용 로직을 음성 녹음 단계(`VOICE_RECORDING`) 내부로 이동하여 특정 단계에서만 스타일이 활성화되도록 수정
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: 1

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/recording/VoiceRecordingScreen.kt (1)

117-128: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

닫기 버튼이 edge-to-edge 안전 영역과 스크린리더 둘 다 놓치고 있습니다.

Line 128/150의 4dp 상단 여백만으로는 노치 기기에서 버튼이 상태바 아래로 밀릴 수 있고, Line 174의 contentDescription = null 때문에 TalkBack에서는 이 탈출 액션을 식별할 수 없습니다.

수정 예시
+import androidx.compose.foundation.layout.statusBarsPadding
...
     Box(
         modifier = Modifier
             .fillMaxWidth()
+            .statusBarsPadding()
             .height(64.dp)
             .background(PrezelTheme.colors.bgRegular),
     ) {
...
         Box(
-            modifier = Modifier.fillMaxWidth(),
+            modifier = Modifier
+                .fillMaxWidth()
+                .statusBarsPadding(),
         ) {
...
         Icon(
             painter = painterResource(PrezelIcons.Cancel),
-            contentDescription = null,
+            contentDescription = stringResource(R.string.feature_analysis_impl_close),
             modifier = Modifier.size(24.dp),
             tint = PrezelTheme.colors.iconRegular,
         )

feature_analysis_impl_close 같은 라벨도 함께 추가해 주세요.

Also applies to: 143-150, 168-176

🤖 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/recording/VoiceRecordingScreen.kt`
around lines 117 - 128, The close button is missing safe-area padding and an
accessible label; update the VoiceRecordingCompletedTopBar and all other
close-button usages in this file (the VoiceRecordingCloseButton invocations) to
include system/status-bar safe padding (e.g., use Modifier.statusBarsPadding()
or Modifier.systemBarsPadding() before align/padding) so the button isn't placed
under a notch, and replace contentDescription = null with a localized string
resource (e.g., R.string.feature_analysis_impl_close) passed into
VoiceRecordingCloseButton (or set contentDescription directly) so TalkBack can
identify the escape action; apply the same fixes to the other
top-bar/close-button occurrences in this file.
🤖 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/app/src/main/java/com/team/prezel/MainActivity.kt`:
- Around line 33-35: MainActivity currently forces light mode by calling
PrezelTheme(isDarkTheme = false) inside setContent; remove the hardcoded
isDarkTheme = false and let PrezelTheme use its default/system-driven value (or
pass a proper boolean from rememberPrezelAppState or isSystemInDarkTheme()) so
theme selection respects user/system settings; update the setContent call to
invoke PrezelTheme without the isDarkTheme override (or wire it to a computed
value from rememberPrezelAppState or Compose's isSystemInDarkTheme()) and verify
status bar styling is handled separately if needed.

---

Outside diff comments:
In
`@Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.kt`:
- Around line 117-128: The close button is missing safe-area padding and an
accessible label; update the VoiceRecordingCompletedTopBar and all other
close-button usages in this file (the VoiceRecordingCloseButton invocations) to
include system/status-bar safe padding (e.g., use Modifier.statusBarsPadding()
or Modifier.systemBarsPadding() before align/padding) so the button isn't placed
under a notch, and replace contentDescription = null with a localized string
resource (e.g., R.string.feature_analysis_impl_close) passed into
VoiceRecordingCloseButton (or set contentDescription directly) so TalkBack can
identify the escape action; apply the same fixes to the other
top-bar/close-button occurrences in this file.
🪄 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: 5db5c0f5-b59a-492b-bbd7-68ab0a107c8c

📥 Commits

Reviewing files that changed from the base of the PR and between d786791 and 1372a52.

📒 Files selected for processing (14)
  • Prezel/app/src/main/java/com/team/prezel/MainActivity.kt
  • Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.kt
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.kt
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.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/recording/VoiceRecordingScreen.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStateProperties.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStatusBarStyle.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (11)
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStatusBarStyle.kt
  • Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.kt
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.kt
  • Prezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.kt
  • Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStateProperties.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/AnalysisFlowViewModel.kt

Comment thread Prezel/app/src/main/java/com/team/prezel/MainActivity.kt
- `VoiceRecordingScreen`의 닫기 아이콘에 `contentDescription` 적용 및 관련 문자열 리소스 추가
- `MainActivity`에서 `PrezelTheme` 호출 시 불필요한 `isDarkTheme` 매개변수 제거하여 기본 테마 설정을 따르도록 수정
@HamBeomJoon HamBeomJoon merged commit e3831de into develop May 30, 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