From d33a5c2ba2552d5b25f9e56bfdcd78bf37a353e7 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 19 Jan 2025 23:32:28 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[UI/#48]=20OrbitSlider=20=EC=BB=B4?= =?UTF-8?q?=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 --- .../com/yapp/ui/component/slider/Slider.kt | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt diff --git a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt new file mode 100644 index 0000000..7e681a3 --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt @@ -0,0 +1,150 @@ +package com.yapp.ui.component.slider + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +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.mutableFloatStateOf +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.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme + +@Composable +fun OrbitSlider( + value: Int, + onValueChange: (Int) -> Unit, + modifier: Modifier = Modifier, + trackHeight: Dp = 6.dp, + thumbSize: Dp = 22.dp, + thumbColor: Color = Color.White, + inactiveBarColor: Color = OrbitTheme.colors.gray_600, + activeBarColor: Color = OrbitTheme.colors.main, +) { + var thumbX by remember { mutableFloatStateOf(value.toFloat()) } + var isDragging by remember { mutableStateOf(false) } + + Box( + modifier = Modifier.height(IntrinsicSize.Min), + ) { + Canvas( + modifier = modifier + .fillMaxWidth() + .height(trackHeight) + .align(Alignment.Center) + .clip(RoundedCornerShape(100.dp)), + ) { + val totalWidth = size.width + val activeWidth = thumbX + + drawRect( + color = activeBarColor, + size = size.copy(width = activeWidth), + ) + + drawRect( + color = inactiveBarColor, + topLeft = Offset(activeWidth, 0f), + size = size.copy(width = totalWidth - activeWidth), + ) + } + + Canvas( + modifier = modifier + .fillMaxWidth() + .height(thumbSize) + .align(Alignment.Center) + .pointerInput(true) { + detectTapGestures( + onPress = { offset -> + isDragging = isInCircle( + offset.x, + offset.y, + thumbX, + size.height.toFloat() / 2, + thumbSize.toPx() / 2, + ) + }, + ) + } + .pointerInput(true) { + detectHorizontalDragGestures( + onDragEnd = { + isDragging = false + }, + ) { _, dragAmount -> + if (isDragging) { + thumbX += dragAmount + thumbX = thumbX.coerceIn(0f, size.width.toFloat()) + val newValue = ((thumbX / size.width) * 100).toInt().coerceIn(0, 100) + onValueChange(newValue) + } + } + }, + ) { + val height = size.height + + drawCircle( + color = thumbColor, + radius = thumbSize.toPx() / 2, + center = Offset(thumbX, height / 2), + ) + } + } +} + +private fun isInCircle(x: Float, y: Float, centerX: Float, centerY: Float, radius: Float): Boolean { + val dx = x - centerX + val dy = y - centerY + return (dx * dx + dy * dy) <= (radius * radius) +} + +@Preview +@Composable +fun PreviewOrbitSlider() { + var currentValue by remember { mutableStateOf(50) } + + OrbitTheme { + Column( + modifier = Modifier.padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "Value: $currentValue", + style = OrbitTheme.typography.body1Medium, + color = OrbitTheme.colors.gray_50, + ) + + Spacer(modifier = Modifier.height(20.dp)) + + OrbitSlider( + value = currentValue, + onValueChange = { currentValue = it }, + trackHeight = 10.dp, + thumbSize = 22.dp, + thumbColor = OrbitTheme.colors.white, + inactiveBarColor = OrbitTheme.colors.gray_600, + activeBarColor = OrbitTheme.colors.main, + ) + } + } +} From cbafb2836d20cd351261558b94c09ff9191200a0 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 00:32:13 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[UI/#48]=20OrbitSlider=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=EB=B0=94=20=EC=B5=9C=EC=86=8C=EB=84=88=EB=B9=84=201.dp=20?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/ui/component/slider/Slider.kt | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt index 7e681a3..3e04a9f 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt @@ -10,7 +10,6 @@ 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 @@ -20,14 +19,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.utils.toPx @Composable fun OrbitSlider( @@ -40,31 +45,50 @@ fun OrbitSlider( inactiveBarColor: Color = OrbitTheme.colors.gray_600, activeBarColor: Color = OrbitTheme.colors.main, ) { - var thumbX by remember { mutableFloatStateOf(value.toFloat()) } + var thumbX by remember { mutableFloatStateOf(0f) } var isDragging by remember { mutableStateOf(false) } + var sliderSize by remember { mutableStateOf(IntSize.Zero) } + val startOffset = thumbSize.toPx() / 2 + 1.dp.toPx() Box( - modifier = Modifier.height(IntrinsicSize.Min), + modifier = Modifier + .height(IntrinsicSize.Min) + .onSizeChanged { + size -> + sliderSize = size + thumbX = startOffset + (value / 100f) * (sliderSize.width - startOffset * 2) + }, ) { Canvas( modifier = modifier .fillMaxWidth() .height(trackHeight) - .align(Alignment.Center) - .clip(RoundedCornerShape(100.dp)), + .align(Alignment.Center), ) { - val totalWidth = size.width - val activeWidth = thumbX + val totalWidth = sliderSize.width - startOffset * 2 + val normalizedThumbX = thumbX.coerceIn(startOffset, sliderSize.width - startOffset) + val activeWidth = (normalizedThumbX - startOffset).coerceAtLeast(startOffset) - drawRect( + val activePath = Path().apply { + val activeRect = Rect(0f, 0f, activeWidth + 3.dp.toPx(), size.height) + addRoundRect( + RoundRect( + rect = activeRect, + topLeft = CornerRadius(100.dp.toPx()), + bottomLeft = CornerRadius(100.dp.toPx()), + ), + ) + } + drawPath( + path = activePath, color = activeBarColor, - size = size.copy(width = activeWidth), ) - drawRect( + drawRoundRect( color = inactiveBarColor, - topLeft = Offset(activeWidth, 0f), size = size.copy(width = totalWidth - activeWidth), + cornerRadius = CornerRadius(100.dp.toPx()), + topLeft = Offset(activeWidth + 2.dp.toPx(), 0f), ) } @@ -80,7 +104,7 @@ fun OrbitSlider( offset.x, offset.y, thumbX, - size.height.toFloat() / 2, + sliderSize.height.toFloat() / 2, thumbSize.toPx() / 2, ) }, @@ -94,14 +118,16 @@ fun OrbitSlider( ) { _, dragAmount -> if (isDragging) { thumbX += dragAmount - thumbX = thumbX.coerceIn(0f, size.width.toFloat()) - val newValue = ((thumbX / size.width) * 100).toInt().coerceIn(0, 100) + thumbX = thumbX.coerceIn(startOffset, sliderSize.width - startOffset) + val newValue = (((thumbX - startOffset) / (sliderSize.width - startOffset * 2)) * 100) + .toInt() + .coerceIn(0, 100) onValueChange(newValue) } } }, ) { - val height = size.height + val height = sliderSize.height.toFloat() drawCircle( color = thumbColor, From f99dc64b6d59ddce69198cd3680561d68ca83aab Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 01:52:18 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[UI/#48]=20AlarmSnoozeBottomSheet=20?= =?UTF-8?q?=EC=83=81=EB=8B=A8=20=ED=8C=A8=EB=94=A9=20=EA=B0=92=2020.dp=20-?= =?UTF-8?q?>=2026.dp=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/bottomsheet/AlarmSnoozeBottomSheet.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 ae2fb36..bd319e4 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 @@ -93,10 +93,14 @@ private fun BottomSheetContent( Column( modifier = Modifier .fillMaxWidth() - .padding(24.dp), + .padding( + horizontal = 24.dp, + vertical = 12.dp, + ), horizontalAlignment = Alignment.CenterHorizontally, ) { - HeaderSection(isSnoozeEnabled, onSnoozeToggle) + Spacer(modifier = Modifier.height(6.dp)) + VibrationSection(isSnoozeEnabled, onSnoozeToggle) Spacer(modifier = Modifier.height(20.dp)) SelectorSection( title = stringResource(id = R.string.alarm_add_edit_interval), @@ -130,12 +134,11 @@ private fun BottomSheetContent( pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f), onClick = onComplete, ) - Spacer(modifier = Modifier.height(12.dp)) } } @Composable -private fun HeaderSection(isSnoozeEnabled: Boolean, onSnoozeToggle: () -> Unit) { +private fun VibrationSection(isSnoozeEnabled: Boolean, onSnoozeToggle: () -> Unit) { Row( modifier = Modifier .fillMaxWidth() From 99673d694e011d9a0669318e10da82607ba2a4e0 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 01:53:49 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[UI/#48]=20OrbitSlider=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20inactiveBar=EA=B0=80=20=EB=81=9D?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=EC=B0=A8=EC=A7=80=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt index 3e04a9f..cfdbb2f 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt @@ -65,7 +65,6 @@ fun OrbitSlider( .height(trackHeight) .align(Alignment.Center), ) { - val totalWidth = sliderSize.width - startOffset * 2 val normalizedThumbX = thumbX.coerceIn(startOffset, sliderSize.width - startOffset) val activeWidth = (normalizedThumbX - startOffset).coerceAtLeast(startOffset) @@ -86,7 +85,7 @@ fun OrbitSlider( drawRoundRect( color = inactiveBarColor, - size = size.copy(width = totalWidth - activeWidth), + size = size.copy(width = sliderSize.width.toFloat() - activeWidth), cornerRadius = CornerRadius(100.dp.toPx()), topLeft = Offset(activeWidth + 2.dp.toPx(), 0f), ) From 179b2aede861b897f43a6d2dce5effe7b65f3836 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 02:07:41 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[UI/#48]=20AlarmSoundBottomSheet=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 --- .../src/main/res/drawable/ic_sound_volume.xml | 9 + .../ui/component/radiobutton/RadioButton.kt | 6 +- .../bottomsheet/AlarmSoundBottomSheet.kt | 306 ++++++++++++++++++ feature/home/src/main/res/values/strings.xml | 3 +- 4 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/ic_sound_volume.xml create mode 100644 feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt diff --git a/core/designsystem/src/main/res/drawable/ic_sound_volume.xml b/core/designsystem/src/main/res/drawable/ic_sound_volume.xml new file mode 100644 index 0000000..22fc331 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_sound_volume.xml @@ -0,0 +1,9 @@ + + + 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 ed17246..7ffacd8 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 @@ -22,7 +22,7 @@ import com.yapp.designsystem.theme.OrbitTheme fun OrbitRadioButton( isSelected: Boolean, isEnabled: Boolean = true, - onClick: (Boolean) -> Unit, + onClick: () -> Unit, ) { val backgroundColor = if (isSelected) { OrbitTheme.colors.main.copy(alpha = 0.3f) @@ -52,7 +52,7 @@ fun OrbitRadioButton( .clip(CircleShape) .clickable { if (isEnabled) { - onClick(!isSelected) + onClick() } }, contentAlignment = Alignment.Center, @@ -77,7 +77,7 @@ fun OrbitRadioButtonPreview() { OrbitRadioButton( isSelected = isSelected, onClick = { - isSelected = it + isSelected = !isSelected }, ) } diff --git a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt new file mode 100644 index 0000000..c8d5140 --- /dev/null +++ b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt @@ -0,0 +1,306 @@ +package com.yapp.alarm.component.bottomsheet + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.layout.width +import androidx.compose.foundation.lazy.LazyColumn +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.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 +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +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.slider.OrbitSlider +import com.yapp.ui.component.switch.OrbitSwitch +import feature.home.R +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun AlarmSoundBottomSheet( + isVibrationEnabled: Boolean, + isSoundEnabled: Boolean, + soundVolume: Int, + soundIndex: Int, + sounds: List, + onVibrationToggle: () -> Unit, + onSoundToggle: () -> Unit, + onVolumeChanged: (Int) -> Unit, + onSoundSelected: (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( + isVibrationEnabled = isVibrationEnabled, + isSoundEnabled = isSoundEnabled, + soundVolume = soundVolume, + soundIndex = soundIndex, + sounds = sounds, + onVibrationToggle = onVibrationToggle, + onSoundToggle = onSoundToggle, + onVolumeChanged = onVolumeChanged, + onSoundSelected = onSoundSelected, + onComplete = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { onComplete() } + }, + ) + } +} + +@Composable +private fun BottomSheetContent( + modifier: Modifier = Modifier, + isVibrationEnabled: Boolean, + isSoundEnabled: Boolean, + soundVolume: Int, + soundIndex: Int, + sounds: List, + onVibrationToggle: () -> Unit, + onSoundToggle: () -> Unit, + onVolumeChanged: (Int) -> Unit, + onSoundSelected: (Int) -> Unit, + onComplete: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 24.dp, + vertical = 12.dp, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.height(6.dp)) + VibrationSection( + isVibrationEnabled = isVibrationEnabled, + onVibrationToggle = onVibrationToggle, + ) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .background(color = OrbitTheme.colors.gray_700), + ) + SoundSection( + isSoundEnabled = isSoundEnabled, + onSoundToggle = onSoundToggle, + soundVolume = soundVolume, + onVolumeChanged = onVolumeChanged, + soundIndex = soundIndex, + sounds = sounds, + onSoundSelected = { onSoundSelected(it) }, + ) + + OrbitButton( + 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, + ) + } +} + +@Composable +private fun VibrationSection( + isVibrationEnabled: Boolean, + onVibrationToggle: () -> Unit, +) { + Column { + Text( + text = stringResource(id = R.string.alarm_add_edit_sound), + style = OrbitTheme.typography.heading2SemiBold, + color = OrbitTheme.colors.white, + modifier = Modifier.padding(vertical = 8.dp), + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = R.string.alarm_add_edit_vibration), + style = OrbitTheme.typography.headline2Medium, + color = OrbitTheme.colors.gray_50, + ) + Spacer(modifier = Modifier.weight(1f)) + OrbitSwitch( + isChecked = isVibrationEnabled, + isEnabled = true, + onClick = onVibrationToggle, + ) + } + } +} + +@Composable +private fun SoundSection( + isSoundEnabled: Boolean, + onSoundToggle: () -> Unit, + soundVolume: Int, + onVolumeChanged: (Int) -> Unit, + sounds: List, + soundIndex: Int, + onSoundSelected: (Int) -> Unit, +) { + Column( + modifier = Modifier.padding(top = 20.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = R.string.alarm_add_edit_ringtone), + style = OrbitTheme.typography.headline2Medium, + color = OrbitTheme.colors.gray_50, + ) + Spacer(modifier = Modifier.weight(1f)) + OrbitSwitch( + isChecked = isSoundEnabled, + isEnabled = true, + onClick = onSoundToggle, + ) + } + + if (isSoundEnabled) { + Spacer(modifier = Modifier.height(20.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = core.designsystem.R.drawable.ic_sound_volume), + contentDescription = "Volume", + tint = OrbitTheme.colors.gray_400, + ) + Spacer(modifier = Modifier.width(8.dp)) + OrbitSlider( + value = soundVolume, + onValueChange = onVolumeChanged, + ) + } + Spacer(modifier = Modifier.height(20.dp)) + SoundSelectionSection( + soundIndex = soundIndex, + sounds = sounds, + onSoundSelected = { onSoundSelected(it) }, + ) + } + } +} + +@Composable +private fun SoundSelectionSection( + soundIndex: Int, + sounds: List, + onSoundSelected: (Int) -> Unit, +) { + LazyColumn( + modifier = Modifier + .height(LocalConfiguration.current.screenHeightDp.dp * 0.4f) + .padding(vertical = 8.dp), + contentPadding = PaddingValues(bottom = 20.dp), + ) { + items(sounds.size) { index -> + SoundSelectionItem( + sound = sounds[index], + isSelected = index == soundIndex, + onClick = { onSoundSelected(index) }, + ) + if (index != sounds.size - 1) { + Spacer(modifier = Modifier.height(20.dp)) + } + } + } +} + +@Composable +private fun SoundSelectionItem( + sound: String, + isSelected: Boolean, + onClick: () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + OrbitRadioButton( + isSelected = isSelected, + onClick = onClick, + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = sound, + style = OrbitTheme.typography.body1Medium, + color = OrbitTheme.colors.white, + ) + } +} + +@Preview +@Composable +private fun AlarmSoundBottomSheetPreview() { + var isVibrationEnabled by remember { mutableStateOf(true) } + var isSoundEnabled by remember { mutableStateOf(true) } + var soundVolume by remember { mutableIntStateOf(0) } + var soundIndex by remember { mutableIntStateOf(0) } + val sounds by remember { mutableStateOf((1..20).map { "sound $it" }) } + var isSheetOpen by remember { mutableStateOf(true) } + + OrbitTheme { + AlarmSoundBottomSheet( + isVibrationEnabled = isVibrationEnabled, + isSoundEnabled = isSoundEnabled, + soundVolume = soundVolume, + soundIndex = soundIndex, + sounds = sounds, + onVibrationToggle = { isVibrationEnabled = !isVibrationEnabled }, + onSoundToggle = { isSoundEnabled = !isSoundEnabled }, + onVolumeChanged = { soundVolume = it }, + onSoundSelected = { soundIndex = it }, + onComplete = { isSheetOpen = false }, + isSheetOpen = isSheetOpen, + onDismiss = { isSheetOpen = false }, + ) + } +} diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml index b39a20b..5637fc0 100644 --- a/feature/home/src/main/res/values/strings.xml +++ b/feature/home/src/main/res/values/strings.xml @@ -26,8 +26,7 @@ %d회 무한 - 안 함 - 소리 + 사운드 진동 알람음 From 33bd1387e9057f966cd21ef487c74620e2d28bd1 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 02:26:57 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[FEAT/#48]=20AlarmSoundBottomSheet=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/AlarmAddEditContract.kt | 15 ++++ .../java/com/yapp/alarm/AlarmAddEditScreen.kt | 48 +++++++++++-- .../com/yapp/alarm/AlarmAddEditViewModel.kt | 68 ++++++++++++++----- .../bottomsheet/AlarmSoundBottomSheet.kt | 3 +- 4 files changed, 111 insertions(+), 23 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 a9c3ec5..5c30a71 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt @@ -9,6 +9,7 @@ sealed class AlarmAddEditContract { val daySelectionState: AlarmDaySelectionState = AlarmDaySelectionState(), val holidayState: AlarmHolidayState = AlarmHolidayState(), val snoozeState: AlarmSnoozeState = AlarmSnoozeState(), + val soundState: AlarmSoundState = AlarmSoundState(), ) : UiState data class AlarmTimeState( @@ -39,6 +40,15 @@ sealed class AlarmAddEditContract { val snoozeCounts: List = listOf("1회", "3회", "5회", "10회", "무한"), ) + data class AlarmSoundState( + val isVibrationEnabled: Boolean = true, + val isSoundEnabled: Boolean = true, + val soundVolume: Int = 70, + val soundIndex: Int = 0, + val sounds: List = (1..7).map { "기본 알람음 $it" }, + val isBottomSheetOpen: Boolean = false, + ) + sealed class Action { data object ClickBack : Action() data object ClickSave : Action() @@ -51,6 +61,11 @@ sealed class AlarmAddEditContract { data object ToggleSnoozeEnabled : Action() data class UpdateSnoozeInterval(val index: Int) : Action() data class UpdateSnoozeCount(val index: Int) : Action() + data object ToggleVibrationEnabled : Action() + data object ToggleSoundEnabled : Action() + data class UpdateSoundVolume(val volume: Int) : Action() + data class UpdateSoundIndex(val index: Int) : Action() + data object ToggleSoundSettingBottomSheetOpen : 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 0c3066a..012f186 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -33,6 +33,7 @@ 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.alarm.component.bottomsheet.AlarmSoundBottomSheet import com.yapp.common.navigation.OrbitNavigator import com.yapp.designsystem.theme.OrbitTheme import com.yapp.ui.component.button.OrbitButton @@ -81,7 +82,8 @@ fun AlarmAddEditScreen( ) { val state = stateProvider() val snoozeState = state.snoozeState - val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val snoozeBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val soundBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() Column( @@ -132,7 +134,7 @@ fun AlarmAddEditScreen( onCountSelected = { index -> eventDispatcher(AlarmAddEditContract.Action.UpdateSnoozeCount(index)) }, onComplete = { scope.launch { - bottomSheetState.hide() + snoozeBottomSheetState.hide() }.invokeOnCompletion { eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) } @@ -140,12 +142,39 @@ fun AlarmAddEditScreen( isSheetOpen = snoozeState.isBottomSheetOpen, onDismiss = { scope.launch { - bottomSheetState.hide() + snoozeBottomSheetState.hide() }.invokeOnCompletion { eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) } }, ) + + AlarmSoundBottomSheet( + isVibrationEnabled = state.soundState.isVibrationEnabled, + isSoundEnabled = state.soundState.isSoundEnabled, + soundVolume = state.soundState.soundVolume, + soundIndex = state.soundState.soundIndex, + sounds = state.soundState.sounds, + onVibrationToggle = { eventDispatcher(AlarmAddEditContract.Action.ToggleVibrationEnabled) }, + onSoundToggle = { eventDispatcher(AlarmAddEditContract.Action.ToggleSoundEnabled) }, + onVolumeChanged = { eventDispatcher(AlarmAddEditContract.Action.UpdateSoundVolume(it)) }, + onSoundSelected = { eventDispatcher(AlarmAddEditContract.Action.UpdateSoundIndex(it)) }, + onComplete = { + scope.launch { + soundBottomSheetState.hide() + }.invokeOnCompletion { + eventDispatcher(AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen) + } + }, + isSheetOpen = state.soundState.isBottomSheetOpen, + onDismiss = { + scope.launch { + soundBottomSheetState.hide() + }.invokeOnCompletion { + eventDispatcher(AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen) + } + }, + ) } @Composable @@ -230,9 +259,16 @@ private fun AlarmAddEditSettingsSection( .background(OrbitTheme.colors.gray_700), ) AlarmAddEditSettingItem( - label = "사운드", - description = "진동, 알림음1", - onClick = { }, + label = stringResource(id = R.string.alarm_add_edit_sound), + description = when { + state.soundState.isSoundEnabled && state.soundState.isVibrationEnabled -> { + "${state.soundState.sounds[state.soundState.soundIndex]}, ${stringResource(id = R.string.alarm_add_edit_vibration)}" + } + state.soundState.isSoundEnabled -> state.soundState.sounds[state.soundState.soundIndex] + state.soundState.isVibrationEnabled -> stringResource(id = R.string.alarm_add_edit_vibration) + else -> stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none) + }, + onClick = { processAction(AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen) }, ) } } 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 e209c5a..3c5d9cc 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -1,9 +1,7 @@ package com.yapp.alarm -import androidx.lifecycle.viewModelScope import com.yapp.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -11,20 +9,23 @@ 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.ToggleSnoozeSettingBottomSheetOpen -> toggleSnoozeSettingBottomSheet() - } + when (action) { + is AlarmAddEditContract.Action.ClickBack -> 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.ToggleSnoozeSettingBottomSheetOpen -> toggleSnoozeSettingBottomSheet() + is AlarmAddEditContract.Action.ToggleVibrationEnabled -> toggleVibrationEnabled() + is AlarmAddEditContract.Action.ToggleSoundEnabled -> toggleSoundEnabled() + is AlarmAddEditContract.Action.UpdateSoundVolume -> updateSoundVolume(action.volume) + is AlarmAddEditContract.Action.UpdateSoundIndex -> updateSoundIndex(action.index) + is AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen -> toggleSoundSettingBottomSheet() } } @@ -167,6 +168,41 @@ 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/AlarmSoundBottomSheet.kt b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt index c8d5140..118e1d4 100644 --- a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt +++ b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt @@ -86,7 +86,6 @@ internal fun AlarmSoundBottomSheet( @Composable private fun BottomSheetContent( - modifier: Modifier = Modifier, isVibrationEnabled: Boolean, isSoundEnabled: Boolean, soundVolume: Int, @@ -226,6 +225,8 @@ private fun SoundSection( sounds = sounds, onSoundSelected = { onSoundSelected(it) }, ) + } else { + Spacer(modifier = Modifier.height(20.dp)) } } } From 85590eafc19bbbbdc47855955996ab110026b34b Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 02:44:06 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[UI/#48]=20AlarmDayButton=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=8B=9C=20=ED=85=8C=EB=91=90=EB=A6=AC=20=EC=83=89?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/alarm/component/AlarmDayButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt b/feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt index 98d1c75..6ab4ba5 100644 --- a/feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt +++ b/feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt @@ -30,7 +30,7 @@ internal fun AlarmDayButton( Triple( OrbitTheme.colors.main.copy(alpha = 0.1f), OrbitTheme.colors.main, - Color.Transparent, + OrbitTheme.colors.main.copy(alpha = 0.2f), ) } else { Triple( From 0237a144dbf2142410aa80b6f3f26eaa429dd94d Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 15:14:38 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[REFACTOR/#48]=20OrbitSlider=20=EC=BA=94?= =?UTF-8?q?=EB=B2=84=EC=8A=A4=20=ED=95=98=EB=82=98=EB=A7=8C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yapp/ui/component/slider/OrbitSlider.kt | 150 +++++++++++++++ .../com/yapp/ui/component/slider/Slider.kt | 175 ------------------ 2 files changed, 150 insertions(+), 175 deletions(-) create mode 100644 core/ui/src/main/java/com/yapp/ui/component/slider/OrbitSlider.kt delete mode 100644 core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt diff --git a/core/ui/src/main/java/com/yapp/ui/component/slider/OrbitSlider.kt b/core/ui/src/main/java/com/yapp/ui/component/slider/OrbitSlider.kt new file mode 100644 index 0000000..dca8df0 --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ui/component/slider/OrbitSlider.kt @@ -0,0 +1,150 @@ +package com.yapp.ui.component.slider + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Column +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +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.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.utils.toPx + +@Composable +fun OrbitSlider( + value: Int, + onValueChange: (Int) -> Unit, + modifier: Modifier = Modifier, + trackHeight: Dp = 6.dp, + thumbSize: Dp = 22.dp, + thumbColor: Color = Color.White, + inactiveBarColor: Color = OrbitTheme.colors.gray_600, + activeBarColor: Color = OrbitTheme.colors.main, +) { + val thumbRadius = thumbSize.toPx() / 2 + var sliderWidth by remember { mutableFloatStateOf(0f) } + val startOffset = thumbRadius + 1.dp.toPx() + + var thumbX = remember(value, startOffset, sliderWidth) { + startOffset + (value / 100f) * (sliderWidth - startOffset * 2) + } + + var isDragging by remember { mutableStateOf(false) } + + Canvas( + modifier = modifier + .fillMaxWidth() + .height(thumbSize) + .pointerInput(Unit) { + detectTapGestures( + onPress = { offset -> + isDragging = isInCircle( + offset.x, + offset.y, + thumbX, + thumbRadius, + thumbRadius, + ) + }, + ) + } + .pointerInput(Unit) { + detectHorizontalDragGestures( + onDragEnd = { isDragging = false }, + ) { _, dragAmount -> + if (isDragging) { + thumbX += dragAmount + thumbX = thumbX.coerceIn(startOffset, sliderWidth - startOffset) + val newValue = (((thumbX - startOffset) / (sliderWidth - 2 * startOffset)) * 100) + .toInt() + .coerceIn(0, 100) + onValueChange(newValue) + } + } + }, + ) { + sliderWidth = size.width + + val normalizedThumbX by derivedStateOf { + thumbX.coerceIn(startOffset, sliderWidth - startOffset) + } + val activeWidth = (normalizedThumbX - startOffset).coerceAtLeast(0f) + + drawRoundRect( + color = activeBarColor, + size = Size(activeWidth + 3.dp.toPx(), trackHeight.toPx()), + topLeft = Offset(0f, (size.height - trackHeight.toPx()) / 2), + cornerRadius = CornerRadius(100.dp.toPx(), 100.dp.toPx()), + ) + + drawRoundRect( + color = inactiveBarColor, + size = Size(size.width - activeWidth - 5.dp.toPx(), trackHeight.toPx()), + topLeft = Offset(activeWidth + 2.dp.toPx(), (size.height - trackHeight.toPx()) / 2), + cornerRadius = CornerRadius(100.dp.toPx(), 100.dp.toPx()), + ) + + drawCircle( + color = thumbColor, + radius = thumbSize.toPx() / 2, + center = Offset(normalizedThumbX, size.height / 2), + ) + } +} + +private fun isInCircle(x: Float, y: Float, centerX: Float, centerY: Float, radius: Float): Boolean { + val dx = x - centerX + val dy = y - centerY + return (dx * dx + dy * dy) <= (radius * radius) +} + +@Preview +@Composable +fun PreviewOrbitSlider() { + var currentValue by remember { mutableIntStateOf(50) } + + OrbitTheme { + Column( + modifier = Modifier.padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "Value: $currentValue", + style = OrbitTheme.typography.body1Medium, + color = OrbitTheme.colors.gray_50, + ) + + Spacer(modifier = Modifier.height(20.dp)) + + OrbitSlider( + value = currentValue, + onValueChange = { currentValue = it }, + trackHeight = 10.dp, + thumbSize = 22.dp, + thumbColor = OrbitTheme.colors.white, + inactiveBarColor = OrbitTheme.colors.gray_600, + activeBarColor = OrbitTheme.colors.main, + ) + } + } +} diff --git a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt b/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt deleted file mode 100644 index cfdbb2f..0000000 --- a/core/ui/src/main/java/com/yapp/ui/component/slider/Slider.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.yapp.ui.component.slider - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.gestures.detectHorizontalDragGestures -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -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.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -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.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.RoundRect -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.dp -import com.yapp.designsystem.theme.OrbitTheme -import com.yapp.ui.utils.toPx - -@Composable -fun OrbitSlider( - value: Int, - onValueChange: (Int) -> Unit, - modifier: Modifier = Modifier, - trackHeight: Dp = 6.dp, - thumbSize: Dp = 22.dp, - thumbColor: Color = Color.White, - inactiveBarColor: Color = OrbitTheme.colors.gray_600, - activeBarColor: Color = OrbitTheme.colors.main, -) { - var thumbX by remember { mutableFloatStateOf(0f) } - var isDragging by remember { mutableStateOf(false) } - var sliderSize by remember { mutableStateOf(IntSize.Zero) } - val startOffset = thumbSize.toPx() / 2 + 1.dp.toPx() - - Box( - modifier = Modifier - .height(IntrinsicSize.Min) - .onSizeChanged { - size -> - sliderSize = size - thumbX = startOffset + (value / 100f) * (sliderSize.width - startOffset * 2) - }, - ) { - Canvas( - modifier = modifier - .fillMaxWidth() - .height(trackHeight) - .align(Alignment.Center), - ) { - val normalizedThumbX = thumbX.coerceIn(startOffset, sliderSize.width - startOffset) - val activeWidth = (normalizedThumbX - startOffset).coerceAtLeast(startOffset) - - val activePath = Path().apply { - val activeRect = Rect(0f, 0f, activeWidth + 3.dp.toPx(), size.height) - addRoundRect( - RoundRect( - rect = activeRect, - topLeft = CornerRadius(100.dp.toPx()), - bottomLeft = CornerRadius(100.dp.toPx()), - ), - ) - } - drawPath( - path = activePath, - color = activeBarColor, - ) - - drawRoundRect( - color = inactiveBarColor, - size = size.copy(width = sliderSize.width.toFloat() - activeWidth), - cornerRadius = CornerRadius(100.dp.toPx()), - topLeft = Offset(activeWidth + 2.dp.toPx(), 0f), - ) - } - - Canvas( - modifier = modifier - .fillMaxWidth() - .height(thumbSize) - .align(Alignment.Center) - .pointerInput(true) { - detectTapGestures( - onPress = { offset -> - isDragging = isInCircle( - offset.x, - offset.y, - thumbX, - sliderSize.height.toFloat() / 2, - thumbSize.toPx() / 2, - ) - }, - ) - } - .pointerInput(true) { - detectHorizontalDragGestures( - onDragEnd = { - isDragging = false - }, - ) { _, dragAmount -> - if (isDragging) { - thumbX += dragAmount - thumbX = thumbX.coerceIn(startOffset, sliderSize.width - startOffset) - val newValue = (((thumbX - startOffset) / (sliderSize.width - startOffset * 2)) * 100) - .toInt() - .coerceIn(0, 100) - onValueChange(newValue) - } - } - }, - ) { - val height = sliderSize.height.toFloat() - - drawCircle( - color = thumbColor, - radius = thumbSize.toPx() / 2, - center = Offset(thumbX, height / 2), - ) - } - } -} - -private fun isInCircle(x: Float, y: Float, centerX: Float, centerY: Float, radius: Float): Boolean { - val dx = x - centerX - val dy = y - centerY - return (dx * dx + dy * dy) <= (radius * radius) -} - -@Preview -@Composable -fun PreviewOrbitSlider() { - var currentValue by remember { mutableStateOf(50) } - - OrbitTheme { - Column( - modifier = Modifier.padding(horizontal = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - text = "Value: $currentValue", - style = OrbitTheme.typography.body1Medium, - color = OrbitTheme.colors.gray_50, - ) - - Spacer(modifier = Modifier.height(20.dp)) - - OrbitSlider( - value = currentValue, - onValueChange = { currentValue = it }, - trackHeight = 10.dp, - thumbSize = 22.dp, - thumbColor = OrbitTheme.colors.white, - inactiveBarColor = OrbitTheme.colors.gray_600, - activeBarColor = OrbitTheme.colors.main, - ) - } - } -} From d1e7abc2acf0b038a0eb1289cceab80141b20a65 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 15:15:29 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[RENAME/#48]=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=9D=98=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=95=9E?= =?UTF-8?q?=EC=97=90=20Orbit=EC=9D=84=20=EB=B6=99=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yapp/ui/component/checkbox/{CheckBox.kt => OrbitCheckBox.kt} | 0 .../component/radiobutton/{RadioButton.kt => OrbitRadioButton.kt} | 0 .../com/yapp/ui/component/switch/{Switch.kt => OrbitSwitch.kt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename core/ui/src/main/java/com/yapp/ui/component/checkbox/{CheckBox.kt => OrbitCheckBox.kt} (100%) rename core/ui/src/main/java/com/yapp/ui/component/radiobutton/{RadioButton.kt => OrbitRadioButton.kt} (100%) rename core/ui/src/main/java/com/yapp/ui/component/switch/{Switch.kt => OrbitSwitch.kt} (100%) diff --git a/core/ui/src/main/java/com/yapp/ui/component/checkbox/CheckBox.kt b/core/ui/src/main/java/com/yapp/ui/component/checkbox/OrbitCheckBox.kt similarity index 100% rename from core/ui/src/main/java/com/yapp/ui/component/checkbox/CheckBox.kt rename to core/ui/src/main/java/com/yapp/ui/component/checkbox/OrbitCheckBox.kt 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/OrbitRadioButton.kt similarity index 100% rename from core/ui/src/main/java/com/yapp/ui/component/radiobutton/RadioButton.kt rename to core/ui/src/main/java/com/yapp/ui/component/radiobutton/OrbitRadioButton.kt 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/OrbitSwitch.kt similarity index 100% rename from core/ui/src/main/java/com/yapp/ui/component/switch/Switch.kt rename to core/ui/src/main/java/com/yapp/ui/component/switch/OrbitSwitch.kt From 5aa347826ecb294ee20dc3b0ce5aaa51a5b62288 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Mon, 20 Jan 2025 15:38:40 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[REFACTOR/#48]=20=EC=97=AC=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=94=ED=85=80=20=EC=8B=9C=ED=8A=B8=EB=A5=BC=20=ED=95=98?= =?UTF-8?q?=EB=82=98=EC=9D=98=20=EB=B0=94=ED=85=80=20=EC=8B=9C=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=A1=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/AlarmAddEditContract.kt | 11 ++++++---- .../java/com/yapp/alarm/AlarmAddEditScreen.kt | 16 +++++++-------- .../com/yapp/alarm/AlarmAddEditViewModel.kt | 20 ++++++++----------- 3 files changed, 23 insertions(+), 24 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 5c30a71..3f0b7f7 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt @@ -10,6 +10,7 @@ sealed class AlarmAddEditContract { val holidayState: AlarmHolidayState = AlarmHolidayState(), val snoozeState: AlarmSnoozeState = AlarmSnoozeState(), val soundState: AlarmSoundState = AlarmSoundState(), + val bottomSheetState: BottomSheetType? = null, ) : UiState data class AlarmTimeState( @@ -35,7 +36,6 @@ sealed class AlarmAddEditContract { 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회", "무한"), ) @@ -46,7 +46,6 @@ sealed class AlarmAddEditContract { val soundVolume: Int = 70, val soundIndex: Int = 0, val sounds: List = (1..7).map { "기본 알람음 $it" }, - val isBottomSheetOpen: Boolean = false, ) sealed class Action { @@ -57,7 +56,6 @@ sealed class AlarmAddEditContract { data object ToggleWeekendsChecked : Action() data class ToggleDaySelection(val day: AlarmDay) : Action() data object ToggleDisableHolidayChecked : Action() - data object ToggleSnoozeSettingBottomSheetOpen : Action() data object ToggleSnoozeEnabled : Action() data class UpdateSnoozeInterval(val index: Int) : Action() data class UpdateSnoozeCount(val index: Int) : Action() @@ -65,7 +63,12 @@ sealed class AlarmAddEditContract { data object ToggleSoundEnabled : Action() data class UpdateSoundVolume(val volume: Int) : Action() data class UpdateSoundIndex(val index: Int) : Action() - data object ToggleSoundSettingBottomSheetOpen : Action() + data class ToggleBottomSheetOpen(val sheetType: BottomSheetType) : Action() + } + + sealed class BottomSheetType { + data object SnoozeSetting : BottomSheetType() + data object SoundSetting : BottomSheetType() } 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 012f186..4b90caa 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt @@ -136,15 +136,15 @@ fun AlarmAddEditScreen( scope.launch { snoozeBottomSheetState.hide() }.invokeOnCompletion { - eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) + eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SnoozeSetting)) } }, - isSheetOpen = snoozeState.isBottomSheetOpen, + isSheetOpen = state.bottomSheetState == AlarmAddEditContract.BottomSheetType.SnoozeSetting, onDismiss = { scope.launch { snoozeBottomSheetState.hide() }.invokeOnCompletion { - eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) + eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SnoozeSetting)) } }, ) @@ -163,15 +163,15 @@ fun AlarmAddEditScreen( scope.launch { soundBottomSheetState.hide() }.invokeOnCompletion { - eventDispatcher(AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen) + eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SoundSetting)) } }, - isSheetOpen = state.soundState.isBottomSheetOpen, + isSheetOpen = state.bottomSheetState == AlarmAddEditContract.BottomSheetType.SoundSetting, onDismiss = { scope.launch { soundBottomSheetState.hide() }.invokeOnCompletion { - eventDispatcher(AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen) + eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SoundSetting)) } }, ) @@ -250,7 +250,7 @@ private fun AlarmAddEditSettingsSection( } else { stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none) }, - onClick = { processAction(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen) }, + onClick = { processAction(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SnoozeSetting)) }, ) Spacer( modifier = Modifier.fillMaxWidth() @@ -268,7 +268,7 @@ private fun AlarmAddEditSettingsSection( state.soundState.isVibrationEnabled -> stringResource(id = R.string.alarm_add_edit_vibration) else -> stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none) }, - onClick = { processAction(AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen) }, + onClick = { processAction(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SoundSetting)) }, ) } } 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 3c5d9cc..de08838 100644 --- a/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt +++ b/feature/home/src/main/java/com/yapp/alarm/AlarmAddEditViewModel.kt @@ -20,12 +20,11 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel toggleSnoozeEnabled() is AlarmAddEditContract.Action.UpdateSnoozeInterval -> updateSnoozeInterval(action.index) is AlarmAddEditContract.Action.UpdateSnoozeCount -> updateSnoozeCount(action.index) - is AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen -> toggleSnoozeSettingBottomSheet() is AlarmAddEditContract.Action.ToggleVibrationEnabled -> toggleVibrationEnabled() is AlarmAddEditContract.Action.ToggleSoundEnabled -> toggleSoundEnabled() is AlarmAddEditContract.Action.UpdateSoundVolume -> updateSoundVolume(action.volume) is AlarmAddEditContract.Action.UpdateSoundIndex -> updateSoundIndex(action.index) - is AlarmAddEditContract.Action.ToggleSoundSettingBottomSheetOpen -> toggleSoundSettingBottomSheet() + is AlarmAddEditContract.Action.ToggleBottomSheetOpen -> toggleBottomSheet(action.sheetType) } } @@ -147,13 +146,6 @@ class AlarmAddEditViewModel @Inject constructor() : BaseViewModel