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

[Setting] 캘린더 컴포넌트 구현 #33

Merged
merged 3 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ac.dnd.bookkeeping.android.presentation.common.view.calendar

import androidx.compose.ui.graphics.Color

data class CalendarColorProperties(
val otherMonthColor: Color,
val todayBeforeColor: Color,
val todayColor: Color,
val todayAfterColor: Color
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package ac.dnd.bookkeeping.android.presentation.common.view.calendar

import ac.dnd.bookkeeping.android.presentation.R
import ac.dnd.bookkeeping.android.presentation.common.theme.Body1
import ac.dnd.bookkeeping.android.presentation.common.theme.Body2
import ac.dnd.bookkeeping.android.presentation.common.theme.Gray000
import ac.dnd.bookkeeping.android.presentation.common.theme.Gray300
import ac.dnd.bookkeeping.android.presentation.common.theme.Gray600
import ac.dnd.bookkeeping.android.presentation.common.theme.Gray700
import ac.dnd.bookkeeping.android.presentation.common.theme.Primary4
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun CalendarComponent(
modifier: Modifier = Modifier,
selectedYear: Int = 0,
selectedMonth: Int = 0,
selectedDay: Int = -1,
verticalSpace: Dp = 0.dp,
backgroundImageSize: Dp = 30.dp,
calendarConfig: CalendarConfig,
calendarColorProperties: CalendarColorProperties = CalendarColorProperties(
otherMonthColor = Color.Transparent,
todayBeforeColor = Gray700,
todayColor = Gray600,
todayAfterColor = Gray700
),
unClickableDays: Set<CalendarDayType> = setOf(CalendarDayType.AFTER_TODAY),
onDaySelect: (Int) -> Unit,
itemContent: @Composable (dayItem: CalendarDay) -> Unit = { }
) {
val dayItems = remember { mutableStateListOf<CalendarDay>() }
LaunchedEffect(selectedYear, selectedMonth) {
dayItems.clear()
dayItems.addAll(calendarConfig.getCurrentCalendarDate(selectedYear, selectedMonth))
onDaySelect(calendarConfig.getCalendarDay())
}

Column(
modifier = modifier.fillMaxWidth()
) {
LazyVerticalGrid(
columns = GridCells.Fixed(7),
horizontalArrangement = Arrangement.SpaceBetween,
verticalArrangement = Arrangement.spacedBy(verticalSpace)
) {
items(calendarConfig.getDayOfWeek()) {
Box(
modifier = Modifier.aspectRatio(1f),
contentAlignment = Alignment.Center
) {
Text(
text = it,
style = Body2.merge(
color = Gray600,
fontWeight = FontWeight.SemiBold
)
)
}
}
items(dayItems) { dayItem ->
val dayItemColor = animateColorAsState(
targetValue = if (selectedDay == dayItem.day && dayItem.dayType != CalendarDayType.OTHER_MONTH) Gray000
else when (dayItem.dayType) {
CalendarDayType.BEFORE_TODAY -> calendarColorProperties.todayBeforeColor
CalendarDayType.TODAY -> calendarColorProperties.todayAfterColor
CalendarDayType.AFTER_TODAY -> calendarColorProperties.todayAfterColor
CalendarDayType.OTHER_MONTH -> Color.Transparent
},
label = "text color"
)
val dayItemBackGroundColor = animateColorAsState(
targetValue = if (dayItem.day == selectedDay && dayItem.dayType != CalendarDayType.OTHER_MONTH) Primary4
else if (dayItem.dayType == CalendarDayType.TODAY) Gray300
else Color.Transparent,
label = "text bacgound color"
)

Column(
modifier = Modifier
.clickable {
if (!unClickableDays.contains(dayItem.dayType) && dayItem.dayType != CalendarDayType.OTHER_MONTH)
onDaySelect(dayItem.day)
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier.aspectRatio(1f),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(backgroundImageSize)
.background(
color = dayItemBackGroundColor.value,
shape = CircleShape
)
)
Text(
text = dayItem.day.toString(),
style = Body1.merge(
color = dayItemColor.value,
fontWeight = FontWeight.SemiBold
)
)
}
if (dayItem.dayType != CalendarDayType.OTHER_MONTH) {
itemContent(dayItem)
}
}
}
}
}
}

@Preview
@Composable
fun CalendarWithIconPreview() {
CalendarComponent(
modifier = Modifier
.background(Color.White)
.padding(horizontal = 20.dp),
calendarConfig = CalendarConfig(),
selectedYear = 2023,
selectedMonth = 12,
onDaySelect = {}
) { _ ->
Image(
painter = painterResource(R.drawable.ic_check_circle),
contentDescription = null,
modifier = Modifier.size(5.dp)
)
}
}

@Preview
@Composable
fun CalendarSelectedPreview() {
val selectedDay by remember { mutableIntStateOf(10) }
CalendarComponent(
modifier = Modifier
.background(Color.White)
.padding(horizontal = 20.dp),
selectedYear = 2024,
selectedMonth = 2,
calendarConfig = CalendarConfig(),
selectedDay = selectedDay,
onDaySelect = {}
)
}

@Preview
@Composable
fun CalendarBottomSheetPreview() {
val calendarConfig = CalendarConfig()
var selectedYear by remember { mutableIntStateOf(calendarConfig.getCalendarYear()) }
var selectedMonth by remember { mutableIntStateOf(calendarConfig.getCalendarMonth()) }
var selectedDay by remember { mutableIntStateOf(calendarConfig.getCalendarDay()) }
Column(
modifier = Modifier.background(Color.White),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(R.drawable.ic_chevron_left),
contentDescription = null,
modifier = Modifier.clickable {
if (selectedMonth > 1) {
selectedMonth -= 1
} else {
selectedMonth = 12
selectedYear -= 1
}
}
)
Text(
text = "$selectedYear-$selectedMonth",
fontSize = 20.sp,
color = Color.Black,
modifier = Modifier
.padding(vertical = 10.dp)
)
Image(
painter = painterResource(R.drawable.ic_chevron_right),
contentDescription = null,
modifier = Modifier.clickable {
if (selectedMonth < 12) {
selectedMonth += 1
} else {
selectedMonth = 1
selectedYear += 1
}
}
)
}
CalendarComponent(
modifier = Modifier.padding(36.dp),
calendarConfig = calendarConfig,
selectedYear = selectedYear,
selectedMonth = selectedMonth,
selectedDay = selectedDay,
onDaySelect = {
selectedDay = it
},
unClickableDays = setOf()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package ac.dnd.bookkeeping.android.presentation.common.view.calendar

import java.util.Calendar

class CalendarConfig {
private val calendar: Calendar = Calendar.getInstance()
private var maxDate = 0
private var todayDay = calendar.get(Calendar.DAY_OF_MONTH)

fun getCurrentCalendarDate(
year: Int,
month: Int
): List<CalendarDay> {
calendar.apply {
set(Calendar.YEAR, year)
set(Calendar.MONTH, month - 1)
maxDate = getActualMaximum(Calendar.DATE)
}
val dataSet = ArrayList<CalendarDay>()
makeMonthDate(dataSet)
return dataSet
}

fun getCalendarYear() = calendar.get(Calendar.YEAR)
fun getCalendarMonth() = calendar.get(Calendar.MONTH)+1
fun getCalendarDay() = todayDay
fun getDayOfWeek() = DAY_OF_WEEK

private fun makeMonthDate(dataSet: ArrayList<CalendarDay>) {
calendar.set(Calendar.DATE, 1)
todayDay = 0
val prevTail = calendar.get(Calendar.DAY_OF_WEEK) - 1
makeBeforeDays(prevTail, dataSet)
makeCurrentDays(dataSet)
val nextHead = LOW_OF_CALENDAR * WEEK_DAY_COUNT - (prevTail + maxDate)
makeNextHeadDays(nextHead, dataSet)
}

private fun makeBeforeDays(
prevTail: Int,
dataSet: ArrayList<CalendarDay>,
) {
var maxOffsetDate = maxDate - prevTail
for (i in 1..prevTail) {
dataSet.add(
CalendarDay(
day = ++maxOffsetDate,
dayType = CalendarDayType.OTHER_MONTH
)
)
}
}

private fun makeCurrentDays(dataSet: ArrayList<CalendarDay>) {
val todayCalendar = Calendar.getInstance()
val calenderDate = todayCalendar.get(Calendar.DATE)
val todayDate = todayCalendar.get(Calendar.YEAR) * 12 + todayCalendar.get(Calendar.MONTH)
val currentDate = calendar.get(Calendar.YEAR) * 12 + calendar.get(Calendar.MONTH)
var currentDayType =
if (todayDate < currentDate) CalendarDayType.AFTER_TODAY else CalendarDayType.BEFORE_TODAY

for (i in 1..maxDate) {
if (todayDate == currentDate && i == calenderDate) {
todayDay = i
dataSet.add(CalendarDay(i, CalendarDayType.TODAY))
currentDayType = CalendarDayType.AFTER_TODAY
} else {
dataSet.add(CalendarDay(i, currentDayType))
}
}
}

private fun makeNextHeadDays(
nextHead: Int,
dataSet: ArrayList<CalendarDay>
) {
for (i in 1..nextHead) dataSet.add(CalendarDay(i, CalendarDayType.OTHER_MONTH))
}

companion object {
private val DAY_OF_WEEK = listOf("일", "월", "화", "수", "목", "금", "토")
const val WEEK_DAY_COUNT = 7
const val LOW_OF_CALENDAR = 6
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ac.dnd.bookkeeping.android.presentation.common.view.calendar

data class CalendarDay(
val day: Int,
val dayType: CalendarDayType
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ac.dnd.bookkeeping.android.presentation.common.view.calendar

enum class CalendarDayType {
OTHER_MONTH,
BEFORE_TODAY,
TODAY,
AFTER_TODAY,
}
Loading