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

Switch 컴포넌트 추가 #13

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/git_toolbox_blame.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions app/src/main/kotlin/com/yourssu/handy/demo/SwitchPreview.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.yourssu.handy.demo

import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
import com.yourssu.handy.compose.Switch
import com.yourssu.handy.compose.SwitchSize
import com.yourssu.handy.compose.SwitchState
import com.yourssu.handy.compose.Text


data class SwitchPreviewParameter(
var switchState: SwitchState,
val switchSize: SwitchSize
)

@Preview(showBackground = true)
@Composable
private fun PreviewMultipleSwitchState() {
val samples = remember { mutableStateListOf<SwitchPreviewParameter>() }

LaunchedEffect(Unit) {// SwitchState와 SwitchSize의 모든 조합 생성
val switchStates =
listOf(SwitchState.Unselected, SwitchState.Selected, SwitchState.Disabled)
val switchSizes = listOf(SwitchSize.Large, SwitchSize.Medium, SwitchSize.Small)

switchStates.forEach { state ->
switchSizes.forEach { size ->
samples.add(
SwitchPreviewParameter(
switchState = state,
switchSize = size
)
)
}
}
Comment on lines +26 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val switchStates =
listOf(SwitchState.Unselected, SwitchState.Selected, SwitchState.Disabled)
val switchSizes = listOf(SwitchSize.Large, SwitchSize.Medium, SwitchSize.Small)
switchStates.forEach { state ->
switchSizes.forEach { size ->
samples.add(
SwitchPreviewParameter(
switchState = state,
switchSize = size
)
)
}
}
val switchStates =
listOf(SwitchState.Unselected, SwitchState.Selected, SwitchState.Disabled)
val switchSizes = listOf(SwitchSize.Large, SwitchSize.Medium, SwitchSize.Small)
val samples = switchStates.flatMap { state ->
switchSizes.map { size -> SwitchPreviewParameter(state, size) }
}

밖에 LaunchedEffect도 없어도 될 것 같네요

}


Column {
samples.forEachIndexed { index, switchPreviewParameter ->
Text(text = "Size:${switchPreviewParameter.switchSize} State:${switchPreviewParameter.switchState}")
Switch(
switchState = switchPreviewParameter.switchState,
switchSize = switchPreviewParameter.switchSize,
onToggle = {
samples[index] = samples[index].copy(switchState = it)
}
)
}
}
}
197 changes: 197 additions & 0 deletions compose/src/main/kotlin/com/yourssu/handy/compose/Switch.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package com.yourssu.handy.compose

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
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.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* Switch의 크기를 나타내는 Sealed Class입니다.
* [SwitchSize.Large], [SwitchSize.Medium], [SwitchSize.Small] 중 하나를 가질 수 있습니다.
*/
sealed class SwitchSize {
data object Large : SwitchSize()
data object Medium : SwitchSize()
data object Small : SwitchSize()
}

/**
* Switch의 상태를 나타내는 Sealed Class입니다.
* [SwitchState.Unselected], [SwitchState.Selected], [SwitchState.Disabled] 중 하나를 가질 수 있습니다.
*/
sealed class SwitchState {
data object Unselected : SwitchState()
data object Selected : SwitchState()
data object Disabled : SwitchState()
}

/**
* Switch를 그리는 함수입니다.
*
* 스위치는 특정 기능을 활성 또는 비활성의 상태로 만들 수 있도록 도와주는 요소입니다.
* 내부적으로 Track과 Thumb으로 구성되어 있습니다.
*
* @param switchState Switch의 상태. [SwitchState.Unselected], [SwitchState.Selected], [SwitchState.Disabled] 중 하나를 가질 수 있습니다.
* @param onToggle Switch의 상태를 변경하는 함수. [SwitchState]를 인자로 가집니다.
* @param switchSize Switch의 크기. [SwitchSize.Large], [SwitchSize.Medium], [SwitchSize.Small] 중 하나를 가질 수 있습니다.
* @param modifier Modifier
*/
@Composable
fun Switch(
switchState: SwitchState = SwitchState.Unselected,
onToggle: (SwitchState) -> Unit,
switchSize: SwitchSize = SwitchSize.Large,
modifier: Modifier = Modifier
) {
Comment on lines +59 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun Switch(
switchState: SwitchState = SwitchState.Unselected,
onToggle: (SwitchState) -> Unit,
switchSize: SwitchSize = SwitchSize.Large,
modifier: Modifier = Modifier
) {
fun Switch(
onToggle: (SwitchState) -> Unit,
modifier: Modifier = Modifier,
switchState: SwitchState = SwitchState.Unselected,
switchSize: SwitchSize = SwitchSize.Large,
) {

순서 변경해주세요!

val switchTrackWidth = remember(switchSize) { switchTrackWidth(switchSize) }
val switchTrackHeight = remember(switchSize) { switchTrackHeight(switchSize) }
val switchThumbSize = remember(switchSize) { switchThumbSize(switchSize) }
val switchPadding = remember(switchSize) { switchPadding(switchSize) }

// Switch의 Track의 색상 변경을 에니메이션하기 위한 상태입니다.
val trackColor: Color by animateColorAsState(switchTrackColor(switchState))

val easeIn = SwitchAnimationEasing
val easeOut = SwitchAnimationEasing

// Trainstion을 사용하면 Switch의 상태가 변경될 때 애니메이션을 적용할 수 있습니다.
val transition = updateTransition(targetState = switchState, label = "SwitchStateTransition")

// Switch의 Thumb의 Offset을 에니메이션 하기위한 상태입니다.
val thumbOffset: Dp by transition.animateDp(
transitionSpec = {
// Switch의 상태가 변경될 때 애니메이션을 적용합니다.
// easeIn: Unselected -> Selected, easeOut: Selected -> Unselected
when {
SwitchState.Selected isTransitioningTo SwitchState.Unselected ->
tween(durationMillis = SwitchAnimationDuration, easing = easeOut)

else ->
tween(durationMillis = SwitchAnimationDuration, easing = easeIn)
}
},
label = "ThumbOffset"
) { state ->
if (state == SwitchState.Selected) {
// Switch의 Thumb이 Track의 오른쪽 끝에 위치하도록 하는 Offset입니다.
switchTrackWidth - switchThumbSize - switchPadding
} else {
// Switch의 Thumb이 Track의 왼쪽 끝에 위치하도록 하는 Offset입니다.
switchPadding
}
}

Surface(
checked = switchState == SwitchState.Selected,
onCheckedChange = { onToggle(if (switchState == SwitchState.Selected) SwitchState.Unselected else SwitchState.Selected) },
modifier = modifier
.padding(switchPadding)
.width(switchTrackWidth)
.height(switchTrackHeight),
enabled = switchState != SwitchState.Disabled,
shape = RoundedCornerShape(size = SwitchTrackRoundedCorner.dp),
backgroundColor = trackColor,
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.CenterStart) {
// Offset을 사용하여 Switch의 Thumb을 이동시킵니다.
SwitchThumb(
switchSize = switchSize,
modifier = Modifier
.offset(x = thumbOffset)
)
}
}
}

// Switch의 상태가 변경될 때 애니메이션을 적용하기 위한 상수입니다.(ms)
private const val SwitchAnimationDuration = 150

// Switch의 Track의 모서리를 둥글게 만들기 위한 상수입니다.
private const val SwitchTrackRoundedCorner = 999

Comment on lines +128 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

둥근 모서리를 999 주는 거랑 CircleShape로 그냥 둥글게 만드는 거와의 차이가 있을까요...?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아이고 너무 늦게 봤네요.. figma에 나와있는 값을 그대로 사용했는데 CircleShape랑 어떻게 다른지 모르겠네요. 알아보겠습니다.

// Switch의 애니메이션 Easing입니다.
private val SwitchAnimationEasing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1f)

/**
* Switch의 Thumb을 그리는 함수입니다.
* @param switchSize Switch의 크기
*/
@Composable
private fun SwitchThumb(switchSize: SwitchSize, modifier: Modifier) {
Surface(
modifier = modifier
.clip(CircleShape)
.size(switchThumbSize(switchSize)),
contentColor = HandyTheme.colors.switchThumb,
) {}
Comment on lines +140 to +145
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Box로 해도 괜찮지 않을까요?

}

/**
* Switch의 Track의 색상을 반환하는 함수입니다.
* @param switchState Switch의 상태
*/
@Composable
private fun switchTrackColor(switchState: SwitchState): Color = when (switchState) {
SwitchState.Unselected -> HandyTheme.colors.switchUnselected
SwitchState.Selected -> HandyTheme.colors.switchSelected
SwitchState.Disabled -> HandyTheme.colors.switchDisabled
}

/**
* Switch의 Track의 높이를 반환하는 함수입니다.
* @param switchSize Switch의 크기
*/
private fun switchTrackHeight(switchSize: SwitchSize): Dp = when (switchSize) {
SwitchSize.Large -> 30.dp
SwitchSize.Medium -> 20.dp
SwitchSize.Small -> 16.dp
}

/**
* Switch의 Track의 너비를 반환하는 함수입니다.
* @param switchSize Switch의 크기
*/
private fun switchTrackWidth(switchSize: SwitchSize): Dp = when (switchSize) {
SwitchSize.Large -> 48.dp
SwitchSize.Medium -> 32.dp
SwitchSize.Small -> 24.dp
}

/**
* Switch의 Padding을 반환하는 함수입니다.
* @param switchSize Switch의 크기
*/
fun switchPadding(switchSize: SwitchSize): Dp = when (switchSize) {
SwitchSize.Large -> 2.5.dp
SwitchSize.Medium -> 2.dp
SwitchSize.Small -> 1.5.dp
}

/**
* Switch의 Thumb의 크기를 반환하는 함수입니다.
* @param switchSize Switch의 크기
*/
private fun switchThumbSize(switchSize: SwitchSize): Dp = when (switchSize) {
SwitchSize.Large -> 25.dp
SwitchSize.Medium -> 16.dp
SwitchSize.Small -> 13.dp
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ data class ColorScheme(

// Pagination / Basic
val paginationBasicSelected: Color = ColorNeutralBlack,
val paginationBasicUnselected: Color = ColorGray500
val paginationBasicUnselected: Color = ColorGray500,

// Switch
val switchUnselected: Color = ColorGray300,
val switchSelected: Color = ColorViolet500,
val switchDisabled: Color = ColorGray200,
val switchThumb: Color = ColorNeutralWhite,
)

val lightColorScheme = ColorScheme()
Expand Down