From 9d9dd725f53e337751166d245c0142570ff9d876 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sat, 18 Jan 2025 00:13:53 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[FEAT/#45]=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/AlarmAddEditContract.kt | 1 + .../com/yapp/alarm/AlarmAddEditViewModel.kt | 102 ++++++++++++++++-- .../src/main/java/com/yapp/alarm/AlarmDay.kt | 12 +++ 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt index fa7bff7c..66bbbf0a 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt @@ -8,6 +8,7 @@ sealed class AlarmAddEditContract { val currentAmPm: String = "오전", val currentHour: Int = 6, val currentMinute: Int = 0, + val alarmMessage: String = "", val days: Set = enumValues().toSet(), val isWeekdaysChecked: Boolean = false, val isWeekendsChecked: Boolean = false, diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt index 46641c7f..b914e24d 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -1,24 +1,32 @@ package com.yapp.alarm +import androidx.lifecycle.viewModelScope import com.yapp.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class AlarmAddEditViewModel @Inject constructor() : BaseViewModel( initialState = AlarmAddEditContract.State(), ) { + private var debounceJob: Job? = null // 디바운싱을 위한 Job + fun processAction(action: AlarmAddEditContract.Action) { - when (action) { - is AlarmAddEditContract.Action.UpdateAlarmTime -> updateAlarmTime(action.amPm, action.hour, action.minute) - is AlarmAddEditContract.Action.ToggleWeekdaysChecked -> toggleWeekdaysChecked() - is AlarmAddEditContract.Action.ToggleWeekendsChecked -> toggleWeekendsChecked() - is AlarmAddEditContract.Action.ToggleDaySelection -> toggleDaySelection(action.day) - is AlarmAddEditContract.Action.ToggleDisableHolidayChecked -> toggleDisableHolidayChecked() + viewModelScope.launch { + when (action) { + is AlarmAddEditContract.Action.UpdateAlarmTime -> updateAlarmTime(action.amPm, action.hour, action.minute) + is AlarmAddEditContract.Action.ToggleWeekdaysChecked -> toggleWeekdaysChecked() + is AlarmAddEditContract.Action.ToggleWeekendsChecked -> toggleWeekendsChecked() + is AlarmAddEditContract.Action.ToggleDaySelection -> toggleDaySelection(action.day) + is AlarmAddEditContract.Action.ToggleDisableHolidayChecked -> toggleDisableHolidayChecked() + } } } - private fun updateAlarmTime(amPm: String, hour: Int, minute: Int) { + private suspend fun updateAlarmTime(amPm: String, hour: Int, minute: Int) { updateState { copy( currentAmPm = amPm, @@ -26,6 +34,43 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel 0 -> "${days}일 ${hours}시간 후에 울려요" + hours > 0 -> "${hours}시간 ${minutes}분 후에 울려요" + else -> "${minutes}분 후에 울려요" + } + + updateState { copy(alarmMessage = newMessage) } } private fun toggleWeekdaysChecked() { @@ -36,12 +81,14 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel, + ): java.time.LocalDateTime { + if (selectedDays.isEmpty()) { + return if (alarmTimeToday.isBefore(now)) { + alarmTimeToday.plusDays(1) + } else { + alarmTimeToday + } + } + + val currentDayOfWeek = now.dayOfWeek.value + val selectedDaysOfWeek = selectedDays.map { it.toDayOfWeek().value }.sorted() + val nextDay = selectedDaysOfWeek.firstOrNull { it > currentDayOfWeek } + ?: selectedDaysOfWeek.first() + val daysToAdd = if (nextDay > currentDayOfWeek) { + nextDay - currentDayOfWeek + } else { + 7 - (currentDayOfWeek - nextDay) + } + val nextAlarmDate = now.toLocalDate().plusDays(daysToAdd.toLong()) + return nextAlarmDate.atTime(alarmTimeToday.toLocalTime()) } } diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmDay.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmDay.kt index 96df9efb..734b29ba 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmDay.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmDay.kt @@ -27,3 +27,15 @@ enum class AlarmDay( R.string.alarm_add_edit_saturday, ), } + +fun AlarmDay.toDayOfWeek(): java.time.DayOfWeek { + return when (this) { + AlarmDay.SUN -> java.time.DayOfWeek.SUNDAY + AlarmDay.MON -> java.time.DayOfWeek.MONDAY + AlarmDay.TUE -> java.time.DayOfWeek.TUESDAY + AlarmDay.WED -> java.time.DayOfWeek.WEDNESDAY + AlarmDay.THU -> java.time.DayOfWeek.THURSDAY + AlarmDay.FRI -> java.time.DayOfWeek.FRIDAY + AlarmDay.SAT -> java.time.DayOfWeek.SATURDAY + } +} From b52dc41e736785baeb2b2dd96b4e979fae4b73ff Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sat, 18 Jan 2025 00:15:14 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[UI/#45]=20=EC=95=8C=EB=9E=8C=20=EB=AF=B8?= =?UTF-8?q?=EB=A3=A8=EA=B8=B0,=20=EC=82=AC=EC=9A=B4=EB=93=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=95=84=EC=9D=B4=ED=85=9C=20UI=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable/ic_arrow_right.xml | 13 ++++ .../java/com/yapp/alarm/AlarmAddEditScreen.kt | 75 ++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 core/designsystem/src/main/res/drawable/ic_arrow_right.xml diff --git a/core/designsystem/src/main/res/drawable/ic_arrow_right.xml b/core/designsystem/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 00000000..2e9c43f3 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,13 @@ + + + diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt index db686923..f69924f1 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -72,7 +73,7 @@ fun AlarmAddEditScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { AlarmAddEditTopBar( - title = "1일 12시간 후에 울려요", + title = state.alarmMessage, onBack = { }, ) Box( @@ -154,6 +155,9 @@ private fun AlarmAddEditSettingsSection( .background( color = OrbitTheme.colors.gray_800, shape = RoundedCornerShape(12.dp), + ) + .clip( + shape = RoundedCornerShape(12.dp), ), ) { AlarmAddEditSelectDaysSection( @@ -167,6 +171,65 @@ private fun AlarmAddEditSettingsSection( isDisableHolidayChecked = isDisableHolidayChecked, processAction = processAction, ) + Spacer( + modifier = Modifier.fillMaxWidth() + .height(1.dp) + .padding(horizontal = 20.dp) + .background(OrbitTheme.colors.gray_700), + ) + AlarmAddEditSettingItem( + label = "알람 미루기", + description = "5분, 무한", + onClick = { }, + ) + Spacer( + modifier = Modifier.fillMaxWidth() + .height(1.dp) + .padding(horizontal = 20.dp) + .background(OrbitTheme.colors.gray_700), + ) + AlarmAddEditSettingItem( + label = "사운드", + description = "진동, 알림음1", + onClick = { }, + ) + } +} + +@Composable +private fun AlarmAddEditSettingItem( + label: String, + description: String, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onClick() + } + .padding( + horizontal = 20.dp, + vertical = 14.dp, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + label, + style = OrbitTheme.typography.body1SemiBold, + color = OrbitTheme.colors.white, + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + description, + style = OrbitTheme.typography.body2Regular, + color = OrbitTheme.colors.gray_50, + ) + Icon( + painter = painterResource(id = core.designsystem.R.drawable.ic_arrow_right), + contentDescription = "Arrow", + tint = OrbitTheme.colors.gray_300, + ) } } @@ -276,6 +339,16 @@ fun AlarmAddEditSettingsSectionPreview() { ) } +@Preview +@Composable +fun AlarmAddEditSettingItemPreview() { + AlarmAddEditSettingItem( + label = "알람 미루기", + description = "5분, 무한", + onClick = { }, + ) +} + @Preview @Composable fun AlarmAddEditScreenPreview() { From 8b135d1abfe787f6a5e482b62897d52d4c0641bb Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sat, 18 Jan 2025 15:36:34 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[UI/#45]=20AlarmCheckItem=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98,=20=EA=B8=80=EC=9E=90=20=EC=83=89=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt | 5 +++-- .../main/java/com/yapp/alarm/component/AlarmCheckItem.kt | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt index f69924f1..8714f799 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -100,8 +100,9 @@ fun AlarmAddEditScreen( enabled = true, modifier = Modifier .padding( - horizontal = 20.dp, - vertical = 12.dp, + start = 20.dp, + end = 20.dp, + bottom = 12.dp, ) .height(56.dp) .fillMaxWidth(), diff --git a/feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt b/feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt index bb9e1612..d3aaa522 100644 --- a/feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt +++ b/feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt @@ -32,12 +32,12 @@ internal fun AlarmCheckItem( Icon( painter = painterResource(id = core.designsystem.R.drawable.ic_check), contentDescription = "Check", - tint = if (isPressed) OrbitTheme.colors.main else OrbitTheme.colors.white, + tint = if (isPressed) OrbitTheme.colors.main else OrbitTheme.colors.gray_400, ) Text( text = label, - style = OrbitTheme.typography.body1Medium, - color = if (isPressed) OrbitTheme.colors.main else OrbitTheme.colors.white, + style = OrbitTheme.typography.label1Medium, + color = if (isPressed) OrbitTheme.colors.main else OrbitTheme.colors.gray_400, modifier = Modifier.padding(start = 4.dp), textAlign = TextAlign.Center, ) From c27b57f2429fff47a7b6524153d698df1da7afd1 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 01:27:09 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[UI/#45]=20Switch=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/ui/component/switch/Switch.kt | 117 +++++++++++++----- 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/core/ui/src/main/java/com/yapp/ui/component/switch/Switch.kt b/core/ui/src/main/java/com/yapp/ui/component/switch/Switch.kt index 8d6b74fe..e12c9a81 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/switch/Switch.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/switch/Switch.kt @@ -1,54 +1,87 @@ package com.yapp.ui.component.switch +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.yapp.designsystem.theme.OrbitTheme +import kotlin.math.roundToInt @Composable fun OrbitSwitch( - isSelected: Boolean, - onClick: (Boolean) -> Unit, + modifier: Modifier = Modifier, + isChecked: Boolean, + isEnabled: Boolean = true, + onClick: () -> Unit, ) { - val thumbColor = if (isSelected) { - OrbitTheme.colors.gray_900 + val (thumbColor, backgroundColor) = if (isChecked && isEnabled) { + Pair(OrbitTheme.colors.gray_900, OrbitTheme.colors.main) + } else if (!isEnabled) { + Pair(OrbitTheme.colors.gray_500, OrbitTheme.colors.gray_700) } else { - OrbitTheme.colors.gray_300 + Pair(OrbitTheme.colors.gray_300, OrbitTheme.colors.gray_600) } - Switch( - checked = isSelected, - onCheckedChange = onClick, - thumbContent = { - Box( - modifier = Modifier - .size(20.dp) - .background( - color = thumbColor, - shape = CircleShape, - ), - ) - }, - colors = SwitchDefaults.colors( - checkedThumbColor = Color.Transparent, - checkedTrackColor = OrbitTheme.colors.main, - uncheckedThumbColor = Color.Transparent, - uncheckedTrackColor = OrbitTheme.colors.gray_600, - uncheckedBorderColor = Color.Transparent, - ), + val density = LocalDensity.current + val minBound = with(density) { 0.dp.toPx() } + val maxBound = with(density) { 20.dp.toPx() } + val state by animateFloatAsState( + targetValue = if (isChecked && isEnabled) maxBound else minBound, + animationSpec = tween(durationMillis = 200), + label = "custom_switch", ) + + Box( + modifier = modifier + .size(width = 46.dp, height = 26.dp) + .background( + color = backgroundColor, + shape = CircleShape, + ) + .clip(CircleShape) + .padding(3.dp) + .pointerInput(isEnabled) { + detectTapGestures( + onTap = { + if (isEnabled) { + onClick() + } + }, + ) + }, + ) { + Box( + modifier = Modifier + .offset { IntOffset(state.roundToInt(), 0) } + .size(20.dp) + .background( + color = thumbColor, + shape = CircleShape, + ), + ) + } } @Preview @@ -57,11 +90,27 @@ fun OrbitTogglePreview() { OrbitTheme { var isSelected by remember { mutableStateOf(false) } - OrbitSwitch( - isSelected = isSelected, - onClick = { - isSelected = it - }, - ) + var isEnabled by remember { mutableStateOf(true) } + + Column { + OrbitSwitch( + isChecked = isSelected, + isEnabled = isEnabled, + onClick = { + isSelected = !isSelected + }, + ) + Spacer(modifier = Modifier.height(20.dp)) + Button( + onClick = { + isEnabled = !isEnabled + }, + ) { + Text( + text = "스위치 활성화 여부 반영", + style = OrbitTheme.typography.title2Medium, + ) + } + } } } From 27e799766a06d7f08162f650a4e331352090f522 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 01:52:15 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[FEAT/#45]=20=EC=9A=94=EC=9D=BC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EA=B3=B5?= =?UTF-8?q?=ED=9C=B4=EC=9D=BC=20=EC=95=8C=EB=9E=8C=20=EB=81=84=EA=B8=B0=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=97=AC=EB=B6=80=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/AlarmAddEditContract.kt | 1 + .../java/com/yapp/alarm/AlarmAddEditScreen.kt | 14 ++++++-- .../com/yapp/alarm/AlarmAddEditViewModel.kt | 33 ++++++------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt index 66bbbf0a..e36d99af 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt @@ -13,6 +13,7 @@ sealed class AlarmAddEditContract { val isWeekdaysChecked: Boolean = false, val isWeekendsChecked: Boolean = false, val selectedDays: Set = setOf(), + val isDisableHolidayEnabled: Boolean = false, val isDisableHolidayChecked: Boolean = false, ) : UiState diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt index 8714f799..0fc3032e 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -90,6 +90,7 @@ fun AlarmAddEditScreen( isWeekendsChecked = state.isWeekendsChecked, selectedDays = state.selectedDays, isDisableHolidayChecked = state.isDisableHolidayChecked, + isDisableHolidayEnabled = state.isDisableHolidayEnabled, days = state.days, processAction = eventDispatcher, ) @@ -147,6 +148,7 @@ private fun AlarmAddEditSettingsSection( isWeekendsChecked: Boolean, selectedDays: Set, isDisableHolidayChecked: Boolean, + isDisableHolidayEnabled: Boolean, days: Set, processAction: (AlarmAddEditContract.Action) -> Unit, ) { @@ -170,6 +172,7 @@ private fun AlarmAddEditSettingsSection( ) AlarmAddEditDisableHolidaySwitch( isDisableHolidayChecked = isDisableHolidayChecked, + isDisableHolidayEnabled = isDisableHolidayEnabled, processAction = processAction, ) Spacer( @@ -296,12 +299,17 @@ private fun AlarmAddEditSelectDaysSection( @Composable private fun AlarmAddEditDisableHolidaySwitch( isDisableHolidayChecked: Boolean, + isDisableHolidayEnabled: Boolean, processAction: (AlarmAddEditContract.Action) -> Unit, ) { Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp), + .padding( + start = 20.dp, + end = 20.dp, + bottom = 16.dp, + ), verticalAlignment = Alignment.CenterVertically, ) { Icon( @@ -319,7 +327,8 @@ private fun AlarmAddEditDisableHolidaySwitch( Spacer(modifier = Modifier.weight(1f)) OrbitSwitch( - isSelected = isDisableHolidayChecked, + isChecked = isDisableHolidayChecked, + isEnabled = isDisableHolidayEnabled, onClick = { processAction(AlarmAddEditContract.Action.ToggleDisableHolidayChecked) }, @@ -335,6 +344,7 @@ fun AlarmAddEditSettingsSectionPreview() { isWeekendsChecked = false, selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE), isDisableHolidayChecked = false, + isDisableHolidayEnabled = true, days = AlarmDay.entries.toSet(), processAction = { }, ) diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt index b914e24d..084bc519 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -3,8 +3,6 @@ package com.yapp.alarm import androidx.lifecycle.viewModelScope import com.yapp.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -12,8 +10,6 @@ import javax.inject.Inject class AlarmAddEditViewModel @Inject constructor() : BaseViewModel( initialState = AlarmAddEditContract.State(), ) { - private var debounceJob: Job? = null // 디바운싱을 위한 Job - fun processAction(action: AlarmAddEditContract.Action) { viewModelScope.launch { when (action) { @@ -26,27 +22,18 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel 0 -> "${days}일 ${hours}시간 후에 울려요" hours > 0 -> "${hours}시간 ${minutes}분 후에 울려요" else -> "${minutes}분 후에 울려요" } - - updateState { copy(alarmMessage = newMessage) } } private fun toggleWeekdaysChecked() { @@ -86,9 +71,10 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel Date: Sun, 19 Jan 2025 14:12:29 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[UI/#45]=20AlarmSnoozeBottomSheet=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bottomsheet/AlarmSnoozeBottomSheet.kt | 225 ++++++++++++++++++ feature/home/src/main/res/values/strings.xml | 3 +- 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt diff --git a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt new file mode 100644 index 00000000..fa3fdb88 --- /dev/null +++ b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt @@ -0,0 +1,225 @@ +package com.yapp.alarm.component.bottomsheet + +import androidx.compose.foundation.background +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.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.component.button.OrbitButton +import com.yapp.ui.component.radiobutton.OrbitRadioButton +import com.yapp.ui.component.switch.OrbitSwitch +import feature.home.R + +@Composable +internal fun AlarmSnoozeBottomSheet( + isSnoozeEnabled: Boolean, + snoozeIntervalIndex: Int, + snoozeIntervals: List, + onIntervalSelected: (Int) -> Unit, + snoozeCountIndex: Int, + snoozeCounts: List, + onSnoozeToggle: () -> Unit, + onCountSelected: (Int) -> Unit, + onComplete: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + HeaderSection(isSnoozeEnabled, onSnoozeToggle) + Spacer(modifier = Modifier.height(20.dp)) + SelectorSection( + title = stringResource(id = R.string.alarm_add_edit_interval), + selectedIndex = if (isSnoozeEnabled) snoozeIntervalIndex else -1, + items = snoozeIntervals, + isEnabled = isSnoozeEnabled, + onItemSelected = onIntervalSelected, + ) + Spacer(modifier = Modifier.height(32.dp)) + SelectorSection( + title = stringResource(id = R.string.alarm_add_edit_repeat_count), + selectedIndex = if (isSnoozeEnabled) snoozeCountIndex else -1, + items = snoozeCounts, + isEnabled = isSnoozeEnabled, + onItemSelected = onCountSelected, + ) + Spacer(modifier = Modifier.height(20.dp)) + if (isSnoozeEnabled) { + AlarmSnoozeMessage( + interval = snoozeIntervals[snoozeIntervalIndex], + count = snoozeCounts[snoozeCountIndex], + ) + } + Spacer(modifier = Modifier.height(32.dp)) + OrbitButton( + label = stringResource(id = R.string.alarm_add_edit_alarm_snooze), + enabled = isSnoozeEnabled, + containerColor = OrbitTheme.colors.gray_600, + contentColor = OrbitTheme.colors.white, + onClick = onComplete, + ) + Spacer(modifier = Modifier.height(12.dp)) + } +} + +@Composable +private fun HeaderSection(isSnoozeEnabled: Boolean, onSnoozeToggle: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = R.string.alarm_add_edit_alarm_snooze), + style = OrbitTheme.typography.heading2SemiBold, + color = OrbitTheme.colors.white, + ) + Spacer(modifier = Modifier.weight(1f)) + OrbitSwitch( + isChecked = isSnoozeEnabled, + isEnabled = true, + onClick = onSnoozeToggle, + ) + } +} + +@Composable +private fun SelectorSection( + title: String, + selectedIndex: Int, + items: List, + isEnabled: Boolean, + onItemSelected: (Int) -> Unit, +) { + Column { + Text( + text = title, + style = OrbitTheme.typography.headline2Medium, + color = OrbitTheme.colors.gray_50, + ) + Spacer(modifier = Modifier.height(16.dp)) + SelectorItems(items, selectedIndex, isEnabled, onItemSelected) + } +} + +@Composable +private fun SelectorItems( + items: List, + selectedIndex: Int, + isEnabled: Boolean, + onItemSelected: (Int) -> Unit, +) { + Box { + Column { + Spacer(modifier = Modifier.height(7.dp)) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(6.dp) + .padding(horizontal = 6.dp) + .background(OrbitTheme.colors.gray_600), + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 6.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + items.forEachIndexed { index, item -> + Column(horizontalAlignment = getAlignment(index, items.size)) { + OrbitRadioButton( + isSelected = (index == selectedIndex && isEnabled), + isEnabled = isEnabled, + onClick = { if (isEnabled) onItemSelected(index) }, + ) + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = item, + style = OrbitTheme.typography.body1Medium, + color = OrbitTheme.colors.gray_50, + ) + } + } + } + } +} + +private fun getAlignment(index: Int, size: Int): Alignment.Horizontal = + when (index) { + 0 -> Alignment.Start + size - 1 -> Alignment.End + else -> Alignment.CenterHorizontally + } + +@Composable +private fun AlarmSnoozeMessage(interval: String, count: String) { + val formattedCount = if (count == stringResource(id = R.string.alarm_add_edit_repeat_count_infinite)) "${count}번" else count + + Box( + modifier = Modifier + .background( + color = OrbitTheme.colors.gray_700, + shape = RoundedCornerShape(8.dp), + ) + .padding(horizontal = 12.dp, vertical = 6.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(id = R.string.alarm_add_edit_alarm_snooze_description, interval, formattedCount), + style = OrbitTheme.typography.label1Medium, + color = OrbitTheme.colors.main, + ) + } +} + +@Preview +@Composable +private fun AlarmSnoozeBottomSheetPreview() { + var isSnoozeEnabled by remember { mutableStateOf(true) } + var snoozeIntervalIndex by remember { mutableIntStateOf(2) } + var snoozeCountIndex by remember { mutableIntStateOf(1) } + + OrbitTheme { + AlarmSnoozeBottomSheet( + isSnoozeEnabled = isSnoozeEnabled, + snoozeIntervalIndex = snoozeIntervalIndex, + snoozeCountIndex = snoozeCountIndex, + snoozeIntervals = listOf(1, 3, 5, 10, 15).map { + stringResource(id = R.string.alarm_add_edit_interval_minute, it) + }, + snoozeCounts = listOf( + stringResource(id = R.string.alarm_add_edit_repeat_count_times, 1), + stringResource(id = R.string.alarm_add_edit_repeat_count_times, 3), + stringResource(id = R.string.alarm_add_edit_repeat_count_times, 5), + stringResource(id = R.string.alarm_add_edit_repeat_count_times, 10), + stringResource(id = R.string.alarm_add_edit_repeat_count_infinite), + ), + onSnoozeToggle = { isSnoozeEnabled = !isSnoozeEnabled }, + onIntervalSelected = { index -> snoozeIntervalIndex = index }, + onCountSelected = { index -> snoozeCountIndex = index }, + onComplete = { /* 완료 버튼 클릭 처리 */ }, + ) + } +} diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml index 187c9d1a..ca32a1f4 100644 --- a/feature/home/src/main/res/values/strings.xml +++ b/feature/home/src/main/res/values/strings.xml @@ -14,13 +14,14 @@ 공휴일 알람 끄기 알람 미루기 + %s 간격으로 %s 울립니다. + 간격 %d분 횟수 %d회 무한 - %d분 간격으로 무한히 울립니다. 안 함 소리 From 82da905a295fbb2983d12d095a4216b6c83956c5 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 14:13:12 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[UI/#45]=20RadioButton=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/ui/component/radiobutton/RadioButton.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/yapp/ui/component/radiobutton/RadioButton.kt b/core/ui/src/main/java/com/yapp/ui/component/radiobutton/RadioButton.kt index 4c02e738..ed172464 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/radiobutton/RadioButton.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/radiobutton/RadioButton.kt @@ -21,6 +21,7 @@ import com.yapp.designsystem.theme.OrbitTheme @Composable fun OrbitRadioButton( isSelected: Boolean, + isEnabled: Boolean = true, onClick: (Boolean) -> Unit, ) { val backgroundColor = if (isSelected) { @@ -50,7 +51,9 @@ fun OrbitRadioButton( ) .clip(CircleShape) .clickable { - onClick(!isSelected) + if (isEnabled) { + onClick(!isSelected) + } }, contentAlignment = Alignment.Center, ) { From c14383dcd99278716c07216a43cce5cde2173bae Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 14:14:10 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[REFACTOR/#45]=20AlarmAddEditContract.Sta?= =?UTF-8?q?te=EB=A5=BC=20=EC=97=AC=EB=9F=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/AlarmAddEditContract.kt | 27 ++- .../java/com/yapp/alarm/AlarmAddEditScreen.kt | 103 ++++++----- .../com/yapp/alarm/AlarmAddEditViewModel.kt | 172 ++++++++++++------ 3 files changed, 195 insertions(+), 107 deletions(-) diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt index e36d99af..85d20f0d 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt @@ -5,24 +5,49 @@ import com.yapp.ui.base.UiState sealed class AlarmAddEditContract { data class State( + val timeState: AlarmTimeState = AlarmTimeState(), // 알람 시간 관련 상태 + val daySelectionState: AlarmDaySelectionState = AlarmDaySelectionState(), // 요일 선택 상태 + val holidayState: AlarmHolidayState = AlarmHolidayState(), // 휴일 관련 상태 + val snoozeState: AlarmSnoozeState = AlarmSnoozeState(), // 스누즈 관련 상태 + ) : UiState + + data class AlarmTimeState( val currentAmPm: String = "오전", val currentHour: Int = 6, val currentMinute: Int = 0, val alarmMessage: String = "", + ) + + data class AlarmDaySelectionState( val days: Set = enumValues().toSet(), val isWeekdaysChecked: Boolean = false, val isWeekendsChecked: Boolean = false, val selectedDays: Set = setOf(), + ) + + data class AlarmHolidayState( val isDisableHolidayEnabled: Boolean = false, val isDisableHolidayChecked: Boolean = false, - ) : UiState + ) + + data class AlarmSnoozeState( + val isSnoozeEnabled: Boolean = true, // 스누즈 활성화 여부 + val snoozeIntervalIndex: Int = 2, // 선택된 간격 인덱스 + val snoozeCountIndex: Int = 1, // 선택된 횟수 인덱스 + ) sealed class Action { + data object ClickBack : Action() + data object ClickSave : Action() data class UpdateAlarmTime(val amPm: String, val hour: Int, val minute: Int) : Action() data object ToggleWeekdaysChecked : Action() data object ToggleWeekendsChecked : Action() data class ToggleDaySelection(val day: AlarmDay) : Action() data object ToggleDisableHolidayChecked : Action() + data object OpenSnoozeSettingBottomSheet : Action() + data object ToggleSnoozeEnabled : Action() // 스누즈 활성화 상태 토글 + data class UpdateSnoozeInterval(val index: Int) : Action() // 간격 변경 + data class UpdateSnoozeCount(val index: Int) : Action() // 횟수 변경 } sealed class SideEffect : com.yapp.ui.base.SideEffect { diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt index 0fc3032e..e36df104 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -29,6 +29,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.yapp.alarm.component.AlarmCheckItem import com.yapp.alarm.component.AlarmDayButton +import com.yapp.common.navigation.OrbitNavigator import com.yapp.designsystem.theme.OrbitTheme import com.yapp.ui.component.button.OrbitButton import com.yapp.ui.component.switch.OrbitSwitch @@ -39,6 +40,7 @@ import feature.home.R @Composable fun AlarmAddEditRoute( viewModel: AlarmAddEditViewModel = hiltViewModel(), + navigator: OrbitNavigator, ) { val state by viewModel.container.stateFlow.collectAsStateWithLifecycle() val sideEffect = viewModel.container.sideEffectFlow @@ -47,9 +49,14 @@ fun AlarmAddEditRoute( sideEffect.collect { effect -> when (effect) { is AlarmAddEditContract.SideEffect.NavigateBack -> { + navigator.navigateBack() } - is AlarmAddEditContract.SideEffect.Navigate -> { + navigator.navigateTo( + route = effect.route, + popUpTo = effect.popUpTo, + inclusive = effect.inclusive, + ) } } } @@ -73,8 +80,8 @@ fun AlarmAddEditScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { AlarmAddEditTopBar( - title = state.alarmMessage, - onBack = { }, + title = state.timeState.alarmMessage, + onBack = { eventDispatcher(AlarmAddEditContract.Action.ClickBack) }, ) Box( modifier = Modifier.weight(1f), @@ -86,18 +93,13 @@ fun AlarmAddEditScreen( } AlarmAddEditSettingsSection( modifier = Modifier.padding(horizontal = 20.dp), - isWeekdaysChecked = state.isWeekdaysChecked, - isWeekendsChecked = state.isWeekendsChecked, - selectedDays = state.selectedDays, - isDisableHolidayChecked = state.isDisableHolidayChecked, - isDisableHolidayEnabled = state.isDisableHolidayEnabled, - days = state.days, + state = state, processAction = eventDispatcher, ) Spacer(modifier = Modifier.height(24.dp)) OrbitButton( label = stringResource(R.string.alarm_add_edit_save), - onClick = { }, + onClick = { eventDispatcher(AlarmAddEditContract.Action.ClickSave) }, enabled = true, modifier = Modifier .padding( @@ -144,12 +146,7 @@ private fun AlarmAddEditTopBar( @Composable private fun AlarmAddEditSettingsSection( modifier: Modifier = Modifier, - isWeekdaysChecked: Boolean, - isWeekendsChecked: Boolean, - selectedDays: Set, - isDisableHolidayChecked: Boolean, - isDisableHolidayEnabled: Boolean, - days: Set, + state: AlarmAddEditContract.State, processAction: (AlarmAddEditContract.Action) -> Unit, ) { Column( @@ -164,15 +161,11 @@ private fun AlarmAddEditSettingsSection( ), ) { AlarmAddEditSelectDaysSection( - isWeekdaysChecked = isWeekdaysChecked, - isWeekendsChecked = isWeekendsChecked, - selectedDays = selectedDays, - days = days, + state = state.daySelectionState, processAction = processAction, ) AlarmAddEditDisableHolidaySwitch( - isDisableHolidayChecked = isDisableHolidayChecked, - isDisableHolidayEnabled = isDisableHolidayEnabled, + state = state.holidayState, processAction = processAction, ) Spacer( @@ -184,7 +177,7 @@ private fun AlarmAddEditSettingsSection( AlarmAddEditSettingItem( label = "알람 미루기", description = "5분, 무한", - onClick = { }, + onClick = { processAction(AlarmAddEditContract.Action.OpenSnoozeSettingBottomSheet) }, ) Spacer( modifier = Modifier.fillMaxWidth() @@ -239,10 +232,7 @@ private fun AlarmAddEditSettingItem( @Composable private fun AlarmAddEditSelectDaysSection( - isWeekdaysChecked: Boolean, - isWeekendsChecked: Boolean, - selectedDays: Set, - days: Set, + state: AlarmAddEditContract.AlarmDaySelectionState, processAction: (AlarmAddEditContract.Action) -> Unit, ) { Column( @@ -262,7 +252,7 @@ private fun AlarmAddEditSelectDaysSection( AlarmCheckItem( label = stringResource(id = R.string.alarm_add_edit_weekdays), - isPressed = isWeekdaysChecked, + isPressed = state.isWeekdaysChecked, onClick = { processAction(AlarmAddEditContract.Action.ToggleWeekdaysChecked) }, @@ -270,7 +260,7 @@ private fun AlarmAddEditSelectDaysSection( Spacer(modifier = Modifier.width(2.dp)) AlarmCheckItem( label = stringResource(id = R.string.alarm_add_edit_weekends), - isPressed = isWeekendsChecked, + isPressed = state.isWeekendsChecked, onClick = { processAction(AlarmAddEditContract.Action.ToggleWeekendsChecked) }, @@ -283,10 +273,10 @@ private fun AlarmAddEditSelectDaysSection( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - days.forEach { day -> + state.days.forEach { day -> AlarmDayButton( label = stringResource(id = day.label), - isPressed = selectedDays.contains(day), + isPressed = state.selectedDays.contains(day), onClick = { processAction(AlarmAddEditContract.Action.ToggleDaySelection(day)) }, @@ -298,8 +288,7 @@ private fun AlarmAddEditSelectDaysSection( @Composable private fun AlarmAddEditDisableHolidaySwitch( - isDisableHolidayChecked: Boolean, - isDisableHolidayEnabled: Boolean, + state: AlarmAddEditContract.AlarmHolidayState, processAction: (AlarmAddEditContract.Action) -> Unit, ) { Row( @@ -327,8 +316,8 @@ private fun AlarmAddEditDisableHolidaySwitch( Spacer(modifier = Modifier.weight(1f)) OrbitSwitch( - isChecked = isDisableHolidayChecked, - isEnabled = isDisableHolidayEnabled, + isChecked = state.isDisableHolidayChecked, + isEnabled = state.isDisableHolidayEnabled, onClick = { processAction(AlarmAddEditContract.Action.ToggleDisableHolidayChecked) }, @@ -340,12 +329,22 @@ private fun AlarmAddEditDisableHolidaySwitch( @Composable fun AlarmAddEditSettingsSectionPreview() { AlarmAddEditSettingsSection( - isWeekdaysChecked = true, - isWeekendsChecked = false, - selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE), - isDisableHolidayChecked = false, - isDisableHolidayEnabled = true, - days = AlarmDay.entries.toSet(), + state = AlarmAddEditContract.State( + timeState = AlarmAddEditContract.AlarmTimeState( + currentAmPm = "AM", + currentHour = 9, + currentMinute = 30, + ), + daySelectionState = AlarmAddEditContract.AlarmDaySelectionState( + isWeekdaysChecked = true, + isWeekendsChecked = false, + selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE), + days = AlarmDay.entries.toSet(), + ), + holidayState = AlarmAddEditContract.AlarmHolidayState( + isDisableHolidayChecked = false, + ), + ), processAction = { }, ) } @@ -366,14 +365,20 @@ fun AlarmAddEditScreenPreview() { AlarmAddEditScreen( stateProvider = { AlarmAddEditContract.State( - currentAmPm = "AM", - currentHour = 9, - currentMinute = 30, - isWeekdaysChecked = true, - isWeekendsChecked = false, - selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE), - isDisableHolidayChecked = false, - days = AlarmDay.entries.toSet(), + timeState = AlarmAddEditContract.AlarmTimeState( + currentAmPm = "AM", + currentHour = 9, + currentMinute = 30, + ), + daySelectionState = AlarmAddEditContract.AlarmDaySelectionState( + isWeekdaysChecked = true, + isWeekendsChecked = false, + selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE), + days = AlarmDay.entries.toSet(), + ), + holidayState = AlarmAddEditContract.AlarmHolidayState( + isDisableHolidayChecked = false, + ), ) }, eventDispatcher = { }, diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt index 084bc519..7246978a 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -13,114 +13,172 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel navigateBack() + is AlarmAddEditContract.Action.ClickSave -> saveAlarm() is AlarmAddEditContract.Action.UpdateAlarmTime -> updateAlarmTime(action.amPm, action.hour, action.minute) is AlarmAddEditContract.Action.ToggleWeekdaysChecked -> toggleWeekdaysChecked() is AlarmAddEditContract.Action.ToggleWeekendsChecked -> toggleWeekendsChecked() is AlarmAddEditContract.Action.ToggleDaySelection -> toggleDaySelection(action.day) is AlarmAddEditContract.Action.ToggleDisableHolidayChecked -> toggleDisableHolidayChecked() + is AlarmAddEditContract.Action.ToggleSnoozeEnabled -> toggleSnoozeEnabled() + is AlarmAddEditContract.Action.UpdateSnoozeInterval -> updateSnoozeInterval(action.index) + is AlarmAddEditContract.Action.UpdateSnoozeCount -> updateSnoozeCount(action.index) + is AlarmAddEditContract.Action.OpenSnoozeSettingBottomSheet -> openSnoozeSettingBottomSheet() } } } - private fun updateAlarmTime(amPm: String, hour: Int, minute: Int) { - updateState { - copy( - currentAmPm = amPm, - currentHour = hour, - currentMinute = minute, - alarmMessage = getAlarmMessage(), - ) - } + private fun navigateBack() { + emitSideEffect(AlarmAddEditContract.SideEffect.NavigateBack) } - private fun getAlarmMessage(): String { - val now = java.time.LocalDateTime.now() - - // 설정된 알람 시간 계산 - val alarmHour = convertTo24HourFormat(currentState.currentAmPm, currentState.currentHour) - val alarmTimeToday = now.toLocalDate().atTime(alarmHour, currentState.currentMinute) - - // 선택된 요일 중 다음 알람 시간이 언제인지 계산 - val nextAlarmDateTime = calculateNextAlarmDateTime(now, alarmTimeToday, currentState.selectedDays) - - // 남은 시간 계산 - val duration = java.time.Duration.between(now, nextAlarmDateTime) - val totalMinutes = duration.toMinutes() - val days = totalMinutes / (24 * 60) - val hours = (totalMinutes % (24 * 60)) / 60 - val minutes = totalMinutes % 60 + private fun saveAlarm() { + emitSideEffect(AlarmAddEditContract.SideEffect.NavigateBack) + } - // 출력 문구 생성 - return when { - days > 0 -> "${days}일 ${hours}시간 후에 울려요" - hours > 0 -> "${hours}시간 ${minutes}분 후에 울려요" - else -> "${minutes}분 후에 울려요" + private fun updateAlarmTime(amPm: String, hour: Int, minute: Int) { + val newTimeState = currentState.timeState.copy( + currentAmPm = amPm, + currentHour = hour, + currentMinute = minute, + alarmMessage = getAlarmMessage(amPm, hour, minute, currentState.daySelectionState.selectedDays), + ) + updateState { + copy(timeState = newTimeState) } } private fun toggleWeekdaysChecked() { val weekdays = setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI) - val isChecked = !currentState.isWeekdaysChecked + val isChecked = !currentState.daySelectionState.isWeekdaysChecked val updatedDays = if (isChecked) { - currentState.selectedDays + weekdays + currentState.daySelectionState.selectedDays + weekdays } else { - currentState.selectedDays - weekdays + currentState.daySelectionState.selectedDays - weekdays } - + val newDayState = currentState.daySelectionState.copy( + isWeekdaysChecked = isChecked, + selectedDays = updatedDays, + ) updateState { copy( - isWeekdaysChecked = isChecked, - selectedDays = updatedDays, - alarmMessage = getAlarmMessage(), - isDisableHolidayEnabled = updatedDays.isNotEmpty(), + timeState = timeState.copy( + alarmMessage = getAlarmMessage(timeState.currentAmPm, timeState.currentHour, timeState.currentMinute, newDayState.selectedDays), + ), + daySelectionState = newDayState, + holidayState = holidayState.copy( + isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(), + isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else holidayState.isDisableHolidayChecked, + ), ) } } private fun toggleWeekendsChecked() { val weekends = setOf(AlarmDay.SAT, AlarmDay.SUN) - val isChecked = !currentState.isWeekendsChecked + val isChecked = !currentState.daySelectionState.isWeekendsChecked val updatedDays = if (isChecked) { - currentState.selectedDays + weekends + currentState.daySelectionState.selectedDays + weekends } else { - currentState.selectedDays - weekends + currentState.daySelectionState.selectedDays - weekends } - + val newDayState = currentState.daySelectionState.copy( + isWeekendsChecked = isChecked, + selectedDays = updatedDays, + ) updateState { copy( - isWeekendsChecked = isChecked, - selectedDays = updatedDays, - alarmMessage = getAlarmMessage(), - isDisableHolidayEnabled = updatedDays.isNotEmpty(), + timeState = timeState.copy( + alarmMessage = getAlarmMessage(timeState.currentAmPm, timeState.currentHour, timeState.currentMinute, newDayState.selectedDays), + ), + daySelectionState = newDayState, + holidayState = holidayState.copy( + isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(), + isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else holidayState.isDisableHolidayChecked, + ), ) } } private fun toggleDaySelection(day: AlarmDay) { - val updatedDays = if (day in currentState.selectedDays) { - currentState.selectedDays - day + val updatedDays = if (day in currentState.daySelectionState.selectedDays) { + currentState.daySelectionState.selectedDays - day } else { - currentState.selectedDays + day + currentState.daySelectionState.selectedDays + day } val weekdays = setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI) val weekends = setOf(AlarmDay.SAT, AlarmDay.SUN) + val newDayState = currentState.daySelectionState.copy( + selectedDays = updatedDays, + isWeekdaysChecked = updatedDays.containsAll(weekdays), + isWeekendsChecked = updatedDays.containsAll(weekends), + ) updateState { copy( - selectedDays = updatedDays, - isWeekdaysChecked = updatedDays.containsAll(weekdays), - isWeekendsChecked = updatedDays.containsAll(weekends), - alarmMessage = getAlarmMessage(), - isDisableHolidayEnabled = updatedDays.isNotEmpty(), + timeState = timeState.copy( + alarmMessage = getAlarmMessage(timeState.currentAmPm, timeState.currentHour, timeState.currentMinute, newDayState.selectedDays), + ), + daySelectionState = newDayState, + holidayState = holidayState.copy( + isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(), + isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else holidayState.isDisableHolidayChecked, + ), ) } } private fun toggleDisableHolidayChecked() { + val newHolidayState = currentState.holidayState.copy( + isDisableHolidayChecked = !currentState.holidayState.isDisableHolidayChecked, + ) updateState { - copy( - isDisableHolidayChecked = !currentState.isDisableHolidayChecked, - ) + copy(holidayState = newHolidayState) + } + } + + private fun toggleSnoozeEnabled() { + val newSnoozeState = currentState.snoozeState.copy( + isSnoozeEnabled = !currentState.snoozeState.isSnoozeEnabled, + ) + updateState { + copy(snoozeState = newSnoozeState) + } + } + + private fun updateSnoozeInterval(index: Int) { + val newSnoozeState = currentState.snoozeState.copy(snoozeIntervalIndex = index) + updateState { + copy(snoozeState = newSnoozeState) + } + } + + private fun updateSnoozeCount(index: Int) { + val newSnoozeState = currentState.snoozeState.copy(snoozeCountIndex = index) + updateState { + copy(snoozeState = newSnoozeState) + } + } + + private fun openSnoozeSettingBottomSheet() { + // TODO: open snooze setting bottom sheet + } + + private fun getAlarmMessage(amPm: String, hour: Int, minute: Int, selectedDays: Set): String { + val now = java.time.LocalDateTime.now() + val alarmHour = convertTo24HourFormat(amPm, hour) + val alarmTimeToday = now.toLocalDate().atTime(alarmHour, minute) + val nextAlarmDateTime = calculateNextAlarmDateTime(now, alarmTimeToday, selectedDays) + val duration = java.time.Duration.between(now, nextAlarmDateTime) + val totalMinutes = duration.toMinutes() + val days = totalMinutes / (24 * 60) + val hours = (totalMinutes % (24 * 60)) / 60 + val minutes = totalMinutes % 60 + + return when { + days > 0 -> "${days}일 ${hours}시간 후에 울려요" + hours > 0 -> "${hours}시간 ${minutes}분 후에 울려요" + else -> "${minutes}분 후에 울려요" } } From ff07fb2a9639860a7d4fe0598a922b6bbe8a5954 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 15:31:15 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[FEAT/#45]=20AlarmAddEditScreen=EC=97=90?= =?UTF-8?q?=20AlarmSnoozeBottomSheet=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yapp/common/navigation/OrbitNavigator.kt | 4 +- .../com/yapp/ui/component/OrbitBottomSheet.kt | 52 +++++++++++++++- .../com/yapp/alarm/AlarmAddEditContract.kt | 27 ++++---- .../java/com/yapp/alarm/AlarmAddEditScreen.kt | 50 ++++++++++++++- .../com/yapp/alarm/AlarmAddEditViewModel.kt | 13 ++-- .../bottomsheet/AlarmSnoozeBottomSheet.kt | 61 ++++++++++++++++++- .../main/java/com/yapp/home/HomeNavGraph.kt | 6 +- feature/home/src/main/res/values/strings.xml | 3 + 8 files changed, 186 insertions(+), 30 deletions(-) diff --git a/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt b/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt index 25ed69d0..37ee427e 100644 --- a/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt +++ b/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt @@ -7,13 +7,13 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions -import com.yapp.common.navigation.destination.OnboardingDestination +import com.yapp.common.navigation.destination.HomeDestination import com.yapp.common.navigation.destination.TopLevelDestination class OrbitNavigator( val navController: NavHostController, ) { - val startDestination = OnboardingDestination.Route.route + val startDestination = HomeDestination.Route.route private val currentDestination: NavDestination? @Composable get() = navController diff --git a/core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt b/core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt index 73d605ab..f4b53abb 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt @@ -1,10 +1,15 @@ package com.yapp.ui.component +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState +import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -12,6 +17,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable 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.graphics.Shape import androidx.compose.ui.tooling.preview.Preview @@ -54,12 +61,51 @@ fun OrbitBottomSheet( @Preview @Composable fun OrbitBottomSheetPreview() { - var isSheetOpen by rememberSaveable { mutableStateOf(false) } + var isSheetOpen by rememberSaveable { mutableStateOf(true) } + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val scope = rememberCoroutineScope() + OrbitTheme { + Button( + onClick = { + scope.launch { + sheetState.show() + }.invokeOnCompletion { + if (!isSheetOpen) { + isSheetOpen = true + } + } + }, + ) { + Text("Toggle Bottom Sheet") + } + OrbitBottomSheet( - isSheetOpen = true, + isSheetOpen = isSheetOpen, + sheetState = sheetState, onDismissRequest = { isSheetOpen = !isSheetOpen }, - content = {}, + content = { + Box( + modifier = Modifier + .fillMaxWidth() + .height(600.dp), + contentAlignment = Alignment.Center, + ) { + Button( + onClick = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { + if (isSheetOpen) { + isSheetOpen = false + } + } + }, + ) { + Text("Toggle Bottom Sheet") + } + } + }, ) } } diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt index 85d20f0d..a9c3ec55 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt @@ -5,15 +5,15 @@ import com.yapp.ui.base.UiState sealed class AlarmAddEditContract { data class State( - val timeState: AlarmTimeState = AlarmTimeState(), // 알람 시간 관련 상태 - val daySelectionState: AlarmDaySelectionState = AlarmDaySelectionState(), // 요일 선택 상태 - val holidayState: AlarmHolidayState = AlarmHolidayState(), // 휴일 관련 상태 - val snoozeState: AlarmSnoozeState = AlarmSnoozeState(), // 스누즈 관련 상태 + val timeState: AlarmTimeState = AlarmTimeState(), + val daySelectionState: AlarmDaySelectionState = AlarmDaySelectionState(), + val holidayState: AlarmHolidayState = AlarmHolidayState(), + val snoozeState: AlarmSnoozeState = AlarmSnoozeState(), ) : UiState data class AlarmTimeState( val currentAmPm: String = "오전", - val currentHour: Int = 6, + val currentHour: Int = 1, val currentMinute: Int = 0, val alarmMessage: String = "", ) @@ -31,9 +31,12 @@ sealed class AlarmAddEditContract { ) data class AlarmSnoozeState( - val isSnoozeEnabled: Boolean = true, // 스누즈 활성화 여부 - val snoozeIntervalIndex: Int = 2, // 선택된 간격 인덱스 - val snoozeCountIndex: Int = 1, // 선택된 횟수 인덱스 + val isSnoozeEnabled: Boolean = true, + val snoozeIntervalIndex: Int = 2, + val snoozeCountIndex: Int = 1, + val isBottomSheetOpen: Boolean = false, + val snoozeIntervals: List = listOf("1분", "3분", "5분", "10분", "15분"), + val snoozeCounts: List = listOf("1회", "3회", "5회", "10회", "무한"), ) sealed class Action { @@ -44,10 +47,10 @@ sealed class AlarmAddEditContract { data object ToggleWeekendsChecked : Action() data class ToggleDaySelection(val day: AlarmDay) : Action() data object ToggleDisableHolidayChecked : Action() - data object OpenSnoozeSettingBottomSheet : Action() - data object ToggleSnoozeEnabled : Action() // 스누즈 활성화 상태 토글 - data class UpdateSnoozeInterval(val index: Int) : Action() // 간격 변경 - data class UpdateSnoozeCount(val index: Int) : Action() // 횟수 변경 + data object ToggleSnoozeSettingBottomSheetOpen : Action() + data object ToggleSnoozeEnabled : Action() + data class UpdateSnoozeInterval(val index: Int) : Action() + data class UpdateSnoozeCount(val index: Int) : Action() } sealed class SideEffect : com.yapp.ui.base.SideEffect { diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt index e36df104..0c3066a3 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -14,10 +14,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -29,6 +32,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.yapp.alarm.component.AlarmCheckItem import com.yapp.alarm.component.AlarmDayButton +import com.yapp.alarm.component.bottomsheet.AlarmSnoozeBottomSheet import com.yapp.common.navigation.OrbitNavigator import com.yapp.designsystem.theme.OrbitTheme import com.yapp.ui.component.button.OrbitButton @@ -36,6 +40,7 @@ import com.yapp.ui.component.switch.OrbitSwitch import com.yapp.ui.component.timepicker.OrbitPicker import com.yapp.ui.lifecycle.LaunchedEffectWithLifecycle import feature.home.R +import kotlinx.coroutines.launch @Composable fun AlarmAddEditRoute( @@ -68,12 +73,16 @@ fun AlarmAddEditRoute( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AlarmAddEditScreen( stateProvider: () -> AlarmAddEditContract.State, eventDispatcher: (AlarmAddEditContract.Action) -> Unit, ) { val state = stateProvider() + val snoozeState = state.snoozeState + val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val scope = rememberCoroutineScope() Column( modifier = Modifier.fillMaxSize(), @@ -111,6 +120,32 @@ fun AlarmAddEditScreen( .fillMaxWidth(), ) } + + AlarmSnoozeBottomSheet( + isSnoozeEnabled = snoozeState.isSnoozeEnabled, + snoozeIntervalIndex = snoozeState.snoozeIntervalIndex, + snoozeCountIndex = snoozeState.snoozeCountIndex, + snoozeIntervals = snoozeState.snoozeIntervals, + snoozeCounts = snoozeState.snoozeCounts, + onSnoozeToggle = { eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeEnabled) }, + onIntervalSelected = { index -> eventDispatcher(AlarmAddEditContract.Action.UpdateSnoozeInterval(index)) }, + onCountSelected = { index -> eventDispatcher(AlarmAddEditContract.Action.UpdateSnoozeCount(index)) }, + onComplete = { + scope.launch { + bottomSheetState.hide() + }.invokeOnCompletion { + eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) + } + }, + isSheetOpen = snoozeState.isBottomSheetOpen, + onDismiss = { + scope.launch { + bottomSheetState.hide() + }.invokeOnCompletion { + eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) + } + }, + ) } @Composable @@ -174,10 +209,19 @@ private fun AlarmAddEditSettingsSection( .padding(horizontal = 20.dp) .background(OrbitTheme.colors.gray_700), ) + AlarmAddEditSettingItem( - label = "알람 미루기", - description = "5분, 무한", - onClick = { processAction(AlarmAddEditContract.Action.OpenSnoozeSettingBottomSheet) }, + label = stringResource(id = R.string.alarm_add_edit_alarm_snooze), + description = if (state.snoozeState.isSnoozeEnabled) { + stringResource( + id = R.string.alarm_add_edit_alarm_selected_option, + state.snoozeState.snoozeIntervals[state.snoozeState.snoozeIntervalIndex], + state.snoozeState.snoozeCounts[state.snoozeState.snoozeCountIndex], + ) + } else { + stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none) + }, + onClick = { processAction(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) }, ) Spacer( modifier = Modifier.fillMaxWidth() diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt index 7246978a..ed68b56e 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -23,7 +23,7 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel toggleSnoozeEnabled() is AlarmAddEditContract.Action.UpdateSnoozeInterval -> updateSnoozeInterval(action.index) is AlarmAddEditContract.Action.UpdateSnoozeCount -> updateSnoozeCount(action.index) - is AlarmAddEditContract.Action.OpenSnoozeSettingBottomSheet -> openSnoozeSettingBottomSheet() + is AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen -> toggleSnoozeSettingBottomSheet() } } } @@ -146,6 +146,13 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel): String { val now = java.time.LocalDateTime.now() val alarmHour = convertTo24HourFormat(amPm, hour) diff --git a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt index fa3fdb88..ae2fb36a 100644 --- a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt +++ b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt @@ -10,12 +10,15 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable 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.ui.Alignment import androidx.compose.ui.Modifier @@ -23,11 +26,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.component.OrbitBottomSheet import com.yapp.ui.component.button.OrbitButton import com.yapp.ui.component.radiobutton.OrbitRadioButton import com.yapp.ui.component.switch.OrbitSwitch import feature.home.R +import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun AlarmSnoozeBottomSheet( isSnoozeEnabled: Boolean, @@ -39,6 +45,50 @@ internal fun AlarmSnoozeBottomSheet( onSnoozeToggle: () -> Unit, onCountSelected: (Int) -> Unit, onComplete: () -> Unit, + isSheetOpen: Boolean, + onDismiss: () -> Unit, +) { + val scope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + OrbitBottomSheet( + isSheetOpen = isSheetOpen, + sheetState = sheetState, + onDismissRequest = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { onDismiss() } + }, + ) { + BottomSheetContent( + isSnoozeEnabled = isSnoozeEnabled, + snoozeIntervalIndex = snoozeIntervalIndex, + snoozeIntervals = snoozeIntervals, + onIntervalSelected = onIntervalSelected, + snoozeCountIndex = snoozeCountIndex, + snoozeCounts = snoozeCounts, + onSnoozeToggle = onSnoozeToggle, + onCountSelected = onCountSelected, + onComplete = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { onComplete() } + }, + ) + } +} + +@Composable +private fun BottomSheetContent( + isSnoozeEnabled: Boolean, + snoozeIntervalIndex: Int, + snoozeIntervals: List, + onIntervalSelected: (Int) -> Unit, + snoozeCountIndex: Int, + snoozeCounts: List, + onSnoozeToggle: () -> Unit, + onCountSelected: (Int) -> Unit, + onComplete: () -> Unit, ) { Column( modifier = Modifier @@ -72,10 +122,12 @@ internal fun AlarmSnoozeBottomSheet( } Spacer(modifier = Modifier.height(32.dp)) OrbitButton( - label = stringResource(id = R.string.alarm_add_edit_alarm_snooze), - enabled = isSnoozeEnabled, + label = stringResource(id = R.string.alarm_add_edit_complete), + enabled = true, containerColor = OrbitTheme.colors.gray_600, contentColor = OrbitTheme.colors.white, + pressedContainerColor = OrbitTheme.colors.gray_500, + pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f), onClick = onComplete, ) Spacer(modifier = Modifier.height(12.dp)) @@ -200,6 +252,7 @@ private fun AlarmSnoozeBottomSheetPreview() { var isSnoozeEnabled by remember { mutableStateOf(true) } var snoozeIntervalIndex by remember { mutableIntStateOf(2) } var snoozeCountIndex by remember { mutableIntStateOf(1) } + var isSheetOpen by remember { mutableStateOf(true) } OrbitTheme { AlarmSnoozeBottomSheet( @@ -219,7 +272,9 @@ private fun AlarmSnoozeBottomSheetPreview() { onSnoozeToggle = { isSnoozeEnabled = !isSnoozeEnabled }, onIntervalSelected = { index -> snoozeIntervalIndex = index }, onCountSelected = { index -> snoozeCountIndex = index }, - onComplete = { /* 완료 버튼 클릭 처리 */ }, + onComplete = { isSheetOpen = false }, + isSheetOpen = isSheetOpen, + onDismiss = { isSheetOpen = false }, ) } } diff --git a/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt b/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt index e4e5bdaf..a6ebbe98 100644 --- a/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt +++ b/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt @@ -12,14 +12,16 @@ fun NavGraphBuilder.homeNavGraph( ) { navigation( route = HomeDestination.Route.route, - startDestination = HomeDestination.Home.route, + startDestination = HomeDestination.AlarmAddEdit.route, ) { composable(route = HomeDestination.Home.route) { HomeRoute() } composable(route = HomeDestination.AlarmAddEdit.route) { - AlarmAddEditRoute() + AlarmAddEditRoute( + navigator = navigator, + ) } } } diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml index ca32a1f4..b39a20bf 100644 --- a/feature/home/src/main/res/values/strings.xml +++ b/feature/home/src/main/res/values/strings.xml @@ -13,6 +13,9 @@ 공휴일 알람 끄기 + %s, %s + 안 함 + 알람 미루기 %s 간격으로 %s 울립니다. From c824f112d6e2e8f2167a5a8043194cbaffa19066 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 20:46:51 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[REFACTOR/#45]=20convertTo24HourFormat=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/alarm/AlarmAddEditViewModel.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt index ed68b56e..e209c5a3 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -185,14 +185,10 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel hour + 12 + amPm == "오전" && hour == 12 -> 0 + else -> hour } private fun calculateNextAlarmDateTime(