Skip to content

Feature/#89 implement additional mypage#104

Open
Hongji03 wants to merge 14 commits intodevelopfrom
feature/#89-implementAdditionalMypage
Open

Feature/#89 implement additional mypage#104
Hongji03 wants to merge 14 commits intodevelopfrom
feature/#89-implementAdditionalMypage

Conversation

@Hongji03
Copy link
Copy Markdown
Collaborator

@Hongji03 Hongji03 commented Mar 26, 2026

📝 설명

해당 PR이 진행한 사항을 자세히 적어주세요.
마이페이지에서 추가된 화면들을 만들었습니다.

✔️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📎 관련 이슈 번호

해당 PR과 관련된 이슈 번호를 적어주세요.
#89

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • FAQ 섹션 추가 (검색 및 카테고리 필터링)
    • 공지사항 화면 추가 (확장 가능한 아이템)
    • AI 요약 링크 관리 기능 추가
    • 서비스 탈퇴 프로세스 개선 (이유 선택 및 약관 동의)
  • 개선 사항

    • 마이페이지 레이아웃 재구성
    • 알림 설정 기능 강화 (계층적 토글 관리)
    • 사용자 인터페이스 및 시각적 요소 개선

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

마이페이지 모듈에 새로운 Jetpack Compose UI 컴포넌트, 스크린 및 drawable 리소스를 추가합니다. AI 링크 목록, FAQ, 공지사항, 서비스 탈퇴 흐름을 구현하고, 기존 화면의 레이아웃과 네비게이션을 재설계합니다. foundation-layout 의존성을 추가합니다.

Changes

Cohort / File(s) Summary
빌드 설정 및 의존성
feature/mypage/build.gradle.kts, gradle/libs.versions.toml
Jetpack Compose foundation-layout 의존성 추가 (v1.10.5) 및 버전 카탈로그 업데이트
마이페이지 아키텍처 및 네비게이션
feature/mypage/src/main/java/com/example/mypage/MyPageApp.kt, feature/mypage/src/main/java/com/example/mypage/screen/MyPageScreen.kt
MyPageScreen 파라미터 변경 (navController, gender, jobName 제거, 네비게이션 콜백 추가) 및 레이아웃 재구성
마이페이지 상단 바
feature/mypage/src/main/java/com/example/mypage/ui/top/bar/MypageTopBar.kt
프로필 정보, 링크 통계 타일, 알림 영역을 포함하는 새로운 최상단 바 컴포넌트 추가
AI 링크 관련 컴포넌트
feature/mypage/src/main/java/com/example/mypage/component/AILinkuItem.kt, feature/mypage/src/main/java/com/example/mypage/screen/AILinkuListScreen.kt
AI 링크 아이템 카드 컴포넌트 및 카테고리 필터링, 삭제 기능이 있는 목록 화면 추가
모달 및 확인 UI 컴포넌트
feature/mypage/src/main/java/com/example/mypage/component/DeleteLinkuModal.kt, feature/mypage/src/main/java/com/example/mypage/component/LinkuItemModal.kt, feature/mypage/src/main/java/com/example/mypage/component/ServiceQuitModal.kt
링크 삭제 확인 모달, 링크 아이템 옵션 메뉴, 서비스 탈퇴 경고 메시지 업데이트 추가
정보성 UI 컴포넌트
feature/mypage/src/main/java/com/example/mypage/component/FaqItem.kt, feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt, feature/mypage/src/main/java/com/example/mypage/component/QuitReasonItem.kt
확장 가능한 FAQ 항목, 공지사항 항목, 선택 가능한 탈퇴 사유 항목 컴포넌트 추가
정보 화면
feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt, feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt
검색/필터링 기능이 있는 FAQ 화면 및 공지사항 목록 화면 추가
설정 및 탈퇴 화면
feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt, feature/mypage/src/main/java/com/example/mypage/screen/ServiceQuitScreen.kt
알람 설정 화면 헤더/레이아웃 개선, 서비스 탈퇴 화면 다단계 사유 선택 및 약관 동의 플로우 추가
Drawable 리소스
feature/mypage/src/main/res/drawable/ic_*.xml, feature/mypage/src/main/res/drawable/img_*.xml
AI 북마크, 화살표, 체크박스, 소셜 로고(카카오, 네이버), 프로필, 공지 아이콘, 빈 상태 이미지 등 25개의 벡터 드로어블 리소스 추가

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #74: MyPageApp 및 MyPageScreen 파라미터 변경사항이 겹치므로 코드 레벨에서 직접 관련됨
  • PR #30: MyPageScreen 및 AlarmSettingScreen 파일이 동일하게 수정되므로 코드 레벨에서 관련됨
  • PR #93: ServiceQuitModal 및 LogoutModal 같은 마이페이지 모달 UI 파일이 겹치므로 관련됨

Suggested reviewers

  • ugmin1030
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 'Feature/#89 implement additional mypage'로, 마이페이지 추가 기능 구현이라는 주요 변경사항을 명확하게 요약합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/#89-implementAdditionalMypage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Hongji03 Hongji03 requested review from KateteDeveloper and ugmin1030 and removed request for KateteDeveloper March 26, 2026 08:53
Copy link
Copy Markdown

@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: 13

🧹 Nitpick comments (8)
feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt (3)

44-46: 화면 회전 시 알림 설정 상태가 유실됩니다.

알림 설정 상태가 remember로 관리되어 구성 변경 시 초기화됩니다. 실제 앱에서는 이 상태를 서버나 DataStore에 저장하고 ViewModel을 통해 관리해야 합니다.

As per coding guidelines: "Focus on ViewModel and UI coupling issues in Android/Kotlin/Compose code"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt`
around lines 44 - 46, The three mutable UI states (isAlarmEnabled,
isAICurationEnabled, isNoticeEventEnabled) are kept with remember in
AlarmSettingScreen and are lost on rotation; move them into a ViewModel (e.g.,
AlarmSettingsViewModel) as persistent state holders (MutableStateFlow/LiveData
or SavedStateHandle-backed properties) and expose read-only flows/state to the
UI, update AlarmSettingScreen to collectAsState/from viewModel instead of
remember, and persist changes to DataStore or the server from the ViewModel
(onToggle handlers should call ViewModel methods that update state and write to
DataStore/server).

84-91: Modifier 순서로 인해 그림자가 잘릴 수 있습니다.

clipgraphicsLayer보다 먼저 적용되어 있습니다. graphicsLayer의 shadow가 클리핑 영역 밖에 렌더링되어야 하므로, 순서를 조정하거나 graphicsLayer 내에서 clip = false를 설정해야 합니다.

♻️ 수정 제안
         Column(modifier = Modifier
             .fillMaxWidth()
             .padding(horizontal = 20.dp)
-            .clip(RoundedCornerShape(22.dp))
-            .background(LocalColorTheme.current.white)
             .graphicsLayer {
                 shadowElevation = 12.dp.toPx()
+                shape = RoundedCornerShape(22.dp)
+                clip = true
                 ambientShadowColor = Color.Black.copy(alpha = 0.02f)
                 spotShadowColor = Color.Black.copy(alpha = 0.02f)
             }
+            .clip(RoundedCornerShape(22.dp))
+            .background(LocalColorTheme.current.white)
             .padding(horizontal = 20.dp, vertical = 18.dp)

As per coding guidelines: "Focus on Modifier order issues in Android/Kotlin/Compose code"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt`
around lines 84 - 91, The shadow is being clipped because
.clip(RoundedCornerShape(22.dp)) is applied before .graphicsLayer; either move
.clip(...) after the .graphicsLayer block or set clip=false inside the
graphicsLayer configuration so the shadowElevation in the graphicsLayer
(shadowElevation, ambientShadowColor, spotShadowColor) can draw outside the
clipped bounds; update the modifier chain around the .clip and .graphicsLayer
calls in AlarmSettingScreen.kt accordingly.

142-169: onSwitchOn 파라미터가 사용되지 않습니다.

NotificationSwitch에서 onSwitchOn 파라미터가 정의되어 있지만, 실제로 호출하는 곳에서 전달되지 않습니다. 사용하지 않는 파라미터라면 제거하는 것이 좋습니다.

♻️ 수정 제안
 `@Composable`
 fun NotificationSwitch(
     title: String,
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
-    onSwitchOn: (() -> Unit)? = null,
 ) {
     Row(
         verticalAlignment = Alignment.CenterVertically,
         modifier = Modifier.fillMaxWidth()
     ) {
         // ...
         CustomSwitch(
             checked = checked,
-            onCheckedChange = {
-                onCheckedChange(it)
-                if (it) onSwitchOn?.invoke()
-            }
+            onCheckedChange = onCheckedChange
         )
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt`
around lines 142 - 169, The NotificationSwitch function declares an unused
optional parameter onSwitchOn; remove this parameter from the function signature
and from its internal usage: delete the onSwitchOn: (() -> Unit)? = null
parameter and remove the if (it) onSwitchOn?.invoke() call inside the
CustomSwitch onCheckedChange lambda, leaving only onCheckedChange(it) so callers
don't need to supply the unused callback and behavior stays the same.
feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt (3)

3-3: 사용하지 않는 import를 제거하세요.

android.icu.number.Scale.none은 이 파일에서 사용되지 않습니다.

♻️ 수정 제안
-import android.icu.number.Scale.none
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt` at line
3, Remove the unused import android.icu.number.Scale.none from FaqScreen.kt;
locate the import statement (import android.icu.number.Scale.none) at the top of
the file and delete it (or run the IDE's optimize/organize imports) so only
necessary imports remain.

83-91: 필터링된 리스트가 매 리컴포지션마다 재계산됩니다.

filteredFaqList가 매 리컴포지션마다 다시 계산됩니다. remember와 키를 사용하여 불필요한 재계산을 방지할 수 있습니다.

♻️ 수정 제안
-    val filteredFaqList = faqList.filter { faq ->
+    val filteredFaqList = remember(selectedFilter, keyword) {
+        faqList.filter { faq ->
-        val matchesFilter = selectedFilter == "전체" || faq.category == selectedFilter
-        val matchesKeyword =
-            keyword.isBlank() ||
-                    faq.question.contains(keyword, ignoreCase = true) ||
-                    faq.answer.contains(keyword, ignoreCase = true)
-
-        matchesFilter && matchesKeyword
+            val matchesFilter = selectedFilter == "전체" || faq.category == selectedFilter
+            val matchesKeyword =
+                keyword.isBlank() ||
+                        faq.question.contains(keyword, ignoreCase = true) ||
+                        faq.answer.contains(keyword, ignoreCase = true)
+            matchesFilter && matchesKeyword
+        }
     }

As per coding guidelines: "Focus on unnecessary recomposition and performance issues in Android/Kotlin/Compose code"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt` around
lines 83 - 91, filteredFaqList is being recomputed on every recomposition; wrap
the filter logic with remember and use the relevant keys (faqList,
selectedFilter, keyword) or, better, use remember + derivedStateOf so the list
is only recalculated when those values change. Specifically, replace the direct
faqList.filter call with a remembered/derived state (e.g., use remember(faqList,
selectedFilter, keyword) { derivedStateOf { faqList.filter { ... } } } and read
the value) so filteredFaqList updates only when faqList, selectedFilter or
keyword change.

58-60: 화면 회전 시 상태가 유실됩니다.

remember를 사용하면 구성 변경(화면 회전 등) 시 keyword, isFocused, selectedFilter 상태가 초기화됩니다. 사용자 경험 개선을 위해 rememberSaveable 사용을 고려해 주세요.

♻️ 수정 제안
-    var keyword by remember { mutableStateOf("") }
-    var isFocused by remember { mutableStateOf(false) }
-    var selectedFilter by remember { mutableStateOf("전체") }
+    var keyword by rememberSaveable { mutableStateOf("") }
+    var isFocused by remember { mutableStateOf(false) } // 포커스는 유지할 필요 없음
+    var selectedFilter by rememberSaveable { mutableStateOf("전체") }

As per coding guidelines: "Focus on state management (state hoisting, remember vs rememberSaveable) in Android/Kotlin/Compose code"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt` around
lines 58 - 60, The state variables keyword, isFocused and selectedFilter are
using remember and will be lost on configuration changes; change their
declarations to use rememberSaveable instead (e.g., replace usages of remember {
mutableStateOf(...) } with rememberSaveable { mutableStateOf(...) }) so keyword
(String), isFocused (Boolean) and selectedFilter (String) survive screen
rotation; ensure any non-Parcelable/custom types are converted to savable types
or provide a saver if needed.
feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt (1)

37-73: 공지 리스트 상수는 Composable 바깥(또는 remember)으로 이동해 주세요.

Line [37]~Line [73]의 큰 리스트가 재구성 때마다 다시 생성됩니다. 정적 데이터면 파일 레벨 상수로 빼는 편이 안전합니다.

As per coding guidelines **/*.{kt,kts}: Focus on recomposition cost in Android/Kotlin/Compose code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt`
around lines 37 - 73, The large immutable list assigned to the variable notices
is being recreated on every recomposition; move this static data out of the
Composable scope by promoting notices to a file-level constant (e.g., private
val NOTICES) or wrap it in remember { ... } so it is only created once; update
any references to use the new NOTICES constant (or the remembered value) to
avoid repeated allocations in NoticeScreen/Composable code.
feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt (1)

47-48: 아이템 내부 remember 상태는 리스트 문맥에서 유지성 이슈가 생길 수 있습니다.

확장 상태를 NoticeItem 내부 remember로 들고 있으면 리스트 재배치/재생성 시 상태 유실 가능성이 있습니다. 부모(NoticeScreen)에서 key 기반으로 상태를 호이스팅하거나 rememberSaveable 전략을 검토해 주세요.

As per coding guidelines **/*.{kt,kts}: Focus on state management (state hoisting, remember vs rememberSaveable) in Android/Kotlin/Compose code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt`
around lines 47 - 48, The expanded and hasBeenExpanded states are stored inside
NoticeItem using remember, which can be lost during list
reordering/recomposition; hoist these states up to the parent (e.g.,
NoticeScreen) keyed by each item's unique id and pass them into NoticeItem as
parameters (e.g., expanded: Boolean, onExpandedChange: (Boolean) -> Unit) or, if
per-item persistence across process death is required, replace remember with
rememberSaveable inside the parent keyed by item id; update NoticeItem to remove
internal remember/hasBeenExpanded and rely on the provided props (and callbacks)
so state survives list changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@feature/mypage/build.gradle.kts`:
- Line 64: The dependency implementation(libs.androidx.foundation.layout) is
pulling an explicit version from the version catalog which conflicts with the
Compose BOM; update the catalog and/or build file so BOM manages foundation
layout: remove or change the explicit catalog entry `androidx-foundation-layout`
(version 1.10.5) and instead rely on the BOM-managed coordinate (e.g.
`androidx-compose-foundation-layout` / `androidx.compose.foundation`) so
implementation(libs.androidx.foundation.layout) is resolved by the BOM, or if
you must keep the explicit version, document the reason for overriding the BOM
in the repo README and rename the catalog key to make the override intent
explicit; ensure references to `libs.androidx.foundation.layout` and any catalog
keys are adjusted accordingly.

In `@feature/mypage/src/main/java/com/example/mypage/component/AILinkuItem.kt`:
- Around line 41-48: The AILinkuItem composable currently holds its own
isMenuVisible state causing multiple menus to open and preventing coordinated
cleanup; hoist this state into the parent (AILinkuListScreen) by replacing
per-item isMenuVisible with a single activeItemId (or similar) in the parent and
expose an expanded: Boolean and onExpandedChange(itemId: String?) pair to
AILinkuItem; update AILinkuItem signature to accept expanded and
onExpandedChange (and use item id passed from parent) and remove remember {
mutableStateOf(false) } so the parent manages which item's menu is shown and can
clear it for overlays like delete dialogs.

In `@feature/mypage/src/main/java/com/example/mypage/component/FaqItem.kt`:
- Around line 93-105: The Image's clickable area is only 6.dp because
noRippleClickable is applied after .height(6.dp); move the clickable to a larger
touch target by either applying noRippleClickable to the parent Row (make the
Row's Modifier handle clicks and toggle expanded) or change the Image's modifier
to ensure a 48.dp+ touch target (e.g. replace .height(6.dp) with a larger
container like .size(48.dp) or use Compose's minimumTouchTargetSize() / sizeIn
to enforce >=48.dp) while keeping the visual icon small (use .padding or
contentAlignment inside the larger container) so noRippleClickable, Image,
expanded and rotation behavior remain correct.

In `@feature/mypage/src/main/java/com/example/mypage/component/LinkuItemModal.kt`:
- Around line 32-36: In the graphicsLayer block inside LinkuItemModal, the
symbol shape is undefined; fix by declaring a Shape in scope (e.g., val shape =
RoundedCornerShape(8.dp) or use an existing theme shape like
MaterialTheme.shapes.medium) before the graphicsLayer call and then keep
this.shape = shape (or replace the assignment with this.shape =
MaterialTheme.shapes.medium). Ensure the appropriate import (RoundedCornerShape
and androidx.compose.ui.unit.dp) and that the declared variable name matches the
one used in graphicsLayer.

In `@feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt`:
- Around line 115-130: Move the noRippleClickable modifier from the Image to the
whole header Row (wrap the Row that contains the title and the Image with
Modifier.noRippleClickable) so the entire header is the touch target; keep the
same click lambda (toggle expanded and set hasBeenExpanded = true when expanded)
and remove noRippleClickable from the Image modifier. Leave the Image's
graphicsLayer with rotation/TransformOrigin.Center intact but add a meaningful
contentDescription on the Image (e.g., use a string resource or conditional text
like "Collapse" when expanded else "Expand") so screen readers describe the
action. Ensure you reference and update the same symbols: noRippleClickable,
expanded, hasBeenExpanded, Image, rotation, and TransformOrigin.Center.

In `@feature/mypage/src/main/java/com/example/mypage/component/QuitReasonItem.kt`:
- Around line 29-33: The Row that represents the radio option should be made
accessible and ensure a 48dp minimum touch target: replace the current
Modifier.clickable + fillMaxWidth with a Modifier.fillMaxWidth().heightIn(min =
48.dp).selectable(selected = isSelected, onClick = onClick, role =
Role.RadioButton) (use the appropriate selected boolean from QuitReasonItem and
keep onClick) so screen readers receive the radio role and the touch area meets
the minimum; add the required imports for Role and heightIn/dp if missing.

In
`@feature/mypage/src/main/java/com/example/mypage/component/ServiceQuitModal.kt`:
- Line 72: The modal text in ServiceQuitModal.kt ("계정을 탈퇴하면 14일 이후\n회원님의 모든 활동
정보가 삭제됩니다.") conflicts with the messaging in ServiceQuitScreen.kt (the deletion
notice around the account quit explanation), so update both places to present a
single, consistent message: choose whether to state a 14-day grace period or to
explicitly separate items deleted immediately vs after 14 days, then change the
string in ServiceQuitModal (text property) and the explanatory string(s) in
ServiceQuitScreen (the quit guidance block) to match exactly; ensure both UI
components use the same wording or shared constant/resource (e.g., consolidate
into a shared string resource or constant) so future edits remain consistent.

In `@feature/mypage/src/main/java/com/example/mypage/screen/MyPageScreen.kt`:
- Around line 77-85: The MypageTopBar call in MyPageScreen currently hardcodes
isNoticeExist = false and socialLoginType = "kakao", which prevents real account
state from being shown; update MyPageScreen to accept or derive these values
from the screen/state (ViewModel or parent caller) and pass them through to
MypageTopBar (use the existing isNoticeExist and socialLoginType parameters
instead of literals), e.g., obtain isNoticeExist and socialLoginType from your
ViewModel/state in MyPageScreen and forward them into the MypageTopBar
invocation so the unread-notification badge and social logo reflect the actual
user state.
- Around line 74-76: The top-level Column in MyPageScreen (the one using
Modifier.fillMaxSize()) was made non-scrollable causing the bottom "회원탈퇴/로그아웃"
card to be clipped on small devices; restore scrolling by either replacing that
Column with a LazyColumn or by adding vertical scrolling: e.g., add
Modifier.verticalScroll(rememberScrollState()) to the Column's modifier (and
import rememberScrollState/verticalScroll), and apply the same change to the
other Column instance referenced around lines 92-96 so the bottom card can be
scrolled into view.
- Around line 65-69: The MyPageScreen callback parameters
onNavigateAlarmSetting, onNavigateQuit, onNavigateFAQ, onNavigateNotice, and
onNavigateTerms currently have no-op default values which hide missing wiring;
remove the default empty lambda initializers from MyPageScreen's parameter list
so callers must supply these handlers, then update the caller MyPageApp (the
site that constructs MyPageScreen) to pass the appropriate navigation lambdas
for each of onNavigateAlarmSetting, onNavigateQuit, onNavigateFAQ,
onNavigateNotice and onNavigateTerms.

In `@feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt`:
- Around line 86-93: The back-button Image in NoticeScreen.kt uses
contentDescription = null and a tiny touch area; update the Image
(painterResource(R.drawable.ic_back)) so it provides an accessible label (e.g.,
contentDescription = "Back" or string resource) and increase its touch target to
at least 48.dp x 48.dp by wrapping it in a larger clickable modifier or
container (e.g., Box or Modifier.size(48.dp).clickable {
navController.popBackStack() } with proper alignment) so the click handler
navController.popBackStack() stays attached to the larger hit area; ensure you
use a localized string resource for the description rather than a hardcoded
literal.
- Around line 34-36: NoticeScreen is not registered in navigation and is
unreachable; import NoticeScreen into MyPageApp, add a NavHost entry
composable("notice") { NoticeScreen(navController = navController) }, and when
calling MyPageScreen pass onNavigateNotice = { navController.navigate("notice")
} instead of leaving the default empty callback. Also prevent recreating the
notices list every recomposition by moving the val notices = listOf(...) outside
the composable or wrapping it with remember (refer to the notices variable in
NoticeScreen), and improve the back icon accessibility by providing a non-null
contentDescription (e.g., "뒤로가기") and increasing its touch target to at least
44.dp (replace the current width(10.dp) with a Modifier ensuring minimum touch
size).

In `@feature/mypage/src/main/java/com/example/mypage/screen/ServiceQuitScreen.kt`:
- Around line 58-70: The confirm flow currently sends only reasonText and lets
predefined selections yield an empty string, and allows "기타" to submit when
blank; update state so selectedReason and reasonText are saved with
rememberSaveable (instead of remember), compute a finalReason value where if
selectedReason == "기타" then finalReason = reasonText.trim() (and require it
non-blank), else finalReason = selectedReason, and use that finalReason in the
confirm callback; also update isEtcSelected and isQuitEnabled to reflect this
logic (isEtcSelected = selectedReason == "기타", isQuitEnabled = (selectedReason
!= null && (selectedReason != "기타" || reasonText.isNotBlank()) &&
isAgreeChecked)) and ensure the confirm handler sends finalReason.

---

Nitpick comments:
In `@feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt`:
- Around line 47-48: The expanded and hasBeenExpanded states are stored inside
NoticeItem using remember, which can be lost during list
reordering/recomposition; hoist these states up to the parent (e.g.,
NoticeScreen) keyed by each item's unique id and pass them into NoticeItem as
parameters (e.g., expanded: Boolean, onExpandedChange: (Boolean) -> Unit) or, if
per-item persistence across process death is required, replace remember with
rememberSaveable inside the parent keyed by item id; update NoticeItem to remove
internal remember/hasBeenExpanded and rely on the provided props (and callbacks)
so state survives list changes.

In
`@feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt`:
- Around line 44-46: The three mutable UI states (isAlarmEnabled,
isAICurationEnabled, isNoticeEventEnabled) are kept with remember in
AlarmSettingScreen and are lost on rotation; move them into a ViewModel (e.g.,
AlarmSettingsViewModel) as persistent state holders (MutableStateFlow/LiveData
or SavedStateHandle-backed properties) and expose read-only flows/state to the
UI, update AlarmSettingScreen to collectAsState/from viewModel instead of
remember, and persist changes to DataStore or the server from the ViewModel
(onToggle handlers should call ViewModel methods that update state and write to
DataStore/server).
- Around line 84-91: The shadow is being clipped because
.clip(RoundedCornerShape(22.dp)) is applied before .graphicsLayer; either move
.clip(...) after the .graphicsLayer block or set clip=false inside the
graphicsLayer configuration so the shadowElevation in the graphicsLayer
(shadowElevation, ambientShadowColor, spotShadowColor) can draw outside the
clipped bounds; update the modifier chain around the .clip and .graphicsLayer
calls in AlarmSettingScreen.kt accordingly.
- Around line 142-169: The NotificationSwitch function declares an unused
optional parameter onSwitchOn; remove this parameter from the function signature
and from its internal usage: delete the onSwitchOn: (() -> Unit)? = null
parameter and remove the if (it) onSwitchOn?.invoke() call inside the
CustomSwitch onCheckedChange lambda, leaving only onCheckedChange(it) so callers
don't need to supply the unused callback and behavior stays the same.

In `@feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt`:
- Line 3: Remove the unused import android.icu.number.Scale.none from
FaqScreen.kt; locate the import statement (import android.icu.number.Scale.none)
at the top of the file and delete it (or run the IDE's optimize/organize
imports) so only necessary imports remain.
- Around line 83-91: filteredFaqList is being recomputed on every recomposition;
wrap the filter logic with remember and use the relevant keys (faqList,
selectedFilter, keyword) or, better, use remember + derivedStateOf so the list
is only recalculated when those values change. Specifically, replace the direct
faqList.filter call with a remembered/derived state (e.g., use remember(faqList,
selectedFilter, keyword) { derivedStateOf { faqList.filter { ... } } } and read
the value) so filteredFaqList updates only when faqList, selectedFilter or
keyword change.
- Around line 58-60: The state variables keyword, isFocused and selectedFilter
are using remember and will be lost on configuration changes; change their
declarations to use rememberSaveable instead (e.g., replace usages of remember {
mutableStateOf(...) } with rememberSaveable { mutableStateOf(...) }) so keyword
(String), isFocused (Boolean) and selectedFilter (String) survive screen
rotation; ensure any non-Parcelable/custom types are converted to savable types
or provide a saver if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt`:
- Around line 37-73: The large immutable list assigned to the variable notices
is being recreated on every recomposition; move this static data out of the
Composable scope by promoting notices to a file-level constant (e.g., private
val NOTICES) or wrap it in remember { ... } so it is only created once; update
any references to use the new NOTICES constant (or the remembered value) to
avoid repeated allocations in NoticeScreen/Composable code.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f60750c-cac7-4a96-ae7a-86f0fb5c21f8

📥 Commits

Reviewing files that changed from the base of the PR and between 3a999d5 and 7a34b3b.

⛔ Files ignored due to path filters (1)
  • feature/mypage/src/main/res/drawable/ic_google_logo.png is excluded by !**/*.png
📒 Files selected for processing (32)
  • feature/mypage/build.gradle.kts
  • feature/mypage/src/main/java/com/example/mypage/MyPageApp.kt
  • feature/mypage/src/main/java/com/example/mypage/component/AILinkuItem.kt
  • feature/mypage/src/main/java/com/example/mypage/component/DeleteLinkuModal.kt
  • feature/mypage/src/main/java/com/example/mypage/component/FaqItem.kt
  • feature/mypage/src/main/java/com/example/mypage/component/LinkuItemModal.kt
  • feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt
  • feature/mypage/src/main/java/com/example/mypage/component/QuitReasonItem.kt
  • feature/mypage/src/main/java/com/example/mypage/component/ServiceQuitModal.kt
  • feature/mypage/src/main/java/com/example/mypage/screen/AILinkuListScreen.kt
  • feature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.kt
  • feature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.kt
  • feature/mypage/src/main/java/com/example/mypage/screen/MyPageScreen.kt
  • feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt
  • feature/mypage/src/main/java/com/example/mypage/screen/ServiceQuitScreen.kt
  • feature/mypage/src/main/java/com/example/mypage/ui/top/bar/MypageTopBar.kt
  • feature/mypage/src/main/res/drawable/ic_ai_bookmark.xml
  • feature/mypage/src/main/res/drawable/ic_arrow_down_gray500.xml
  • feature/mypage/src/main/res/drawable/ic_checkbox_checked.xml
  • feature/mypage/src/main/res/drawable/ic_checkbox_unchecked.xml
  • feature/mypage/src/main/res/drawable/ic_kakao_logo.xml
  • feature/mypage/src/main/res/drawable/ic_logo_gray.xml
  • feature/mypage/src/main/res/drawable/ic_more.xml
  • feature/mypage/src/main/res/drawable/ic_naver_logo.xml
  • feature/mypage/src/main/res/drawable/ic_notice.xml
  • feature/mypage/src/main/res/drawable/ic_notice_gray.xml
  • feature/mypage/src/main/res/drawable/ic_profile_default.xml
  • feature/mypage/src/main/res/drawable/ic_q.xml
  • feature/mypage/src/main/res/drawable/ic_sparkle.xml
  • feature/mypage/src/main/res/drawable/img_empty_ailinku.xml
  • feature/mypage/src/main/res/drawable/img_link_default.xml
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (1)
  • feature/mypage/src/main/java/com/example/mypage/MyPageApp.kt

implementation(libs.androidx.material3)
// implementation(libs.androidx.navigation.compose.jvmstubs)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.foundation.layout)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: foundation-layout 패키지의 실제 사용처 검색

# androidx.compose.foundation.layout 패키지 임포트 검색
rg -n --type=kt 'import androidx\.compose\.foundation\.layout\.' -C2

# FlowRow, FlowColumn 등 foundation-layout 전용 컴포넌트 사용 확인
rg -n --type=kt -P '\b(FlowRow|FlowColumn)\s*\(' -C2

Repository: LinkYou-2025/LinkU_Android

Length of output: 123


🏁 Script executed:

#!/bin/bash
# First, find the build.gradle.kts file in question
find . -path "*/feature/mypage/build.gradle.kts" -type f

Repository: LinkYou-2025/LinkU_Android

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Search for foundation.layout imports using correct syntax
rg 'import androidx\.compose\.foundation\.layout\.' --type gradle,kotlin,kotlin-script -C 2

Repository: LinkYou-2025/LinkU_Android

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Search for FlowRow, FlowColumn and other foundation-layout components
rg '\b(FlowRow|FlowColumn|spacedBy|fillMaxHeight|fillMaxWidth)\s*\(' -C 2

Repository: LinkYou-2025/LinkU_Android

Length of output: 50383


🏁 Script executed:

#!/bin/bash
# Read the build.gradle.kts file to see BOM and dependencies
cat -n feature/mypage/build.gradle.kts | head -80

Repository: LinkYou-2025/LinkU_Android

Length of output: 2972


🏁 Script executed:

#!/bin/bash
# Search for FlowRow and FlowColumn usage in mypage feature
rg 'FlowRow|FlowColumn' feature/mypage/ -C 2

Repository: LinkYou-2025/LinkU_Android

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Find the version catalog file
find . -name "*.toml" -o -name "*.gradle" | grep -E "(libs|versions|catalog)" | head -20

Repository: LinkYou-2025/LinkU_Android

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Search for imports of androidx.compose.foundation.layout in mypage feature
rg 'import androidx\.compose\.foundation\.layout' feature/mypage/ -C 1

Repository: LinkYou-2025/LinkU_Android

Length of output: 22942


🏁 Script executed:

#!/bin/bash
# Check what foundation-layout includes and foundation includes in the version catalog
rg 'androidx.compose.foundation' gradle/ -A 1 -B 1

Repository: LinkYou-2025/LinkU_Android

Length of output: 1142


🏁 Script executed:

#!/bin/bash
# Read the libs.versions.toml file to see version definitions
cat -n gradle/libs.versions.toml | grep -A 5 -B 5 "foundationLayout\|foundation"

Repository: LinkYou-2025/LinkU_Android

Length of output: 2433


🏁 Script executed:

#!/bin/bash
# Check which foundation-layout alias is actually defined in the toml
rg 'androidx-foundation-layout|androidx-compose-foundation-layout' gradle/libs.versions.toml -A 1 -B 1

Repository: LinkYou-2025/LinkU_Android

Length of output: 576


foundation-layout 의존성의 버전 관리 방식을 정규화하세요.

Compose BOM(57줄)을 사용하고 있으면서 foundation-layout만 명시적 버전(1.10.5)으로 지정되어 있습니다. 현재 버전 카탈로그에는 두 가지 정의가 있어 혼동을 야기합니다:

  • androidx-compose-foundation-layout: 버전 없음 (BOM 관리)
  • androidx-foundation-layout: 명시적 버전 1.10.5

build.gradle.kts에서 사용 중인 libs.androidx.foundation.layout은 명시적 버전 정의를 참조하므로 BOM과 버전 충돌 위험이 있습니다. 사용 중인 컴포넌트(Box, Column, Row, Spacer 등)들은 BOM이 관리하는 androidx.compose.foundation에 이미 포함되어 있습니다.

다음을 수행하세요:

  • 버전 카탈로그 정의를 정리하고 BOM이 버전을 관리하도록 수정
  • 또는 명시적 이유가 있다면 그 이유를 문서화
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/build.gradle.kts` at line 64, The dependency
implementation(libs.androidx.foundation.layout) is pulling an explicit version
from the version catalog which conflicts with the Compose BOM; update the
catalog and/or build file so BOM manages foundation layout: remove or change the
explicit catalog entry `androidx-foundation-layout` (version 1.10.5) and instead
rely on the BOM-managed coordinate (e.g. `androidx-compose-foundation-layout` /
`androidx.compose.foundation`) so
implementation(libs.androidx.foundation.layout) is resolved by the BOM, or if
you must keep the explicit version, document the reason for overriding the BOM
in the repo README and rename the catalog key to make the override intent
explicit; ensure references to `libs.androidx.foundation.layout` and any catalog
keys are adjusted accordingly.

Comment on lines +41 to +48
fun AILinkuItem(
linkTitle: String,
tags: List<String>,
domainImage: Int? = null,
domainName: String? = null,
onClickDelete: () -> Unit = {}
) {
var isMenuVisible by remember { mutableStateOf(false) }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

더보기 메뉴 상태는 상위에서 단일하게 관리해 주세요.

isMenuVisible를 row 내부 remember로 두면 여러 항목의 메뉴를 동시에 열 수 있고, 리스트 화면이 삭제 다이얼로그 같은 다른 오버레이를 띄울 때도 이 상태를 함께 정리할 수 없습니다. AILinkuListScreen에서 active item id를 한 군데서 들고 expanded/onExpandedChange 형태로 내려주는 편이 안전합니다. As per coding guidelines **/*.{kt,kts}: Focus on state management (state hoisting, remember vs rememberSaveable) in Android/Kotlin/Compose code.

Also applies to: 153-179

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/component/AILinkuItem.kt`
around lines 41 - 48, The AILinkuItem composable currently holds its own
isMenuVisible state causing multiple menus to open and preventing coordinated
cleanup; hoist this state into the parent (AILinkuListScreen) by replacing
per-item isMenuVisible with a single activeItemId (or similar) in the parent and
expose an expanded: Boolean and onExpandedChange(itemId: String?) pair to
AILinkuItem; update AILinkuItem signature to accept expanded and
onExpandedChange (and use item id passed from parent) and remove remember {
mutableStateOf(false) } so the parent manages which item's menu is shown and can
clear it for overlays like delete dialogs.

Comment on lines +93 to +105
Image(
painter = painterResource(R.drawable.ic_arrow_down_gray500),
contentDescription = null,
modifier = Modifier
.height(6.dp)
.noRippleClickable {
expanded = !expanded
}
.graphicsLayer {
rotationZ = rotation
transformOrigin = TransformOrigin.Center
}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

터치 영역이 너무 작습니다.

화살표 아이콘의 높이가 6.dp로 설정되어 있어 터치 타겟이 매우 작습니다. Android 접근성 가이드라인에서는 최소 48dp의 터치 영역을 권장합니다. 현재 구조에서는 height 다음에 noRippleClickable이 적용되어 클릭 가능 영역이 6.dp로 제한됩니다.

🔧 터치 영역 확장 제안
             Image(
                 painter = painterResource(R.drawable.ic_arrow_down_gray500),
                 contentDescription = null,
                 modifier = Modifier
-                    .height(6.dp)
-                    .noRippleClickable {
-                        expanded = !expanded
-                    }
+                    .size(48.dp)
+                    .noRippleClickable { expanded = !expanded }
                     .graphicsLayer {
                         rotationZ = rotation
                         transformOrigin = TransformOrigin.Center
                     }
+                    .padding(21.dp) // 시각적 크기를 6dp로 유지
             )

또는 전체 Row를 클릭 가능하게 만드는 것도 좋은 대안입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/component/FaqItem.kt` around
lines 93 - 105, The Image's clickable area is only 6.dp because
noRippleClickable is applied after .height(6.dp); move the clickable to a larger
touch target by either applying noRippleClickable to the parent Row (make the
Row's Modifier handle clicks and toggle expanded) or change the Image's modifier
to ensure a 48.dp+ touch target (e.g. replace .height(6.dp) with a larger
container like .size(48.dp) or use Compose's minimumTouchTargetSize() / sizeIn
to enforce >=48.dp) while keeping the visual icon small (use .padding or
contentAlignment inside the larger container) so noRippleClickable, Image,
expanded and rotation behavior remain correct.

Comment on lines +32 to +36
.graphicsLayer {
shadowElevation = 10.dp.toPx()
this.shape = shape
clip = true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

shape 변수가 정의되지 않았습니다.

graphicsLayer 블록 내에서 this.shape = shape를 사용하고 있지만, shape 변수가 스코프 내에 정의되어 있지 않습니다. 이로 인해 컴파일 오류가 발생하거나 예상치 못한 동작이 발생할 수 있습니다.

🐛 수정 제안
+    val cardShape = RoundedCornerShape(14.dp)
+
     Column(
         modifier = Modifier
             .width(120.dp)
             .graphicsLayer {
                 shadowElevation = 10.dp.toPx()
-                this.shape = shape
+                this.shape = cardShape
                 clip = true
             }
-            .clip(RoundedCornerShape(14.dp))
+            .clip(cardShape)
             .background(LocalColorTheme.current.white)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/component/LinkuItemModal.kt`
around lines 32 - 36, In the graphicsLayer block inside LinkuItemModal, the
symbol shape is undefined; fix by declaring a Shape in scope (e.g., val shape =
RoundedCornerShape(8.dp) or use an existing theme shape like
MaterialTheme.shapes.medium) before the graphicsLayer call and then keep
this.shape = shape (or replace the assignment with this.shape =
MaterialTheme.shapes.medium). Ensure the appropriate import (RoundedCornerShape
and androidx.compose.ui.unit.dp) and that the declared variable name matches the
one used in graphicsLayer.

Comment on lines +115 to +130
Image(
painter = painterResource(R.drawable.ic_arrow_down_gray500),
contentDescription = null,
modifier = Modifier
.height(6.dp)
.noRippleClickable {
expanded = !expanded
if (expanded) {
hasBeenExpanded = true
}
}
.graphicsLayer {
rotationZ = rotation
transformOrigin = TransformOrigin.Center
}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "NoticeItem.kt" | head -20

Repository: LinkYou-2025/LinkU_Android

Length of output: 143


🏁 Script executed:

wc -l ./feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 147


🏁 Script executed:

cat -n ./feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 7545


카드 헤더 전체를 클릭 가능 영역으로 변경하고 접근성 설명을 추가해야 합니다.

현재 확장/축소 토글이 6dp 높이의 화살표 아이콘에만 걸려 있어 터치 타겟으로 부적절합니다. 전체 Row 헤더(제목, 아이콘 포함)를 noRippleClickable로 감싸고, 화살표의 contentDescription을 추가하여 스크린 리더가 동작을 설명할 수 있도록 수정하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.kt`
around lines 115 - 130, Move the noRippleClickable modifier from the Image to
the whole header Row (wrap the Row that contains the title and the Image with
Modifier.noRippleClickable) so the entire header is the touch target; keep the
same click lambda (toggle expanded and set hasBeenExpanded = true when expanded)
and remove noRippleClickable from the Image modifier. Leave the Image's
graphicsLayer with rotation/TransformOrigin.Center intact but add a meaningful
contentDescription on the Image (e.g., use a string resource or conditional text
like "Collapse" when expanded else "Expand") so screen readers describe the
action. Ensure you reference and update the same symbols: noRippleClickable,
expanded, hasBeenExpanded, Image, rotation, and TransformOrigin.Center.

Comment on lines +74 to +76
Column(
modifier = Modifier.fillMaxSize()
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

스크롤을 제거하면서 하단 메뉴가 기기 높이에 따라 잘립니다.

이제 전체 화면이 고정 Column이라서 top bar + 세 섹션 + 큰 하단 spacer가 한 화면 안에 모두 들어와야 합니다. 작은 기기나 큰 글꼴에서는 회원탈퇴/로그아웃 카드가 화면 밖으로 밀려 접근 불가해집니다. 최소한 카드 영역은 verticalScroll이나 LazyColumn으로 유지해야 합니다.

수정 예시
-        Column(
-            modifier = Modifier
-                .fillMaxWidth()
-                .padding(horizontal = 20.dp)
-        ) {
+        Column(
+            modifier = Modifier
+                .weight(1f)
+                .verticalScroll(rememberScrollState())
+                .fillMaxWidth()
+                .padding(horizontal = 20.dp)
+        ) {

Also applies to: 92-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/MyPageScreen.kt`
around lines 74 - 76, The top-level Column in MyPageScreen (the one using
Modifier.fillMaxSize()) was made non-scrollable causing the bottom "회원탈퇴/로그아웃"
card to be clipped on small devices; restore scrolling by either replacing that
Column with a LazyColumn or by adding vertical scrolling: e.g., add
Modifier.verticalScroll(rememberScrollState()) to the Column's modifier (and
import rememberScrollState/verticalScroll), and apply the same change to the
other Column instance referenced around lines 92-96 so the bottom card can be
scrolled into view.

Comment on lines +77 to +85
MypageTopBar(
isNoticeExist = false, // TODO: 실제 알림 여부 연결
nickname = nickname,
email = email,
gender = gender,
jobName = jobName,
myLinku = myLinku,
myFolder = myFolder,
myAiLinku = myAiLinku
myAiLinku = myAiLinku,
socialLoginType = "kakao", // TODO: 실제 소셜 로그인 타입 연결
onAlarmClick = { onNavigateAlarm() },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

상단바 상태를 상수로 고정하면 실제 계정 정보가 반영되지 않습니다.

MypageTopBar는 이미 isNoticeExistsocialLoginType를 받도록 분리돼 있는데, 여기서 false"kakao"로 고정하면 읽지 않은 알림 배지와 소셜 로고가 실제 사용자 상태와 다르게 표시됩니다. 이 값도 화면 state나 상위 caller에서 받아 그대로 전달해 주세요. Based on learnings Applies to **/*.{kt,kts} : Focus on ViewModel and UI coupling issues in Android/Kotlin/Compose code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/MyPageScreen.kt`
around lines 77 - 85, The MypageTopBar call in MyPageScreen currently hardcodes
isNoticeExist = false and socialLoginType = "kakao", which prevents real account
state from being shown; update MyPageScreen to accept or derive these values
from the screen/state (ViewModel or parent caller) and pass them through to
MypageTopBar (use the existing isNoticeExist and socialLoginType parameters
instead of literals), e.g., obtain isNoticeExist and socialLoginType from your
ViewModel/state in MyPageScreen and forward them into the MypageTopBar
invocation so the unread-notification badge and social logo reflect the actual
user state.

Comment on lines +34 to +36
fun NoticeScreen(
navController: NavController
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# notice 라우트 등록 및 콜백 연결 여부 확인
rg -nP --type=kt -C3 'composable\("notice"\)|onNavigateNotice|navigate\("notice"\)'

Repository: LinkYou-2025/LinkU_Android

Length of output: 96


🏁 Script executed:

rg -nP -C3 'composable\("notice"\)|onNavigateNotice|navigate\("notice"\)' --glob='*.kt'

Repository: LinkYou-2025/LinkU_Android

Length of output: 2331


🏁 Script executed:

fd -e kt MyPageApp.kt MyPageScreen.kt | head -20

Repository: LinkYou-2025/LinkU_Android

Length of output: 172


🏁 Script executed:

find . -name 'MyPageApp.kt' -o -name 'MyPageScreen.kt' 2>/dev/null

Repository: LinkYou-2025/LinkU_Android

Length of output: 205


🏁 Script executed:

cat ./feature/mypage/src/main/java/com/example/mypage/MyPageApp.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 10948


🏁 Script executed:

cat ./feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt

Repository: LinkYou-2025/LinkU_Android

Length of output: 4121


NoticeScreen은 네비게이션에 완전히 미등록되어 화면 접근이 불가능합니다.

feature/mypage/src/main/java/com/example/mypage/MyPageApp.kt의 NavHost에 composable("notice") 라우트가 없고, NoticeScreen이 import되지 않았습니다. 또한 MyPageScreen의 onNavigateNotice 콜백을 MyPageApp에서 전달하지 않아(기본값 empty {} 사용) 버튼 클릭 시 어디로도 이동하지 않습니다. 기능을 활성화하려면:

  1. NoticeScreen을 MyPageApp.kt에 import
  2. NavHost에 composable("notice") { NoticeScreen(navController = navController) } 추가
  3. MyPageScreen 호출 시 onNavigateNotice = { navController.navigate("notice") } 전달

notices 리스트가 매 recomposition마다 재생성됩니다 (lines 37-79).
val notices = listOf(...) 을 composable 바깥으로 이동하거나 remember로 메모이제이션하여 불필요한 객체 생성 비용을 제거하세요.

back 아이콘의 접근성과 터치 영역을 개선하세요 (lines 88-92).
contentDescription = null 대신 적절한 설명("뒤로가기" 등)을 제공하고, width(10.dp)를 최소 44dp 이상으로 확대하여 터치 타겟을 충분히 확보하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt`
around lines 34 - 36, NoticeScreen is not registered in navigation and is
unreachable; import NoticeScreen into MyPageApp, add a NavHost entry
composable("notice") { NoticeScreen(navController = navController) }, and when
calling MyPageScreen pass onNavigateNotice = { navController.navigate("notice")
} instead of leaving the default empty callback. Also prevent recreating the
notices list every recomposition by moving the val notices = listOf(...) outside
the composable or wrapping it with remember (refer to the notices variable in
NoticeScreen), and improve the back icon accessibility by providing a non-null
contentDescription (e.g., "뒤로가기") and increasing its touch target to at least
44.dp (replace the current width(10.dp) with a Modifier ensuring minimum touch
size).

Comment on lines +86 to +93
Image(
painter = painterResource(R.drawable.ic_back),
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterStart)
.width(10.dp)
.clickable { navController.popBackStack() }
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "NoticeScreen.kt" | head -20

Repository: LinkYou-2025/LinkU_Android

Length of output: 142


🏁 Script executed:

cat -n feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt | sed -n '75,105p'

Repository: LinkYou-2025/LinkU_Android

Length of output: 1311


뒤로가기 버튼 접근성 개선이 필요합니다.

contentDescription = null이고 터치 영역이 10.dp × 24.dp로 매우 작습니다. Material Design 가이드라인에 따라 최소 터치 영역을 48.dp × 48.dp로 확보하고, 스크린 리더를 위한 설명 텍스트를 추가해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.kt`
around lines 86 - 93, The back-button Image in NoticeScreen.kt uses
contentDescription = null and a tiny touch area; update the Image
(painterResource(R.drawable.ic_back)) so it provides an accessible label (e.g.,
contentDescription = "Back" or string resource) and increase its touch target to
at least 48.dp x 48.dp by wrapping it in a larger clickable modifier or
container (e.g., Box or Modifier.size(48.dp).clickable {
navController.popBackStack() } with proper alignment) so the click handler
navController.popBackStack() stays attached to the larger hit area; ensure you
use a localized string resource for the description rather than a hardcoded
literal.

Comment on lines +58 to +70
var selectedReason by remember { mutableStateOf<String?>(null) }
var isAgreeChecked by remember { mutableStateOf(false) }

val quitReasons = listOf(
"다른 유사 서비스를 이용해요.",
"사용을 잘 안하게 돼요.",
"잦은 오류와 장애가 발생해요.",
"새 계정을 만들고 싶어요.",
"기타"
)

val isEtcSelected = selectedReason == "기타"
val isQuitEnabled = selectedReason != null && isAgreeChecked
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

선택한 탈퇴 사유가 최종 콜백으로 전달되지 않습니다.

지금 구조에서는 confirm 시 항상 reasonText만 넘겨서, 미리 정의된 사유를 고르면 빈 문자열이 전달됩니다. 게다가 기타도 빈 입력으로 submit 가능합니다. selectedReasonreasonText에서 최종 전송값을 계산하고, 기타는 non-blank일 때만 enable해야 합니다. As per coding guidelines **/*.{kt,kts}: Focus on state management (state hoisting, remember vs rememberSaveable) in Android/Kotlin/Compose code.

수정 예시
-    val isEtcSelected = selectedReason == "기타"
-    val isQuitEnabled = selectedReason != null && isAgreeChecked
+    val isEtcSelected = selectedReason == "기타"
+    val quitReason = if (isEtcSelected) reasonText.trim() else selectedReason.orEmpty()
+    val isQuitEnabled = selectedReason != null &&
+        isAgreeChecked &&
+        (!isEtcSelected || quitReason.isNotBlank())
...
-                        onRequestQuit(reasonText)
+                        onRequestQuit(quitReason)

Also applies to: 135-145, 296-299

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/mypage/src/main/java/com/example/mypage/screen/ServiceQuitScreen.kt`
around lines 58 - 70, The confirm flow currently sends only reasonText and lets
predefined selections yield an empty string, and allows "기타" to submit when
blank; update state so selectedReason and reasonText are saved with
rememberSaveable (instead of remember), compute a finalReason value where if
selectedReason == "기타" then finalReason = reasonText.trim() (and require it
non-blank), else finalReason = selectedReason, and use that finalReason in the
confirm callback; also update isEtcSelected and isQuitEnabled to reflect this
logic (isEtcSelected = selectedReason == "기타", isQuitEnabled = (selectedReason
!= null && (selectedReason != "기타" || reasonText.isNotBlank()) &&
isAgreeChecked)) and ensure the confirm handler sends finalReason.

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.

1 participant