-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
13 changed files
with
823 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
core/designsystem/src/main/res/drawable/ic_more_informations.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,6 @@ plugins { | |
android { | ||
namespace = "com.record.detail" | ||
} | ||
dependencies { | ||
implementation(projects.domain.video) | ||
} |
278 changes: 278 additions & 0 deletions
278
feature/detail/src/main/java/com/record/detail/DetailScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
) | ||
} | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
feature/detail/src/main/java/com/record/detail/DetailpageContract.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
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 | ||
|
||
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, String, String>> = emptyList<Triple<String, String, String>>().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 | ||
} |
6 changes: 6 additions & 0 deletions
6
feature/detail/src/main/java/com/record/detail/DetailpageTab.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("후기 영상"), | ||
} |
Oops, something went wrong.