diff --git a/app/src/main/java/com/texthip/thip/ui/common/buttons/ExpandableFloatingButton.kt b/app/src/main/java/com/texthip/thip/ui/common/buttons/ExpandableFloatingButton.kt index a9378da4..48b318e5 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/buttons/ExpandableFloatingButton.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/buttons/ExpandableFloatingButton.kt @@ -65,7 +65,7 @@ fun ExpandableFloatingButton( ) { Column( modifier = Modifier - .padding(bottom = 94.dp, end = 20.dp) + .padding(bottom = 62.dp) .width(184.dp) .background( color = colors.Black, @@ -100,8 +100,7 @@ fun ExpandableFloatingButton( contentColor = colors.NeonGreen, shape = CircleShape, modifier = Modifier - .padding(16.dp) - .size(56.dp) + .size(50.dp) .border(width = 2.dp, color = colors.NeonGreen50, shape = CircleShape) ) { Icon( diff --git a/app/src/main/java/com/texthip/thip/ui/common/buttons/ToggleSwitchButton.kt b/app/src/main/java/com/texthip/thip/ui/common/buttons/ToggleSwitchButton.kt index 5e70609c..4e3f8c07 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/buttons/ToggleSwitchButton.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/buttons/ToggleSwitchButton.kt @@ -2,6 +2,7 @@ package com.texthip.thip.ui.common.buttons import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.Switch @@ -22,20 +23,26 @@ import com.texthip.thip.ui.theme.ThipTheme.colors @Composable fun ToggleSwitchButton( isChecked: Boolean, + enabled: Boolean = true, onToggleChange: (Boolean) -> Unit ) { - //var isChecked by remember { mutableStateOf(true) } Switch( - modifier = Modifier.padding(4.dp), + modifier = Modifier.height(30.dp), checked = isChecked, onCheckedChange = onToggleChange, colors = SwitchDefaults.colors( checkedThumbColor = colors.White, + disabledCheckedThumbColor = colors.White, checkedTrackColor = colors.Purple, + disabledCheckedTrackColor = colors.Purple, uncheckedThumbColor = colors.White, + disabledUncheckedThumbColor = colors.White, uncheckedTrackColor = colors.DarkGrey, + disabledUncheckedTrackColor = colors.DarkGrey, uncheckedBorderColor = Color.Transparent, + disabledUncheckedBorderColor = Color.Transparent, ), + enabled = enabled, thumbContent = { Icon( painter = painterResource(R.drawable.ic_circle), diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt index fe832cae..3061c3f9 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/BookPageTextField.kt @@ -1,11 +1,12 @@ package com.texthip.thip.ui.common.forms +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions @@ -41,27 +42,28 @@ import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun BookPageTextField( modifier: Modifier = Modifier, - bookPage: Int + bookTotalPage: Int, + enabled: Boolean = true, + text: String, + onValueChange: (String) -> Unit, ) { - var text by rememberSaveable { mutableStateOf("") } var isError by rememberSaveable { mutableStateOf(false) } var errorMessageRes by rememberSaveable { mutableStateOf(null) } - var errorMessageParam by rememberSaveable { mutableStateOf(0) } Column { OutlinedTextField( value = text, onValueChange = { newText: String -> if (newText.isEmpty() || newText.all { it.isDigit() }) { - text = newText + onValueChange(newText) + if (newText.isNotEmpty()) { val pageNum = newText.toInt() - isError = pageNum > bookPage - if (isError) { - errorMessageRes = R.string.error_page_over - errorMessageParam = bookPage + isError = pageNum > bookTotalPage + errorMessageRes = if (isError) { + R.string.error_page_over } else { - errorMessageRes = null + null } } else { isError = false @@ -69,51 +71,64 @@ fun BookPageTextField( } } }, - visualTransformation = SuffixTransformation( - suffix = "/${bookPage}p", - suffixColor = colors.Grey02 - ), + enabled = enabled, + visualTransformation = if (enabled) { + SuffixTransformation("/${bookTotalPage}p", colors.Grey02) + } else { + VisualTransformation.None + }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = modifier.size(width = 320.dp, height = 48.dp), + modifier = modifier + .size(width = 320.dp, height = 48.dp) + .then( + if (isError) + Modifier.border( + width = 1.dp, + color = colors.Red, + shape = RoundedCornerShape(12.dp) + ) + else Modifier + ), textStyle = typography.menu_r400_s14_h24.copy(lineHeight = 12.sp), maxLines = 1, shape = RoundedCornerShape(12.dp), colors = TextFieldDefaults.colors( focusedTextColor = colors.White, - focusedIndicatorColor = if (isError) colors.Red else Color.Transparent, - unfocusedIndicatorColor = if (isError) colors.Red else Color.Transparent, - focusedContainerColor = colors.Black, - unfocusedContainerColor = colors.Black, - cursorColor = colors.NeonGreen + unfocusedTextColor = colors.White, + disabledTextColor = colors.White, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + focusedContainerColor = colors.DarkGrey02, + unfocusedContainerColor = colors.DarkGrey02, + disabledContainerColor = colors.DarkGrey02, + cursorColor = colors.NeonGreen, ), trailingIcon = { - if (text.isNotEmpty()) { - Icon( - painter = painterResource(id = R.drawable.ic_x_circle_white), - contentDescription = "Clear text", - modifier = Modifier.clickable { - text = "" + Icon( + painter = painterResource(id = R.drawable.ic_x_circle_grey), + contentDescription = "Clear text", + modifier = Modifier.clickable { + if (text.isNotEmpty()) { + onValueChange("") isError = false errorMessageRes = null - }, - tint = Color.Unspecified - ) - } else { - Icon( - painter = painterResource(id = R.drawable.ic_x_circle), - contentDescription = "Clear text" - ) - } + } + }, + tint = Color.Unspecified + ) } ) - if (isError && errorMessageRes != null) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(id = errorMessageRes!!, errorMessageParam), - color = colors.Red, - style = typography.menu_r400_s14_h24.copy(lineHeight = 12.sp) - ) + Box(modifier = Modifier.height(22.dp)) { + if (isError && errorMessageRes != null) { + Text( + modifier = Modifier.padding(start = 4.dp, top = 8.dp), + text = stringResource(id = errorMessageRes!!), + color = colors.Red, + style = typography.menu_r400_s14_h24.copy(lineHeight = 12.sp) + ) + } } } } @@ -135,6 +150,7 @@ class SuffixTransformation( val offsetMapping = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int = offset.coerceAtMost(original.length) + override fun transformedToOriginal(offset: Int): Int = offset.coerceAtMost(original.length) } @@ -147,12 +163,18 @@ class SuffixTransformation( @Composable @Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) fun BookPageTextFieldPreviewEmpty() { + var text by rememberSaveable { mutableStateOf("") } + Box( modifier = Modifier.size(width = 360.dp, height = 200.dp), contentAlignment = Alignment.Center ) { BookPageTextField( - bookPage = 456 + bookTotalPage = 456, + text = text, + onValueChange = { + text = it + } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt b/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt index 6307dd2a..c9157d1e 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/forms/BorderedTextField.kt @@ -1,86 +1,141 @@ package com.texthip.thip.ui.common.forms +import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography @Composable fun BorderedTextField( modifier: Modifier = Modifier, - hint: String + hint: String, + text: String, + canRemove: Boolean = true, + onTextChange: (String) -> Unit, + onDelete: () -> Unit, + onRemoveField: () -> Unit ) { - var text by rememberSaveable { mutableStateOf("") } val myStyle = typography.menu_r400_s14_h24.copy(lineHeight = 14.sp) + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() - OutlinedTextField( - value = text, - onValueChange = { text = it }, - placeholder = { - Text( - text = hint, - color = colors.Grey02, - style = myStyle - ) - }, - textStyle = myStyle, - modifier = modifier.size(width = 320.dp, height = 48.dp), - shape = RoundedCornerShape(12.dp), - colors = TextFieldDefaults.colors( - focusedTextColor = colors.White, - focusedIndicatorColor = colors.Grey02, - unfocusedIndicatorColor = colors.Grey02, - focusedContainerColor = colors.Black00, - unfocusedContainerColor = colors.Black00, - cursorColor = colors.NeonGreen, - ), + val iconRes = when { + isFocused && text.isEmpty() -> R.drawable.ic_x_circle_darkgrey + isFocused && text.isNotEmpty() -> R.drawable.ic_x_circle_grey + !isFocused && canRemove -> R.drawable.ic_delete + else -> null + } + + val iconEnabled = when (iconRes) { + R.drawable.ic_x_circle_grey -> true + R.drawable.ic_delete -> true + else -> false + } + + val iconTint = when (iconRes) { + R.drawable.ic_delete -> colors.Grey02 + + else -> Color.Unspecified + } + + Box( + modifier = modifier + .fillMaxWidth() + .height(48.dp) + .border(width = 1.dp, color = colors.Grey02, shape = RoundedCornerShape(12.dp)), + ) { + BasicTextField( + value = text, + onValueChange = onTextChange, + textStyle = myStyle.copy(color = colors.White), + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .matchParentSize() + .padding(horizontal = 12.dp, vertical = 14.dp), + interactionSource = interactionSource, + cursorBrush = SolidColor(colors.NeonGreen), + decorationBox = { innerTextField -> + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box(modifier = Modifier.weight(1f)) { + if (text.isEmpty()) { + Text( + text = hint, + color = colors.Grey02, + style = myStyle + ) + } + innerTextField() + } - trailingIcon = { - if (text.isNotEmpty()) { - Icon( - painter = painterResource(id = R.drawable.ic_x_circle_white), - contentDescription = "Clear text", - modifier = Modifier.clickable { text = "" }, - tint = Color.Unspecified - ) - } else { - Icon( - painter = painterResource(id = R.drawable.ic_x_circle), - contentDescription = "Clear text" - ) + iconRes?.let { + Icon( + painter = painterResource(id = it), + contentDescription = "Action Icon", + modifier = Modifier + .clickable(enabled = iconEnabled) { + when (it) { + R.drawable.ic_x_circle_grey -> onDelete() + R.drawable.ic_delete -> onRemoveField() + } + }, + tint = iconTint + ) + } + } } - } - ) + ) + } } @Composable -@Preview(showBackground = true, backgroundColor = 0xFF000000, widthDp = 360, heightDp = 200) +@Preview() fun BorderedTextFieldPreview() { - Box( - modifier = Modifier.size(width = 360.dp, height = 200.dp), - contentAlignment = Alignment.Center - ) { + ThipTheme { + var text by rememberSaveable { mutableStateOf("") } + BorderedTextField( - hint = "가이드 텍스트를 입력" + hint = "가이드 텍스트를 입력", + text = text, + onTextChange = { newText -> + text = newText + }, + onDelete = { + text = "" + }, + onRemoveField = { + // 필드 제거 + }, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/modal/ToolTip.kt b/app/src/main/java/com/texthip/thip/ui/common/modal/ToolTip.kt index 183acbc1..dd292125 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/modal/ToolTip.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/modal/ToolTip.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -19,6 +18,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -27,7 +27,6 @@ import androidx.compose.ui.unit.dp import com.texthip.thip.R import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography -import androidx.compose.ui.platform.LocalDensity enum class ArrowPosition { LEFT, CENTER, RIGHT @@ -57,6 +56,7 @@ fun PopupModal( text: String, modifier: Modifier = Modifier, arrowPosition: ArrowPosition = ArrowPosition.RIGHT, + isEligible: Boolean = true, onClose: () -> Unit = {} ) { Column( @@ -70,14 +70,14 @@ fun PopupModal( val arrowModifier = when (arrowPosition) { ArrowPosition.LEFT -> Modifier .align(Alignment.TopStart) - .padding(start = 24.dp) + .padding(start = 64.dp) ArrowPosition.CENTER -> Modifier .align(Alignment.TopCenter) ArrowPosition.RIGHT -> Modifier .align(Alignment.TopEnd) - .padding(end = 24.dp) + .padding(end = 64.dp) } TriangleArrow( color = colors.DarkGrey, @@ -95,7 +95,7 @@ fun PopupModal( ) { Text( text = text, - color = colors.NeonGreen, + color = if (isEligible) colors.NeonGreen else colors.Red, style = typography.info_m500_s12, modifier = Modifier.weight(1f) ) @@ -117,25 +117,26 @@ fun PopupModal( private fun PopupPrev() { Column { PopupModal( - text = stringResource(R.string.condition_of_general_review,80), + text = stringResource(R.string.condition_of_general_review), modifier = Modifier .width(400.dp) .padding(16.dp), arrowPosition = ArrowPosition.LEFT ) PopupModal( - text = stringResource(R.string.condition_of_general_review,80), + text = stringResource(R.string.condition_of_general_review), modifier = Modifier .width(400.dp) .padding(16.dp), arrowPosition = ArrowPosition.CENTER ) PopupModal( - text = stringResource(R.string.condition_of_general_review,80), + text = stringResource(R.string.condition_of_general_review), modifier = Modifier .width(400.dp) .padding(16.dp), - arrowPosition = ArrowPosition.RIGHT + arrowPosition = ArrowPosition.RIGHT, + isEligible = false ) } } diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt new file mode 100644 index 00000000..81eadf69 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/OpinionInputSection.kt @@ -0,0 +1,94 @@ +package com.texthip.thip.ui.group.note.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun OpinionInputSection( + title: String = stringResource(R.string.my_opinion_title), + text: String, + onTextChange: (String) -> Unit, + hint: String = stringResource(R.string.my_opinion_placeholder), + maxLength: Int = 500 +) { + val isOverLimit = text.length > maxLength + val displayCount = if (text.length > maxLength) maxLength else text.length + + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text( + text = title, + style = typography.smalltitle_sb600_s18_h24, + color = colors.White + ) + + Box( + modifier = Modifier.fillMaxWidth() + ) { + BasicTextField( + value = text, + onValueChange = { + if (it.length <= maxLength) onTextChange(it) + }, + textStyle = typography.menu_r400_s14_h24.copy(color = colors.White), + modifier = Modifier + .fillMaxWidth(), + cursorBrush = SolidColor(colors.NeonGreen), + decorationBox = { innerTextField -> + Box( + modifier = Modifier.background(color = colors.Black) + ) { + if (text.isEmpty()) { + Text( + text = hint, + color = colors.Grey02, + style = typography.menu_r400_s14_h24 + ) + } + innerTextField() + } + } + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Text( + text = stringResource(R.string.group_input_count, displayCount, maxLength), + color = if (isOverLimit) colors.Red else colors.NeonGreen, + style = typography.info_r400_s12 + ) + } + } +} + +@Preview +@Composable +private fun OpinionInputSectionPreview() { + var text by rememberSaveable { mutableStateOf("") } + + OpinionInputSection( + text = text, + onTextChange = { text = it } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt new file mode 100644 index 00000000..752580fb --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/PageInputSection.kt @@ -0,0 +1,128 @@ +package com.texthip.thip.ui.group.note.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.common.buttons.ToggleSwitchButton +import com.texthip.thip.ui.common.forms.BookPageTextField +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun PageInputSection( + title: String = stringResource(R.string.page_to_record), + pageText: String, + onPageTextChange: (String) -> Unit, + isGeneralReview: Boolean, + onGeneralReviewToggle: (Boolean) -> Unit, + bookTotalPage: Int, + isEligible: Boolean, + onInfoClick: () -> Unit, + onInfoPositionCaptured: (LayoutCoordinates) -> Unit +) { + val allRangeText = stringResource(R.string.all_range) + + Column( + modifier = Modifier, + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = title, + style = typography.smalltitle_sb600_s18_h24, + color = colors.White + ) + + Box( + modifier = Modifier.height(90.dp), + ) { + BookPageTextField( + modifier = Modifier.fillMaxWidth(), + bookTotalPage = bookTotalPage, + text = if (isGeneralReview) allRangeText else pageText, + onValueChange = { + if (!isGeneralReview) onPageTextChange(it) + }, + enabled = !isGeneralReview + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomEnd), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), + verticalAlignment = Alignment.CenterVertically + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.ic_information), + contentDescription = null, + modifier = Modifier + .size(20.dp) + .onGloballyPositioned { coordinates -> + onInfoPositionCaptured(coordinates) + } + .clickable { onInfoClick() }, + tint = colors.Grey02 + ) + + Text( + text = stringResource(R.string.general_review), + style = typography.info_r400_s12, + color = colors.Grey + ) + } + + ToggleSwitchButton( + isChecked = isGeneralReview, + onToggleChange = { checked -> + onGeneralReviewToggle(checked) + onPageTextChange(if (checked) allRangeText else "") + }, + enabled = isEligible + ) + } + } + } +} + +@Preview +@Composable +private fun PageInputSectionPreview() { + var pageText by rememberSaveable { mutableStateOf("") } + var isGeneralReview by rememberSaveable { mutableStateOf(false) } + + PageInputSection( + pageText = pageText, + onPageTextChange = { pageText = it }, + isGeneralReview = isGeneralReview, + onGeneralReviewToggle = { isGeneralReview = it }, + bookTotalPage = 500, + isEligible = true, + onInfoClick = {}, + onInfoPositionCaptured = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt b/app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt new file mode 100644 index 00000000..6c780861 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/note/component/VoteInputSection.kt @@ -0,0 +1,134 @@ +package com.texthip.thip.ui.group.note.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.texthip.thip.R +import com.texthip.thip.ui.common.forms.BorderedTextField +import com.texthip.thip.ui.theme.ThipTheme.colors +import com.texthip.thip.ui.theme.ThipTheme.typography + +@Composable +fun VoteInputSection( + title: String, + onTitleChange: (String) -> Unit, + options: List, + onOptionChange: (index: Int, newText: String) -> Unit, + onAddOption: () -> Unit, + onRemoveOption: (index: Int) -> Unit, + modifier: Modifier = Modifier, + maxOptionLength: Int = 20, + maxOptions: Int = 5 +) { + val focusManager = LocalFocusManager.current + + Column( + modifier = modifier + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + focusManager.clearFocus() // 바깥 클릭 시 포커스 해제 + }, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + BasicTextField( + value = title, + onValueChange = { if (it.length <= maxOptionLength) onTitleChange(it) }, + textStyle = typography.smalltitle_m500_s18_h24.copy(color = colors.White), + modifier = Modifier + .fillMaxWidth(), + cursorBrush = SolidColor(colors.NeonGreen), + decorationBox = { innerTextField -> + if (title.isEmpty()) { + Text( + text = stringResource(R.string.vote_title_placeholder), + color = colors.Grey02, + style = typography.smalltitle_m500_s18_h24 + ) + } + innerTextField() + } + ) + + options.forEachIndexed { index, option -> + val canRemove = index >= 2 // 3번째 항목부터 삭제 가능 + BorderedTextField( + text = option, + onTextChange = { + if (it.length <= maxOptionLength) onOptionChange(index, it) + }, + onDelete = { onOptionChange(index, "") }, // x 버튼 클릭 시 내용 삭제 + onRemoveField = { if (canRemove) onRemoveOption(index) }, // 쓰레기통 클릭 시 항목 제거 + canRemove = canRemove, + hint = stringResource(R.string.vote_content_placeholder) + ) + } + + if (options.size < maxOptions) { + Button( + onClick = { + focusManager.clearFocus() // 항목 추가 시 포커스 해제 + onAddOption() + }, + modifier = Modifier + .fillMaxWidth() + .height(44.dp), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = colors.White, + ), + ) { + Text( + text = stringResource(R.string.add_content), + style = typography.smalltitle_sb600_s16_h24, + color = colors.Black + ) + } + } + } +} + +@Preview +@Composable +private fun VoteInputSectionPreview() { + var title by rememberSaveable { mutableStateOf("") } + var options by rememberSaveable { mutableStateOf(mutableListOf("", "")) } + + VoteInputSection( + title = title, + onTitleChange = { title = it }, + options = options, + onOptionChange = { index, newText -> + options = options.toMutableList().also { it[index] = newText } + }, + onAddOption = { + if (options.size < 5) { + options = options.toMutableList().also { it.add("") } + } + }, + onRemoveOption = { index -> + options = options.toMutableList().also { it.removeAt(index) } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt new file mode 100644 index 00000000..083c01c5 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupNoteCreateScreen.kt @@ -0,0 +1,110 @@ +package com.texthip.thip.ui.group.note.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.absoluteOffset +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.texthip.thip.R +import com.texthip.thip.ui.common.modal.ArrowPosition +import com.texthip.thip.ui.common.modal.PopupModal +import com.texthip.thip.ui.common.topappbar.InputTopAppBar +import com.texthip.thip.ui.group.note.component.OpinionInputSection +import com.texthip.thip.ui.group.note.component.PageInputSection +import com.texthip.thip.ui.theme.ThipTheme + +@Composable +fun GroupNoteCreateScreen() { + var pageText by rememberSaveable { mutableStateOf("") } + var isGeneralReview by rememberSaveable { mutableStateOf(false) } + var opinionText by rememberSaveable { mutableStateOf("") } + + val isFormFilled = pageText.isNotBlank() && opinionText.isNotBlank() + + val density = LocalDensity.current + var showTooltip by rememberSaveable { mutableStateOf(false) } + + // Tooltip 위치 측정용 state + val iconCoordinates = remember { mutableStateOf(null) } + + var isEligible by rememberSaveable { mutableStateOf(true) } // TODO: 서버 데이터? + + Box( + modifier = Modifier.fillMaxSize() + ) { + Column { + InputTopAppBar( + title = stringResource(R.string.write_record), + isRightButtonEnabled = isFormFilled, + onLeftClick = { /* 뒤로가기 동작 */ }, + onRightClick = { /* 완료 동작 */ } + ) + + Column( + modifier = Modifier + .padding(vertical = 32.dp, horizontal = 20.dp), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + PageInputSection( + pageText = pageText, + onPageTextChange = { pageText = it }, + isGeneralReview = isGeneralReview, + onGeneralReviewToggle = { isGeneralReview = it }, + isEligible = isEligible, + bookTotalPage = 600, + onInfoClick = { showTooltip = true }, + onInfoPositionCaptured = { iconCoordinates.value = it } + ) + + OpinionInputSection( + text = opinionText, + onTextChange = { opinionText = it } + ) + + } + } + if (showTooltip && iconCoordinates.value != null) { + val yOffsetDp = with(density) { + iconCoordinates.value!!.positionInRoot().y.toDp() + 32.dp + } + + Box( + modifier = Modifier + .absoluteOffset(y = yOffsetDp) + .padding(horizontal = 20.dp) + .zIndex(1f) + ) { + PopupModal( + text = stringResource(R.string.condition_of_general_review), + arrowPosition = ArrowPosition.RIGHT, + isEligible = isEligible, + onClose = { showTooltip = false } + ) + } + } + } + +} + +@Preview +@Composable +private fun GroupNoteCreateScreenPreview() { + ThipTheme { + GroupNoteCreateScreen() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt new file mode 100644 index 00000000..f3b3953d --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/group/note/screen/GroupVoteCreateScreen.kt @@ -0,0 +1,128 @@ +package com.texthip.thip.ui.group.note.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.absoluteOffset +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.texthip.thip.R +import com.texthip.thip.ui.common.modal.ArrowPosition +import com.texthip.thip.ui.common.modal.PopupModal +import com.texthip.thip.ui.common.topappbar.InputTopAppBar +import com.texthip.thip.ui.group.note.component.PageInputSection +import com.texthip.thip.ui.group.note.component.VoteInputSection +import com.texthip.thip.ui.theme.ThipTheme + +@Composable +fun GroupVoteCreateScreen() { + var pageText by rememberSaveable { mutableStateOf("") } + var isGeneralReview by rememberSaveable { mutableStateOf(false) } + + var title by rememberSaveable { mutableStateOf("") } + val options = remember { mutableStateListOf("", "") } + + val density = LocalDensity.current + var showTooltip by rememberSaveable { mutableStateOf(false) } + + // Tooltip 위치 측정용 state + val iconCoordinates = remember { mutableStateOf(null) } + + var isEligible by rememberSaveable { mutableStateOf(false) } // TODO: 서버 데이터? + + // 완료 버튼 활성화 조건 + val filledOptionsCount = options.count { it.isNotBlank() } + val isRightButtonEnabled = + (isGeneralReview || pageText.isNotBlank()) && + title.isNotBlank() && + filledOptionsCount >= 2 + + Box(modifier = Modifier.fillMaxSize()) { + Column { + InputTopAppBar( + title = stringResource(R.string.create_vote), + isRightButtonEnabled = isRightButtonEnabled, + onLeftClick = { /* 뒤로가기 동작 */ }, + onRightClick = { /* 완료 동작 */ } + ) + + Column( + modifier = Modifier + .padding(vertical = 32.dp, horizontal = 20.dp), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + PageInputSection( + pageText = pageText, + onPageTextChange = { pageText = it }, + isGeneralReview = isGeneralReview, + onGeneralReviewToggle = { isGeneralReview = it }, + isEligible = isEligible, + bookTotalPage = 600, + onInfoClick = { showTooltip = true }, + onInfoPositionCaptured = { iconCoordinates.value = it } + ) + + VoteInputSection( + title = title, + onTitleChange = { title = it }, + options = options, + onOptionChange = { index, newText -> + options[index] = newText + }, + onAddOption = { + if (options.size < 5) { + options.add("") + } + }, + onRemoveOption = { index -> + options.removeAt(index) + } + ) + } + } + + + if (showTooltip && iconCoordinates.value != null) { + val yOffsetDp = with(density) { + iconCoordinates.value!!.positionInRoot().y.toDp() + 32.dp + } + + Box( + modifier = Modifier + .absoluteOffset(y = yOffsetDp) + .padding(horizontal = 20.dp) + .zIndex(1f) + ) { + PopupModal( + text = stringResource(R.string.condition_of_general_review), + arrowPosition = ArrowPosition.RIGHT, + isEligible = isEligible, + onClose = { showTooltip = false } + ) + } + } + } +} + +@Preview +@Composable +private fun GroupVoteCreateScreenPreview() { + ThipTheme { + GroupVoteCreateScreen() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_x_circle_darkgrey.xml b/app/src/main/res/drawable/ic_x_circle_darkgrey.xml new file mode 100644 index 00000000..01013a94 --- /dev/null +++ b/app/src/main/res/drawable/ic_x_circle_darkgrey.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/ic_x_circle_grey.xml b/app/src/main/res/drawable/ic_x_circle_grey.xml index 300f0c30..b30ba54b 100644 --- a/app/src/main/res/drawable/ic_x_circle_grey.xml +++ b/app/src/main/res/drawable/ic_x_circle_grey.xml @@ -1,16 +1,16 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e61783ec..1b87df01 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,7 @@ %d시간 전 - 독서진행도 %d%%부터 총평을 작성할 수 있습니다. + 독서 진행도 80%를 달성해야 총평을 작성할 수 있어요. 댓글 작성이 완료되었습니다. 보러가기 소개 @@ -52,9 +52,6 @@ 기록장 🔥모임방의 뜨거운 감자 - - 해당 도서는 %1$dp까지만 있습니다. - 님의 구독자 다음 @@ -128,8 +125,8 @@ 참여 "%1$s님의 진행도 " 모임방 참여할 사람! - %1$s / %1$s명 + %1$s %1$s일 남음 참여하기 참여 취소하기 @@ -153,6 +150,8 @@ 방 나가기 방 신고하기 방 삭제하기 + + ~ p 그룹 기록 @@ -179,6 +178,15 @@ 참여를 취소하시겠어요? 참여 취소 후 다시 참여할 수 있어요. 모임방 참여가 취소되었어요! 다른 방을 찾아보세요. + 기록할 페이지 + 총평 + 전체범위 + 전체페이지를 초과할 수 없어요. + 지금 읽은 그 부분, 어땠나요? + ...한 생각이 들었어요.🤔 + 투표 내용을 20자 이내로 입력하세요. + 항목을 20자 이내로 입력 + 항목 추가 피드