Skip to content

Conversation

@nasohee
Copy link
Collaborator

@nasohee nasohee commented Aug 14, 2025

Related issue 🛠

Fixes #71

Work Description 📝

작업 내용

  • 닉네임 변경 팝업
  • 닉네임 변경 시 오류 처리
  • 프로필 이미지 변경
  • 알람 설정 토글 변경
  • 문의하기 MyInquireFragment 페이지
  • 버전 정보에 따른 상태 변경
  • 로그아웃 팝업
  • 회원탈퇴 팝업

Screenshot 📸

2025-08-14.14.22.52-2.mov

Uncompleted Tasks 😅

  • 알림설정 권한

To Reviewers 📢

Summary by CodeRabbit

  • 신규 기능

    • 프로필 이미지 변경 지원: 갤러리 선택 또는 기본 이미지 선택.
    • 닉네임 변경 다이얼로그 추가: 형식 검증·중복확인 후 적용.
    • 알림 설정 토글 추가.
    • 문의하기 화면 추가: 카테고리 선택, 제목/내용 입력, 유효성 검증 및 안내 토스트.
    • 버전 정보 표시 및 업데이트 이동(마켓 미설치 시 웹으로 대체).
  • UI/UX

    • 마이 화면 레이아웃 전면 개편: 프로필 헤더, 메뉴, 알림/버전 섹션 추가.
    • 프로필/닉네임/로그아웃/탈퇴 다이얼로그 디자인 개선.
    • 칩/버튼/툴팁 등 시각 리소스 추가 및 아이콘 색상 조정.
  • 내비게이션

    • 마이 화면에서 문의하기 화면으로 이동 경로 추가.

@coderabbitai
Copy link

coderabbitai bot commented Aug 14, 2025

Walkthrough

마이페이지 전반 UI/플로우가 개편되었습니다. 닉네임/프로필변경이 다이얼로그 기반으로 전환되고, 갤러리 이미지 선택 플로우가 추가되었습니다. 문의하기 화면과 네비게이션이 신설되고, 버전 정보/업데이트 노출, 알림 토글 상태 관리가 추가되었습니다. 로그아웃/회원탈퇴 팝업 레이아웃이 교체되었습니다.

Changes

Cohort / File(s) Summary
My 화면 로직/플로우
app/src/main/java/.../my/MyFragment.kt, app/src/main/java/.../my/MyViewModel.kt
MyFragment: 프로필 이미지 다이얼로그/갤러리 픽커, 닉네임 다이얼로그, 알림 토글, 버전 표시/업데이트 이동, 뷰모델 수집 로직 추가. ViewModel: 선택 이미지/URI/알림 StateFlow 추가, 토글/업데이트 메서드 추가, 생성자에 DeleteReportUseCase 추가.
신규 화면/다이얼로그
app/src/main/java/.../my/MyInquireFragment.kt, app/src/main/java/.../my/dialog/MyNicknameDialog.kt, app/src/main/java/.../my/dialog/MyProfileImageDialog.kt
문의하기 Fragment 추가(입력 검증/커스텀 토스트). 닉네임 변경 다이얼로그(형식/중복 체크, 팝업). 프로필 이미지 다이얼로그(갤러리/프리셋 선택, 콜백).
네비게이션
app/src/main/res/navigation/nav_graph.xml
fragment_my → fragment_my_inquire 액션 및 목적지 추가.
레이아웃: My/다이얼로그/문의하기
app/src/main/res/layout/fragment_my.xml, .../fragment_my_inquire.xml, .../dialog_my_nickname_edit.xml, .../dialog_my_profile_image.xml, .../dialog_logout.xml, .../dialog_my_withdrawal.xml, .../item_my_nickname_error.xml, .../item_toast_inquire.xml
My 레이아웃 대규모 개편(프로필 이미지 편집 오버레이, 메뉴, 버전/업데이트/알림 UI). 문의하기 화면 추가. 닉네임/프로필 변경 다이얼로그 추가. 로그아웃/탈퇴 팝업 전면 개편. 에러 툴팁/토스트 레이아웃 추가.
드로어블/색상 리소스
app/src/main/res/drawable/* (bg_btn_gray_30.xml, bg_btn_main_30.xml, bg_button_logout_*, bg_dialog_my_nickname_edit.xml, bg_my_dialog.xml, bg_my_inquire_chip.xml, bg_my_profile_round.xml, bg_nickname_edittext_*, bg_toast_inquire_dark.xml, ic_dialog_close.xml, ic_message_tail_up.xml, ic_my_edit_pen.xml), app/src/main/res/color/chip_inquire_color.xml
버튼/칩/다이얼로그/입력 배경 및 아이콘 리소스 추가/수정. 칩 상태 컬러 selector 추가. 편집 펜 아이콘 색상 리소스화.
기타
.idea/misc.xml, app/src/main/res/layout/fragment_search_all.xml
IDE 헤더 제거. search_all 레이아웃에 빈 ConstraintLayout 추가.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MyFragment
  participant MyProfileImageDialog
  participant GalleryPicker
  participant MyViewModel

  User->>MyFragment: 프로필 이미지 영역 탭
  MyFragment->>MyProfileImageDialog: show()
  User->>MyProfileImageDialog: 갤러리 아이콘 탭
  MyProfileImageDialog->>MyFragment: launchGallery()
  MyFragment->>GalleryPicker: PickVisualMedia.launch()
  GalleryPicker-->>MyFragment: Uri?
  alt Uri 존재
    MyFragment->>MyProfileImageDialog: setGalleryImage(Uri)
    User->>MyProfileImageDialog: 변경하기
    MyProfileImageDialog->>MyViewModel: updateProfileImageFromGallery(Uri)
  else 미선택
    MyFragment->>User: Toast("이미지를 선택해주세요")
  end
  MyViewModel-->>MyFragment: selectedProfileImageUri 업데이트
  MyFragment->>MyFragment: iv_my_illust 업데이트
Loading
sequenceDiagram
  participant User
  participant MyFragment
  participant MyNicknameDialog
  participant MyViewModel

  User->>MyFragment: 닉네임 편집 아이콘 탭
  MyFragment->>MyNicknameDialog: show()
  User->>MyNicknameDialog: 닉네임 입력/중복확인
  User->>MyNicknameDialog: 변경하기
  MyNicknameDialog->>MyViewModel: updateNickName(newName)
  MyViewModel-->>MyFragment: nickNameState 성공
  MyFragment->>MyFragment: 닉네임 텍스트 갱신
Loading
sequenceDiagram
  participant User
  participant MyFragment
  participant NavController
  participant MyInquireFragment

  User->>MyFragment: 문의하기 탭
  MyFragment->>NavController: navigate(action_fragment_my_to_fragment_inquire)
  NavController-->>MyInquireFragment: 표시
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
닉네임 수정, 프로필 변경 (#71)
버전 정보, 문의하기 (#71)
로그아웃 팝업, 회원탈퇴 팝업 (#71)
알림 설정 (#71) 토글/아이콘 상태는 추가됐으나 실제 설정 저장/시스템 연동 여부가 불명확함.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
빈 ConstraintLayout 추가로 HorizontalScrollView 자식 증가 (app/src/main/res/layout/fragment_search_all.xml; multiple lines) 마이페이지 UI 개선(#71)과 직접 관련이 없는 검색 화면 레이아웃 변경입니다.

Poem

프로필 둥글게, 닉네임 반짝- 새로고침
갤러리 열고 추억 한 장, 톡! 첨부함
문의는 살짝 톡, 토스트가 먼저 인사하고
버전은 말없이 칩 위에 살포시
알림은 깜빡—다음 커밋에 완성될 운명 🌙

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-mypage-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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nasohee nasohee requested a review from t1nm1ksun August 18, 2025 13:20
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (1)
app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt (1)

186-224: 중복 로직 간소화 제안: 관심 등록/해제 공통 처리

postProtectInterestpostReportInterest가 패턴이 동일합니다. 공통 헬퍼로 중복을 줄이면 가독성과 유지보수성이 좋아집니다.

예시:

private fun handleInterestChange(
    id: Long,
    isInterest: Boolean,
    register: suspend (Long) -> Result<Unit>,
    delete: suspend (Long) -> Result<Unit>,
) = viewModelScope.launch {
    val result = if (isInterest) register(id) else delete(id)
    result.fold(
        onSuccess = {},
        onFailure = { _errorMessage.value = it.message ?: "관심 상태 변경 중 오류가 발생했습니다." }
    )
}

// 사용
private fun postProtectInterest(id: Long, isInterest: Boolean) =
    handleInterestChange(id, isInterest, postInterestProtectingAnimalUseCase::invoke, deleteInterestProtectingAnimalUseCase::invoke)

private fun postReportInterest(id: Long, isInterest: Boolean) =
    handleInterestChange(id, isInterest, postInterestReportAnimalUseCase::invoke, deleteInterestReportAnimalUseCase::invoke)
🧹 Nitpick comments (81)
app/src/main/res/layout/fragment_search_all.xml (4)

31-38: 접근성: ImageButton에 contentDescription 추가 필요

두 ImageButton 모두 contentDescription이 없습니다. 스크린 리더 사용자를 위해 의미 있는 설명을 추가해주세요.

수정 제안(diff):

             <ImageButton
                 android:id="@+id/ib_search_all_filter"
                 android:layout_width="39dp"
                 android:layout_height="34dp"
                 android:background="@null"
                 android:src="@drawable/ic_search_filters"
+                android:contentDescription="@string/cd_search_all_filter"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
     <ImageButton
         android:id="@+id/ib_search_all_horizontal_sort"
         android:layout_width="25dp"
         android:layout_height="25dp"
         android:layout_marginTop="5dp"
         android:layout_marginEnd="20dp"
-        android:background="@null"
+        android:background="@null"
         android:elevation="2dp"
         android:minHeight="0dp"
         android:src="@drawable/ic_search_horizontal_sort"
+        android:contentDescription="@string/cd_search_all_sort"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toBottomOf="@id/hs_search_all_filters" />

참고: strings.xml에 cd_search_all_filter, cd_search_all_sort 리소스를 추가해야 합니다.

Also applies to: 69-80


31-38: 터치 피드백 추가 권장

배경을 @null로 둔 ImageButton은 머티리얼 터치 리플 이펙트가 없습니다. UX 향상을 위해 borderless ripple을 적용하는 것을 권장합니다.

수정 제안(diff):

-                android:background="@null"
+                android:background="?attr/selectableItemBackgroundBorderless"
-        android:background="@null"
+        android:background="?attr/selectableItemBackgroundBorderless"

Also applies to: 69-80


56-67: RecyclerView 고정 폭(320dp) 대신 제약 기반 확장 권장

현재 폭을 320dp로 고정하면 다양한 해상도/화면비에서 좌우 여백·자른 표시가 발생할 수 있습니다. ConstraintLayout에서는 양쪽에 제약을 건 상태에서 width=0dp로 설정해 가용 폭을 꽉 채우는 것이 일반적입니다.

수정 제안(diff):

-        android:layout_width="320dp"
+        android:layout_width="0dp"
         android:layout_height="0dp"
         android:layout_marginHorizontal="20dp"

40-50: ChipGroup 높이 고정(34dp)은 폰트 스케일/다국어에서 클리핑 위험

Chip 텍스트 길이나 접근성 폰트 스케일에 따라 34dp 고정 높이는 내용이 잘릴 수 있습니다. wrap_content + minHeight로 안전하게 가는 것을 권장합니다.

수정 제안(diff):

-                android:layout_height="34dp"
+                android:layout_height="wrap_content"
+                android:minHeight="34dp"
app/src/main/res/drawable/bg_my_dialog.xml (1)

5-9: 다크 모드/동적 테마 대응을 위해 하드코딩 색상/치수 치환 제안

white/헥스 컬러를 직접 넣기보다는 테마 속성/공용 dimen을 쓰면 다크 모드와 머티리얼(동적 컬러)에서 일관성이 좋아집니다.

권장 변경안:

-    <solid android:color="@android:color/white" />
-    <corners android:radius="15dp" />
-    <stroke
-        android:width="1dp"
-        android:color="#AE9696" />
+    <solid android:color="?attr/colorSurface" />
+    <corners android:radius="@dimen/my_dialog_corner_radius" />
+    <stroke
+        android:width="1dp"
+        android:color="?attr/colorOutline" />

추가로, 공용 radius를 쓰려면 다음 dimen 정의를 한 번에 정리해두시면 재사용성이 좋아요:

<!-- res/values/dimens.xml -->
<resources>
    <dimen name="my_dialog_corner_radius">16dp</dimen>
</resources>
app/src/main/res/drawable/ic_dialog_close.xml (1)

1-5: 아이콘 색상을 테마 속성으로 전환해 다크 모드 대비/가독성 확보

현재 tint가 #000000로 고정되어 있어 다크 테마에서 배경과 대비가 떨어질 수 있습니다. 테마 속성으로 교체하면 화면 전반의 톤에 맞춰 자동으로 색이 맞춰집니다.

-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?attr/colorOnSurface" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">

참고: path의 fillColor가 white여도 tint가 적용되어 최종 색은 colorOnSurface로 표시됩니다.

app/src/main/res/drawable/ic_message_tail_up.xml (2)

7-9: 툴팁 꼬리 색상을 테마 기반으로 전환

툴팁/말풍선 배경 톤과 맞추기 위해 고정 색(@color/gray1) 대신 테마 속성을 권장합니다. 동적 색상/다크 모드에서 이득이 큽니다.

-    <path
-        android:fillColor="@color/gray1"
-        android:pathData="M0,8 L8,0 L16,8 Z"/>
+    <path
+        android:fillColor="?attr/colorSurfaceVariant"
+        android:pathData="M0,8 L8,0 L16,8 Z"/>

1-10: ic_message_tail_up.xml 리소스 미사용 확인됨 – 제거 검토 요청

  • @drawable/ic_message_tail_up에 대한 XML·Java·Kotlin 참조가 검색되지 않았습니다.
  • 동적 호출이나 기타 리소스 사용 가능성을 최종 검토해 주세요.
  • 실제 사용되지 않는 리소스라면 추후 정리(제거)를 권장드립니다.
app/src/main/res/drawable/bg_my_profile_round.xml (1)

4-4: 프로필 배경색을 테마 surface로 교체

하드코딩 white는 다크 모드에서 뜨게 보일 수 있습니다. surface 계열을 쓰면 자연스러워요.

-    <solid android:color="@android:color/white" />
+    <solid android:color="?attr/colorSurface" />
app/src/main/res/drawable/bg_nickname_edittext_success.xml (1)

3-5: 성공 상태 배경의 테마화 및 컬러 토큰 제안

  • 배경색은 surface로, 성공 색상은 테마/토큰으로 관리하면 라이트/다크에서 일관됩니다.
  • 현재 @color/green1을 계속 쓰시려면 night 리소스에 대비 강화 톤을 별도로 두는 것을 권장합니다.
-    <solid android:color="@android:color/white" />
+    <solid android:color="?attr/colorSurface" />
@@
-    <stroke android:width="1dp" android:color="@color/green1" />
+    <stroke android:width="1dp" android:color="@color/success" />

추가: success 토큰이 없다면 다음처럼 정의해 공용화하는 방식을 추천드립니다.

<!-- res/values/colors.xml -->
<color name="success">#2E7D32</color>

<!-- res/values-night/colors.xml -->
<color name="success">#81C784</color>
app/src/main/res/drawable/bg_nickname_edittext_default.xml (2)

2-5: 다크 모드 대응: 배경 색상 하드코딩 대신 테마 속성 사용 제안

@android:color/white, #DDDDDD는 다크 모드에서 이질감/대비 문제를 유발할 수 있습니다. 테마 색상으로 치환하는 게 안전합니다.

아래처럼 colorSurface/outlineVariant 계열로 대체를 제안드립니다:

-    <solid android:color="@android:color/white" />
+    <solid android:color="?attr/colorSurface" />
@@
-    <stroke android:width="1dp" android:color="#DDDDDD" />
+    <stroke android:width="1dp" android:color="?attr/colorOutline" />

2-5: EditText 배경 커스텀 시 내부 패딩 명시 권장

기본 Material 배경을 대체하면 내부 패딩이 사라져 텍스트가 테두리에 붙어 보일 수 있습니다. 패딩을 명시해 입력 가독성을 확보하세요.

 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
     <solid android:color="@android:color/white" />
     <corners android:radius="8dp" />
     <stroke android:width="1dp" android:color="#DDDDDD" />
+    <padding
+        android:left="12dp"
+        android:top="10dp"
+        android:right="12dp"
+        android:bottom="10dp" />
 </shape>
app/src/main/res/drawable/bg_btn_main_30.xml (1)

4-16: pressed 상태가 기본과 동일해 시각적 피드백이 없습니다

누름 피드백이 없어 버튼 반응성이 낮게 느껴질 수 있습니다. RippleDrawable 사용 또는 pressed 색상 분리(어둡게) 중 하나를 권장드립니다.

Ripple로 대체 (API 21+):

-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="true">
-        <shape android:shape="rectangle">
-            <solid android:color="@color/main_color" />
-            <corners android:radius="30dp" />
-        </shape>
-    </item>
-
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="@color/main_color" />
-            <corners android:radius="30dp" />
-        </shape>
-    </item>
-
-</selector>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/colorControlHighlight">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/main_color" />
+            <corners android:radius="30dp" />
+        </shape>
+    </item>
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white" />
+            <corners android:radius="30dp" />
+        </shape>
+    </item>
+</ripple>

대안: pressed 색 분리

-            <solid android:color="@color/main_color" />
+            <solid android:color="@color/main_color_pressed" />

(별도 색 리소스 추가 필요)

Ripple 사용 시 minSdk가 21 이상인지 확인 부탁드립니다. 21 미만 지원 시 selector 방식 유지가 안전합니다.

app/src/main/res/drawable/ic_my_edit_pen.xml (1)

1-13: 테마 대응을 위해 tint 적용 권장

개별 path에 고정 색(@color/gray4) 지정 대신 벡터 tint를 사용하면 다크 모드/테마에 자동 적응합니다.

-<vector xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="20dp"
     android:height="20dp"
     android:viewportWidth="20"
-    android:viewportHeight="20">
+    android:viewportHeight="20"
+    android:tint="?attr/colorControlNormal"
+    android:tintMode="src_in">
@@
-      android:fillColor="@color/gray4"
+      android:fillColor="@android:color/white"
@@
-      android:strokeColor="@color/gray4"/>
+      android:strokeColor="@android:color/white"/>

주의: 이 방식은 벡터가 흰색으로 그려지고, tint로 최종 색이 입혀집니다. 필요 시 아이콘 컨테이너 뷰에 tint 지정도 가능합니다.

app/src/main/res/color/chip_inquire_color.xml (1)

2-5: 메인 컬러 하드코딩 대신 테마 속성 사용 고려

상태 리스트의 checked 색상을 @color/main_color 대신 ?attr/colorPrimary로 두면 테마/다크 모드 대응이 용이합니다.

-    <item android:state_checked="true" android:color="@color/main_color"/>
+    <item android:state_checked="true" android:color="?attr/colorPrimary"/>

추가로 disabled 상태 표현이 필요하다면 state_enabled="false" 항목을 하단에 추가하는 것도 고려해주세요.

이 리소스가 텍스트/아이콘 tint로도 사용된다면, 대비(AA 기준) 확인이 필요할 수 있습니다.

app/src/main/res/drawable/bg_btn_gray_30.xml (1)

4-16: 버튼 회색 배경도 누름 피드백 부재 + 색상 상수 하드코딩

  • pressed와 default가 동일합니다.
  • #F2F4F6 하드코딩 대신 color 리소스를 사용하면 일관성/다크 모드 대응에 유리합니다.

Ripple로 대체:

-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="true">
-        <shape android:shape="rectangle">
-            <solid android:color="#F2F4F6" />
-            <corners android:radius="30dp" />
-        </shape>
-    </item>
-
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="#F2F4F6" />
-            <corners android:radius="30dp" />
-        </shape>
-    </item>
-
-</selector>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/colorControlHighlight">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/gray_button_bg" />
+            <corners android:radius="30dp" />
+        </shape>
+    </item>
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white" />
+            <corners android:radius="30dp" />
+        </shape>
+    </item>
+</ripple>

대안: selector 유지 시 색 리소스화 및 pressed 명도 약간 하향:

-            <solid android:color="#F2F4F6" />
+            <solid android:color="@color/gray_button_bg_pressed" />

(새 색 리소스 추가 필요)

app/src/main/res/layout/dialog_my_withdrawal.xml (6)

50-60: LinearLayout 자식에 불필요한 Constraint 속성 + 잘못된 ID 참조 제거 필요

LinearLayout 내부 TextView에 app:layout_constraint*가 존재하고, 존재하지 않는 ID(ll_report_image_dialog_mention)를 참조하고 있습니다. LinearLayout에서는 무시되지만, Lint 경고/혼동을 유발합니다. 삭제 제안합니다.

             <TextView
                     android:id="@+id/tv_my_withdrawal_dialog_mention"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="10dp"
                     android:gravity="center"
                     android:text="탈퇴하기를 선택하시면, \n계정 정보와 신고 기록들이 삭제되고\n복구되지 않아요."
                     android:textAppearance="@style/TextAppearance.FindU.Body2_SB_14"
                     android:textColor="@color/gray5"
-                    app:layout_constraintStart_toStartOf="@id/ll_report_image_dialog_mention"
-                    app:layout_constraintTop_toBottomOf="@id/ll_report_image_dialog_mention" />
+                    />

20-28: 로고 ImageView 접근성 처리(장식 이미지) 권장

장식성 로고 이미지로 보입니다. 스크린리더가 읽지 않도록 contentDescription="@null"을 명시해 주세요.

         <ImageView
                 android:id="@+id/iv_my_withdrawal_dialog_logo"
                 android:layout_width="96dp"
                 android:layout_height="96dp"
                 android:layout_marginTop="50dp"
                 android:src="@drawable/img_findu_logo"
+                android:contentDescription="@null"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />

63-71: 닫기 영역 UI가 비어 있음 — 실제 클릭 타깃/아이콘 확인 필요

닫기용 컨테이너가 비어 있고(아이콘 없음), clickable 속성도 없습니다. 코드에서 onClick을 걸 계획이라도, 시각적 피드백/접근성 측면에서 최소한 아이콘과 contentDescription이 필요합니다. 아래처럼 아이콘/터치 피드백을 추가하는 것을 권장합니다.

         <androidx.constraintlayout.widget.ConstraintLayout
                 android:id="@+id/cl_my_withdrawal_dialog_close"
                 android:layout_width="24dp"
                 android:layout_height="24dp"
                 android:layout_marginTop="16dp"
                 android:layout_marginEnd="16dp"
+                android:clickable="true"
+                android:focusable="true"
+                android:background="?attr/selectableItemBackgroundBorderless"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toTopOf="parent">
 
+            <ImageView
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:src="@drawable/ic_close_24"
+                android:contentDescription="@string/common_close" />
         </androidx.constraintlayout.widget.ConstraintLayout>

추가로 strings.xml에 아래 키가 없다면 생성이 필요합니다:

<string name="common_close">닫기</string>

95-104: 보더 버튼 텍스트 색상 지정

테두리형 버튼 배경(bg_button_my_dialog_stroked)을 쓰는 경우 텍스트 색상을 명시하지 않으면 대비가 약하거나 테마에 따라 의도치 않게 보일 수 있습니다. 메인 컬러로 지정 제안합니다.

             <androidx.appcompat.widget.AppCompatButton
                     android:id="@+id/btn_my_withdrawal_dialog_withdrawal"
                     android:layout_width="match_parent"
                     android:layout_height="45dp"
                     android:layout_marginTop="10dp"
                     android:background="@drawable/bg_button_my_dialog_stroked"
                     android:backgroundTint="@null"
+                    android:textColor="@color/main_color"
                     android:text="탈퇴하기"
                     android:layout_marginBottom="20dp"
                     android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18" />

43-47: 하드코딩된 문자열 리소스화 권장

다국어/일관성 유지를 위해 문자열은 strings.xml로 이동하는 것이 좋습니다.

-                    android:text="정말로 탈퇴하시겠어요?"
+                    android:text="@string/my_withdrawal_title"
...
-                    android:text="탈퇴하기를 선택하시면, \n계정 정보와 신고 기록들이 삭제되고\n복구되지 않아요."
+                    android:text="@string/my_withdrawal_mention"
...
-                    android:text="취소"
+                    android:text="@string/common_cancel"
...
-                    android:text="탈퇴하기"
+                    android:text="@string/my_withdrawal_confirm"

strings.xml 예시(별도 파일 변경 필요):

<string name="my_withdrawal_title">정말로 탈퇴하시겠어요?</string>
<string name="my_withdrawal_mention">탈퇴하기를 선택하시면, \n계정 정보와 신고 기록들이 삭제되고\n복구되지 않아요.</string>
<string name="common_cancel">취소</string>
<string name="my_withdrawal_confirm">탈퇴하기</string>

원하시면 PR에 맞춰 strings.xml 패치까지 제안드릴게요.

Also applies to: 49-57, 85-93, 95-104


11-18: 라운드 배경 클리핑 의도 확인

배경이 라운드(@drawable/bg_report_dialog_rd12)라면, 자식의 모서리 넘침을 막으려면 android:clipToOutline="true"가 필요할 수 있습니다. 현재는 clipChildren/clipToPadding=false 상태라 내부 콘텐츠가 둥근 모서리를 넘어갈 수 있습니다.

     <androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="288dp"
             android:layout_height="wrap_content"
             android:clipToPadding="false"
             android:layout_marginHorizontal="20dp"
             android:clipChildren="false"
             android:elevation="8dp"
             android:outlineProvider="background"
+            android:clipToOutline="true"
             android:background="@drawable/bg_report_dialog_rd12">
app/src/main/res/drawable/bg_toast_inquire_dark.xml (1)

1-5: 토스트 배경 모서리 반경 값 미세 조정 제안

고정 50dp 대신 매우 큰 값(예: 999dp)을 사용하면 높이 변화가 있어도 항상 pill 형태를 유지할 수 있습니다. 또한 텍스트 색 대비(회색 배경 위 흰색/검정 텍스트)가 WCAG 기준을 만족하는지 한번만 확인 부탁드려요.

 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="@color/gray5" />
-    <corners android:radius="50dp" />
+    <corners android:radius="999dp" />
 </shape>
app/src/main/res/drawable/bg_dialog_my_nickname_edit.xml (1)

2-20: 포커스 상태 시각적 차이 없음 — 간소화 또는 포커스 하이라이트 제안

현재 focused=true/false가 동일 스타일이라 selector가 불필요합니다. 둘 중 하나를 권장:

  • 간소화: 단일 shape로 축소
  • 개선: 포커스 시 스트로크를 메인 컬러로 변경해 입력 포커스 가시성 향상

옵션 A(가시성 개선):

     <item android:state_focused="true">
         <shape android:shape="rectangle">
             <solid android:color="@color/white" />
-            <stroke android:width="1dp" android:color="@color/gray3" />
+            <stroke android:width="1dp" android:color="@color/main_color" />
             <corners android:radius="8dp" />
         </shape>
     </item>

옵션 B(간소화):
전체를 아래 단일 shape로 대체

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@color/white" />
    <stroke android:width="1dp" android:color="@color/gray3" />
    <corners android:radius="8dp" />
</shape>
app/src/main/res/drawable/bg_my_inquire_chip.xml (1)

1-17: 선택 상태 구분 명확 — 터치 피드백(pressed/ripple) 추가 고려

checked 상태 대비가 좋아요. 터치 피드백을 위해 pressed 상태나 ripple을 더하면 체감 품질이 올라갑니다. Material Chip 컴포넌트 사용이 가능하다면 그것도 좋은 대안입니다.

예: pressed 상태 추가(살짝 톤다운된 배경)

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/gray1" />
+            <stroke android:width="1dp" android:color="@color/gray3" />
+            <corners android:radius="8dp" />
+        </shape>
+    </item>
     <item android:state_checked="true">
         <shape android:shape="rectangle">
             <solid android:color="@android:color/white" />
             <stroke android:width="1dp" android:color="@color/main_color" />
             <corners android:radius="8dp" />
         </shape>
     </item>
     <item>
         <shape android:shape="rectangle">
             <solid android:color="@android:color/white" />
             <stroke android:width="1dp" android:color="@color/gray3" />
             <corners android:radius="8dp" />
         </shape>
     </item>
 </selector>
app/src/main/res/layout/item_toast_inquire.xml (3)

11-15: 접근성: 장식용 아이콘에 contentDescription 누락

장식 목적의 로고 이미지에는 android:contentDescription="@null"을 명시해 스크린리더가 불필요하게 읽지 않도록 해주세요.

     <ImageView
             android:layout_marginStart="26dp"
             android:layout_width="20dp"
             android:layout_height="20dp"
-            android:src="@drawable/img_findu_logo" />
+            android:src="@drawable/img_findu_logo"
+            android:contentDescription="@null" />

21-21: 하드코딩된 문자열을 string 리소스로 이동

토스트 문구는 다국어 및 재사용을 위해 strings.xml로 분리하는 것이 좋습니다.

-            android:text="모든 내용을 채워주세요!"
+            android:text="@string/toast_fill_all_fields"

추가 작업: app/src/main/res/values/strings.xmltoast_fill_all_fields 키를 정의해주세요.


6-6: 호환성: paddingVertical 사용 검토

android:paddingVertical 사용은 minSdk에 따라 호환성 이슈가 있을 수 있습니다. 보수적으로 paddingTop/paddingBottom으로 명시하는 것을 고려해주세요.

-        android:paddingVertical="10dp"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
app/src/main/res/navigation/nav_graph.xml (1)

246-251: 레이블 하드코딩 제거 제안

프래그먼트 레이블 "문의하기"를 string 리소스로 이동하면 일관성과 i18n에 유리합니다.

-            android:label="문의하기"
+            android:label="@string/label_inquire"
app/src/main/res/layout/fragment_my_inquire.xml (8)

16-23: 접근성: 뒤로가기 아이콘에 contentDescription 추가

내비게이션 버튼은 보조기기 사용자를 위해 설명이 필요합니다.

         <ImageView
             android:id="@+id/iv_my_inquire_back"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:src="@drawable/ic_arrow_back_24"
+            android:contentDescription="@string/cd_navigate_up"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />

25-33: 하드코딩된 타이틀 문자열 리소스화

상단 타이틀 "문의하기"는 strings.xml로 이동해주세요.

-            android:text="문의하기"
+            android:text="@string/label_inquire"

44-58: 라벨/힌트 텍스트 일괄 리소스화

"카테고리", "중복 선택 가능" 등 UI 문자열은 모두 string 리소스로 분리해주세요.

예시:

-        android:text="카테고리"
+        android:text="@string/inquire_category"

-        android:text="중복 선택 가능"
+        android:text="@string/inquire_multi_select_hint"

73-107: Chip 스타일 적용 방식 점검 (상태 반영/리플 효과)

android:background로 커스텀 드로어블을 지정하면서 app:chipBackgroundColor도 함께 사용 중입니다. 체크 상태/리플/터치 피드백이 깨질 가능성이 있습니다. Material Chip 속성 중심으로 스타일링하는 것을 권장합니다.

권장 방향:

  • 배경: app:chipBackgroundColor (ColorStateList)
  • 선택 상태 테두리: app:chipStrokeColor, app:chipStrokeWidth
  • 리플: app:rippleColor

만약 bg_my_inquire_chipstate_checked/state_pressed를 모두 처리한다면 현 구현도 동작할 수 있으니, 체크/비체크/프레스 상태에서의 시각 피드백을 한번 확인 부탁드립니다.


122-145: TextInputLayout 고정 높이로 인한 에러 메시지/라인 클리핑 가능성

android:layout_height="52dp" 고정은 에러 메시지 노출 시(예: setError) 또는 폰트 스케일링에서 클리핑을 유발할 수 있습니다. wrap_content로 전환을 권장합니다.

     <com.google.android.material.textfield.TextInputLayout
         android:id="@+id/tfi_inquire_title"
         android:layout_width="0dp"
-        android:layout_height="52dp"
+        android:layout_height="wrap_content"

137-144: 힌트/접근성: TextInputEditText에 inputType 지정

키보드 타입과 오토코렉트 동작을 명확히 하기 위해 inputType을 지정하는 것이 좋습니다.

         <com.google.android.material.textfield.TextInputEditText
             android:id="@+id/tf_inquire_title"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:hint="제목을 입력해주세요."
+            android:inputType="textCapSentences"
             android:textAppearance="@style/TextAppearance.FindU.Body2_R_14"
             android:textColorHint="@color/gray3" />

동일하게 내용 입력에도 textMultiLine 지정이 되어 있어 좋습니다.


185-209: 버튼 스타일 일관성 및 틴트 중복

MaterialButton에 커스텀 배경과 app:backgroundTint를 함께 쓰면 틴트가 적용되지 않거나 중복 정의가 될 수 있습니다. 디자인 시스템(스타일/테마) 기반으로 통일하는 것을 추천합니다.

예시 방향:

  • 공통 스타일 Widget.Material3.Button 또는 프로젝트 커스텀 스타일 적용
  • 배경 드로어블 제거 후 tint 기반으로 색상 제어, 또는 반대로 tint 제거하고 드로어블만 사용

111-121: 하드코딩 문자열 전반 정리

"문의 제목", "제목을 입력해주세요.", "문의 내용", "내용을 입력해주세요." 등 모든 사용자 노출 텍스트를 string 리소스로 옮겨주세요.

Also applies to: 146-156, 172-183

app/src/main/res/layout/item_my_nickname_error.xml (3)

11-18: 접근성: 말풍선 꼬리 이미지 contentDescription

장식용 요소이므로 스크린리더 제외를 위해 contentDescription="@null"을 지정해주세요.

     <ImageView
         android:id="@+id/iv_tooltip_tail"
         android:layout_width="15dp"
         android:layout_height="15dp"
         android:layout_marginEnd="28dp"
         android:src="@drawable/img_my_message_tail"
+        android:contentDescription="@null"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent" />

37-60: 하드코딩 문자열 → string 리소스, 포매팅/목록화 고려

툴팁 타이틀과 1~3번 항목을 strings.xml로 이동하고, 가독성을 위해 줄바꿈/불릿을 활용하거나 String.format/플레이스홀더로 관리하면 유지보수에 좋습니다.

예시:

-            android:text="다음과 같은 경우에 불가능해요."
+            android:text="@string/nickname_rule_title"
...
-            android:text="1. 중복된 닉네임인 경우"
+            android:text="@string/nickname_rule_dup"

24-31: 음수 마진(-2dp) 사용 최소화 권장

말풍선 꼬리와 말풍선 본체를 맞추기 위해 음수 마진을 쓰면 해상도/접근성 설정(글꼴 확대)에서 오프셋이 어긋날 수 있습니다. 드로어블 레이아웃(레이어리스트)로 꼬리를 포함하거나, 컨테이너 내에서 정렬을 재조정하는 방식을 검토해 주세요.

app/src/main/res/layout/dialog_my_nickname_edit.xml (7)

7-8: ConstraintLayout에 orientation 속성은 무의미

ConstraintLayoutandroid:orientation을 사용하지 않습니다. 제거해도 동작 동일합니다.

-    android:orientation="vertical"

22-29: 닫기 아이콘 contentDescription 추가

접근성 향상을 위해 닫기 버튼에 설명을 달아주세요.

     <ImageView
         android:id="@+id/iv_dialog_close"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:src="@drawable/ic_dialog_close"
+        android:contentDescription="@string/cd_close"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         app:tint="@color/gray3" />

41-52: 닉네임 입력란 inputType/IME 옵션 지정

키보드 타입과 완료 액션을 명확히 해 UX를 개선하세요. 닉네임 특성상 textPersonName 또는 제한 커스텀을 적용하는 것이 일반적입니다.

         <EditText
             android:id="@+id/et_nickname"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:background="@android:color/transparent"
             android:hint="닉네임 입력"
             android:maxLength="8"
             android:minHeight="48dp"
             android:paddingHorizontal="16dp"
+            android:inputType="textPersonName"
+            android:imeOptions="actionDone"
             android:textAppearance="@style/TextAppearance.FindU.Body2_SB_14" />

추가로, 허용 문자(한글/영문/숫자 등) 제한이 필요하면 코드에서 InputFilter 또는 정규식 검증을 병행해주세요.


53-61: "중복확인"은 상호작용 요소 — 버튼 컴포넌트 사용 권장

접근성/포커스 처리를 위해 TextView 대신 TextButton(MaterialButton) 사용을 권장합니다. 최소한 focusable/clickable 속성 지정이 필요합니다.

예시:

-        <TextView
+        <com.google.android.material.button.MaterialButton
             android:id="@+id/tv_check_duplicate"
-            android:layout_width="wrap_content"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginEnd="12dp"
-            android:paddingStart="8dp"
-            android:text="중복확인"
-            android:textAppearance="@style/TextAppearance.FindU.Body2_R_14"
-            android:textColor="@color/main_color" />
+            style="?attr/materialButtonOutlinedStyle"
+            android:text="@string/nickname_check_duplicate"
+            android:textAppearance="@style/TextAppearance.FindU.Body2_R_14"
+            android:textColor="@color/main_color"
+            app:iconPadding="0dp"
+            app:backgroundTint="@android:color/transparent"
+            app:strokeColor="@color/main_color"
+            app:strokeWidth="1dp"/>

65-74: 경고 아이콘 contentDescription

경고 아이콘도 장식 목적이면 @null, 의미가 있다면 적절한 설명을 부여해주세요.

     <ImageView
         android:id="@+id/iv_nickname_warning"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:visibility="invisible"
         android:layout_marginEnd="10dp"
         android:src="@drawable/img_my_nickname_warning"
+        android:contentDescription="@null"

88-99: 버튼 배경 리소스 불일치 가능성 (bg_edittext 사용)

btn_change_nickname@drawable/bg_edittext를 배경으로 사용 중입니다. 버튼에 EditText 배경을 재사용하면 포커스/프레스/활성-비활성 상태 표현이 어색해질 수 있습니다. MaterialButton으로 교체하거나 버튼 전용 배경을 사용하세요.

예시:

-    <Button
+    <com.google.android.material.button.MaterialButton
         android:id="@+id/btn_change_nickname"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="55dp"
-        android:background="@drawable/bg_edittext"
-        android:backgroundTint="@color/gray1"
+        style="?attr/materialButtonOutlinedStyle"
+        app:backgroundTint="@color/gray1"
         android:enabled="false"
-        android:text="변경하기"
+        android:text="@string/nickname_change"
         android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18"
         android:textColor="@color/gray4"
         app:layout_constraintTop_toBottomOf="@id/ll_dialog_my_nickname_edit" />

10-20: 하드코딩 문자열 전반 리소스화

"닉네임 변경", "닉네임 입력", "중복확인", "(최대 8글자)", "변경하기" 등은 모두 string 리소스로 이동해 주세요.

Also applies to: 31-36, 41-61, 76-83, 88-99

app/src/main/res/layout/dialog_my_profile_image.xml (4)

21-28: 접근성: 닫기 아이콘에 contentDescription 추가 권장

보조기기 사용자를 위해 닫기 버튼에 내용 설명을 추가해 주세요. 현재 클릭 가능한 요소인데 설명이 없습니다.

적용 예시(간단히 하드코딩, 추후 strings.xml로 분리 권장):

 <ImageView
     android:id="@+id/iv_dialog_close"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:src="@drawable/ic_dialog_close"
+    android:contentDescription="닫기"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintTop_toTopOf="parent"
     app:tint="@color/gray3" />

71-76: 오타: id 'iv_profile_img_dafult' → 'iv_profile_img_default'로 수정 권장

오타가 그대로 ViewBinding/코드에 반영되어 유지보수성과 가독성이 떨어집니다. 레이아웃 id와 참조 코드를 함께 정정해 주세요.

레이아웃 변경:

-    <ImageView
-        android:id="@+id/iv_profile_img_dafult"
+    <ImageView
+        android:id="@+id/iv_profile_img_default"
         android:layout_width="58dp"
         android:layout_height="58dp"
         android:src="@drawable/img_my_profile_default" />

코틀린 참조 변경은 MyProfileImageDialog.kt에 별도 코멘트로 제안드립니다.


39-49: 접근성: 대표 이미지에도 설명 추가 권장

갤러리 실행 액션이 매핑된 클릭 가능한 이미지입니다. contentDescription을 부여해 주세요.

 <ImageView
     android:id="@+id/iv_my_illust"
     android:layout_width="146dp"
     android:layout_height="146dp"
     android:background="@drawable/bg_my_profile_round"
     android:clipToOutline="true"
     android:scaleType="centerCrop"
     android:src="@drawable/img_my_profile_default"
+    android:contentDescription="프로필 이미지 미리보기"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent" />

50-57: 편집 아이콘도 클릭 액션 고려 시 접근성 설명 추가 권장

해당 아이콘으로 갤러리 실행을 유도할 계획이라면 contentDescription을 추가해 주세요. (코드 쪽 클릭 리스너 제안은 Dialog 파일에서 드립니다)

 <ImageView
     android:id="@+id/iv_my_image_edit"
     android:layout_width="40dp"
     android:layout_height="40dp"
     android:elevation="4dp"
     android:src="@drawable/img_my_image_edit"
+    android:contentDescription="이미지 변경"
     app:layout_constraintBottom_toBottomOf="parent"
     app:layout_constraintEnd_toEndOf="parent" />
app/src/main/java/com/example/findu/presentation/ui/my/MyInquireFragment.kt (3)

82-88: TextInputLayout 테두리 두께가 px 기준(1px)으로 고정됨

setBoxStrokeWidthFocused(1f.toInt())는 화면 밀도에 상관없이 1px로 적용되어 고해상도 기기에서 너무 얇게 보입니다. dp→px 변환을 적용해 주세요.

 private fun applyTitleStroke() {
-    binding.tfiInquireTitle.setBoxStrokeWidthFocused(1f.toInt())
+    binding.tfiInquireTitle.setBoxStrokeWidthFocused(1.dp())
     val main = ContextCompat.getColor(requireContext(), R.color.main_color)
 private fun applyContentStroke() {
-    binding.tfiInquireContent.setBoxStrokeWidthFocused(1f.toInt())
+    binding.tfiInquireContent.setBoxStrokeWidthFocused(1.dp())
     val main = ContextCompat.getColor(requireContext(), R.color.main_color)

아래 확장 함수 하나를 파일 내에 추가해 사용하면 깔끔합니다:

// 파일 하단 등에 추가
private fun Int.dp(): Int = (this * resources.displayMetrics.density).toInt()

144-145: Chip 최소 높이도 px로 고정됨 — dp 변환 적용 권장

minHeight = 54는 54px입니다. 의도하신 54dp라면 dp→px 변환을 적용해 주세요.

-            chip.apply { minHeight = 54 }
+            chip.apply { minHeight = 54.dp() }

64-69: 네비게이션 컴포넌트 사용 시 popBackStack은 NavController 경유 권장

parentFragmentManager.popBackStack() 대신 findNavController().popBackStack()이 NavHost 기반 백스택과 더 일관적입니다. (import: androidx.navigation.fragment.findNavController)

-            parentFragmentManager.popBackStack()
+            findNavController().popBackStack()

그리고 뒤로가기 버튼 리스너(라인 67-69)도 동일하게 교체해 주세요.

app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyProfileImageDialog.kt (3)

37-40: 편집 아이콘에도 갤러리 실행 연결하면 UX 향상

상단 대표 이미지뿐 아니라 연필 아이콘 클릭 시에도 갤러리를 여는 편이 자연스럽습니다.

-        binding.ivMyIllust.setOnClickListener {
+        binding.ivMyIllust.setOnClickListener {
             launchGallery()
         }
+        binding.ivMyImageEdit.setOnClickListener {
+            launchGallery()
+        }

41-45: 오타: 'ivProfileImgDafult' → 'ivProfileImgDefault' (레이아웃 변경에 맞춰 코드도 수정)

레이아웃 id를 iv_profile_img_default로 변경하면, 바인딩 참조도 함께 변경해야 합니다.

-        ivProfileImgDafult.setOnClickListener {
+        ivProfileImgDefault.setOnClickListener {
             selectedImageResId = R.drawable.img_my_profile_default
             selectedUri = null
             ivMyIllust.setImageResource(selectedImageResId)
         }

3-12: 불필요 import 정리

Intent는 사용되지 않습니다. 정리해 주세요. (자동 최적화 권장)

app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt (1)

61-69: 프로필 이미지 상태 플로우 노출 방식 일관성

selectedImageResIdasStateFlow()로, selectedProfileImageUri는 직접 StateFlow 타입으로 노출하고 있습니다. 기능상 문제는 없지만 스타일을 맞추면 읽기 편합니다.

-    val selectedProfileImageUri: StateFlow<Uri?> = _selectedProfileImageUri
+    val selectedProfileImageUri = _selectedProfileImageUri.asStateFlow()
app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyNicknameDialog.kt (3)

58-69: 중복 확인 로직이 placeholder 수준

val isDuplicated = nickname == " "는 임시 로직으로 보입니다. 실제 API/UseCase 연동 또는 ViewModel을 통해 비동기 검증으로 교체해 주세요. 필요 시 의존성 주입 설계까지 도와드릴게요.


61-84: 문구/컬러/배경 하드코딩 다수 — strings.xml 및 theme로 분리 권장

UX엔 문제 없지만 리소스로 분리하면 i18n/다크모드 대응이 쉬워집니다. 다음 항목을 우선 분리하는 것을 추천합니다: 오류/성공 메시지 텍스트, 버튼 색상, EditText 배경.


152-154: dp 변환 함수 중복 — 공용 확장으로 추출 제안

본 파일의 dpToPx와 다른 클래스의 dp 변환 로직이 중복됩니다. ui 모듈에 확장함수로 추출하면 재사용성이 좋아집니다.

app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (7)

135-149: Play Store 미설치 대응 시 예외 로그 남기기 (detekt: SwallowedException 해소)

예외를 처리하면서 로그가 없어 원인 추적이 어려워요. 경고 로그를 남기면 detekt 경고도 해소됩니다.

                 try {
                     startActivity(
                         Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkg"))
                     )
                 } catch (e: ActivityNotFoundException) {
+                    Log.w("MyFragment", "Play Store app is not installed. Falling back to web URL.", e)
                     startActivity(
                         Intent(
                             Intent.ACTION_VIEW,
                             Uri.parse("https://play.google.com/store/apps/details?id=$pkg")
                         )
                     )
                 }

239-243: 다이얼로그 참조 정리로 메모리 누수 예방

뷰 파괴 시 myProfileImageDialog 참조를 null로 만들어 메모리 누수를 방지하세요.

 override fun onDestroyView() {
     super.onDestroyView()
-    _binding = null
+    _binding = null
+    myProfileImageDialog = null
 }

48-55: ActivityResult 콜백의 생명주기 가드(마이너)

드물지만 Fragment가 분리(detached)된 직후 콜백이 돌아올 수 있어요. isAdded 가드로 안전하게 방어할 수 있습니다.

         pickMedia = registerForActivityResult(
             ActivityResultContracts.PickVisualMedia()
         ) { uri: Uri? ->
+            if (!isAdded) return@registerForActivityResult
             if (uri != null) {
                 myProfileImageDialog?.setGalleryImage(uri)
             } else {
                 Toast.makeText(requireContext(), "이미지가 선택되지 않았어요.", Toast.LENGTH_SHORT).show()
             }
         }

99-102: 닉네임 다이얼로그 전환 후 잔여 코드 정리

닉네임 변경을 다이얼로그로 이관했는데, 보이지 않는 EditText에 대한 TextWatcher와 setText 동기화 코드가 남아 있습니다. 혼란을 줄이기 위해 제거하는 것을 권장합니다.

-            etMyNickname.addTextChangedListener { text ->
-                tvMyNickname.text = text.toString()
-            }
-                            binding.etMyNickname.setText(nickName)

Also applies to: 193-197


201-213: 에러/삭제 메시지 토스트의 중복 노출 방지

StateFlow를 그대로 수집하면 재수집 시 동일 메시지가 반복 노출될 수 있어 UX가 떨어질 수 있습니다. 일회성 이벤트(Event) 래퍼(또는 SharedFlow/replay=0)를 도입해 주세요.

원하시면 Event 래퍼와 확장 함수(doOnConsumed 등) 포함해 적용 PR 초안을 만들어 드릴게요.


119-129: 로그아웃 전환 시 백스택 정리 권장

LoginActivity로 이동 후 finish()만 호출하면 백스택 상황에 따라 예외 케이스가 생길 수 있습니다. 태스크를 정리하는 플래그를 추가하는 것이 안전합니다.

-                            startActivity(Intent(requireContext(), LoginActivity::class.java))
+                            startActivity(
+                                Intent(requireContext(), LoginActivity::class.java).apply {
+                                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                                }
+                            )
                             finish()

81-97: 오탈자: clMyProflieImage → clMyProfileImage (ID/바인딩 네이밍 정리)

ID 오탈자가 바인딩 필드명까지 전파되어 가독성이 떨어집니다. XML의 cl_my_proflie_image를 cl_my_profile_image로, 본 코드의 바인딩 접근자도 함께 변경해 주세요.

-            clMyProflieImage.setOnClickListener {
+            clMyProfileImage.setOnClickListener {

참고: XML/다른 참조들도 함께 리네임되어야 합니다(아래 레이아웃 코멘트 참고).

app/src/main/res/layout/fragment_my.xml (5)

114-121: 오탈자(ID) 수정: cl_my_proflie_image → cl_my_profile_image

오탈자(ID)가 바인딩/코드 전반에 전파되니 지금 정리하는 것이 좋습니다. 아래처럼 ID 및 참조를 함께 변경해 주세요.

-                android:id="@+id/cl_my_proflie_image"
+                android:id="@+id/cl_my_profile_image"
-                app:layout_constraintTop_toBottomOf="@id/cl_my_proflie_image">
+                app:layout_constraintTop_toBottomOf="@id/cl_my_profile_image">

Also applies to: 155-156


171-176: 하드코딩 문자열 리소스화 권장

"신고 내역", "최근 본 동물", "관심 동물", "문의하기", "버전 정보 1.0", "최신버전", "업데이트 하러가기", "로그아웃" 등이 직접 문자열로 하드코딩되어 있습니다. i18n/접근성/일관성 관점에서 strings.xml로 옮겨 주세요. tv_my_version_info는 코드에서 실제 버전명을 세팅하므로 기본값은 포맷 리소스(예: "버전 정보 %1$s")가 적절합니다.

원하시면 문자열 키/리소스 추가와 XML 치환까지 포함한 변경 패치를 만들어 드릴게요.

Also applies to: 199-204, 226-232, 306-310, 335-336, 356-357, 381-383, 413-414


31-33: 툴 전용 속성 정리(마이너)

tools:layout_editor_absoluteX는 에디터 전용입니다. 유지해도 동작에는 문제 없지만, 불필요한 속성은 정리하는 편이 깔끔합니다.

-        app:layout_constraintVertical_bias="0.0"
-        tools:layout_editor_absoluteX="72dp">
+        app:layout_constraintVertical_bias="0.0">

122-141: 접근성: contentDescription 추가 권장

클릭 가능한 아이콘(iv_my_image_edit, iv_my_alarm_icon)과 프로필 이미지(iv_my_illust)에 contentDescription이 없습니다. TalkBack 사용자 입장에서 의미 있는 설명이 필요합니다.

예시:

  • iv_my_illust: "@string/cd_my_profile_image"
  • iv_my_image_edit: "@string/cd_edit_profile_image"
  • iv_my_alarm_icon: "@string/cd_alarm_setting_state"

Also applies to: 284-292


186-192: ID 접두어와 실제 뷰 타입 불일치(마이너 네이밍)

LinearLayout인데 cl_ 접두어(보통 ConstraintLayout에 사용)를 쓰고 있습니다(cl_my_recent_history, cl_my_keep_animal). 기능에는 문제 없지만, 네이밍 일관성을 위해 ll_ 접두어로 조정하는 것을 권장합니다.

Also applies to: 214-220

app/src/main/res/drawable/bg_button_logout_cancel.xml (1)

5-7: 불필요한 투명 스트로크 제거 + 상태(ripple/pressed) 추가 고려

현재 1dp 투명 스트로크는 시각적 효과가 없어 불필요합니다. 또한 selector에 상태 아이템이 없어 터치 피드백이 없습니다. 최소한 pressed/disabled 혹은 ripple 효과 추가를 권장합니다.

아래처럼 투명 스트로크를 제거할 수 있습니다:

 <shape android:shape="rectangle">
   <solid android:color="@color/main_color"/>
-  <stroke android:width="1dp" android:color="@android:color/transparent"/>
   <corners android:radius="8dp"/>
 </shape>

추가 제안:

  • 버튼 배경으로 커스텀 셰이프를 쓰는 경우, 버튼 뷰 쪽에 android:foreground="?attr/selectableItemBackground"를 설정하여 시스템 ripple을 주거나,
  • selector에 android:state_pressed, android:state_enabled=false 상태 아이템을 추가해 피드백과 접근성을 개선하는 것을 권장합니다.
    (색상 리소스/명명 규칙은 팀 컨벤션에 맞춰 주시면 됩니다.)
app/src/main/res/drawable/bg_button_logout_confirm.xml (1)

2-10: 기본 형태는 적절함. 다만 상태(ripple/pressed/disabled) 정의를 추가하면 UX가 좋아집니다

현재 단일 아이템 selector로 정의되어 있어 터치 피드백이 없습니다. pressed/disabled 상태 추가 또는 ripple 적용을 권장합니다. 이외 모서리 반경/스트로크 정의는 일관적이며 괜찮습니다.

app/src/main/res/layout/dialog_logout.xml (6)

22-30: 로고 이미지는 장식용이면 접근성 제외 처리 필요

스크린 리더가 불필요하게 로고를 읽지 않도록 contentDescription을 null로, 접근성 제외를 권장합니다.

 <ImageView
     android:id="@+id/iv_logout_img"
     android:layout_width="96dp"
     android:layout_height="96dp"
     android:src="@drawable/img_findu_logo"
+    android:contentDescription="@null"
+    android:importantForAccessibility="no"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintTop_toTopOf="parent"
     app:layout_constraintEnd_toEndOf="parent"
     android:layout_marginTop="50dp" />

69-77: 취소 버튼 배경: 신규 리소스(bg_button_logout_cancel) 사용으로 일관성 정리

PR에서 추가한 drawable을 사용하도록 변경하면 리소스 일관성이 맞고, backgroundTint 우회도 필요 없습니다.

 <androidx.appcompat.widget.AppCompatButton
     android:id="@+id/btn_logout_dialog_cancel"
     android:layout_width="match_parent"
     android:textColor="@color/white"
     android:layout_height="50dp"
-    android:background="@drawable/bg_button_report_dialog"
-    android:backgroundTint="@color/main_color"
+    android:background="@drawable/bg_button_logout_cancel"
     android:text="취소"
     android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18" />

79-89: 로그아웃 버튼 배경: 신규 리소스(bg_button_logout_confirm) 사용 + 불필요한 backgroundTint 제거

신규 stroked 배경을 사용해 네이밍/스타일을 통일하고, backgroundTint는 null로 둘 필요가 없어집니다.

 <androidx.appcompat.widget.AppCompatButton
     android:layout_marginTop="10dp"
     android:id="@+id/btn_logout_dialog_logout"
     android:layout_width="match_parent"
     android:layout_height="50dp"
     android:textColor="@color/gray6"
-    android:background="@drawable/bg_button_my_dialog_stroked"
-    android:backgroundTint="@null"
+    android:background="@drawable/bg_button_logout_confirm"
     android:text="로그아웃"
     android:layout_marginBottom="20dp"
     android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18" />

56-66: 사소한 정리: LinearLayout의 불필요 속성 제거 및 중복 여백 해소

  • app:flow_verticalGap은 Flow 헬퍼 전용 속성으로 LinearLayout에서 무의미합니다.
  • elevation="0dp"는 기본값과 동일하여 제거 가능.
  • 하단 여백은 LinearLayout paddingBottom과 마지막 버튼 marginBottom이 중복됩니다. 한쪽만 유지하세요. 여기서는 LinearLayout paddingBottom 제거를 권장합니다.
 <LinearLayout
     android:layout_width="match_parent"
-    android:elevation="0dp"
     android:layout_height="wrap_content"
     android:layout_marginTop="50dp"
-    app:flow_verticalGap="10dp"
     android:layout_marginHorizontal="20dp"
     android:orientation="vertical"
-    android:paddingBottom="20dp"
     app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintStart_toStartOf="parent"
-    app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_mention">
+    app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_mention">

36-37: 하드코딩 문자열을 string 리소스로 분리

국제화/테마 관리 차원에서 문자열을 values/strings.xml로 이동하는 것을 권장합니다.

예시:

  • strings.xml
<string name="logout_title">로그아웃 하시겠어요?</string>
<string name="logout_subtitle">언제나 기다리고 있을게요!</string>
<string name="cancel">취소</string>
<string name="logout">로그아웃</string>
  • 레이아웃 적용
android:text="@string/logout_title"
android:text="@string/logout_subtitle"
android:text="@string/cancel"
android:text="@string/logout"

원하시면 strings.xml 패치까지 만들어 드릴게요.

Also applies to: 53-53, 76-76, 87-87


12-20: 고정 폭(288dp) 사용은 작은 화면에서 레이아웃 이슈 가능성 — wrap_content/최대폭 기반으로 개선 고려

Dialog 폭을 고정 dp로 두면 폰 해상도/글자크기 확대 설정에서 잘림이 발생할 수 있습니다. maxWidth + wrap_content 조합 또는 theme Dialog 스타일을 고려해주세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 74a2dde and e286c3d.

⛔ Files ignored due to path filters (15)
  • app/src/main/res/drawable/bg_message_round_gray.png is excluded by !**/*.png
  • app/src/main/res/drawable/bg_my_version_chip.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_alarm_off.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_alarm_on.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_image_edit.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_logo1.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_message_tail.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_nickname_warning.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_profile1.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_profile2.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_profile3.png is excluded by !**/*.png
  • app/src/main/res/drawable/img_my_profile_default.png is excluded by !**/*.png
  • app/src/main/res/drawable/iv_my_favorite.png is excluded by !**/*.png
  • app/src/main/res/drawable/iv_my_history.png is excluded by !**/*.png
  • app/src/main/res/drawable/iv_my_report.png is excluded by !**/*.png
📒 Files selected for processing (32)
  • .idea/misc.xml (0 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (7 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyInquireFragment.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt (7 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyNicknameDialog.kt (1 hunks)
  • app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyProfileImageDialog.kt (1 hunks)
  • app/src/main/res/color/chip_inquire_color.xml (1 hunks)
  • app/src/main/res/drawable/bg_btn_gray_30.xml (1 hunks)
  • app/src/main/res/drawable/bg_btn_main_30.xml (1 hunks)
  • app/src/main/res/drawable/bg_button_logout_cancel.xml (1 hunks)
  • app/src/main/res/drawable/bg_button_logout_confirm.xml (1 hunks)
  • app/src/main/res/drawable/bg_dialog_my_nickname_edit.xml (1 hunks)
  • app/src/main/res/drawable/bg_my_dialog.xml (1 hunks)
  • app/src/main/res/drawable/bg_my_inquire_chip.xml (1 hunks)
  • app/src/main/res/drawable/bg_my_profile_round.xml (1 hunks)
  • app/src/main/res/drawable/bg_nickname_edittext_default.xml (1 hunks)
  • app/src/main/res/drawable/bg_nickname_edittext_fail.xml (1 hunks)
  • app/src/main/res/drawable/bg_nickname_edittext_success.xml (1 hunks)
  • app/src/main/res/drawable/bg_toast_inquire_dark.xml (1 hunks)
  • app/src/main/res/drawable/ic_dialog_close.xml (1 hunks)
  • app/src/main/res/drawable/ic_message_tail_up.xml (1 hunks)
  • app/src/main/res/drawable/ic_my_edit_pen.xml (1 hunks)
  • app/src/main/res/layout/dialog_logout.xml (1 hunks)
  • app/src/main/res/layout/dialog_my_nickname_edit.xml (1 hunks)
  • app/src/main/res/layout/dialog_my_profile_image.xml (1 hunks)
  • app/src/main/res/layout/dialog_my_withdrawal.xml (1 hunks)
  • app/src/main/res/layout/fragment_my.xml (8 hunks)
  • app/src/main/res/layout/fragment_my_inquire.xml (1 hunks)
  • app/src/main/res/layout/fragment_search_all.xml (1 hunks)
  • app/src/main/res/layout/item_my_nickname_error.xml (1 hunks)
  • app/src/main/res/layout/item_toast_inquire.xml (1 hunks)
  • app/src/main/res/navigation/nav_graph.xml (2 hunks)
💤 Files with no reviewable changes (1)
  • .idea/misc.xml
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/src/main/java/com/example/findu/presentation/ui/my/MyInquireFragment.kt (1)
app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt (1)
  • initListener (70-171)
app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyNicknameDialog.kt (1)
app/src/main/java/com/example/findu/presentation/ui/my/MyInquireFragment.kt (1)
  • dpToPx (115-117)
🪛 detekt (1.23.8)
app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt

[warning] 141-141: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (5)
app/src/main/res/drawable/bg_nickname_edittext_fail.xml (1)

1-6: LGTM — 실패 상태 강조 배경 적절합니다

화이트 배경 + red1 스트로크로 실패 상태가 명확합니다. 테마와도 충돌 없어 보입니다.

app/src/main/res/navigation/nav_graph.xml (1)

106-108: 네비게이션 액션 추가 LGTM

My문의하기 액션 추가가 자연스럽고 ID 네이밍도 일관적입니다.

app/src/main/java/com/example/findu/presentation/ui/my/MyInquireFragment.kt (1)

50-63: 유효성 검증 플로우 LGTM

타이틀/내용 공백 체크 + 최소 1개 칩 선택 검증 로직이 명확하고, 실패 시 커스텀 토스트로 피드백 제공되는 흐름 좋습니다.

app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyNicknameDialog.kt (1)

97-104: 상태 기반 버튼 활성화 로직 깔끔합니다

형식 검증 + 중복검사 완료 시에만 활성화, Tint/텍스트 컬러까지 동기화된 점 좋습니다.

app/src/main/res/layout/dialog_logout.xml (1)

39-39: 접근성: gray5/gray6 대비비 충분 확인

gray5 (#585858) vs White 대비비 ≈ 7.12:1, gray6 (#111111) vs White 대비비 ≈ 18.9:1로
둘 다 WCAG AA(4.5:1) 및 AAA(7:1) 기준을 상회합니다.
별도 수정 없이 디자인 가이드에 부합합니다.

Comment on lines +33 to +36
init {
setupListeners(context)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

PopupWindow 정리 누락 시 윈도 리크 가능 — dismiss 연동 권장

다이얼로그가 닫힐 때 떠 있는 PopupWindow를 함께 닫아 주세요.

 init {
     setupListeners(context)
     dialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
+    dialog.setOnDismissListener { errorPopup?.dismiss() }
 }

참고: Color.TRANSPARENT.toDrawable() 활용을 원치 않으면 기존 코드에 setOnDismissListener만 추가해도 됩니다.

📝 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
init {
setupListeners(context)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
init {
setupListeners(context)
dialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
dialog.setOnDismissListener { errorPopup?.dismiss() }
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyNicknameDialog.kt
around lines 33 to 36, the dialog init block sets up listeners and a transparent
background but does not ensure any active PopupWindow is dismissed when the
dialog is closed; add a reference to the PopupWindow used by this dialog (or
accept it via constructor), and register a setOnDismissListener (and/or override
onCancel/onDismiss) on the dialog to call popupWindow.dismiss() if it is
showing; ensure null checks and isShowing guard are used so dismiss is safe.

Comment on lines +62 to +69
binding.btnChangeProfileImage.setOnClickListener {
when{
selectedUri != null ->onGallerySelected(selectedUri!!)
selectedImageResId != 0 -> onDrawableSelected(selectedImageResId)
}

dismiss()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

선택 없음 상태에서도 다이얼로그가 닫힘 — 사용자 경험 개선 필요

현재 선택(URI/리소스) 없이 "변경하기"를 누르면 아무 동작 없이 닫힙니다. 선택이 없는 경우는 닫지 않도록 하거나 안내 토스트를 보여주세요.

-        binding.btnChangeProfileImage.setOnClickListener {
-            when{
-                selectedUri != null ->onGallerySelected(selectedUri!!)
-                selectedImageResId != 0 -> onDrawableSelected(selectedImageResId)
-            }
-
-            dismiss()
-        }
+        binding.btnChangeProfileImage.setOnClickListener {
+            when {
+                selectedUri != null -> {
+                    onGallerySelected(selectedUri!!)
+                    dismiss()
+                }
+                selectedImageResId != 0 -> {
+                    onDrawableSelected(selectedImageResId)
+                    dismiss()
+                }
+                else -> {
+                    android.widget.Toast
+                        .makeText(context, "이미지를 선택해 주세요.", android.widget.Toast.LENGTH_SHORT)
+                        .show()
+                }
+            }
+        }
📝 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
binding.btnChangeProfileImage.setOnClickListener {
when{
selectedUri != null ->onGallerySelected(selectedUri!!)
selectedImageResId != 0 -> onDrawableSelected(selectedImageResId)
}
dismiss()
}
binding.btnChangeProfileImage.setOnClickListener {
when {
selectedUri != null -> {
onGallerySelected(selectedUri!!)
dismiss()
}
selectedImageResId != 0 -> {
onDrawableSelected(selectedImageResId)
dismiss()
}
else -> {
android.widget.Toast
.makeText(context, "이미지를 선택해 주세요.", android.widget.Toast.LENGTH_SHORT)
.show()
}
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/my/dialog/MyProfileImageDialog.kt
around lines 62 to 69, the "Change" button currently always dismisses the dialog
even when neither selectedUri nor selectedImageResId is set; change the logic so
the dialog only dismisses after a successful selection (call onGallerySelected
or onDrawableSelected) and when no selection exists, do not dismiss but show a
user-facing hint (e.g., a Toast or Snackbar) indicating "Please select an image"
so the dialog remains open for the user to choose.

Comment on lines +131 to +134
clMyLogout.setOnClickListener(logoutClickListener)
tvMyVersionInfo.setOnClickListener(logoutClickListener)
chipMyVersion.setOnClickListener(logoutClickListener)

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

버전 정보/칩이 '로그아웃'으로 연결되는 버그 수정 필요

tvMyVersionInfo, chipMyVersion에 logoutClickListener가 연결되어 있어, 사용자가 버전 관련 UI를 탭하면 로그아웃 팝업이 떠버립니다. clMyGotoUpdate에 이미 업데이트 이동 로직이 있으므로 두 컴포넌트의 클릭 리스너는 제거하는 것이 맞습니다.

아래처럼 정리해 주세요:

-            clMyLogout.setOnClickListener(logoutClickListener)
-            tvMyVersionInfo.setOnClickListener(logoutClickListener)
-            chipMyVersion.setOnClickListener(logoutClickListener)
+            clMyLogout.setOnClickListener(logoutClickListener)
📝 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
clMyLogout.setOnClickListener(logoutClickListener)
tvMyVersionInfo.setOnClickListener(logoutClickListener)
chipMyVersion.setOnClickListener(logoutClickListener)
clMyLogout.setOnClickListener(logoutClickListener)
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt around
lines 131-134, tvMyVersionInfo and chipMyVersion are incorrectly wired to
logoutClickListener causing the logout popup when users tap version-related UI;
remove the logoutClickListener assignments for these two views (leave
clMyLogout.setOnClickListener(logoutClickListener) intact) so version taps no
longer trigger logout, and ensure navigation to updates remains handled by
clMyGotoUpdate's existing click logic.

Comment on lines +173 to +181
private fun setupVersion() = with(binding) {
val currentVersion = BuildConfig.VERSION_NAME
val latest = "1.0"
tvMyVersionInfo.text = "버전 정보 $currentVersion"

val isLatest = currentVersion.replace(".", "").toInt() >= latest.replace(".", "").toInt()
clMyVersionChip.isVisible = isLatest
clMyGotoUpdate.isVisible = !isLatest
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

버전 비교 로직이 취약합니다 (문자 포함/세그먼트 수 불일치 시 NFE 위험)

"." 제거 후 toInt 비교는 "1.0.0-alpha" 같은 버전에서 NumberFormatException을 유발할 수 있습니다. 세그먼트 단위 비교로 교체해주세요.

     private fun setupVersion() = with(binding) {
         val currentVersion = BuildConfig.VERSION_NAME
         val latest = "1.0"
         tvMyVersionInfo.text = "버전 정보 $currentVersion"
 
-        val isLatest = currentVersion.replace(".", "").toInt() >= latest.replace(".", "").toInt()
+        val isLatest = compareVersionNames(currentVersion, latest) >= 0
         clMyVersionChip.isVisible = isLatest
         clMyGotoUpdate.isVisible = !isLatest
     }

아래 유틸 함수를 본 파일 하단(클래스 안) 어딘가에 추가해 주세요:

private fun compareVersionNames(a: String, b: String): Int {
    fun parse(v: String): List<Int> =
        v.split('.', '-', '_')
            .map { seg -> seg.filter { it.isDigit() } }
            .filter { it.isNotEmpty() }
            .map { it.toIntOrNull() ?: 0 }

    val av = parse(a)
    val bv = parse(b)
    val max = maxOf(av.size, bv.size)
    for (i in 0 until max) {
        val ai = av.getOrElse(i) { 0 }
        val bi = bv.getOrElse(i) { 0 }
        if (ai != bi) return ai.compareTo(bi)
    }
    return 0
}

또한, latest를 하드코딩하기보다 원격 구성(Remote Config) 또는 백엔드에서 최신 버전을 내려받는 구조로 바꾸는 것을 권장합니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/presentation/ui/my/MyFragment.kt around
lines 173-181, the version-comparison removes dots then parses to Int which can
throw NumberFormatException for pre-release or non-numeric segments; replace
that logic by adding the provided compareVersionNames(a,b) helper as a private
function inside the class (near the file bottom) and change setupVersion() to
call compareVersionNames(currentVersion, latest) to determine isLatest (>= 0
means current is latest); additionally remove the hardcoded latest value and
wire it to a Remote Config / backend value when available.

Comment on lines 39 to 41
private val deleteInterestReportAnimalUseCase: PostInterestReportAnimalUseCase,
private val deleteReportUseCase: DeleteReportUseCase
private val deleteReportUseCase: DeleteReportUseCase,
) : ViewModel() {
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

중대: 신고 관심 해제 UseCase 타입 오배선 가능성 (삭제 경로에 Post UseCase 주입)

deleteInterestReportAnimalUseCase가 타입상 PostInterestReportAnimalUseCase로 선언되어 있습니다. 해제(isInterest=false) 분기에서 등록(Post)이 호출될 위험이 큽니다. 실제 삭제 UseCase로 타입/주입을 교체해 주세요.

-import com.example.findu.domain.usecase.interest.PostInterestReportAnimalUseCase
+import com.example.findu.domain.usecase.interest.PostInterestReportAnimalUseCase
+import com.example.findu.domain.usecase.interest.DeleteInterestReportAnimalUseCase
...
-    private val deleteInterestReportAnimalUseCase: PostInterestReportAnimalUseCase,
+    private val deleteInterestReportAnimalUseCase: DeleteInterestReportAnimalUseCase,

검증 포인트:

  • Hilt 모듈에 DeleteInterestReportAnimalUseCase 바인딩이 있는지
  • postReportInterest()의 else 분기에서 실제 삭제 UseCase가 호출되는지

다음 스크립트로 코드베이스 내 정의/사용을 빠르게 점검할 수 있어요:


🏁 Script executed:

#!/bin/bash
# DeleteInterestReportAnimalUseCase 정의/사용 검색
rg -nP -C2 '\bclass\s+DeleteInterestReportAnimalUseCase\b|\bDeleteInterestReportAnimalUseCase\b'

# ViewModel 내 선언부와 분기 확인
rg -nP -C3 'deleteInterestReportAnimalUseCase\W' app/src/main/java

Length of output: 10975


중대: MyViewModel에서 잘못된 UseCase 주입으로 관심 해제 로직 오동작 위험
MyViewModel의 deleteInterestReportAnimalUseCasePostInterestReportAnimalUseCase로 선언되어 있어, 관심 해제(isInterest=false) 분기에서 등록 로직(Post)이 잘못 호출될 수 있습니다. 아래와 같이 DeleteInterestReportAnimalUseCase로 수정해주세요.

  • 파일: app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt (39–41줄)
-import com.example.findu.domain.usecase.interest.PostInterestReportAnimalUseCase
+import com.example.findu.domain.usecase.interest.DeleteInterestReportAnimalUseCase-    private val deleteInterestReportAnimalUseCase: PostInterestReportAnimalUseCase,
+    private val deleteInterestReportAnimalUseCase: DeleteInterestReportAnimalUseCase,

• UseCaseModule.kt에 이미 provideDeleteInterestReportAnimalUseCase 바인딩이 정의되어 있으므로, 추가 모듈 작업 없이도 정상 주입됩니다.

📝 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
private val deleteInterestReportAnimalUseCase: PostInterestReportAnimalUseCase,
private val deleteReportUseCase: DeleteReportUseCase
private val deleteReportUseCase: DeleteReportUseCase,
) : ViewModel() {
// app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt
import com.example.findu.domain.usecase.interest.DeleteInterestReportAnimalUseCase
class MyViewModel(
private val deleteInterestReportAnimalUseCase: DeleteInterestReportAnimalUseCase,
private val deleteReportUseCase: DeleteReportUseCase,
) : ViewModel() {
//
}
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/presentation/ui/my/MyViewModel.kt around
lines 39–41, the constructor injects PostInterestReportAnimalUseCase into
deleteInterestReportAnimalUseCase causing the "uninterest" branch to call the
wrong (post) use case; change the injected type to
DeleteInterestReportAnimalUseCase (and adjust the parameter name/import if
needed) so that un-interest (isInterest = false) invokes the delete use case; no
module changes required because provideDeleteInterestReportAnimalUseCase is
already bound.

Comment on lines +44 to +52
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@color/gray5"
android:textAppearance="@style/TextAppearance.FindU.Body1_R_16"
app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_mention"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="언제나 기다리고 있을게요!" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

버튼 영역 앵커를 부제목(TextView) 아래로 연결해 레이아웃 안정화

현재 버튼 LinearLayout이 제목(tv_logout_dialog_mention) 기준으로만 배치되어 있어, 중간 부제목 텍스트 길이 변화 시 간격이 무너질 수 있습니다. 부제목 TextView에 id를 부여하고 그 아래로 버튼 레이아웃을 고정하세요.

-<TextView
+<TextView
+    android:id="@+id/tv_logout_dialog_submention"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_marginTop="10dp"
     android:textColor="@color/gray5"
     android:textAppearance="@style/TextAppearance.FindU.Body1_R_16"
     app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_mention"
     app:layout_constraintStart_toStartOf="parent"
     app:layout_constraintEnd_toEndOf="parent"
     android:text="언제나 기다리고 있을게요!" />
-    app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_mention">
+    app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_submention">

Also applies to: 67-67

🤖 Prompt for AI Agents
In app/src/main/res/layout/dialog_logout.xml around lines 44-52 (and similarly
at line 67), the button LinearLayout is currently anchored only to the title,
which can break spacing when the subtitle text length changes; give the subtitle
TextView a unique id (e.g., tv_logout_dialog_subtitle) and change the button
LinearLayout constraints to
app:layout_constraintTop_toBottomOf="@id/tv_logout_dialog_subtitle" so the
buttons are fixed below the subtitle, ensuring stable spacing.

Comment on lines +100 to +111
<android.widget.Button
android:id="@+id/btn_change_profile_image"
android:layout_width="288dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/bg_radius_8_g3"
android:backgroundTint="@color/main_color"
android:text="변경하기"
android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18"
android:textColor="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_profile_icon" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

버튼 레이아웃 폭/제약 재조정 필요 (현재 320dp-패딩 대비 288dp로 살짝 넘침 가능)

부모에 padding 20dp가 있어 가로 유효 폭이 280dp인데, 버튼이 288dp 고정폭이라 중앙 정렬·일관성이 흔들릴 수 있습니다. match-constraint(0dp) + 양쪽 제약으로 안정적으로 맞춰주세요.

 <android.widget.Button
     android:id="@+id/btn_change_profile_image"
-    android:layout_width="288dp"
+    android:layout_width="0dp"
     android:layout_height="wrap_content"
     android:layout_marginTop="30dp"
     android:background="@drawable/bg_radius_8_g3"
     android:backgroundTint="@color/main_color"
     android:text="변경하기"
     android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18"
     android:textColor="@color/white"
     app:layout_constraintStart_toStartOf="parent"
+    app:layout_constraintEnd_toEndOf="parent"
     app:layout_constraintTop_toBottomOf="@id/ll_profile_icon" />
📝 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
<android.widget.Button
android:id="@+id/btn_change_profile_image"
android:layout_width="288dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/bg_radius_8_g3"
android:backgroundTint="@color/main_color"
android:text="변경하기"
android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18"
android:textColor="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_profile_icon" />
<android.widget.Button
android:id="@+id/btn_change_profile_image"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/bg_radius_8_g3"
android:backgroundTint="@color/main_color"
android:text="변경하기"
android:textAppearance="@style/TextAppearance.FindU.Head3_SB_18"
android:textColor="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_profile_icon" />
🤖 Prompt for AI Agents
In app/src/main/res/layout/dialog_my_profile_image.xml around lines 100 to 111
the Button has a fixed width of 288dp which can exceed the parent’s available
width (parent padding 20dp each side => 280dp usable); change the layout to use
match-constraint (android:layout_width="0dp") and ensure it has both start and
end constraints to the parent (app:layout_constraintStart_toStartOf and
app:layout_constraintEnd_toEndOf) so the button reliably fills the available
width and remains centered and consistent across devices.

Comment on lines +22 to +25
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

HorizontalScrollView는 직계 자식이 1개만 허용됩니다 — 현재 상태는 런타임 크래시 위험

HorizontalScrollView 아래에 비어있는 ConstraintLayout(라인 22-25)과 실제 컨텐츠 ConstraintLayout(라인 26-54)이 함께 존재합니다. HSV/ScrollView는 하나의 직계 자식만 가질 수 있어, 이 상태로는 "HorizontalScrollView can host only one direct child" 예외를 유발할 수 있습니다. PR 범위(마이페이지)와도 무관한 변경으로 보이니 의도치 않은 삽입일 가능성이 높습니다. 빈 레이아웃을 제거하세요.

수정 제안(diff):

-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>

Also applies to: 26-54


아래 스크립트로 fragment_search_all.xml 안의 HorizontalScrollView 블록과 직계 자식들을 확인해볼게요.


🏁 Script executed:

#!/bin/bash
rg -nC3 '<HorizontalScrollView' -A60 app/src/main/res/layout/fragment_search_all.xml

Length of output: 2775


HorizontalScrollView 자식은 하나만 허용 — 빈 ConstraintLayout 제거 필요

HorizontalScrollView 내부에 빈 ConstraintLayout(22–25)과 실제 콘텐츠 ConstraintLayout(26–54)이 함께 존재해
런타임에 “HorizontalScrollView can host only one direct child” 예외가 발생할 수 있습니다.
다음 위치의 빈 레이아웃을 삭제해주세요.

  • app/src/main/res/layout/fragment_search_all.xml: 22–25
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
📝 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
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
🤖 Prompt for AI Agents
In app/src/main/res/layout/fragment_search_all.xml around lines 22 to 25 there
is an extra empty ConstraintLayout placed inside the HorizontalScrollView
alongside the real content, which causes the runtime "HorizontalScrollView can
host only one direct child" error; delete the empty ConstraintLayout block at
lines 22–25 so the HorizontalScrollView has only the single intended child (the
actual content ConstraintLayout at lines 26–54).

@nasohee nasohee merged commit 4f6779e into develop Sep 2, 2025
1 check passed
This was referenced Sep 21, 2025
@coderabbitai coderabbitai bot mentioned this pull request Oct 8, 2025
1 task
@coderabbitai coderabbitai bot mentioned this pull request Oct 27, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 23, 2025
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 마이페이지 UI

4 participants