Skip to content

Commit

Permalink
[Feat]: 캘린더 컴포넌트 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
jinuemong committed Jan 31, 2024
1 parent 4fef053 commit 613096e
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 0 deletions.
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,84 @@
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)
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,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
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
)
)
}
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()
)
}
}

0 comments on commit 613096e

Please sign in to comment.