diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteScreen.kt index dfdb7fd8..52f00266 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteScreen.kt @@ -41,6 +41,7 @@ import com.threegap.bitnagil.presentation.screen.routinewrite.component.template import com.threegap.bitnagil.presentation.screen.routinewrite.contract.RoutineWriteSideEffect import com.threegap.bitnagil.presentation.screen.routinewrite.contract.RoutineWriteState import com.threegap.bitnagil.presentation.screen.routinewrite.model.Day +import com.threegap.bitnagil.presentation.screen.routinewrite.model.ExpandableSection import com.threegap.bitnagil.presentation.screen.routinewrite.model.RepeatType import com.threegap.bitnagil.presentation.screen.routinewrite.model.RoutineWriteType import com.threegap.bitnagil.presentation.screen.routinewrite.model.Time @@ -184,7 +185,7 @@ private fun RoutineWriteScreen( verticalArrangement = Arrangement.spacedBy(12.dp), ) { ExpandableContent( - expand = state.subRoutineUiExpanded, + expand = state.expandedSection == ExpandableSection.SUB_ROUTINE, required = false, iconResourceId = R.drawable.img_subroutines, title = "세부루틴", @@ -230,7 +231,7 @@ private fun RoutineWriteScreen( } ExpandableContent( - expand = state.repeatDaysUiExpanded, + expand = state.expandedSection == ExpandableSection.REPEAT_DAYS, required = true, iconResourceId = R.drawable.img_repeat_days, title = "반복 요일", @@ -286,7 +287,7 @@ private fun RoutineWriteScreen( } ExpandableContent( - expand = state.periodUiExpanded, + expand = state.expandedSection == ExpandableSection.PERIOD, required = true, iconResourceId = R.drawable.img_routine_period, title = "목표 기간", @@ -315,7 +316,7 @@ private fun RoutineWriteScreen( } ExpandableContent( - expand = state.startTimeUiExpanded, + expand = state.expandedSection == ExpandableSection.START_TIME, required = true, iconResourceId = R.drawable.img_start_time, title = "시간", @@ -371,7 +372,7 @@ private fun getSubRoutinePlaceHolder(index: Int): String { fun RoutineWriteScreenPreview() { BitnagilTheme { RoutineWriteScreen( - state = RoutineWriteState.INIT.copy(periodUiExpanded = true, startTimeUiExpanded = true), + state = RoutineWriteState.INIT.copy(expandedSection = ExpandableSection.PERIOD), setRoutineName = {}, setSubRoutineName = { _, _ -> }, selectRepeatTime = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteViewModel.kt index f356f2b0..44f19da9 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/RoutineWriteViewModel.kt @@ -12,6 +12,7 @@ import com.threegap.bitnagil.presentation.screen.routinewrite.contract.RoutineWr import com.threegap.bitnagil.presentation.screen.routinewrite.contract.RoutineWriteState import com.threegap.bitnagil.presentation.screen.routinewrite.model.Date import com.threegap.bitnagil.presentation.screen.routinewrite.model.Day +import com.threegap.bitnagil.presentation.screen.routinewrite.model.ExpandableSection import com.threegap.bitnagil.presentation.screen.routinewrite.model.RepeatType import com.threegap.bitnagil.presentation.screen.routinewrite.model.RoutineWriteType import com.threegap.bitnagil.presentation.screen.routinewrite.model.SelectableDay @@ -299,37 +300,49 @@ class RoutineWriteViewModel @AssistedInject constructor( } fun toggleSubRoutineUiExpanded() = intent { - val currentSubRoutineUiExpanded = state.subRoutineUiExpanded reduce { state.copy( - subRoutineUiExpanded = !currentSubRoutineUiExpanded, + expandedSection = if (state.expandedSection == ExpandableSection.SUB_ROUTINE) { + ExpandableSection.NONE + } else { + ExpandableSection.SUB_ROUTINE + }, ) } } fun toggleRepeatDaysUiExpanded() = intent { - val currentRepeatDaysUiExpanded = state.repeatDaysUiExpanded reduce { state.copy( - repeatDaysUiExpanded = !currentRepeatDaysUiExpanded, + expandedSection = if (state.expandedSection == ExpandableSection.REPEAT_DAYS) { + ExpandableSection.NONE + } else { + ExpandableSection.REPEAT_DAYS + }, ) } } fun togglePeriodUiExpanded() = intent { - val currentPeriodUiExpanded = state.periodUiExpanded reduce { state.copy( - periodUiExpanded = !currentPeriodUiExpanded, + expandedSection = if (state.expandedSection == ExpandableSection.PERIOD) { + ExpandableSection.NONE + } else { + ExpandableSection.PERIOD + }, ) } } fun toggleStartTimeUiExpanded() = intent { - val currentStartTimeUiExpanded = state.startTimeUiExpanded reduce { state.copy( - startTimeUiExpanded = !currentStartTimeUiExpanded, + expandedSection = if (state.expandedSection == ExpandableSection.START_TIME) { + ExpandableSection.NONE + } else { + ExpandableSection.START_TIME + }, ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/atom/namefield/NameField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/atom/namefield/NameField.kt index 4747f3fb..71330d2c 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/atom/namefield/NameField.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/atom/namefield/NameField.kt @@ -9,11 +9,15 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme @@ -29,6 +33,8 @@ fun NameField( modifier: Modifier = Modifier, placeholder: String = "", ) { + val focusManager = LocalFocusManager.current + BasicTextField( value = value, onValueChange = onValueChange, @@ -36,6 +42,8 @@ fun NameField( .fillMaxWidth(), singleLine = true, textStyle = BitnagilTheme.typography.title3SemiBold, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), decorationBox = { innerTextField -> Column { Row( diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/expandablecontent/ExpandableContent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/expandablecontent/ExpandableContent.kt index 8b6ed06b..c0f0b123 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/expandablecontent/ExpandableContent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/expandablecontent/ExpandableContent.kt @@ -94,7 +94,7 @@ fun ExpandableContent( } if (!expand) { Text( - text = if (showValueText) valueText ?: placeHolder else placeHolder, + text = if (showValueText) valueText else placeHolder, style = if (showValueText) mainTextStyle else subTextStyle, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/subroutinefield/SubRoutineField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/subroutinefield/SubRoutineField.kt index 3732715b..459c2a29 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/subroutinefield/SubRoutineField.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/block/subroutinefield/SubRoutineField.kt @@ -10,12 +10,16 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme @@ -28,6 +32,8 @@ fun SubRoutineField( onValueChange: (String) -> Unit, enabled: Boolean, ) { + val focusManager = LocalFocusManager.current + Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, @@ -47,6 +53,8 @@ fun SubRoutineField( singleLine = true, enabled = enabled, textStyle = BitnagilTheme.typography.body2Medium.copy(color = if (enabled) BitnagilTheme.colors.coolGray30 else BitnagilTheme.colors.coolGray90), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), decorationBox = { innerTextField -> Column { Box { diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt index a6856d18..3999d3ba 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt @@ -98,19 +98,19 @@ private fun DatePickerBottomSheetContent( val lastDaysOfPrevMonth = remember(currentYear, currentMonth) { CalendarUtils.lastDaysOfPrevMonth(currentYear, currentMonth) } - val firstDaysOfNextMonth = remember(currentYear, currentMonth) { - CalendarUtils.firstDaysOfNextMonth(currentYear, currentMonth) - } val currentDaysOfMonth = remember(currentYear, currentMonth) { CalendarUtils.getDayAmountOfMonth(currentYear, currentMonth) } - val prevMonthButtonEnabled by remember(availableStartDate) { + val totalCells = 42 + val firstDaysOfNextMonthCount = totalCells - lastDaysOfPrevMonth.size - currentDaysOfMonth + + val prevMonthButtonEnabled by remember(availableStartDate, currentYear, currentMonth) { derivedStateOf { (availableStartDate == null) || (availableStartDate.year < currentYear) || (availableStartDate.month < currentMonth) } } - val nextMonthButtonEnabled by remember(availableEndDate) { + val nextMonthButtonEnabled by remember(availableEndDate, currentYear, currentMonth) { derivedStateOf { (availableEndDate == null) || (availableEndDate.year > currentYear) || (availableEndDate.month > currentMonth) } @@ -186,13 +186,31 @@ private fun DatePickerBottomSheetContent( } itemsIndexed(lastDaysOfPrevMonth) { _, day -> + val prevMonth = if (currentMonth == 1) 12 else currentMonth - 1 + val prevYear = if (currentMonth == 1) currentYear - 1 else currentYear + val prevDate = Date(prevYear, prevMonth, day) + val available = prevDate.checkInRange(availableStartDate, availableEndDate) + Box( - modifier = Modifier.aspectRatio(1f), + modifier = Modifier + .aspectRatio(1f) + .clickableWithoutRipple { + if (!available) return@clickableWithoutRipple + if (currentMonth == 1) { + currentMonth = 12 + currentYear -= 1 + } else { + currentMonth -= 1 + } + selectedDate = prevDate + }, contentAlignment = Alignment.Center, ) { Text( "$day", - style = BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray80), + style = BitnagilTheme.typography.subtitle1Regular.copy( + color = if (available) BitnagilTheme.colors.coolGray80 else BitnagilTheme.colors.coolGray95, + ), ) } } @@ -202,13 +220,16 @@ private fun DatePickerBottomSheetContent( val currentDate = Date(year = currentYear, month = currentMonth, day = index + 1) val available = currentDate.checkInRange(startDate = availableStartDate, endDate = availableEndDate) Box( - modifier = Modifier.aspectRatio(1f).background( - color = if (selected) { BitnagilTheme.colors.orange50 } else { Color.Transparent }, - shape = if (selected) { RoundedCornerShape(12.dp) } else { RectangleShape }, - ).clickableWithoutRipple { - if (!available) return@clickableWithoutRipple - selectedDate = currentDate - }, + modifier = Modifier + .aspectRatio(1f) + .background( + color = if (selected) BitnagilTheme.colors.orange50 else Color.Transparent, + shape = if (selected) RoundedCornerShape(12.dp) else RectangleShape, + ) + .clickableWithoutRipple { + if (!available) return@clickableWithoutRipple + selectedDate = currentDate + }, contentAlignment = Alignment.Center, ) { Text( @@ -224,14 +245,33 @@ private fun DatePickerBottomSheetContent( } } - itemsIndexed(firstDaysOfNextMonth) { _, day -> + items(firstDaysOfNextMonthCount) { index -> + val day = index + 1 + val nextMonth = if (currentMonth == 12) 1 else currentMonth + 1 + val nextYear = if (currentMonth == 12) currentYear + 1 else currentYear + val nextDate = Date(nextYear, nextMonth, day) + val available = nextDate.checkInRange(availableStartDate, availableEndDate) + Box( - modifier = Modifier.aspectRatio(1f), + modifier = Modifier + .aspectRatio(1f) + .clickableWithoutRipple { + if (!available) return@clickableWithoutRipple + if (currentMonth == 12) { + currentMonth = 1 + currentYear += 1 + } else { + currentMonth += 1 + } + selectedDate = nextDate + }, contentAlignment = Alignment.Center, ) { Text( "$day", - style = BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray80), + style = BitnagilTheme.typography.subtitle1Regular.copy( + color = if (available) BitnagilTheme.colors.coolGray80 else BitnagilTheme.colors.coolGray95, + ), ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/contract/RoutineWriteState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/contract/RoutineWriteState.kt index 3b8ec9ee..1caa1c20 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/contract/RoutineWriteState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/contract/RoutineWriteState.kt @@ -4,6 +4,7 @@ import android.os.Parcelable import com.threegap.bitnagil.domain.recommendroutine.model.RecommendCategory import com.threegap.bitnagil.presentation.screen.routinewrite.model.Date import com.threegap.bitnagil.presentation.screen.routinewrite.model.Day +import com.threegap.bitnagil.presentation.screen.routinewrite.model.ExpandableSection import com.threegap.bitnagil.presentation.screen.routinewrite.model.RepeatType import com.threegap.bitnagil.presentation.screen.routinewrite.model.RoutineWriteType import com.threegap.bitnagil.presentation.screen.routinewrite.model.SelectableDay @@ -26,10 +27,7 @@ data class RoutineWriteState( val showStartDatePickerBottomSheet: Boolean, val showEndDatePickerBottomSheet: Boolean, val routineWriteType: RoutineWriteType, - val subRoutineUiExpanded: Boolean, - val repeatDaysUiExpanded: Boolean, - val periodUiExpanded: Boolean, - val startTimeUiExpanded: Boolean, + val expandedSection: ExpandableSection, val recommendedRoutineType: RecommendCategory?, ) : Parcelable { companion object { @@ -77,10 +75,7 @@ data class RoutineWriteState( showEndDatePickerBottomSheet = false, showTimePickerBottomSheet = false, routineWriteType = RoutineWriteType.Add, - subRoutineUiExpanded = false, - repeatDaysUiExpanded = false, - periodUiExpanded = false, - startTimeUiExpanded = false, + expandedSection = ExpandableSection.NONE, startDate = Date.now(), endDate = Date.now(), recommendedRoutineType = null, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/model/ExpandableSection.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/model/ExpandableSection.kt new file mode 100644 index 00000000..7595940d --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinewrite/model/ExpandableSection.kt @@ -0,0 +1,9 @@ +package com.threegap.bitnagil.presentation.screen.routinewrite.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +enum class ExpandableSection : Parcelable { + SUB_ROUTINE, REPEAT_DAYS, PERIOD, START_TIME, NONE +}