분석 녹음 화면 구현 (음성 직접 녹음)#142
Conversation
- 분석 플로우의 새로운 단계로 `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`로 통합
|
Warning Review limit reached
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 Walkthrough워크스루분석 플로우에 음성 녹음 단계를 추가하고 오디오 세션/컨트롤을 확장했으며, 네비게이션·뷰모델·네트워크·UI(녹음 화면, 파형, 권한 처리)·리소스가 함께 갱신되었습니다. 변경 사항음성 녹음 및 재분석 통합
가능한 관련 PR:
|
There was a problem hiding this comment.
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() }가CancellationException을Result.failure로 만들고, 상위onFailure { throwable -> handleAnalysisFailure(throwable.toAnalysisFailureAction()) }흐름을 타면서toAnalysisFailureAction()이error=null로AnalysisUiMessage.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 winnull 검사 추가를 권장합니다.
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오디오/스크립트 경로 해석 로직이 두 함수에 중복됩니다.
analyzePresentationRecording와reAnalyzePresentationRecording에서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
📒 Files selected for processing (60)
Prezel/.gitignorePrezel/app/src/main/java/com/team/prezel/ui/PrezelApp.ktPrezel/core/audio/build.gradle.ktsPrezel/core/audio/src/main/java/com/team/prezel/core/audio/AudioRecorderSession.ktPrezel/core/audio/src/main/java/com/team/prezel/core/audio/AudioSessionState.ktPrezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.ktPrezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecordingAudioController.ktPrezel/core/audio/src/main/java/com/team/prezel/core/audio/RecordingAudioController.ktPrezel/core/common/src/main/kotlin/com/team/prezel/core/common/error/AppError.ktPrezel/core/common/src/main/kotlin/com/team/prezel/core/common/event/GlobalEvent.ktPrezel/core/common/src/main/kotlin/com/team/prezel/core/common/event/GlobalEventBus.ktPrezel/core/common/src/main/kotlin/com/team/prezel/core/common/event/GlobalEventBusImpl.ktPrezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.ktPrezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.ktPrezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/voice/PrezelVoiceChrome.ktPrezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/voice/PrezelVoiceChromeWave.ktPrezel/core/designsystem/src/main/res/values/strings.xmlPrezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.ktPrezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/ReAnalyzePresentationUseCase.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.ktPrezel/feature/analysis/api/src/main/java/com/team/prezel/feature/analysis/api/AnalysisNavKey.ktPrezel/feature/analysis/impl/build.gradle.ktsPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisBackStateReducer.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFailureHandler.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFormMapper.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisScreen.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCache.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/cache/AnalysisFileCacheImpl.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiEffect.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiIntent.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/contract/AnalysisFlowUiState.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/model/AnalysisUiMessage.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/RecordAudioPermission.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingButtonArea.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingContent.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStateProperties.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStatusBarStyle.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/result/FileRecognitionFailedScreen.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/script/ScriptInputScreen.ktPrezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_error_analysis.xmlPrezel/feature/analysis/impl/src/main/res/drawable/feature_analysis_impl_error_voice.xmlPrezel/feature/analysis/impl/src/main/res/values/strings.xmlPrezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/navigation/HistoryEntryBuilder.ktPrezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.ktPrezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/PracticeRecordingViewModel.ktPrezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/RecordAudioPermission.ktPrezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/component/PracticeRecordingContent.ktPrezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/component/PracticeRecordingControl.ktPrezel/feature/practice/impl/src/main/java/com/team/prezel/feature/practice/impl/recording/contract/PracticeRecordingUiState.ktPrezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.ktPrezel/feature/report/impl/build.gradle.ktsPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportViewModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiEffect.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.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`) 내부로 이동하여 특정 단계에서만 스타일이 활성화되도록 수정
There was a problem hiding this comment.
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
📒 Files selected for processing (14)
Prezel/app/src/main/java/com/team/prezel/MainActivity.ktPrezel/app/src/main/java/com/team/prezel/ui/PrezelApp.ktPrezel/core/audio/src/main/java/com/team/prezel/core/audio/MediaRecorderSession.ktPrezel/core/data/src/main/java/com/team/prezel/core/data/repository/PresentationRepositoryImpl.ktPrezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/repository/presentation/PresentationRepository.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSource.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/datasource/PresentationRemoteDataSourceImpl.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisFlowViewModel.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/AnalysisScreen.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingScreen.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStateProperties.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/recording/VoiceRecordingStatusBarStyle.ktPrezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.ktPrezel/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
- `VoiceRecordingScreen`의 닫기 아이콘에 `contentDescription` 적용 및 관련 문자열 리소스 추가 - `MainActivity`에서 `PrezelTheme` 호출 시 불필요한 `isDarkTheme` 매개변수 제거하여 기본 테마 설정을 따르도록 수정
📌 작업 내용
bgMediumbgRegularbgRegularGlobalEvent기반 status bar 스타일 변경 이벤트를 추가했습니다.## 🧩 관련 이슈 - close #116
📸 스크린샷
Summary by CodeRabbit
신규 기능
개선