-
Notifications
You must be signed in to change notification settings - Fork 0
[PC-1623] 키패드 표출 시 focuse 영역이 화면에 보이도록 스크롤 기능 고도화 #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughAdds an IME- and focus-aware Modifier extension that auto-scrolls a LazyList to a given index when an item gains focus and the IME is visible; ValueTalk screens propagate list state and item indices, track input focus, and add IME bottom padding. Changes
Sequence DiagramsequenceDiagram
actor User
participant FocusSystem as Focus
participant Modifier as animateScrollToItemOnFocus
participant IME as WindowInsets/IME
participant List as LazyListState
User->>FocusSystem: tap input (gains focus)
FocusSystem->>Modifier: onFocusChanged(isFocused=true)
Modifier->>IME: check ime visibility
alt IME visible
IME-->>Modifier: imeVisible=true
Modifier->>Modifier: delay briefly
Modifier->>List: animateScrollToItem(index, offsetPx)
List-->>User: item scrolled into view
else IME hidden
IME-->>Modifier: imeVisible=false
Modifier-->>User: no scroll
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
core/common-ui/src/main/java/com/puzzle/common/ui/Modifier.kt (1)
210-232: Consider addingindexto LaunchedEffect keys.The
indexparameter is not included in theLaunchedEffectkeys. If the index changes while the item is focused (e.g., due to list reordering), the scroll will target the stale index captured at effect launch time.Additionally, consider extracting the
200delay as a named constant for clarity.♻️ Suggested improvement
+private const val IME_ANIMATION_DELAY_MS = 200L + @OptIn(ExperimentalLayoutApi::class) fun Modifier.animateScrollToItemOnFocus( listState: LazyListState, index: Int, scrollOffsetDp: Dp = 20.dp ): Modifier = composed { val density = LocalDensity.current val imeBottom = WindowInsets.ime.asPaddingValues().calculateBottomPadding() var isFocused by remember { mutableStateOf(false) } - LaunchedEffect(imeBottom, isFocused) { + LaunchedEffect(imeBottom, isFocused, index) { if (isFocused && imeBottom > 0.dp) { - delay(200) + delay(IME_ANIMATION_DELAY_MS) listState.animateScrollToItem( index = index, scrollOffset = with(density) { -scrollOffsetDp.roundToPx() } ) } } this.onFocusChanged { isFocused = it.isFocused } }feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt (1)
158-158: Remove unused variable.The
densityvariable is no longer used after replacinganimateScrollWhenFocuswithanimateScrollToItemOnFocus. This is dead code that can be removed.🧹 Suggested cleanup
- val density = LocalDensity.current - Column(modifier = modifier) {
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
core/common-ui/src/main/java/com/puzzle/common/ui/Modifier.ktfeature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt (1)
125-135: LGTM! Integration looks correct.The
idx + 1calculation correctly accounts for the header item at LazyList index 0, ensuring the scroll targets the correct ValueTalkCard. The modifier usage and parameter passing are properly wired.Also applies to: 183-189
kkh725
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다
tgyuuAn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 Modifier가 다른 디바이스에서도 온전히 동작하나요?
체크가 필요할 것 같아요.
추가적으로 해당 Modifier가 생겨나면 기존 Modifier는 제거해도 될까요 ?
| state = listState, | ||
| modifier = modifier, | ||
| contentPadding = PaddingValues(bottom = imeBottom) | ||
| contentPadding = PaddingValues(bottom = imeBottom + 40.dp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
질문 : 이거 40.dp는 뭐에요 ?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @OptIn(ExperimentalLayoutApi::class) | ||
| fun Modifier.animateScrollToItemOnFocus( | ||
| listState: LazyListState, | ||
| index: Int, | ||
| scrollOffsetDp: Dp = 20.dp | ||
| ): Modifier = composed { | ||
| val density = LocalDensity.current | ||
| val imeBottom = WindowInsets.ime.asPaddingValues().calculateBottomPadding() | ||
| var isFocused by remember { mutableStateOf(false) } | ||
|
|
||
| LaunchedEffect(imeBottom, isFocused) { | ||
| if (isFocused && imeBottom > 0.dp) { | ||
| delay(200) | ||
| listState.animateScrollToItem( | ||
| index = index, | ||
| scrollOffset = with(density) { -scrollOffsetDp.roundToPx() } | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| this.onFocusChanged { isFocused = it.isFocused } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이거 Composed 말고 Modfiier.Node()로 변경해주시는 게 좋을 것 같아요.
composed로 작성되면 상위 Composable에서 리컴포지션 일어나면 항상 Modifier가 재트리거 되거든요.
참고 : https://developer.android.com/develop/ui/compose/custom-modifiers?hl=ko
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 로직이 태블릿일 때 제대로 작성하지 않아 수정했습니다.
=> e6870f4
추가
아래 Column에서 스크롤 로직에서 Node를 적용해보려고 했지만 IME이가 Show/Hide일 때를 캐치하는 것이 어려워 유지하기로 했습니다. => 포커스가 변경 될 때는 잘 되지만 포커스는 그대로인 상태에서 IME만 Show/Hide가 되는 상황에서 스크롤이 작동하지 않는 문제가 있었습니다.
@Composable
fun Modifier.animateScrollWhenFocus(
scrollState: ScrollState,
verticalWeightPx: Int = 0
): Modifier = composed {
var scrollToPosition by remember { mutableFloatStateOf(0f) }
val scope = rememberCoroutineScope()
this
.onGloballyPositioned { coordinates ->
val itemTop = coordinates.positionInParent().y
scrollToPosition = itemTop + verticalWeightPx
}
.onFocusEvent {
if (it.isFocused) {
scope.launch {
delay(300L)
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아래 방법으로는 안되나용?
근데 Modifier.Node() 말고는 상관없을 것 같긴 하네요...
https://developer.android.com/develop/ui/compose/custom-modifiers?hl=ko#never-skipped
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넴, 해봐도 포커스 그대에 키패드 show/hide일 때가 안되네요... 나중에 천천히 해봐야 할 거 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
@feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt:
- Line 175: The composable contains an unused coroutine scope declaration "val
scope = rememberCoroutineScope()" in ValueTalkPage; delete this unused variable
(and remove the now-unused import of rememberCoroutineScope if it becomes
unused) or, if you intended asynchronous work, replace usage by launching
coroutines with that scope (e.g., use scope.launch { ... }) so the variable is
actually used; otherwise simply remove the declaration to clean up the
composable.
🧹 Nitpick comments (2)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt (1)
165-173: Extract magic numbers to named constants.The delay duration (200ms) and scroll offset (-100) are magic numbers that should be extracted to named constants for better maintainability and clarity. This would also help maintain consistency if these values need to be adjusted later.
♻️ Proposed refactor
At the top of the file, add constants:
private const val IME_SCROLL_DELAY_MS = 200L private const val SCROLL_OFFSET_PX = -100Then update the LaunchedEffect:
LaunchedEffect(isImeVisible, isFocused) { if (isImeVisible && isFocused) { - delay(200) + delay(IME_SCROLL_DELAY_MS) listState.animateScrollToItem( index = index, - scrollOffset = -100 + scrollOffset = SCROLL_OFFSET_PX ) } }feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt (1)
306-322: Extract magic numbers to named constants.Similar to ValueTalkPage.kt, the delay duration (200ms) and scroll offsets (160, -100) should be extracted to named constants for better maintainability and to make the different offsets' purposes clearer.
♻️ Proposed refactor
At the top of the file, add constants:
private const val IME_SCROLL_DELAY_MS = 200L private const val ANSWER_SCROLL_OFFSET_PX = -100 private const val AI_SUMMARY_SCROLL_OFFSET_PX = 160Then update the LaunchedEffect:
LaunchedEffect(isImeVisible, isAnswerFocused, isAiFocused) { if (!isImeVisible) return@LaunchedEffect val targetOffset = when { - isAiFocused -> 160 - isAnswerFocused -> -100 + isAiFocused -> AI_SUMMARY_SCROLL_OFFSET_PX + isAnswerFocused -> ANSWER_SCROLL_OFFSET_PX else -> null } targetOffset?.let { offset -> - delay(200) + delay(IME_SCROLL_DELAY_MS) listState.animateScrollToItem( index = index, scrollOffset = offset ) } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.ktfeature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt
🧰 Additional context used
🧬 Code graph analysis (1)
feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt (2)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt (1)
ValueTalkCard(153-214)feature/matching/src/main/java/com/puzzle/matching/graph/detail/page/ValueTalkPage.kt (1)
ValueTalkCard(159-216)
🔇 Additional comments (1)
feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt (1)
248-253: Verify if both IME padding approaches are needed.The LazyColumn applies
contentPadding = PaddingValues(bottom = imeBottom)at line 253, and there's also aSpacerwith.imePadding()at the end (lines 280-283). This appears to apply IME-aware bottom padding twice, which might result in excessive spacing when the keyboard is visible. Consider whether both approaches are necessary or if one should be removed.Also applies to: 278-284
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
@feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt:
- Line 34: Remove the unused import of rememberCoroutineScope by deleting the
line importing androidx.compose.runtime.rememberCoroutineScope; verify there are
no references to rememberCoroutineScope in this file (e.g., in ValueTalkPage or
related composables) and run the Kotlin linter/IDE auto-import cleanup to ensure
no other unused imports remain.
🧹 Nitpick comments (3)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt (1)
163-184: Consider extracting magic numbers to named constants.The implementation is correct, but the hardcoded values (200ms delay, 0.1f scroll offset percentage) could be extracted as named constants for better maintainability and clarity.
♻️ Suggested improvement
At the top of the file, define constants:
private const val IME_SCROLL_DELAY_MS = 200L private const val SCROLL_OFFSET_PERCENTAGE = 0.1fThen update the usage:
LaunchedEffect(isImeVisible, isFocused) { if (isImeVisible && isFocused) { - delay(200) + delay(IME_SCROLL_DELAY_MS) listState.animateScrollToItem( index = index, scrollOffset = scrollOffset ) } }And:
val scrollOffset = remember(screenHeightPx) { - -(screenHeightPx * 0.1f).toInt() + -(screenHeightPx * SCROLL_OFFSET_PERCENTAGE).toInt() }feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt (2)
303-330: LGTM: Sophisticated dual-field focus handling.The implementation correctly handles different scroll offsets for the answer field (scrolls up 10%) versus the AI summary field (scrolls down 15%), which makes sense given their different positions in the card layout.
The force unwrap (
targetOffset!!) at line 327 is safe because the condition at line 323 ensurestargetOffset != null.Consider extracting the magic numbers (0.1f, 0.15f, 200) as named constants for consistency with best practices:
private const val ANSWER_SCROLL_OFFSET_PERCENTAGE = 0.1f private const val AI_SCROLL_OFFSET_PERCENTAGE = 0.15f private const val IME_SCROLL_DELAY_MS = 200L
303-330: Consider consolidating scroll logic across files.Both
ValueTalkPage.ktandValueTalkScreen.ktcontain similar IME-aware scrolling logic (screen height calculation, delay,animateScrollToItem). Consider extracting this into a shared composable or utility function to reduce duplication and ensure consistent behavior.Example shared utility:
// In a shared utility file @OptIn(ExperimentalLayoutApi::class) @Composable fun rememberImeAwareScroller( listState: LazyListState, index: Int, isFocused: Boolean, scrollOffsetPercentage: Float = -0.1f, delayMs: Long = 200L ) { val isImeVisible = WindowInsets.isImeVisible val configuration = LocalConfiguration.current val density = LocalDensity.current val screenHeightPx = remember(configuration, density) { with(density) { configuration.screenHeightDp.dp.roundToPx() } } val scrollOffset = remember(screenHeightPx, scrollOffsetPercentage) { (screenHeightPx * scrollOffsetPercentage).toInt() } LaunchedEffect(isImeVisible, isFocused) { if (isImeVisible && isFocused) { delay(delayMs) listState.animateScrollToItem( index = index, scrollOffset = scrollOffset ) } } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.ktfeature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (6)
feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt (2)
133-133: LGTM: Index calculation correctly accounts for header.The
idx + 1calculation is correct because the LazyColumn has a header item at index 0, so the firstValueTalkCardis at LazyColumn index 1.
211-213: LGTM: Focus change handling is correct.The
onFocusChangedmodifier properly tracks focus state, which triggers the IME-aware scrolling behavior.feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt (4)
250-255: LGTM: IME padding correctly applied.The bottom padding is properly calculated from IME insets and applied to the LazyColumn's
contentPadding, ensuring content remains accessible when the keyboard is visible.
263-264: LGTM: Index correctly maps to LazyColumn position.Unlike
ValueTalkPage.kt, this LazyColumn has no header item, so passingidxdirectly is correct.
368-370: LGTM: Focus tracking for answer field.The
onFocusChangedmodifier correctly updatesisAnswerFocused, which drives the scroll offset calculation in theLaunchedEffect.
382-382: LGTM: Focus callback properly propagated.The
onFocusChangedcallback is correctly threaded throughAiSummaryContentto capture focus state from the AI summary input field and propagate it to the parentValueTalkCard.Also applies to: 434-434, 470-470
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.runtime.mutableStateOf | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.rememberCoroutineScope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused import.
rememberCoroutineScope is imported but never used in this file.
🧹 Proposed fix
-import androidx.compose.runtime.rememberCoroutineScope📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import androidx.compose.runtime.rememberCoroutineScope |
🤖 Prompt for AI Agents
In
@feature/profile/src/main/java/com/puzzle/profile/graph/register/page/ValueTalkPage.kt
at line 34, Remove the unused import of rememberCoroutineScope by deleting the
line importing androidx.compose.runtime.rememberCoroutineScope; verify there are
no references to rememberCoroutineScope in this file (e.g., in ValueTalkPage or
related composables) and run the Kotlin linter/IDE auto-import cleanup to ensure
no other unused imports remain.
* [PC-1623] 가치관 톡 작성 시 각 항목 입력 시 키패드에 화면에 보이도록 스크롤 기능 고도화 * [PC-1623] 가치관 톡 작성 시 각 항목 입력 시 키패드 스크롤 로직 변경 * [PC-1623] 가치관 톡 수정 시 각 항목 입력 시 키패드 스크롤 로직 추가 * [PC-1623] 가치관 톡 ai 수정 시 각 항목 입력 시 키패드 스크롤 로직 추가 * [PC-1623] 스크롤 offset 수정 * [PC-1623] 중복 패딩 제거, 매직 넘버 대신 화면 높이 기반으로 변경
1. ⭐️ 변경된 내용
2. 🖼️ 스크린샷(선택)
3. 💡 알게된 부분
4. 📌 이 부분은 꼭 봐주세요!
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.