-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
b5aa382
72b1320
275e64c
4277b88
befe40d
04dab6c
d07040a
6ae5a42
29b841d
9993671
8263ae3
8761a5e
1bd770a
5df785c
2b3198d
01e12c4
c4f4305
c862039
f733a29
e840841
a70eac7
306e36d
1b4fe57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
) | ||
} | ||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스크린 함수 안에서 uiStateType으로 분기처리 해서 안에 값들을 처리안하고 이렇게 하니까 스크린 파일 훨씬 깔끔해지네요 ㄷㄷ