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

[refactor] CourseDetail (코스 상세 + 관리자 아카이빙) 분기처리 리팩토링 #199

Merged
merged 23 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b5aa382
[chore] #176 advertisement view
jihyunniiii Aug 13, 2024
72b1320
[chore] #176 관리자 아카이빙, 코스 상세 뷰 분리
jihyunniiii Aug 13, 2024
275e64c
Merge branch 'refs/heads/develop' into refactor-course-detail-view
jihyunniiii Aug 13, 2024
4277b88
Merge branch 'refs/heads/develop' into refactor-course-detail-view
jihyunniiii Aug 17, 2024
befe40d
[chore] #176 conflict 해결
jihyunniiii Aug 17, 2024
04dab6c
[chore] #176 HomeTopBar 패키징 수정
jihyunniiii Aug 17, 2024
d07040a
[feat] #176 DateRoadScrollResponsiveTopBar
jihyunniiii Aug 17, 2024
6ae5a42
[chore] #176 DateRoadBasicTopBar Parameter naming
jihyunniiii Aug 17, 2024
29b841d
[feat] #176 DateRoadScrollResponsiveTopBar Gradient
jihyunniiii Aug 17, 2024
9993671
[feat] #176 DateRoadImagePager
jihyunniiii Aug 17, 2024
8263ae3
[feat] #176 CourseDetailBasicInfo
jihyunniiii Aug 17, 2024
8761a5e
[feat] #176 courseDetailTimeline
jihyunniiii Aug 17, 2024
1bd770a
[feat] #176 CourseDetailTag
jihyunniiii Aug 17, 2024
5df785c
[feat] #176 CourseDetailCost
jihyunniiii Aug 17, 2024
2b3198d
[feat] #176 CourseDetailBottomBar
jihyunniiii Aug 17, 2024
01e12c4
[feat] #176 CourseDetailUnopenedDetail
jihyunniiii Aug 17, 2024
c4f4305
[feat] #176 courseDetailOpenedDetail
jihyunniiii Aug 17, 2024
c862039
[feat] #176 CourseDetailScreen 화면 구성
jihyunniiii Aug 17, 2024
f733a29
[chore] #176 좌우 패딩 수정
jihyunniiii Aug 17, 2024
e840841
[feat] #176 AdvertisementDetail
jihyunniiii Aug 17, 2024
a70eac7
[chore] #176 AdvertisementNavigation route 수정
jihyunniiii Aug 17, 2024
306e36d
[chore] #176 AdvertisementScreen
jihyunniiii Aug 17, 2024
1b4fe57
[chore] #176 ktlitFormat
jihyunniiii Aug 17, 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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.sopt.dateroad.presentation.type

import androidx.annotation.StringRes
import org.sopt.dateroad.R

enum class CourseDetailUnopenedDetailType(
@StringRes val descriptionStringRes: Int,
@StringRes val buttonTextStringRes: Int
) {
POINT(
descriptionStringRes = R.string.course_detail_point_read_button_description,
buttonTextStringRes = R.string.course_detail_point_read_button
),
FREE(
descriptionStringRes = R.string.course_detail_free_read_button_description,
buttonTextStringRes = R.string.course_detail_free_read_button
)
}
Comment on lines +6 to +18
Copy link
Contributor

Choose a reason for hiding this comment

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

스크린 함수 안에서 uiStateType으로 분기처리 해서 안에 값들을 처리안하고 이렇게 하니까 스크린 파일 훨씬 깔끔해지네요 ㄷㄷ

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.sopt.dateroad.presentation.ui.advertisement

import org.sopt.dateroad.domain.model.AdvertisementDetail
import org.sopt.dateroad.presentation.util.base.UiEvent
import org.sopt.dateroad.presentation.util.base.UiSideEffect
import org.sopt.dateroad.presentation.util.base.UiState
import org.sopt.dateroad.presentation.util.view.LoadState

class AdvertisementContract {
data class AdvertisementUiState(
val loadState: LoadState = LoadState.Idle,
val advertisementDetail: AdvertisementDetail = AdvertisementDetail()
) : UiState

sealed interface AdvertisementSideEffect : UiSideEffect {
data object PopBackStack : AdvertisementSideEffect
}

sealed class AdvertisementEvent : UiEvent {
data class FetchAdvertisementDetail(val loadState: LoadState, val advertisementDetail: AdvertisementDetail) : AdvertisementEvent()
}
}
Comment on lines +9 to +22
Copy link
Contributor

Choose a reason for hiding this comment

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

저는 처음에 코스상세 data class > advertisement data class 여서 필요한 인자만 가져와서 사용하면 된다고 생각했는데 같은 뷰에서 분기처리 하기 보다 이렇게 뷰를 따로 만드는게 추후에 더 좋은가요?? (궁금해서)

Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.sopt.dateroad.presentation.ui.advertisement

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState
import org.sopt.dateroad.presentation.ui.advertisement.component.AdvertisementDetail
import org.sopt.dateroad.presentation.ui.component.pager.DateRoadImagePager
import org.sopt.dateroad.presentation.ui.component.topbar.DateRoadScrollResponsiveTopBar
import org.sopt.dateroad.presentation.ui.component.view.DateRoadErrorView
import org.sopt.dateroad.presentation.ui.component.view.DateRoadIdleView
import org.sopt.dateroad.presentation.ui.component.view.DateRoadLoadingView
import org.sopt.dateroad.presentation.util.view.LoadState
import org.sopt.dateroad.ui.theme.DateRoadTheme

@Composable
fun AdvertisementRoute(
viewmodel: AdvertisementViewModel = hiltViewModel(),
popBackStack: () -> Unit,
advertisementId: Int
) {
val uiState by viewmodel.uiState.collectAsStateWithLifecycle()
val lifecycleOwner = LocalLifecycleOwner.current

LaunchedEffect(viewmodel.sideEffect, lifecycleOwner) {
viewmodel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
.collect { advertisementSideEffect ->
when (advertisementSideEffect) {
is AdvertisementContract.AdvertisementSideEffect.PopBackStack -> popBackStack()
}
}
}

LaunchedEffect(Unit) {
viewmodel.fetchAdvertisementDetail(advertisementId = advertisementId)
}

when (uiState.loadState) {
LoadState.Idle -> DateRoadIdleView()

LoadState.Loading -> DateRoadLoadingView()

LoadState.Success -> AdvertisementScreen(
advertisementUiState = uiState,
onTopBarIconClicked = { viewmodel.setSideEffect(AdvertisementContract.AdvertisementSideEffect.PopBackStack) }
)

LoadState.Error -> DateRoadErrorView()
}
}

@OptIn(ExperimentalPagerApi::class)
@Composable
fun AdvertisementScreen(
advertisementUiState: AdvertisementContract.AdvertisementUiState,
onTopBarIconClicked: () -> Unit
) {
var imageHeight by remember { mutableIntStateOf(0) }

val scrollState = rememberLazyListState()
val isScrollResponsiveDefault by remember {
derivedStateOf {
scrollState.firstVisibleItemIndex == 0 && scrollState.firstVisibleItemScrollOffset < imageHeight
}
}

Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(
state = scrollState,
modifier = Modifier
.fillMaxSize()
.background(DateRoadTheme.colors.white)
) {
with(advertisementUiState.advertisementDetail) {
item {
DateRoadImagePager(
modifier = Modifier
.onGloballyPositioned { coordinates ->
imageHeight = coordinates.size.height
},
pagerState = PagerState(),
images = images,
userScrollEnabled = true,
like = null
)
}

item {
AdvertisementDetail(
advertisementTagTitle = advertisementTagTitle,
createAt = createAt,
title = title,
description = description
)
}
}
}

DateRoadScrollResponsiveTopBar(
isDefault = isScrollResponsiveDefault,
onLeftIconClick = onTopBarIconClicked
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.sopt.dateroad.presentation.ui.advertisement

import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.launch
import org.sopt.dateroad.domain.usecase.GetAdvertisementDetailUseCase
import org.sopt.dateroad.presentation.util.base.BaseViewModel
import org.sopt.dateroad.presentation.util.view.LoadState

@HiltViewModel
class AdvertisementViewModel @Inject constructor(
private val getAdvertisementDetailUseCase: GetAdvertisementDetailUseCase
) : BaseViewModel<AdvertisementContract.AdvertisementUiState, AdvertisementContract.AdvertisementSideEffect, AdvertisementContract.AdvertisementEvent>() {
override fun createInitialState(): AdvertisementContract.AdvertisementUiState = AdvertisementContract.AdvertisementUiState()

override suspend fun handleEvent(event: AdvertisementContract.AdvertisementEvent) {
when (event) {
is AdvertisementContract.AdvertisementEvent.FetchAdvertisementDetail -> setState { copy(loadState = event.loadState, advertisementDetail = event.advertisementDetail) }
}
}

fun fetchAdvertisementDetail(advertisementId: Int) {
viewModelScope.launch {
setEvent(AdvertisementContract.AdvertisementEvent.FetchAdvertisementDetail(loadState = LoadState.Loading, advertisementDetail = currentState.advertisementDetail))
getAdvertisementDetailUseCase(advertisementId = advertisementId).onSuccess { advertisementDetail ->
setEvent(AdvertisementContract.AdvertisementEvent.FetchAdvertisementDetail(loadState = LoadState.Success, advertisementDetail = advertisementDetail))
}.onFailure {
setEvent(AdvertisementContract.AdvertisementEvent.FetchAdvertisementDetail(loadState = LoadState.Error, advertisementDetail = currentState.advertisementDetail))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.sopt.dateroad.presentation.ui.advertisement.component

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.sopt.dateroad.presentation.type.TagType
import org.sopt.dateroad.presentation.ui.component.tag.DateRoadTextTag
import org.sopt.dateroad.ui.theme.DateRoadTheme

@Composable
fun AdvertisementDetail(
advertisementTagTitle: String,
createAt: String,
title: String,
description: String
) {
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(20.dp))
DateRoadTextTag(
textContent = advertisementTagTitle,
tagContentType = TagType.ADVERTISEMENT_TITLE
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = createAt,
style = DateRoadTheme.typography.bodySemi15,
color = DateRoadTheme.colors.gray400
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = title,
style = DateRoadTheme.typography.titleExtra24,
color = DateRoadTheme.colors.black
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = description,
style = DateRoadTheme.typography.bodyMed13Context,
color = DateRoadTheme.colors.black
)
Spacer(modifier = Modifier.height(54.dp))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.sopt.dateroad.presentation.ui.advertisement.navigation

import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import org.sopt.dateroad.presentation.ui.advertisement.AdvertisementRoute

fun NavController.navigationAdvertisement(advertisementId: Int) {
this.navigate(route = AdvertisementRoute.route(advertisementId = advertisementId))
}

fun NavGraphBuilder.advertisementGraph(
popBackStack: () -> Unit
) {
composable(
route = AdvertisementRoute.ROUTE_WITH_ARGUMENT,
arguments = listOf(
navArgument(AdvertisementRoute.ID) {
type = NavType.IntType
}
)
) { navBackStackEntry ->
AdvertisementRoute(
popBackStack = popBackStack,
advertisementId = navBackStackEntry.arguments?.getInt(AdvertisementRoute.ID) ?: 0
)
}
}

object AdvertisementRoute {
private const val ROUTE = "advertisement"
const val ID = "id"
const val ROUTE_WITH_ARGUMENT = "$ROUTE/{$ID}"
fun route(advertisementId: Int) = "$ROUTE/$advertisementId"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.sopt.dateroad.presentation.ui.component.pager

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import org.sopt.dateroad.R
import org.sopt.dateroad.presentation.type.TagType
import org.sopt.dateroad.presentation.ui.component.tag.DateRoadImageTag
import org.sopt.dateroad.presentation.ui.component.tag.DateRoadTextTag

@OptIn(ExperimentalPagerApi::class)
@Composable
fun DateRoadImagePager(
modifier: Modifier = Modifier,
pagerState: PagerState,
images: List<String>,
userScrollEnabled: Boolean,
like: String?
) {
Box(modifier = Modifier.fillMaxWidth()) {
Comment on lines +26 to +33
Copy link
Contributor

Choose a reason for hiding this comment

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

공통 컴포넌트화 이거 내가 했어야 했는데.. 감사링

HorizontalPager(
count = images.size,
state = pagerState,
modifier = Modifier.fillMaxWidth(),
userScrollEnabled = userScrollEnabled
) { page ->
AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(images[page])
.crossfade(true)
.build(),
contentDescription = null,
modifier = modifier
.fillMaxWidth()
.aspectRatio(1f),
contentScale = ContentScale.Crop
)
}

if (like != null) {
DateRoadImageTag(
textContent = like,
imageContent = R.drawable.ic_tag_heart,
tagContentType = TagType.HEART,
modifier = Modifier
.padding(start = 10.dp, bottom = 10.dp)
.align(Alignment.BottomStart)
)
}

DateRoadTextTag(
textContent = stringResource(id = R.string.fraction_format, pagerState.currentPage + 1, pagerState.pageCount),
tagContentType = TagType.COURSE_DETAIL_PHOTO_NUMBER,
modifier = Modifier
.padding(end = 10.dp, bottom = 10.dp)
.align(Alignment.BottomEnd)
)
}
}
Loading
Loading