Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#129 place detail UI #132

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
c32ec87
[feat] #129 ์ƒ์„ธ ํŽ˜์ด์ง€ ์žฅ์†Œ ๊ณ ์ • ui + pager ์ ์šฉ ๋ฐ ํƒญ ๋ณ„ empty view
nagaeng Sep 27, 2024
2c382d1
[feat] #129 ์ƒ์„ธ ํŽ˜์ด์ง€ empty ๋ทฐ ์ „์‹œ ๊ฐœ์ˆ˜ ๋ฌธ๊ตฌ ์ถ”๊ฐ€ ๋ฐ ์ƒ๋Œ€ ์œ„์น˜ ์ˆ˜์ •
nagaeng Sep 27, 2024
b05e290
[feat] #129 ์ƒ์„ธ ํŽ˜์ด์ง€ ํ›„๊ธฐ ์˜์ƒ ui (๋ฐ์ดํ„ฐ O) ๊ตฌํ˜„
nagaeng Sep 28, 2024
8f8c0e7
[feat] #129 ์ƒ์„ธ ํŽ˜์ด์ง€ ์ „์‹œ ๋ฆฌ์ŠคํŠธ chip ์ƒ์„ฑ
nagaeng Sep 29, 2024
e800df3
[feat] #129 ์ƒ์„ธ ํŽ˜์ด์ง€ ์ „์‹œ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด - ์ „์‹œ ์ •๋ณด ๋ฐ•์Šค ui
nagaeng Oct 4, 2024
018c941
[feat] #129 ์ „์‹œ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด - ์ „์‹œ ์ •๋ณด ๋ฐ•์Šค์— ๋”๋ณด๊ธฐ svg ๋ฐฐ์น˜ / ์ „์‹œ ๋ฆฌ์ŠคํŠธ Triple ๋กœ ๋ณ€๊ฒฝ (stโ€ฆ
nagaeng Oct 5, 2024
fa6fef2
[feat] #129 chip bar, ์ „์‹œ count -> scrollable
nagaeng Oct 5, 2024
028ff23
[feat] #129 ํŽ˜์ด์ €๋ณ„ ์œ„์น˜ ์ˆ˜์ • ๋ฐ ํ…Œ์ŠคํŒ… ์ดˆ๊ธฐํ™”
nagaeng Oct 5, 2024
e03cc61
[feat] #129 ktlintformat
nagaeng Oct 5, 2024
0ff665f
[feat] #129 white ์ปฌ๋Ÿฌ ์ˆ˜์ •
nagaeng Oct 7, 2024
c0cf5d0
Merge branch 'feature/#129-space-detail-ui' of https://github.com/Teaโ€ฆ
nagaeng Oct 8, 2024
48315b5
[feat] #129 date ์ž๋ฃŒํ˜• string์œผ๋กœ ๋ณ€ํ™˜
nagaeng Oct 11, 2024
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
14 changes: 14 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_more_informations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.011,4C12.011,3.448 11.563,3 11.011,3L6,3C4.343,3 3,4.343 3,6L3,18.022C3,19.679 4.343,21.022 6,21.022L18.022,21.022C19.679,21.022 21.022,19.679 21.022,18.022L21.022,13.011C21.022,12.459 20.574,12.011 20.022,12.011C19.47,12.011 19.022,12.459 19.022,13.011L19.022,18.022C19.022,18.574 18.574,19.022 18.022,19.022L6,19.022C5.448,19.022 5,18.574 5,18.022L5,6C5,5.448 5.448,5 6,5L11.011,5C11.563,5 12.011,4.552 12.011,4Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M14.681,3.11C14.19,3.11 13.791,3.508 13.791,4C13.791,4.492 14.19,4.89 14.681,4.89L17.733,4.89L14.884,7.739L11.324,11.299C10.976,11.647 11.046,12.28 11.394,12.628C11.741,12.976 12.429,13.1 12.777,12.752L19.132,6.397L19.132,9.341C19.132,9.832 19.53,10.231 20.022,10.231C20.514,10.231 20.912,9.832 20.912,9.341L20.912,4.89C20.912,3.907 20.115,3.11 19.132,3.11L14.681,3.11Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
3 changes: 3 additions & 0 deletions feature/detail/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ plugins {
android {
namespace = "com.record.detail"
}
dependencies {
implementation(projects.domain.video)
}
278 changes: 278 additions & 0 deletions feature/detail/src/main/java/com/record/detail/DetailScreen.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,288 @@
package com.record.detail

import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.layout.width
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.record.designsystem.component.button.BasicButton
import com.record.designsystem.theme.RecordyTheme
import com.record.detail.screen.ChipTab
import com.record.detail.screen.ListScreen
import com.record.detail.screen.ReviewScreen
import com.record.model.VideoType
import com.record.ui.lifecycle.LaunchedEffectWithLifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

@Composable
fun DetailRoute(
padding: PaddingValues,
modifier: Modifier = Modifier,
viewModel: DetailpageViewModel = hiltViewModel(),
navigateToUplaod: () -> Unit,
navigateToVideo: (VideoType, Long) -> Unit,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

LaunchedEffectWithLifecycle {
viewModel.fetchPlaceInfo()
viewModel.initialData()
viewModel.sideEffect.collectLatest { sideEffect ->
when (sideEffect) {
is DetailpageSideEffect.NavigateToVideoDetail -> {
navigateToVideo(sideEffect.type, sideEffect.videoId)
}
}
}
}

Box(
modifier = modifier
.fillMaxSize()
.background(color = RecordyTheme.colors.black)
.padding(bottom = padding.calculateBottomPadding()),
) {
DetailpageScreen(
state = uiState,
onTabSelected = { viewModel.selectTab(it) },
navigateToVideo = viewModel::navigateToVideoDetail,
onLoadMoreReviews = viewModel::loadMoreReviewVideos,
onBookmarkClick = viewModel::bookmark,
navigateToUpload = navigateToUplaod,
)
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DetailpageScreen(
state: DetailpageState,
onTabSelected: (DetailpageTab) -> Unit,
navigateToVideo: (VideoType, Long) -> Unit,
navigateToUpload: () -> Unit,
onLoadMoreReviews: () -> Unit,
onBookmarkClick: (Long) -> Unit,
) {
val pagerState = rememberPagerState(
initialPage = state.detailpageTab.ordinal,
pageCount = { 2 },
)
val coroutineScope = rememberCoroutineScope()

val selectedChipState = remember { mutableStateOf(ChipTab.ALL) }
Column(
modifier = Modifier.fillMaxSize(),
) {
Spacer(modifier = Modifier.height(94.dp))

Column(
modifier = Modifier.fillMaxHeight(),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
) {
Column(
modifier = Modifier.align(Alignment.Center),
) {
Text(
text = state.placeName,
style = RecordyTheme.typography.title1,
color = RecordyTheme.colors.white,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = state.placeAddress,
style = RecordyTheme.typography.body2M,
color = RecordyTheme.colors.gray03,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(24.dp))

Row {
BasicButton(
text = "๊ธธ์ฐพ๊ธฐ",
textStyle = RecordyTheme.typography.body2SB,
textColor = RecordyTheme.colors.background,
backgroundColor = RecordyTheme.colors.gray01,
shape = RoundedCornerShape(8.dp),
onClick = { },
padding = PaddingValues(horizontal = 19.dp, vertical = 8.dp),
modifier = Modifier,
)
Spacer(modifier = Modifier.width(16.dp))
BasicButton(
text = "๊ตฌ๊ธ€ ๋ฆฌ๋ทฐ",
textStyle = RecordyTheme.typography.body2SB,
textColor = RecordyTheme.colors.background,
backgroundColor = RecordyTheme.colors.gray01,
shape = RoundedCornerShape(8.dp),
onClick = { },
padding = PaddingValues(horizontal = 19.dp, vertical = 8.dp),
modifier = Modifier,
)
}
}
}
Spacer(modifier = Modifier.height(54.dp))

CustomTabRow(
selectedTabIndex = state.detailpageTab.ordinal,
onTabSelected = onTabSelected,
pagerState = pagerState,
coroutineScope = coroutineScope,
)
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxWidth(),
userScrollEnabled = false,
) { page ->
when (page) {
DetailpageTab.LIST.ordinal -> {
ListScreen(
exhibitionItems = state.exhibitionList,
exhibitionCount = state.exhibitionCount,
selectedChip = selectedChipState.value,
onItemClick = {},
onChipSelected = { selectedChip ->
selectedChipState.value = selectedChip
},
)
}

DetailpageTab.REVIEW.ordinal -> {
ReviewScreen(
videoItems = state.reviewList,
reviewCount = state.reviewVideoCount,
onItemClick = navigateToVideo,
onLoadMore = onLoadMoreReviews,
onBookmarkClick = onBookmarkClick,
navigateToUpload = navigateToUpload,
)
}
}
}
}
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CustomTabRow(
selectedTabIndex: Int,
onTabSelected: (DetailpageTab) -> Unit,
pagerState: PagerState,
coroutineScope: CoroutineScope,
) {
var tabWidth by remember { mutableStateOf(0.dp) }
var indicatorOffset by remember { mutableStateOf(0.dp) }

val animatedIndicatorOffset by animateDpAsState(
targetValue = indicatorOffset,
animationSpec = tween(200),
)

val animatedIndicatorWidth by animateDpAsState(
targetValue = tabWidth - 12.dp,
animationSpec = tween(200),
)

val density = LocalDensity.current

LaunchedEffect(selectedTabIndex) {
if (tabWidth > 0.dp) {
indicatorOffset = (tabWidth * selectedTabIndex) + 6.dp
}
}

Column(
modifier = Modifier
.fillMaxWidth(),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(38.dp)
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.Bottom,
) {
DetailpageTab.entries.take(2).forEachIndexed { index, tab ->
val selected = index == selectedTabIndex
val textColor = if (selected) RecordyTheme.colors.gray01 else RecordyTheme.colors.gray05
val textStyle = if (selected) RecordyTheme.typography.body2B else RecordyTheme.typography.body2M

Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.clickable {
onTabSelected(tab)
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
}
.onGloballyPositioned { layoutCoordinates ->
tabWidth = with(density) { layoutCoordinates.size.width.toDp() }
},
contentAlignment = Alignment.Center,
) {
Text(
text = tab.displayName,
color = textColor,
style = textStyle,
)
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
) {
Box(
modifier = Modifier
.width(animatedIndicatorWidth)
.height(2.dp)
.offset(x = animatedIndicatorOffset)
.background(color = RecordyTheme.colors.gray01),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.record.detail

import com.record.model.VideoType
import com.record.ui.base.SideEffect
import com.record.ui.base.UiState
import com.record.video.model.VideoData
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import java.util.Date

data class DetailpageState(
val placeName: String = "๊ตญ๋ฆฝํ˜„๋Œ€๋ฏธ์ˆ ๊ด€",
val placeAddress: String = "์„œ์šธํŠน๋ณ„์‹œ ์ข…๋กœ๊ตฌ ์‚ผ์ฒญ๋กœ 30",
val exhibitionCount: Int = 0,
val reviewVideoCount: Int = 0,
val reviewCursor: Long = 0,
val reviewIsEnd: Boolean = false,
val detailpageTab: DetailpageTab = DetailpageTab.LIST,
val exhibitionList: ImmutableList<Triple<String, Date, Date>> = emptyList<Triple<String, Date, Date>>().toImmutableList(),
val reviewList: ImmutableList<VideoData> = emptyList<VideoData>().toImmutableList(),
) : UiState

sealed interface DetailpageSideEffect : SideEffect {
data class NavigateToVideoDetail(val type: VideoType, val videoId: Long) : DetailpageSideEffect
// data object NavigateToDirection : DetailpageSideEffect
// data object NavigateToGoogleReview : DetailpageSideEffect
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.record.detail

enum class DetailpageTab(val displayName: String) {
LIST("์ „์‹œ ๋ฆฌ์ŠคํŠธ"),
REVIEW("ํ›„๊ธฐ ์˜์ƒ"),
}
Loading