대본 교정 화면 구현#143
Hidden character warning
Conversation
* **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에서 노출할 메시지 타입을 정의했습니다.
📝 WalkthroughWalkthrough스크립트 교정 기능을 완전히 구현하는 PR입니다. 도메인 모델을 정리하고(ScriptErrorType 열거형 추가), 상태 머신 기반의 ViewModel을 작성하며, 텍스트 하이라이트와 팝업을 포함한 Compose UI를 구성한 후, 기존 보고서 화면과 통합하고 네비게이션을 연결합니다. ChangesScript Analysis and Correction Feature
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
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.HttpHeadersimport도 함께 다시 추가해야 합니다.의도적으로 디버깅 목적상 일시 비활성화한 것이라면, 머지 전 복원되었는지 확인해 주세요.
🤖 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명칭이 거의 동일해 혼동 위험이 있습니다.두 이펙트는 단어 순서만 다른데(
ScriptAnalysisvsAnalysisScript) 파라미터(analysisResultIdvspresentationId+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
📒 Files selected for processing (52)
Prezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.ktPrezel/core/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/FetchPresentationDetailUseCase.ktPrezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationDetailWithPracticeRecords.ktPrezel/core/model/src/main/java/com/team/prezel/core/model/presentation/PresentationScriptDetail.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/client/HttpClientFactory.ktPrezel/core/ui/src/main/java/com/team/prezel/core/ui/component/HideOnScrollTopBarLayout.ktPrezel/feature/report/impl/build.gradle.ktsPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportInnerNavKey.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/AnalysisReportScreen.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/AnalysisReportViewModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/ReportBodyContent.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/ReportHeaderContent.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/ReportScreenLayout.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/AccuracySection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/ExpectedQuestionsSection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/GrowthGraphSection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/PracticeHistorySection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/ScriptAnalysisSection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/SelfFeedbackSection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/body/SummaryFeedbackSection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/EmptyStateCard.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/ReportMetricCards.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/common/ReportSection.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/component/modal/ReportDialog.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiEffect.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiIntent.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiState.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/contract/AnalysisReportUiStateMapper.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/AnalysisReportDialog.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/AnalysisReportUiMessage.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/GrowthGraphData.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/PracticeRecordsUiModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/PresentationInfoUiModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/QuestionUiModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/ScriptAnalysisGraphData.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/model/SpeedGraphData.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/report/preview/ReportPreviewUiState.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptScreen.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/ScriptViewModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptActionBar.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptAppBar.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptCorrectionPopup.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptFooter.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/component/ScriptTextContent.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/contract/ScriptUiEffect.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/contract/ScriptUiIntent.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/contract/ScriptUiState.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/model/HighlightCorrectionUiModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/model/ScriptCorrectionUiModel.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/script/model/ScriptUiMessage.ktPrezel/feature/report/impl/src/main/res/values/strings.xml
| 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() | ||
| } |
There was a problem hiding this comment.
🧩 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.
📌 작업 내용
HideOnScrollTopBarLayout공통 컴포넌트 구현🧩 관련 이슈
📸 스크린샷
Screen_recording_20260531_192840.mp4
📢 논의하고 싶은 내용
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항