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/#9] 월간 캘린더 화면 #18

Merged
merged 20 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
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
6 changes: 6 additions & 0 deletions core/src/main/java/com/terning/core/extension/LocalDateExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.terning.core.extension

import java.time.LocalDate

fun LocalDate.getStringAsTitle(): String =
"${year}년 ${monthValue.toString().padStart(2, '0')}월"
4 changes: 4 additions & 0 deletions feature/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ dependencies {
implementation(libs.ossLicense)
implementation(libs.lottie)

//Compose Preview
implementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.tooling.preview)

// KakaoDependencies
implementation(libs.kakao.user)

Expand Down
103 changes: 103 additions & 0 deletions feature/src/main/java/com/terning/feature/calendar/CalendarDay.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.terning.feature.calendar

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.terning.core.designsystem.theme.Black
import com.terning.core.designsystem.theme.Grey150
import com.terning.core.designsystem.theme.Grey200
import com.terning.core.designsystem.theme.TerningMain
import com.terning.core.designsystem.theme.TerningTheme
import com.terning.core.designsystem.theme.White
import com.terning.feature.calendar.models.DayModel
import java.time.LocalDate

@Composable
fun CalendarDay(
modifier: Modifier = Modifier,
dayData: DayModel,
isSelected: Boolean,
isToday: Boolean,
onDateSelected: (LocalDate) -> Unit
) {
val backgroundColor =
if (isSelected) TerningMain else if (isToday) Grey200 else Color.Transparent
val textColor =
if (dayData.isOutDate) {
Grey150
} else {
if (isSelected)
White
else
Black
}

Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(22.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = {
if (!dayData.isOutDate) {
onDateSelected(dayData.date)
}
}
)
.background(
color = backgroundColor,
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Text(
text = dayData.date.dayOfMonth.toString(),
color = textColor,
style = TerningTheme.typography.calendar
)
}
}
}

@Preview(showBackground = true)
@Composable
fun CalendarDayPreview() {
TerningTheme {
Row {
CalendarDay(
dayData = DayModel(LocalDate.now(), false),
isSelected = true,
isToday = true,
onDateSelected = {}
)
CalendarDay(
dayData = DayModel(LocalDate.now(), false),
isSelected = false,
isToday = true,
onDateSelected = {}
)
CalendarDay(
dayData = DayModel(LocalDate.now(), false),
isSelected = false,
isToday = false,
onDateSelected = {}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.terning.feature.calendar

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.TerningTheme
import com.terning.feature.calendar.models.MonthData
import com.terning.feature.calendar.models.Scrap
import java.time.LocalDate
import java.time.YearMonth

@Composable
fun CalendarMonth(
modifier: Modifier = Modifier,
monthData: MonthData,
onDateSelected: (LocalDate) -> Unit,
selectedDate: LocalDate,
scrapLists: List<List<Scrap>> = listOf()
) {
Column(
modifier = modifier
.fillMaxSize()
.padding(horizontal = 20.dp),
) {
val month = monthData.calendarMonth.weekDays
for (week in month) {
Row(
modifier = Modifier.weight(1f),
) {
for (day in week) {
Column(
modifier = Modifier
.weight(1f)
.padding(top = 15.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
CalendarDay(
dayData = day,
isSelected = selectedDate == day.date,
isToday = day.date == LocalDate.now(),
onDateSelected = onDateSelected
)
if(!day.isOutDate) {
val index = day.date.dayOfWeek.value - 1
CalendarScrap(
scrapList = scrapLists[index]
)
}
}
}
}
if(month.indexOf(week) != month.lastIndex) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(color = Grey150)
)
}
}
}
}

@Preview(showBackground = true)
@Composable
fun CalendarMonthPreview() {
TerningTheme {
CalendarMonth(
monthData = MonthData(YearMonth.now()),
selectedDate = LocalDate.now(),
onDateSelected = {},
scrapLists = listOf()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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
import androidx.compose.ui.Modifier
import com.terning.core.designsystem.theme.White
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 java.time.LocalDate
import java.time.YearMonth

@Composable
fun CalendarMonths(
modifier: Modifier = Modifier,
listState: LazyListState,
onDateSelected: (LocalDate) -> Unit,
pages: Int,
Comment on lines +16 to +22
Copy link
Member

Choose a reason for hiding this comment

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

CalendarMonths하고 CalendarMonth의 네이밍이 좀 더 구분이 됐으면 좋겠는데 마땅한 네이밍이 떠오르지 않네욤,,,,흠,,

Copy link
Member Author

Choose a reason for hiding this comment

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

혹시라도 나중에 좋은 아이디어 있으면 추천 부탁드려여~!!

selectedDate: LocalDate,
scrapLists: List<List<Scrap>>
) {
LazyRow(
modifier = modifier
.background(White),
state = listState,
userScrollEnabled = true,
flingBehavior = flingBehavior(
state = listState
)
) {
items(pages) { page ->
val date = getDateByPage(page)
val monthData = MonthData(YearMonth.of(date.year, date.month))

CalendarMonth(
modifier = Modifier.fillParentMaxSize(),
selectedDate = selectedDate,
onDateSelected = onDateSelected,
monthData = monthData,
scrapLists = scrapLists
)
}
}
}
114 changes: 114 additions & 0 deletions feature/src/main/java/com/terning/feature/calendar/CalendarRoute.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.terning.feature.calendar

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
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.lazy.rememberLazyListState
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.terning.core.designsystem.theme.Grey200
import com.terning.feature.calendar.models.CalendarState
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch

@Composable
fun CalendarRoute() {
CalendarScreen()
}

@Composable
fun CalendarScreen(
modifier: Modifier = Modifier,
viewModel: CalendarViewModel = hiltViewModel()
) {
val selectedDate by viewModel.selectedDate.collectAsState()
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 isListExpanded by remember { mutableStateOf(false) }
var isWeekEnabled by remember { mutableStateOf(false) }

Comment on lines +50 to +55
Copy link
Member

Choose a reason for hiding this comment

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

줄바꿈도 너무 깔끔해요,,,,,,,

LaunchedEffect(key1 = listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.distinctUntilChanged()
.collect{
val swipeDirection = (listState.firstVisibleItemIndex - currentPage).coerceIn(-1, 1).toLong()
Comment on lines +56 to +60
Copy link
Member

Choose a reason for hiding this comment

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

distinctUntilChanged()까지 폼미ㄷㄷ,,,,,,,,,

currentDate = currentDate.plusMonths(swipeDirection)
currentPage = listState.firstVisibleItemIndex
}
}

LaunchedEffect(key1 = selectedDate) {
isWeekEnabled = true
}

BackHandler {
isWeekEnabled = false
}

Scaffold(
modifier = modifier,
topBar = {
val coroutineScope = rememberCoroutineScope()
CalendarTopBar(
date = currentDate,
isListExpanded = isListExpanded,
onListButtonClicked = { isListExpanded = !isListExpanded },
onMonthNavigationButtonClicked = { direction ->
coroutineScope.launch {
listState.animateScrollToItem(
index = listState.firstVisibleItemIndex + direction,
)
}
Comment on lines +82 to +87
Copy link
Contributor

Choose a reason for hiding this comment

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

animateScrollToItem으로 매끄럽게 전환할 수 있근여 🥹..!

Copy link
Member Author

Choose a reason for hiding this comment

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

LazyListState에서 사용하는 전환 방법이라고 하네요 :D

Copy link
Member

Choose a reason for hiding this comment

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

오호!! 이건 처음보는건데!! 좋네요😊

}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = paddingValues.calculateTopPadding())
) {
WeekDaysHeader()
Spacer(modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(color = Grey200)
)
CalendarMonths(
modifier = Modifier.fillMaxSize(),
Comment on lines +103 to +104
Copy link
Contributor

Choose a reason for hiding this comment

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

CalendarMonths안에서 Column에 fillMaxSize이 적용되고 있는 것 같은데 여기서도 fillMaxSize를 넘겨주는 이유가 뭔가용?!

Copy link
Member Author

Choose a reason for hiding this comment

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

클래스 이름이랑 제 코딩 방식 때문에 혼동이 생긴 것 같네요,,,
CalendarMonths 내부에선 LazyRow가 해당 Modifier를 인자로 받아 사용하도록 구현돼있는데, 여기서 인자로 넘기지 않고 그냥 LazyRow 내부에서 사용해야 혼동이 없겠네요...

selectedDate = selectedDate,
onDateSelected = {
viewModel.updateSelectedDate(it)
},
listState = listState,
pages = state.getPageCount(),
scrapLists = viewModel.mockScrapList
)
}

}
}

This file was deleted.

Loading
Loading