Skip to content

Commit c08fd20

Browse files
authored
[MERGE] #153 -> develop
[FIX/#153] 키보드 이슈 해결
2 parents 46d7396 + b8139b4 commit c08fd20

7 files changed

Lines changed: 156 additions & 96 deletions

File tree

app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ fun MainScreen(
6363
) {
6464
splashNavGraph(
6565
navigateToOnboarding = appState::navigateToOnboarding,
66-
navigateToHome = appState::navigateToHome,
6766
paddingValues = innerPadding
6867
)
6968

app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationScreen.kt

Lines changed: 149 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,45 @@
11
package com.cherrish.android.presentation.onboarding.information
22

3+
import androidx.compose.animation.core.animateDpAsState
4+
import androidx.compose.foundation.ExperimentalFoundationApi
35
import 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
49
import androidx.compose.foundation.layout.Arrangement
510
import androidx.compose.foundation.layout.Column
611
import androidx.compose.foundation.layout.PaddingValues
712
import androidx.compose.foundation.layout.Spacer
13+
import androidx.compose.foundation.layout.WindowInsets
14+
import androidx.compose.foundation.layout.WindowInsetsSides
815
import androidx.compose.foundation.layout.fillMaxSize
916
import androidx.compose.foundation.layout.fillMaxWidth
1017
import 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
1320
import 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
1426
import androidx.compose.foundation.shape.RoundedCornerShape
15-
import androidx.compose.material3.Scaffold
1627
import androidx.compose.material3.Text
1728
import androidx.compose.runtime.Composable
29+
import androidx.compose.runtime.LaunchedEffect
1830
import androidx.compose.runtime.getValue
1931
import androidx.compose.runtime.mutableStateOf
2032
import androidx.compose.runtime.remember
33+
import androidx.compose.runtime.rememberCoroutineScope
2134
import androidx.compose.runtime.setValue
2235
import androidx.compose.ui.Modifier
2336
import androidx.compose.ui.focus.FocusRequester
2437
import androidx.compose.ui.focus.focusRequester
2538
import androidx.compose.ui.focus.onFocusChanged
2639
import 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
2743
import androidx.compose.ui.platform.LocalFocusManager
2844
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
2945
import androidx.compose.ui.text.input.ImeAction
@@ -33,7 +49,6 @@ import androidx.compose.ui.tooling.preview.Preview
3349
import androidx.compose.ui.unit.dp
3450
import androidx.hilt.navigation.compose.hiltViewModel
3551
import androidx.lifecycle.compose.collectAsStateWithLifecycle
36-
import com.cherrish.android.core.common.extension.addFocusCleaner
3752
import com.cherrish.android.core.common.extension.collectLatestSideEffect
3853
import com.cherrish.android.core.designsystem.component.button.CherrishButton
3954
import com.cherrish.android.core.designsystem.component.textfield.CherrishTextField
@@ -69,6 +84,7 @@ fun OnboardingInformationRoute(
6984
)
7085
}
7186

87+
@OptIn(ExperimentalFoundationApi::class)
7288
@Composable
7389
private 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))

app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.cherrish.android.presentation.onboarding.information
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import com.cherrish.android.core.common.extension.onLogFailure
6-
import com.cherrish.android.core.local.TokenManager
76
import com.cherrish.android.data.model.OnboardingProfileRequestModel
87
import com.cherrish.android.data.repository.OnboardingProfileRepository
98
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -19,8 +18,7 @@ import kotlinx.coroutines.launch
1918

2019
@HiltViewModel
2120
class OnboardingInformationViewModel @Inject constructor(
22-
private val onboardingProfileRepository: OnboardingProfileRepository,
23-
private val tokenManager: TokenManager
21+
private val onboardingProfileRepository: OnboardingProfileRepository
2422
) : ViewModel() {
2523
private val _uiState = MutableStateFlow(InformationUiState())
2624
val uiState: StateFlow<InformationUiState> = _uiState.asStateFlow()
@@ -43,7 +41,7 @@ class OnboardingInformationViewModel @Inject constructor(
4341
_uiState.update { it.copy(age = filtered) }
4442
}
4543

46-
fun onAgeErrorCase(age: String): Boolean = age.toIntOrNull() ?.let { it > 100 } ?: false
44+
fun onAgeErrorCase(age: String): Boolean = age.toIntOrNull()?.let { it > 100 } ?: false
4745

4846
fun onNextClicked() {
4947
val age = uiState.value.age.toIntOrNull() ?: return
@@ -55,7 +53,6 @@ class OnboardingInformationViewModel @Inject constructor(
5553
age = age
5654
)
5755
).onSuccess { response ->
58-
tokenManager.saveId(response.id)
5956
_sideEffect.emit(InformationSideEffect.NavigateToHome)
6057
}.onLogFailure {}
6158
}

app/src/main/java/com/cherrish/android/presentation/splash/SplashScreen.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
99
import androidx.compose.foundation.layout.height
1010
import androidx.compose.foundation.layout.padding
1111
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.LaunchedEffect
1213
import androidx.compose.runtime.getValue
1314
import androidx.compose.runtime.rememberCoroutineScope
1415
import androidx.compose.ui.Alignment
@@ -19,8 +20,6 @@ import androidx.compose.ui.graphics.Brush
1920
import androidx.compose.ui.tooling.preview.Preview
2021
import androidx.compose.ui.unit.dp
2122
import androidx.hilt.navigation.compose.hiltViewModel
22-
import androidx.lifecycle.Lifecycle
23-
import androidx.lifecycle.compose.LifecycleEventEffect
2423
import com.airbnb.lottie.compose.LottieAnimation
2524
import com.airbnb.lottie.compose.LottieCompositionSpec
2625
import com.airbnb.lottie.compose.LottieConstants
@@ -35,23 +34,21 @@ import kotlinx.coroutines.launch
3534
@Composable
3635
fun SplashRoute(
3736
navigateToOnboarding: () -> Unit,
38-
navigateToHome: () -> Unit,
3937
paddingValues: PaddingValues,
4038
viewModel: SplashViewModel = hiltViewModel()
4139
) {
4240
val scope = rememberCoroutineScope()
4341

44-
LifecycleEventEffect(Lifecycle.Event.ON_START) {
42+
LaunchedEffect(Unit) {
4543
scope.launch {
4644
delay(3000)
47-
viewModel.isAutoLoginCheck()
45+
viewModel.navigateToOnboarding()
4846
}
4947
}
5048

5149
viewModel.sideEffect.collectLatestSideEffect { sideEffect ->
5250
when (sideEffect) {
5351
SplashSideEffect.NavigateToOnboarding -> navigateToOnboarding()
54-
SplashSideEffect.NavigateToHome -> navigateToHome()
5552
}
5653
}
5754

app/src/main/java/com/cherrish/android/presentation/splash/SplashSideEffect.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ package com.cherrish.android.presentation.splash
22

33
sealed interface SplashSideEffect {
44
data object NavigateToOnboarding : SplashSideEffect
5-
data object NavigateToHome : SplashSideEffect
65
}

0 commit comments

Comments
 (0)