diff --git a/core/src/main/java/com/terning/core/designsystem/component/box/ScrapBox.kt b/core/src/main/java/com/terning/core/designsystem/component/box/ScrapBox.kt new file mode 100644 index 000000000..03c6eb7f7 --- /dev/null +++ b/core/src/main/java/com/terning/core/designsystem/component/box/ScrapBox.kt @@ -0,0 +1,103 @@ +package com.terning.core.designsystem.component.box + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.terning.core.designsystem.theme.CalPink +import com.terning.core.designsystem.theme.CalPurple +import com.terning.core.designsystem.theme.Grey150 +import com.terning.core.designsystem.theme.White + +/** + * ScrapBox is made for easy customization of scrap box used in Calendar & Home Screen + * + * [modifier] must be assigned for assigning size of the box and padding + * [elevation] must be set greater than zero for shadow effect, mainly used in Calendar + * [borderWidth] must be set greater than zero for border effect, mainly used in Home + */ + +@Composable +fun ScrapBox( + cornerRadius: Dp, + scrapColor: Color, + modifier: Modifier = Modifier, + elevation: Dp = 0.dp, + borderWidth: Dp = 0.dp, + borderColor: Color = Grey150, + content: @Composable () -> Unit, +) { + Box( + modifier = modifier + .border( + width = borderWidth, + color = borderColor, + RoundedCornerShape(cornerRadius), + ) + .shadow( + elevation = elevation, + RoundedCornerShape(cornerRadius), + ) + ) { + Box( + modifier = Modifier + .background( + color = scrapColor, + shape = RoundedCornerShape(cornerRadius) + ) + .fillMaxSize() + ) + Box( + modifier = Modifier + .fillMaxSize() + .padding(start = 9.dp) + .background( + shape = RoundedCornerShape( + topEnd = cornerRadius, bottomEnd = cornerRadius + ), color = White + ) + ) { + content() + } + } +} + +@Preview(showBackground = true) +@Composable +fun BorderedScrapBoxPreview() { + ScrapBox( + modifier = Modifier + .height(116.dp) + .width(140.dp), + scrapColor = CalPink, + cornerRadius = 5.dp, + borderWidth = 1.dp + ) {} +} + +@Preview(showBackground = true) +@Composable +fun ShadowedScrapBoxPreview() { + ScrapBox( + modifier = Modifier + .height(height = 92.dp) + .fillMaxWidth(), + scrapColor = CalPurple, + cornerRadius = 10.dp, + elevation = 1.dp + ) {} +} \ No newline at end of file diff --git a/core/src/main/java/com/terning/core/extension/LocalDateExt.kt b/core/src/main/java/com/terning/core/extension/LocalDateExt.kt index edcf61c96..ee8c97476 100644 --- a/core/src/main/java/com/terning/core/extension/LocalDateExt.kt +++ b/core/src/main/java/com/terning/core/extension/LocalDateExt.kt @@ -3,4 +3,8 @@ package com.terning.core.extension import java.time.LocalDate fun LocalDate.getStringAsTitle(): String = - "${year}년 ${monthValue.toString().padStart(2, '0')}월" \ No newline at end of file + "${year}년 ${monthValue.toString().padStart(2, '0')}월" + +fun LocalDate.getWeekIndexContainingSelectedDate(): Int = dayOfMonth / 7 + +fun LocalDate.isToday(): Boolean = this == LocalDate.now() \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/calendar/CalendarMonth.kt b/feature/src/main/java/com/terning/feature/calendar/CalendarMonth.kt index ef34392fa..fc9636da3 100644 --- a/feature/src/main/java/com/terning/feature/calendar/CalendarMonth.kt +++ b/feature/src/main/java/com/terning/feature/calendar/CalendarMonth.kt @@ -15,9 +15,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.terning.core.designsystem.theme.Grey150 import com.terning.core.designsystem.theme.TerningPointTheme -import com.terning.core.designsystem.theme.TerningTheme +import com.terning.core.extension.isToday import com.terning.feature.calendar.models.MonthData import com.terning.feature.calendar.models.Scrap +import com.terning.feature.calendar.models.SelectedDateState import java.time.LocalDate import java.time.YearMonth @@ -26,7 +27,7 @@ fun CalendarMonth( modifier: Modifier = Modifier, monthData: MonthData, onDateSelected: (LocalDate) -> Unit, - selectedDate: LocalDate, + selectedDate: SelectedDateState, scrapLists: List> = listOf() ) { Column( @@ -48,8 +49,8 @@ fun CalendarMonth( ) { CalendarDay( dayData = day, - isSelected = selectedDate == day.date, - isToday = day.date == LocalDate.now(), + isSelected = selectedDate.selectedDate == day.date && selectedDate.isEnabled, + isToday = day.date.isToday(), onDateSelected = onDateSelected ) if(!day.isOutDate) { @@ -79,7 +80,7 @@ fun CalendarMonthPreview() { TerningPointTheme { CalendarMonth( monthData = MonthData(YearMonth.now()), - selectedDate = LocalDate.now(), + selectedDate = SelectedDateState(LocalDate.now(), true), onDateSelected = {}, scrapLists = listOf() ) diff --git a/feature/src/main/java/com/terning/feature/calendar/CalendarMonths.kt b/feature/src/main/java/com/terning/feature/calendar/CalendarMonths.kt index 4f179af8d..5d4ecc101 100644 --- a/feature/src/main/java/com/terning/feature/calendar/CalendarMonths.kt +++ b/feature/src/main/java/com/terning/feature/calendar/CalendarMonths.kt @@ -1,7 +1,6 @@ package com.terning.feature.calendar import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.runtime.Composable @@ -11,17 +10,18 @@ import com.terning.feature.calendar.models.CalendarDefaults.flingBehavior import com.terning.feature.calendar.models.CalendarState.Companion.getDateByPage import com.terning.feature.calendar.models.MonthData import com.terning.feature.calendar.models.Scrap +import com.terning.feature.calendar.models.SelectedDateState import java.time.LocalDate import java.time.YearMonth @Composable fun CalendarMonths( - modifier: Modifier = Modifier, listState: LazyListState, onDateSelected: (LocalDate) -> Unit, pages: Int, - selectedDate: LocalDate, - scrapLists: List> + selectedDate: SelectedDateState, + scrapLists: List>, + modifier: Modifier = Modifier, ) { LazyRow( modifier = modifier diff --git a/feature/src/main/java/com/terning/feature/calendar/CalendarRoute.kt b/feature/src/main/java/com/terning/feature/calendar/CalendarRoute.kt index c706c5715..827f8d416 100644 --- a/feature/src/main/java/com/terning/feature/calendar/CalendarRoute.kt +++ b/feature/src/main/java/com/terning/feature/calendar/CalendarRoute.kt @@ -1,6 +1,11 @@ package com.terning.feature.calendar import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.slideInVertically +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.Spacer @@ -21,14 +26,18 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.terning.core.designsystem.theme.Grey200 +import com.terning.feature.R import com.terning.feature.calendar.component.CalendarTopBar import com.terning.feature.calendar.component.WeekDaysHeader import com.terning.feature.calendar.models.CalendarState import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +import java.time.LocalDate @Composable fun CalendarRoute() { @@ -40,35 +49,33 @@ fun CalendarScreen( modifier: Modifier = Modifier, viewModel: CalendarViewModel = hiltViewModel() ) { - val selectedDate by viewModel.selectedDate.collectAsState() - val state by remember{ mutableStateOf(CalendarState()) } + val selectedDate by viewModel.selectedDate.collectAsStateWithLifecycle() + val state by remember { mutableStateOf(CalendarState()) } val listState = rememberLazyListState( initialFirstVisibleItemIndex = state.getInitialPage() ) - var currentDate by remember { mutableStateOf(selectedDate) } - var currentPage by remember { mutableIntStateOf(listState.firstVisibleItemIndex)} + var currentDate by remember { mutableStateOf(LocalDate.now()) } + var currentPage by remember { mutableIntStateOf(listState.firstVisibleItemIndex) } var isListExpanded by remember { mutableStateOf(false) } - var isWeekEnabled by remember { mutableStateOf(false) } LaunchedEffect(key1 = listState) { snapshotFlow { listState.firstVisibleItemIndex } .distinctUntilChanged() - .collect{ - val swipeDirection = (listState.firstVisibleItemIndex - currentPage).coerceIn(-1, 1).toLong() + .collect { + val swipeDirection = + (listState.firstVisibleItemIndex - currentPage).coerceIn(-1, 1).toLong() currentDate = currentDate.plusMonths(swipeDirection) currentPage = listState.firstVisibleItemIndex } } - LaunchedEffect(key1 = selectedDate) { - isWeekEnabled = true - } - BackHandler { - isWeekEnabled = false + if (selectedDate.isEnabled) { + viewModel.updateSelectedDate(selectedDate.selectedDate) + } } Scaffold( @@ -91,26 +98,57 @@ fun CalendarScreen( ) { paddingValues -> Column( modifier = Modifier - .fillMaxWidth() + .fillMaxSize() .padding(top = paddingValues.calculateTopPadding()) ) { WeekDaysHeader() - Spacer(modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(color = Grey200) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .background(color = Grey200) ) - CalendarMonths( - modifier = Modifier.fillMaxSize(), - selectedDate = selectedDate, - onDateSelected = { - viewModel.updateSelectedDate(it) + + AnimatedContent( + targetState = selectedDate.isEnabled, + transitionSpec = { + if (!targetState) { + slideInVertically { fullHeight -> -fullHeight } togetherWith + slideOutVertically { fullHeight -> fullHeight } + } else { + slideInVertically { fullHeight -> fullHeight } togetherWith + slideOutVertically { fullHeight -> -fullHeight } + }.using( + sizeTransform = SizeTransform(clip = true) + ) }, - listState = listState, - pages = state.getPageCount(), - scrapLists = viewModel.mockScrapList - ) + label = stringResource(id = R.string.calendar_animation_label) + ) { targetState -> + if (!targetState) { + CalendarMonths( + modifier = Modifier.fillMaxSize(), + selectedDate = selectedDate, + onDateSelected = { + viewModel.updateSelectedDate(it) + }, + listState = listState, + pages = state.getPageCount(), + scrapLists = viewModel.mockScrapList, + ) + } else { + CalendarWeekWithScrap( + modifier = Modifier + .fillMaxSize(), + selectedDate = selectedDate, + scrapLists = viewModel.mockScrapList, + onDateSelected = { + viewModel.updateSelectedDate(it) + } + ) + } + } } - } } + + diff --git a/feature/src/main/java/com/terning/feature/calendar/CalendarViewModel.kt b/feature/src/main/java/com/terning/feature/calendar/CalendarViewModel.kt index d57863782..6a286807b 100644 --- a/feature/src/main/java/com/terning/feature/calendar/CalendarViewModel.kt +++ b/feature/src/main/java/com/terning/feature/calendar/CalendarViewModel.kt @@ -10,6 +10,7 @@ import com.terning.core.designsystem.theme.CalPurple import com.terning.core.designsystem.theme.CalRed import com.terning.core.designsystem.theme.CalYellow import com.terning.feature.calendar.models.Scrap +import com.terning.feature.calendar.models.SelectedDateState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -21,12 +22,29 @@ import javax.inject.Inject @HiltViewModel class CalendarViewModel @Inject constructor( ) : ViewModel() { - private val _selectedDate = MutableStateFlow(LocalDate.now()) + private val _selectedDate = MutableStateFlow( + SelectedDateState( + selectedDate = LocalDate.now(), + isEnabled = false + ) + ) val selectedDate get() = _selectedDate.asStateFlow() fun updateSelectedDate(date: LocalDate) = viewModelScope.launch { - if (_selectedDate.value != date) { - _selectedDate.value = date + if (_selectedDate.value.selectedDate != date) { + _selectedDate.update { currentState -> + currentState.copy( + selectedDate = date, + isEnabled = true + ) + } + } else { + _selectedDate.update { currentState -> + currentState.copy( + selectedDate = date, + isEnabled = !_selectedDate.value.isEnabled + ) + } } } @@ -61,18 +79,21 @@ class CalendarViewModel @Inject constructor( ) ) } + 3 -> { list.add( i, listOf() ) } + 4 -> { list.add( i, listOf() ) } + 5 -> { list.add( i, diff --git a/feature/src/main/java/com/terning/feature/calendar/CalendarWeek.kt b/feature/src/main/java/com/terning/feature/calendar/CalendarWeek.kt new file mode 100644 index 000000000..7fb0e5d43 --- /dev/null +++ b/feature/src/main/java/com/terning/feature/calendar/CalendarWeek.kt @@ -0,0 +1,54 @@ +package com.terning.feature.calendar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.terning.core.extension.getWeekIndexContainingSelectedDate +import com.terning.core.extension.isToday +import com.terning.feature.calendar.models.MonthData +import com.terning.feature.calendar.models.SelectedDateState +import java.time.LocalDate +import java.time.YearMonth + +@Composable +fun CalendarWeek( + modifier: Modifier = Modifier, + selectedDate: SelectedDateState, + onDateSelected: (LocalDate) -> Unit = {} +) { + val date = selectedDate.selectedDate + val monthData = MonthData(YearMonth.of(date.year, date.monthValue)) + val currentWeek = date.getWeekIndexContainingSelectedDate() + + val pagerState = rememberPagerState ( + initialPage = currentWeek, + pageCount = {monthData.totalDays / 7} + ) + + HorizontalPager( + modifier = modifier, + state = pagerState) { page -> + LazyRow( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + items(items = monthData.calendarMonth.weekDays[page]) { day -> + CalendarDay( + dayData = day, + isSelected = selectedDate.selectedDate == day.date && selectedDate.isEnabled, + isToday = day.date.isToday(), + onDateSelected = onDateSelected + ) + } + } + } +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/calendar/CalendarWeekWithScrap.kt b/feature/src/main/java/com/terning/feature/calendar/CalendarWeekWithScrap.kt new file mode 100644 index 000000000..fbdf774cc --- /dev/null +++ b/feature/src/main/java/com/terning/feature/calendar/CalendarWeekWithScrap.kt @@ -0,0 +1,63 @@ +package com.terning.feature.calendar + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.unit.dp +import com.terning.core.designsystem.theme.Grey200 +import com.terning.core.designsystem.theme.White +import com.terning.feature.calendar.models.Scrap +import com.terning.feature.calendar.models.SelectedDateState +import java.time.LocalDate + +@Composable +fun CalendarWeekWithScrap( + modifier: Modifier = Modifier, + selectedDate: SelectedDateState, + scrapLists: List> = listOf(), + onDateSelected: (LocalDate) -> Unit +) { + Column( + modifier = modifier + ) { + Card( + modifier = Modifier + .border( + width = 0.dp, + color = Grey200, + shape = RoundedCornerShape(bottomStart = 20.dp, bottomEnd = 20.dp)) + .shadow( + shape = RoundedCornerShape(bottomStart = 20.dp, bottomEnd = 20.dp), + elevation = 1.dp + ), + + shape = RoundedCornerShape(bottomStart = 20.dp, bottomEnd = 20.dp), + ) { + CalendarWeek( + modifier = Modifier + .fillMaxWidth() + .background(White), + selectedDate = selectedDate, + onDateSelected = onDateSelected + ) + } + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + ) { + /*items(items = scrapLists[selectedDate?.dayOfMonth - 1]) { + + }*/ + + } + } + +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/calendar/models/SelectedDateState.kt b/feature/src/main/java/com/terning/feature/calendar/models/SelectedDateState.kt new file mode 100644 index 000000000..04864c848 --- /dev/null +++ b/feature/src/main/java/com/terning/feature/calendar/models/SelectedDateState.kt @@ -0,0 +1,8 @@ +package com.terning.feature.calendar.models + +import java.time.LocalDate + +data class SelectedDateState( + val selectedDate: LocalDate, + val isEnabled: Boolean +) diff --git a/feature/src/main/res/values/strings.xml b/feature/src/main/res/values/strings.xml index 059aa2ebd..9594d9aa8 100644 --- a/feature/src/main/res/values/strings.xml +++ b/feature/src/main/res/values/strings.xml @@ -44,5 +44,6 @@ previous next list + Calendar Transition \ No newline at end of file