Skip to content

Commit bfbd80c

Browse files
authored
Merge pull request #73 from YAPP-Github/feat/#52-task-certification-detail
인증샷 보기 UI 구현
2 parents eb4421f + 48a8435 commit bfbd80c

File tree

65 files changed

+2917
-347
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2917
-347
lines changed

app/src/main/java/com/yapp/twix/main/MainActivity.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.enableEdgeToEdge
77
import androidx.compose.foundation.layout.Box
8-
import androidx.compose.foundation.layout.fillMaxSize
9-
import androidx.compose.foundation.layout.safeContentPadding
8+
import androidx.compose.foundation.layout.WindowInsets
9+
import androidx.compose.foundation.layout.WindowInsetsSides
10+
import androidx.compose.foundation.layout.only
11+
import androidx.compose.foundation.layout.systemBars
12+
import androidx.compose.foundation.layout.windowInsetsPadding
1013
import androidx.compose.ui.Alignment
1114
import androidx.compose.ui.Modifier
1215
import androidx.core.view.WindowCompat
@@ -31,8 +34,9 @@ class MainActivity : ComponentActivity() {
3134
Box(
3235
modifier =
3336
Modifier
34-
.fillMaxSize()
35-
.safeContentPadding(),
37+
.windowInsetsPadding(
38+
WindowInsets.systemBars.only(WindowInsetsSides.Vertical),
39+
),
3640
) {
3741
AppNavHost()
3842

core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentBox.kt

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@ import androidx.compose.foundation.layout.padding
77
import androidx.compose.runtime.Composable
88
import androidx.compose.ui.Alignment
99
import androidx.compose.ui.Modifier
10-
import androidx.compose.ui.geometry.Rect
11-
import androidx.compose.ui.layout.boundsInRoot
12-
import androidx.compose.ui.layout.onGloballyPositioned
1310
import androidx.compose.ui.res.stringResource
14-
import androidx.compose.ui.text.input.TextFieldValue
1511
import androidx.compose.ui.unit.dp
1612
import com.twix.designsystem.R
1713
import com.twix.designsystem.components.comment.model.CommentUiModel
@@ -22,34 +18,25 @@ import com.twix.domain.model.enums.AppTextStyle
2218
@Composable
2319
fun CommentBox(
2420
uiModel: CommentUiModel,
25-
onCommentChanged: (TextFieldValue) -> Unit,
21+
onCommentChanged: (String) -> Unit,
2622
onFocusChanged: (Boolean) -> Unit,
27-
onGuideTextPositioned: (Rect) -> Unit,
28-
onTextFieldPositioned: (Rect) -> Unit,
2923
modifier: Modifier = Modifier,
3024
) {
3125
Column(
3226
horizontalAlignment = Alignment.CenterHorizontally,
3327
modifier = modifier,
3428
) {
35-
if (uiModel.isFocused) {
36-
AppText(
37-
text = stringResource(R.string.comment_condition_guide),
38-
style = AppTextStyle.B2,
39-
color = GrayColor.C100,
40-
modifier =
41-
Modifier.onGloballyPositioned {
42-
onGuideTextPositioned(it.boundsInRoot())
43-
},
44-
)
29+
AppText(
30+
text = if (uiModel.isFocused) stringResource(R.string.comment_condition_guide) else "",
31+
style = AppTextStyle.B2,
32+
color = GrayColor.C100,
33+
)
4534

46-
Spacer(modifier = Modifier.height(8.dp))
47-
}
35+
Spacer(modifier = Modifier.height(8.dp))
4836
CommentTextField(
4937
uiModel = uiModel,
50-
onCommentChanged = onCommentChanged,
38+
onCommitComment = onCommentChanged,
5139
onFocusChanged = onFocusChanged,
52-
onPositioned = onTextFieldPositioned,
5340
modifier =
5441
Modifier
5542
.padding(bottom = 20.dp),

core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentTextField.kt

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package com.twix.designsystem.components.comment
33
import androidx.compose.foundation.layout.Arrangement
44
import androidx.compose.foundation.layout.Box
55
import androidx.compose.foundation.layout.Row
6-
import androidx.compose.foundation.text.BasicTextField
6+
import androidx.compose.foundation.layout.width
7+
import androidx.compose.foundation.text.KeyboardActions
78
import androidx.compose.foundation.text.KeyboardOptions
9+
import androidx.compose.material3.TextField
810
import androidx.compose.runtime.Composable
911
import androidx.compose.runtime.LaunchedEffect
1012
import androidx.compose.runtime.getValue
1113
import androidx.compose.runtime.mutableStateOf
1214
import androidx.compose.runtime.remember
15+
import androidx.compose.runtime.saveable.rememberSaveable
1316
import androidx.compose.runtime.setValue
1417
import androidx.compose.ui.Modifier
1518
import androidx.compose.ui.draw.alpha
@@ -23,22 +26,18 @@ import androidx.compose.ui.graphics.drawscope.Stroke
2326
import androidx.compose.ui.layout.boundsInRoot
2427
import androidx.compose.ui.layout.onGloballyPositioned
2528
import androidx.compose.ui.platform.LocalFocusManager
26-
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
2729
import androidx.compose.ui.res.stringResource
28-
import androidx.compose.ui.text.TextRange
2930
import androidx.compose.ui.text.input.ImeAction
30-
import androidx.compose.ui.text.input.TextFieldValue
3131
import androidx.compose.ui.tooling.preview.Preview
3232
import androidx.compose.ui.unit.Dp
3333
import androidx.compose.ui.unit.dp
3434
import com.twix.designsystem.R
3535
import com.twix.designsystem.components.comment.model.CommentUiModel
36-
import com.twix.designsystem.keyboard.Keyboard
37-
import com.twix.designsystem.keyboard.keyboardAsState
3836
import com.twix.designsystem.theme.GrayColor
3937
import com.twix.designsystem.theme.TwixTheme
4038
import com.twix.ui.extension.noRippleClickable
41-
import kotlinx.coroutines.android.awaitFrame
39+
import com.twix.ui.keyboard.Keyboard
40+
import com.twix.ui.keyboard.keyboardAsState
4241

4342
val CIRCLE_PADDING_START: Dp = 50.dp
4443
val CIRCLE_SIZE: Dp = 64.dp
@@ -47,35 +46,30 @@ private val CIRCLE_GAP: Dp = CIRCLE_PADDING_START - CIRCLE_SIZE
4746
@Composable
4847
fun CommentTextField(
4948
uiModel: CommentUiModel,
50-
onCommentChanged: (TextFieldValue) -> Unit,
51-
onFocusChanged: (Boolean) -> Unit,
52-
onPositioned: (Rect) -> Unit,
5349
modifier: Modifier = Modifier,
50+
enabled: Boolean = true,
51+
onCommitComment: (String) -> Unit = {},
52+
onFocusChanged: (Boolean) -> Unit = {},
53+
onPositioned: (Rect) -> Unit = {},
5454
) {
5555
val focusManager = LocalFocusManager.current
5656
val focusRequester = remember { FocusRequester() }
57-
val keyboardController = LocalSoftwareKeyboardController.current
58-
val placeholder = stringResource(R.string.comment_text_field_placeholder)
57+
val keyboardState by keyboardAsState()
5958

60-
val keyboardVisibility by keyboardAsState()
59+
var internalValue by rememberSaveable(uiModel.comment) { mutableStateOf(uiModel.comment) }
60+
var isInitialized by remember { mutableStateOf(false) }
6161

62-
LaunchedEffect(keyboardVisibility) {
63-
when (keyboardVisibility) {
64-
Keyboard.Opened -> Unit
65-
Keyboard.Closed -> {
66-
focusManager.clearFocus()
67-
onFocusChanged(false)
68-
}
69-
}
70-
}
71-
72-
LaunchedEffect(uiModel.isFocused) {
73-
if (uiModel.isFocused) {
74-
focusRequester.requestFocus()
75-
awaitFrame()
76-
keyboardController?.show()
62+
LaunchedEffect(keyboardState) {
63+
if (!isInitialized) {
64+
isInitialized = true
7765
} else {
78-
keyboardController?.hide()
66+
when (keyboardState) {
67+
Keyboard.Opened -> Unit
68+
Keyboard.Closed -> {
69+
onCommitComment(internalValue.trim())
70+
focusManager.clearFocus()
71+
}
72+
}
7973
}
8074
}
8175

@@ -88,17 +82,24 @@ fun CommentTextField(
8882
focusRequester.requestFocus()
8983
},
9084
) {
91-
BasicTextField(
92-
value = uiModel.comment,
93-
onValueChange = { newValue -> onCommentChanged(newValue) },
85+
TextField(
86+
value = internalValue,
87+
onValueChange = { newValue ->
88+
if (newValue.length <= CommentUiModel.COMMENT_COUNT) {
89+
internalValue = newValue
90+
}
91+
},
92+
enabled = enabled,
9493
modifier =
9594
Modifier
95+
.width(0.dp)
9696
.alpha(0f)
9797
.focusRequester(focusRequester)
98-
.onFocusChanged { focusState ->
99-
onFocusChanged(focusState.isFocused)
98+
.onFocusChanged { state ->
99+
onFocusChanged(state.isFocused)
100100
},
101101
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
102+
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
102103
singleLine = true,
103104
)
104105

@@ -125,22 +126,19 @@ fun CommentTextField(
125126
) {
126127
repeat(CommentUiModel.COMMENT_COUNT) { index ->
127128
val char =
128-
if (uiModel.hidePlaceholder) {
129-
uiModel.comment.text
130-
.getOrNull(index)
131-
?.toString()
129+
if (uiModel.isFocused || internalValue.isNotEmpty()) {
130+
internalValue.getOrNull(index)?.toString()
132131
} else {
133-
placeholder.getOrNull(index)?.toString()
132+
stringResource(R.string.comment_text_field_placeholder)[index].toString()
134133
}.orEmpty()
135134

136135
CommentCircle(
137136
text = char,
138-
showPlaceholder = !uiModel.hidePlaceholder,
139-
showCursor = uiModel.showCursor(index),
137+
showPlaceholder = !uiModel.isFocused && internalValue.isEmpty(),
138+
showCursor = uiModel.isFocused && index == internalValue.length,
140139
modifier =
141140
Modifier.noRippleClickable {
142141
focusRequester.requestFocus()
143-
onCommentChanged(uiModel.comment.copy(selection = TextRange(uiModel.comment.text.length)))
144142
},
145143
)
146144
}
@@ -152,11 +150,10 @@ fun CommentTextField(
152150
@Composable
153151
private fun CommentTextFieldPreview() {
154152
TwixTheme {
155-
var text by remember { mutableStateOf(TextFieldValue("")) }
153+
var text by remember { mutableStateOf("") }
156154
var isFocused by remember { mutableStateOf(false) }
157155
CommentTextField(
158156
uiModel = CommentUiModel(text, isFocused),
159-
onCommentChanged = { text = it },
160157
onFocusChanged = { isFocused = it },
161158
onPositioned = {},
162159
)

core/design-system/src/main/java/com/twix/designsystem/components/comment/model/CommentUiModel.kt

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,25 @@
11
package com.twix.designsystem.components.comment.model
22

33
import androidx.compose.runtime.Immutable
4-
import androidx.compose.ui.text.input.TextFieldValue
54

65
@Immutable
76
data class CommentUiModel(
8-
val comment: TextFieldValue = TextFieldValue(""),
7+
val comment: String = "",
98
val isFocused: Boolean = false,
109
) {
11-
val isEmpty: Boolean
12-
get() = comment.text.isEmpty()
13-
1410
val hasMaxCommentLength: Boolean
15-
get() = comment.text.length == COMMENT_COUNT
11+
get() = comment.length == COMMENT_COUNT
1612

1713
val canUpload: Boolean
1814
get() =
19-
comment.text.isEmpty() ||
20-
comment.text.isNotEmpty() &&
15+
comment.isEmpty() ||
16+
comment.isNotEmpty() &&
2117
hasMaxCommentLength
2218

23-
fun updateComment(newComment: TextFieldValue): CommentUiModel {
24-
if (comment.text.length > COMMENT_COUNT) return this
25-
return copy(comment = newComment)
26-
}
19+
fun updateComment(newComment: String): CommentUiModel = copy(comment = newComment)
2720

2821
fun updateFocus(isFocused: Boolean) = copy(isFocused = isFocused)
2922

30-
/**
31-
* 특정 index 위치에 커서를 표시할지 여부를 반환한다.
32-
*
33-
* 표시 조건
34-
* 1. 포커스 상태일 것
35-
* 2. 현재 selection 시작 위치가 해당 index 일 것
36-
* 3. 해당 위치에 문자가 없을 것 (빈 칸)
37-
*
38-
* @param index 확인할 문자 위치
39-
*/
40-
fun showCursor(index: Int): Boolean {
41-
val isCharEmpty = comment.text.getOrNull(index) == null
42-
return isFocused && comment.selection.start == index && isCharEmpty
43-
}
44-
45-
/**
46-
* 플레이스홀더 표시 여부.
47-
*
48-
* 표시 조건
49-
* - 포커스 중이 아니거나
50-
* - 텍스트가 하나라도 존재하지 않으면
51-
*
52-
*/
53-
val hidePlaceholder: Boolean
54-
get() = isFocused || comment.text.isNotEmpty()
55-
5623
companion object {
5724
const val COMMENT_COUNT = 5
5825
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="132dp"
3+
android:height="113dp"
4+
android:viewportWidth="133"
5+
android:viewportHeight="113">
6+
<path
7+
android:pathData="M80.05,63.6C77.3,63.46 75.18,61.12 75.32,58.36C75.46,55.61 77.8,53.49 80.56,53.63C83.31,53.77 85.43,56.11 85.29,58.87C85.16,61.62 82.81,63.74 80.05,63.6Z"
8+
android:fillColor="#171717"/>
9+
<path
10+
android:pathData="M80.83,26.65C85.05,21.38 82.71,13.5 82.01,11.96C79.66,7.43 77.44,6.95 75.05,6.99C72.66,7.01 70.19,8.39 69.78,10.48"
11+
android:strokeWidth="1.64572"
12+
android:fillColor="#00000000"
13+
android:strokeColor="#464646"
14+
android:strokeLineCap="round"/>
15+
<path
16+
android:pathData="M74.56,15.42C75.6,13.98 78,12.13 82.35,13.4"
17+
android:strokeWidth="1.64572"
18+
android:fillColor="#00000000"
19+
android:strokeColor="#464646"
20+
android:strokeLineCap="round"/>
21+
<path
22+
android:pathData="M53.1,71.09L43.28,55.28L36.92,61.92L34.75,69.41L36.92,74.4L41.6,77.14L48.63,75.96L53.1,71.09Z"
23+
android:fillColor="#ffffff"/>
24+
<path
25+
android:pathData="M42.78,55.65C37.7,59.17 32.86,66.43 36.14,72.93C40.23,81.05 51.8,74.5 53.21,71.36"
26+
android:strokeWidth="1.64572"
27+
android:fillColor="#00000000"
28+
android:strokeColor="#464646"
29+
android:strokeLineCap="round"/>
30+
<path
31+
android:pathData="M128.59,103.61a9.38,43.76 90,1 1,-87.52 0a9.38,43.76 90,1 1,87.52 0z"
32+
android:fillColor="#C6C6C6"/>
33+
<path
34+
android:pathData="M127.71,60.01C127.71,38.9 111.11,21.55 89.88,19.5C84.78,19.53 73.66,20.14 69.97,22.27C54.54,28.3 43.64,42.9 43.64,60.01C43.64,82.49 62.44,100.72 85.68,100.72C108.91,100.72 127.71,82.49 127.71,60.01Z"
35+
android:strokeWidth="1.64572"
36+
android:fillColor="#ffffff"
37+
android:strokeColor="#464646"
38+
android:strokeLineCap="round"/>
39+
<path
40+
android:pathData="M60.13,49.6C59.5,55.39 67.21,60.97 77.34,62.07C87.47,63.17 96.2,59.37 96.82,53.58C97.45,47.79 89.72,42.99 79.58,41.89C69.45,40.79 60.76,43.81 60.13,49.6Z"
41+
android:fillColor="#C6C6C6"/>
42+
<path
43+
android:pathData="M96.8,57.37C95.07,57.28 93.75,55.81 93.83,54.09C93.92,52.36 95.39,51.03 97.11,51.12C98.84,51.21 100.16,52.68 100.08,54.4C99.99,56.13 98.52,57.45 96.8,57.37Z"
44+
android:fillColor="#464646"/>
45+
<path
46+
android:pathData="M59.49,51.47C57.77,51.38 56.44,49.92 56.53,48.19C56.62,46.47 58.08,45.14 59.81,45.23C61.53,45.31 62.86,46.78 62.77,48.51C62.68,50.23 61.22,51.56 59.49,51.47Z"
47+
android:fillColor="#464646"/>
48+
<path
49+
android:pathData="M92.34,75.47C92.34,75.47 76,61.62 58.39,69.83"
50+
android:strokeWidth="1.64572"
51+
android:fillColor="#00000000"
52+
android:strokeColor="#464646"
53+
android:strokeLineCap="round"/>
54+
<path
55+
android:pathData="M108.71,77.74L120.33,62.13L127.51,68.9L130.68,73.6L130.68,79.59L127.51,84.55L121.35,86.11L112.36,82.59L108.71,77.74Z"
56+
android:fillColor="#ffffff"/>
57+
<path
58+
android:pathData="M123.27,64C128,67.82 133.32,75.06 129.33,81.71C124.34,90.02 110.82,82.96 109.22,79.69"
59+
android:strokeWidth="1.64572"
60+
android:fillColor="#00000000"
61+
android:strokeColor="#464646"
62+
android:strokeLineCap="round"/>
63+
<path
64+
android:pathData="M0.82,40.73L35.3,61.75"
65+
android:strokeWidth="1.64572"
66+
android:fillColor="#00000000"
67+
android:strokeColor="#464646"
68+
android:strokeLineCap="round"/>
69+
</vector>

0 commit comments

Comments
 (0)