셀프 피드백 작성 화면 구현 #144
Conversation
- `feature:feedback` 모듈(api, impl) 추가 및 의존성 설정
- 셀프 피드백 작성을 위한 `FeedbackScreen`, `FeedbackViewModel` 구현
- `WriteSelfFeedbackUseCase` 및 관련 Repository, RemoteDataSource 로직 추가
- 홈 화면에서 피드백 작성 화면으로의 내비게이션 연결
- `POST recording/{presentationId}/review` API 엔드포인트 정의 및 `SelfFeedbackRequest` DTO 추가
- 피드백 작성 중 이탈 시 확인 다이얼로그 표시 로직 구현
- 피드백 저장 실패 시 스낵바 알림 표시 추가
- 피드백 작성 완료 시 이전 화면이 아닌 홈 화면으로 이동하도록 변경 - 분석 리포트에서 피드백 작성 화면으로 이동 시 발표 제목(`title`) 파라미터 추가 - `FeedbackUiEffect`에 `NavigateToHome` 상태 추가 - `feature:feedback:impl` 및 `feature:report:impl` 모듈의 의존성 업데이트 (`feature:home:api`, `feature:feedback:api`) - `FeedbackEntryBuilder` 및 `ReportEntryBuilder` 내 네비게이션 핸들러 구현 수정
- `PresentationSummaryResponse`의 `growthGraph` 필드에 null 허용 설정 및 매퍼 로직 수정 - `HomeScreenContent`의 `HorizontalPager`에서 인덱스 참조 시 발생할 수 있는 잠재적 오류 방지 로직 추가 - `PresentationHero`에서 사용하던 `PrezelChip`을 커스텀 `HomeCategoryChip`으로 교체하여 디자인 시스템 요구사항 반영
- `GetPresentationDetailResponse`에 `reviewContent` 필드 추가 - `PresentationRemoteDataSourceImpl`에서 상세 응답 데이터 변환 시 `reviewContent`를 우선적으로 매핑하도록 로직 개선 - `FeedbackEntryBuilder`에서 홈 화면 이동 시 기존 스택을 제거하고 루트를 교체하도록 변경 (`replaceRoot`) - `PresentationHero` 컴포넌트 프리뷰의 D-Day 표시 형식을 서비스 스펙에 맞게 수정 (`D-3`, `D+5`)
- `HomeScreen`에 발표 분석 페이지로 이동하는 `navigateToAnalyzePresentation` 콜백 추가 - 발표 목록 아이템 클릭 시 해당 발표의 ID와 과거 데이터 여부(`isPastPresentation`)를 전달하도록 구현 - `HomeEntryBuilder`에서 `AnalysisNavKey.ReRecording`을 사용하여 실제 내비게이션 로직 연결
- `FeedbackNavKey`에 `isPast`, `returnToReportOnSave` 파라미터 추가 - 피드백 저장 완료 시 홈 화면 또는 리포트 화면으로 이동하는 분기 로직 구현 - 피드백 작성 시 기존 저장된 내용을 불러오는 `fetchInitialContent` 로직 추가 - 변경 사항 여부를 판단하여 종료 다이얼로그 노출 로직 개선 - `AnalysisReport` 및 `Home` 화면에서 피드백 화면 이동 시 필요한 파라미터 전달 로직 수정 - `feature:feedback:impl` 모듈에 필요한 의존성 추가 (`coreModel`, `featureReportApi`)
📝 WalkthroughWalkthrough네트워크 API부터 도메인 유스케이스, ViewModel·Composable UI, 네비게이션 및 빌드 설정까지 포함한 프레젠테이션 자기 피드백 작성 기능을 추가합니다. ChangesSelf Feedback 작성 기능 구현
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ 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: 1
🧹 Nitpick comments (2)
Prezel/detekt-config.yml (1)
40-40: ⚡ Quick winDetekt 전역 완화 대신 범위 제한 적용을 권장합니다.
thresholdInInterfaces전역 상향은 unrelated 인터페이스까지 규칙 신호를 약화시킬 수 있습니다. 이 PR 목적이 특정 인터페이스 대응이라면 해당 지점에@Suppress("TooManyFunctions")또는 인터페이스 분리로 범위를 제한하는 쪽이 유지보수에 더 안전합니다.🤖 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/detekt-config.yml` at line 40, Revert the global raise of thresholdInInterfaces in detekt-config.yml and instead apply the suppression or refactor locally: identify the specific interface(s) that triggered the rule and either annotate that interface declaration with `@Suppress`("TooManyFunctions") or split the interface into smaller interfaces to reduce function count; ensure detekt-config.yml keeps the default thresholdInInterfaces and only use `@Suppress`("TooManyFunctions") on the precise interface(s) you changed (or perform the interface split) so the rule weakening is scoped rather than global.Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt (1)
185-191: ⚡ Quick win글자 수 제한 상수가 중복됩니다.
maxLength = 200이 하드코딩되어 있는데,FeedbackViewModel의MAX_CONTENT_COUNT = 200과 동일한 값입니다. 한쪽만 변경되면 UI에 표시되는 카운트/제한과 ViewModel의 실제 truncate 길이가 어긋날 수 있습니다. 공용 상수로 추출해 단일 출처로 관리하는 것을 권장합니다.🤖 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/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt` around lines 185 - 191, Hardcoded maxLength (200) in PrezelTextArea duplicates FeedbackViewModel's MAX_CONTENT_COUNT; extract a shared constant (e.g., FeedbackConstants.MAX_CONTENT_COUNT) and use it in both places so the UI and ViewModel stay in sync: create a common constant holder (object/class) and replace the literal in FeedbackScreen's PrezelTextArea (maxLength) and in FeedbackViewModel's MAX_CONTENT_COUNT reference to use that single constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt`:
- Around line 45-61: fetchInitialContent uses fetchPresentationDetailUseCase but
lacks an onFailure branch so initialContent and the UI can be cleared on network
error; add an onFailure handler on the Result returned by
fetchPresentationDetailUseCase inside viewModelScope.launch that logs the error
and emits a user-facing event (or shows a toast/snackbar) via existing
state/events instead of setting initialContent to an empty string, and ensure
you only set initialContent and call updateState { copy(content = ...) } inside
the onSuccess path (do not overwrite currentState.content on failure). Use the
existing symbols: fetchInitialContent, fetchPresentationDetailUseCase,
initialContent, updateState, and viewModelScope.launch to locate where to add
the onFailure handling and UI/error emission.
---
Nitpick comments:
In `@Prezel/detekt-config.yml`:
- Line 40: Revert the global raise of thresholdInInterfaces in detekt-config.yml
and instead apply the suppression or refactor locally: identify the specific
interface(s) that triggered the rule and either annotate that interface
declaration with `@Suppress`("TooManyFunctions") or split the interface into
smaller interfaces to reduce function count; ensure detekt-config.yml keeps the
default thresholdInInterfaces and only use `@Suppress`("TooManyFunctions") on the
precise interface(s) you changed (or perform the interface split) so the rule
weakening is scoped rather than global.
In
`@Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt`:
- Around line 185-191: Hardcoded maxLength (200) in PrezelTextArea duplicates
FeedbackViewModel's MAX_CONTENT_COUNT; extract a shared constant (e.g.,
FeedbackConstants.MAX_CONTENT_COUNT) and use it in both places so the UI and
ViewModel stay in sync: create a common constant holder (object/class) and
replace the literal in FeedbackScreen's PrezelTextArea (maxLength) and in
FeedbackViewModel's MAX_CONTENT_COUNT reference to use that single constant.
🪄 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: 1cfd6e0d-7aab-4e2f-85df-3e11e1c37aae
📒 Files selected for processing (39)
Prezel/app/build.gradle.ktsPrezel/core/data/src/main/java/com/team/prezel/core/data/mapper/PresentationMapper.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/domain/src/main/kotlin/com/team/prezel/core/domain/usecase/presentation/WriteSelfFeedbackUseCase.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/presentation/GetPresentationDetailResponse.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/PresentationSummaryResponse.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/model/presentation/review/SelfFeedbackRequest.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/service/PresentationService.ktPrezel/detekt-config.ymlPrezel/feature/feedback/api/build.gradle.ktsPrezel/feature/feedback/api/consumer-rules.proPrezel/feature/feedback/api/proguard-rules.proPrezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.ktPrezel/feature/feedback/api/src/main/res/values/strings.xmlPrezel/feature/feedback/impl/build.gradle.ktsPrezel/feature/feedback/impl/consumer-rules.proPrezel/feature/feedback/impl/proguard-rules.proPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiEffect.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiIntent.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/contract/FeedbackUiState.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.ktPrezel/feature/feedback/impl/src/main/res/values/strings.xmlPrezel/feature/home/impl/build.gradle.ktsPrezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/HomeScreen.ktPrezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/HomeScreenContent.ktPrezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/main/component/title/PresentationHero.ktPrezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/navigation/HomeEntryBuilder.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.ktPrezel/settings.gradle.kts
- `AnalysisReportUiIntent.FetchData` 인텐트를 추가하고 `LaunchedEffect`를 통해 데이터를 호출하도록 변경 - `ReportNavKey`와 `FeedbackNavKey`에서 불필요한 `refreshKey`, `returnToReportOnSave` 파라미터 제거 - 피드백 저장 완료 시 복잡한 화면 전환 로직을 `navigator.goBack()`으로 단순화 - `AnalysisReportViewModel`의 초기 데이터 로드 로직을 `init` 블록에서 `onIntent` 처리 방식으로 이전 - `feature:feedback:impl` 모듈의 불필요한 의존성(`feature:home:api`, `feature:report:api`) 제거
- 서버 에러 코드(`ServerErrorCode`) 및 앱 에러(`AppError`) 매핑 로직 추가
- `SELF_FEEDBACK_ALREADY_WRITTEN`, `PRESENTATION_REVIEW_NOT_FOUND`, `PRESENTATION_REVIEW_FORBIDDEN` 추가
- `FeedbackUiMessage` enum 클래스에 에러 케이스 추가
- `FETCH_FEEDBACK_FAILED`, `PRESENTATION_FORBIDDEN`, `PRESENTATION_NOT_FOUND`, `SELF_FEEDBACK_ALREADY_WRITTEN` 정의
- `FeedbackViewModel` 내 에러 핸들링 로직 개선
- 발표 상세 조회 및 피드백 저장 실패 시 `FeedbackUiMessage`로 변환하여 UI 에러 메시지 표시
- `AppException` 기반의 `toFeedbackUiMessage` 확장 함수 구현
- 피드백 화면(`FeedbackScreen`) 에러 메시지 스낵바 연동 및 다국어 리소스 추가
- `feature:feedback:impl` 모듈에 `coreCommon` 의존성 추가 및 최대 글자 수 상수명 변경(`FEEDBACK_CONTENT_MAX_LENGTH`)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt`:
- Around line 93-95: 성공 분기에서 isSaving 상태를 false로 복구하지 않아 저장 버튼이 비활성화된 상태로 남는 문제를
고치세요: FeedbackViewModel의 해당 비동기 호출의 onSuccess 블록(현재
sendEffect(FeedbackUiEffect.SaveComplete) 호출이 있는 위치)에서 isSaving 상태를 false로 설정하도록
상태 업데이트 로직(예: 상태 복사나 setState/updateState 호출)을 추가해 주세요 so that after
sendEffect(FeedbackUiEffect.SaveComplete) completes the viewModel restores
isSaving=false.
🪄 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: 5e13377d-49b6-4a17-8c83-d6453f901e0f
📒 Files selected for processing (16)
Prezel/app/build.gradle.ktsPrezel/core/data/src/main/java/com/team/prezel/core/data/error/AppErrorExt.ktPrezel/core/network/src/main/java/com/team/prezel/core/network/model/BaseResponse.ktPrezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.ktPrezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.ktPrezel/feature/feedback/impl/build.gradle.ktsPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/model/FeedbackUiMessage.ktPrezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/navigation/FeedbackEntryBuilder.ktPrezel/feature/feedback/impl/src/main/res/values/strings.xmlPrezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.ktPrezel/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/AnalysisReportUiIntent.ktPrezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt
💤 Files with no reviewable changes (4)
- Prezel/feature/feedback/api/src/main/java/com/team/prezel/feature/feedback/api/FeedbackNavKey.kt
- Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/navigation/ReportEntryBuilder.kt
- Prezel/feature/report/api/src/main/java/com/team/prezel/feature/report/api/ReportNavKey.kt
- Prezel/feature/analysis/impl/src/main/java/com/team/prezel/feature/analysis/impl/navigation/AnalysisEntryBuilder.kt
✅ Files skipped from review due to trivial changes (2)
- Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/contract/AnalysisReportUiIntent.kt
- Prezel/feature/feedback/impl/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (3)
- Prezel/app/build.gradle.kts
- Prezel/feature/report/impl/src/main/java/com/team/prezel/feature/report/impl/AnalysisReportScreen.kt
- Prezel/feature/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackScreen.kt
| ).onSuccess { | ||
| sendEffect(FeedbackUiEffect.SaveComplete) | ||
| }.onFailure { throwable -> |
There was a problem hiding this comment.
저장 성공 시 isSaving 상태를 복구해 주세요.
성공 분기에서 isSaving을 false로 되돌리지 않아, SaveComplete 이펙트가 소비되지 않거나 내비게이션이 실패하면 저장 버튼이 계속 비활성화될 수 있습니다.
수정 예시
).onSuccess {
+ updateState { copy(isSaving = false) }
sendEffect(FeedbackUiEffect.SaveComplete)
}.onFailure { throwable ->
updateState { copy(isSaving = false) }
sendEffect(FeedbackUiEffect.ShowMessage(throwable.toFeedbackUiMessage()))
Timber.e(throwable)🤖 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/feedback/impl/src/main/java/com/team/prezel/feature/feedback/impl/FeedbackViewModel.kt`
around lines 93 - 95, 성공 분기에서 isSaving 상태를 false로 복구하지 않아 저장 버튼이 비활성화된 상태로 남는
문제를 고치세요: FeedbackViewModel의 해당 비동기 호출의 onSuccess 블록(현재
sendEffect(FeedbackUiEffect.SaveComplete) 호출이 있는 위치)에서 isSaving 상태를 false로 설정하도록
상태 업데이트 로직(예: 상태 복사나 setState/updateState 호출)을 추가해 주세요 so that after
sendEffect(FeedbackUiEffect.SaveComplete) completes the viewModel restores
isSaving=false.
📌 작업 내용
셀프 피드백 작성 화면 신규 구현
셀프 피드백 저장 API 연동
홈 화면에서 셀프 피드백 작성 진입 연결
분석 리포트와 셀프 피드백 플로우 연결
🧩 관련 이슈
📸 스크린샷
Summary by CodeRabbit
New Features
Chores