Skip to content

Commit 5b0c1ae

Browse files
committed
[feat]: 수정화면으로 넘어갈 때 cursor 맨 뒤에 focus 잡히게 구현 (#133)
1 parent 299ec7e commit 5b0c1ae

5 files changed

Lines changed: 65 additions & 24 deletions

File tree

app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import androidx.compose.material3.Text
1111
import androidx.compose.runtime.Composable
1212
import androidx.compose.runtime.getValue
1313
import androidx.compose.runtime.mutableStateOf
14+
import androidx.compose.runtime.remember
1415
import androidx.compose.runtime.saveable.rememberSaveable
1516
import androidx.compose.runtime.setValue
1617
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.focus.FocusRequester
19+
import androidx.compose.ui.focus.focusRequester
1720
import androidx.compose.ui.graphics.SolidColor
1821
import androidx.compose.ui.res.stringResource
1922
import androidx.compose.ui.text.input.TextFieldValue
@@ -29,7 +32,8 @@ fun OpinionInputSection(
2932
textFieldValue: TextFieldValue,
3033
onTextChange: (TextFieldValue) -> Unit,
3134
hint: String = stringResource(R.string.my_opinion_placeholder),
32-
maxLength: Int = 500
35+
maxLength: Int = 500,
36+
focusRequester: FocusRequester = remember { FocusRequester() }
3337
) {
3438
val text = textFieldValue.text
3539

@@ -52,7 +56,8 @@ fun OpinionInputSection(
5256
},
5357
textStyle = typography.menu_r400_s14_h24.copy(color = colors.White),
5458
modifier = Modifier
55-
.fillMaxWidth(),
59+
.fillMaxWidth()
60+
.focusRequester(focusRequester),
5661
cursorBrush = SolidColor(colors.NeonGreen),
5762
decorationBox = { innerTextField ->
5863
Box(

app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import androidx.compose.runtime.remember
1818
import androidx.compose.runtime.saveable.rememberSaveable
1919
import androidx.compose.runtime.setValue
2020
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.focus.FocusRequester
22+
import androidx.compose.ui.focus.focusRequester
2123
import androidx.compose.ui.graphics.SolidColor
2224
import androidx.compose.ui.platform.LocalFocusManager
2325
import androidx.compose.ui.res.stringResource
26+
import androidx.compose.ui.text.input.TextFieldValue
2427
import androidx.compose.ui.tooling.preview.Preview
2528
import androidx.compose.ui.unit.dp
2629
import com.texthip.thip.R
@@ -30,16 +33,17 @@ import com.texthip.thip.ui.theme.ThipTheme.typography
3033

3134
@Composable
3235
fun VoteInputSection(
33-
title: String,
34-
onTitleChange: (String) -> Unit,
36+
titleValue: TextFieldValue,
37+
onTitleChange: (TextFieldValue) -> Unit,
3538
options: List<String>,
3639
onOptionChange: (index: Int, newText: String) -> Unit,
3740
onAddOption: () -> Unit,
3841
onRemoveOption: (index: Int) -> Unit,
3942
modifier: Modifier = Modifier,
4043
isEnabled: Boolean = true,
4144
maxOptionLength: Int = 20,
42-
maxOptions: Int = 5
45+
maxOptions: Int = 5,
46+
focusRequester: FocusRequester = remember { FocusRequester() },
4347
) {
4448
val focusManager = LocalFocusManager.current
4549

@@ -55,15 +59,15 @@ fun VoteInputSection(
5559
verticalArrangement = Arrangement.spacedBy(16.dp)
5660
) {
5761
BasicTextField(
58-
value = title,
59-
onValueChange = { if (it.length <= maxOptionLength) onTitleChange(it) },
60-
// enabled = isEnabled,
62+
value = titleValue,
63+
onValueChange = { if (it.text.length <= maxOptionLength) onTitleChange(it) },
6164
textStyle = typography.smalltitle_m500_s18_h24.copy(color = colors.White),
6265
modifier = Modifier
63-
.fillMaxWidth(),
66+
.fillMaxWidth()
67+
.focusRequester(focusRequester),
6468
cursorBrush = SolidColor(colors.NeonGreen),
6569
decorationBox = { innerTextField ->
66-
if (title.isEmpty()) {
70+
if (titleValue.text.isEmpty()) {
6771
Text(
6872
text = stringResource(R.string.vote_title_placeholder),
6973
color = colors.Grey02,
@@ -116,12 +120,14 @@ fun VoteInputSection(
116120
@Preview
117121
@Composable
118122
private fun VoteInputSectionPreview() {
119-
var title by rememberSaveable { mutableStateOf("") }
123+
var value by rememberSaveable(stateSaver = TextFieldValue.Saver) {
124+
mutableStateOf(TextFieldValue(""))
125+
}
120126
var options by rememberSaveable { mutableStateOf(mutableListOf("", "")) }
121127

122128
VoteInputSection(
123-
title = title,
124-
onTitleChange = { title = it },
129+
titleValue = value,
130+
onTitleChange = { value = it },
125131
options = options,
126132
onOptionChange = { index, newText ->
127133
options = options.toMutableList().also { it[index] = newText }

app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
1818
import androidx.compose.runtime.setValue
1919
import androidx.compose.ui.Alignment
2020
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.focus.FocusRequester
2122
import androidx.compose.ui.layout.LayoutCoordinates
2223
import androidx.compose.ui.layout.positionInRoot
2324
import androidx.compose.ui.platform.LocalDensity
@@ -38,6 +39,7 @@ import com.texthip.thip.ui.group.note.viewmodel.GroupNoteCreateUiState
3839
import com.texthip.thip.ui.group.note.viewmodel.GroupNoteCreateViewModel
3940
import com.texthip.thip.ui.theme.ThipTheme
4041
import com.texthip.thip.utils.rooms.advancedImePadding
42+
import kotlinx.coroutines.delay
4143

4244
@Composable
4345
fun GroupNoteCreateScreen(
@@ -87,6 +89,15 @@ fun GroupNoteCreateContent(
8789
// Tooltip 위치 측정용 state
8890
val iconCoordinates = remember { mutableStateOf<LayoutCoordinates?>(null) }
8991

92+
val focusRequester = remember { FocusRequester() }
93+
94+
LaunchedEffect(uiState.isEditMode) {
95+
if (uiState.isEditMode) {
96+
delay(100)
97+
focusRequester.requestFocus()
98+
}
99+
}
100+
90101
Box(
91102
modifier = Modifier
92103
.fillMaxSize()
@@ -125,7 +136,8 @@ fun GroupNoteCreateContent(
125136

126137
OpinionInputSection(
127138
textFieldValue = uiState.opinionTextFieldValue,
128-
onTextChange = { onEvent(GroupNoteCreateEvent.OpinionChanged(it)) }
139+
onTextChange = { onEvent(GroupNoteCreateEvent.OpinionChanged(it)) },
140+
focusRequester = focusRequester
129141
)
130142
}
131143
}

app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
1818
import androidx.compose.runtime.setValue
1919
import androidx.compose.ui.Alignment
2020
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.focus.FocusRequester
2122
import androidx.compose.ui.layout.LayoutCoordinates
2223
import androidx.compose.ui.layout.positionInRoot
2324
import androidx.compose.ui.platform.LocalDensity
2425
import androidx.compose.ui.res.stringResource
26+
import androidx.compose.ui.text.input.TextFieldValue
2527
import androidx.compose.ui.tooling.preview.Preview
2628
import androidx.compose.ui.unit.dp
2729
import androidx.compose.ui.zIndex
@@ -38,6 +40,7 @@ import com.texthip.thip.ui.group.note.viewmodel.GroupVoteCreateUiState
3840
import com.texthip.thip.ui.group.note.viewmodel.GroupVoteCreateViewModel
3941
import com.texthip.thip.ui.theme.ThipTheme
4042
import com.texthip.thip.utils.rooms.advancedImePadding
43+
import kotlinx.coroutines.delay
4144

4245
@Composable
4346
fun GroupVoteCreateScreen(
@@ -86,6 +89,15 @@ fun GroupVoteCreateContent(
8689
var showTooltip by rememberSaveable { mutableStateOf(false) }
8790
val iconCoordinates = remember { mutableStateOf<LayoutCoordinates?>(null) }
8891

92+
val focusRequester = remember { FocusRequester() }
93+
94+
LaunchedEffect(uiState.isEditMode) {
95+
if (uiState.isEditMode) {
96+
delay(100)
97+
focusRequester.requestFocus()
98+
}
99+
}
100+
89101
Box(
90102
modifier = Modifier
91103
.fillMaxSize()
@@ -120,7 +132,7 @@ fun GroupVoteCreateContent(
120132
)
121133

122134
VoteInputSection(
123-
title = uiState.title,
135+
titleValue = uiState.titleValue,
124136
onTitleChange = { onEvent(GroupVoteCreateEvent.TitleChanged(it)) },
125137
options = uiState.options,
126138
onOptionChange = { index, newText ->
@@ -130,7 +142,8 @@ fun GroupVoteCreateContent(
130142
onRemoveOption = { index ->
131143
onEvent(GroupVoteCreateEvent.RemoveOptionClicked(index))
132144
},
133-
isEnabled = !uiState.isEditMode
145+
isEnabled = !uiState.isEditMode,
146+
focusRequester = focusRequester
134147
)
135148
}
136149
}
@@ -173,7 +186,7 @@ private fun GroupVoteCreateScreenPreview() {
173186
GroupVoteCreateContent(
174187
uiState = GroupVoteCreateUiState(
175188
pageText = "123",
176-
title = "가장 인상깊은 구절은?",
189+
titleValue = TextFieldValue("이번 모임의 장소는 어디가 좋을까요?"),
177190
options = listOf("1연 1행", "2연 3행", ""),
178191
bookTotalPage = 600,
179192
isGeneralReviewEnabled = true

app/src/main/java/com/texthip/thip/ui/group/note/viewmodel/GroupVoteCreateViewModel.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.texthip.thip.ui.group.note.viewmodel
22

3+
import androidx.compose.ui.text.TextRange
4+
import androidx.compose.ui.text.input.TextFieldValue
35
import androidx.lifecycle.ViewModel
46
import androidx.lifecycle.viewModelScope
57
import com.texthip.thip.data.model.rooms.request.VoteItem
@@ -14,7 +16,7 @@ import javax.inject.Inject
1416
data class GroupVoteCreateUiState(
1517
// 입력 값
1618
val pageText: String = "",
17-
val title: String = "",
19+
val titleValue: TextFieldValue = TextFieldValue(""),
1820
val options: List<String> = listOf("", ""), // 옵션은 최소 2개로 시작
1921
val isGeneralReview: Boolean = false,
2022
val isEditMode: Boolean = false,
@@ -37,14 +39,14 @@ data class GroupVoteCreateUiState(
3739
get() {
3840
val filledOptionsCount = options.count { it.isNotBlank() }
3941
return (isGeneralReview || pageText.isNotBlank()) &&
40-
title.isNotBlank() &&
42+
titleValue.text.isNotBlank() &&
4143
filledOptionsCount >= 2
4244
}
4345
}
4446

4547
sealed interface GroupVoteCreateEvent {
4648
data class PageChanged(val text: String) : GroupVoteCreateEvent
47-
data class TitleChanged(val text: String) : GroupVoteCreateEvent
49+
data class TitleChanged(val newValue: TextFieldValue) : GroupVoteCreateEvent
4850
data class OptionChanged(val index: Int, val text: String) : GroupVoteCreateEvent
4951
data class GeneralReviewToggled(val isChecked: Boolean) : GroupVoteCreateEvent
5052
data object AddOptionClicked : GroupVoteCreateEvent
@@ -83,7 +85,10 @@ class GroupVoteCreateViewModel @Inject constructor(
8385
postId = postId,
8486
pageText = page.toString(),
8587
isGeneralReview = isOverview,
86-
title = title,
88+
titleValue = TextFieldValue(
89+
text = title,
90+
selection = TextRange(title.length)
91+
),
8792
options = options,
8893
bookTotalPage = totalPage,
8994
isGeneralReviewEnabled = isOverviewPossible
@@ -106,7 +111,7 @@ class GroupVoteCreateViewModel @Inject constructor(
106111
_uiState.update { it.copy(pageText = event.text) }
107112
}
108113

109-
is GroupVoteCreateEvent.TitleChanged -> _uiState.update { it.copy(title = event.text) }
114+
is GroupVoteCreateEvent.TitleChanged -> _uiState.update { it.copy(titleValue = event.newValue) }
110115
is GroupVoteCreateEvent.OptionChanged -> _uiState.update {
111116
val newOptions = it.options.toMutableList()
112117
newOptions[event.index] = event.text
@@ -152,7 +157,7 @@ class GroupVoteCreateViewModel @Inject constructor(
152157
roomsRepository.patchRoomsVote(
153158
roomId = roomId,
154159
voteId = postId,
155-
content = currentState.title,
160+
content = currentState.titleValue.text
156161
).onSuccess {
157162
_uiState.update { it.copy(isLoading = false, isSuccess = true) }
158163
}.onFailure { throwable ->
@@ -185,7 +190,7 @@ class GroupVoteCreateViewModel @Inject constructor(
185190
roomId = roomId,
186191
page = pageNumber,
187192
isOverview = currentState.isGeneralReview,
188-
content = currentState.title,
193+
content = currentState.titleValue.text,
189194
voteItemList = voteItems
190195
).onSuccess {
191196
_uiState.update { it.copy(isLoading = false, isSuccess = true) }

0 commit comments

Comments
 (0)