Skip to content

대본 교정 화면 구현#143

Open
moondev03 wants to merge 6 commits into
developfrom
feat/#122-대본-교정-화면-구현

Hidden character warning

The head ref may contain hidden characters: "feat/#122-\ub300\ubcf8-\uad50\uc815-\ud654\uba74-\uad6c\ud604"
Open

대본 교정 화면 구현#143
moondev03 wants to merge 6 commits into
developfrom
feat/#122-대본-교정-화면-구현

Conversation

@moondev03
Copy link
Copy Markdown
Member

@moondev03 moondev03 commented May 31, 2026

📌 작업 내용

  • 대본 교정 화면 구현
  • HideOnScrollTopBarLayout 공통 컴포넌트 구현

🧩 관련 이슈


📸 스크린샷

Screen_recording_20260531_192840.mp4

📢 논의하고 싶은 내용

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 발표 스크립트 교정 화면 추가: 철자/문법 오류를 시각적으로 확인하고 개별 또는 일괄 적용 가능
    • 현재 스크립트를 클립보드로 복사 기능 추가
    • 리포트 화면에서 스크립트 분석으로 직접 이동 가능
  • 개선사항

    • 스크립트 오류 타입 체계 강화로 더 정확한 에러 분류
    • 과거 발표의 경우 연습 기록 표시, 예정된 발표는 미표시로 개선
    • 전체 UI 구조 정리로 코드 유지보수성 향상

moondev03 added 6 commits May 31, 2026 14:38
* **refactor: 발표 상세 데이터 모델 및 UI 상태의 연습 기록 필드 널 허용 변경**
    * `PresentationDetailWithPracticeRecords` 및 `AnalysisReportUiState.Content` 내 `practiceRecords` 타입을 널 허용(`?`)으로 변경하여 연습 기록이 없는 경우를 대응했습니다.
    * `AnalysisReportUiStateMapper`에서 널 허용 타입 변경에 따른 매핑 로직을 수정했습니다.

* **refactor: `FetchPresentationDetailUseCase` 로직 분리 및 최적화**
    * `isPast` 플래그에 따라 과거 발표와 예정된 발표의 상세 정보를 가져오는 로직을 `getPastPresentationDetail`과 `getUpcomingPresentationDetail`로 분리했습니다.
    * 예정된 발표 조회 시에는 연습 기록(PracticeRecords)을 요청하지 않고 `null`로 설정하도록 변경했습니다.
    * 불필요한 `coroutineScope` 및 `async` 블록을 제거하고 로직을 단순화했습니다.

* **refactor: 연습 기록 유무에 따른 UI 노출 제어 및 프리뷰 데이터 수정**
    * `ReportBodyContent`에서 과거 발표이면서 연습 기록이 존재하는 경우에만 `SelfFeedbackSection`이 표시되도록 조건을 강화했습니다.
    * `ReportPreviewUiState`에서 예정된 발표(Upcoming) 시나리오에 맞춰 `selfFeedback`과 `practiceRecords`가 없는 상태를 정의했습니다.
* **refactor: Report 관련 클래스 및 패키지 경로 변경**
    * `AnalysisReportScreen` 및 `AnalysisReportViewModel`의 위치를 `feature.report.impl`에서 `feature.report.impl.report`로 이동했습니다.
    * `component`, `contract`, `model`, `preview` 등 보고서 화면 구성을 위한 내부 패키지들을 모두 `report` 패키지 하위로 이동하여 구조를 체계화했습니다.

* **refactor: 패키지 이동에 따른 참조 수정**
    * `ReportEntryBuilder`에서 변경된 `AnalysisReportScreen` 및 `AnalysisReportViewModel`의 import 경로를 업데이트했습니다.
    * 각 컴포저블 파일(`GrowthGraphSection`, `AccuracySection`, `ScriptAnalysisSection` 등) 내에서 변경된 `model` 및 `contract` 패키지 참조를 일괄 수정했습니다.

* **style: 불필요한 코드 정리 및 import 최적화**
    * 패키지 경로 변경 과정에서 사용되지 않는 import를 제거하고, `ReportScreenLayout` 내 `ScrollState` 등의 참조를 정리했습니다.
* **fix: `getUpcomingPresentationDetail` 로직 수정**
    * 예정된 발표 상세 정보를 조회할 때 `presentationRepository.getPastPresentationDetail`을 호출하던 코드를 `getUpcomingPresentationDetail`로 올바르게 수정했습니다.
* **feat: `HideOnScrollTopBarLayout` 컴포저블 추가**
    * 사용자의 스크롤 방향과 이동 거리를 감지하여 상단 바의 가시성을 자동으로 조절하는 레이아웃 컴포넌트를 구현했습니다.
    * `scrollThreshold`를 통해 상단 바가 전환되는 스크롤 임계값을 설정할 수 있도록 지원합니다.
    * `AnimatedVisibility`를 활용하여 상단 바가 나타나거나 사라질 때 부드러운 페이드 효과를 적용했습니다.
    * `LaunchedEffect` 내에서 `snapshotFlow`를 사용하여 리컴포지션을 최소화하면서 효율적으로 스크롤 상태를 관찰하도록 로직을 최적화했습니다.
    * 상단 바(topBar), 스크롤 가능한 본문(body), 고정 하단 바(bottomBar)로 구성된 표준 레이아웃 구조를 제공합니다.
* **feat: 발표 대본 교정 화면(`ScriptScreen`) 및 관련 컴포넌트 구현**
    * 대본 내 맞춤법 및 문장 호응 오류를 하이라이트하고 교정 제안을 보여주는 `ScriptTextContent`를 구현했습니다.
    * 선택한 오류의 교정 내용과 사유를 보여주는 팝업(`ScriptCorrectionPopup`)을 추가했습니다.
    * 오류 통계 요약(`ScriptResultSummary`) 및 전체 복사/일괄 수정 액션 바(`ScriptActionBar`)를 추가했습니다.

* **feat: 대본 교정 로직 및 ViewModel 구현**
    * `ScriptViewModel`을 통해 개별 교정 적용, 일괄 교정 적용, 대본 재구성(Rebuild) 로직을 구현했습니다.
    * 교정된 대본을 클립보드에 복사하는 `ScriptUiEffect` 처리를 추가했습니다.
    * `ScriptCorrectionUiModel`을 정의하여 UI 상태 관리에 필요한 데이터 구조를 설계했습니다.

* **refactor: 분석 리포트 화면 내 대본 분석 연동 및 네비게이션 확장**
    * 분석 리포트의 '전체 오류' 카드 클릭 시 대본 교정 화면으로 이동하는 로직을 추가했습니다.
    * `Navigation3` 기반의 `ReportInnerNavKey.ScriptCorrection`을 정의하고 `ReportEntryBuilder`에 경로를 등록했습니다.
    * `MetricResultCard` 컴포넌트에 클릭 이벤트를 처리할 수 있도록 `onClick` 파라미터를 추가했습니다.

* **core: 도메인 모델 및 데이터 매퍼 개선**
    * `ScriptErrorType`을 `String`에서 `enum class`로 변경하여 타입 안정성을 확보했습니다.
    * `PresentationScriptDetail` 모델에서 불필요한 필드를 정리하고 매퍼 로직을 최신화했습니다.

* **build: 의존성 설정 업데이트**
    * 네비게이션 및 데이터 처리를 위해 `kotlinx.serialization` 플러그인을 추가했습니다.
* **feat: `ScriptViewModel` 내 실제 UseCase 연동 및 데이터 로딩 로직 구현**
    * 하드코딩된 더미 데이터 대신 `FetchPresentationScriptDetailUseCase`를 사용하여 대본 분석 상세 데이터를 조회하도록 수정했습니다.
    * 데이터 로드 결과에 따라 성공 시 UI 상태를 갱신하고, 실패 시 `ShowMessage`와 `NavigateToBack` 이펙트를 전달하도록 처리했습니다.

* **refactor: `ScriptUiEffect` 정의 확장 및 UI 핸들링 로직 추가**
    * 화면 종료를 위한 `NavigateToBack`과 메시지 노출을 위한 `ShowMessage` 이펙트를 추가했습니다.
    * `ScriptScreen`에서 `LaunchedEffect`를 통해 에러 발생 시 스낵바를 노출하고 이전 화면으로 네비게이션하는 로직을 구현했습니다.

* **refactor: 대본 교정 관련 모델 명칭 및 UI 컴포넌트 구조 개선**
    * `ScriptErrorType`의 상수를 `SPELL`에서 `SPELLING`으로 변경하여 명칭의 일관성을 맞췄습니다.
    * `ScriptFooter`에 포함되어 있던 `ScriptActionBar`를 별도 파일로 분리하고, 공통 UI 컴포넌트인 `PrezelButtonArea`를 사용하도록 리팩터링했습니다.
    * 교정 결과 팝업 및 텍스트 하이라이트 로직 내 변경된 에러 타입 명칭을 반영했습니다.

* **style: 리소스 및 모델 정의 정리**
    * 대본 상세 정보 로드 실패 시의 에러 메시지(`feature_report_impl_fetch_script_detail_failed`)를 추가했습니다.
    * `ScriptUiMessage` enum을 추가하여 UI에서 노출할 메시지 타입을 정의했습니다.
@moondev03 moondev03 self-assigned this May 31, 2026
@moondev03 moondev03 requested a review from HamBeomJoon as a code owner May 31, 2026 10:31
@moondev03 moondev03 added the ✨ feat 새로운 기능 추가 또는 기존 기능 확장 label May 31, 2026
@moondev03 moondev03 linked an issue May 31, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

스크립트 교정 기능을 완전히 구현하는 PR입니다. 도메인 모델을 정리하고(ScriptErrorType 열거형 추가), 상태 머신 기반의 ViewModel을 작성하며, 텍스트 하이라이트와 팝업을 포함한 Compose UI를 구성한 후, 기존 보고서 화면과 통합하고 네비게이션을 연결합니다.

Changes

Script Analysis and Correction Feature

Layer / File(s) Summary
Script Domain Model Updates
core/model/presentation/PresentationScriptDetail.kt, core/model/presentation/PresentationDetailWithPracticeRecords.kt, core/domain/usecase/presentation/FetchPresentationDetailUseCase.kt, core/data/mapper/PresentationMapper.kt, core/network/client/HttpClientFactory.kt
PresentationScriptDetail에서 presentationId, audioUrl 필드를 제거하고 ScriptErrorType 열거형을 추가하여 에러 타입을 String에서 타입 안전하게 변경합니다. PresentationDetailWithPracticeRecords.practiceRecords를 nullable로 만들고, FetchPresentationDetailUseCase에서 isPast 분기에 따라 practiceRecords 포함 여부를 결정하도록 리팩토링합니다.
Script Analysis State Machine
feature/report/impl/script/contract/ScriptUiState.kt, feature/report/impl/script/contract/ScriptUiIntent.kt, feature/report/impl/script/contract/ScriptUiEffect.kt, feature/report/impl/script/model/ScriptUiMessage.kt, feature/report/impl/script/ScriptViewModel.kt, feature/report/impl/script/model/ScriptCorrectionUiModel.kt
스크립트 교정 화면의 상태(원본/현재 스크립트, 선택된 교정, 적용 여부)를 ScriptUiState로 정의하고, 사용자 인텐트(교정 선택, 적용, 복사)를 ScriptUiIntent로, 네비게이션/메시지/클립보드 동작을 ScriptUiEffect로 구분합니다. ScriptViewModel에서 교정 범위 계산, 스크립트 재구성(offset 보정 포함), 상태 업데이트 로직을 구현합니다.
Script Correction UI Components
feature/report/impl/script/ScriptScreen.kt, feature/report/impl/script/component/ScriptTextContent.kt, feature/report/impl/script/component/ScriptAppBar.kt, feature/report/impl/script/component/ScriptActionBar.kt, feature/report/impl/script/component/ScriptFooter.kt, feature/report/impl/script/component/ScriptCorrectionPopup.kt, core/ui/component/HideOnScrollTopBarLayout.kt
메인 ScriptScreen 컴포저블은 ViewModel의 상태/이펙트를 수집하고 렌더링 및 이펙트 처리를 분리합니다. ScriptTextContent에서 교정 범위를 AnnotatedString으로 하이라이트하고 탭 감지 시 popup Y 좌표를 계산합니다. 앱 바, 액션 버튼, 결과 요약, 교정 팝업 컴포넌트를 조합하고, 스크롤에 따라 상단바를 숨기는 HideOnScrollTopBarLayout을 제공합니다.
Report Feature Restructuring and Navigation
feature/report/impl/navigation/ReportInnerNavKey.kt, feature/report/impl/navigation/ReportEntryBuilder.kt, feature/report/impl/report/AnalysisReportScreen.kt, feature/report/impl/report/AnalysisReportViewModel.kt, feature/report/impl/report/component/ReportBodyContent.kt, feature/report/impl/report/component/ScriptAnalysisSection.kt, feature/report/impl/report/contract/AnalysisReportUiEffect.kt, feature/report/impl/report/contract/AnalysisReportUiIntent.kt
보고서 모듈을 impl.report.* 계층으로 정리합니다. ReportInnerNavKey.ScriptCorrection 네비게이션 키를 추가하고, ReportEntryBuilder에서 ScriptScreen entry를 등록하며 ScriptViewModelanalysisResultId로 생성합니다. AnalysisReportScreenAnalysisReportViewModel에 스크립트 분석 네비게이션 콜백을 추가하고, ScriptAnalysisSection에서 클릭 이벤트를 전파합니다.
String Resources and Configuration
feature/report/impl/build.gradle.kts, feature/report/impl/src/main/res/values/strings.xml
Kotlinx Serialization 플러그인을 build.gradle에 추가하고, 스크립트 교정 UI 라벨(닫기, 맞춤법, 문법, 개수, 전체 복사, 일괄 수정, 수정) 및 오류 메시지 문자열을 리소스에 추가합니다.

Possibly related PRs

  • Team-Prezel/Prezel-Android#141: FetchPresentationDetailUseCase.ktisPast 분기 및 practiceRecords 널 처리 로직이 이 PR의 핵심 변경사항과 직접 관련되어 있습니다.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 일부 범위 외 변경사항이 포함되어 있습니다: HttpClientFactory의 로깅 설정 변경, FetchPresentationDetailUseCase 리팩토링, Report 모듈 패키지 구조 정리 등이 #122 이슈 범위를 벗어납니다. 이슈 #122에 직접 관련되지 않은 변경사항들(로깅 설정, 기존 UseCase 리팩토링, 패키지 구조 변경)을 별도 PR로 분리하거나 이슈 범위를 확대하여 문서화하시기 바랍니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항(대본 교정 화면 구현)을 명확하게 요약하고 있으며, 실제 변경사항과 일치합니다.
Description check ✅ Passed PR 설명이 작업 내용, 관련 이슈, 스크린샷 등 템플릿의 주요 섹션을 채우고 있으나 논의 내용은 구체적으로 기재되지 않았습니다.
Linked Issues check ✅ Passed PR의 코드 변경사항이 이슈 #122의 대본 교정 화면 구현 요구사항을 충족합니다(ScriptScreen, ViewModel, 컴포넌트 등).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Caution

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

⚠️ Outside diff range comments (1)
Prezel/core/network/src/main/java/com/team/prezel/core/network/client/HttpClientFactory.kt (1)

91-97: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Authorization 헤더 마스킹이 비활성화되어 토큰이 로그에 노출됩니다.

sanitizeHeader가 주석 처리되면서, BuildConfig.DEBUG에서 LogLevel.ALL로 로깅될 때 Bearer 액세스/리프레시 토큰이 logcat에 평문으로 기록됩니다. 디버그 빌드라도 토큰이 로그(공유 캡처/버그리포트 등)로 유출될 위험이 있으므로 마스킹을 복원하는 것을 권장합니다.

🔒 마스킹 복원
         install(Logging) {
             logger = KtorPrettyLogger
             level = if (BuildConfig.DEBUG) LogLevel.ALL else LogLevel.NONE
-//            sanitizeHeader { header -> header == HttpHeaders.Authorization }
+            sanitizeHeader { header -> header == HttpHeaders.Authorization }
         }

복원 시 제거된 io.ktor.http.HttpHeaders import도 함께 다시 추가해야 합니다.

의도적으로 디버깅 목적상 일시 비활성화한 것이라면, 머지 전 복원되었는지 확인해 주세요.

🤖 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/network/src/main/java/com/team/prezel/core/network/client/HttpClientFactory.kt`
around lines 91 - 97, The Logging installer in
HttpClientConfig<*>.installLogging currently has sanitizeHeader commented out
causing Authorization headers to be logged; re-enable header masking by calling
sanitizeHeader { header -> header == HttpHeaders.Authorization } inside the
install(Logging) block so Bearer tokens are redacted (ensure the
io.ktor.http.HttpHeaders import is present), keeping level = if
(BuildConfig.DEBUG) LogLevel.ALL else LogLevel.NONE and leaving KtorPrettyLogger
as the logger.
🧹 Nitpick comments (3)
Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptScreen.kt (1)

107-184: 💤 Low value

동일 이름 ScriptCorrectionPopup 중복으로 혼동 가능(선택).

import한 컴포넌트(component.ScriptCorrectionPopup)와 이 파일의 private 래퍼가 같은 이름이라 시그니처로만 구분됩니다. 컴파일은 되지만 가독성/유지보수 측면에서 래퍼를 ScriptCorrectionPopupOverlay 등으로 구분하면 의도가 명확해집니다.

🤖 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/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptScreen.kt`
around lines 107 - 184, The private composable wrapper ScriptCorrectionPopup
conflicts by name with the imported component and should be renamed for clarity;
rename the private function ScriptCorrectionPopup (the wrapper that takes
uiState, onDismissCorrectionPopup, onApplyCorrection) to
ScriptCorrectionPopupOverlay and update its sole call site (the earlier
ScriptCorrectionPopup(...) invocation) accordingly, leaving the inner call to
the imported ScriptCorrectionPopup (the component.ScriptCorrectionPopup)
unchanged; ensure parameters and references inside the wrapper
(uiState.selectedCorrection, selectedCorrectionPopupY, onDismissCorrectionPopup,
onApplyCorrection) are preserved while you rename the function and its usage.
Prezel/core/ui/src/main/java/com/team/prezel/core/ui/component/HideOnScrollTopBarLayout.kt (1)

46-62: 💤 Low value

map 내부에서의 상태 변경은 부수효과 위치로 부적절합니다(선택).

calculateTopBarVisibility.map 변환 단계에서 previousScrollPosition/topBarToggleAnchorPosition 같은 Compose 상태를 갱신하고 있습니다. 동작 자체는 단일 코루틴에서 순차 수집되므로 문제는 없지만, 부수효과는 onEach/collect 로 옮기고 map 은 순수 변환만 담당하게 두면 의도가 더 명확해집니다. 우선순위는 낮습니다.

🤖 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/ui/src/main/java/com/team/prezel/core/ui/component/HideOnScrollTopBarLayout.kt`
around lines 46 - 62, The map operator currently calls calculateTopBarVisibility
and mutates Compose state (previousScrollPosition, topBarToggleAnchorPosition)
inside it; move any state updates out of map into a side-effect stage by making
map return a pure visibility boolean from calculateTopBarVisibility, then
perform previousScrollPosition/topBarToggleAnchorPosition updates in an onEach
or inside the collect block (within the LaunchedEffect that uses snapshotFlow
and distinctUntilChanged) so map stays a pure transform and side-effects occur
in onEach/collect.
Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiEffect.kt (1)

27-29: ⚡ Quick win

NavigateToScriptAnalysis와 기존 NavigateToAnalysisScript 명칭이 거의 동일해 혼동 위험이 있습니다.

두 이펙트는 단어 순서만 다른데(ScriptAnalysis vs AnalysisScript) 파라미터(analysisResultId vs presentationId+isPast)와 목적지가 서로 다릅니다. 호출부에서 잘못된 이펙트를 발행할 가능성이 있으니, 의도가 드러나도록 명확히 구분되는 이름(예: 교정 화면 이동은 NavigateToScriptCorrection)을 고려해 주세요.

🤖 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/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiEffect.kt`
around lines 27 - 29, The two UI effect data classes NavigateToScriptAnalysis
and NavigateToAnalysisScript are easily confused; rename the less-clear one
(suggested: NavigateToScriptCorrection or another name that reflects its
destination) to make intent explicit, update its constructor/parameter names
(keep analysisResultId vs presentationId + isPast distinction) and then update
all usages and imports where NavigateToScriptAnalysis or
NavigateToAnalysisScript are constructed or referenced so callers dispatch the
correct effect; ensure both classes still implement AnalysisReportUiEffect and
run a project-wide rename to avoid missing call sites.
🤖 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/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/FetchPresentationDetailUseCase.kt`:
- Around line 20-29: getPastPresentationDetail currently calls
presentationRepository.getPracticeRecords(...).getOrThrow() inside mapCatching
which turns any practice-record lookup failure into a total failure of
getPastPresentationDetail; change this to treat practiceRecords as optional by
calling getPracticeRecords(...) and using safe extraction (e.g., .getOrNull() or
runCatching { ... }.getOrNull()) so that when getPracticeRecords fails you pass
practiceRecords = null into PresentationDetailWithPracticeRecords instead of
throwing; update the mapCatching block in getPastPresentationDetail to build
PresentationDetailWithPracticeRecords with a nullable practiceRecords and ensure
errors from getPastPresentationDetail itself still propagate as before.

In
`@Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationScriptDetail.kt`:
- Around line 24-27: ScriptErrorType.from(...) currently throws
IllegalArgumentException for unknown PresentationScriptDetailResponse.errorType
values which bubbles up through scriptDetails.map { it.toDomain() } and causes
fetchScriptDetail()/ViewModel navigation failures; change ScriptErrorType to
safely handle unknown values by adding an UNKNOWN enum constant (or adding a
fromOrNull/fromResult variant) and update ScriptErrorType.from(value: String) to
return UNKNOWN (or a Result) instead of throwing; then ensure
PresentationMapper.toDomain() uses the safe mapper so
PresentationRepositoryImpl.fetchScriptDetail() and consumers like
ScriptViewModel / AnalysisFlowViewModel no longer fail on unexpected errorType
strings.

In
`@Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/ReportMetricCards.kt`:
- Around line 36-40: The ripple color currently matches the background (the
clickable call using ripple(color = PrezelTheme.colors.bgMedium) under the
modifier chain that includes background(color = PrezelTheme.colors.bgMedium)),
so the tap feedback is invisible; change the ripple color to a contrasting color
from the design system (for example a suitable value from PrezelTheme.colors
such as primary, accent, or a designated ripple/fgContrast color) so the ripple
is visually distinct from the background while keeping theme consistency; update
the ripple(color = ...) argument in the clickable modifier for the
ReportMetricCards composable to use that contrasting theme color.

In
`@Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptTextContent.kt`:
- Around line 115-133: The loop building the annotated string can throw
StringIndexOutOfBoundsException when highlightCorrections contains overlapping
ranges because script.substring(lastIndex, correction.range.first) may be called
with begin > end; inside the forEach over highlightCorrections guard against
overlap by checking if correction.range.first <= lastIndex and if so either skip
that correction or clamp the begin to lastIndex before calling script.substring,
then call appendCorrectionText (the existing function) only when the effective
range is valid and update lastIndex to max(lastIndex, correction.range.last + 1)
to keep subsequent substring calls safe; ensure you also validate
correction.range bounds against script.length so substring arguments are within
[0, script.length].

In
`@Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptViewModel.kt`:
- Around line 144-171: PresentationScriptDetail.toCorrectionUiModels currently
reconstructs originalRange using originalScript.indexOf(...) which breaks when
sentences or originalText repeat; update the matching to be robust by (1)
preferring server-provided offsets if available in
PresentationScriptAnalysisResponse/ScriptCorrection, and otherwise (2) restrict
searches to the sentence span (use sentenceStartIndex..sentenceEndIndex) and
iterate indexOf with a moving start (e.g., loop calling
originalScript.indexOf(correction.originalText, fromIndex) advancing fromIndex
past each found match) until you find an occurrence inside the sentence that
does not overlap any previously committed originalRange (track used ranges) and
only then create ScriptCorrectionUiModel; reference
PresentationScriptDetail.toCorrectionUiModels, scriptCorrections,
originalScript, ScriptCorrectionUiModel to locate and implement the change.

---

Outside diff comments:
In
`@Prezel/core/network/src/main/java/com/team/prezel/core/network/client/HttpClientFactory.kt`:
- Around line 91-97: The Logging installer in HttpClientConfig<*>.installLogging
currently has sanitizeHeader commented out causing Authorization headers to be
logged; re-enable header masking by calling sanitizeHeader { header -> header ==
HttpHeaders.Authorization } inside the install(Logging) block so Bearer tokens
are redacted (ensure the io.ktor.http.HttpHeaders import is present), keeping
level = if (BuildConfig.DEBUG) LogLevel.ALL else LogLevel.NONE and leaving
KtorPrettyLogger as the logger.

---

Nitpick comments:
In
`@Prezel/core/ui/src/main/java/com/team/prezel/core/ui/component/HideOnScrollTopBarLayout.kt`:
- Around line 46-62: The map operator currently calls calculateTopBarVisibility
and mutates Compose state (previousScrollPosition, topBarToggleAnchorPosition)
inside it; move any state updates out of map into a side-effect stage by making
map return a pure visibility boolean from calculateTopBarVisibility, then
perform previousScrollPosition/topBarToggleAnchorPosition updates in an onEach
or inside the collect block (within the LaunchedEffect that uses snapshotFlow
and distinctUntilChanged) so map stays a pure transform and side-effects occur
in onEach/collect.

In
`@Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiEffect.kt`:
- Around line 27-29: The two UI effect data classes NavigateToScriptAnalysis and
NavigateToAnalysisScript are easily confused; rename the less-clear one
(suggested: NavigateToScriptCorrection or another name that reflects its
destination) to make intent explicit, update its constructor/parameter names
(keep analysisResultId vs presentationId + isPast distinction) and then update
all usages and imports where NavigateToScriptAnalysis or
NavigateToAnalysisScript are constructed or referenced so callers dispatch the
correct effect; ensure both classes still implement AnalysisReportUiEffect and
run a project-wide rename to avoid missing call sites.

In
`@Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptScreen.kt`:
- Around line 107-184: The private composable wrapper ScriptCorrectionPopup
conflicts by name with the imported component and should be renamed for clarity;
rename the private function ScriptCorrectionPopup (the wrapper that takes
uiState, onDismissCorrectionPopup, onApplyCorrection) to
ScriptCorrectionPopupOverlay and update its sole call site (the earlier
ScriptCorrectionPopup(...) invocation) accordingly, leaving the inner call to
the imported ScriptCorrectionPopup (the component.ScriptCorrectionPopup)
unchanged; ensure parameters and references inside the wrapper
(uiState.selectedCorrection, selectedCorrectionPopupY, onDismissCorrectionPopup,
onApplyCorrection) are preserved while you rename the function and its usage.
🪄 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: f5087f5e-0fe3-4876-a38a-87905ba3aaa3

📥 Commits

Reviewing files that changed from the base of the PR and between e3831de and 81f6f12.

📒 Files selected for processing (52)
  • Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.kt
  • Prezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/FetchPresentationDetailUseCase.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationDetailWithPracticeRecords.kt
  • Prezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationScriptDetail.kt
  • Prezel/core/network/src/main/java/com/team/prezel/core/network/client/HttpClientFactory.kt
  • Prezel/core/ui/src/main/java/com/team/prezel/core/ui/component/HideOnScrollTopBarLayout.kt
  • Prezel/feature/report/impl/build.gradle.kts
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportInnerNavKey.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/AnalysisReportScreen.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/AnalysisReportViewModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/ReportBodyContent.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/ReportHeaderContent.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/ReportScreenLayout.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/AccuracySection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/ExpectedQuestionsSection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/GrowthGraphSection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/PracticeHistorySection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/ScriptAnalysisSection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/SelfFeedbackSection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/SummaryFeedbackSection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/EmptyStateCard.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/ReportMetricCards.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/ReportSection.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/modal/ReportDialog.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiEffect.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiIntent.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiState.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiStateMapper.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/AnalysisReportDialog.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/AnalysisReportUiMessage.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/GrowthGraphData.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/PracticeRecordsUiModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/PresentationInfoUiModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/QuestionUiModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/ScriptAnalysisGraphData.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/SpeedGraphData.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/preview/ReportPreviewUiState.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptScreen.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptViewModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptActionBar.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptAppBar.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptCorrectionPopup.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptFooter.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptTextContent.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/contract/ScriptUiEffect.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/contract/ScriptUiIntent.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/contract/ScriptUiState.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/model/HighlightCorrectionUiModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/model/ScriptCorrectionUiModel.kt
  • Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/model/ScriptUiMessage.kt
  • Prezel/feature/report/impl/src/main/res/values/strings.xml

Comment on lines +144 to +171
private fun PresentationScriptDetail.toCorrectionUiModels(): ImmutableList<ScriptCorrectionUiModel> {
return scriptCorrections
.mapIndexedNotNull { index, correction ->
val sentenceStartIndex = originalScript.indexOf(correction.sentence)

if (sentenceStartIndex == -1) return@mapIndexedNotNull null

val sentenceEndIndex = sentenceStartIndex + correction.sentence.length

val correctionStartIndex = originalScript.indexOf(
string = correction.originalText,
startIndex = sentenceStartIndex,
)

if (correctionStartIndex == -1) return@mapIndexedNotNull null
if (correctionStartIndex >= sentenceEndIndex) return@mapIndexedNotNull null

ScriptCorrectionUiModel(
id = index.toLong(),
errorType = correction.errorType,
sentence = correction.sentence,
originalText = correction.originalText,
correctedText = correction.correctedText,
reason = correction.reason,
originalRange = correctionStartIndex until correctionStartIndex + correction.originalText.length,
)
}.toImmutableList()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# PresentationScriptDetail 및 교정 항목 모델 정의 확인
fd -e kt -i 'PresentationScriptDetail' --exec cat {}
rg -nP --type=kotlin -C3 '\b(class|data class)\s+\w*(ScriptCorrection|Correction)\w*\b'

Repository: Team-Prezel/Prezel-Android

Length of output: 7845


indexOf 기반 originalRange 산출 견고성 개선 필요

PresentationScriptAnalysisResponse/ScriptCorrection 모두 시작/끝 문자 오프셋을 제공하지 않아(sentence, originalText 등 문자열만 있음) originalScript.indexOf(...)(첫 일치)로 범위를 재구성할 수밖에 없습니다. 이 방식은 동일한 문장/originalText가 대본에 반복될 경우 하이라이트/치환 범위가 어긋날 위험이 있으니, 서버가 오프셋을 제공하도록 확정하거나(가능하면), 클라이언트 측에서 중복 방지/재매칭 전략(예: 문장 내에서의 유일성, 추가 조건, 마지막 매칭 재사용 등)을 보강하는 방향이 필요합니다.

🤖 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/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptViewModel.kt`
around lines 144 - 171, PresentationScriptDetail.toCorrectionUiModels currently
reconstructs originalRange using originalScript.indexOf(...) which breaks when
sentences or originalText repeat; update the matching to be robust by (1)
preferring server-provided offsets if available in
PresentationScriptAnalysisResponse/ScriptCorrection, and otherwise (2) restrict
searches to the sentence span (use sentenceStartIndex..sentenceEndIndex) and
iterate indexOf with a moving start (e.g., loop calling
originalScript.indexOf(correction.originalText, fromIndex) advancing fromIndex
past each found match) until you find an occurrence inside the sentence that
does not overlap any previously committed originalRange (track used ranges) and
only then create ScriptCorrectionUiModel; reference
PresentationScriptDetail.toCorrectionUiModels, scriptCorrections,
originalScript, ScriptCorrectionUiModel to locate and implement the change.

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.

대본 교정 화면 구현

1 participant