Conversation
Walkthrough마이페이지 모듈에 새로운 Jetpack Compose UI 컴포넌트, 스크린 및 drawable 리소스를 추가합니다. AI 링크 목록, FAQ, 공지사항, 서비스 탈퇴 흐름을 구현하고, 기존 화면의 레이아웃과 네비게이션을 재설계합니다. foundation-layout 의존성을 추가합니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 순서로 인해 그림자가 잘릴 수 있습니다.
clip이graphicsLayer보다 먼저 적용되어 있습니다.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
⛔ Files ignored due to path filters (1)
feature/mypage/src/main/res/drawable/ic_google_logo.pngis excluded by!**/*.png
📒 Files selected for processing (32)
feature/mypage/build.gradle.ktsfeature/mypage/src/main/java/com/example/mypage/MyPageApp.ktfeature/mypage/src/main/java/com/example/mypage/component/AILinkuItem.ktfeature/mypage/src/main/java/com/example/mypage/component/DeleteLinkuModal.ktfeature/mypage/src/main/java/com/example/mypage/component/FaqItem.ktfeature/mypage/src/main/java/com/example/mypage/component/LinkuItemModal.ktfeature/mypage/src/main/java/com/example/mypage/component/NoticeItem.ktfeature/mypage/src/main/java/com/example/mypage/component/QuitReasonItem.ktfeature/mypage/src/main/java/com/example/mypage/component/ServiceQuitModal.ktfeature/mypage/src/main/java/com/example/mypage/screen/AILinkuListScreen.ktfeature/mypage/src/main/java/com/example/mypage/screen/AlarmSettingScreen.ktfeature/mypage/src/main/java/com/example/mypage/screen/FaqScreen.ktfeature/mypage/src/main/java/com/example/mypage/screen/MyPageScreen.ktfeature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.ktfeature/mypage/src/main/java/com/example/mypage/screen/ServiceQuitScreen.ktfeature/mypage/src/main/java/com/example/mypage/ui/top/bar/MypageTopBar.ktfeature/mypage/src/main/res/drawable/ic_ai_bookmark.xmlfeature/mypage/src/main/res/drawable/ic_arrow_down_gray500.xmlfeature/mypage/src/main/res/drawable/ic_checkbox_checked.xmlfeature/mypage/src/main/res/drawable/ic_checkbox_unchecked.xmlfeature/mypage/src/main/res/drawable/ic_kakao_logo.xmlfeature/mypage/src/main/res/drawable/ic_logo_gray.xmlfeature/mypage/src/main/res/drawable/ic_more.xmlfeature/mypage/src/main/res/drawable/ic_naver_logo.xmlfeature/mypage/src/main/res/drawable/ic_notice.xmlfeature/mypage/src/main/res/drawable/ic_notice_gray.xmlfeature/mypage/src/main/res/drawable/ic_profile_default.xmlfeature/mypage/src/main/res/drawable/ic_q.xmlfeature/mypage/src/main/res/drawable/ic_sparkle.xmlfeature/mypage/src/main/res/drawable/img_empty_ailinku.xmlfeature/mypage/src/main/res/drawable/img_link_default.xmlgradle/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) |
There was a problem hiding this comment.
🧩 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*\(' -C2Repository: 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 fRepository: 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 2Repository: 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 2Repository: 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 -80Repository: 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 2Repository: 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 -20Repository: 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 1Repository: 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 1Repository: 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 1Repository: 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.
| fun AILinkuItem( | ||
| linkTitle: String, | ||
| tags: List<String>, | ||
| domainImage: Int? = null, | ||
| domainName: String? = null, | ||
| onClickDelete: () -> Unit = {} | ||
| ) { | ||
| var isMenuVisible by remember { mutableStateOf(false) } |
There was a problem hiding this comment.
더보기 메뉴 상태는 상위에서 단일하게 관리해 주세요.
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.
| 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 | ||
| } | ||
| ) |
There was a problem hiding this comment.
터치 영역이 너무 작습니다.
화살표 아이콘의 높이가 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.
| .graphicsLayer { | ||
| shadowElevation = 10.dp.toPx() | ||
| this.shape = shape | ||
| clip = true | ||
| } |
There was a problem hiding this comment.
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.
| 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 | ||
| } | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "NoticeItem.kt" | head -20Repository: LinkYou-2025/LinkU_Android
Length of output: 143
🏁 Script executed:
wc -l ./feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.ktRepository: LinkYou-2025/LinkU_Android
Length of output: 147
🏁 Script executed:
cat -n ./feature/mypage/src/main/java/com/example/mypage/component/NoticeItem.ktRepository: 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.
| Column( | ||
| modifier = Modifier.fillMaxSize() | ||
| ) { |
There was a problem hiding this comment.
스크롤을 제거하면서 하단 메뉴가 기기 높이에 따라 잘립니다.
이제 전체 화면이 고정 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.
| 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() }, |
There was a problem hiding this comment.
상단바 상태를 상수로 고정하면 실제 계정 정보가 반영되지 않습니다.
MypageTopBar는 이미 isNoticeExist와 socialLoginType를 받도록 분리돼 있는데, 여기서 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.
| fun NoticeScreen( | ||
| navController: NavController | ||
| ) { |
There was a problem hiding this comment.
🧩 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 -20Repository: LinkYou-2025/LinkU_Android
Length of output: 172
🏁 Script executed:
find . -name 'MyPageApp.kt' -o -name 'MyPageScreen.kt' 2>/dev/nullRepository: LinkYou-2025/LinkU_Android
Length of output: 205
🏁 Script executed:
cat ./feature/mypage/src/main/java/com/example/mypage/MyPageApp.ktRepository: LinkYou-2025/LinkU_Android
Length of output: 10948
🏁 Script executed:
cat ./feature/mypage/src/main/java/com/example/mypage/screen/NoticeScreen.ktRepository: 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 {} 사용) 버튼 클릭 시 어디로도 이동하지 않습니다. 기능을 활성화하려면:
- NoticeScreen을 MyPageApp.kt에 import
- NavHost에
composable("notice") { NoticeScreen(navController = navController) }추가 - 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).
| Image( | ||
| painter = painterResource(R.drawable.ic_back), | ||
| contentDescription = null, | ||
| modifier = Modifier | ||
| .align(Alignment.CenterStart) | ||
| .width(10.dp) | ||
| .clickable { navController.popBackStack() } | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "NoticeScreen.kt" | head -20Repository: 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.
| var selectedReason by remember { mutableStateOf<String?>(null) } | ||
| var isAgreeChecked by remember { mutableStateOf(false) } | ||
|
|
||
| val quitReasons = listOf( | ||
| "다른 유사 서비스를 이용해요.", | ||
| "사용을 잘 안하게 돼요.", | ||
| "잦은 오류와 장애가 발생해요.", | ||
| "새 계정을 만들고 싶어요.", | ||
| "기타" | ||
| ) | ||
|
|
||
| val isEtcSelected = selectedReason == "기타" | ||
| val isQuitEnabled = selectedReason != null && isAgreeChecked |
There was a problem hiding this comment.
선택한 탈퇴 사유가 최종 콜백으로 전달되지 않습니다.
지금 구조에서는 confirm 시 항상 reasonText만 넘겨서, 미리 정의된 사유를 고르면 빈 문자열이 전달됩니다. 게다가 기타도 빈 입력으로 submit 가능합니다. selectedReason와 reasonText에서 최종 전송값을 계산하고, 기타는 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.
📝 설명
✔️ PR 유형
어떤 변경 사항이 있나요?
📎 관련 이슈 번호
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항