Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UI/YAF-79] 알람 설정 시 사운드 설정을 할 수 있다 #49

Merged
merged 10 commits into from
Jan 20, 2025
Merged
9 changes: 9 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_sound_volume.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:pathData="M15.47,3.85C15.653,3.719 15.866,3.639 16.09,3.617C16.313,3.594 16.538,3.631 16.743,3.723C16.948,3.815 17.125,3.96 17.257,4.142C17.388,4.323 17.47,4.537 17.493,4.76L17.5,4.893V23.107C17.5,23.331 17.441,23.552 17.329,23.747C17.218,23.941 17.057,24.103 16.863,24.216C16.669,24.329 16.448,24.389 16.224,24.391C15.999,24.392 15.778,24.334 15.583,24.223L15.471,24.151L7.793,18.667H4.667C4.078,18.667 3.511,18.444 3.079,18.044C2.648,17.644 2.383,17.095 2.339,16.508L2.333,16.333V11.667C2.333,11.078 2.555,10.511 2.956,10.079C3.356,9.648 3.905,9.383 4.492,9.339L4.667,9.333H7.793L15.47,3.85ZM22.945,7.913C23.802,8.679 24.488,9.617 24.957,10.666C25.426,11.715 25.668,12.851 25.667,14C25.668,15.149 25.426,16.285 24.957,17.334C24.488,18.383 23.802,19.321 22.945,20.086C22.831,20.19 22.698,20.271 22.553,20.323C22.407,20.375 22.254,20.397 22.1,20.39C21.946,20.382 21.795,20.344 21.656,20.277C21.517,20.211 21.392,20.118 21.289,20.003C21.187,19.888 21.108,19.754 21.057,19.608C21.006,19.463 20.985,19.309 20.994,19.155C21.003,19.001 21.043,18.85 21.111,18.712C21.178,18.573 21.273,18.45 21.389,18.348C22.001,17.802 22.491,17.132 22.826,16.382C23.162,15.633 23.334,14.821 23.333,14C23.333,12.273 22.584,10.722 21.389,9.652C21.273,9.55 21.178,9.426 21.111,9.288C21.043,9.149 21.003,8.999 20.994,8.845C20.985,8.691 21.006,8.537 21.057,8.392C21.108,8.246 21.187,8.112 21.289,7.997C21.392,7.882 21.517,7.789 21.656,7.723C21.795,7.656 21.946,7.618 22.1,7.61C22.254,7.602 22.407,7.625 22.553,7.677C22.698,7.729 22.831,7.81 22.945,7.913ZM20.611,10.522C21.101,10.959 21.493,11.495 21.761,12.094C22.029,12.693 22.167,13.342 22.167,13.999C22.167,15.382 21.565,16.625 20.611,17.478C20.39,17.674 20.103,17.779 19.807,17.772C19.511,17.764 19.229,17.644 19.018,17.437C18.807,17.229 18.683,16.949 18.671,16.654C18.659,16.358 18.76,16.069 18.952,15.844L19.055,15.739C19.534,15.31 19.833,14.691 19.833,14C19.835,13.403 19.606,12.828 19.194,12.395L19.055,12.26C18.939,12.159 18.845,12.035 18.777,11.897C18.709,11.758 18.67,11.608 18.661,11.454C18.652,11.3 18.673,11.146 18.723,11C18.774,10.855 18.853,10.721 18.956,10.606C19.059,10.491 19.183,10.398 19.323,10.331C19.462,10.265 19.612,10.227 19.766,10.219C19.92,10.211 20.074,10.234 20.219,10.286C20.364,10.338 20.498,10.418 20.611,10.522Z"
android:fillColor="#7B8696"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -52,7 +52,7 @@ fun OrbitRadioButton(
.clip(CircleShape)
.clickable {
if (isEnabled) {
onClick(!isSelected)
onClick()
}
},
contentAlignment = Alignment.Center,
Expand All @@ -77,7 +77,7 @@ fun OrbitRadioButtonPreview() {
OrbitRadioButton(
isSelected = isSelected,
onClick = {
isSelected = it
isSelected = !isSelected
},
)
}
Expand Down
150 changes: 150 additions & 0 deletions core/ui/src/main/java/com/yapp/ui/component/slider/OrbitSlider.kt
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
}
22 changes: 20 additions & 2 deletions feature/home/src/main/java/com/yapp/alarm/AlarmAddEditContract.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ sealed class AlarmAddEditContract {
val daySelectionState: AlarmDaySelectionState = AlarmDaySelectionState(),
val holidayState: AlarmHolidayState = AlarmHolidayState(),
val snoozeState: AlarmSnoozeState = AlarmSnoozeState(),
val soundState: AlarmSoundState = AlarmSoundState(),
val bottomSheetState: BottomSheetType? = null,
) : UiState

data class AlarmTimeState(
Expand All @@ -34,11 +36,18 @@ sealed class AlarmAddEditContract {
val isSnoozeEnabled: Boolean = true,
val snoozeIntervalIndex: Int = 2,
val snoozeCountIndex: Int = 1,
val isBottomSheetOpen: Boolean = false,
val snoozeIntervals: List<String> = listOf("1분", "3분", "5분", "10분", "15분"),
val snoozeCounts: List<String> = 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<String> = (1..7).map { "기본 알람음 $it" },
)

sealed class Action {
data object ClickBack : Action()
data object ClickSave : Action()
Expand All @@ -47,10 +56,19 @@ 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()
data object ToggleVibrationEnabled : Action()
data object ToggleSoundEnabled : Action()
data class UpdateSoundVolume(val volume: Int) : Action()
data class UpdateSoundIndex(val index: Int) : 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 {
Expand Down
56 changes: 46 additions & 10 deletions feature/home/src/main/java/com/yapp/alarm/AlarmAddEditScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -132,17 +134,44 @@ fun AlarmAddEditScreen(
onCountSelected = { index -> eventDispatcher(AlarmAddEditContract.Action.UpdateSnoozeCount(index)) },
onComplete = {
scope.launch {
bottomSheetState.hide()
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 {
bottomSheetState.hide()
snoozeBottomSheetState.hide()
}.invokeOnCompletion {
eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeSettingBottomSheetOpen)
eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SnoozeSetting))
}
},
)

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.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SoundSetting))
}
},
isSheetOpen = state.bottomSheetState == AlarmAddEditContract.BottomSheetType.SoundSetting,
onDismiss = {
scope.launch {
soundBottomSheetState.hide()
}.invokeOnCompletion {
eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SoundSetting))
}
},
)
Expand Down Expand Up @@ -221,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()
Expand All @@ -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.ToggleBottomSheetOpen(AlarmAddEditContract.BottomSheetType.SoundSetting)) },
)
}
}
Expand Down
Loading
Loading