Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
35b374d
✨ Feat: CommentTextField.kt 패딩 수정
chanho0908 Mar 7, 2026
7cbd7bd
✨ Feat: `CommentTextField` `onHeightMeasured` 콜백을 추가하여 컴포넌트 높이 변화를 감지…
chanho0908 Mar 7, 2026
08802c1
♻️ Refactor: `CameraControlBar` 내부 레이아웃 구조 개선
chanho0908 Mar 7, 2026
153a8f5
♻️ Refactor: `AppRoundButton` 테두리 두께 변경 (1.dp -> 1.6.dp)
chanho0908 Mar 7, 2026
a203dd7
✨ Feat: `AppRoundButton` 공용 컴포넌트 고도화 및 UI 일관성 적용
chanho0908 Mar 7, 2026
f8f2e52
♻️ Refactor: "인증샷 찍기"에서 "업로드하기"로 변경하여 사용자 가이드 문구 수정
chanho0908 Mar 7, 2026
47611dd
♻️ Refactor: `CommentTextField` 배경 그리기 방식 개선 및 리팩토링`
chanho0908 Mar 7, 2026
a24d38d
♻️ Refactor: 인증샷 편집 화면 재촬영 버튼 높이 수정
chanho0908 Mar 8, 2026
07b2fcb
✨ Feat: `AppRoundButton` 롱 버튼 프리뷰 추가
chanho0908 Mar 8, 2026
26e2cf3
♻️ Refactor: `CameraControlBar` 업로드 버튼 영역 높이 수정
chanho0908 Mar 8, 2026
fbbecc0
♻️ Refactor: 포토로그 업로드 관련 문자열 리소스 수정
chanho0908 Mar 8, 2026
a7f88b4
✨ Feat: `CommonColor`에 `Black` 색상 추가
chanho0908 Mar 8, 2026
8b1c408
✨ Feat: 카메라 플래시(Torch) 버튼 UI 구현 및 적용
chanho0908 Mar 8, 2026
e4944f6
🔥 Chore: 사용하지 않는 카메라 권한 안내 문자열 제거
chanho0908 Mar 8, 2026
1da3655
♻️ Refactor: 토스트 관련 문자열 리소스 네이밍 컨벤션 적용 및 정리
chanho0908 Mar 8, 2026
fe31f88
♻️ Refactor: 내 사진이 없을 때 텍스트가 인증샷을 올려보세요!로 변경
chanho0908 Mar 8, 2026
b6e49a2
♻️ Refactor: 인증샷 수정 버튼 배경색 변경
chanho0908 Mar 8, 2026
9740f5c
♻️ Refactor: 인증샷 업로드 시간 텍스트 색상 변경
chanho0908 Mar 8, 2026
4c2fa5c
♻️ Refactor: 인증샷 프레임 radius 수정
chanho0908 Mar 8, 2026
c7952fe
♻️ Refactor: 리액션바 shadow 크기 수정
chanho0908 Mar 8, 2026
c0273a4
♻️ Refactor: ReactionBar` 아이템 간격 및 패딩 조정
chanho0908 Mar 8, 2026
d9699ce
🎨 Style: PhotologDetailPreviewProvider 코드 포맷팅 수정
chanho0908 Mar 8, 2026
1f86b09
♻️ Refactor: 댓글 입력 로직 개선 및 공백 검증 강화
chanho0908 Mar 8, 2026
5a88f49
✨ Feat: `CommentAnchorFrame` 하단 패딩 커스텀 기능 추가
chanho0908 Mar 8, 2026
50f3f6c
♻️ Refactor: 다시찍기 버튼 너비 수정
chanho0908 Mar 8, 2026
e7cde7c
♻️ Refactor: `PhotologCardPreview` 접근 제어자 수정
chanho0908 Mar 8, 2026
995cf52
♻️ Refactor: `PhotologEditorRoute` 내 `Spacer` 높이를 `101.dp`에서 `103.dp`…
chanho0908 Mar 8, 2026
39511e8
♻️ Refactor: 사용하지 않는 `CommentUiModel` 임포트 제거
chanho0908 Mar 8, 2026
eb0f518
✨ Refactor: `SwipeableCard` 애니메이션 로직 간소화 및 수치 조정
chanho0908 Mar 11, 2026
de3c01f
♻️ Refactor: `dismissDistance`를 화면 너비에 비례하도록 동적 계산 로직 적용
chanho0908 Mar 11, 2026
78fc526
✨ Feat: 인증샷 상세 화면 스크롤 적용 및 파일명 변경
chanho0908 Mar 11, 2026
082ae06
♻️ Refactor: `CameraPreviewBox` 반응형 레이아웃 대응
chanho0908 Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,40 @@ 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.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
Expand All @@ -49,15 +58,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) { 28.dp.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,
Expand All @@ -72,34 +84,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()
}
}
Comment on lines +150 to +181
Copy link
Copy Markdown
Contributor Author

@chanho0908 chanho0908 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위치 계산 과정

1. anchorBottom 기준으로 배치

val defaultY = anchorBottom
image

2. commentTextFieldHeight 만큼 Y축 이동

val defaultY = anchorBottom - textFieldHeight
image

3. padding 28 만큼 Y축 이동

val defaultY = anchorBottom - textFieldHeight - paddingBottom
image

🚨 문제: 키보드 활성 시 의도하지 않은 여백 발생

키보드가 올라올 때 screenHeight - imeBottom으로 키보드 상단을 계산하면 아래처럼 예상보다 위쪽에 댓글창이 붙는 현상이 발생했음

val keyboardTop = screenHeight - imeBottom
image

문제 원인

  • WindowInsets.ime의 기준점 오차 발생
  • 키보드 높이(imeBottom)에 시스템 네비게이션 바 인셋의 높이가 포함되어 실제 키보드 상단 좌표가 계산보다 더 높게 잡히는 현상이 발생 !!

문제 해결

navigationBars 인셋을 차감하여 기기별 내비게이션 바 유무와 상관없이 순수 키보드 높이를 구하도록 수정

val pureImeHeight = (imeBottom - navBottom).coerceAtLeast(0)
val keyboardTop = screenHeight - pureImeHeight

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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
Expand All @@ -22,6 +23,7 @@ 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
Expand All @@ -47,6 +49,7 @@ fun CommentTextField(
uiModel: CommentUiModel,
modifier: Modifier = Modifier,
enabled: Boolean = true,
onHeightMeasured: (Float) -> Unit = {},
onCommitComment: (String) -> Unit = {},
onFocusChanged: (Boolean) -> Unit = {},
) {
Expand Down Expand Up @@ -98,9 +101,12 @@ fun CommentTextField(
}

Box(
contentAlignment = Alignment.Center,
modifier =
modifier
.noRippleClickable {
.onSizeChanged { size ->
onHeightMeasured(size.height.toFloat())
}.noRippleClickable {
focusRequester.requestFocus()
},
) {
Expand Down Expand Up @@ -180,6 +186,7 @@ private fun CommentTextFieldPreview() {
CommentTextField(
uiModel = CommentUiModel(text, isFocused),
onFocusChanged = { isFocused = it },
onHeightMeasured = {},
)
}
}