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

[UI/#305] 캘린터 터치 영역 수정 #311

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
package com.terning.feature.calendar.calendar

import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.terning.core.analytics.EventType
import com.terning.core.analytics.LocalTracker
import com.terning.core.designsystem.component.topappbar.CalendarTopAppBar
import com.terning.core.designsystem.extension.getWeekIndexContainingSelectedDate
import com.terning.core.designsystem.theme.Grey200
import com.terning.core.designsystem.theme.White
import com.terning.feature.calendar.calendar.component.ScreenTransition
import com.terning.feature.calendar.calendar.component.WeekDaysHeader
import com.terning.feature.calendar.calendar.model.CalendarModel.Companion.getLocalDateByPage
import com.terning.feature.calendar.calendar.model.CalendarModel.Companion.getYearMonthByPage
import com.terning.feature.calendar.calendar.model.CalendarUiState
import com.terning.feature.calendar.calendar.model.LocalPagerState
import com.terning.feature.calendar.calendar.model.DayModel
import com.terning.feature.calendar.calendar.model.TerningCalendarModel
import com.terning.feature.calendar.list.CalendarListRoute
import com.terning.feature.calendar.month.CalendarMonthRoute
import com.terning.feature.calendar.week.CalendarWeekRoute
Expand All @@ -45,12 +44,10 @@ fun CalendarRoute(
modifier: Modifier = Modifier,
viewModel: CalendarViewModel = hiltViewModel()
) {
val lifecycleOwner = LocalLifecycleOwner.current
val uiState by viewModel.uiState.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val amplitudeTracker = LocalTracker.current

CalendarScreen(
modifier = modifier,
uiState = uiState,
navigateToAnnouncement = navigateToAnnouncement,
onClickNewDate = viewModel::onSelectNewDate,
Expand All @@ -65,23 +62,29 @@ fun CalendarRoute(
)
}
viewModel.updateListVisibility(!uiState.isListEnabled)
}
},
modifier = modifier,
)
}

@Composable
private fun CalendarScreen(
uiState: CalendarUiState,
navigateToAnnouncement: (Long) -> Unit,
onClickNewDate: (LocalDate) -> Unit,
updateSelectedDate: (LocalDate) -> Unit,
onClickNewDate: (DayModel) -> Unit,
updateSelectedDate: (DayModel) -> Unit,
disableListVisibility: () -> Unit,
disableWeekVisibility: () -> Unit,
onClickListButton: () -> Unit,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()

val calendarListTransition =
updateTransition(!uiState.isListEnabled, label = "calendarListTransition")
val monthWeekTransition =
updateTransition(!uiState.isWeekEnabled, label = "monthWeekTransition")

val pagerState = rememberPagerState(
initialPage = uiState.calendarModel.initialPage,
pageCount = { uiState.calendarModel.pageCount }
Expand All @@ -90,95 +93,148 @@ private fun CalendarScreen(
LaunchedEffect(key1 = pagerState, key2 = uiState.selectedDate) {
snapshotFlow { pagerState.currentPage }
.collect { current ->
val date = getLocalDateByPage(current)
val date = uiState.calendarModel.getLocalDateByPage(current)
val month = uiState.calendarModel.getMonthModelByPage(current)

val newDate = LocalDate.of(
date.year,
date.month,
uiState.selectedDate.dayOfMonth.coerceAtMost(date.month.minLength())
uiState.selectedDate.date.dayOfMonth.coerceAtMost(date.month.minLength())
)
updateSelectedDate(newDate)

val currentWeek = newDate.getWeekIndexContainingSelectedDate(month.inDays)
updateSelectedDate(DayModel(newDate, currentWeek))
}
}

CompositionLocalProvider(
LocalPagerState provides pagerState

Column(
modifier = modifier,
) {
Column(
modifier = modifier,
) {
CalendarTopAppBar(
date = getYearMonthByPage(pagerState.settledPage),
isListExpanded = uiState.isListEnabled,
onListButtonClicked = onClickListButton,
onMonthNavigationButtonClicked = { direction ->
coroutineScope.launch {
pagerState.animateScrollToPage(
page = pagerState.settledPage + direction,
animationSpec = tween(500)
)
}
CalendarTopAppBar(
date = uiState.calendarModel.getYearMonthByPage(pagerState.settledPage),
isListExpanded = uiState.isListEnabled,
onListButtonClicked = {
if(!calendarListTransition.isRunning)
onClickListButton()
},
onMonthNavigationButtonClicked = { direction ->
coroutineScope.launch {
pagerState.animateScrollToPage(
page = pagerState.settledPage + direction,
animationSpec = tween(500)
)
}
)
ScreenTransition(
targetState = !uiState.isListEnabled,
transitionOne = slideInHorizontally { fullWidth -> -fullWidth } togetherWith
slideOutHorizontally { fullWidth -> fullWidth },
transitionTwo = slideInHorizontally { fullWidth -> fullWidth } togetherWith
slideOutHorizontally { fullWidth -> -fullWidth },
contentOne = {
Column(
modifier = Modifier
.fillMaxSize()
) {
WeekDaysHeader()

HorizontalDivider(
thickness = 1.dp,
color = Grey200
)

ScreenTransition(
targetState = !uiState.isWeekEnabled,
transitionOne = slideInVertically { fullHeight -> -fullHeight } togetherWith
slideOutVertically { fullHeight -> fullHeight },
transitionTwo = slideInVertically { fullHeight -> fullHeight } togetherWith
slideOutVertically { fullHeight -> -fullHeight },
contentOne = {
CalendarMonthRoute(
selectedDate = uiState.selectedDate,
updateSelectedDate = { newDate ->
if (!pagerState.isScrollInProgress)
onClickNewDate(newDate)
},
modifier = Modifier
.fillMaxSize()
.background(White),
)
},
contentTwo = {
CalendarWeekRoute(
calendarUiState = uiState,
modifier = Modifier
.fillMaxSize(),
navigateUp = disableWeekVisibility,
navigateToAnnouncement = navigateToAnnouncement,
updateSelectedDate = onClickNewDate
)
}
)
}
},
contentTwo = {
CalendarListRoute(
navigateToAnnouncement = navigateToAnnouncement,
navigateUp = disableListVisibility,
modifier = Modifier
.fillMaxSize()
}
)

CalendarListTransition(
transition = calendarListTransition,
calendarModel = uiState.calendarModel,
pagerState = pagerState,
onNavigateToAnnouncement = navigateToAnnouncement,
onNavigateUpToCalendar = disableListVisibility,
calendarContent = {
Column(
modifier = Modifier
.fillMaxSize()
) {
WeekDaysHeader()

HorizontalDivider(
thickness = 1.dp,
color = Grey200
)

MonthWeekTransition(
transition = monthWeekTransition,
selectedDate = uiState.selectedDate,
calendarModel = uiState.calendarModel,
pagerState = pagerState,
onSelectDate = { newDate -> onClickNewDate(newDate) },
onNavigateToAnnouncement = navigateToAnnouncement,
onNavigateUpToMonth = disableWeekVisibility
)
}
}
)
}
}


/** 달력 <-> 목록 전환 컴포저블 */
@Composable
private fun CalendarListTransition(
transition: Transition<Boolean>,
calendarModel: TerningCalendarModel,
pagerState: PagerState,
onNavigateToAnnouncement: (Long) -> Unit,
onNavigateUpToCalendar: () -> Unit,
calendarContent: @Composable () -> Unit,
) {
Comment on lines +165 to +174
Copy link
Member

Choose a reason for hiding this comment

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

함수화를 해놓으니 훨씬 가독성이 좋아졌네요!

ScreenTransition(
transition = transition,
transitionOne = slideInHorizontally { fullWidth -> -fullWidth } togetherWith
slideOutHorizontally { fullWidth -> fullWidth },
transitionTwo = slideInHorizontally { fullWidth -> fullWidth } togetherWith
slideOutHorizontally { fullWidth -> -fullWidth },
contentOne = {
calendarContent()
},
contentTwo = {
CalendarListRoute(
calendarModel = calendarModel,
navigateToAnnouncement = onNavigateToAnnouncement,
navigateUp = onNavigateUpToCalendar,
pagerState = pagerState,
modifier = Modifier
.fillMaxSize()
)
},
)
}

/**월간 <-> 주간 전환 컴포저블*/
@Composable
private fun MonthWeekTransition(
transition: Transition<Boolean>,
selectedDate: DayModel,
calendarModel: TerningCalendarModel,
pagerState: PagerState,
onSelectDate: (DayModel) -> Unit,
onNavigateToAnnouncement: (Long) -> Unit,
onNavigateUpToMonth: () -> Unit,
) {
ScreenTransition(
transition = transition,
transitionOne = slideInVertically { fullHeight -> -fullHeight } togetherWith
slideOutVertically { fullHeight -> fullHeight },
transitionTwo = slideInVertically { fullHeight -> fullHeight } togetherWith
slideOutVertically { fullHeight -> -fullHeight },
contentOne = {
CalendarMonthRoute(
selectedDate = selectedDate,
updateSelectedDate = { newDate ->
if (!pagerState.isScrollInProgress)
onSelectDate(newDate)
},
pagerState = pagerState,
calendarModel = calendarModel
)
},
contentTwo = {
CalendarWeekRoute(
modifier = Modifier
.fillMaxSize(),
navigateUp = onNavigateUpToMonth,
navigateToAnnouncement = onNavigateToAnnouncement,
updateSelectedDate = onSelectDate,
selectedDate = selectedDate,
calendarModel = calendarModel,
pagerState = pagerState,
)
}
}
)
}


Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
package com.terning.feature.calendar.calendar

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.terning.feature.calendar.calendar.model.CalendarUiState
import com.terning.feature.calendar.calendar.model.DayModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.LocalDate
import javax.inject.Inject

@HiltViewModel
class CalendarViewModel @Inject constructor() : ViewModel() {
private var _uiState: MutableStateFlow<CalendarUiState> = MutableStateFlow(CalendarUiState())
val uiState get() = _uiState.asStateFlow()

fun onSelectNewDate(selectedDate: LocalDate) = viewModelScope.launch {
if (_uiState.value.selectedDate == selectedDate) {
_uiState.update { currentState ->
currentState.copy(
isWeekEnabled = !_uiState.value.isWeekEnabled
)
}
fun onSelectNewDate(selectedDate: DayModel) {
if (_uiState.value.selectedDate.date == selectedDate.date) {
updateWeekVisibility(!_uiState.value.isWeekEnabled)
} else {
_uiState.update { currentState ->
currentState.copy(
Expand All @@ -33,27 +27,23 @@ class CalendarViewModel @Inject constructor() : ViewModel() {
}
}

fun updateSelectedDate(date: LocalDate) = viewModelScope.launch {
_uiState.update { currentState ->
fun updateSelectedDate(value: DayModel) = _uiState.update { currentState ->
Copy link
Member

Choose a reason for hiding this comment

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

update 함수들의 파라미터 이름을 value로 수정하신 이유가 궁금해요!!

Copy link
Member Author

Choose a reason for hiding this comment

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

함수명에서 무엇을 구현하는지 명시해뒀는데 굳이 한번 더 명시할 필요가 있을까 싶었고, 모든 flow 업데이트 함수를 일관되게 관리하고자 value라고 수정했습니다!

currentState.copy(
selectedDate = date
selectedDate = value
)
}
}

fun updateListVisibility(visibility: Boolean) = viewModelScope.launch {
_uiState.update { currentState ->

fun updateListVisibility(value: Boolean) = _uiState.update { currentState ->
currentState.copy(
isListEnabled = visibility
isListEnabled = value
)
}
Comment on lines +36 to 41
Copy link
Member

Choose a reason for hiding this comment

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

상태를 업데이트할 때 이렇게 표현식을 사용해도 되는군요..!
제가 잘 몰라서 그러는데 표현식으로 사용하는 것과 그냥 블록함수로 사용하는 것과의 차이점이 있을까요...?

Copy link
Member Author

Choose a reason for hiding this comment

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

update라는 블록함수를 사용하면 Flow를 원자적으로 업데이트한다고 합니다!
원자적이라는건 서로 다른 스레드에서 동시에 같은 객체를 업데이트하지 못하도록 보장하는 것입니다.

}

fun updateWeekVisibility(visibility: Boolean) = viewModelScope.launch {
_uiState.update { currentState ->

fun updateWeekVisibility(value: Boolean) = _uiState.update { currentState ->
currentState.copy(
isWeekEnabled = visibility
isWeekEnabled = value
)
}
}
}
Loading
Loading