11package com.cherrish.android.presentation.onboarding.information
22
3+ import androidx.compose.animation.core.animateDpAsState
4+ import androidx.compose.foundation.ExperimentalFoundationApi
35import androidx.compose.foundation.background
6+ import androidx.compose.foundation.gestures.awaitEachGesture
7+ import androidx.compose.foundation.gestures.awaitFirstDown
8+ import androidx.compose.foundation.gestures.waitForUpOrCancellation
49import androidx.compose.foundation.layout.Arrangement
510import androidx.compose.foundation.layout.Column
611import androidx.compose.foundation.layout.PaddingValues
712import androidx.compose.foundation.layout.Spacer
13+ import androidx.compose.foundation.layout.WindowInsets
14+ import androidx.compose.foundation.layout.WindowInsetsSides
815import androidx.compose.foundation.layout.fillMaxSize
916import androidx.compose.foundation.layout.fillMaxWidth
1017import androidx.compose.foundation.layout.height
11- import androidx.compose.foundation.layout.imePadding
12- import androidx.compose.foundation.layout.navigationBarsPadding
18+ import androidx.compose.foundation.layout.ime
19+ import androidx.compose.foundation.layout.only
1320import androidx.compose.foundation.layout.padding
21+ import androidx.compose.foundation.layout.windowInsetsPadding
22+ import androidx.compose.foundation.lazy.LazyColumn
23+ import androidx.compose.foundation.lazy.rememberLazyListState
24+ import androidx.compose.foundation.relocation.BringIntoViewRequester
25+ import androidx.compose.foundation.relocation.bringIntoViewRequester
1426import androidx.compose.foundation.shape.RoundedCornerShape
15- import androidx.compose.material3.Scaffold
1627import androidx.compose.material3.Text
1728import androidx.compose.runtime.Composable
29+ import androidx.compose.runtime.LaunchedEffect
1830import androidx.compose.runtime.getValue
1931import androidx.compose.runtime.mutableStateOf
2032import androidx.compose.runtime.remember
33+ import androidx.compose.runtime.rememberCoroutineScope
2134import androidx.compose.runtime.setValue
2235import androidx.compose.ui.Modifier
2336import androidx.compose.ui.focus.FocusRequester
2437import androidx.compose.ui.focus.focusRequester
2538import androidx.compose.ui.focus.onFocusChanged
2639import androidx.compose.ui.graphics.Color
40+ import androidx.compose.ui.input.pointer.PointerEventPass
41+ import androidx.compose.ui.input.pointer.pointerInput
42+ import androidx.compose.ui.platform.LocalDensity
2743import androidx.compose.ui.platform.LocalFocusManager
2844import androidx.compose.ui.platform.LocalSoftwareKeyboardController
2945import androidx.compose.ui.text.input.ImeAction
@@ -33,7 +49,6 @@ import androidx.compose.ui.tooling.preview.Preview
3349import androidx.compose.ui.unit.dp
3450import androidx.hilt.navigation.compose.hiltViewModel
3551import androidx.lifecycle.compose.collectAsStateWithLifecycle
36- import com.cherrish.android.core.common.extension.addFocusCleaner
3752import com.cherrish.android.core.common.extension.collectLatestSideEffect
3853import com.cherrish.android.core.designsystem.component.button.CherrishButton
3954import com.cherrish.android.core.designsystem.component.textfield.CherrishTextField
@@ -69,6 +84,7 @@ fun OnboardingInformationRoute(
6984 )
7085}
7186
87+ @OptIn(ExperimentalFoundationApi ::class )
7288@Composable
7389private fun OnboardingInformationScreen (
7490 paddingValues : PaddingValues ,
@@ -84,82 +100,144 @@ private fun OnboardingInformationScreen(
84100) {
85101 val focusManager = LocalFocusManager .current
86102 val keyboardController = LocalSoftwareKeyboardController .current
103+ val density = LocalDensity .current
104+
105+ val nameFocusRequester = remember { FocusRequester () }
87106 val ageFocusRequester = remember { FocusRequester () }
107+ var isNameFocused by remember { mutableStateOf(false ) }
88108 var isAgeFocused by remember { mutableStateOf(false ) }
89109
90- Scaffold (
91- bottomBar = {
92- CherrishButton (
93- text = " 다음" ,
94- onClick = onNextClick,
95- enabled = enabled,
96- modifier = Modifier
97- .fillMaxWidth()
98- .padding(horizontal = 24 .dp, vertical = 30 .dp)
99- .background(CherrishTheme .colors.gray0)
100- .navigationBarsPadding()
110+ val coroutineScope = rememberCoroutineScope()
111+
112+ val bringIntoViewRequester = remember { BringIntoViewRequester () }
101113
102- )
114+ LaunchedEffect (isNameFocused, isAgeFocused) {
115+ if (isNameFocused || isAgeFocused) {
116+ delay(300 )
117+ bringIntoViewRequester.bringIntoView()
103118 }
104- ) { innerPadding ->
105- Column (
106- modifier = modifier
107- .fillMaxSize()
108- .background(color = CherrishTheme .colors.gray0)
109- .addFocusCleaner(focusManager)
110- .padding(paddingValues = paddingValues)
111- .imePadding()
119+ }
120+
121+ val listState = rememberLazyListState()
122+ val imeBottom = WindowInsets .ime.getBottom(density)
123+ val imeBottomDp = with (density) { imeBottom.toDp() }
124+ val targetBottomInset = if (imeBottomDp > 0 .dp) 0 .dp else paddingValues.calculateBottomPadding()
125+ val bottomInset by animateDpAsState(targetValue = targetBottomInset, label = " bottomInset" )
126+
127+ Column (
128+ modifier = modifier
129+ .fillMaxSize()
130+ .background(color = CherrishTheme .colors.gray0)
131+ ) {
132+ LazyColumn (
133+ state = listState,
134+ modifier = Modifier
135+ .fillMaxWidth()
136+ .weight(1f ),
137+ contentPadding = PaddingValues (bottom = 24 .dp)
112138 ) {
113- Spacer (modifier = Modifier .weight(135f ))
114- UserInfoHeader ()
115- Spacer (modifier = Modifier .weight(70f ))
139+ item {
140+ Spacer (modifier = Modifier .height(157 .dp))
141+ }
142+
143+ stickyHeader {
144+ Column (
145+ modifier = Modifier
146+ .fillMaxWidth()
147+ .background(CherrishTheme .colors.gray0)
148+ ) {
149+ UserInfoHeader ()
150+ }
151+ }
116152
117- UserInfoTextField (
118- textFieldName = " 이름" ,
119- value = username,
120- onValueChange = onNameChange,
121- placeholder = " 김체리" ,
122- keyboardImeAction = ImeAction .Next ,
123- onNextAction = { ageFocusRequester.requestFocus() },
124- keyboardType = KeyboardType .Text ,
125- errorText = " 이름은 최대 7자까지 입력 가능합니다." ,
126- errorCase = nameErrorCase
127- )
153+ item {
154+ Spacer (modifier = Modifier .height(70 .dp))
155+ }
128156
129- Spacer (modifier = Modifier .weight(30f ))
157+ item {
158+ Column (
159+ modifier = Modifier
160+ .bringIntoViewRequester(bringIntoViewRequester)
161+ .windowInsetsPadding(WindowInsets .ime.only(WindowInsetsSides .Bottom ))
162+ ) {
163+ UserInfoTextField (
164+ textFieldName = " 이름" ,
165+ value = username,
166+ onValueChange = onNameChange,
167+ placeholder = " 김체리" ,
168+ keyboardImeAction = ImeAction .Next ,
169+ onNextAction = { ageFocusRequester.requestFocus() },
170+ keyboardType = KeyboardType .Text ,
171+ errorText = " 이름은 최대 7자까지 입력 가능합니다." ,
172+ errorCase = nameErrorCase,
173+ textFieldModifier = Modifier
174+ .focusRequester(nameFocusRequester)
175+ .pointerInput(Unit ) {
176+ awaitEachGesture {
177+ awaitFirstDown(pass = PointerEventPass .Initial )
178+ nameFocusRequester.requestFocus()
179+ waitForUpOrCancellation()
180+ }
181+ }
182+ .onFocusChanged { state ->
183+ isNameFocused = state.isFocused
184+ }
185+ )
130186
131- UserInfoTextField (
132- textFieldName = " 나이" ,
133- value = age,
134- onValueChange = onAgeChange,
135- placeholder = " 20" ,
136- keyboardImeAction = ImeAction .Done ,
137- onDoneAction = {
138- keyboardController?.hide()
139- kotlinx.coroutines.MainScope ().launch {
140- delay(100 )
141- focusManager.clearFocus()
142- }
143- },
144- keyboardType = KeyboardType .Number ,
145- visualTransformation = if (isAgeFocused) {
146- VisualTransformation .None
147- } else {
148- AgeSuffixTransformation (" 세" )
149- },
150- errorText = " 입력 가능한 최대 나이 100세를 초과했습니다." ,
151- errorCase = ageErrorCase,
152- modifier = Modifier
153- .focusRequester(ageFocusRequester)
154- .onFocusChanged { state ->
155- isAgeFocused = state.isFocused
156- }
157- )
187+ Spacer (modifier = Modifier .height(30 .dp))
158188
159- Spacer (modifier = Modifier .weight(200f ))
189+ UserInfoTextField (
190+ textFieldName = " 나이" ,
191+ value = age,
192+ onValueChange = onAgeChange,
193+ placeholder = " 20" ,
194+ keyboardImeAction = ImeAction .Done ,
195+ onDoneAction = {
196+ keyboardController?.hide()
197+ coroutineScope.launch {
198+ delay(100 )
199+ focusManager.clearFocus()
200+ }
201+ },
202+ keyboardType = KeyboardType .Number ,
203+ visualTransformation = if (isAgeFocused) {
204+ VisualTransformation .None
205+ } else {
206+ AgeSuffixTransformation (
207+ " 세"
208+ )
209+ },
210+ errorText = " 입력 가능한 최대 나이 100세를 초과했습니다." ,
211+ errorCase = ageErrorCase,
212+ textFieldModifier = Modifier
213+ .focusRequester(ageFocusRequester)
214+ .pointerInput(Unit ) {
215+ awaitEachGesture {
216+ awaitFirstDown(pass = PointerEventPass .Initial )
217+ ageFocusRequester.requestFocus()
218+ waitForUpOrCancellation()
219+ }
220+ }
221+ .onFocusChanged { state ->
222+ isAgeFocused = state.isFocused
223+ }
224+ )
225+ }
160226
161- Spacer (modifier = Modifier .padding(innerPadding.calculateBottomPadding()))
227+ Spacer (modifier = Modifier .height(24 .dp))
228+ }
162229 }
230+
231+ CherrishButton (
232+ text = " 다음" ,
233+ onClick = onNextClick,
234+ enabled = enabled,
235+ modifier = Modifier
236+ .fillMaxWidth()
237+ .background(CherrishTheme .colors.gray0)
238+ .padding(horizontal = 24 .dp)
239+ .padding(top = 30 .dp, bottom = 30 .dp + bottomInset)
240+ )
163241 }
164242}
165243
@@ -168,7 +246,7 @@ private fun UserInfoHeader() {
168246 Column (
169247 modifier = Modifier
170248 .fillMaxWidth()
171- .padding(horizontal = 26 .dp),
249+ .padding(horizontal = 15 .dp),
172250 verticalArrangement = Arrangement .spacedBy(4 .dp)
173251 ) {
174252 Text (
@@ -195,6 +273,7 @@ private fun UserInfoTextField(
195273 keyboardType : KeyboardType ,
196274 errorText : String ,
197275 modifier : Modifier = Modifier ,
276+ textFieldModifier : Modifier = Modifier ,
198277 onNextAction : () -> Unit = {},
199278 onDoneAction : () -> Unit = {},
200279 visualTransformation : VisualTransformation = VisualTransformation .None ,
@@ -227,7 +306,7 @@ private fun UserInfoTextField(
227306 onDoneAction = onDoneAction,
228307 keyboardType = keyboardType,
229308 visualTransformation = visualTransformation,
230- modifier = Modifier .fillMaxWidth()
309+ modifier = textFieldModifier .fillMaxWidth()
231310 )
232311
233312 Spacer (modifier = Modifier .height(4 .dp))
0 commit comments