Skip to content

Commit

Permalink
feat: 그룹 생성 플로우 종료 날짜 #20
Browse files Browse the repository at this point in the history
  • Loading branch information
easyhz committed Jul 20, 2024
1 parent fe75fef commit e287e26
Show file tree
Hide file tree
Showing 14 changed files with 424 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.easyhz.noffice.core.design_system.component.calendar

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.easyhz.noffice.core.design_system.theme.Grey700
import com.easyhz.noffice.core.design_system.theme.SubBody14
import com.easyhz.noffice.core.design_system.util.calendar.RANGE_MONTH
import com.easyhz.noffice.core.design_system.util.calendar.displayText
import com.easyhz.noffice.core.design_system.util.calendar.rememberFirstMostVisibleMonth
import com.kizitonwose.calendar.compose.CalendarState
import com.kizitonwose.calendar.compose.HorizontalCalendar
import com.kizitonwose.calendar.compose.rememberCalendarState
import com.kizitonwose.calendar.core.OutDateStyle
import com.kizitonwose.calendar.core.daysOfWeek
import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale
import com.kizitonwose.calendar.core.yearMonth
import kotlinx.coroutines.launch
import java.time.LocalDate

@Composable
fun MonthCalendarView(
modifier: Modifier = Modifier,
selection: LocalDate,
calendarPadding: Dp,
onChangeDate: (LocalDate) -> Unit
) {
val scope = rememberCoroutineScope()
val today = remember { LocalDate.now() }
val startMonth = remember { today.yearMonth }
val endMonth = remember { today.yearMonth.plusMonths(RANGE_MONTH) }
val monthState = rememberCalendarState(
startMonth = startMonth,
endMonth = endMonth,
firstDayOfWeek = firstDayOfWeekFromLocale(),
outDateStyle = OutDateStyle.EndOfRow,
)
val title = rememberFirstMostVisibleMonth(state = monthState)

LaunchedEffect(selection) {
if(title.yearMonth == selection.yearMonth) return@LaunchedEffect
monthState.animateScrollToMonth(selection.yearMonth)
}
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
MonthHeader(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
title = title.yearMonth.displayText(),
onClickBefore = {
scope.launch {
monthState.animateScrollToMonth(title.yearMonth.minusMonths(1))
}
},
onClickNext = {
scope.launch {
monthState.animateScrollToMonth(title.yearMonth.plusMonths(1))
}
}
)
MonthCalendarContent(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(horizontal = calendarPadding),
monthState = monthState,
selection = selection,
today = today,
onChangeDate = onChangeDate
)
}
}

@Composable
fun MonthCalendarContent(
modifier: Modifier = Modifier,
monthState: CalendarState,
selection: LocalDate,
today: LocalDate,
onChangeDate: (LocalDate) -> Unit
) {
HorizontalCalendar(
modifier = modifier,
state = monthState,
monthHeader = { CalendarHeader() },
dayContent = {day ->
Day(
day = day,
isSelected = selection == day.date,
today = today,
) { clickedDay ->
onChangeDate(clickedDay)
}
},
)
}

@Stable
@Composable
internal fun CalendarHeader() {
val daysOfWeek = remember { daysOfWeek() }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
) {
for (dayOfWeek in daysOfWeek) {
Text(
modifier = Modifier.weight(1f),
text = dayOfWeek.displayText(),
style = SubBody14,
color = Grey700,
textAlign = TextAlign.Center
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.easyhz.noffice.core.design_system.component.calendar

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.easyhz.noffice.core.design_system.extension.noRippleClickable
import com.easyhz.noffice.core.design_system.theme.SemiBold14
import com.easyhz.noffice.core.design_system.util.calendar.dateFormatter
import com.easyhz.noffice.core.design_system.util.calendar.getSelectionBoxColor
import com.easyhz.noffice.core.design_system.util.calendar.getTextColor
import com.kizitonwose.calendar.core.CalendarDay
import java.time.LocalDate

@Composable
internal fun Day(
modifier: Modifier = Modifier,
day: CalendarDay,
today: LocalDate,
isSelected: Boolean,
onClick: (LocalDate) -> Unit
) {
val enabled = day.date >= today
val selectionBoxColor = getSelectionBoxColor(isSelected)
val textColor = getTextColor(day, isSelected, enabled)
Box(
modifier = modifier
.fillMaxWidth()
.noRippleClickable(enabled = enabled) { onClick(day.date) },
contentAlignment = Alignment.TopCenter,
) {
Box(modifier = Modifier.padding(bottom = 6.dp)
.size(40.dp)
.clip(RoundedCornerShape(8.dp))
.background(selectionBoxColor),
) {
Text(
modifier = Modifier.align(Alignment.TopCenter).padding(top = 4.dp),
text = dateFormatter.format(day.date),
style = SemiBold14,
textAlign = TextAlign.Center,
color = textColor
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.easyhz.noffice.core.design_system.component.calendar

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.easyhz.noffice.core.design_system.R
import com.easyhz.noffice.core.design_system.extension.noRippleClickable
import com.easyhz.noffice.core.design_system.theme.Grey500
import com.easyhz.noffice.core.design_system.theme.InputDialogTitle

@Composable
internal fun MonthHeader(
modifier: Modifier = Modifier,
title: String,
onClickBefore: () -> Unit,
onClickNext: () -> Unit
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Box(modifier = Modifier.size(32.dp).noRippleClickable { onClickBefore() }) {
Icon(
modifier = Modifier.align(Alignment.CenterStart).size(24.dp),
painter = painterResource(id = R.drawable.ic_chevron_left),
contentDescription = "before",
tint = Grey500
)
}
Text(
text = title,
style = InputDialogTitle
)
Box(modifier = Modifier.size(32.dp).noRippleClickable { onClickNext() }) {
Icon(
modifier = Modifier.align(Alignment.CenterEnd).size(24.dp),
painter = painterResource(id = R.drawable.ic_chevron_right),
contentDescription = "next",
tint = Grey500
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ fun Modifier.screenHorizonPadding(): Modifier = padding(horizontal = 16.dp)

inline fun Modifier.noRippleClickable(
interactionSource: MutableInteractionSource? = null,
enabled: Boolean = true,
crossinline onClick: () -> Unit,
): Modifier = composed {
clickable(indication = null,
clickable(
indication = null,
enabled = enabled,
interactionSource = interactionSource ?: remember { MutableInteractionSource() }) {
onClick()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,7 @@ val Blue600 = Color(0xFF00A0EB)
val Blue700 = Color(0xFF007DB8)

@Stable
val DimColor = Color(0xFF121212).copy(alpha = 0.5f)
val DimColor = Color(0xFF121212).copy(alpha = 0.5f)

@Stable
val Red = Color(0xFFFF5757)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.easyhz.noffice.core.design_system.util.calendar

internal const val CONTENT_SIZE = 40
internal const val NUM_OF_DAYS_IN_WEEK = 7
internal const val PADDING_NUM_OF_DAYS_IN_WEEK = NUM_OF_DAYS_IN_WEEK * 2
internal const val WITHOUT_PADDING_NUM = PADDING_NUM_OF_DAYS_IN_WEEK - 2
fun getCalendarPadding(target: Int, screenWidth: Int) =
(((PADDING_NUM_OF_DAYS_IN_WEEK * target) + (NUM_OF_DAYS_IN_WEEK * CONTENT_SIZE) - screenWidth) / WITHOUT_PADDING_NUM).coerceAtLeast(0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.easyhz.noffice.core.design_system.util.calendar

import java.time.DayOfWeek
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.format.TextStyle
import java.util.Locale

internal val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("d")

internal fun YearMonth.displayText(): String =
"${year}.${month.value}"
internal fun DayOfWeek.displayText(uppercase: Boolean = false): String =
getDisplayName(TextStyle.SHORT, Locale.KOREA).let { value ->
if (uppercase) value.uppercase(Locale.KOREA) else value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.easyhz.noffice.core.design_system.util.calendar

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.graphics.Color
import com.easyhz.noffice.core.design_system.theme.Green500
import com.easyhz.noffice.core.design_system.theme.Grey300
import com.easyhz.noffice.core.design_system.theme.Grey700
import com.easyhz.noffice.core.design_system.theme.Red
import com.easyhz.noffice.core.design_system.theme.White
import com.kizitonwose.calendar.compose.CalendarLayoutInfo
import com.kizitonwose.calendar.compose.CalendarState
import com.kizitonwose.calendar.core.CalendarDay
import com.kizitonwose.calendar.core.CalendarMonth
import com.kizitonwose.calendar.core.DayPosition
import com.kizitonwose.calendar.core.Week
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import java.time.DayOfWeek

const val RANGE_MONTH = 500L


/**
* 스크롤 감지하고 해당 월 반환
*
* @param state HorizontalCalendar 의 상태인 [CalendarState]
* @param viewportPercent 넘어오는 정도 default : 50f
*
* @return 해당 주 [Week]
*/
@Composable
internal fun rememberFirstMostVisibleMonth(
state: CalendarState,
viewportPercent: Float = 50f,
): CalendarMonth {
val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) }
LaunchedEffect(state) {
snapshotFlow { state.layoutInfo.firstMostVisibleMonth(viewportPercent) }
.distinctUntilChanged()
.filterNotNull()
.collect { month -> visibleMonth.value = month }
}
return visibleMonth.value
}


private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50f): CalendarMonth? {
return if (visibleMonthsInfo.isEmpty()) {
null
} else {
val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f
visibleMonthsInfo.firstOrNull { itemInfo ->
if (itemInfo.offset < 0) {
itemInfo.offset + itemInfo.size >= viewportSize
} else {
itemInfo.size - itemInfo.offset >= viewportSize
}
}?.month
}
}

internal fun getSelectionBoxColor(isSelected: Boolean): Color {
return if (isSelected) Green500 else White
}

internal fun getTextColor(day: CalendarDay, isSelected: Boolean, enabled: Boolean): Color {
return when {
isSelected -> White
!enabled || day.position != DayPosition.MonthDate -> Grey300
day.date.dayOfWeek == DayOfWeek.SUNDAY -> Red
else -> Grey700
}
}
1 change: 1 addition & 0 deletions core/design-system/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@
<string name="organization_creation_name_placeholder">이름을 입력해 주세요.</string>
<string name="organization_creation_category_title">그룹의 카테고리를 모두 선택해 주세요</string>
<string name="organization_creation_image_title">대표 이미지를 설정해 주세요</string>
<string name="organization_creation_end_date_title">활동 종료 날짜가 언제인가요?</string>
</resources>
Loading

0 comments on commit e287e26

Please sign in to comment.