Skip to content

[Feat] 마이페이지 UI 구현 - 대표 화면 (#11)#18

Merged
WAcAW9 merged 8 commits into
developfrom
feature/11-mypage-ui
Jul 17, 2026
Merged

[Feat] 마이페이지 UI 구현 - 대표 화면 (#11)#18
WAcAW9 merged 8 commits into
developfrom
feature/11-mypage-ui

Conversation

@WAcAW9

@WAcAW9 WAcAW9 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

📄 작업 내용 요약

  • 마이페이지 대표 화면 구현
  • 로그아웃 팝업 구현
  • 계정 탈퇴 팝업 구현
  • 준비중입니다 화면 구현

📎 Issue 번호


✅ 작업 목록

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

📝 기타 참고사항

image

Summary by CodeRabbit

Summary by CodeRabbit

  • 새 기능

    • 마이페이지 화면을 추가해 프로필(이미지·닉네임·티어)과 잔여 티켓 정보를 표시합니다.
    • 티켓 충전/구매/내역 진입, 설정 섹션(메뉴) UI를 제공합니다.
    • 로그아웃/계정 삭제 확인 다이얼로그 및 “준비 중입니다...” 화면을 추가했습니다.
  • UI 개선

    • 공통 상단바(좌측 뒤로가기/타이틀), 좌·우 화살표 및 티켓 아이콘, 기본 프로필 이미지 리소스를 적용했습니다.

@WAcAW9 WAcAW9 linked an issue Jul 17, 2026 that may be closed by this pull request
3 tasks
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

마이페이지 프로필 모델과 저장소를 추가하고, 더미 프로필 조회·로그아웃·계정 삭제를 Hilt와 ViewModel에 연결했습니다. Compose 기반 프로필 카드, 설정 메뉴, 다이얼로그 및 상태 기반 대표 화면을 구현했습니다.

Changes

마이페이지 기능

Layer / File(s) Summary
프로필 계약과 저장소 연결
core/domain/..., core/data/...
MyPageProfileMyPageRepository를 정의하고, 더미 구현체의 프로필 조회·로그아웃·계정 삭제를 Hilt로 바인딩했습니다.
마이페이지 UI 컴포넌트
core/designsystem/..., feature/mypage/impl/build.gradle.kts, feature/mypage/impl/src/main/java/kr/co/call/impl/component/*, feature/mypage/impl/src/main/java/kr/co/call/impl/screen/ComingSoonScreen.kt
프로필·티켓 카드, 설정 메뉴, 공통 상단바, 준비중 화면과 관련 벡터 리소스를 추가하고 Compose용 Coil 의존성을 등록했습니다.
상태와 사용자 의도 처리
feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/*
MyPageState, MyPageIntent, MyPageSideEffect를 추가하고 ViewModel에서 프로필 로딩, 메뉴 이벤트, 로그아웃 및 계정 삭제 후 내비게이션을 처리했습니다.
마이페이지 화면 통합
feature/mypage/impl/src/main/java/kr/co/call/impl/screen/MyPageScreen.kt
화면 상태와 사이드 이펙트를 수집해 프로필 카드, 설정 섹션, 로그아웃·계정 삭제 다이얼로그 및 준비중 화면을 렌더링하도록 변경했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant MyPageScreen
  participant MyPageViewModel
  participant MyPageRepository
  participant MyPageRepositoryImpl

  MyPageScreen->>MyPageViewModel: 프로필 로딩 시작
  MyPageViewModel->>MyPageRepository: getMyProfile()
  MyPageRepository->>MyPageRepositoryImpl: 구현체 호출
  MyPageRepositoryImpl-->>MyPageViewModel: MyPageProfile 반환
  MyPageViewModel-->>MyPageScreen: 상태 업데이트 및 화면 렌더링
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning 제목이 Conventional Commits 형식과 타입 규칙을 따르지 않습니다. 예: "feat: 마이페이지 대표 화면 UI 구현 (#11)"처럼 "type: description" 형식으로 수정하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed 마이페이지 대표 화면, 로그아웃 팝업, 계정 탈퇴 팝업이 모두 구현되어 이슈 #11 요구를 충족합니다.
Out of Scope Changes check ✅ Passed 추가된 모델, 뷰모델, 다이얼로그, 리소스, 의존성은 모두 마이페이지 UI 구현을 지원해 뚜렷한 범위 이탈이 보이지 않습니다.
Ui 변경 시 스크린샷 첨부 확인 ✅ Passed 마이페이지 Screen.kt가 포함됐고 PR 설명에 <img ...> 스크린샷 첨부가 있어 요구를 충족합니다.
모듈 의존성 방향 검증 ✅ Passed feature/mypage/impl은 자체 impl 패키지와 core/domain만 사용했고, core/domain에는 android/androidx import가 없으며, MyPageViewModel도 MyPageRepository 인터페이스만 주입받습니다.
✨ 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 feature/11-mypage-ui

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

@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

코드 리뷰: 마이페이지 기능 구현

안녕하세요! 마이페이지 기능 구현 PR 잘 봤습니다. Orbit MVI, Jetpack Compose, Hilt 등 최신 기술 스택을 활용하여 모듈화되고 깔끔하게 구현해주셨네요. 전반적으로 코드 품질이 높고 가이드라인을 잘 따라주신 것 같아 만족스럽습니다. 몇 가지 개선 사항과 Follow-up 할 부분에 대한 코멘트 남깁니다.


1. Kotlin 코드 리뷰

  • Coroutine 사용 시 Dispatcher 명시 여부, viewModelScope/lifecycleScope 오남용
    • MyPageRepositoryImpl에서 delay()는 cooperative suspend 함수이므로 명시적인 Dispatcher 설정 없이도 메인 스레드를 블로킹하지 않습니다. 실제 API 연동 시에는 Repository Impl 내부에서 withContext(Dispatchers.IO) 등을 사용하여 네트워크/DB 작업이 백그라운드 스레드에서 동작하도록 하는 것이 좋습니다.
    • MyPageViewModelintent 블록은 Orbit의 Container가 제공하는 Coroutine Scope 내에서 동작하므로, viewModelScopelifecycleScope를 직접 오남용하는 경우는 없습니다. 올바른 패턴입니다.
  • Flow/StateFlow 사용 시 collect 시점 (repeatOnLifecycle 사용 여부)
    • Compose 환경에서 Orbit의 collectAsStatecollectSideEffect를 사용했기 때문에 Composable의 lifecycle에 맞춰 안전하게 collect 되고 있습니다. repeatOnLifecycle 등의 명시적인 처리는 필요하지 않습니다.
  • null 안전성 (!! 사용 지양)
    • 코드 전반적으로 !! (non-null assertion operator) 사용은 발견되지 않았습니다. e.message ?: "내 프로필 불러오기 실패" 처럼 Elvis operator를 사용하여 null 안전성을 확보한 점 좋습니다.
  • data class에 불필요한 var 사용 여부
    • MyPageProfileMyPageState 모두 val로만 선언되어 있습니다. 불필요한 var 사용 없이 immutable한 데이터 클래스로 잘 정의되었습니다.
  • Hilt DI 시 생성자 주입 원칙 준수 여부
    • RepositoryModule에서 @Binds를 사용하여 인터페이스-구현체 바인딩을 잘 처리했습니다.
    • MyPageRepositoryImpl, MyPageViewModel 모두 @Inject constructor를 통해 생성자 주입 원칙을 준수했습니다.
  • 하드코딩된 문자열/매직 넘버
    • 개선 필요:
      • core/designsystem/src/main/res/drawable 내의 XML 드로어블 파일들 (ic_mypage_arrow_right.xml, ic_mypage_ticket.xml, img_mypage_profile_default.xml 등)에서 android:fillColorandroid:strokeColor 값들이 #FF8AAF, #FFF2F4 등 하드코딩된 hex 값으로 되어 있습니다. Design System의 테마 색상 (예: CallTheme.colors.mainVariant1)을 사용하도록 colors.xml에 정의하거나, Compose 테마에 컬러를 추가하여 참조하는 것이 좋습니다.
      • MyPageViewModelonFailure 블록에서 사용된 에러 메시지 "내 프로필 불러오기 실패"는 하드코딩된 문자열입니다. 추후 다국어 지원 및 유지보수를 위해 strings.xml 등의 리소스 파일로 분리하는 것을 권장합니다.
      • ComingSoonScreen에서 "준비 중입니다..." 텍스트의 fontSize = 34.sp는 하드코딩된 매직 넘버입니다. Design System의 CallTheme.typography에 해당 스타일을 정의하여 사용하는 것이 일관성을 유지하는 데 좋습니다.

2. Jetpack Compose 코드

  • Composable 함수의 불필요한 recomposition 유발 여부 (remember, key 사용)

    • 대부분의 Composable 함수들이 stateless하게 데이터를 받아 UI를 그리는 방식으로 잘 구성되어 있습니다. rememberkey 사용이 필요한 복잡한 리스트/상태는 아직 보이지 않습니다.
    • ProfileCardSettingsSectionCard에서 RoundedCornerShape(20.dp)와 같이 객체를 생성하는 코드가 매 리컴포지션마다 실행되지만, 이 정도는 성능에 큰 영향이 없으므로 허용 가능합니다.
    • SettingsSectionCarditems: List<@Composable () -> Unit> 패턴은 Composable을 Slot API처럼 전달하는 좋은 방법이지만, 리스트가 매우 크고 빈번하게 변경되는 경우에는 리컴포지션 최적화를 추가로 고려할 수 있습니다 (여기서는 마이페이지 설정 목록이라 괜찮습니다).
  • State hoisting 원칙 준수

    • MyPageScreen에서 stateonIntentMyPageScreenContent로 hoisting하고, 다이얼로그의 showLogoutDialog 등은 MyPageScreen에서 관리하는 등 상태 호이스팅 원칙을 잘 준수했습니다.
  • side effect (LaunchedEffect, DisposableEffect) 사용의 적절성

    • Orbit의 collectSideEffect를 통해 SideEffect를 처리하고 있으며, 이는 Composable의 lifecycle에 맞춰 적절하게 동작합니다. 직접적인 LaunchedEffect 등의 사용은 보이지 않습니다.
  • UI 상태를 data class로 표현했는지

    • MyPageState라는 data class로 UI 상태를 명확하게 표현했습니다.
  • 로딩/에러 상태를 Boolean 대신 LoadStatus로 관리하는지

    • LoadStatus sealed class (Idle, Loading, Error)를 사용하여 로딩 상태를 잘 관리하고 있습니다.
    • Follow-up 필요: MyPageScreenif (state.loadStatus == LoadStatus.Loading) { // TODO: 로딩 인디케이터 오버레이 } 주석이 있습니다. 사용자 경험을 위해 로딩 상태에 따른 UI (예: Progress Bar)를 구현해야 합니다.
  • 기타 Compose 관련

    • AsyncImagecontentDescription: ProfileSummaryContentAsyncImage에서 contentDescription = if (profileImageUrl.isNullOrBlank()) null else profileImageUrl 로 되어 있습니다. contentDescription은 이미지의 소스보다는 의미를 전달하는 것이 좋습니다. null 대신 "프로필 이미지"와 같은 의미 있는 기본값을 제공하거나, profileImageUrl이 공백이 아닐 때 "사용자 [닉네임]의 프로필 이미지" 와 같이 상세하게 제공하는 것을 고려해보세요.
    • CommonTopAppBarcontentDescription: 뒤로가기 아이콘에 "뒤로가기"가 명확하게 명시되어 있어 좋습니다.
    • ProfileTicketContentIcon: ic_mypage_ticketcontentDescriptionnull로 되어 있는데, 이 아이콘이 "통화 이용권"을 시각적으로 나타내는 중요한 요소라면 "통화 이용권 아이콘"과 같은 설명이 스크린 리더 사용자에게 유용할 수 있습니다.

3. Repository/DataSource 레이어

  • Retrofit 에러 핸들링 (try-catch, Result 래핑)
    • 현재 MyPageRepositoryImpl은 "TODO: API 연동 전 임시 구현체" 입니다. 실제 API 연동 시에는 runCatching이나 Result 타입으로 API 응답을 래핑하고, HTTP 에러 (4xx, 5xx)나 네트워크 에러 등을 적절히 처리하는 로직이 추가되어야 합니다. 현재 MyPageViewModelrunCatching은 repository layer가 throw하는 예외를 잡고 있습니다.
  • 네트워크 응답과 도메인 모델 매핑 분리 여부
    • 현재 임시 구현체는 직접 도메인 모델 (MyPageProfile)을 생성하여 반환하고 있습니다. 실제 API 연동 시에는 Data Source (Retrofit 서비스)에서 DTO (Data Transfer Object)를 받아 Repository 구현체에서 이를 도메인 모델로 매핑하는 역할을 수행해야 합니다. 현재 구조는 이 역할을 MyPageRepositoryImpl이 할 수 있도록 잘 설계되어 있습니다.
  • 캐싱 전략 (로컬 DB vs 메모리)
    • 현재 캐싱 전략은 구현되지 않았습니다. 마이페이지 데이터 특성상 빈번한 변경이 없거나, 앱 시작 시 한 번 로드 후 유지되는 경우 메모리 캐싱을 고려해볼 수 있습니다. 하지만 현재 단계에서는 중요한 부분이 아니므로 이후 필요 시 추가해도 좋을 것 같습니다.

4. ViewModel (Orbit)

  • UI 상태와 비즈니스 로직 분리
    • MyPageState로 UI 상태를, MyPageIntent로 사용자 이벤트를, MyPageSideEffect로 일회성 이벤트를 명확히 분리하여 MVI 아키텍처 원칙을 잘 따르고 있습니다. 비즈니스 로직은 MyPageViewModel 내부에 캡슐화되어 있습니다.
  • Orbit ContainerHost 패턴 준수 여부
    • MyPageViewModelContainerHost 인터페이스를 구현하고, container를 사용하여 초기 상태 설정 및 초기화 작업을 수행하는 등 Orbit 패턴을 완벽하게 준수했습니다.
  • intent 내부에서 상태 변경(reduce)만 수행하는지
    • intent 블록 내에서 reduce 함수를 사용하여 상태 변경 로직을 수행하고, postSideEffect를 사용하여 일회성 이벤트를 발생시키는 등 Orbit의 가이드라인을 잘 따르고 있습니다.
  • postSideEffect 남용 여부
    • postSideEffect는 내비게이션, 다이얼로그 표시와 같은 일회성 이벤트에만 사용되어 적절합니다.
  • blocking 작업을 intent 내부에서 직접 수행하지 않는지
    • myPageRepository.getMyProfile()과 같은 suspend 함수 호출은 블로킹 작업이 아니므로 intent 내부에서 안전하게 호출 가능합니다. delay 또한 cooperative 함수이므로 문제가 없습니다.

5. FCM/SSE/실시간 통신 관련 코드

  • 해당 PR에는 FCM/SSE/실시간 통신 관련 코드가 포함되어 있지 않아 검토 범위에서 제외됩니다.

총평

전반적으로 MyPage 기능 구현은 현대 Android 개발 트렌드에 맞춰 매우 잘 설계되고 구현되었습니다. Orbit MVI 패턴을 정확히 이해하고 적용한 점, Compose의 상태 호이스팅 및 효율적인 UI 구성에 신경 쓴 점이 특히 인상 깊습니다.

위에 언급된 개선 사항들은 대부분 마이너한 부분이며, 주로 디자인 시스템 컬러 하드코딩 제거, 접근성 향상을 위한 Content Description 개선, 로딩 UI 구현, 국제화를 위한 문자열 리소스 분리에 집중되어 있습니다. 이 부분들을 반영하면 더욱 완성도 높은 코드가 될 것입니다.

훌륭한 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: 7

🧹 Nitpick comments (3)
feature/mypage/impl/src/main/java/kr/co/call/impl/screen/ComingSoonScreen.kt (2)

47-52: 📐 Maintainability & Code Quality | 🔵 Trivial

앱 로고 아이콘 TODO 처리

Line 48에 TODO: 실제 앱 로고 아이콘으로 교체 주석이 있습니다. 현재 ic_mypage_ticket을 임시로 사용 중입니다. 실제 로고 아이콘으로 교체하는 작업을 추적할 이슈가 필요해 보입니다.

도움이 필요하시면 교체용 drawable 리소스 추가 및 참조 변경 작업을 도와드릴 수 있습니다. 별도 이슈를 열어 추적하시겠습니까?

🤖 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/mypage/impl/src/main/java/kr/co/call/impl/screen/ComingSoonScreen.kt`
around lines 47 - 52, Track the TODO in ComingSoonScreen’s Icon by creating a
follow-up issue for replacing the temporary ic_mypage_ticket painter with the
actual app logo drawable; leave the current resource reference unchanged until
that asset is available.

32-39: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

전체 화면 Dialog를 위한 decorFitsSystemWindows 검토

fillMaxSize()usePlatformDefaultWidth = false를 사용해 전체 화면 Dialog를 의도한 것으로 보이나, decorFitsSystemWindows = false가 설정되어 있지 않아 시스템 바(상태바/내비게이션바) 영역까지 콘텐츠가 확장되지 않습니다. CommonTopAppBar가 화면 최상단에 닿지 않을 수 있습니다.

🔧 제안: decorFitsSystemWindows 추가
     Dialog(
         onDismissRequest = onBackClick,
-        properties = DialogProperties(usePlatformDefaultWidth = false),
+        properties = DialogProperties(
+            usePlatformDefaultWidth = false,
+            decorFitsSystemWindows = false,
+        ),
     ) {
🤖 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/mypage/impl/src/main/java/kr/co/call/impl/screen/ComingSoonScreen.kt`
around lines 32 - 39, Update the full-screen Dialog in ComingSoonScreen by
configuring its DialogProperties with decorFitsSystemWindows disabled, while
preserving usePlatformDefaultWidth = false and the existing Column layout so the
content can extend into system bar areas.
feature/mypage/impl/src/main/java/kr/co/call/impl/screen/MyPageScreen.kt (1)

174-182: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

버전 정보 메뉴의 빈 onClick — 클릭 ripple이 표시되지만 동작 없음

onClick = { }로 전달된 버전 정보 행은 SettingsMenuContent 내부에서 .clickable(onClick = onClick)이 항상 적용되어, 사용자가 탭하면 ripple 효과만 나타나고 아무 일도 일어나지 않습니다. SettingsMenuContentonClick을 nullable로 변경하여 비클릭 항목을 지원하는 것을 권장합니다.

♻️ 제안: SettingsMenuContent onClick을 nullable로 변경

SettingsMenuContent.kt:

 `@Composable`
 fun SettingsMenuContent(
     icon: String,
     label: String,
     modifier: Modifier = Modifier,
-    onClick: () -> Unit,
+    onClick: (() -> Unit)? = null,
     trailing: `@Composable` () -> Unit = { ... },
 ) {
     Row(
         modifier = modifier
-            .clickable(onClick = onClick)
+            .then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
             .padding(vertical = 11.dp, horizontal = 25.dp),
         ...

MyPageScreen.kt 버전 정보 항목:

 SettingsMenuContent(
-    icon = "ℹ️", label = "버전 정보", onClick = { },
+    icon = "ℹ️", label = "버전 정보",
     trailing = { ... }
 })
🤖 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/mypage/impl/src/main/java/kr/co/call/impl/screen/MyPageScreen.kt`
around lines 174 - 182, Update SettingsMenuContent to accept a nullable onClick
callback and apply clickable behavior only when onClick is non-null. Change the
version information item in MyPageScreen to pass null instead of an empty
lambda, preserving click behavior for other menu items while removing the
inactive ripple.
🤖 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
`@feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileSummaryContent.kt`:
- Around line 86-102: Update the Box modifier in TierBadge to use the function’s
modifier parameter as the base before applying clip, background, and padding, so
modifiers supplied by callers are preserved.
- Around line 48-57: Update the AsyncImage contentDescription expression in
ProfileSummaryContent so it no longer exposes profileImageUrl as accessibility
text. Since profileImageUrl is non-nullable, use isBlank() for any blank check
and provide a fixed meaningful description such as “프로필 이미지”, or null when the
image should be decorative.

In
`@feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileTicketContent.kt`:
- Around line 76-96: Update TicketActionButton to remove the fixed width(105.dp)
modifier so callers’ Modifier.weight(1f) controls equal-width distribution
within the Row; preserve the button’s other sizing, styling, click behavior, and
content alignment.

In `@feature/mypage/impl/src/main/java/kr/co/call/impl/screen/MyPageScreen.kt`:
- Line 36: MyPageScreen에서 Orbit MVI 상태 구독에 collectAsState() 대신
collectAsStateWithLifecycle()을 사용하도록 import와 해당 상태 수집 호출을 변경하세요. 화면의 기존 상태 처리와
UI 동작은 그대로 유지하세요.

In
`@feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt`:
- Around line 60-67: Update MyPageViewModel.logout and deleteAccount to catch
repository exceptions, convert them into the established domain error type, and
publish the UI error flow through the existing ShowError-style side effect or
explicit operation state. Only post NavigateToLogin or NavigateToLanding after
the repository call succeeds, preserving the current success destinations.
- Around line 13-15: MyPageViewModel의 MyPageRepository 직접 의존성을 제거하고, 프로필
조회·로그아웃·탈퇴에 대응하는 core:domain UseCase를 주입하세요. ViewModel의 관련 처리 로직은 Repository 호출
대신 각 UseCase를 호출하도록 변경하며, 상태 업데이트와 SideEffect 처리 흐름은 유지하세요.
- Around line 42-56: Update the failure handling in MyPageViewModel’s
runCatching flow so CancellationException is rethrown instead of being converted
to LoadStatus.Error. Keep the existing error-state reduction for all other
exceptions, and add the necessary cancellation check before reduce.

---

Nitpick comments:
In
`@feature/mypage/impl/src/main/java/kr/co/call/impl/screen/ComingSoonScreen.kt`:
- Around line 47-52: Track the TODO in ComingSoonScreen’s Icon by creating a
follow-up issue for replacing the temporary ic_mypage_ticket painter with the
actual app logo drawable; leave the current resource reference unchanged until
that asset is available.
- Around line 32-39: Update the full-screen Dialog in ComingSoonScreen by
configuring its DialogProperties with decorFitsSystemWindows disabled, while
preserving usePlatformDefaultWidth = false and the existing Column layout so the
content can extend into system bar areas.

In `@feature/mypage/impl/src/main/java/kr/co/call/impl/screen/MyPageScreen.kt`:
- Around line 174-182: Update SettingsMenuContent to accept a nullable onClick
callback and apply clickable behavior only when onClick is non-null. Change the
version information item in MyPageScreen to pass null instead of an empty
lambda, preserving click behavior for other menu items while removing the
inactive ripple.
🪄 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: 92e3b12d-b938-40d9-9ff7-1e5a10b97cad

📥 Commits

Reviewing files that changed from the base of the PR and between 148b3eb and 1afe494.

📒 Files selected for processing (21)
  • core/data/src/main/java/kr/co/call/data/di/RepositoryModule.kt
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/MyPageRepositoryImpl.kt
  • core/designsystem/src/main/res/drawable/ic_mypage_arrow_left.xml
  • core/designsystem/src/main/res/drawable/ic_mypage_arrow_right.xml
  • core/designsystem/src/main/res/drawable/ic_mypage_ticket.xml
  • core/designsystem/src/main/res/drawable/img_mypage_profile_default.xml
  • core/domain/src/main/java/kr/co/call/domain/model/mypage/MyPageProfile.kt
  • core/domain/src/main/java/kr/co/call/domain/repository/MyPageRepository.kt
  • feature/mypage/impl/build.gradle.kts
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/CommonTopAppBar.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileCard.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileSummaryContent.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileTicketContent.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/SettingSectionCard.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/SettingsMenuContent.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/screen/ComingSoonScreen.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/screen/MyPageScreen.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageIntent.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageSideEffect.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageState.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt

Comment on lines +13 to +15
class MyPageViewModel @Inject constructor(
private val myPageRepository: MyPageRepository,
) : ViewModel(), ContainerHost<MyPageState, MyPageSideEffect> {

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 | 🟠 Major | 🏗️ Heavy lift

ViewModel에서 Repository를 직접 호출하지 말고 UseCase로 위임하세요.

현재 MyPageViewModelMyPageRepository를 직접 주입해 프로필 조회·로그아웃·탈퇴를 모두 수행합니다. core:domain에 각 작업의 UseCase를 두고 ViewModel은 UseCase만 호출하도록 분리해야 계정 정책과 오류 매핑이 ViewModel에 누적되지 않습니다.

As per path instructions: ViewModel의 비즈니스 로직은 UseCase(core:domain) 호출로 위임해야 합니다.

Also applies to: 40-67

🤖 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/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt`
around lines 13 - 15, MyPageViewModel의 MyPageRepository 직접 의존성을 제거하고, 프로필
조회·로그아웃·탈퇴에 대응하는 core:domain UseCase를 주입하세요. ViewModel의 관련 처리 로직은 Repository 호출
대신 각 UseCase를 호출하도록 변경하며, 상태 업데이트와 SideEffect 처리 흐름은 유지하세요.

Source: Path instructions

Comment thread feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt Outdated
Comment on lines +60 to +67
private fun logout() = intent {
myPageRepository.logout()
postSideEffect(MyPageSideEffect.NavigateToLogin)
}

private fun deleteAccount() = intent {
myPageRepository.deleteAccount()
postSideEffect(MyPageSideEffect.NavigateToLanding)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

로그아웃·탈퇴 실패를 UI 오류 흐름으로 전달하세요.

logout()deleteAccount()가 예외를 던지면 성공 SideEffect가 게시되지 않고, 사용자에게 실패 원인이나 재시도 방법도 전달되지 않습니다. 일반 예외를 도메인 오류로 변환한 뒤 ShowError 같은 SideEffect 또는 명시적인 작업 상태로 전달하고, 성공한 경우에만 로그인/랜딩 화면으로 이동해야 합니다.

🤖 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/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt`
around lines 60 - 67, Update MyPageViewModel.logout and deleteAccount to catch
repository exceptions, convert them into the established domain error type, and
publish the UI error flow through the existing ShowError-style side effect or
explicit operation state. Only post NavigateToLogin or NavigateToLanding after
the repository call succeeds, preserving the current success destinations.

- contentDescription에 URL 문자열 수정
- modifier 파라미터 오타 수정
- 무효화된 width(105.dp) 제거
@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요! 시니어 Android 개발자입니다. 마이페이지 기능 구현을 위한 PR을 올려주셔서 감사합니다. 전반적으로 코드 품질이 매우 좋고, 최신 아키텍처 패턴(Orbit MVI, Hilt, Clean Architecture)을 잘 적용하셨습니다. Jetpack Compose UI 구현도 깔끔하고 State Hoisting 원칙도 잘 지켜진 것 같습니다. 몇 가지 개선 사항과 TODO 항목에 대한 코멘트들을 남깁니다.


1. Kotlin 코드 리뷰

  • Coroutine 사용 시 Dispatcher 명시 여부, viewModelScope/lifecycleScope 오남용:
    • MyPageRepositoryImpl에서 delay 함수를 사용하고 있는데, delaysuspend 함수이므로 별도의 Dispatcher 명시 없이도 비동기로 동작합니다. 실제 네트워크 통신 시 Retrofit이 적절한 Dispatcher를 사용하여 백그라운드 스레드에서 요청을 처리하므로 현재로서는 문제가 없습니다. (만약 Repository에서 CPU-intensive한 작업을 수행한다면 withContext(Dispatchers.Default) 등을 고려할 수 있습니다.)
    • MyPageViewModel에서 Orbit의 intent 블록을 사용하고 있으며, 이는 내부적으로 viewModelScope를 사용하므로 적절한 사용입니다.
  • Flow/StateFlow 사용 시 collect 시점 (repeatOnLifecycle 사용 여부):
    • MyPageScreen에서 collectAsState를 사용하고 있습니다. 이는 Compose 컴포저블의 생명주기에 맞춰 상태를 수집하므로 repeatOnLifecycle과 유사한 효과를 내며 적절한 사용입니다. collectSideEffect 또한 Orbit의 Composables 확장 함수를 통해 적절히 사용되었습니다.
  • null 안전성 (!! 사용 지양): !! 연산자를 사용한 곳은 발견되지 않았습니다. 좋습니다.
  • data class에 불필요한 var 사용 여부: MyPageProfileMyPageState 모두 val만 사용하여 불변성을 유지하고 있습니다. 좋은 접근입니다.
  • Hilt DI 시 생성자 주입 원칙 준수 여부:
    • RepositoryModule에서 @Binds를 사용하여 인터페이스(MyPageRepository)와 구현체(MyPageRepositoryImpl)를 바인딩하는 방식이 올바릅니다. 또한, 모듈을 abstract class로 정의한 것도 좋습니다.
    • MyPageRepositoryImplMyPageViewModel 모두 @Inject constructor()를 사용하여 Hilt의 생성자 주입 원칙을 잘 준수하고 있습니다.
  • 하드코딩된 문자열/매직 넘버:
    • 하드코딩된 문자열: 사용자에게 노출되는 모든 문자열은 strings.xml 리소스 파일로 관리해야 합니다. 현재 많은 문자열이 하드코딩되어 있습니다. (예: "뒤로가기", "캐릭터 관리", "로그아웃 하시겠습니까?", "준비 중입니다...", 등)
      • 특히 MyPageRepositoryImplnickname, tier, appVersion은 임시 mock 데이터이므로 현재로서는 괜찮지만, 추후 실제 데이터 연동 시에는 서버 응답을 사용해야 합니다.
      • MyPageViewModelrunCatching 실패 메시지 ("내 프로필 불러오기 실패")도 string resource로 관리해야 합니다.
      • ProfileTicketContent의 "${remainingCount}회 남음"과 같이 숫자와 함께 사용되는 문자열은 복수형(plural) 리소스를 고려하거나 String.format을 사용하여 유연하게 처리하는 것이 좋습니다.
    • 매직 넘버: UI 컴포넌트의 크기, 패딩, 여백 등에 사용된 dpsp 값들 (예: 58.dp, 20.dp, 1.dp, 54.dp, 34.sp 등)이 하드코딩되어 있습니다. 이러한 값들은 디자인 시스템에서 정의된 Dimens 또는 Spacing 객체를 통해 관리하거나, 컴포저블 파일 내에서 private const val로 정의하여 재사용 및 관리 편의성을 높이는 것이 좋습니다.

2. Jetpack Compose 코드 리뷰

  • Composable 함수의 불필요한 recomposition 유발 여부 (remember, key 사용):
    • MyPageScreen에서 mutableStateOf, remember를 사용하여 UI 상태를 관리하고, collectAsState로 ViewModel의 상태를 구독하여 recomposition을 효율적으로 관리하고 있습니다.
    • 각 컴포저블 함수들은 파라미터로 데이터를 직접 받거나 람다를 받으므로, 불필요한 recomposition을 유발하지 않습니다.
    • rememberScrollState()도 적절하게 사용되었습니다.
  • State hoisting 원칙 준수:
    • MyPageScreen이 모든 UI 상태(MyPageState 및 dialog 표시 여부)를 관리하고, 하위 컴포저블에는 데이터와 이벤트를 처리할 람다를 넘겨주는 형태로 State Hoisting 원칙을 잘 준수하고 있습니다.
  • side effect (LaunchedEffect, DisposableEffect) 사용의 적절성:
    • 명시적인 LaunchedEffectDisposableEffect 사용은 없지만, viewModel.collectSideEffect를 통해 Orbit의 postSideEffect가 처리되고 있습니다. 이는 일회성 이벤트(네비게이션, 스낵바 등)를 처리하는 데 적합한 방식이며, Compose의 Side Effect 처리 원칙을 잘 따르고 있습니다.
  • UI 상태를 data class로 표현했는지: MyPageStatedata class로 정의하여 UI 상태를 명확하게 표현하고 있습니다. 좋습니다.
  • 로딩/에러 상태를 Boolean 대신 LoadStatus로 관리하는지: MyPageState에서 LoadStatus enum class를 사용하여 로딩 상태를 관리하고 있습니다. 이는 Boolean 플래그를 여러 개 사용하는 것보다 상태를 명확하게 나타내고 관리하기 용이합니다.
    • 개선 필요: 현재 if (state.loadStatus == LoadStatus.Loading) 블록에 // TODO: 로딩 인디케이터 오버레이 주석이 있습니다. 실제 로딩 중일 때 사용자에게 피드백을 줄 수 있는 로딩 인디케이터 (예: CircularProgressIndicator를 오버레이로 띄우는 등) 구현이 필요합니다.

3. Repository/DataSource 레이어

  • Retrofit 에러 핸들링 (try-catch, Result 래핑):
    • 현재 MyPageRepositoryImpl은 임시 구현체이므로 Retrofit을 사용하고 있지 않습니다. MyPageViewModel에서 runCatching 블록을 통해 Repository 호출의 성공/실패를 처리하고 있으며, 이는 Result 래핑과 유사한 효과를 냅니다. 실제 API 연동 시에는 Repository 레이어에서 네트워크 응답을 Result 타입으로 래핑하여 에러를 도메인 에러로 변환하는 패턴을 적용하는 것이 좋습니다.
  • 네트워크 응답과 도메인 모델 매핑 분리 여부:
    • 현재 Repository는 mock 데이터를 직접 MyPageProfile 도메인 모델로 반환하고 있습니다. 실제 API 연동 시에는 네트워크 응답 DTO(Data Transfer Object)를 별도로 정의하고, 이를 도메인 모델(MyPageProfile)로 매핑하는 변환 로직(Mapper)을 Repository나 DataSource 계층에 두는 것이 좋습니다.
  • 캐싱 전략 (로컬 DB vs 메모리): 현재 캐싱 전략은 구현되지 않았으며, 이 PR의 범위에는 해당되지 않는 것으로 보입니다.

4. ViewModel

  • UI 상태와 비즈니스 로직 분리: MyPageViewModelMyPageState를 통해 UI 상태를 관리하고, MyPageRepository를 통해 비즈니스 로직(프로필 로드, 로그아웃, 계정 삭제)을 수행합니다. 비즈니스 로직 자체는 Repository에 위임하는 좋은 분리 방식입니다.
  • Orbit ContainerHost 패턴 준수 여부: MyPageViewModelContainerHost 인터페이스를 구현하고 container 델리게이트를 사용하여 Orbit MVI 패턴을 잘 준수하고 있습니다.
  • intent 내부에서 상태 변경(reduce)만 수행하는지:
    • loadMyProfile intent는 reduce를 사용하여 상태를 업데이트하고 있습니다.
    • logout, deleteAccount, navigate intent는 postSideEffect를 통해 일회성 이벤트를 발생시킵니다.
    • 일반적으로 intent 내부에서는 reduce로 상태를 변경하거나 postSideEffect로 이벤트를 발생시키는 것 외에 복잡한 비즈니스 로직이나 직접적인 UI 조작은 하지 않는 것이 좋습니다. 현재 구현은 이러한 원칙을 잘 지키고 있습니다.
  • postSideEffect 남용 여부: 네비게이션, 다이얼로그 표시와 같은 일회성 이벤트를 postSideEffect로 처리하고 있으며, 이는 postSideEffect의 올바른 사용 사례입니다. 남용하고 있지 않습니다.
  • blocking 작업을 intent 내부에서 직접 수행하지 않는지: myPageRepository의 메서드들은 suspend 함수이므로 intent 블록 내에서 호출해도 메인 스레드를 블로킹하지 않습니다. 좋습니다.

5. FCM/SSE/실시간 통신 관련 코드

  • 해당 PR에서는 FCM/SSE/실시간 통신 관련 코드가 포함되어 있지 않습니다.

기타 개선 사항

  • SettingsMenuContent의 아이콘: 현재 아이콘으로 이모지("\uD83D\uDC64", "❓", etc.)를 직접 사용하고 있습니다. 이모지는 디바이스 및 OS 버전에 따라 렌더링 방식이 다를 수 있어 일관된 디자인을 보장하기 어렵습니다. ImageVector 또는 painterResource를 통해 벡터 에셋을 사용하거나, Font Awesome과 같은 아이콘 폰트를 사용하는 것을 권장합니다.
  • ComingSoonScreen의 로고: TODO: 실제 앱 로고 아이콘으로 교체 주석이 있습니다. 현재 ic_mypage_ticket을 임시로 사용 중인데, 추후 실제 앱 로고 이미지 리소스로 교체해야 합니다.
  • MyPageScreen의 배경색 설정:
    Column(
        modifier = modifier
        .fillMaxSize()
        .verticalScroll(rememberScrollState())
        .background(CallTheme.colors.background), // 여기에 배경색이 지정되어 있음
    ) {
        Box{
            // 배경 핑크 박스
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(200.dp)
                    .background(CallTheme.colors.mainVariant3),
            )
            // ...
        }
    }
    Column 자체에 CallTheme.colors.background (회색 추정)가 적용되어 있고, 그 위에 BoxmainVariant3 (핑크색 추정) 박스를 올리는 방식입니다. 의도하신 디자인이라면 문제가 없지만, 하단 부분이 CallTheme.colors.background로 채워지게 됩니다. 디자인 의도에 맞는지 한 번 더 확인해보는 것이 좋습니다. (현재는 verticalScroll 때문에 아래까지 배경이 채워지므로 문제 없어 보입니다.)
  • Preview Composables: 각 컴포넌트와 화면에 프리뷰 함수를 잘 작성해두셔서 개발 및 확인에 용이합니다. 특히 로딩 상태와 다이얼로그 상태 프리뷰를 추가한 점은 훌륭합니다.

총평

전반적으로 매우 높은 수준의 코드이며, 마이페이지 기능 구현에 필요한 핵심 요소들을 잘 갖추고 있습니다. 특히 Orbit MVI 패턴과 Clean Architecture 구조를 효과적으로 적용한 점이 인상 깊습니다. 위에서 언급한 하드코딩된 문자열/매직 넘버들을 리소스 및 디자인 시스템 상수로 분리하고, TODO 항목들을 보완하면 더욱 완성도 높은 코드가 될 것입니다.

수고하셨습니다! 👍

@JiwonLee42 JiwonLee42 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.

수고했어용 안드짱짱걸

// 프로필 이미지
AsyncImage(
model = profileImageUrl,
contentDescription = if (profileImageUrl.isBlank()) null else "프로필 이미지",

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.

description에 굳이 url이 있어야할까요..?! 프로필 이미지 정도로 해도 될 것 같아요

- contentDescription을 프로필 이미지로 통일
@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요! PR 리뷰 요청 감사합니다. 시니어 Android 개발자의 관점에서 PR diff를 꼼꼼히 살펴보겠습니다.

전반적으로 아키텍처(Orbit MVI, Hilt DI, Repository 패턴)와 Jetpack Compose 활용(State Hoisting, UI 상태 관리)이 잘 적용되어 있으며, 코드도 깔끔하게 작성되어 있습니다. 특히 MyPageViewModel에서 LoadStatus를 통해 로딩/에러 상태를 관리하고, runCatching을 사용하여 예외를 처리하는 방식은 훌륭합니다. Orbit MVI 패턴도 잘 준수하고 있습니다.

다만, 몇 가지 개선할 점들이 있어 피드백을 드립니다. 주로 하드코딩된 문자열과 몇몇 Compose 관행에 대한 내용입니다.


1. Kotlin 코드 리뷰

  • Coroutine 사용 시 Dispatcher 명시 여부, viewModelScope/lifecycleScope 오남용

    • MyPageRepositoryImpl에서 delay를 사용하는 것은 임시 구현체임을 감안하면 허용 가능합니다. 실제 네트워크 통신 시에는 Retrofit이 IO Dispatcher를 사용하여 백그라운드에서 동작하므로, ViewModel에서는 특별히 Dispatcher를 명시할 필요는 없습니다.
    • MyPageViewModel은 Orbit container를 사용하여 viewModelScope를 내부적으로 관리하므로, viewModelScopelifecycleScope를 직접적으로 오남용한 흔적은 없습니다. intent 블록 내에서 suspend 함수를 호출하는 것도 적절합니다.
  • Flow/StateFlow 사용 시 collect 시점 (repeatOnLifecycle 사용 여부)

    • MyPageScreen에서 Orbit의 collectAsStatecollectSideEffect를 사용하고 있습니다. collectAsState는 Compose 내에서 사용될 때 자동으로 repeatOnLifecycle과 유사하게 컴포저블의 생명주기를 따르며 수집을 시작/중지합니다. collectSideEffect는 단발성 이벤트를 처리하는 데 적절하게 사용되었습니다. 모두 올바른 사용입니다.
  • null 안전성 (!! 사용 지양)

    • 코드에서 !! (non-null assertion operator) 사용은 발견되지 않았습니다. 훌륭합니다.
  • data class에 불필요한 var 사용 여부

    • MyPageProfileMyPageState 모두 모든 필드를 val로 선언하여 불변성을 유지하고 있습니다. 데이터 클래스 사용 원칙을 잘 준수하고 있습니다.
  • Hilt DI 시 생성자 주입 원칙 준수 여부

    • RepositoryModule에서 @Binds를 사용하여 인터페이스-구현체 바인딩을 잘 처리했습니다.
    • MyPageRepositoryImplMyPageViewModel 모두 @Inject constructor를 통해 생성자 주입 원칙을 잘 준수하고 있습니다.
  • 하드코딩된 문자열/매직 넘버

    • (Critical) 사용자에게 표시되는 대부분의 문자열이 하드코딩되어 있습니다. 이는 다국어 지원 및 유지보수 측면에서 문제가 될 수 있습니다. 모든 사용자 대상 문자열은 strings.xml 리소스 파일로 이동시키고 stringResource()를 통해 참조하도록 변경해야 합니다.
      • MyPageRepositoryImpl: Mock 데이터("김수현", "Basic", "1.0.0")는 임시이므로 괜찮지만, 실제 연동 시에는 변경되어야 합니다. delay(500), delay(300)과 같은 매직 넘버는 테스트 목적이라면 괜찮지만, 실제 코드에서는 명확한 상수로 정의하거나 제거해야 합니다.
      • CommonTopAppBar: "뒤로가기"
      • ProfileSummaryContent: "프로필 이미지", "이동"
      • ProfileTicketContent: "통화 이용권", "회 남음", "충전", "구매", "내역"
      • SettingsSectionCard: "고객 지원" (title), 프리뷰에서 사용된 "v1.0.0" (버전 정보는 state.appVersion으로 받아오는 것은 좋습니다만, 프리뷰도 stringResource 사용을 권장합니다.)
      • SettingsMenuContent: "캐릭터 관리", "자주 하는 질문", "문의하기", "약관", "버전 정보", "로그아웃", "계정 삭제", "바로가기". 특히 이모지("❓", "✉️", "📄", "ℹ️", "\uD83D\uDEAA", "⛔")를 직접 문자열로 사용하는 방식은 시스템 폰트나 테마에 따라 다르게 보일 수 있으므로, 가능하다면 ImageVectorpainterResource를 이용한 아이콘으로 대체하는 것을 고려해볼 수 있습니다. 텍스트로 사용한다면 이 역시 stringResource로 관리해야 합니다.
      • ComingSoonScreen: "준비 중입니다..."
      • MyPageScreen (다이얼로그): "로그아웃", "로그아웃 하시겠습니까?", "확인", "취소", "잠시만요 🖐️", "탈퇴 시 계정 및 이용 기록은 모두 삭제되며,\n삭제된 데이터는 복구가 불가능합니다.\n\n탈퇴를 진행할까요?", "탈퇴할게요". 다이얼로그의 모든 텍스트는 stringResource로 관리해야 합니다.
      • MyPageViewModel: 에러 메시지 "내 프로필 불러오기 실패"

2. Jetpack Compose 코드

  • Composable 함수의 불필요한 recomposition 유발 여부 (remember, key 사용)

    • CommonTopAppBaronBackClick과 같이 컴포저블에 람다를 전달할 때, 람다가 매번 새로 생성되면 불필요한 리컴포지션을 유발할 수 있습니다. 상위 컴포저블에서 remember로 람다를 감싸거나(e.g., onBackClick = remember { { navigator.popBackStack() } }) stable한 함수 참조를 전달하는 것이 좋습니다. 이 PR의 경우, 간단한 내비게이션 람다이므로 큰 성능 저하는 아닐 수 있으나, 좋은 습관입니다.
    • SettingsSectionCard에서 items: List<@Composable () -> Unit>을 사용하는 패턴은 유연하지만, items 리스트가 자주 변경되거나 내부의 @Composable 람다가 불안정할 경우 리컴포지션 문제가 발생할 수 있습니다. 현재 마이페이지 메뉴는 비교적 고정적이므로 괜찮지만, 동적인 리스트를 다룰 때는 items: List<YourMenuItemData>와 같이 데이터 리스트를 넘기고 내부에서 SettingsMenuContent(data)를 호출하는 것이 더 안정적입니다.
  • State hoisting 원칙 준수

    • MyPageScreenContentstateonIntent를 받는 등, 화면과 하위 컴포넌트들에서 상태 호이스팅 원칙을 잘 준수하고 있습니다. 이는 UI와 비즈니스 로직을 분리하는 데 매우 중요한 요소입니다.
  • side effect (LaunchedEffect, DisposableEffect) 사용의 적절성

    • Orbit의 collectSideEffect를 사용하여 내비게이션 및 다이얼로그 표시와 같은 단발성 이벤트를 잘 처리하고 있습니다. LaunchedEffectDisposableEffect의 직접적인 사용은 없으며, 현재 요구사항에는 collectSideEffect로 충분해 보입니다.
  • UI 상태를 data class로 표현했는지

    • MyPageState 데이터 클래스를 사용하여 UI 상태를 명확하게 표현했습니다. 훌륭합니다.
  • 로딩/에러 상태를 Boolean 대신 LoadStatus로 관리하는지

    • MyPageState에서 LoadStatus (Idle, Loading, Error)를 사용하여 로딩 상태를 효과적으로 관리하고 있습니다. MyPageScreen에서 LoadStatus.Loading일 때 로딩 인디케이터(TODO 주석)를 표시할 예정인 점도 좋습니다.

3. Repository/DataSource 레이어

  • Retrofit 에러 핸들링 (try-catch, Result 래핑)

    • 현재 MyPageRepositoryImpl은 임시 구현체(mock)이므로 Retrofit을 사용하고 있지 않습니다.
    • MyPageViewModel에서 myPageRepository.getMyProfile() 호출 시 runCatching을 사용하여 에러를 핸들링하는 방식은 올바릅니다. 특히 CancellationException을 다시 던지는 것은 코루틴 취소 메커니즘을 존중하는 좋은 사례입니다. 실제 Repository 구현 시에도 이와 유사한 방식으로 Result 또는 커스텀 Resource 래퍼를 사용하여 에러를 처리하는 것을 권장합니다.
  • 네트워크 응답과 도메인 모델 매핑 분리 여부

    • 임시 구현체라 직접적인 DTO-도메인 모델 매핑은 보이지 않습니다. 하지만 domain 모듈에 MyPageProfile 도메인 모델이 있고, data 모듈의 MyPageRepositoryImpl이 이를 반환하는 구조이므로, 추후 실제 API 연동 시 RemoteDataSource에서 DTO를 받고 Repository에서 도메인 모델로 매핑하는 구조를 쉽게 적용할 수 있을 것으로 보입니다.
  • 캐싱 전략 (로컬 DB vs 메모리)

    • 현재 캐싱 전략은 구현되어 있지 않습니다. 마이페이지 데이터 특성상 빈번하게 변경될 가능성이 낮다면, 초기 로딩 시 한 번 가져와서 메모리에 유지하거나 (ViewModel State), 필요에 따라 Room DB와 같은 로컬 캐싱을 고려해볼 수 있습니다. 현재는 필수 사항이 아니므로 괜찮습니다.

4. ViewModel

  • UI 상태와 비즈니스 로직 분리

    • MyPageViewModelMyPageState를 관리하고 MyPageIntent에 따라 비즈니스 로직(저장소 호출)을 수행하며 상태를 업데이트합니다. UI(MyPageScreen)는 이 상태를 관찰하고 MyPageIntent를 발생시키는 역할만 합니다. 이처럼 UI와 비즈니스 로직이 명확하게 분리되어 있습니다.
  • Orbit ContainerHost 패턴 준수 여부

    • MyPageViewModelContainerHost 인터페이스를 구현하고 container, intent, reduce, postSideEffect를 사용하여 Orbit MVI 패턴을 완벽하게 준수하고 있습니다.
  • intent 내부에서 상태 변경(reduce)만 수행하는지

    • intent 블록 내에서 reduce는 상태 변경에만 사용되고, postSideEffect는 단발성 이벤트(내비게이션, 다이얼로그 표시)에 사용됩니다. 이 원칙을 잘 지키고 있습니다.
  • postSideEffect 남용 여부

    • postSideEffect는 내비게이션, 다이얼로그 표시와 같은 한 번만 발생해야 하는 UI 이벤트를 처리하는 데 적절하게 사용되었습니다. 남용되지 않았습니다.
  • blocking 작업을 intent 내부에서 직접 수행하지 않는지

    • myPageRepository의 suspend 함수들을 intent 블록 내에서 호출하고 있습니다. Orbit은 intent 블록 내부의 suspend 함수 호출을 적절한 코루틴 컨텍스트에서 처리하므로, 메인 스레드를 블로킹하지 않습니다.

5. FCM/SSE/실시간 통신 관련 코드

  • 해당 PR에는 FCM/SSE/실시간 통신 관련 코드가 포함되어 있지 않습니다.

총평 및 최종 제안

전반적으로 매우 좋은 코드이며, 아키텍처 및 Compose 설계 원칙을 잘 따르고 있습니다. 주요 개선 사항은 하드코딩된 문자열을 stringResource로 전환하는 것입니다. 이 부분만 해결된다면 완성도 높은 PR이 될 것입니다.

Action Items:

  1. 모든 사용자 대상 문자열 (CommonTopAppBar, ProfileSummaryContent, ProfileTicketContent, SettingsSectionCard, SettingsMenuContent, ComingSoonScreen, MyPageScreen의 다이얼로그 텍스트, MyPageViewModel의 에러 메시지)을 strings.xml로 이동하고 stringResource()로 참조하도록 변경합니다.
  2. SettingsMenuContent에서 이모지를 텍스트로 사용하고 있는데, 이들도 stringResource로 관리하거나, 디자인 시스템 내에 포함된 아이콘 리소스로 대체하는 것을 고려해 보세요.
  3. MyPageScreenif (state.loadStatus == LoadStatus.Loading) TODO 주석 부분에 적절한 로딩 인디케이터(예: CircularProgressIndicator가 포함된 Box 오버레이)를 추가하여 사용자 경험을 향상시킵니다.
  4. (선택 사항) CommonTopAppBarSettingsSectionCard에 람다를 전달하는 방식에 대한 안정성을 추가로 검토해볼 수 있습니다. (e.g. remember 사용)

위 피드백이 PR 개선에 도움이 되기를 바랍니다. 궁금한 점이 있다면 언제든지 다시 문의해주세요!

@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.

너무 최고야 난그냥 approve줄게 레포지토리 메서드 반환값만 고쳐줭

Comment on lines +11 to +20
override suspend fun getMyProfile(): MyPageProfile {
delay(500)
return MyPageProfile(
profileImageUrl = "",
nickname = "김수현",
tier = "Basic",
remainingTicketCount = 18,
appVersion = "1.0.0",
)
}

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.

이거 반환타입 Result로 하는거 추천~~!!

Comment on lines +3 to +9
data class MyPageProfile(
val profileImageUrl: String,
val nickname: String,
val tier: String,
val remainingTicketCount: Int,
val appVersion: String,
)

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들에 = "" 디폴트값 빈 문자열 처리 어때?

fun CommonTopAppBar(
modifier: Modifier = Modifier,
title: String? = null,
onBackClick: (() -> Unit),

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.

이거 맨 밖에 괄호는 빼는 게 더 예뻐보일듯?

}

// 상단 타이틀
if (title != null) {

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.

title을 디폴트를 "" 으로 바꾸고 isEmpty 로 처리하는게 어때~?

Comment on lines +74 to +95
// 버튼 컴포넌트
@Composable
private fun TicketActionButton(
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.height(46.dp)
.clip(RoundedCornerShape(8.dp))
.background(CallTheme.colors.gray100)
.clickable(onClick = onClick),
contentAlignment = Alignment.Center,
) {
Text(
text = label,
style = CallTheme.typography.bodyMediumMedium,
color = CallTheme.colors.black,
)
}
}

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 SettingsSectionCard(
modifier: Modifier = Modifier,
title: String? = null,

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 +32 to +37
trailing: @Composable () -> Unit = {
Icon(
painter = painterResource(id = R.drawable.ic_mypage_arrow_right),
contentDescription = "바로가기",
)
},

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.

너무 기발한 발상이야 최고최고 안드 천재 개발자 최고!!!!!!!!!!!!!! 너무 탐난다~~~~~ 너 내 동료가 되라

import kr.co.call.impl.component.CommonTopAppBar

@Composable
fun ComingSoonScreen(

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 +32 to +35
Dialog(
onDismissRequest = onBackClick,
properties = DialogProperties(usePlatformDefaultWidth = false),
) {

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.

와 넌 진짜... 어떻게 이런 발상을 해? 다이알로그를 전체 화면처럼 띄워지게 하다니.... 역시 너야....

- 반환 타입을 Result<MyPageProfile> 변경
- String 필드 기본값 "" 변경 및 추가
- TicketActionButton 파일 분리
@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요! GitHub Pull Request 리뷰 요청해주셔서 감사합니다. 시니어 Android 개발자로서 마이페이지 기능 구현 PR을 꼼꼼히 리뷰해 드리겠습니다.

전반적으로 Jetpack Compose와 Orbit MVI 패턴을 잘 적용하여 구조화된 코드를 작성해 주셨습니다. 특히 ViewModel에서 UI 상태와 SideEffect를 명확히 분리하고, Compose UI에서 상태 호이스팅 원칙을 잘 지킨 점이 인상 깊습니다. LoadStatus를 사용하여 로딩/에러 상태를 관리하는 점과 다양한 Preview를 제공하여 UI 확인을 용이하게 한 점도 매우 좋습니다.

아래에 카테고리별로 상세한 피드백을 드립니다.


🔍 코드 리뷰

1. Kotlin & General

  • Hilt DI 시 생성자 주입 원칙 준수 여부 (개선됨)
    • RepositoryModule.kt: abstract class로 변경하고 @Binds를 사용하여 MyPageRepository 인터페이스에 MyPageRepositoryImpl 구현체를 바인딩한 것은 Hilt의 생성자 주입 원칙을 정확히 따른 좋은 변경입니다.
  • null 안전성 (!! 사용 지양)
    • 코드에서 !! (non-null assertion operator) 사용은 발견되지 않았습니다. 좋습니다.
  • data class에 불필요한 var 사용 여부
    • MyPageProfile.kt, MyPageState.kt 모두 val을 사용하여 불변성을 유지하고 있습니다. 좋습니다.
  • 하드코딩된 문자열/매직 넘버 (개선 필요)
    • 문자열: 대부분의 사용자에게 보여지는 텍스트 ("뒤로가기", "프로필 이미지", "통화 이용권", "충전", "구매", "내역", "캐릭터 관리", "자주 하는 질문", "문의하기", "약관", "버전 정보", "로그아웃", "계정 삭제", 다이얼로그 메시지 등)가 코드 내에 하드코딩되어 있습니다. 이는 다국어 지원 및 유지보수 측면에서 문제가 될 수 있으므로, string.xml 리소스 파일로 분리하여 관리하는 것이 좋습니다.
    • 이모지 아이콘: SettingsMenuContent에서 사용된 이모지 문자열 ("\uD83D\uDC64", "❓", 등)도 하드코딩되어 있습니다. 이는 폰트에 따라 렌더링이 다르게 보이거나 아예 표시되지 않을 수 있으므로, 적절한 아이콘 리소스 (VectorDrawable)로 대체하는 것을 고려해 주세요.
    • 매직 넘버 (Dimensions): 58.dp, 20.dp, 1.dp, 25.dp 등 레이아웃의 dp 값들이 컴포넌트마다 직접 명시되어 있습니다. CallTheme.dimens와 같은 디자인 시스템의 표준화된 dimension 값이나 최소한 Dimens.kt와 같은 별도의 파일로 상수화하여 관리하는 것이 유지보수성을 높일 수 있습니다.
    • ComingSoonScreen.kt: fontSize = 34.sp 또한 매직 넘버입니다. CallTheme.typography에서 정의된 스타일을 사용하거나, 필요한 경우 Dimens.kt에 정의된 sp 값을 활용하는 것이 좋습니다.

2. Jetpack Compose

  • Composable 함수의 불필요한 recomposition 유발 여부 (remember, key 사용)
    • 대부분의 Composable 함수들은 단순한 UI를 렌더링하고 상태를 직접 소유하지 않아 불필요한 리컴포지션 발생 가능성이 낮습니다. rememberkey 사용이 필요한 복잡한 로직은 보이지 않습니다.
  • State hoisting 원칙 준수
    • MyPageScreenContentMyPageStateonIntent 콜백을 파라미터로 받아 하위 컴포넌트들에게 필요한 데이터를 전달하는 방식으로 구현되어 있습니다. 이는 Compose의 상태 호이스팅 원칙을 잘 따르고 있으며, UI 컴포넌트의 재사용성과 테스트 용이성을 높여줍니다. 좋습니다.
  • side effect (LaunchedEffect, DisposableEffect) 사용의 적절성
    • Compose UI 컴포넌트 내에서 LaunchedEffectDisposableEffect 사용은 보이지 않습니다. SideEffect는 ViewModel에서 postSideEffect로 관리되고, Screen 레벨에서 collectSideEffect를 통해 처리되므로 적절한 분리가 이루어졌습니다.
  • UI 상태를 data class로 표현했는지
    • MyPageStatedata class로 정의하여 UI 상태를 명확하게 표현했습니다. 좋습니다.
  • 로딩/에러 상태를 Boolean 대신 LoadStatus로 관리하는지
    • MyPageStateloadStatus: LoadStatus = LoadStatus.Idle을 추가하여 로딩/에러/아이들 상태를 enum class로 관리하고 있습니다. 이는 Boolean 플래그 조합보다 훨씬 명확하고 확장성이 뛰어납니다. 좋습니다.
  • ComingSoonScreen 구현 방식 (고려 사항)
    • 현재 ComingSoonScreenDialog로 구현되어 있습니다. Dialog는 주로 사용자의 짧은 상호작용이 필요한 모달 창에 적합합니다. 만약 "준비 중입니다" 화면이 전체 화면을 차지하는 독립적인 페이지라면, Dialog 대신 일반적인 Composable 화면으로 구현하고 네비게이션을 통해 이동하는 방식이 더 적절할 수 있습니다. 현재처럼 usePlatformDefaultWidth = false를 사용하여 전체 화면처럼 보이게 할 수도 있지만, 의미적으로는 Screen이 더 맞을 수 있습니다. 의도에 따라 판단이 필요합니다.

3. Repository/DataSource 레이어

  • Retrofit 에러 핸들링 (try-catch, Result 래핑)
    • MyPageRepositoryImpl.kt는 현재 API 연동 전 임시 구현체(TODO)이므로 실제 네트워크 로직이 없습니다.
    • getMyProfile()에서 runCatching 블록을 사용하여 Result 타입으로 래핑하는 것은 좋은 접근 방식입니다.
    • 추가 제안: 실제 API 연동 시에는 MyPageRepositoryImpl 내부에서 Dispatchers.IO를 명시적으로 사용하여 네트워크 호출을 백그라운드 스레드에서 처리하도록 해야 합니다. (예: withContext(Dispatchers.IO) { ... })
  • 네트워크 응답과 도메인 모델 매핑 분리 여부
    • 현재 임시 구현체이므로, 네트워크 응답 (DTO) 없이 바로 도메인 모델(MyPageProfile)을 반환하고 있습니다. 실제 API 연동 시에는 DTO를 정의하고, Repository 레이어에서 DTO를 도메인 모델로 매핑하는 로직을 추가하여 계층 간 분리를 명확히 해야 합니다.
  • 캐싱 전략 (로컬 DB vs 메모리)
    • 현재 캐싱 전략은 구현되어 있지 않습니다. 추후 필요에 따라 로컬 DB 또는 메모리 캐싱 전략을 고려해야 합니다.

4. ViewModel

  • UI 상태와 비즈니스 로직 분리
    • MyPageViewModelMyPageState를 통해 UI 상태를 관리하고, MyPageIntent를 통해 사용자 액션을 받아 myPageRepository의 비즈니스 로직을 호출하는 등 책임이 명확하게 분리되어 있습니다. 좋습니다.
  • Orbit ContainerHost 패턴 준수 여부
    • MyPageViewModelContainerHost<MyPageState, MyPageSideEffect>를 구현하고 있으며, intent 블록 내에서 reducepostSideEffect를 사용하여 상태 변경 및 단발성 이벤트를 처리합니다. Orbit MVI 패턴을 매우 잘 준수하고 있습니다.
  • intent 내부에서 상태 변경(reduce)만 수행하는지
    • reduce 함수는 상태 변경에만 사용되고, SideEffect는 postSideEffect를 통해 분리되어 있습니다. 좋습니다.
  • postSideEffect 남용 여부
    • 네비게이션, 다이얼로그 표시 등 단발성 이벤트를 MyPageSideEffect로 정의하고 postSideEffect를 통해 처리하고 있습니다. 이는 postSideEffect의 올바른 사용 예시입니다. 남용으로 보이지 않습니다.
  • blocking 작업을 intent 내부에서 직접 수행하지 않는지
    • myPageRepositorysuspend 함수들을 호출하고 있으며, 이 함수들 자체에서 I/O 작업을 처리할 것으로 예상됩니다 (현재는 delay로 시뮬레이션). ViewModel 내부에서 별도의 blocking 작업은 수행하지 않습니다. 좋습니다.

5. FCM/SSE/실시간 통신 관련 코드

  • 해당 PR에는 FCM/SSE/실시간 통신 관련 코드가 포함되어 있지 않습니다.

💡 종합 피드백 및 제안 사항

  • 가장 중요한 개선 사항은 하드코딩된 문자열과 매직 넘버(특히 dp 값들)를 리소스 파일로 분리하고 디자인 시스템에 통합하는 것입니다. 이 작업은 장기적인 유지보수성과 다국어 지원에 필수적입니다.
  • ComingSoonScreenDialog 사용에 대한 재고는 필요에 따라 결정하시면 됩니다.
  • MyPageRepositoryImpl: 현재는 임시 구현체이지만, 실제 API 연동 시 Dispatchers.IO 사용 및 DTO-도메인 모델 매핑 로직을 추가하는 것을 잊지 마시기 바랍니다.
  • MyPageScreen.kt: if (state.loadStatus == LoadStatus.Loading) 부분의 TODO: 로딩 인디케이터 오버레이를 구현하여 사용자에게 로딩 상태를 명확히 보여주는 것이 좋습니다. (예: CircularProgressIndicatorBox를 이용한 오버레이)
  • Preview 활용: 다양한 상태(MyPageScreenLoadingPreview, MyPageScreenLogoutDialogPreview, MyPageScreenDeleteAccountDialogPreview)에 대한 Preview를 제공한 점은 정말 훌륭합니다. 이는 개발 및 리뷰 과정에서 UI 상태를 빠르게 확인하고 오류를 줄이는 데 큰 도움이 됩니다.

전반적으로 매우 깔끔하고 좋은 코드입니다. 제안 드린 사항들을 반영하시면 더 견고하고 유지보수하기 좋은 마이페이지 기능이 될 것입니다. 수고 많으셨습니다!

@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: 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
`@feature/mypage/impl/src/main/java/kr/co/call/impl/component/TicketActionButton.kt`:
- Around line 30-34: TicketActionButton의 고정 너비 설정을 제거하여 호출부의
Modifier.weight(1f)가 버튼 너비를 제어하도록 수정하세요. ProfileTicketContent의 weight(1f) 호출부는
TicketActionButton 수정으로 정상 동작하므로 변경하지 마세요.
🪄 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: 6c03725e-bac2-45d8-b37a-e5e5ef22201a

📥 Commits

Reviewing files that changed from the base of the PR and between 4374336 and d97d348.

📒 Files selected for processing (8)
  • core/data/src/main/java/kr/co/call/data/repositoryImpl/MyPageRepositoryImpl.kt
  • core/domain/src/main/java/kr/co/call/domain/model/mypage/MyPageProfile.kt
  • core/domain/src/main/java/kr/co/call/domain/repository/MyPageRepository.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/CommonTopAppBar.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileTicketContent.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/SettingSectionCard.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/TicketActionButton.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • core/domain/src/main/java/kr/co/call/domain/repository/MyPageRepository.kt
  • core/domain/src/main/java/kr/co/call/domain/model/mypage/MyPageProfile.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/SettingSectionCard.kt
  • feature/mypage/impl/src/main/java/kr/co/call/impl/viewmodel/MyPageViewModel.kt

Comment on lines +30 to +34
.height(46.dp)
.clip(RoundedCornerShape(8.dp))
.width(105.dp)
.background(CallTheme.colors.gray100)
.clickable(onClick = onClick),

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

TicketActionButtonwidth(105.dp)가 호출부의 Modifier.weight(1f)를 무효화합니다. 두 파일 모두 동일한 근본 원인에서 비롯됩니다: TicketActionButton 내부의 고정 너비가 ProfileTicketContent에서 전달한 weight(1f) 균등 분배를 덮어씁니다.

  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/TicketActionButton.kt#L30-L34: .width(105.dp) 제거하여 호출부의 weight(1f)가 너비를 제어하도록 수정.
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileTicketContent.kt#L62-L66: TicketActionButton 수정 후 weight(1f)가 정상 작동하므로 호출부는 변경 불필요.
📍 Affects 2 files
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/TicketActionButton.kt#L30-L34 (this comment)
  • feature/mypage/impl/src/main/java/kr/co/call/impl/component/ProfileTicketContent.kt#L62-L66
🤖 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/mypage/impl/src/main/java/kr/co/call/impl/component/TicketActionButton.kt`
around lines 30 - 34, TicketActionButton의 고정 너비 설정을 제거하여 호출부의
Modifier.weight(1f)가 버튼 너비를 제어하도록 수정하세요. ProfileTicketContent의 weight(1f) 호출부는
TicketActionButton 수정으로 정상 동작하므로 변경하지 마세요.

@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.

굿굿

@WAcAW9
WAcAW9 merged commit 0982a3f into develop Jul 17, 2026
2 checks passed
@WAcAW9 WAcAW9 self-assigned this Jul 20, 2026
@WAcAW9
WAcAW9 deleted the feature/11-mypage-ui branch July 20, 2026 06:54
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] 마이페이지 UI 구현 - 대표 화면

3 participants