Skip to content

[Feat] 통화 기록 UI 및 음성파일 재생 기능 구현 & 홈 화면 팝업창 구현#36

Merged
JiwonLee42 merged 16 commits into
developfrom
feat/#7-home-ui
Jul 26, 2026
Merged

[Feat] 통화 기록 UI 및 음성파일 재생 기능 구현 & 홈 화면 팝업창 구현#36
JiwonLee42 merged 16 commits into
developfrom
feat/#7-home-ui

Conversation

@JiwonLee42

@JiwonLee42 JiwonLee42 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

📄 작업 내용 요약

홈 화면

  • 홈,통화기록 도메인 모델 정리
  • MockRepository 생성해서 데이터 분리
  • RepositoryModule에 Mock을 사용하도록 임시 연결
  • 홈 헤더가 시스템 상태 표시줄과 겹치지 않도록 패딩을 적용했습니다.

홈 화면 다이얼로그

  • 다이얼로그 State를 분리해서 state에 따라 표시 처리했습니다.

통화 기록

  • 통화 기록 화면을 피그마에 맞게 구현했습니다.
  • 통화 정보와 대화 내용을 동시에 불러오도록 구현했습니다.
  • 통화기록을 불러오는 동안 로딩 화면이 표시되도록 했습니다.
  • 재생, 일시정지, 5초 앞뒤 이동 버튼을 정리했습니다.
  • 화면이 다시 만들어져도 마지막 재생 위치가 유지되도록 했습니다.
  • 앱이 백그라운드로 이동하면 재생이 자동으로 멈추도록 했습니다.
  • Media3의 ExoPlayer를 화면에서 생성해 녹음 URL을 연결했습니다.
  • 플레이어의 재생 여부, 현재 위치, 전체 길이, 버퍼링 상태를 Compose 상태로 변환해 재생 UI에 전달했습니다.

📎 Issue 번호


✅ 작업 목록

  • 기능 구현
  • 코드 리뷰 반영
  • 테스트 코드 작성
  • 문서 업데이트

📝 기타 참고사항

Summary by CodeRabbit

  • 새로운 기능

    • 홈 화면에서 통화 기록 상세 화면으로 이동할 수 있습니다.
    • 통화 녹음 재생(재생/일시정지, 5초 이동, 위치 이동)과 대화 내용(화자별 말풍선) 보기를 지원합니다.
    • 통화 연결, 캐릭터 변경(확인/불가), 비메인 대상 통화 안내 팝업을 추가했습니다.
  • 개선 사항

    • 예약 시간 선택 범위와 초기 선택 위치 산정을 개선했습니다.
    • 알림에서 “다시 전화하기” 버튼은 대상 정보가 있을 때만 동작하도록 정리했습니다.
  • 버그 수정

    • 통화 기록 로딩/재생 중 상태 표시와 오류 안내를 보완했습니다.

@JiwonLee42 JiwonLee42 self-assigned this Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fec12d3-5c9a-4d1c-a12c-02d680ac8aeb

📥 Commits

Reviewing files that changed from the base of the PR and between e7e858b and e7c6211.

📒 Files selected for processing (2)
  • app/src/main/java/kr/co/call/callfromai/AppScreen.kt
  • core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/kr/co/call/callfromai/AppScreen.kt
  • core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt

📝 Walkthrough

Walkthrough

홈 화면이 저장소·상태 기반 흐름과 다이얼로그를 지원하도록 확장되었고, 통화 기록 도메인·mock 데이터·전사 매핑·재생 화면이 추가되었습니다. 홈에서 callId를 전달해 통화 기록 화면으로 이동하고 뒤로 돌아오는 내비게이션도 연결되었습니다.

Changes

홈 및 통화 기록 기능

Layer / File(s) Summary
도메인 계약과 데이터 구현
core/domain/..., core/network/..., core/data/...
홈·통화 기록 모델과 저장소 계약, DTO 매퍼, mock/API 저장소 및 Hilt 바인딩이 추가 또는 변경되었습니다.
홈 상태와 상호작용 흐름
feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/..., feature/home/impl/src/main/java/kr/co/call/impl/screen/HomeScreen.kt, feature/home/impl/src/main/java/kr/co/call/impl/component/...
홈 로딩, 캐릭터·통화·예약 이벤트, 다이얼로그 상태, 알림 페이징 및 관련 UI 콜백이 상태 기반으로 연결되었습니다.
통화 기록 조회와 재생 UI
feature/home/impl/src/main/java/kr/co/call/impl/screen/CallRecordScreen.kt, feature/home/impl/src/main/java/kr/co/call/impl/component/record/*, feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/*
통화 정보와 전사를 병렬 조회하고, 로딩·오류·정상 상태와 ExoPlayer 재생·탐색·일시정지를 처리하는 화면이 추가되었습니다.
홈에서 통화 기록으로의 내비게이션
app/src/main/java/kr/co/call/callfromai/AppScreen.kt, feature/home/api/..., feature/home/impl/src/main/java/kr/co/call/impl/entry/HomeEntryBuilder.kt
CallRecordNavKey(callId) 기반 이동과 back stack 복귀가 앱 엔트리에 연결되었습니다.
재생 기능 빌드와 UI 리소스
gradle/libs.versions.toml, feature/home/impl/build.gradle.kts, core/designsystem/..., feature/home/impl/src/test/...
Media3·Compose 그래픽 의존성, 재생 아이콘 리소스와 날짜 반올림 테스트가 추가되었습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: 🧩 Component, ✨ Feature, 🎨 UI/Style

Suggested reviewers: wacaw9

🚥 Pre-merge checks | ✅ 4 | ❌ 4

❌ Failed checks (4 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning 제목이 Conventional Commits 형식인 <type>: <description>을 따르지 않고, 대괄호 접두사와 대문자 타입을 사용했습니다. feat: 통화 기록 UI 및 음성파일 재생 기능 구현처럼 type: description 형식과 허용된 type을 사용해 수정하세요.
Linked Issues check ⚠️ Warning [#10] 통화 기록 화면, 전사 목록, 플레이어 UI는 구현됐지만 5분 이동 요구가 5초 이동으로 보여 핵심 조건을 충족하지 못합니다. 5초 이동을 5분 이동으로 맞추고, 재생·정지·진행도·녹음 재생 기능이 issue #10 요구와 일치하는지 다시 확인하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 31.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Ui 변경 시 스크린샷 첨부 확인 ⚠️ Warning 변경된 Compose 화면(AppScreen, ChatListScreen, MyPageScreen 등)이 있으나 PR 설명에 스크린샷/녹화 첨부 언급이 없습니다. PR 설명에 화면 캡처나 녹화(GIF/동영상) 링크를 추가하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed [#8] 알림 페이징 목록, 개별 알림 컴포넌트, 다시 전화하기 동작이 추가되어 알림창 UI 요구를 충족합니다.
Out of Scope Changes check ✅ Passed 홈·통화 기록·알림 UI, 도메인/리포지토리, 재생 기능 변경은 PR 목적과 연결되며 명백한 무관 변경은 두드러지지 않습니다.
모듈 의존성 방향 검증 ✅ Passed feature/home/impl은 home/api와 core/domain 인터페이스만 의존하고, core/domain 소스에 android/androidx import가 없으며, 변경 파일의 core.data impl 직접 import도 없었습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#7-home-ui

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.

@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요! 시니어 Android 개발자로서 GitHub Pull Request를 리뷰해 드리겠습니다. 전반적으로 코드 품질이 좋고, 새로운 기능 통합 및 기존 코드의 리팩토링이 잘 이루어진 것 같습니다. 특히 Compose Paging3 통합 및 Repository 레이어의 Result 타입 활용은 모범적인 사례로 보입니다.

아래에 파일별로 상세한 피드백을 남기겠습니다.


AppScreen.kt

  • 변경 내용: CallRecordNavKey를 도입하고 homeEntrynavigateToCallRecordonCallRecordBack 콜백을 추가하여 CallRecord 화면으로의 네비게이션을 통합했습니다.
  • 리뷰:
    • @Serializable을 사용하는 NavKey 기반의 네비게이션 방식은 Compose Navigation 3의 권장 방식에 잘 부합합니다.
    • 콜백을 통해 네비게이션 이벤트를 상위 컴포넌트로 전달하는 방식도 State Hoisting 원칙에 맞습니다.
  • 피드백: 새로운 화면으로의 네비게이션 통합이 깔끔하게 잘 처리되었습니다.

RepositoryModule.kt

  • 변경 내용: CallRecordRepositoryCallRecordMockRepository의 바인딩을 추가했고, HomeRepository의 구현체를 HomeRepositoryImpl에서 HomeMockRepository로 변경했습니다.
  • 리뷰:
    • Hilt DI: CallRecordRepository 바인딩은 @Binds@Singleton 어노테이션을 적절히 사용하여 Hilt DI 원칙을 준수하고 있습니다.
    • Mock Repository: 개발 단계에서 HomeRepository의 구현체를 Mock으로 전환한 것은 효율적인 개발을 위한 좋은 전략입니다.
  • 피드백:
    • CallRecordRepository 바인딩은 문제없이 잘 구현되었습니다.
    • HomeRepository를 Mock 구현체로 전환한 것은 개발 과정에서 유용하지만, 프로덕션 빌드 시에는 실제 HomeRepositoryImpl이 바인딩되도록 빌드 타입(flavor)별 DI 모듈 분리 등 적절한 전략이 필요합니다. 현재 PR만으로는 이 전략을 알 수 없어 확인이 필요합니다.

CallHistoryMapper.kt

  • 변경 내용: ReservationListDto, ReservationDto, HomeSummaryDto에 대한 toDomain 매핑 함수들을 다른 파일로 이동시키고, CallHistoryDto에 대한 매퍼만 남겼습니다.
  • 리뷰:
    • 관심사 분리: 매퍼를 더 구체적인 파일로 분리한 것은 좋은 리팩토링입니다.
    • Null 안전성: startedAt, duration 필드는 !! (non-null asserted call)를 사용하여 강제 언래핑하고 있습니다. 만약 이 필드들이 네트워크 응답에서 null이 될 가능성이 있다면 NullPointerException이 발생할 수 있습니다. API 명세를 다시 확인하여 null 가능성을 고려한 안전한 처리가 필요합니다 (예: ?.let {} ?: defaultValue).
    • 하드코딩된 문자열: "SENT", "RECEIVED", "MISSED"와 같은 통화 상태 문자열이 하드코딩되어 있습니다. 서버와 약속된 고정된 값이라면, 이를 상수로 선언하거나 enum 클래스를 활용하여 관리하면 가독성 및 유지보수성을 높일 수 있습니다. else -> error(...)는 예상치 못한 값에 대한 방어 로직으로 좋지만, 상황에 따라서는 Result.failure를 반환하거나 기본값을 제공하는 것이 앱의 크래시를 방지하는 더 나은 선택일 수 있습니다.
  • 피드백:
    • 매퍼 분리는 환영할 만한 변화입니다.
    • 개선 권고: startedAtduration의 nullability를 확인하고 안전하게 처리해 주세요.
    • 개선 권고: 통화 상태 문자열을 상수로 관리하는 것을 고려해 보세요. else -> error(...) 대신 Result.failure를 반환하는 등의 방어적인 에러 핸들링을 검토해 주세요.

CallTranscriptMapper.kt (신규)

  • 변경 내용: CallTranscriptResultDtoCallTranscriptDto를 도메인 모델로 매핑하는 새로운 매퍼 파일입니다.
  • 리뷰:
    • 하드코딩된 문자열: toDomainSpeaker() 함수 내 "사용자", "USER", "AI" 문자열에 대해 CallHistoryMapper.kt와 동일한 피드백입니다.
    • Null 안전성: CallTranscriptDtospeakercontent 필드에 대한 nullability 확인이 필요합니다.
  • 피드백:
    • 개선 권고: toDomainSpeaker()의 하드코딩된 문자열을 상수로 관리하는 것을 고려하고, else -> error(...) 에러 핸들링을 검토해 주세요.

HomeMapper.kt (신규)

  • 변경 내용: HomeSummaryDtoHomeSummary로 매핑하는 새로운 매퍼 파일입니다.
  • 리뷰: 간단한 매핑이며, HomeSummaryDto의 모든 필드가 non-null임을 전제합니다.
  • 피드백: 깔끔하게 잘 작성되었습니다.

ReservationMapper.kt (신규)

  • 변경 내용: ReservationListDtoReservationDto를 도메인 모델로 매핑하는 새로운 매퍼 파일입니다.
  • 리뷰: LocalDateTime.parse(scheduledAt)를 사용하고 있습니다. 만약 scheduledAt 문자열이 유효하지 않은 형식으로 올 수 있다면, DateTimeParseException이 발생하여 앱이 크래시될 수 있습니다. 네트워크 응답의 유효성을 신뢰할 수 없다면 try-catch 블록으로 감싸거나, Result.failure로 반환하는 등의 견고한 에러 처리가 필요합니다.
  • 피드백:
    • 개선 권고: LocalDateTime.parse(scheduledAt) 호출 시 발생할 수 있는 파싱 에러에 대비하여 try-catch 블록을 추가하거나, 유효하지 않은 데이터를 안전하게 처리하는 로직을 고려해 주세요.

CallRecordMockRepository.kt (신규)

  • 변경 내용: CallRecordRepository 인터페이스를 구현하는 Mock 리포지토리입니다. Mock 데이터를 반환하며, 특정 callId 범위에 대한 예외 처리를 시뮬레이션합니다.
  • 리뷰:
    • Mock 구현: Mock 리포지토리로서 실제 API 호출 없이 UI 및 ViewModel 개발을 진행할 수 있도록 잘 구현되었습니다. Result.successResult.failure를 적절히 사용하여 비동기/에러 시나리오를 시뮬레이션합니다.
    • 하드코딩 값: callId 범위, LocalDateTime, recordingUrl 등 Mock 데이터에 하드코딩된 값들이 존재하지만, 이는 Mock의 특성상 허용됩니다.
    • 예외 처리: callNotFound() 함수를 통해 IllegalArgumentException을 발생시키는 것도 Mock 시나리오에 적합합니다.
  • 피드백: UI 및 ViewModel 개발에 매우 유용한 Mock 리포지토리입니다.

CallRecordRepositoryImpl.kt (신규)

  • 변경 내용: CallRecordRepository 인터페이스의 실제 구현체이지만, 모든 메서드가 unsupportedApi()를 호출하여 UnsupportedOperationException을 발생시킵니다.
  • 리뷰:
    • Hilt DI: @Inject constructor()를 사용하여 Hilt의 생성자 주입 원칙을 준수합니다.
    • 미구현 API: 실제 API 연동이 아직 준비되지 않았음을 명확히 보여주는 Placeholder 구현입니다.
  • 피드백: 실제 API 연동이 완료되면 unsupportedApi() 대신 네트워크 호출 및 에러 핸들링 로직을 추가해야 합니다.

HomeMockRepository.kt (신규)

  • 변경 내용: HomeRepository 인터페이스를 구현하는 Mock 리포지토리입니다. Mock 데이터를 제공하고, changeReservationTime, changeMainCharacter, startCall 메서드에 대한 간단한 Mock 로직과 예외 시뮬레이션을 포함합니다.
  • 리뷰:
    • Mock 구현: HomeRepository에 대한 포괄적인 Mock 구현으로, 실제 시나리오를 효과적으로 시뮬레이션합니다.
    • 예외 처리: CharacterChangeUnavailableException (새로운 커스텀 예외) 및 IllegalArgumentException, IllegalStateException을 발생시켜 다양한 에러 케이스를 테스트할 수 있게 한 점이 좋습니다.
    • Data var: charactersreservations 필드를 var로 선언하여 Mock 내부에서 상태 변경을 시뮬레이션하는 것은 Mock의 목적상 적절합니다.
  • 피드백: 견고한 Mock 구현으로, ViewModel 및 UI 개발에 큰 도움이 될 것입니다.

HomeRepositoryImpl.kt

  • 변경 내용: HomeApi를 주입받아 getReservations, getCallHistories, getSummary 메서드에서 실제 네트워크 호출 및 매핑 로직을 runCatching 블록으로 감싸 처리하도록 변경했습니다. getCharacters, changeMainCharacter, startCall 메서드는 아직 unsupportedApi()를 반환합니다. CALL_HISTORY_SIZE 상수를 제거했습니다.
  • 리뷰:
    • Hilt DI: HomeApi를 생성자 주입 방식으로 받는 것은 Hilt 원칙에 부합합니다.
    • Retrofit 에러 핸들링: runCatching { ... } 블록을 사용하여 네트워크 호출 결과를 Result 타입으로 래핑한 것은 매우 훌륭합니다. 이는 Repository 레이어에서 Retrofit 에러를 효과적으로 핸들링하는 모범 사례입니다.
    • 네트워크 응답과 도메인 모델 매핑 분리: .result.toDomain() 패턴을 사용하여 DTO를 도메인 모델로 매핑하는 로직이 명확히 분리되어 있어, 클린 아키텍처 원칙을 잘 따르고 있습니다.
    • 미구현 API: 아직 구현되지 않은 메서드에 unsupportedApi()를 사용하는 것은 일관성 있는 접근 방식입니다.
  • 피드백:
    • runCatchingtoDomain() 매퍼의 활용은 Repository 레이어에서 매우 바람직한 구현입니다.
    • 개선 권고: homeApi.get...().result 접근 시, 서버 응답 구조상 isSuccessfalse일 때 result 필드 자체가 null일 수 있는지 확인해 주세요. 만약 그렇다면 result?.toDomain() 또는 result 필드 자체를 CallTranscriptResultDto?와 같이 nullable하게 선언하여 NullPointerException 발생 가능성을 제거하는 것이 더 안전합니다. 현재는 result가 항상 존재한다고 가정하고 있습니다.

core/designsystem/build.gradle.kts

  • 변경 내용: libs.androidx.ui.graphics 의존성을 추가했습니다.
  • 피드백: 새로운 UI 요소나 드로어블을 위해 필요한 의존성으로 보입니다.

core/designsystem/src/main/res/drawable/*.xml (신규)

  • 변경 내용: 통화 관련 재생/일시정지/되감기 아이콘 벡터 드로어블을 추가했습니다.
  • 리뷰:
    • 벡터 드로어블: 표준 벡터 드로어블 형식을 사용합니다.
    • 하드코딩된 색상: #2E2B2C, #000000과 같은 색상 값이 XML 파일에 직접 하드코딩되어 있습니다.
  • 피드백:
    • 개선 권고: 드로어블 XML 내 하드코딩된 색상 값들을 colors.xml에 정의된 컬러 리소스(예: @color/black)로 대체하여 디자인 시스템의 일관성을 유지하고 테마 변경에 유연하게 대응할 수 있도록 해주세요.

CharacterChangeUnavailableException.kt (신규)

  • 변경 내용: 메인 캐릭터 교체 불가 상황을 위한 커스텀 예외 클래스를 추가했습니다.
  • 리뷰:
    • 커스텀 예외: 도메인 특화된 에러 상황을 명확히 정의하는 것은 좋은 패턴입니다.
    • 메시지: 예외 메시지가 명확합니다.
  • 피드백: 잘 정의된 커스텀 예외입니다.

도메인 모델 리팩토링 (CallHistory.kt 등 삭제, CallRecord.kt Home.kt 신규)

  • 변경 내용: 기존 CallHistory, CallReservation, CallReservations, HomeSummary, HomeOverview, HomeNotification 파일들이 삭제되고, CallRecord.ktHome.kt 파일로 관련 도메인 모델들이 통합되었습니다.
  • 리뷰:
    • 모델 통합: 관련 도메인 모델들을 Home.kt와 같은 단일 파일로 통합한 것은, 해당 기능의 모델들을 한눈에 파악하기 용이하게 만들 수 있습니다. 다만, 파일이 너무 커지면 관리하기 어려워질 수 있으므로 적절한 수준에서 통합하는 것이 중요합니다. 현재 PR의 경우 적정해 보입니다.
    • Data Class 활용: 모든 모델이 data class로 정의되었고, 모든 속성이 val로 선언되어 불변성(immutability)을 잘 유지하고 있습니다.
    • Null 안전성: imageUrl: String?, recordingUrl: String? 등 nullable 필드들이 적절히 ?로 표시되어 있습니다.
    • Enum 활용: CallTranscript.SpeakerNotificationType에 enum 클래스를 사용하여 상태를 명확히 표현한 점이 좋습니다.
  • 피드백: 도메인 모델의 리팩토링은 코드의 구조와 가독성을 크게 향상시켰습니다. 불변성을 지킨 data class 활용이 인상적입니다.

CallRecordRepository.kt (신규)

  • 변경 내용: CallInfoCallTranscript를 가져오는 계약을 정의하는 새로운 CallRecordRepository 인터페이스를 추가했습니다.
  • 리뷰:
    • 인터페이스 정의: 명확하고 잘 정의된 리포지토리 인터페이스입니다.
    • Result<T> 반환: Result 타입을 사용하여 성공/실패 결과를 명확하게 나타내어 클라이언트 코드에서 에러 처리를 강제하는 좋은 패턴입니다.
  • 피드백: 명확한 계약 정의입니다.

HomeRepository.kt

  • 변경 내용: getCharacters, changeMainCharacter, startCall 메서드를 인터페이스에 추가했습니다.
  • 피드백: 홈 화면의 기능 확장에 따라 리포지토리 계약을 확장한 것으로 보이며, 이는 일관된 패턴입니다.

HomeUseCase.kt (삭제)

  • 변경 내용: HomeUseCase 파일이 삭제되었습니다. 기존 HomeUseCaseHomeOverview라는 복합 모델을 반환했습니다.
  • 리뷰: HomeUseCase 삭제는 도메인 로직 처리 방식에 변화가 있음을 시사합니다.
    • 이전에는 HomeOverview를 통해 홈 화면에 필요한 모든 데이터를 한 번에 가져왔는데, 이제는 각 데이터를 ViewModel에서 개별적으로 UseCase 없이 Repository를 통해 가져오거나, 더 세분화된 UseCase를 사용하는 방식으로 변경되었을 가능성이 있습니다.
    • 만약 모든 데이터 로딩 및 조합 로직이 ViewModel로 이동했다면, ViewModel의 책임이 과도하게 커질 수 있습니다.
  • 피드백: HomeUseCase 삭제에 대한 명확한 의도와 함께, 해당 로직이 현재 어디에서 처리되고 있는지 (예: ViewModel에서 직접 처리하는지, 아니면 더 작은 UseCase로 분리되었는지) 확인이 필요합니다. ViewModel이 비즈니스 로직과 데이터 조합 로직으로 과부하되지 않도록 주의해야 합니다.

CallTranscriptResponseDto.kt (신규)

  • 변경 내용: 통화 스크립트 API 응답을 위한 DTO들을 정의한 새로운 파일입니다.
  • 리뷰:
    • Data Class 활용: 표준적인 DTO 구조를 잘 따르고 있으며 모든 필드가 val로 선언되어 있습니다.
    • Null 안전성: CallTranscriptResponseDtoresult 필드가 non-nullable로 선언되어 있습니다. 앞서 HomeRepositoryImpl.kt에서 언급했듯이, API 응답에서 isSuccessfalse일 때 result 필드가 null이 될 가능성이 있다면 val result: CallTranscriptResultDto?로 선언하여 안전하게 처리해야 합니다. 마찬가지로 CallTranscriptResultDtocontent 필드도 List<CallTranscriptDto>? 여부를 확인해야 합니다.
  • 피드백:
    • 개선 권고: CallTranscriptResponseDtoresult 필드와 CallTranscriptResultDtocontent 필드에 대한 nullability를 API 명세와 대조하여 확인하고, 필요한 경우 nullable 타입으로 변경해 주세요.

CallRecordRoute.kt (신규)

  • 변경 내용: callId를 인자로 받는 CallRecordNavKey를 정의한 새로운 네비게이션 라우트 파일입니다.
  • 리뷰:
    • @SerializableNavKey: Compose Navigation 3의 권장 방식에 따라 @Serializable 어노테이션과 NavKey 인터페이스를 잘 사용하고 있습니다.
  • 피드백: 새로운 화면 라우트 정의가 깔끔합니다.

home/impl/build.gradle.kts

  • 변경 내용: androidx.lifecycle.runtime.composeandroidx.media3.exoplayer 의존성을 추가했습니다.
  • 피드백:
    • lifecycle.runtime.composerepeatOnLifecycle과 같은 Compose 라이프사이클 관련 API 사용을 위해 필요합니다.
    • media3.exoplayer는 통화 기록 화면에서 오디오 재생 기능을 구현하기 위한 필수적인 의존성으로 보입니다.
    • 새로운 기능 구현을 위한 적절한 의존성 추가입니다.

TimeChangeBottomSheet.kt

  • 변경 내용: 날짜 선택 휠의 범위를 오늘 기준 ±30일에서 오늘 기준 -30일 ~ +1년으로 확장하고, initialDateIndex 계산 로직을 개선했습니다.
  • 리뷰:
    • Recomposition: remember를 사용하여 today, dates, times 같은 변경되지 않는 데이터를 캐싱하고 있어 불필요한 리컴포지션을 방지하고 있습니다. generateSequence는 효율적인 컬렉션 생성 방법입니다.
    • 매직 넘버: 30L (일 offset), 1 (년 offset), 48 (시간 개수), 30L (분 간격) 등 여러 숫자 값들이 하드코딩되어 있습니다.
    • 초기 인덱스: dates.indexOf(today)를 사용하여 initialFirstVisibleItemIndex를 설정한 것은 selectedDate가 목록에 없을 때 today를 기본값으로 하여 더 견고한 초기화 로직입니다.
  • 피드백:
    • 날짜 범위 확장 및 초기 인덱스 계산 로직 개선은 사용자 경험 향상에 기여할 것입니다.
    • 개선 권고: 30, 1, 48 등의 매직 넘버들을 명명된 상수로 추출하여 코드의 가독성과 유지보수성을 높여주세요.

CallConnectDialog.kt (신규)

  • 변경 내용: 통화 연결 확인 다이얼로그 컴포저블을 추가했습니다.
  • 리뷰:
    • Composable 함수: 컴포저블 함수 명명 규칙을 따르고 있습니다.
    • State Hoisting: characterName, onConnectClick, onDismissRequest를 파라미터로 받아 상태를 상위로 호이스팅하는 원칙을 잘 준수합니다.
    • UI 상태: characterNameString으로, 액션을 람다로 받는 것은 적절합니다.
    • 하드코딩된 문자열: "통화 연결", "연결", "취소" 등의 사용자에게 표시되는 문자열들이 하드코딩되어 있습니다.
    • 매직 넘버: 321.dp와 같은 특정 dp 값이 하드코딩되어 있습니다.
  • 피드백:
    • 깔끔하고 재사용 가능한 다이얼로그 컴포저블입니다.
    • 개선 권고: 사용자에게 표시되는 모든 문자열은 strings.xml 리소스 파일로 이동하여 국제화 및 유지보수성을 확보해 주세요.
    • 개선 권고: 321.dp와 같은 고정된 dimension 값은 dimens.xml 리소스 또는 디자인 시스템에서 정의된 상수로 관리하는 것이 좋습니다.

CharacterChangeConfirmDialog.kt (신규)

  • 변경 내용: 메인 연인 교체 확인 다이얼로그 컴포저블을 추가했습니다.
  • 리뷰:
    • State Hoisting: 파라미터를 통해 상태와 이벤트를 상위로 호이스팅합니다.
    • buildAnnotatedString: 설명 텍스트에서 특정 부분("최소 3일")에 스타일을 적용하기 위해 buildAnnotatedStringwithStyle을 사용한 점이 좋습니다.
    • 하드코딩된 문자열/매직 넘버: CallConnectDialog.kt와 동일한 피드백입니다.
  • 피드백:
    • 개선 권고: 사용자에게 표시되는 모든 문자열은 strings.xml 리소스 파일로 이동해 주세요.
    • 개선 권고: 321.dp 및 "최소 3일"과 같은 상수 값은 리소스로 관리해 주세요.

CharacterChangeUnavailableDialog.kt (신규)

  • 변경 내용: 캐릭터 변경 불가 안내 다이얼로그 컴포저블을 추가했습니다.
  • 리뷰:
    • CharacterChangeConfirmDialog.kt와 동일하게 AnnotatedString을 잘 활용했습니다.
    • 하드코딩된 문자열/매직 넘버: CallConnectDialog.kt와 동일한 피드백입니다.
  • 피드백:
    • 개선 권고: 사용자에게 표시되는 모든 문자열은 strings.xml 리소스 파일로 이동해 주세요.
    • 개선 권고: 321.dp 및 "24시간"과 같은 상수 값은 리소스로 관리해 주세요.

NonMainCharacterCallDialog.kt (신규)

  • 변경 내용: 메인 연인이 아닌 캐릭터에게 전화 시 표시되는 다이얼로그 컴포저블을 추가했습니다.
  • 리뷰:
    • 하드코딩된 문자열/매직 넘버: CallConnectDialog.kt와 동일한 피드백입니다.
  • 피드백:
    • 개선 권고: 사용자에게 표시되는 모든 문자열은 strings.xml 리소스 파일로 이동해 주세요.
    • 개선 권고: 321.dp와 같은 상수 값은 리소스로 관리해 주세요.

HomeHeader.kt

  • 변경 내용: onCharacterChangeClick, onNotificationClick, onCallClick 콜백을 추가하여 상태 호이스팅을 강화했습니다. 상단 바 패딩 처리를 위해 systemBarsPadding()을 적용하고, UI 요소들의 패딩 및 크기 조정을 했습니다.
  • 리뷰:
    • State Hoisting: 새로운 콜백들이 적절히 파라미터로 전달되어 상태 호이스팅 원칙을 잘 지키고 있습니다.
    • systemBarsPadding(): 상단 시스템 바(status bar)와의 충돌을 피하기 위해 systemBarsPadding()을 사용한 것은 Jetpack Compose에서 권장되는 방식입니다.
    • 불필요한 리컴포지션: summarydata class이므로, 내부 값이 변경될 때만 리컴포지션이 발생하여 효율적입니다.
    • 하드코딩된 Dimension: 265.dp, 93.dp, 24.dp 등 많은 dp 값들이 컴포저블 내부에 하드코딩되어 있습니다.
  • 피드백:
    • systemBarsPadding() 적용과 콜백 추가는 UI 안정성과 유지보수성 측면에서 좋은 개선입니다.
    • 개선 권고: 하드코딩된 모든 Dimension 값들(265.dp, 93.dp 등)을 dimens.xml 파일에 정의된 dimension 리소스 또는 디자인 시스템에서 정의된 테마 상수로 추출하여 관리해 주세요. 이는 일관된 디자인과 쉬운 수정, 다양한 화면 크기에 대한 적응력을 높이는 데 도움이 됩니다.

CallHistoryList.kt

  • 변경 내용: 통화 기록 보기 버튼을 Image + clickable에서 IconButton으로 변경하고, IcontintColor.Unspecified로 설정했습니다. 리소스 참조 방식도 변경되었습니다.
  • 리뷰:
    • 접근성 (Accessibility) 개선: Imageclickable 모디파이어를 사용하는 대신 IconButton을 사용하는 것은 매우 훌륭한 개선입니다. IconButton은 시맨틱 정보와 함께 클릭 상태 피드백을 기본적으로 제공하여 접근성과 사용자 경험을 크게 향상시킵니다.
    • tint = Color.Unspecified: 아이콘의 원본 색상을 유지하고 Compose의 LocalContentColor에 의해 색상이 변경되지 않도록 하는 올바른 설정입니다.
    • 하드코딩된 Dimension: 32.dp, 42.dp, 26.dp, 23.dp 등의 dp 값들이 하드코딩되어 있습니다.
  • 피드백:
    • IconButton으로의 변경은 코드 품질과 접근성 측면에서 최고의 변화 중 하나입니다.
    • 개선 권고: 하드코딩된 Dimension 값들을 dimens.xml 리소스 또는 테마 상수로 추출하여 관리해 주세요.

NotificationList.kt

  • 변경 내용: NotificationPagingContent 컴포저블을 도입하여 Flow<PagingData<HomeNotification>>LazyPagingItems로 변환하고, notificationItems 람다에서 onCallClick 콜백을 추가했습니다. notification.characterNamenull일 때의 안전 처리를 추가했습니다.
  • 리뷰:
    • Flow/StateFlow 사용: Flow<PagingData<HomeNotification>>collectAsLazyPagingItems()를 통해 수집하는 것은 Paging3 라이브러리를 Compose에서 사용하는 올바르고 권장되는 방식입니다. repeatOnLifecycle을 명시적으로 사용할 필요 없이, LazyPagingItems가 내부적으로 라이프사이클을 인지하여 데이터 수집을 관리합니다.
    • Null 안전성: notification.characterName?.let(onCallClick)과 같이 characterNamenull일 가능성을 고려하여 안전하게 처리한 점이 좋습니다.
    • State Hoisting: onCallClick 콜백을 파라미터로 전달하여 상태 호이스팅 원칙을 잘 준수하고 있습니다.
  • 피드백: Paging3와 Compose를 통합하는 모범적인 사례입니다. Flow 컬렉션과 null 안전성 처리도 훌륭합니다.

CallRecordHeader.kt (신규)

  • 변경 내용: 통화 기록 화면의 헤더 컴포저블을 추가했습니다.
  • 리뷰:
    • State Hoisting: callInfo (데이터 클래스)와 onBackClick 콜백을 파라미터로 받아 상태를 호이스팅합니다.
    • UI 상태: callInfodata class로 표현한 것은 좋습니다.
    • statusBarsPadding(): 상단 시스템 바 패딩 처리를 위해 적절히 사용되었습니다.
    • 시간 형식화: TimeUtil.formatDateTime 유틸리티를 사용하여 LocalDateTime을 형식화하는 것은 좋습니다. TimeUtil이 견고하게 구현되어 있다고 가정합니다.
    • 하드코딩된 문자열/Dimension: "통화 기록"과 여러 dp 값들이 하드코딩되어 있습니다.
  • 피드백:
    • 잘 구조화된 헤더 컴포저블입니다.
    • 개선 권고: 사용자에게 표시되는 모든 문자열은 strings.xml 리소스 파일로 이동해 주세요.
    • 개선 권고: 하드코딩된 Dimension 값들을 dimens.xml 리소스 또는 테마 상수로 추출하여 관리해 주세요.

전반적인 요약 및 추가 권고 사항

  1. 하드코딩된 문자열 (Hardcoded Strings): 대부분의 Jetpack Compose 컴포저블에서 사용자에게 표시되는 문자열 (label, title, description, buttonText 등)이 하드코딩되어 있습니다. 이를 strings.xml 리소스 파일로 옮겨서 국제화(i18n) 및 유지보수성을 확보해야 합니다. 이는 가장 시급하게 개선해야 할 부분입니다.
  2. 매직 넘버 및 Dimension (Magic Numbers & Dimensions): 321.dp와 같은 고정된 dp 값이나 30, 48 같은 숫자 값들이 컴포저블 코드 내부에 직접 사용되고 있습니다. 이들을 dimens.xml 리소스 또는 디자인 시스템에서 정의된 Kotlin 상수로 추출하여 디자인 일관성과 유지보수성을 높이는 것이 좋습니다.
  3. Null 안전성 재확인 (Null Safety Re-verification): 매퍼 (특히 CallHistoryMapper.kt, CallTranscriptMapper.kt, ReservationMapper.kt)와 DTO (CallTranscriptResponseDto.kt)에서 !! 강제 언래핑을 사용하는 부분이나, null일 가능성이 있는 필드를 non-null로 가정한 부분이 있습니다. API 명세와 대조하여 null 가능성을 정확히 파악하고, ?.let { ... } ?: defaultValue와 같은 안전한 방식으로 처리하거나 DTO 필드를 nullable 타입으로 변경해 주세요. LocalDateTime.parse 호출 시 에러 처리에 대해서도 고려해 주세요.
  4. Vector Drawable 색상 관리: 새로 추가된 벡터 드로어블 XML 파일에 직접 하드코딩된 색상 값들을 colors.xml에 정의된 컬러 리소스로 대체하여 디자인 시스템의 일관성을 유지하고 테마 변경에 유연하게 대응할 수 있도록 해주세요.
  5. HomeUseCase 삭제 후 로직 처리: HomeUseCase 삭제 이후, 기존 HomeOverview 데이터를 구성하던 로직이 어디로 이동했는지 확인이 필요합니다. 만약 ViewModel에서 해당 로직을 직접 처리한다면, ViewModel의 책임이 과도하게 커지지 않도록 주의하고, 필요한 경우 더 작고 특화된 UseCase를 재도입하는 것을 검토해 볼 수 있습니다.

이 PR은 새로운 기능 도입과 기존 아키텍처 개선에 중요한 기여를 했습니다. 위에 언급된 피드백을 반영하면 더욱 견고하고 유지보수성이 높은 코드가 될 것입니다. 잘하셨습니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
feature/home/impl/src/main/java/kr/co/call/impl/component/history/NotificationList.kt (1)

82-93: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

characterName이 null일 때 다시 전화하기 버튼이 무반응입니다.

characterName?.let(onCallClick)은 null이면 콜백을 호출하지 않지만, 아래 버튼은 여전히 활성 상태로 렌더링됩니다. 버튼을 characterName != null일 때만 활성화하거나, 표시 이름이 아닌 안정적인 characterId를 전달하는 계약으로 변경해 주세요.

🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/history/NotificationList.kt`
around lines 82 - 93, NotificationCard 호출부의 onCallClick 처리에서 characterName이
null일 때도 버튼이 활성화되는 문제를 수정하세요. characterName이 존재할 때만 버튼이 활성화되도록 NotificationCard에
해당 상태를 전달하거나, 안정적인 characterId를 콜백 계약으로 사용하도록 관련 onCallClick 흐름을 변경하세요.
feature/home/impl/src/main/java/kr/co/call/impl/component/header/HomeHeader.kt (1)

64-84: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

systemBarsPadding()를 실제 콘텐츠가 배치되는 컨테이너에 적용하세요.

현재 systemBarsPadding()이 내용이 없는 배경 Box에만 있어 상태바 인셋이 콘텐츠 레이아웃에 반영되지 않습니다. 상단 우측 버튼, 헤더 텍스트, 전화 버튼이 바깥 Box 기준 align+고정 오프셋에 배치되어 있어 상태바와 겹칠 수 있으므로, 콘텐츠가 배치되는 바깥 BoxstatusBarsPadding()을 적용하거나 각 콘텐츠 컨테이너/최상위 ContentBox에 인셋을 전달해야 합니다.

🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/header/HomeHeader.kt`
around lines 64 - 84, Apply status-bar insets to the outer content container
that positions the header’s top-right button, header text, and call button,
rather than only the empty background Box. Update the relevant parent Box or
ContentBox around the Row and other header content to use statusBarsPadding(),
while preserving the background sizing and existing child alignment.
🧹 Nitpick comments (10)
feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CallConnectDialog.kt (1)

12-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Modifier를 필수 선행 파라미터로 변경하세요.

이 프로젝트의 경로 지침에 따라 modifier를 파라미터 앞쪽에 배치하고 기본값 없이 받도록 맞춰 주세요. Preview와 모든 호출부에서도 명시적으로 전달해야 합니다.

🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CallConnectDialog.kt`
around lines 12 - 16, CallConnectDialog의 modifier를 기본값 없는 필수 파라미터로 변경하고, 프로젝트
규칙에 맞게 기존 파라미터보다 앞에 배치하세요. Preview와 모든 CallConnectDialog 호출부에서 modifier를 명시적으로
전달하도록 업데이트하세요.

Source: Path instructions

gradle/libs.versions.toml (1)

50-50: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Compose BOM과 개별 라이브러리 버전을 혼용하지 마세요.

composeBom = "2026.02.01"을 사용하면서 ui-graphics1.11.4로 고정하면 BOM이 관리하는 Compose 버전을 우회합니다. 공식 문서도 BOM 사용 시 Compose 라이브러리 의존성에는 개별 버전을 지정하지 않는 방식을 안내합니다. (developer.android.com)

기존 androidx-compose-ui-graphics alias를 재사용하고 uiGraphics 버전과 version.ref를 제거하거나, 전체 Compose BOM을 해당 버전에 맞게 함께 갱신해 주세요.

Also applies to: 123-125

🤖 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 `@gradle/libs.versions.toml` at line 50, Compose BOM을 사용하는 구성에서 uiGraphics의 개별
버전 고정을 제거하세요. gradle/libs.versions.toml의 composeBom과
androidx-compose-ui-graphics alias를 기준으로 해당 alias를 재사용하고, uiGraphics 버전 항목과
version.ref 연결을 삭제해 BOM이 Compose 버전을 관리하도록 수정하세요.

Source: MCP tools

core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeMockRepository.kt (2)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

characters는 재할당되지 않으므로 val로 선언 가능.

파일 전체에서 characters를 다시 할당하는 코드가 없습니다. var 대신 val로 선언해 의도를 명확히 할 수 있습니다.

🤖 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 `@core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeMockRepository.kt`
at line 15, In HomeMockRepository, change the characters property declaration
from var to val, since it is never reassigned and should be immutable.

85-109: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

reservations mutable 상태에 대한 동시성 보호 부재.

@Singleton 스코프인 HomeMockRepositoryreservations 필드를 동기화 없이 read → copy → write 하고 있습니다. 여러 코루틴에서 동시에 changeReservationTime을 호출하면 갱신이 손실될 수 있습니다. Mock 구현이라 당장 영향은 제한적이지만, Mutexsynchronized 블록으로 보호하는 것을 권장합니다.

🤖 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 `@core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeMockRepository.kt`
around lines 85 - 109, Protect the read–modify–write sequence in
HomeMockRepository.changeReservationTime with a Mutex or synchronized block.
Ensure both reservation existence checking and the subsequent reservations
copy/update occur atomically so concurrent calls cannot overwrite each other’s
changes.
core/data/src/main/java/kr/co/call/data/mapper/CallTranscriptMapper.kt (1)

16-21: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

알 수 없는 speaker 값에 대한 예외 처리 검토.

서버가 "사용자", "USER", "AI" 외의 값을 반환하면 error(...)로 즉시 예외가 발생합니다. 향후 이 매퍼가 실제 API 응답 처리에 연동될 때, 호출부에서 runCatching으로 감싸지 않으면 예기치 못한 크래시로 이어질 수 있습니다. 알 수 없는 값에 대해 로깅 후 폴백(예: AI 또는 별도 UNKNOWN)을 반환하는 방식을 고려해보세요.

🤖 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 `@core/data/src/main/java/kr/co/call/data/mapper/CallTranscriptMapper.kt`
around lines 16 - 21, Update String.toDomainSpeaker so unknown speaker values do
not throw via error(...); instead, log the unsupported value and return the
project’s established fallback speaker, preferably a dedicated UNKNOWN value or
AI if no UNKNOWN exists. Preserve the existing mappings for "사용자", "USER", and
"AI".
core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt (1)

50-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

unsupportedApi() 헬퍼가 두 Repository 구현체에 중복 정의됨.

HomeRepositoryImplCallRecordRepositoryImpl이 동일한 구조의 private fun <T> unsupportedApi(): Result<T> 헬퍼를 각각 정의하고 있습니다. "서버 API 미연동" 실패를 나타내는 공통 패턴이므로 하나의 공용 확장 함수(예: core/data 패키지의 공용 Result<T> 확장 또는 공유 유틸 객체)로 추출해 중복을 제거하는 것을 권장합니다.

  • core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt#L50-L53: 이 헬퍼를 공용 유틸로 추출하고 메시지를 파라미터화하세요.
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/CallRecordRepositoryImpl.kt#L17-L20: 동일한 공용 유틸을 재사용하도록 로컬 헬퍼를 제거하세요.
🤖 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 `@core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt`
around lines 50 - 53, Extract the duplicated unsupportedApi() helpers from
HomeRepositoryImpl
(core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt:50-53)
and CallRecordRepositoryImpl
(core/data/src/main/java/kr/co/call/data/repositoryImpl/CallRecordRepositoryImpl.kt:17-20)
into one shared core/data utility or Result<T> extension, parameterizing the
failure message. Remove both local helpers and update each repository to reuse
the shared function while preserving the existing failure behavior.
core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt (1)

26-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mock Repository 임시 바인딩에 추적용 TODO 권장.

bindHomeRepositoryHomeMockRepository로, bindCallRecordRepositoryCallRecordMockRepository로 연결되어 있습니다. PR 설명상 의도된 임시 조치이나, 실제 API(HomeRepositoryImpl, CallRecordRepositoryImpl)로 되돌리는 작업이 누락되기 쉬운 지점입니다. 관련 이슈 번호를 포함한 // TODO: 주석을 남겨 추적하는 것을 권장합니다.

🤖 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 `@core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt` around lines
26 - 43, Add tracking TODO comments with the relevant issue number above
bindHomeRepository and bindCallRecordRepository in RepositoryModule, documenting
that the mock bindings must later be restored to HomeRepositoryImpl and
CallRecordRepositoryImpl. Keep the existing bindings unchanged.
feature/home/impl/src/main/java/kr/co/call/impl/screen/HomeScreen.kt (1)

62-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

notifications가 상태(ViewModel)가 아닌 화면 내부 mock에서 생성됩니다.

stateviewModel.collectAsState()로 구독하는 반면 notificationscreateHomeMockData().notificationFlow()로 composable 안에서 직접 만들어져 ViewModel/State 흐름을 우회합니다. 임시 mock 연결로 보이지만, 다른 상태와 데이터 소스가 이원화되어 있으니 추후 ViewModel(State/Flow) 경유로 통일하는 것을 권장합니다.

🤖 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 `@feature/home/impl/src/main/java/kr/co/call/impl/screen/HomeScreen.kt` around
lines 62 - 64, Unify the notifications data source with the ViewModel state
flow: update the HomeScreen composable to obtain notifications from the
collected viewModel state, and remove the direct
createHomeMockData().notificationFlow() creation from remember. Preserve the
existing notification rendering while routing updates through the ViewModel
alongside the other screen state.
feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/HomeViewModel.kt (1)

338-352: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

loadHome의 독립적인 4개 조회를 병렬화하면 초기 로딩 지연을 줄일 수 있습니다.

getSummary/getReservations/getCallHistories/getCharacters는 서로 의존성이 없는데도 순차적으로 getOrThrow()까지 대기하고 있어 총 지연이 네 호출의 합이 됩니다. coroutineScope + async로 병렬 실행하면 가장 느린 호출 기준으로 단축됩니다.

♻️ 병렬 로딩 예시
-            val summary = homeRepository.getSummary().getOrThrow()
-            val reservations = homeRepository.getReservations().getOrThrow()
-            val callHistories = homeRepository.getCallHistories().getOrThrow()
-            val characters = homeRepository.getCharacters().getOrThrow()
+            val (summary, reservations, callHistories, characters) = coroutineScope {
+                val summaryDeferred = async { homeRepository.getSummary().getOrThrow() }
+                val reservationsDeferred = async { homeRepository.getReservations().getOrThrow() }
+                val callHistoriesDeferred = async { homeRepository.getCallHistories().getOrThrow() }
+                val charactersDeferred = async { homeRepository.getCharacters().getOrThrow() }
+                listOf(summaryDeferred, reservationsDeferred, callHistoriesDeferred, charactersDeferred)
+                    .awaitAll()
+                Quadruple(
+                    summaryDeferred.getCompleted(),
+                    reservationsDeferred.getCompleted(),
+                    callHistoriesDeferred.getCompleted(),
+                    charactersDeferred.getCompleted(),
+                )
+            }
🤖 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 `@feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/HomeViewModel.kt`
around lines 338 - 352, Update loadHome so the independent getSummary,
getReservations, getCallHistories, and getCharacters calls execute concurrently
using coroutineScope with async, then await each result before the existing
reduce mapping. Preserve the current error propagation through getOrThrow() and
the resulting state contents.
feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallTranscriptList.kt (1)

51-53: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

LazyColumn 항목에 안정적인 key 미지정.

items(items = transcripts) { ... }key가 없어 리스트 변경 시 비효율적이거나 잘못된 recomposition이 발생할 수 있습니다. CallTranscript에 고유 id가 있다면 이를 key로, 없다면 최소 index 기반 key를 지정하세요.

이 코멘트는 path instruction **/*Screen.kt(LazyColumn/LazyRow에 안정적인 key를 지정했는지)에 근거합니다.

🛠️ 제안 수정 (예시, id가 없는 경우 index 사용)
-        items(items = transcripts) { transcript ->
+        itemsIndexed(items = transcripts, key = { index, _ -> index }) { _, transcript ->
             CallTranscriptBubble(transcript = transcript)
         }
🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallTranscriptList.kt`
around lines 51 - 53, Update the LazyColumn items call containing
CallTranscriptBubble to provide a stable key for each transcript, using
CallTranscript’s unique identifier if available; otherwise use the item index as
the fallback key. Keep the existing transcript rendering unchanged.

Source: Path instructions

🤖 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 `@app/src/main/java/kr/co/call/callfromai/AppScreen.kt`:
- Around line 106-113: Attach a screenshot or screen recording to the PR
demonstrating the home-to-call-record transition implemented in the homeEntry
navigation flow, including navigation via CallRecordNavKey and returning with
popBackStack.

In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallRecordPlayer.kt`:
- Around line 103-121: CallRecordProgressBar의 pointerInput이 매 리컴포지션마다 변경되는
onProgressChange 람다를 key로 사용하지 않도록 수정하세요. rememberUpdatedState로 최신 콜백을 감싸고,
pointerInput은 Unit 같은 안정적인 key를 사용해 제스처 코루틴을 재시작하지 않으면서도 탭 처리 시 최신 콜백을 호출하도록
유지하세요.

---

Outside diff comments:
In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/header/HomeHeader.kt`:
- Around line 64-84: Apply status-bar insets to the outer content container that
positions the header’s top-right button, header text, and call button, rather
than only the empty background Box. Update the relevant parent Box or ContentBox
around the Row and other header content to use statusBarsPadding(), while
preserving the background sizing and existing child alignment.

In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/history/NotificationList.kt`:
- Around line 82-93: NotificationCard 호출부의 onCallClick 처리에서 characterName이 null일
때도 버튼이 활성화되는 문제를 수정하세요. characterName이 존재할 때만 버튼이 활성화되도록 NotificationCard에 해당
상태를 전달하거나, 안정적인 characterId를 콜백 계약으로 사용하도록 관련 onCallClick 흐름을 변경하세요.

---

Nitpick comments:
In `@core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt`:
- Around line 26-43: Add tracking TODO comments with the relevant issue number
above bindHomeRepository and bindCallRecordRepository in RepositoryModule,
documenting that the mock bindings must later be restored to HomeRepositoryImpl
and CallRecordRepositoryImpl. Keep the existing bindings unchanged.

In `@core/data/src/main/java/kr/co/call/data/mapper/CallTranscriptMapper.kt`:
- Around line 16-21: Update String.toDomainSpeaker so unknown speaker values do
not throw via error(...); instead, log the unsupported value and return the
project’s established fallback speaker, preferably a dedicated UNKNOWN value or
AI if no UNKNOWN exists. Preserve the existing mappings for "사용자", "USER", and
"AI".

In
`@core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeMockRepository.kt`:
- Line 15: In HomeMockRepository, change the characters property declaration
from var to val, since it is never reassigned and should be immutable.
- Around line 85-109: Protect the read–modify–write sequence in
HomeMockRepository.changeReservationTime with a Mutex or synchronized block.
Ensure both reservation existence checking and the subsequent reservations
copy/update occur atomically so concurrent calls cannot overwrite each other’s
changes.

In
`@core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt`:
- Around line 50-53: Extract the duplicated unsupportedApi() helpers from
HomeRepositoryImpl
(core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt:50-53)
and CallRecordRepositoryImpl
(core/data/src/main/java/kr/co/call/data/repositoryImpl/CallRecordRepositoryImpl.kt:17-20)
into one shared core/data utility or Result<T> extension, parameterizing the
failure message. Remove both local helpers and update each repository to reuse
the shared function while preserving the existing failure behavior.

In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CallConnectDialog.kt`:
- Around line 12-16: CallConnectDialog의 modifier를 기본값 없는 필수 파라미터로 변경하고, 프로젝트 규칙에
맞게 기존 파라미터보다 앞에 배치하세요. Preview와 모든 CallConnectDialog 호출부에서 modifier를 명시적으로 전달하도록
업데이트하세요.

In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallTranscriptList.kt`:
- Around line 51-53: Update the LazyColumn items call containing
CallTranscriptBubble to provide a stable key for each transcript, using
CallTranscript’s unique identifier if available; otherwise use the item index as
the fallback key. Keep the existing transcript rendering unchanged.

In `@feature/home/impl/src/main/java/kr/co/call/impl/screen/HomeScreen.kt`:
- Around line 62-64: Unify the notifications data source with the ViewModel
state flow: update the HomeScreen composable to obtain notifications from the
collected viewModel state, and remove the direct
createHomeMockData().notificationFlow() creation from remember. Preserve the
existing notification rendering while routing updates through the ViewModel
alongside the other screen state.

In `@feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/HomeViewModel.kt`:
- Around line 338-352: Update loadHome so the independent getSummary,
getReservations, getCallHistories, and getCharacters calls execute concurrently
using coroutineScope with async, then await each result before the existing
reduce mapping. Preserve the current error propagation through getOrThrow() and
the resulting state contents.

In `@gradle/libs.versions.toml`:
- Line 50: Compose BOM을 사용하는 구성에서 uiGraphics의 개별 버전 고정을 제거하세요.
gradle/libs.versions.toml의 composeBom과 androidx-compose-ui-graphics alias를 기준으로
해당 alias를 재사용하고, uiGraphics 버전 항목과 version.ref 연결을 삭제해 BOM이 Compose 버전을 관리하도록
수정하세요.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5635a074-5b6c-4cbf-8588-04465e837241

📥 Commits

Reviewing files that changed from the base of the PR and between 8d1e029 and e7e858b.

📒 Files selected for processing (65)
  • app/src/main/java/kr/co/call/callfromai/AppScreen.kt
  • core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt
  • core/data/src/main/java/kr/co/call/data/mapper/CallHistoryMapper.kt
  • core/data/src/main/java/kr/co/call/data/mapper/CallTranscriptMapper.kt
  • core/data/src/main/java/kr/co/call/data/mapper/HomeMapper.kt
  • core/data/src/main/java/kr/co/call/data/mapper/ReservationMapper.kt
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/CallRecordMockRepository.kt
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/CallRecordRepositoryImpl.kt
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeMockRepository.kt
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/HomeRepositoryImpl.kt
  • core/designsystem/build.gradle.kts
  • core/designsystem/src/main/res/drawable/ic_home_call_pause.xml
  • core/designsystem/src/main/res/drawable/ic_home_call_play.xml
  • core/designsystem/src/main/res/drawable/ic_home_call_replay_5.xml
  • core/designsystem/src/main/res/drawable/ic_home_call_replay_5_after.xml
  • core/domain/src/main/java/kr/co/call/domain/exception/CharacterChangeUnavailableException.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallHistory.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallRecord.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallReservation.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallReservations.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/Home.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/HomeNotification.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/HomeOverview.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/HomeSummary.kt
  • core/domain/src/main/java/kr/co/call/domain/repository/CallRecordRepository.kt
  • core/domain/src/main/java/kr/co/call/domain/repository/HomeRepository.kt
  • core/domain/src/main/java/kr/co/call/domain/usecase/home/HomeUseCase.kt
  • core/network/src/main/java/kr/co/call/network/dto/CallTranscriptResponseDto.kt
  • feature/home/api/src/main/java/kr/co/call/api/CallRecordRoute.kt
  • feature/home/impl/build.gradle.kts
  • feature/home/impl/src/main/java/kr/co/call/impl/component/CharacterChangeDialog.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/TimeChangeBottomSheet.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CallConnectDialog.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeConfirmDialog.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeUnavailableDialog.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/NonMainCharacterCallDialog.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/header/HomeHeader.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/history/CallHistoryList.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/history/NotificationList.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallRecordHeader.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallRecordLoadingContent.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallRecordPlayer.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallRecordPlayerState.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallTranscriptList.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/entry/HomeEntryBuilder.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/mapper/CallRecordUiMapper.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/mapper/HomeUiMapper.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/mock/CharacterMockData.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/screen/CallRecordScreen.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/screen/HomeScreen.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/util/DateTimeRounding.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/CallRecordIntent.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/CallRecordSideEffect.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/CallRecordViewModel.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/HomeIntent.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/HomeSideEffect.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/HomeViewModel.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/model/CallRecordUiModel.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/model/HomeReservationUiModel.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/state/CallRecordState.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/state/HomeDialogState.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/state/HomeState.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/viewmodel/state/TimeChangeState.kt
  • feature/home/impl/src/test/java/kr/co/call/impl/util/DateTimeRoundingTest.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (10)
  • core/domain/src/main/java/kr/co/call/domain/model/home/HomeNotification.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/CharacterChangeDialog.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/HomeSummary.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallReservations.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/HomeOverview.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallReservation.kt
  • core/domain/src/main/java/kr/co/call/domain/model/home/CallHistory.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/mock/CharacterMockData.kt
  • core/domain/src/main/java/kr/co/call/domain/usecase/home/HomeUseCase.kt
  • core/data/src/main/java/kr/co/call/data/mapper/CallHistoryMapper.kt

Comment on lines +106 to +113
homeEntry(
navigateToCallRecord = { callId ->
appNavigator.navigate(CallRecordNavKey(callId = callId))
},
onCallRecordBack = {
appNavigator.popBackStack()
},
)

@coderabbitai coderabbitai Bot Jul 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

홈→통화 기록 전환 화면 자료를 PR에 첨부해 주세요.

PR 설명에 이 변경 흐름을 확인할 스크린샷 또는 화면 녹화가 없습니다. As per coding guidelines, "*Screen.kt 변경 시 스크린샷 또는 화면 녹화가 첨부되었는지 확인하고 없으면 경고한다" 규칙에 따른 사항입니다.

🤖 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 `@app/src/main/java/kr/co/call/callfromai/AppScreen.kt` around lines 106 - 113,
Attach a screenshot or screen recording to the PR demonstrating the
home-to-call-record transition implemented in the homeEntry navigation flow,
including navigation via CallRecordNavKey and returning with popBackStack.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이거왜안했엇!!!!!!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@codebidoof 맞습니다, 이건 제가 코드 변경과 별개로 PR 설명의 화면 자료 첨부 여부를 체크하는 가이드라인 때문에 잡은 항목이에요. 이번 PR에서 HomeScreen.ktCallRecordScreen.kt가 변경됐는데, 설명에 홈 → 통화 기록 진입 및 뒤로가기 흐름을 보여주는 스크린샷/영상이 없어서 경고됐습니다.

번거롭게 해서 죄송합니다 🙇 PR 설명에 해당 화면 자료만 첨부하면 충분합니다.

🐰💦

Comment on lines +103 to +121
@Composable
private fun CallRecordProgressBar(
progress: Float,
onProgressChange: (Float) -> Unit,
modifier: Modifier = Modifier,
) {
val inactiveColor = CallTheme.colors.gray100
val activeColor = CallTheme.colors.mainVariant1

Canvas(
modifier = modifier
.fillMaxWidth()
.height(12.dp)
.pointerInput(onProgressChange) {
detectTapGestures { offset ->
onProgressChange((offset.x / size.width).coerceIn(0f, 1f))
}
},
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

pointerInput key로 불안정한 람다 사용 — 재생 중 탭 제스처 누락 위험.

onProgressChangestate.durationMillis/onSeek을 캡처해 매 리컴포지션마다 새로 생성됩니다. 재생 중 250ms마다 상위(CallRecordScreen)에서 playerState가 갱신되며 이 람다도 계속 바뀌어 pointerInput 코루틴이 반복적으로 취소·재시작되고, 탭 도중 제스처가 씹힐 수 있습니다. rememberUpdatedState로 콜백을 감싸고 pointerInputUnit 등 안정적인 key로 고정하세요.

🛠️ 제안 수정
 `@Composable`
 private fun CallRecordProgressBar(
     progress: Float,
     onProgressChange: (Float) -> Unit,
     modifier: Modifier = Modifier,
 ) {
     val inactiveColor = CallTheme.colors.gray100
     val activeColor = CallTheme.colors.mainVariant1
+    val currentOnProgressChange = rememberUpdatedState(onProgressChange)

     Canvas(
         modifier = modifier
             .fillMaxWidth()
             .height(12.dp)
-            .pointerInput(onProgressChange) {
+            .pointerInput(Unit) {
                 detectTapGestures { offset ->
-                    onProgressChange((offset.x / size.width).coerceIn(0f, 1f))
+                    currentOnProgressChange.value((offset.x / size.width).coerceIn(0f, 1f))
                 }
             },
     ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Composable
private fun CallRecordProgressBar(
progress: Float,
onProgressChange: (Float) -> Unit,
modifier: Modifier = Modifier,
) {
val inactiveColor = CallTheme.colors.gray100
val activeColor = CallTheme.colors.mainVariant1
Canvas(
modifier = modifier
.fillMaxWidth()
.height(12.dp)
.pointerInput(onProgressChange) {
detectTapGestures { offset ->
onProgressChange((offset.x / size.width).coerceIn(0f, 1f))
}
},
) {
`@Composable`
private fun CallRecordProgressBar(
progress: Float,
onProgressChange: (Float) -> Unit,
modifier: Modifier = Modifier,
) {
val inactiveColor = CallTheme.colors.gray100
val activeColor = CallTheme.colors.mainVariant1
val currentOnProgressChange = rememberUpdatedState(onProgressChange)
Canvas(
modifier = modifier
.fillMaxWidth()
.height(12.dp)
.pointerInput(Unit) {
detectTapGestures { offset ->
currentOnProgressChange.value((offset.x / size.width).coerceIn(0f, 1f))
}
},
) {
🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/record/CallRecordPlayer.kt`
around lines 103 - 121, CallRecordProgressBar의 pointerInput이 매 리컴포지션마다 변경되는
onProgressChange 람다를 key로 사용하지 않도록 수정하세요. rememberUpdatedState로 최신 콜백을 감싸고,
pointerInput은 Unit 같은 안정적인 key를 사용해 제스처 코루틴을 재시작하지 않으면서도 탭 처리 시 최신 콜백을 호출하도록
유지하세요.

@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

Gemini API 응답을 읽지 못했습니다.

{
  "error": {
    "code": 500,
    "message": "An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting",
    "status": "INTERNAL"
  }
}

@codebidoof codebidoof left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

고생했음ㅠㅠ

Comment on lines +3 to +7
/**
* 서버 정책상 메인 캐릭터를 아직 교체할 수 없을 때 발생하는 예외
*/
class CharacterChangeUnavailableException :
IllegalStateException("아직 메인 연인을 변경할 수 없습니다.")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이건 임시로 둔 것 같군요..호엥

Comment on lines +14 to +22
data class CallTranscript(
val content: String,
val speaker: Speaker,
) {
enum class Speaker {
USER,
AI,
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

나같으면 여기서 enum class 따로만들듯 개취입니당

Comment on lines +5 to +59
data class HomeSummary(
val firstName: String,
val relationshipDays: Int,
val totalCallCount: Int,
val callStreakDays: Int,
)

data class HomeCharacter(
val id: Long,
val name: String,
val relationshipDays: Int,
val imageUrl: String?,
val isMain: Boolean,
)

data class CallReservation(
val id: Long,
val characterId: Long,
val firstName: String,
val imageUrl: String?,
val scheduledAt: LocalDateTime,
)

data class CallReservations(
val totalCount: Int,
val items: List<CallReservation>,
)

data class CallHistory(
val callId: Long,
val characterName: String,
val aiSummary: String,
val startedAt: LocalDateTime,
val isOutgoing: Boolean,
val isMissed: Boolean,
)

data class HomeNotification(
val notificationId: Long,
val type: NotificationType,
val title: String,
val content: String,
val isRead: Boolean,
val createdAt: LocalDateTime,
val characterName: String?,
val profileImageUrl: String?,
)

enum class NotificationType(
val title: String,
) {
MISSED_CALL(title = "부재중 전화"),
ANNIVERSARY(title = "기념일"),
CALL_RESERVATION(title = "통화 약속"),
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

주석이 있으면 더 좋을 듯 합니당

Comment on lines +53 to +59
enum class NotificationType(
val title: String,
) {
MISSED_CALL(title = "부재중 전화"),
ANNIVERSARY(title = "기념일"),
CALL_RESERVATION(title = "통화 약속"),
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

여기서 정적 팩토리 메서드까지 정의해놓는 게 더 좋을 듯?

Comment on lines +11 to +29
@Composable
fun CallConnectDialog(
characterName: String,
onConnectClick: () -> Unit,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
) {
TwoButtonPopup(
label = "통화 연결",
title = "${characterName}에게 바로\n통화를 연결할까요?",
positiveText = "연결",
negativeText = "취소",
onPositiveClick = onConnectClick,
onNegativeClick = onDismissRequest,
onDismissRequest = onDismissRequest,
modifier = modifier.width(321.dp),
)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

와 ㅁㅊ 공용컴포넌트를 이런 식으로 써먹을 수도 있구나..

Comment on lines +27 to +30
append("전화는 메인 연인과만 이용할 수 있어요.\n")
append("다른 캐릭터와 전화를 하고 싶다면\n")
append("상단에서 연인 교체하기를 클릭해주세요.")
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

string.xml을 사용하면 더 좋을 듯?

Comment on lines 162 to 173
val iconResource = when (iconType) {
CallHistoryIconType.SENT ->
kr.co.call.designsystem.R.drawable.img_home_call_send
R.drawable.img_home_call_send

CallHistoryIconType.RECEIVED ->
kr.co.call.designsystem.R.drawable.img_home_call_received
R.drawable.img_home_call_received

CallHistoryIconType.MISSED ->
kr.co.call.designsystem.R.drawable.img_home_call_missed
R.drawable.img_home_call_missed
}
val iconSize = when (iconType) {
CallHistoryIconType.SENT -> DpSize(26.dp, 23.dp)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이거는 별도 매퍼 함수를 추출해서 하는게 더 나을 듯? util 패키지 같은 거 하나 파서 정의하는 식으로 개인적인 개발스타일입니당

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

확장 프로퍼티로 해결하면 더 좋을 듯

*/
@Composable
fun NotificationPagingContent(
notifications: Flow<PagingData<HomeNotification>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PagingData는 도메인 레이어에서만 알고 UI 레이어에서는 LazyPagingItems만 다루는 것이 계층 분리 측면에서 더 적절해서 이거 repository에서 Flow<PagingData>를 반환하고 ui에서는 LazyPagingItems 만 아는 게 더 이상적이야.

내 PR에 domain모듈에 paging:common 라이브러리(안드 의존성이 없어서 코틀린 모듈에서도 사용가능) 추가해놓은게 있는데 나중에 api 연동할 때 계층 분리도 같이 해 주면 될 것 같어

Comment on lines +19 to +25
entry<CallRecordNavKey> { key ->
CallRecordScreen(
callId = key.callId,
onBackClick = onCallRecordBack,
)
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

그 단톡에 보낸 방식으로 고쳐주면될듯?

Comment on lines +257 to 285
try {
val reservations = homeRepository.changeReservationTime(
reservationId = reservationId,
scheduledAt = LocalDateTime.of(
timeChange.state.selectedDate,
timeChange.state.selectedTime,
),
).getOrThrow()

reduce {
state.copy(
reservation = reservations.toUiModel(),
dialogState = null,
)
}
postSideEffect(
HomeSideEffect.ShowMessage(
message = "예약 시간을 변경했습니다.",
),
)
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (throwable: Throwable) {
postSideEffect(
HomeSideEffect.ShowMessage(
message = throwable.message ?: "예약 시간을 변경하지 못했습니다.",
),
)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

에러 핸들링 책임은 레포지토리 구현체로 옮기는 게 좋아보임 뷰모델단부턴 성공/실패 만 알게

@JiwonLee42
JiwonLee42 merged commit 1819d65 into develop Jul 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 통화 기록 화면 구현 [Feat] 알림창 UI 구현

2 participants