diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt b/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt
index 09e80e72..a0a3e638 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt
@@ -3,9 +3,7 @@ package com.twix.designsystem.components.button
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width
@@ -14,8 +12,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.twix.designsystem.R
import com.twix.designsystem.components.text.AppText
import com.twix.designsystem.theme.CommonColor
import com.twix.designsystem.theme.GrayColor
@@ -24,54 +25,65 @@ import com.twix.domain.model.enums.AppTextStyle
@Composable
fun AppRoundButton(
- text: String,
- textColor: Color,
- backgroundColor: Color,
- modifier: Modifier = Modifier,
- textStyle: AppTextStyle = AppTextStyle.T2,
- borderColor: Color = GrayColor.C500,
- hasBorder: Boolean = true,
+ contentHeight: Dp,
+ contentColor: Color,
+ contentBorderColor: Color,
+ contentBorderWidth: Dp,
+ shadowHeight: Dp,
+ shadowOffset: Dp,
+ modifier: Modifier,
+ content: @Composable () -> Unit,
) {
- Box(
- modifier = modifier,
- ) {
- if (hasBorder) {
- Box(
- modifier =
- Modifier
- .fillMaxSize()
- .offset(y = 4.dp)
- .background(
- color = borderColor,
- shape = RoundedCornerShape(100),
- ),
- )
- }
+ val shape = RoundedCornerShape(999.dp)
+
+ Box(modifier = modifier) {
+ Box(
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .height(shadowHeight)
+ .offset(y = shadowOffset)
+ .background(color = contentBorderColor, shape = shape),
+ )
Box(
modifier =
Modifier
- .fillMaxSize()
- .background(
- color = backgroundColor,
- shape = RoundedCornerShape(100),
- ).then(
- if (hasBorder) {
- Modifier.border(
- width = 1.dp,
- color = borderColor,
- shape = RoundedCornerShape(100),
- )
- } else {
- Modifier
- },
+ .fillMaxWidth()
+ .height(contentHeight)
+ .background(color = contentColor, shape = shape)
+ .border(
+ color = contentBorderColor,
+ shape = shape,
+ width = contentBorderWidth,
),
contentAlignment = Alignment.Center,
+ ) {
+ content()
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun WhiteAppRoundButtonPreview() {
+ TwixTheme {
+ AppRoundButton(
+ modifier =
+ Modifier
+ .width(150.dp)
+ .height(74.dp),
+ contentColor = CommonColor.White,
+ contentHeight = 68.dp,
+ contentBorderColor = GrayColor.C500,
+ contentBorderWidth = 1.6.dp,
+ shadowHeight = 70.dp,
+ shadowOffset = 4.dp,
) {
AppText(
- style = textStyle,
- color = textColor,
- text = text,
+ style = AppTextStyle.T2,
+ color = GrayColor.C500,
+ text = "버튼 이름",
)
}
}
@@ -79,30 +91,75 @@ fun AppRoundButton(
@Preview(showBackground = true)
@Composable
-fun AppRoundButtonPreview() {
+private fun PokeAppRoundButtonPreview() {
+ TwixTheme {
+ AppRoundButton(
+ modifier =
+ Modifier
+ .width(64.dp)
+ .height(32.dp),
+ contentColor = CommonColor.White,
+ contentHeight = 28.dp,
+ contentBorderColor = GrayColor.C500,
+ contentBorderWidth = 1.dp,
+ shadowHeight = 31.dp,
+ shadowOffset = 1.dp,
+ ) {
+ AppText(
+ style = AppTextStyle.C2,
+ color = GrayColor.C500,
+ text = "찌르기!",
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true, backgroundColor = 0xFF000000)
+@Composable
+private fun BlackAppRoundButtonPreview() {
TwixTheme {
- Column {
- AppRoundButton(
- modifier =
- Modifier
- .width(330.dp)
- .height(68.dp),
- text = "버튼임니다",
- textColor = GrayColor.C500,
- backgroundColor = CommonColor.White,
+ AppRoundButton(
+ modifier =
+ Modifier
+ .width(150.dp)
+ .height(74.dp),
+ contentColor = GrayColor.C500,
+ contentHeight = 68.dp,
+ contentBorderColor = CommonColor.White,
+ contentBorderWidth = 1.6.dp,
+ shadowHeight = 70.dp,
+ shadowOffset = 4.dp,
+ ) {
+ AppText(
+ style = AppTextStyle.T2,
+ color = CommonColor.White,
+ text = "버튼 이름",
)
- Spacer(modifier = Modifier.height(10.dp))
- AppRoundButton(
- modifier =
- Modifier
- .width(330.dp)
- .height(68.dp),
- text = "버튼임니다",
- textColor = CommonColor.White,
- backgroundColor = GrayColor.C500,
- hasBorder = false,
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun LongAppRoundButtonPreview() {
+ TwixTheme {
+ AppRoundButton(
+ modifier =
+ Modifier
+ .width(330.dp)
+ .height(70.dp),
+ contentColor = CommonColor.White,
+ contentHeight = 68.dp,
+ contentBorderColor = GrayColor.C500,
+ contentBorderWidth = 1.6.dp,
+ shadowHeight = 70.dp,
+ shadowOffset = 4.dp,
+ ) {
+ AppText(
+ style = AppTextStyle.T2,
+ color = GrayColor.C500,
+ text = stringResource(R.string.photolog_editor_retake),
)
- Spacer(modifier = Modifier.height(10.dp))
}
}
}
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentAnchorFrame.kt b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentAnchorFrame.kt
index cce5a1ea..d448aa1a 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentAnchorFrame.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentAnchorFrame.kt
@@ -10,37 +10,48 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.ime
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
+import com.twix.designsystem.R
import com.twix.designsystem.components.comment.model.CommentUiModel
+import com.twix.designsystem.components.text.AppText
import com.twix.designsystem.theme.DimmedColor
+import com.twix.designsystem.theme.GrayColor
+import com.twix.domain.model.enums.AppTextStyle
import com.twix.ui.extension.noRippleClickable
/**
- * 특정 UI 요소(Anchor) 하단에 [CommentBox]를 배치하고, 키보드 활성화 상태에 따라 위치를 동적으로 조정하는 프레임 컴포저블
+ * 특정 UI 요소(Anchor) 하단에 [CommentTextField]를 배치하고, 키보드 활성화 상태에 따라 위치를 동적으로 조정하는 프레임 컴포저블
*
* 이 컴포저블은 평상시에는 [anchorBottom] 좌표를 기준으로 배치
* 키보드가 올라와 코맨트창이 가려질 경우 키보드 바로 위로 위치를 자동으로 이동
*
- * @param uiModel 댓글창의 상태(텍스트, 포커스 상태)를 담고 있는 데이터 모델
- * @param anchorBottom 댓글창 배치의 기준이 되는 상위 요소의 바닥(Bottom) Y 좌표 (px 단위)
- * @param onCommentChanged 댓글 내용이 변경될 때 호출되는 콜백
- * @param onFocusChanged 댓글창의 포커스 상태가 변경될 때 호출되는 콜백 (포커스 시 배경 딤 처리 등에 사용)
+ * @param uiModel 코멘트창의 상태(텍스트, 포커스 상태)를 담고 있는 데이터 모델
+ * @param anchorBottom 코멘트창 배치의 기준이 되는 상위 요소의 바닥(Bottom) Y 좌표 (px 단위).
+ * @param onCommentChanged 코멘트 내용이 변경될 때 호출되는 콜백
+ * @param onFocusChanged 코멘트창의 포커스 상태가 변경될 때 호출되는 콜백 (포커스 시 배경 딤 처리 등에 사용)
* @param modifier 레이아웃 수정을 위한 [Modifier]
*/
@Composable
fun CommentAnchorFrame(
uiModel: CommentUiModel,
anchorBottom: Float,
+ paddingBottom: Dp,
onCommentChanged: (String) -> Unit,
onFocusChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
@@ -49,15 +60,18 @@ fun CommentAnchorFrame(
val density = LocalDensity.current
val focusManager = LocalFocusManager.current
+
val imeBottom = WindowInsets.ime.getBottom(density)
- val paddingBottom = with(density) { 24.dp.toPx() }
+ val navBottom = WindowInsets.navigationBars.getBottom(density)
+
+ val commentTextFieldPaddingBottom = with(density) { paddingBottom.toPx() }
+ val guideTextPaddingBottom = with(density) { 8.dp.toPx() }
- var commentBoxHeight by remember { mutableFloatStateOf(0f) }
- val defaultY = anchorBottom - commentBoxHeight - paddingBottom
+ var commentTextFieldHeight by remember { mutableFloatStateOf(0f) }
+ var guideTextHeight by remember { mutableFloatStateOf(0f) }
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
val screenHeight = constraints.maxHeight.toFloat()
- val keyboardTop = screenHeight - imeBottom
AnimatedVisibility(
visible = uiModel.isFocused,
@@ -72,34 +86,96 @@ fun CommentAnchorFrame(
.noRippleClickable { focusManager.clearFocus() },
)
}
- CommentBox(
+
+ val commentTextFieldY =
+ calculateCommentFieldY(
+ anchorBottom = anchorBottom,
+ screenHeight = screenHeight,
+ imeBottom = imeBottom,
+ navBottom = navBottom,
+ textFieldHeight = commentTextFieldHeight,
+ paddingBottom = commentTextFieldPaddingBottom,
+ )
+
+ if (uiModel.isFocused) {
+ AppText(
+ text = stringResource(R.string.comment_condition_guide),
+ style = AppTextStyle.B2,
+ color = GrayColor.C100,
+ textAlign = TextAlign.Center,
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp)
+ .onSizeChanged { size -> guideTextHeight = size.height.toFloat() }
+ .offset {
+ // 코멘트 입력창 바로 위에서 텍스트 높이와 패딩만큼 올려서 배치
+ val offsetY = commentTextFieldY - guideTextHeight - guideTextPaddingBottom
+ IntOffset(0, offsetY.toInt())
+ },
+ )
+ }
+
+ CommentTextField(
uiModel = uiModel,
- onCommentChanged = onCommentChanged,
+ onCommitComment = onCommentChanged,
onFocusChanged = onFocusChanged,
onHeightMeasured = { height ->
- if (commentBoxHeight != height) commentBoxHeight = height
+ if (height != commentTextFieldHeight) commentTextFieldHeight = height
},
modifier =
Modifier
.fillMaxWidth()
- .offset {
- /**
- * 키보드가 활성화되었고, 댓글창이 키보드 위치보다 아래에 있어 가려지는 경우
- * */
- val targetY =
- if (imeBottom > 0 && (defaultY + commentBoxHeight) > keyboardTop) {
- /**
- * 화면 전체 높이 - 키보드 높이 - 코멘트 높이
- * */
- (screenHeight - imeBottom - commentBoxHeight).toInt()
- } else {
- /**
- * 키보드가 없거나, 댓글창이 키보드에 가려지지 않는 경우
- * */
- defaultY.toInt()
- }
- IntOffset(x = 0, y = targetY)
- },
+ .offset { IntOffset(x = 0, y = commentTextFieldY) },
)
}
}
+
+/**
+ * 키보드(IME) 상태에 따라 코멘트 입력창의 최적 Y 좌표(px)를 계산합니다.
+ *
+ * 키보드가 올라오지 않은 경우에는 Anchor 기준 위치를 반환하고,
+ * 키보드가 코멘트창을 조금이라도 가리는 경우에는 키보드 바로 위로 위치를 이동합니다.
+ *
+ * 모든 파라미터와 반환값은 px 단위입니다.
+ *
+ * @param anchorBottom 코멘트창이 기준으로 삼는 앵커 요소의 하단 Y 좌표 (px)
+ * @param screenHeight 화면 전체 높이 (px). 키보드 상단 좌표 계산에 사용
+ * @param imeBottom 키보드(IME) 인셋 높이 (px)
+ * @param navBottom 네비게이션 바 인셋 높이 (px)
+ * @param textFieldHeight 코멘트 입력창의 실측 높이 (px)
+ * @param paddingBottom 코멘트 입력창과 기준점 사이의 여백 (px)
+ * @return 코멘트 입력창을 배치할 Y 좌표 (px)
+ */
+private fun calculateCommentFieldY(
+ anchorBottom: Float,
+ screenHeight: Float,
+ imeBottom: Int,
+ navBottom: Int,
+ textFieldHeight: Float,
+ paddingBottom: Float,
+): Int {
+ // 1. 키보드가 없을 때의 기본 위치: 앵커 하단에서 코멘트창 높이 + 패딩만큼 위
+ val defaultY = anchorBottom - textFieldHeight - paddingBottom
+
+ // 2. 네비게이션 바 인셋을 제외한 키보드 순수 높이
+ // 제스처 내비게이션 환경에서는 navBottom이 0이므로 그대로 imeBottom이 사용됨
+ // 버튼 내비게이션 환경에서는 navBottom만큼 차감하여 실제 키보드 높이만 반영
+ val pureImeHeight = (imeBottom - navBottom).coerceAtLeast(0)
+
+ // 3. 키보드 상단 Y 좌표: 화면 하단에서 키보드 높이만큼 올라간 지점
+ val keyboardTop = screenHeight - pureImeHeight
+
+ // 4. 키보드가 활성화된 경우 코멘트창을 키보드 바로 위에 배치할 Y 좌표
+ val keyboardTopY = keyboardTop - textFieldHeight - paddingBottom
+
+ // 판정: 키보드가 올라와 있고, 기본 위치의 코멘트창 하단이 키보드 상단을 침범하는 경우
+ // 즉 키보드가 조금이라도 코멘트창을 가리면 키보드 위로 즉시 이동
+ val isImeVisible = imeBottom > 0 && (defaultY + textFieldHeight + paddingBottom) > keyboardTop
+
+ return if (isImeVisible) {
+ keyboardTopY.toInt()
+ } else {
+ defaultY.toInt()
+ }
+}
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentBox.kt b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentBox.kt
deleted file mode 100644
index 1eaa61a6..00000000
--- a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentBox.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.twix.designsystem.components.comment
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.twix.designsystem.R
-import com.twix.designsystem.components.comment.model.CommentUiModel
-import com.twix.designsystem.components.text.AppText
-import com.twix.designsystem.theme.GrayColor
-import com.twix.domain.model.enums.AppTextStyle
-
-@Composable
-fun CommentBox(
- uiModel: CommentUiModel,
- onCommentChanged: (String) -> Unit,
- onFocusChanged: (Boolean) -> Unit,
- onHeightMeasured: (Float) -> Unit,
- modifier: Modifier = Modifier,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- modifier.onSizeChanged { size ->
- onHeightMeasured(size.height.toFloat())
- },
- ) {
- AppText(
- text = if (uiModel.isFocused) stringResource(R.string.comment_condition_guide) else "",
- style = AppTextStyle.B2,
- color = GrayColor.C100,
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- CommentTextField(
- uiModel = uiModel,
- onCommitComment = onCommentChanged,
- onFocusChanged = onFocusChanged,
- )
- }
-}
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentCircle.kt b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentCircle.kt
deleted file mode 100644
index bcb9d2ce..00000000
--- a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentCircle.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.twix.designsystem.components.comment
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.twix.designsystem.components.text.AppText
-import com.twix.designsystem.theme.CommonColor
-import com.twix.designsystem.theme.GrayColor
-import com.twix.designsystem.theme.TwixTheme
-import com.twix.domain.model.enums.AppTextStyle
-
-@Composable
-internal fun CommentCircle(
- text: String,
- showPlaceholder: Boolean,
- showCursor: Boolean,
- modifier: Modifier = Modifier,
-) {
- Box(
- contentAlignment = Alignment.Center,
- modifier =
- modifier
- .size(64.dp)
- .background(color = CommonColor.White, shape = CircleShape),
- ) {
- AppText(
- text = text,
- style = AppTextStyle.H1,
- color = if (showPlaceholder) GrayColor.C200 else GrayColor.C500,
- )
-
- if (showCursor) CursorBar()
- }
-}
-
-@Composable
-private fun CursorBar() {
- Box(
- modifier =
- Modifier
- .width(2.dp)
- .height(28.dp)
- .background(GrayColor.C500),
- )
-}
-
-@Preview
-@Composable
-private fun CommentCirclePreview() {
- TwixTheme {
- CommentCircle(text = "1", showPlaceholder = false, showCursor = false)
- }
-}
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentFieldBackground.kt b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentFieldBackground.kt
new file mode 100644
index 00000000..2875eb18
--- /dev/null
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentFieldBackground.kt
@@ -0,0 +1,175 @@
+package com.twix.designsystem.components.comment
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathOperation
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.twix.designsystem.theme.CommonColor
+import com.twix.designsystem.theme.GrayColor
+import com.twix.designsystem.theme.TwixTheme
+
+/**
+ * 코멘트 입력 UI의 배경이 되는 연결형 원형 도형을 그리는 컴포저블.
+ *
+ * 여러 개의 원을 가로 방향으로 배치한 뒤 [PathOperation.Union]으로 합쳐,
+ * 하나의 연결된 배경 도형처럼 렌더링한다.
+ *
+ * @param circleCount 배경을 구성하는 원의 개수.
+ * @param circleSize 각 원의 지름이자 전체 배경의 높이.
+ * @param circleCenterSpacing 인접한 원 중심점 사이의 거리.
+ * @param modifier 외부에서 전달받는 [Modifier].
+ */
+@Composable
+internal fun CommentFieldBackground(
+ circleCount: Int,
+ circleSize: Dp,
+ circleCenterSpacing: Dp,
+ modifier: Modifier = Modifier,
+) {
+ /**
+ * Canvas 내부 draw scope는 px 단위를 사용하므로,
+ * 디자인에서 정의된 dp 값을 실제 그리기에 사용할 px 값으로 변환한다.
+ */
+ val density = LocalDensity.current
+
+ /**
+ * 피그마 기준 테두리 두께(1.6dp)를 px로 변환한 값.
+ */
+ val strokeWidth = with(density) { 1.6.dp.toPx() }
+
+ /**
+ * 원 중심 간 간격을 px로 변환한 값.
+ * 각 원의 x 시작 위치(left)를 계산할 때 사용한다.
+ */
+ val circleSpacing = with(density) { circleCenterSpacing.toPx() }
+
+ /**
+ * 전체 배경 도형의 가로 길이.
+ *
+ * 첫 번째 원의 전체 너비(circleSize)에
+ * 나머지 원들이 중심 간격(circleCenterSpacing)만큼 오른쪽으로 이동하며 배치되므로
+ * 아래 공식으로 전체 폭을 계산한다.
+ *
+ * e.g
+ * - circleSize = 64.dp
+ * - circleCenterSpacing = 50.dp
+ * - circleCount = 5
+ * => totalWidth = 64 + 50 * 4 = 264.dp
+ */
+ val totalWidth = circleSize + circleCenterSpacing * (circleCount - 1)
+
+ Canvas(
+ modifier =
+ modifier
+ .width(totalWidth)
+ .height(circleSize),
+ ) {
+ /**
+ * Canvas의 높이를 원의 지름으로 사용한다.
+ * 이 컴포저블은 높이 == 원 지름인 정원(circular) 기준으로 동작한다.
+ */
+ val circleDiameter = size.height
+
+ /**
+ * 모든 원을 합쳐 만든 최종 Path.
+ *
+ * 처음에는 null이고,
+ * 원을 하나씩 만들면서 Union 연산으로 계속 누적한다.
+ */
+ var unionPath: Path? = null
+
+ repeat(circleCount) { index ->
+ /**
+ * 현재 원의 왼쪽 시작 x 좌표.
+ *
+ * 원의 중심 간격을 기준으로 각 원을 오른쪽으로 이동시킨다.
+ * 0번째 원 -> 0
+ * 1번째 원 -> spacing
+ * 2번째 원 -> spacing * 2
+ * ...
+ */
+ val left = index * circleSpacing
+
+ /**
+ * 현재 순서의 원 하나를 나타내는 Path.
+ *
+ * addOval(Rect)는 지정된 사각형에 맞는 타원을 추가한다.
+ */
+ val ovalPath =
+ Path().apply {
+ addOval(
+ Rect(
+ left = left,
+ top = 0f,
+ right = left + circleDiameter,
+ bottom = circleDiameter,
+ ),
+ )
+ }
+
+ /**
+ * 누적된 Path와 현재 원 Path를 합집합(Union)으로 결합한다.
+ *
+ * - 첫 번째 원이면 unionPath가 없으므로 그대로 사용
+ * - 두 번째 원부터는 기존 Path와 새 원을 합쳐 하나의 연결된 도형으로 만든다
+ *
+ * 이 과정 덕분에 원과 원이 겹치는 내부 경계선은 제거되고,
+ * 최종적으로 외곽선만 남는 하나의 shape처럼 다룰 수 있다.
+ */
+ unionPath =
+ if (unionPath == null) {
+ ovalPath
+ } else {
+ Path.combine(
+ operation = PathOperation.Union,
+ path1 = unionPath,
+ path2 = ovalPath,
+ )
+ }
+ }
+
+ /**
+ * circleCount가 0인 비정상 케이스를 방어한다.
+ * 정상 흐름에서는 null이 아니어야 한다.
+ */
+ val finalPath = unionPath ?: return@Canvas
+
+ /**
+ * 합쳐진 최종 배경 도형 내부를 흰색으로 채운다.
+ */
+ drawPath(
+ path = finalPath,
+ color = CommonColor.White,
+ )
+
+ /**
+ * 최종 Path의 외곽선을 그린다.
+ */
+ drawPath(
+ path = finalPath,
+ color = GrayColor.C500,
+ style = Stroke(strokeWidth),
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun CommentFieldBackgroundPreview() {
+ TwixTheme {
+ CommentFieldBackground(
+ circleCount = 5,
+ circleSize = 40.dp,
+ circleCenterSpacing = 30.dp,
+ )
+ }
+}
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentTextField.kt b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentTextField.kt
index 8f3ca856..68dedfa7 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentTextField.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/comment/CommentTextField.kt
@@ -1,8 +1,11 @@
package com.twix.designsystem.components.comment
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@@ -14,14 +17,13 @@ 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.draw.alpha
-import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
@@ -32,14 +34,16 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.twix.designsystem.R
import com.twix.designsystem.components.comment.model.CommentUiModel
+import com.twix.designsystem.components.text.AppText
import com.twix.designsystem.theme.GrayColor
import com.twix.designsystem.theme.TwixTheme
+import com.twix.domain.model.enums.AppTextStyle
import com.twix.ui.extension.noRippleClickable
import com.twix.ui.keyboard.Keyboard
import com.twix.ui.keyboard.keyboardAsState
-val CIRCLE_PADDING_START: Dp = 50.dp
-val CIRCLE_SIZE: Dp = 64.dp
+private val CIRCLE_PADDING_START: Dp = 50.dp
+private val CIRCLE_SIZE: Dp = 64.dp
private val CIRCLE_GAP: Dp = CIRCLE_PADDING_START - CIRCLE_SIZE
@Composable
@@ -47,6 +51,7 @@ fun CommentTextField(
uiModel: CommentUiModel,
modifier: Modifier = Modifier,
enabled: Boolean = true,
+ onHeightMeasured: (Float) -> Unit = {},
onCommitComment: (String) -> Unit = {},
onFocusChanged: (Boolean) -> Unit = {},
) {
@@ -98,19 +103,25 @@ fun CommentTextField(
}
Box(
+ contentAlignment = Alignment.Center,
modifier =
modifier
- .noRippleClickable {
+ .onSizeChanged { size ->
+ onHeightMeasured(size.height.toFloat())
+ }.noRippleClickable {
focusRequester.requestFocus()
},
) {
TextField(
value = internalValue,
onValueChange = { newValue ->
- if (newValue.text.length <= CommentUiModel.COMMENT_COUNT) {
+ val filteredText = newValue.text.filterNot(Char::isWhitespace)
+
+ if (filteredText.length <= CommentUiModel.COMMENT_COUNT) {
internalValue =
newValue.copy(
- selection = TextRange(newValue.text.length),
+ text = filteredText,
+ selection = TextRange(filteredText.length),
)
}
},
@@ -128,49 +139,73 @@ fun CommentTextField(
singleLine = true,
)
- Row(
- horizontalArrangement = Arrangement.spacedBy(CIRCLE_GAP),
- modifier =
- Modifier.drawWithCache {
- val radius = size.height / 2
- val paddingStart = CIRCLE_PADDING_START.toPx()
-
- onDrawBehind {
- repeat(CommentUiModel.COMMENT_COUNT) { index ->
- val cx = radius + index * paddingStart
-
- drawCircle(
- color = GrayColor.C500,
- radius = radius,
- center = Offset(cx, radius),
- style = Stroke(2.dp.toPx()),
- )
- }
- }
- },
+ Box(
+ contentAlignment = Alignment.Center,
) {
- repeat(CommentUiModel.COMMENT_COUNT) { index ->
- val char =
- if (uiModel.isFocused || internalValue.text.isNotEmpty()) {
- internalValue.text.getOrNull(index)?.toString()
- } else {
- stringResource(R.string.comment_text_field_placeholder)[index].toString()
- }.orEmpty()
-
- CommentCircle(
- text = char,
- showPlaceholder = !uiModel.isFocused && internalValue.text.isEmpty(),
- showCursor = uiModel.isFocused && index == internalValue.text.length,
- modifier =
- Modifier.noRippleClickable {
- focusRequester.requestFocus()
- },
- )
+ CommentFieldBackground(
+ circleCount = CommentUiModel.COMMENT_COUNT,
+ circleSize = CIRCLE_SIZE,
+ circleCenterSpacing = CIRCLE_PADDING_START,
+ )
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(CIRCLE_GAP),
+ ) {
+ repeat(CommentUiModel.COMMENT_COUNT) { index ->
+ val char =
+ if (uiModel.isFocused || internalValue.text.isNotEmpty()) {
+ internalValue.text.getOrNull(index)?.toString()
+ } else {
+ stringResource(R.string.comment_text_field_placeholder)[index].toString()
+ }.orEmpty()
+
+ CommentText(
+ text = char,
+ showPlaceholder = !uiModel.isFocused && internalValue.text.isEmpty(),
+ showCursor = uiModel.isFocused && index == internalValue.text.length,
+ modifier =
+ Modifier.noRippleClickable {
+ focusRequester.requestFocus()
+ },
+ )
+ }
}
}
}
}
+@Composable
+private fun CommentText(
+ text: String,
+ showPlaceholder: Boolean,
+ showCursor: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier.size(CIRCLE_SIZE),
+ ) {
+ AppText(
+ text = text,
+ style = AppTextStyle.H1,
+ color = if (showPlaceholder) GrayColor.C200 else GrayColor.C500,
+ )
+
+ if (showCursor) CursorBar()
+ }
+}
+
+@Composable
+private fun CursorBar() {
+ Box(
+ modifier =
+ Modifier
+ .width(2.dp)
+ .height(28.dp)
+ .background(GrayColor.C500),
+ )
+}
+
@Preview(showBackground = true)
@Composable
private fun CommentTextFieldPreview() {
@@ -180,6 +215,7 @@ private fun CommentTextFieldPreview() {
CommentTextField(
uiModel = CommentUiModel(text, isFocused),
onFocusChanged = { isFocused = it },
+ onHeightMeasured = {},
)
}
}
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/comment/model/CommentUiModel.kt b/core/design-system/src/main/java/com/twix/designsystem/components/comment/model/CommentUiModel.kt
index 284c0bad..21277408 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/comment/model/CommentUiModel.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/comment/model/CommentUiModel.kt
@@ -12,8 +12,8 @@ data class CommentUiModel(
val canUpload: Boolean
get() =
- value.isEmpty() ||
- value.isNotEmpty() &&
+ value.isBlank() ||
+ value.isNotBlank() &&
hasMaxCommentLength
fun updateComment(newComment: String): CommentUiModel = copy(value = newComment)
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/photolog/BackgroundCard.kt b/core/design-system/src/main/java/com/twix/designsystem/components/photolog/BackgroundCard.kt
index 482eda29..2bfa2de4 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/photolog/BackgroundCard.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/photolog/BackgroundCard.kt
@@ -43,7 +43,7 @@ fun BackgroundCard(
AppText(
text = uploadedAt,
style = AppTextStyle.B4,
- color = GrayColor.C500,
+ color = GrayColor.C300,
modifier =
Modifier
.padding(end = 36.dp, top = 14.dp)
@@ -61,10 +61,19 @@ fun BackgroundCard(
.width(150.dp)
.height(74.dp)
.noRippleClickable { onClickAction() },
- text = actionLabel,
- textColor = GrayColor.C500,
- backgroundColor = CommonColor.White,
- )
+ contentColor = CommonColor.White,
+ contentHeight = 68.dp,
+ contentBorderColor = GrayColor.C500,
+ contentBorderWidth = 1.6.dp,
+ shadowHeight = 70.dp,
+ shadowOffset = 4.dp,
+ ) {
+ AppText(
+ style = AppTextStyle.T2,
+ color = GrayColor.C500,
+ text = actionLabel,
+ )
+ }
}
Image(
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/photolog/ForegroundCard.kt b/core/design-system/src/main/java/com/twix/designsystem/components/photolog/ForegroundCard.kt
index 38303e28..b3b3f833 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/photolog/ForegroundCard.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/photolog/ForegroundCard.kt
@@ -2,6 +2,7 @@ package com.twix.designsystem.components.photolog
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.twix.designsystem.R
import com.twix.designsystem.components.text.AppText
@@ -31,7 +32,7 @@ fun ForegroundCard(
AppText(
text =
when (currentShow) {
- BetweenUs.ME -> stringResource(R.string.keep_it_up)
+ BetweenUs.ME -> stringResource(R.string.photolog_detail_upload_guide)
BetweenUs.PARTNER ->
stringResource(R.string.partner_not_photolog).format(
nickName,
@@ -39,6 +40,7 @@ fun ForegroundCard(
},
style = AppTextStyle.H2,
color = GrayColor.C500,
+ textAlign = TextAlign.Center,
)
}
}
@@ -49,7 +51,7 @@ fun ForegroundCard(
private fun ForegroundCardPreview() {
TwixTheme {
ForegroundCard(
- isCertificated = true,
+ isCertificated = false,
nickName = "닉네임",
imageUrl = "https://picsum.photos/200/300",
comment = null,
diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/photolog/PhotologCard.kt b/core/design-system/src/main/java/com/twix/designsystem/components/photolog/PhotologCard.kt
index c89f068b..848297b3 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/components/photolog/PhotologCard.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/components/photolog/PhotologCard.kt
@@ -28,6 +28,8 @@ fun PhotologCard(
rotation: Float = 0f,
content: @Composable BoxScope.() -> Unit = {},
) {
+ val shape = RoundedCornerShape(20.dp)
+
Box(
modifier =
modifier
@@ -35,11 +37,11 @@ fun PhotologCard(
.fillMaxWidth()
.aspectRatio(1f)
.rotate(rotation)
- .clip(shape = RoundedCornerShape(12.dp))
+ .clip(shape = shape)
.border(
width = 1.6.dp,
color = borderColor,
- shape = RoundedCornerShape(12.dp),
+ shape = shape,
).background(background),
contentAlignment = Alignment.Center,
content = content,
@@ -48,7 +50,7 @@ fun PhotologCard(
@Preview
@Composable
-fun PhotologCardPreview() {
+private fun PhotologCardPreview() {
TwixTheme {
PhotologCard(
rotation = 0f,
diff --git a/core/design-system/src/main/java/com/twix/designsystem/theme/Colors.kt b/core/design-system/src/main/java/com/twix/designsystem/theme/Colors.kt
index 197f0057..7c97bb29 100644
--- a/core/design-system/src/main/java/com/twix/designsystem/theme/Colors.kt
+++ b/core/design-system/src/main/java/com/twix/designsystem/theme/Colors.kt
@@ -13,6 +13,7 @@ object GrayColor {
object CommonColor {
val White = Color(0xFFFFFFFF)
+ val Black = Color(0xFF000000)
}
object DimmedColor {
diff --git a/core/design-system/src/main/res/drawable/ic_camera_torch_off.xml b/core/design-system/src/main/res/drawable/ic_camera_torch_off.xml
index db90d65e..8a781105 100644
--- a/core/design-system/src/main/res/drawable/ic_camera_torch_off.xml
+++ b/core/design-system/src/main/res/drawable/ic_camera_torch_off.xml
@@ -1,22 +1,18 @@
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
-
diff --git a/core/design-system/src/main/res/drawable/ic_camera_torch_on.xml b/core/design-system/src/main/res/drawable/ic_camera_torch_on.xml
index c3949a31..6758d592 100644
--- a/core/design-system/src/main/res/drawable/ic_camera_torch_on.xml
+++ b/core/design-system/src/main/res/drawable/ic_camera_torch_on.xml
@@ -1,14 +1,9 @@
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
-
+ android:pathData="M12.393,3.289C12.556,3.04 12.87,2.937 13.15,3.04C13.43,3.143 13.601,3.427 13.563,3.723L12.752,9.913H17.906C18.138,9.913 18.351,10.037 18.465,10.239C18.578,10.441 18.573,10.688 18.452,10.886L12.117,21.254C11.963,21.506 11.658,21.62 11.377,21.531C11.097,21.442 10.912,21.173 10.931,20.879L11.34,14.648H6.14C5.905,14.648 5.689,14.519 5.577,14.313C5.466,14.106 5.475,13.854 5.604,13.657L12.393,3.289Z"
+ android:fillColor="#ffffff"/>
diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml
index abf6eb67..c77e0576 100644
--- a/core/design-system/src/main/res/values/strings.xml
+++ b/core/design-system/src/main/res/values/strings.xml
@@ -114,6 +114,16 @@
인증샷 촬영을 위해서 카메라 권한이 필요해요.
알림 목록 조회에 실패했습니다.
찌르기에 실패했습니다.
+ 리액션 요청에 실패했어요.
+ 인증샷 등록에 실패했습니다.
+ 이미지 변환에 실패했습니다.
+ 인증샷 촬영에 실패했습니다. 다시 시도해 주세요.
+ 인증샷 조회에 실패했습니다.
+ 코멘트가 수정 되었어요.
+ 코멘트가 수정되지 않았어요.
+ 코멘트 수정에 실패했어요.
+ 인증샷 수정에 실패했어요.
+ 코멘트는 5글자로 입력해주세요!
%s\n목표를 이루셨나요?
@@ -131,27 +141,16 @@
업로드
- 이미지 캡처에 실패했습니다. 다시 시도해 주세요.
- 이미지를 불러오는 데 실패했습니다. 다시 시도해 주세요.
+ 업로드하기
인증샷 찍기
- 인증샷 등록에 실패했습니다.
- 이미지 변환에 실패했습니다.
인증샷 업로드 중...
잠시만 기다려 주세요.
- 코멘트는 5글자로 입력해주세요!
-
- 인증샷 조회에 실패했습니다.
- 인증샷 수정에 실패했어요.
+
+ 인증샷을\n올려보세요!
다시 찍기
- 코멘트가 수정 되었어요.
- 코멘트가 수정되지 않았어요.
- 코멘트 수정에 실패했어요.
- 리액션 요청에 실패했어요.
-
- 인증샷 촬영을 위해서 카메라 권한이 필요해요.
Google로 시작하기
diff --git a/feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt b/feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt
index e1b25212..9364325e 100644
--- a/feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt
+++ b/feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt
@@ -9,6 +9,7 @@ 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.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
@@ -89,17 +90,25 @@ private fun EmptyContent(
contentDescription = null,
modifier = Modifier.size(width = 85.dp, height = 53.dp),
)
-
AppRoundButton(
- text = stringResource(R.string.action_poke_emphasized),
- textColor = GrayColor.C500,
- textStyle = AppTextStyle.C2,
- backgroundColor = CommonColor.White,
modifier =
Modifier
- .size(width = 64.dp, height = 28.dp)
+ .width(64.dp)
+ .height(32.dp)
.noRippleClickable { onClick() },
- )
+ contentColor = CommonColor.White,
+ contentHeight = 28.dp,
+ contentBorderColor = GrayColor.C500,
+ contentBorderWidth = 1.dp,
+ shadowHeight = 31.dp,
+ shadowOffset = 1.dp,
+ ) {
+ AppText(
+ style = AppTextStyle.C2,
+ color = GrayColor.C500,
+ text = stringResource(R.string.action_poke_emphasized),
+ )
+ }
}
} else {
Column(
diff --git a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureScreen.kt b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureScreen.kt
index ef71b8fc..5880ec52 100644
--- a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureScreen.kt
+++ b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureScreen.kt
@@ -130,7 +130,7 @@ fun PhotologCaptureRoute(
onToggleCameraClick = {
viewModel.dispatch(PhotologCaptureIntent.ToggleLens)
},
- onClickFlash = {
+ onClickTorch = {
viewModel.dispatch(PhotologCaptureIntent.ToggleTorch)
},
onClickGallery = {
@@ -159,7 +159,7 @@ private fun PhotologCaptureScreen(
onClickClose: () -> Unit,
onCaptureClick: () -> Unit,
onToggleCameraClick: () -> Unit,
- onClickFlash: () -> Unit,
+ onClickTorch: () -> Unit,
onClickGallery: () -> Unit,
onClickRefresh: () -> Unit,
onClickUpload: () -> Unit,
@@ -200,7 +200,7 @@ private fun PhotologCaptureScreen(
capture = uiState.capture,
previewRequest = cameraPreview,
torch = uiState.torch,
- onClickFlash = onClickFlash,
+ onClickTorch = onClickTorch,
onPositioned = { previewBoxBottom = it },
)
@@ -219,6 +219,7 @@ private fun PhotologCaptureScreen(
CommentAnchorFrame(
uiModel = uiState.comment,
anchorBottom = previewBoxBottom,
+ paddingBottom = 28.dp,
onCommentChanged = onCommentChanged,
onFocusChanged = onFocusChanged,
)
@@ -235,7 +236,7 @@ fun PhotologCaptureScreenPreview() {
onClickClose = {},
onCaptureClick = {},
onToggleCameraClick = {},
- onClickFlash = {},
+ onClickTorch = {},
onClickGallery = {},
onClickRefresh = {},
onClickUpload = {},
diff --git a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureViewModel.kt b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureViewModel.kt
index da1c8d1e..1297034d 100644
--- a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureViewModel.kt
+++ b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/PhotologCaptureViewModel.kt
@@ -61,7 +61,7 @@ class PhotologCaptureViewModel(
private fun takePicture(uri: Uri?) {
uri?.let { reducePicture(it) } ?: showToast(
- R.string.photolog_image_capture_fail,
+ R.string.toast_image_capture_fail,
ToastType.ERROR,
)
}
@@ -112,7 +112,7 @@ class PhotologCaptureViewModel(
if (imageBytes != null) {
upload(imageBytes)
} else {
- showToast(R.string.photolog_image_translate_fail, ToastType.ERROR)
+ showToast(R.string.toast_image_translate_fail, ToastType.ERROR)
}
}
}
@@ -140,7 +140,7 @@ class PhotologCaptureViewModel(
onSuccess = { fileName -> handleUploadPhotologSuccess(fileName) },
onError = {
reduce { copy(isLoading = false) }
- showToast(R.string.photolog_upload_fail, ToastType.ERROR)
+ showToast(R.string.toast_photolog_upload_fail, ToastType.ERROR)
},
)
}
@@ -169,7 +169,7 @@ class PhotologCaptureViewModel(
},
onSuccess = { handleUploadPhotologSuccess() },
onError = {
- showToast(R.string.photolog_upload_fail, ToastType.ERROR)
+ showToast(R.string.toast_photolog_upload_fail, ToastType.ERROR)
},
)
}
@@ -198,7 +198,7 @@ class PhotologCaptureViewModel(
},
onSuccess = { handleModifyPhotologSuccess() },
onError = {
- showToast(R.string.photolog_modify_fail, ToastType.ERROR)
+ showToast(R.string.toast_photolog_modify_fail, ToastType.ERROR)
},
)
}
diff --git a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraControlBar.kt b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraControlBar.kt
index 5b5b9deb..80b2d0bd 100644
--- a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraControlBar.kt
+++ b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraControlBar.kt
@@ -2,7 +2,6 @@ package com.twix.photolog.capture.component
import androidx.compose.foundation.Image
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.Spacer
@@ -27,6 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.twix.designsystem.R
import com.twix.designsystem.components.button.AppRoundButton
+import com.twix.designsystem.components.text.AppText
import com.twix.designsystem.theme.CommonColor
import com.twix.designsystem.theme.GrayColor
import com.twix.designsystem.theme.TwixTheme
@@ -119,40 +119,40 @@ private fun ImageCapturedBar(
onClickUpload: () -> Unit,
modifier: Modifier = Modifier,
) {
- Box(
+ Row(
modifier =
modifier
.fillMaxWidth()
.padding(horizontal = 58.dp),
+ verticalAlignment = Alignment.CenterVertically,
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_camera_retake),
contentDescription = null,
modifier =
Modifier
- .size(52.dp)
- .align(Alignment.CenterStart)
.noRippleClickable(onClick = onClickRefresh),
)
- Row(
+ Spacer(modifier = Modifier.width(12.dp))
+
+ AppRoundButton(
modifier =
Modifier
- .align(Alignment.Center),
+ .width(150.dp)
+ .height(74.dp)
+ .noRippleClickable(onClick = onClickUpload),
+ contentColor = GrayColor.C500,
+ contentHeight = 68.dp,
+ contentBorderColor = CommonColor.White,
+ contentBorderWidth = 1.6.dp,
+ shadowHeight = 70.dp,
+ shadowOffset = 4.dp,
) {
- Spacer(modifier = Modifier.width(12.dp))
-
- AppRoundButton(
- borderColor = CommonColor.White,
- backgroundColor = GrayColor.C500,
+ AppText(
+ style = AppTextStyle.T2,
+ color = CommonColor.White,
text = stringResource(R.string.photolog_upload),
- textStyle = AppTextStyle.T2,
- textColor = CommonColor.White,
- modifier =
- Modifier
- .width(150.dp)
- .height(74.dp)
- .noRippleClickable(onClick = onClickUpload),
)
}
}
diff --git a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraPreviewBox.kt b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraPreviewBox.kt
index 68668ea2..5639fa7b 100644
--- a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraPreviewBox.kt
+++ b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CameraPreviewBox.kt
@@ -1,31 +1,27 @@
package com.twix.photolog.capture.component
import androidx.camera.compose.CameraXViewfinder
-import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
-import com.twix.designsystem.R
import com.twix.designsystem.theme.GrayColor
import com.twix.designsystem.theme.TwixTheme
import com.twix.photolog.capture.model.CaptureStatus
import com.twix.photolog.capture.model.TorchStatus
import com.twix.photolog.capture.model.camera.CameraPreview
-import com.twix.ui.extension.noRippleClickable
@Composable
fun CameraPreviewBox(
@@ -33,14 +29,15 @@ fun CameraPreviewBox(
capture: CaptureStatus,
previewRequest: CameraPreview?,
torch: TorchStatus,
- onClickFlash: () -> Unit,
+ onClickTorch: () -> Unit,
onPositioned: (Float) -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier =
modifier
- .size(375.66.dp)
+ .fillMaxWidth()
+ .aspectRatio(1f)
.padding(horizontal = 5.dp)
.onGloballyPositioned { coordinates ->
onPositioned(coordinates.boundsInParent().bottom)
@@ -53,7 +50,11 @@ fun CameraPreviewBox(
CameraSurface(capture, previewRequest)
if (showTorch) {
- TorchIcon(torch, onClickFlash)
+ TorchButton(
+ torch = torch,
+ onClickTorch = onClickTorch,
+ modifier = Modifier.padding(top = 31.dp, start = 30.dp),
+ )
}
}
}
@@ -84,27 +85,6 @@ private fun CameraSurface(
}
}
-@Composable
-private fun TorchIcon(
- torch: TorchStatus,
- onClickFlash: () -> Unit,
-) {
- val torchIcon =
- when (torch) {
- TorchStatus.On -> ImageVector.vectorResource(id = R.drawable.ic_camera_torch_on)
- TorchStatus.Off -> ImageVector.vectorResource(id = R.drawable.ic_camera_torch_off)
- }
-
- Image(
- imageVector = torchIcon,
- contentDescription = null,
- modifier =
- Modifier
- .noRippleClickable(onClick = onClickFlash)
- .padding(start = 30.33.dp, top = 31.82.dp),
- )
-}
-
@Preview
@Composable
fun CameraPreviewBoxNotCapturedPreview() {
@@ -114,7 +94,7 @@ fun CameraPreviewBoxNotCapturedPreview() {
showTorch = true,
torch = TorchStatus.Off,
previewRequest = null,
- onClickFlash = {},
+ onClickTorch = {},
onPositioned = {},
)
}
diff --git a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CommentErrorText.kt b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CommentErrorText.kt
index 4d1bc90e..6e2e7b40 100644
--- a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CommentErrorText.kt
+++ b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/CommentErrorText.kt
@@ -30,7 +30,7 @@ fun CommentErrorText(modifier: Modifier = Modifier) {
.clip(RoundedCornerShape(12.dp)),
) {
AppText(
- text = stringResource(R.string.comment_error_message),
+ text = stringResource(R.string.toast_comment_length_guide),
style = AppTextStyle.B1,
color = CommonColor.White,
modifier = Modifier.align(Alignment.Center),
diff --git a/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/TorchButton.kt b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/TorchButton.kt
new file mode 100644
index 00000000..a407ee6c
--- /dev/null
+++ b/feature/photolog/capture/src/main/java/com/twix/photolog/capture/component/TorchButton.kt
@@ -0,0 +1,78 @@
+package com.twix.photolog.capture.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.dropShadow
+import androidx.compose.ui.graphics.shadow.Shadow
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import com.twix.designsystem.R
+import com.twix.designsystem.theme.CommonColor
+import com.twix.designsystem.theme.TwixTheme
+import com.twix.photolog.capture.model.TorchStatus
+import com.twix.ui.extension.noRippleClickable
+
+@Composable
+internal fun TorchButton(
+ torch: TorchStatus,
+ onClickTorch: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val torchIcon =
+ when (torch) {
+ TorchStatus.On -> R.drawable.ic_camera_torch_on
+ TorchStatus.Off -> R.drawable.ic_camera_torch_off
+ }
+
+ Box(
+ modifier =
+ modifier
+ .size(44.dp)
+ .dropShadow(
+ shape = CircleShape,
+ shadow =
+ Shadow(
+ radius = 10.67.dp,
+ spread = 0.dp,
+ color = CommonColor.Black.copy(alpha = 0.3f),
+ offset = DpOffset(0.dp, 0.dp),
+ ),
+ ).background(
+ color = CommonColor.White.copy(alpha = 0.1f),
+ shape = CircleShape,
+ ).noRippleClickable(onClick = onClickTorch),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ painter = painterResource(torchIcon),
+ contentDescription = "Flash Icon",
+ tint = CommonColor.White,
+ )
+ }
+}
+
+@Preview(showBackground = true, backgroundColor = 0xFFD9D9D9)
+@Composable
+private fun TorchButtonPreview() {
+ TwixTheme {
+ Column {
+ TorchButton(
+ torch = TorchStatus.On,
+ onClickTorch = {},
+ )
+ TorchButton(
+ torch = TorchStatus.Off,
+ onClickTorch = {},
+ )
+ }
+ }
+}
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetail.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailScreen.kt
similarity index 89%
rename from feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetail.kt
rename to feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailScreen.kt
index a232fde8..e3f1d108 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetail.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailScreen.kt
@@ -9,10 +9,15 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -28,6 +33,7 @@ import com.twix.designsystem.components.toast.model.ToastType
import com.twix.designsystem.extension.showCameraPermissionToastWithNavigateToSettingAction
import com.twix.designsystem.theme.CommonColor
import com.twix.designsystem.theme.TwixTheme
+import com.twix.domain.model.enums.BetweenUs
import com.twix.domain.model.enums.GoalReactionType
import com.twix.photolog.detail.component.PhotologCardContent
import com.twix.photolog.detail.component.PhotologDetailTopBar
@@ -161,9 +167,12 @@ fun PhotologDetailScreen(
onPoke: () -> Unit,
onSwipe: () -> Unit,
) {
+ val scrollState = rememberScrollState()
+
Column(
Modifier
.fillMaxSize()
+ .verticalScroll(scrollState)
.background(color = CommonColor.White),
) {
PhotologDetailTopBar(
@@ -199,14 +208,25 @@ private fun PhotologDetailScreenPreview(
uiState: PhotologDetailUiState,
) {
TwixTheme {
+ var previewState by remember { mutableStateOf(uiState) }
PhotologDetailScreen(
- uiState = uiState,
+ uiState = previewState,
onBack = {},
onClickModify = {},
onClickReaction = {},
onClickUpload = {},
onPoke = {},
- onSwipe = {},
+ onSwipe = {
+ previewState =
+ previewState.copy(
+ currentShow =
+ if (previewState.currentShow == BetweenUs.ME) {
+ BetweenUs.PARTNER
+ } else {
+ BetweenUs.ME
+ },
+ )
+ },
)
}
}
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailViewModel.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailViewModel.kt
index 3c4cebdc..441189dd 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailViewModel.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/PhotologDetailViewModel.kt
@@ -79,7 +79,7 @@ class PhotologDetailViewModel(
}
},
onError = {
- showToast(R.string.photolog_detail_fetch_photolog_fail, ToastType.ERROR)
+ showToast(R.string.toast_photolog_detail_fetch_fail, ToastType.ERROR)
},
onFinally = { reduce { copy(isLoading = true) } },
)
@@ -106,7 +106,7 @@ class PhotologDetailViewModel(
onSuccess = {},
onError = {
rollbackReaction()
- showToast(R.string.photolog_detail_reaction_fail, ToastType.ERROR)
+ showToast(R.string.toast_reaction_fail, ToastType.ERROR)
},
)
}
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologCardContent.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologCardContent.kt
index d8768c55..1c6bd0d2 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologCardContent.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologCardContent.kt
@@ -34,7 +34,7 @@ internal fun PhotologCardContent(
uploadedAt = uiState.displayedGoalUpdateAt,
actionLabel =
when (uiState.currentShow) {
- BetweenUs.ME -> stringResource(R.string.photolog_take_picture)
+ BetweenUs.ME -> stringResource(R.string.photolog_picture_upload)
BetweenUs.PARTNER -> stringResource(R.string.action_poke)
},
rotation = if (uiState.isDisplayedMyPhotolog) -8f else 0f,
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologDetailTopBar.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologDetailTopBar.kt
index 4e14cc9c..23fd40c7 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologDetailTopBar.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/PhotologDetailTopBar.kt
@@ -49,7 +49,7 @@ internal fun PhotologDetailTopBar(
modifier =
Modifier
.fillMaxSize()
- .background(GrayColor.C100)
+ .background(GrayColor.C050)
.noRippleClickable { onClickModify() },
contentAlignment = Alignment.Center,
) {
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/reaction/ReactionBar.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/reaction/ReactionBar.kt
index 3d62265c..49284f18 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/reaction/ReactionBar.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/reaction/ReactionBar.kt
@@ -6,8 +6,10 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.VerticalDivider
@@ -31,21 +33,20 @@ fun ReactionBar(
selectedReaction: GoalReactionType? = null,
) {
val shape = RoundedCornerShape(999.dp)
- val height = 68.dp
- val shadowOffset = 10.dp
- val shadowStart = 13.dp
Box(
modifier =
modifier
.fillMaxWidth()
- .height(height + shadowOffset),
+ .height(77.dp),
) {
Box(
modifier =
Modifier
- .matchParentSize()
- .padding(start = shadowStart, top = shadowOffset)
+ .fillMaxWidth()
+ .height(67.dp)
+ .padding(start = 1.dp)
+ .offset(y = 10.dp)
.background(GrayColor.C200, shape),
)
@@ -53,14 +54,22 @@ fun ReactionBar(
modifier =
Modifier
.fillMaxWidth()
- .height(height)
+ .height(68.dp)
.border(width = 1.dp, color = GrayColor.C500, shape = shape)
.background(GrayColor.C100, shape)
.clip(shape),
verticalAlignment = Alignment.CenterVertically,
) {
+ val lastIndex = ReactionUiModel.entries.lastIndex
+
ReactionUiModel.entries.forEachIndexed { index, reaction ->
val isSelected = reaction.type == selectedReaction
+ val paddingModifier =
+ when (index) {
+ 0 -> Modifier.padding(start = 11.dp, end = 7.dp)
+ lastIndex -> Modifier.padding(start = 7.dp, end = 11.dp)
+ else -> Modifier.padding(horizontal = 9.dp)
+ }
Box(
modifier =
@@ -69,16 +78,24 @@ fun ReactionBar(
.fillMaxHeight()
.background(
if (isSelected) GrayColor.C300 else GrayColor.C100,
- ).noRippleClickable(onClick = { onSelectReaction(reaction.type) }),
+ ).noRippleClickable(onClick = { onSelectReaction(reaction.type) })
+ .then(paddingModifier),
contentAlignment = Alignment.Center,
) {
- Image(
- imageVector = ImageVector.vectorResource(reaction.imageResources),
- contentDescription = null,
- )
+ Box(
+ modifier =
+ Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ Image(
+ imageVector = ImageVector.vectorResource(reaction.imageResources),
+ contentDescription = null,
+ )
+ }
}
- if (index < 4) {
+ if (index < lastIndex) {
VerticalDivider(
modifier = Modifier.fillMaxHeight(),
color = GrayColor.C500,
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeCardSpec.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeCardSpec.kt
index 3ad087cd..99cb0caa 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeCardSpec.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeCardSpec.kt
@@ -8,19 +8,13 @@ import androidx.compose.ui.unit.dp
*/
data class SwipeCardSpec(
/** dismiss 판정 거리(dp) */
- val dismissThresholdDp: Dp = 60.dp,
- /** 화면 밖으로 날아가는 거리(px) */
- val dismissDistancePx: Float = 1000f,
+ val dismissThreshold: Dp = 80.dp,
/** dismiss 애니메이션 시간(ms) */
val dismissDuration: Int = 150,
/** 회전 계산 비율 */
val rotationFactor: Float = 28f,
/** 최대 회전 각도 */
- val maxRotation: Float = 8f,
+ val maxRotation: Float = 16f,
/** 복귀 시 위치 비율 */
val reappearOffsetRatio: Float = 0.2f,
- /** spring damping */
- val springDamping: Float = 0.84f,
- /** spring stiffness */
- val springStiffness: Float = 300f,
)
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeableCard.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeableCard.kt
index f5483dca..23e6654b 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeableCard.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/component/swipe/SwipeableCard.kt
@@ -1,10 +1,9 @@
package com.twix.photolog.detail.component.swipe
import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@@ -14,7 +13,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.roundToInt
@@ -26,10 +24,8 @@ import kotlin.math.roundToInt
* 1. 사용자가 카드를 드래그
* 2. threshold 이상 이동 시 → 카드 dismiss
* 3. 화면 밖으로 날아간 뒤 onSwipe 호출
- * 4. 반대편에서 다시 등장 (spring 복귀)
+ * 4. 반대편에서 다시 등장
*
- * ## 커스터마이징
- * 모든 애니메이션/거리 값은 [SwipeCardSpec] 으로 조절 가능
*/
@Composable
fun SwipeableCard(
@@ -41,6 +37,7 @@ fun SwipeableCard(
) {
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
+ val threshold = with(density) { spec.dismissThreshold.toPx() }
/**
* 카드 상태 값
@@ -55,108 +52,70 @@ fun SwipeableCard(
(offsetX.value / spec.rotationFactor)
.coerceIn(-spec.maxRotation, spec.maxRotation)
- Box(
- modifier =
- modifier
- /**
- * 카드 위치 이동
- */
- .offset {
- IntOffset(
- offsetX.value.roundToInt(),
- 0,
- )
- }
- /**
- * 회전 + 투명도 적용
- */
- .graphicsLayer {
- rotationZ = rotation
- alpha = opacity.value
- }.pointerInput(isDisplayingMyPhoto) {
- detectDragGestures(
- /**
- * 드래그 중
- * → 위치 즉시 반영 (snap)
- */
- onDrag = { change, dragAmount ->
- change.consume()
+ BoxWithConstraints(modifier = modifier) {
+ val dismissDistance = constraints.maxWidth * 1.3f
- coroutineScope.launch {
- offsetX.snapTo(offsetX.value + dragAmount.x)
- }
- },
- /**
- * 드래그 종료 시 처리
- */
- onDragEnd = {
- val thresholdPx = with(density) { spec.dismissThresholdDp.toPx() }
- val shouldDismiss = abs(offsetX.value) > thresholdPx
-
- if (shouldDismiss) {
+ Box(
+ modifier =
+ modifier
+ /**
+ * 카드 위치 이동
+ */
+ .offset {
+ IntOffset(
+ offsetX.value.roundToInt(),
+ 0,
+ )
+ }
+ /**
+ * 회전 + 투명도 적용
+ */
+ .graphicsLayer {
+ rotationZ = rotation
+ alpha = opacity.value
+ }.pointerInput(isDisplayingMyPhoto) {
+ detectDragGestures(
+ /**
+ * 드래그 중
+ * → 위치 즉시 반영 (snap)
+ */
+ onDrag = { _, dragAmount ->
coroutineScope.launch {
- /**
- * 화면 밖으로 날리기
- */
- val targetX =
- if (offsetX.value > 0) {
- spec.dismissDistancePx
- } else {
- -spec.dismissDistancePx
- }
-
- val targetY = 0f
-
- /**
- * dismiss 애니메이션 완료 대기
- */
- coroutineScope {
- launch { offsetX.animateTo(targetX, tween(spec.dismissDuration)) }
- launch { opacity.animateTo(0f, tween(spec.dismissDuration)) }
- }
-
- /**
- * 데이터 교체
- */
- onSwipe()
+ offsetX.snapTo(offsetX.value + dragAmount.x)
+ }
+ },
+ /**
+ * 드래그 종료 시 처리
+ */
+ onDragEnd = {
+ val shouldDismiss = abs(offsetX.value) > threshold
+ coroutineScope.launch {
+ if (shouldDismiss) {
+ onSwipe()
- /**
- * 반대편 위치 세팅
- */
- val reappearStartX =
- if (isDisplayingMyPhoto) {
- spec.dismissDistancePx * spec.reappearOffsetRatio
- } else {
- -spec.dismissDistancePx * spec.reappearOffsetRatio
- }
- offsetX.snapTo(reappearStartX)
+ /**
+ * 반대편 위치 세팅
+ */
+ val reappearStartX =
+ if (isDisplayingMyPhoto) {
+ dismissDistance * spec.reappearOffsetRatio
+ } else {
+ -dismissDistance * spec.reappearOffsetRatio
+ }
+ offsetX.snapTo(reappearStartX)
- /**
- * 스프링 복귀
- */
- launch {
- offsetX.animateTo(
- 0f,
- spring(
- dampingRatio = spec.springDamping,
- stiffness = spec.springStiffness,
- ),
- )
- }
- launch {
- opacity.animateTo(1f, spring(spec.springDamping))
+ launch { offsetX.animateTo(0f) }
+ launch { opacity.animateTo(1f) }
+ } else {
+ // threshold 미만 → 제자리 복귀
+ launch { offsetX.animateTo(0f) }
}
}
- } else {
- // threshold 미만 → 제자리 복귀
- coroutineScope.launch {
- launch { offsetX.animateTo(0f, spring()) }
- }
- }
- },
- )
- },
- ) {
- content()
+ },
+ )
+ },
+ ) {
+ content()
+ }
}
}
diff --git a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/preview/PhotologDetailPreviewProvider.kt b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/preview/PhotologDetailPreviewProvider.kt
index a4d06649..8b8f2389 100644
--- a/feature/photolog/detail/src/main/java/com/twix/photolog/detail/preview/PhotologDetailPreviewProvider.kt
+++ b/feature/photolog/detail/src/main/java/com/twix/photolog/detail/preview/PhotologDetailPreviewProvider.kt
@@ -29,6 +29,7 @@ class PhotologDetailPreviewProvider : PreviewParameterProvider {
toastManager.tryShow(
ToastData(sideEffect.message, ToastType.SUCCESS),
@@ -157,7 +165,9 @@ fun PhotologEditorScreen(
.background(color = CommonColor.White)
.noRippleClickable { focusManager.clearFocus() },
) {
- Column {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
PhotologEditorTopBar(
title = uiState.goalName,
onBack = onBack,
@@ -196,12 +206,37 @@ fun PhotologEditorScreen(
CommentAnchorFrame(
uiModel = uiState.comment,
anchorBottom = photologBottom,
+ paddingBottom = 24.dp,
onCommentChanged = onCommentChanged,
onFocusChanged = onFocusChanged,
)
}
}
+@Composable
+private fun RetakeButton(onClickRetake: () -> Unit) {
+ AppRoundButton(
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .height(70.dp)
+ .noRippleClickable(onClick = onClickRetake)
+ .padding(horizontal = 30.dp),
+ contentColor = CommonColor.White,
+ contentHeight = 68.dp,
+ contentBorderColor = GrayColor.C500,
+ contentBorderWidth = 1.6.dp,
+ shadowHeight = 70.dp,
+ shadowOffset = 4.dp,
+ ) {
+ AppText(
+ style = AppTextStyle.T2,
+ color = GrayColor.C500,
+ text = stringResource(R.string.photolog_editor_retake),
+ )
+ }
+}
+
@Preview(showBackground = true)
@Composable
private fun PhotologEditorScreenPreview() {
diff --git a/feature/photolog/editor/src/main/java/com/twix/photolog/editor/PhotologEditorViewModel.kt b/feature/photolog/editor/src/main/java/com/twix/photolog/editor/PhotologEditorViewModel.kt
index b7f0002d..6ccf737c 100644
--- a/feature/photolog/editor/src/main/java/com/twix/photolog/editor/PhotologEditorViewModel.kt
+++ b/feature/photolog/editor/src/main/java/com/twix/photolog/editor/PhotologEditorViewModel.kt
@@ -55,19 +55,19 @@ class PhotologEditorViewModel(
private fun modifyComment() {
if (currentState.comment.canUpload.not()) {
- showToast(R.string.comment_error_message, ToastType.ERROR)
+ showToast(R.string.toast_comment_length_guide, ToastType.ERROR)
} else if (currentState.isCommentNotChanged) {
- showToast(R.string.photolog_editor_not_modified, ToastType.ERROR)
+ showToast(R.string.toast_comment_not_modified, ToastType.ERROR)
} else {
launchResult(
block = { launchModifyComment() },
onSuccess = {
detailRefreshBus.notifyChanged(PhotologRefreshBus.Publisher.EDITOR)
goalRefreshBus.notifyGoalListChanged()
- showToast(R.string.photolog_editor_modify_success, ToastType.SUCCESS)
+ showToast(R.string.toast_comment_modify_success, ToastType.SUCCESS)
},
onError = {
- showToast(R.string.photolog_editor_modify_fail, ToastType.ERROR)
+ showToast(R.string.toast_comment_modify_fail, ToastType.ERROR)
},
)
}
@@ -89,7 +89,7 @@ class PhotologEditorViewModel(
block = { photologRepository.fetchPhotologs(argTargetDate, argGoalId) },
onSuccess = { reduce { it.toEditorUiState(argGoalId, argTargetDate) } },
onError = {
- showToast(R.string.photolog_detail_fetch_photolog_fail, ToastType.ERROR)
+ showToast(R.string.toast_photolog_detail_fetch_fail, ToastType.ERROR)
},
)
}
diff --git a/feature/photolog/editor/src/main/java/com/twix/photolog/editor/component/RetakeButton.kt b/feature/photolog/editor/src/main/java/com/twix/photolog/editor/component/RetakeButton.kt
deleted file mode 100644
index 7ea34426..00000000
--- a/feature/photolog/editor/src/main/java/com/twix/photolog/editor/component/RetakeButton.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.twix.photolog.editor.component
-
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.twix.designsystem.R
-import com.twix.designsystem.components.button.AppRoundButton
-import com.twix.designsystem.theme.CommonColor
-import com.twix.designsystem.theme.GrayColor
-import com.twix.designsystem.theme.TwixTheme
-import com.twix.ui.extension.noRippleClickable
-
-@Composable
-internal fun RetakeButton(
- onClickRetake: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- AppRoundButton(
- text = stringResource(R.string.photolog_editor_retake),
- textColor = GrayColor.C500,
- backgroundColor = CommonColor.White,
- modifier =
- modifier
- .fillMaxWidth()
- .height(68.dp)
- .padding(horizontal = 30.dp)
- .noRippleClickable { onClickRetake() },
- )
-}
-
-@Preview
-@Composable
-private fun RetakeButtonPreview() {
- TwixTheme {
- RetakeButton(onClickRetake = {})
- }
-}