diff --git a/core/common-ui/src/main/java/com/puzzle/common/ui/CollapsingHeaderNestedScrollConnection.kt b/core/common-ui/src/main/java/com/puzzle/common/ui/CollapsingHeaderNestedScrollConnection.kt new file mode 100644 index 00000000..e80acf7d --- /dev/null +++ b/core/common-ui/src/main/java/com/puzzle/common/ui/CollapsingHeaderNestedScrollConnection.kt @@ -0,0 +1,37 @@ +package com.puzzle.common.ui + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource + +class CollapsingHeaderNestedScrollConnection( + val headerHeight: Int +) : NestedScrollConnection { + + // 헤더 offset(픽셀 단위), 0이면 펼침, -headerHeight이면 완전 접힘 + var headerOffset: Int by mutableIntStateOf(0) + private set + + // 스크롤 이벤트가 오기 전, 먼저 얼마나 소모할지 계산 + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + // y축 델타(수직 스크롤 양) + val delta = available.y.toInt() + + // 새 offset = 기존 offset + 스크롤 델타 + val newOffset = headerOffset + delta + val previousOffset = headerOffset + + // -headerHeight ~ 0 사이로 제한 + // -> 최소 -105: 완전히 접힘, 최대 0: 완전히 펼침 + headerOffset = newOffset.coerceIn(-headerHeight, 0) + + // 소비(consumed)된 스크롤 양 = (바뀐 offset - 기존 offset) + val consumed = headerOffset - previousOffset + + // x축은 소비 안 함(0f), y축은 consumed만큼 소비했다고 반환 + return Offset(0f, consumed.toFloat()) + } +} diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt index 980816cd..18eb1887 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/Button.kt @@ -90,8 +90,8 @@ fun PieceSubButton( colors = ButtonDefaults.buttonColors( containerColor = PieceTheme.colors.primaryLight, contentColor = PieceTheme.colors.primaryDefault, - disabledContainerColor = PieceTheme.colors.light1, - disabledContentColor = PieceTheme.colors.white, + disabledContainerColor = PieceTheme.colors.light3, + disabledContentColor = PieceTheme.colors.dark2, ), modifier = modifier.height(52.dp), ) { diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt index 4d933dc7..6588ede2 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TopBar.kt @@ -209,4 +209,4 @@ fun PreviewPieceSubCloseTopBar() { .padding(vertical = 20.dp), ) } -} \ No newline at end of file +} diff --git a/core/designsystem/src/main/res/drawable/ic_image_default.xml b/core/designsystem/src/main/res/drawable/ic_image_default.xml index 50fc3426..cb580b72 100644 --- a/core/designsystem/src/main/res/drawable/ic_image_default.xml +++ b/core/designsystem/src/main/res/drawable/ic_image_default.xml @@ -4,27 +4,31 @@ android:height="120dp" android:viewportWidth="120" android:viewportHeight="120"> - - - - - - - - - - + + + + + + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_profile_image.xml b/core/designsystem/src/main/res/drawable/ic_profile_image.xml new file mode 100644 index 00000000..edf42ce9 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_profile_image.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_question.xml b/core/designsystem/src/main/res/drawable/ic_question.xml new file mode 100644 index 00000000..31ec4b74 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_question.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index 0b807c26..c5f0d6cf 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -21,4 +21,9 @@ 활동 지역 직업 흡연 + 전체 + 나와 같은 + 나와 다른 + 매칭 거절하기 + 나와 같은 \ No newline at end of file diff --git a/feature/matching/src/main/java/com/puzzle/matching/MatchingScreen.kt b/feature/matching/src/main/java/com/puzzle/matching/MatchingScreen.kt index bfed3102..9db06406 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/MatchingScreen.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/MatchingScreen.kt @@ -36,12 +36,12 @@ import androidx.compose.ui.unit.dp import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import com.puzzle.common.ui.verticalScrollbar +import com.puzzle.designsystem.R import com.puzzle.designsystem.component.PieceMainTopBar import com.puzzle.designsystem.component.PieceSolidButton import com.puzzle.designsystem.foundation.PieceTheme import com.puzzle.matching.contract.MatchingIntent import com.puzzle.matching.contract.MatchingState -import com.puzzle.designsystem.R @Composable internal fun MatchingRoute( diff --git a/feature/matching/src/main/java/com/puzzle/matching/detail/MatchingDetailRoute.kt b/feature/matching/src/main/java/com/puzzle/matching/detail/MatchingDetailRoute.kt index c60bebed..e618f25c 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/detail/MatchingDetailRoute.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/detail/MatchingDetailRoute.kt @@ -58,6 +58,9 @@ internal fun MatchingDetailRoute( onPreviousPageClick = { viewModel.onIntent(MatchingDetailIntent.OnPreviousPageClick) }, onNextPageClick = { viewModel.onIntent(MatchingDetailIntent.OnNextPageClick) }, onMoreClick = { viewModel.onIntent(MatchingDetailIntent.OnMoreClick) }, + onDeclineClick = { }, + onAcceptClick = { }, + onShowPicturesClick = { }, ) } @@ -68,6 +71,9 @@ private fun MatchingDetailScreen( onPreviousPageClick: () -> Unit, onNextPageClick: () -> Unit, onMoreClick: () -> Unit, + onDeclineClick: () -> Unit, + onShowPicturesClick: () -> Unit, + onAcceptClick: () -> Unit, modifier: Modifier = Modifier, ) { var showDialog by remember { mutableStateOf(false) } @@ -163,6 +169,7 @@ private fun MatchingDetailScreen( MatchingDetailContent( state = state, onMoreClick = onMoreClick, + onDeclineClick = onDeclineClick, modifier = Modifier .fillMaxSize() .padding(top = topBarHeight, bottom = bottomBarHeight), @@ -171,9 +178,7 @@ private fun MatchingDetailScreen( PieceSubCloseTopBar( title = state.currentPage.title, onCloseClick = onCloseClick, - showCloseButton = if (showDialog && dialogType == DialogType.PROFILE_IMAGE_DETAIL) { - false - } else true, + showCloseButton = !(showDialog && dialogType == DialogType.PROFILE_IMAGE_DETAIL), modifier = Modifier .fillMaxWidth() .height(topBarHeight) @@ -196,10 +201,12 @@ private fun MatchingDetailScreen( onShowPicturesClick = { dialogType = DialogType.PROFILE_IMAGE_DETAIL showDialog = true + onShowPicturesClick() }, onAcceptClick = { dialogType = DialogType.ACCEPT_MATCHING showDialog = true + onAcceptClick() }, modifier = Modifier .fillMaxWidth() @@ -221,8 +228,8 @@ private fun BackgroundImage(modifier: Modifier = Modifier) { Image( painter = painterResource(id = R.drawable.matchingdetail_bg), contentDescription = "basic info 배경화면", - modifier = Modifier.matchParentSize(), contentScale = ContentScale.Crop, + modifier = Modifier.matchParentSize(), ) } } @@ -231,11 +238,12 @@ private fun BackgroundImage(modifier: Modifier = Modifier) { private fun MatchingDetailContent( state: MatchingDetailState, onMoreClick: () -> Unit, + onDeclineClick: () -> Unit, modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxSize()) { when (state.currentPage) { - MatchingDetailState.MatchingDetailPage.BasicInfoState -> { + MatchingDetailState.MatchingDetailPage.BasicInfoState -> BasicInfoBody( nickName = state.nickName, selfDescription = state.selfDescription, @@ -247,24 +255,24 @@ private fun MatchingDetailContent( occupation = state.occupation, smokeStatue = state.smokeStatue, onMoreClick = onMoreClick, - modifier = Modifier.padding(horizontal = 20.dp) + modifier = Modifier.padding(horizontal = 20.dp), ) - } - MatchingDetailState.MatchingDetailPage.ValueTalkState -> { + MatchingDetailState.MatchingDetailPage.ValueTalkState -> ValueTalkBody( nickName = state.nickName, selfDescription = state.selfDescription, talkCards = state.talkCards, - onMoreClick = onMoreClick + onMoreClick = onMoreClick, ) - } - MatchingDetailState.MatchingDetailPage.ValuePickState -> { + MatchingDetailState.MatchingDetailPage.ValuePickState -> ValuePickBody( - pickCards = state.pickCards + nickName = state.nickName, + selfDescription = state.selfDescription, + pickCards = state.pickCards, + onDeclineClick = onDeclineClick, ) - } } } } @@ -284,7 +292,7 @@ private fun MatchingDetailBottomBar( ) { if (currentPage == MatchingDetailPage.ValuePickState) { Image( - painter = painterResource(id = R.drawable.ic_profile_image_temp), + painter = painterResource(id = R.drawable.ic_profile_image), contentDescription = "이전 페이지 버튼", modifier = Modifier .size(52.dp) @@ -346,6 +354,9 @@ private fun MatchingDetailScreenPreview() { {}, {}, {}, + {}, + {}, + {}, ) } } diff --git a/feature/matching/src/main/java/com/puzzle/matching/detail/component/ValueTalkHeader.kt b/feature/matching/src/main/java/com/puzzle/matching/detail/component/BasicInfoHeader.kt similarity index 90% rename from feature/matching/src/main/java/com/puzzle/matching/detail/component/ValueTalkHeader.kt rename to feature/matching/src/main/java/com/puzzle/matching/detail/component/BasicInfoHeader.kt index 52bdd91a..b04660fe 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/detail/component/ValueTalkHeader.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/detail/component/BasicInfoHeader.kt @@ -14,18 +14,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import com.puzzle.designsystem.R import com.puzzle.designsystem.foundation.PieceTheme @Composable -internal fun ValueTalkHeader( +internal fun BasicInfoHeader( nickName: String, selfDescription: String, onMoreClick: () -> Unit, modifier: Modifier = Modifier ) { - Column( - modifier = modifier, - ) { + Column(modifier = modifier) { Text( text = selfDescription, style = PieceTheme.typography.bodyMR, @@ -45,7 +44,7 @@ internal fun ValueTalkHeader( Spacer(modifier = Modifier.width(28.dp)) Image( - painter = painterResource(id = com.puzzle.designsystem.R.drawable.ic_more), + painter = painterResource(id = R.drawable.ic_more), contentDescription = "basic info 배경화면", modifier = Modifier .size(32.dp) diff --git a/feature/matching/src/main/java/com/puzzle/matching/detail/content/BasicInfoBody.kt b/feature/matching/src/main/java/com/puzzle/matching/detail/content/BasicInfoBody.kt index c4fe34d7..082701fd 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/detail/content/BasicInfoBody.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/detail/content/BasicInfoBody.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.puzzle.designsystem.R import com.puzzle.designsystem.foundation.PieceTheme -import com.puzzle.matching.detail.component.ValueTalkHeader +import com.puzzle.matching.detail.component.BasicInfoHeader @Composable internal fun BasicInfoBody( @@ -64,7 +64,7 @@ private fun BasicInfoName( nickName: String, selfDescription: String, onMoreClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Column(modifier = modifier) { Text( @@ -75,7 +75,7 @@ private fun BasicInfoName( Spacer(modifier = Modifier.weight(1f)) - ValueTalkHeader( + BasicInfoHeader( nickName = nickName, selfDescription = selfDescription, onMoreClick = onMoreClick, @@ -194,11 +194,11 @@ private fun InfoItem( text: @Composable () -> Unit? = {}, ) { Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier .clip(RoundedCornerShape(8.dp)) .background(PieceTheme.colors.white) .padding(vertical = 16.dp, horizontal = 12.dp), - horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = title, diff --git a/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValuePickBody.kt b/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValuePickBody.kt index 563485c9..a7885903 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValuePickBody.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValuePickBody.kt @@ -1,75 +1,355 @@ package com.puzzle.matching.detail.content +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Tab import androidx.compose.material3.TabRow +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import com.puzzle.common.ui.CollapsingHeaderNestedScrollConnection +import com.puzzle.designsystem.R +import com.puzzle.designsystem.component.PieceSubButton import com.puzzle.designsystem.foundation.PieceTheme import com.puzzle.domain.model.pick.ValuePick +import com.puzzle.matching.detail.component.BasicInfoHeader @Composable internal fun ValuePickBody( + nickName: String, + selfDescription: String, pickCards: List, + onDeclineClick: () -> Unit, modifier: Modifier = Modifier, ) { - val tabIndex = remember { mutableIntStateOf(0) } + val density = LocalDensity.current - val tabTitles = listOf("전체", "나와 같은", "나와 다른") + val valuePickHeaderHeight = 104.dp - Column(modifier = modifier.fillMaxSize()) { - TabRow( - selectedTabIndex = tabIndex.intValue, - modifier = Modifier.fillMaxWidth(), - ) { - tabTitles.forEachIndexed { index, title -> - Tab( - selected = (tabIndex.intValue == index), - onClick = { tabIndex.intValue = index }, - text = { Text(text = title) }, - ) + val valuePickHeaderHeightPx = with(density) { valuePickHeaderHeight.roundToPx() } + + val connection = remember(valuePickHeaderHeightPx) { + CollapsingHeaderNestedScrollConnection(valuePickHeaderHeightPx) + } + + val spaceHeight by remember(density) { + derivedStateOf { + with(density) { + (valuePickHeaderHeightPx + connection.headerOffset).toDp() } } + } + + val tabIndex = rememberSaveable { mutableIntStateOf(0) } + + val tabTitles = listOf( + stringResource(R.string.valuepick_all), + stringResource(R.string.valuepick_same), + stringResource(R.string.valuepick_different), + ) + + Box(modifier = modifier.nestedScroll(connection)) { + Column { + Spacer(Modifier.height(spaceHeight)) - when (tabIndex.intValue) { - 0 -> TabContent("전체 탭의 내용 예시...") - 1 -> TabContent("나만 탭의 내용 예시...") - 2 -> TabContent("너만 탭의 내용 예시...") + Column( + modifier = modifier.fillMaxSize(), + ) { + ValuePickTabRow( + tabIndex = tabIndex.intValue, + tabTitles = tabTitles, + onTabClick = { tabIndex.intValue = it.toInt() }, + ) + + ValuePickTabContent( + tabIndex = tabIndex.intValue, + pickCards = pickCards, + onDeclineClick = onDeclineClick, + ) + } } + + BasicInfoHeader( + nickName = nickName, + selfDescription = selfDescription, + onMoreClick = { }, + modifier = Modifier + .offset { IntOffset(0, connection.headerOffset) } + .background(PieceTheme.colors.white) + .height(valuePickHeaderHeight) + .padding( + vertical = 20.dp, + horizontal = 20.dp + ), + ) + + Spacer( + modifier = Modifier + .height(1.dp) + .fillMaxWidth() + .background(PieceTheme.colors.light2) + .align(Alignment.TopCenter), + ) + } +} + +@Composable +private fun ValuePickTabContent( + tabIndex: Int, + pickCards: List, + onDeclineClick: () -> Unit, +) { + when (tabIndex) { + 0 -> + ValuePickCards( + pickCards = pickCards, + onDeclineClick = onDeclineClick, + ) + + 1 -> + ValuePickCards( + pickCards = pickCards.filter { it.isSimilarToMe }, + onDeclineClick = onDeclineClick, + ) + + 2 -> + ValuePickCards( + pickCards = pickCards.filterNot { it.isSimilarToMe }, + onDeclineClick = onDeclineClick, + ) } } @Composable -private fun TabContent(contentText: String) { +private fun ValuePickCards( + pickCards: List, + onDeclineClick: () -> Unit, +) { LazyColumn( modifier = Modifier .fillMaxSize() - .padding(16.dp), + .background(PieceTheme.colors.light3) + .padding(horizontal = 20.dp), ) { - items(15) { index -> + itemsIndexed(pickCards) { idx, item -> + Spacer(Modifier.height(20.dp)) + + ValuePickCard( + valuePick = item, + ) + } + + item { + Spacer(Modifier.height(60.dp)) + Text( - text = "$contentText 아이템 $index", - modifier = Modifier.padding(vertical = 8.dp), + text = stringResource(R.string.valuepick_refuse), + style = PieceTheme.typography.bodyMM.copy( + textDecoration = TextDecoration.Underline + ), + color = PieceTheme.colors.dark3, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .clickable { + onDeclineClick() + }, ) + + Spacer(Modifier.height(60.dp)) } } } +@Composable +private fun ValuePickTabRow( + tabIndex: Int, + onTabClick: (Int) -> Unit, + tabTitles: List +) { + TabRow( + containerColor = PieceTheme.colors.white, + selectedTabIndex = tabIndex, + indicator = { tabPositions -> + if (tabIndex < tabPositions.size) { + TabRowDefaults.SecondaryIndicator( + color = PieceTheme.colors.black, + modifier = Modifier.tabIndicatorOffset(tabPositions[tabIndex]), + ) + } + }, + divider = {}, + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + ) { + tabTitles.forEachIndexed { index, title -> + Tab( + selected = (tabIndex == index), + onClick = { onTabClick(index) }, + text = { + Text( + text = title, + style = PieceTheme.typography.bodyMM, + ) + }, + selectedContentColor = PieceTheme.colors.black, + unselectedContentColor = PieceTheme.colors.dark3, + ) + } + } +} + +@Composable +private fun ValuePickCard( + valuePick: ValuePick, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(12.dp)) + .background(PieceTheme.colors.white) + .padding( + horizontal = 20.dp, + vertical = 24.dp, + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(28.dp), + ) { + Image( + painter = painterResource(id = R.drawable.ic_question), + contentDescription = "basic info 배경화면", + modifier = Modifier + .size(20.dp), + ) + + Spacer(modifier = modifier.width(6.dp)) + + Text( + text = valuePick.category, + style = PieceTheme.typography.bodySSB, + color = PieceTheme.colors.primaryDefault, + ) + + Spacer(modifier = modifier.weight(1f)) + + if (valuePick.isSimilarToMe) { + Text( + text = stringResource(R.string.valuepick_similar), + style = PieceTheme.typography.captionM, + color = PieceTheme.colors.subDefault, + textAlign = TextAlign.Center, + modifier = Modifier + .clip(RoundedCornerShape(23.dp)) + .background(PieceTheme.colors.subLight) + .padding(vertical = 6.dp, horizontal = 12.dp), + ) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = valuePick.question, + style = PieceTheme.typography.headingMSB, + color = PieceTheme.colors.dark1, + ) + + Spacer(modifier = Modifier.height(24.dp)) + + PieceSubButton( + label = valuePick.option1, + onClick = {}, + enabled = true, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + PieceSubButton( + label = valuePick.option2, + onClick = {}, + enabled = false, + modifier = Modifier.fillMaxWidth(), + ) + } +} + @Preview @Composable private fun ProfileValuePickBodyPreview() { PieceTheme { ValuePickBody( - pickCards = emptyList() + nickName = "nickName", + selfDescription = "selfDescription", + pickCards = listOf( + ValuePick( + category = "음주", + question = "사귀는 사람과 함께 술을 마시는 것을 좋아하나요?", + option1 = "함께 술을 즐기고 싶어요", + option2 = "같이 술을 즐길 수 없어도 괜찮아요", + isSimilarToMe = true, + ), + ValuePick( + category = "만남 빈도", + question = "주말에 얼마나 자주 데이트를 하고싶나요?", + option1 = "주말에는 최대한 같이 있고 싶어요", + option2 = "하루 정도는 각자 보내고 싶어요", + isSimilarToMe = false, + ), + ValuePick( + category = "연락 빈도", + question = "연인 사이에 얼마나 자주 연락하는게 좋은가요?", + option1 = "바빠도 최대한 자주 연락하고 싶어요", + option2 = "연락은 생각날 때만 종종 해도 괜찮아요", + isSimilarToMe = true, + ), + ValuePick( + category = "연락 방식", + question = "연락할 때 어떤 방법을 더 좋아하나요?", + option1 = "전화보다는 문자나 카톡이 좋아요", + option2 = "문자나 카톡보다는 전화가 좋아요", + isSimilarToMe = false, + ) + ), + onDeclineClick = {}, ) } } diff --git a/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValueTalkBody.kt b/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValueTalkBody.kt index 61132413..569eba1b 100644 --- a/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValueTalkBody.kt +++ b/feature/matching/src/main/java/com/puzzle/matching/detail/content/ValueTalkBody.kt @@ -18,25 +18,21 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import com.puzzle.common.ui.CollapsingHeaderNestedScrollConnection import com.puzzle.designsystem.R import com.puzzle.designsystem.foundation.PieceTheme import com.puzzle.domain.model.value.ValueTalk -import com.puzzle.matching.detail.component.ValueTalkHeader +import com.puzzle.matching.detail.component.BasicInfoHeader @Composable internal fun ValueTalkBody( @@ -48,7 +44,7 @@ internal fun ValueTalkBody( ) { val density = LocalDensity.current // 1) 고정 헤더 높이(105.dp) - val valueTalkHeaderHeight = 105.dp + val valueTalkHeaderHeight = 104.dp val valueTalkHeaderHeightPx = with(density) { valueTalkHeaderHeight.roundToPx() } // 2) 헤더가 얼마나 접혔는지(offset)를 관리해주는 NestedScrollConnection @@ -71,44 +67,31 @@ internal fun ValueTalkBody( // 4) Box에 nestedScroll(connection)을 달아, 스크롤 이벤트가 // CollapsingHeaderNestedScrollConnection으로 전달되도록 함 - Box(modifier = modifier.nestedScroll(connection)) { + Box( + modifier = modifier.nestedScroll(connection) + ) { // 5) Column: Spacer + LazyColumn을 세로로 배치 // 헤더가 접힐수록 Spacer의 높이가 줄어들고, 그만큼 리스트가 위로 올라옴 Column { // 5-1) 헤더 높이만큼 Spacer를 줘서 리스트가 '헤더 아래'에서 시작 // 헤더 offset이 변하면, spaceHeight가 변해 리스트도 따라 위로 올라감 - Spacer(Modifier.height(spaceHeight)) - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .background(PieceTheme.colors.light3) - .padding(horizontal = 20.dp), - ) { - itemsIndexed(talkCards) { idx, item -> - Spacer(Modifier.height(20.dp)) - - ValueTalkCard( - item = item, - idx = idx, - ) - } - - item { - Spacer(Modifier.height(60.dp)) - } - } + Spacer( + Modifier.height(spaceHeight) + ) + + ValueTalkCards(talkCards) } // 6) 실제 헤더 뷰 // offset을 통해 y축 이동 (headerOffset이 음수면 위로 올라가며 사라짐) - ValueTalkHeader( + BasicInfoHeader( nickName = nickName, selfDescription = selfDescription, onMoreClick = onMoreClick, modifier = Modifier .offset { IntOffset(0, connection.headerOffset) } .background(PieceTheme.colors.white) + .height(valueTalkHeaderHeight) .padding( vertical = 20.dp, horizontal = 20.dp @@ -125,32 +108,26 @@ internal fun ValueTalkBody( } } -private class CollapsingHeaderNestedScrollConnection( - val headerHeight: Int -) : NestedScrollConnection { - - // 헤더 offset(픽셀 단위), 0이면 펼침, -headerHeight이면 완전 접힘 - var headerOffset: Int by mutableIntStateOf(0) - private set - - // 스크롤 이벤트가 오기 전, 먼저 얼마나 소모할지 계산 - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - // y축 델타(수직 스크롤 양) - val delta = available.y.toInt() - - // 새 offset = 기존 offset + 스크롤 델타 - val newOffset = headerOffset + delta - val previousOffset = headerOffset - - // -headerHeight ~ 0 사이로 제한 - // -> 최소 -105: 완전히 접힘, 최대 0: 완전히 펼침 - headerOffset = newOffset.coerceIn(-headerHeight, 0) +@Composable +private fun ValueTalkCards(talkCards: List) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(PieceTheme.colors.light3) + .padding(horizontal = 20.dp), + ) { + itemsIndexed(talkCards) { idx, item -> + Spacer(Modifier.height(20.dp)) - // 소비(consumed)된 스크롤 양 = (바뀐 offset - 기존 offset) - val consumed = headerOffset - previousOffset + ValueTalkCard( + item = item, + idx = idx, + ) + } - // x축은 소비 안 함(0f), y축은 consumed만큼 소비했다고 반환 - return Offset(0f, consumed.toFloat()) + item { + Spacer(Modifier.height(60.dp)) + } } } @@ -192,7 +169,8 @@ private fun ValueTalkCard( Image( painter = painterResource(id = icons[idxInRange]), contentDescription = "basic info 배경화면", - modifier = Modifier.size(60.dp), + modifier = Modifier + .size(60.dp), ) Spacer(modifier = Modifier.height(24.dp)) @@ -212,7 +190,6 @@ private fun ValueTalkCard( } } - @Preview @Composable private fun ProfileValueTalkBodyPreview() {