From 07ab728ae4bed8b70fff8f5e47314a33fba72e88 Mon Sep 17 00:00:00 2001
From: Gael-Android <84790707+Gael-Android@users.noreply.github.com>
Date: Sun, 19 Jan 2025 19:59:27 +0900
Subject: [PATCH] =?UTF-8?q?Switch=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?=
=?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#13)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* add : 스위치를 위한 기본 색상 추가
* add : 스위치 제작
* add : 스위치 프리뷰 추가
* chore : 스위치 에니메이션 변경
* chore : Preview 위치 변경
* chore : 스위치 주석 추가 및 포맷 통일
* chore : 주석추가
* chore : Preview 굳이 프리뷰파라미터로 만들 필요 없을거 같아서 Column으로 나타냄
* chore : figma 설정에 roundedCorner에 대한 dp 값이 없어져서 CIrcleShape로 변경함
* chore : SwitchPreview 업데이트. 간단하게 flatmap을 사용하는 방식으로
* chore : Switch Composable 파라미터 순서 변경: 필수 파라미터, modifier, 옵셔널 파라미터 순으로
* chore : SwitchTHumb에 대해서 Surface를 Box로 변경. 머티리얼 디자인을 따르지 않기 떄문
* chore : 불필요하게 상태와 ui값을 매핑해주는 함수 제거. sealed class를 전부 enum class로 변경
---
.idea/deploymentTargetSelector.xml | 4 +-
.../com/yourssu/handy/demo/SwitchPreview.kt | 40 +++++
.../com/yourssu/handy/compose/Switch.kt | 151 ++++++++++++++++++
.../compose/foundation/SemanticColors.kt | 8 +-
4 files changed, 200 insertions(+), 3 deletions(-)
create mode 100644 app/src/main/kotlin/com/yourssu/handy/demo/SwitchPreview.kt
create mode 100644 compose/src/main/kotlin/com/yourssu/handy/compose/Switch.kt
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 5394540f..48ea7696 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -5,10 +5,10 @@
-
+
-
+
diff --git a/app/src/main/kotlin/com/yourssu/handy/demo/SwitchPreview.kt b/app/src/main/kotlin/com/yourssu/handy/demo/SwitchPreview.kt
new file mode 100644
index 00000000..8fdb0209
--- /dev/null
+++ b/app/src/main/kotlin/com/yourssu/handy/demo/SwitchPreview.kt
@@ -0,0 +1,40 @@
+package com.yourssu.handy.demo
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+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 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) }
+ }
+
+
+ Column {
+ samples.forEachIndexed { index, switchPreviewParameter ->
+ Text(text = "Size:${switchPreviewParameter.switchSize} State:${switchPreviewParameter.switchState}")
+ Switch(
+ switchState = switchPreviewParameter.switchState,
+ switchSize = switchPreviewParameter.switchSize,
+ onToggle = {
+
+ }
+ )
+ }
+ }
+}
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/Switch.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/Switch.kt
new file mode 100644
index 00000000..002ead90
--- /dev/null
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/Switch.kt
@@ -0,0 +1,151 @@
+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.background
+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.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.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+
+enum class SwitchSize(
+ val trackWidth: Dp,
+ val trackHeight: Dp,
+ val padding: Dp,
+ val thumbSize: Dp,
+) {
+ Large(48.dp, 30.dp, 2.5.dp, 25.dp),
+ Medium(32.dp, 20.dp, 2.dp, 16.dp),
+ Small(24.dp, 16.dp, 1.5.dp, 13.dp),
+}
+
+enum class SwitchState {
+ Unselected,
+ Selected,
+ Disabled,
+}
+
+/**
+ * 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(
+ onToggle: (SwitchState) -> Unit,
+ modifier: Modifier = Modifier,
+ switchState: SwitchState = SwitchState.Unselected,
+ switchSize: SwitchSize = SwitchSize.Large
+) {
+ val trackWidth = switchSize.trackWidth
+ val trackHeight = switchSize.trackHeight
+ val trackPadding = switchSize.padding
+ val thumbSize = switchSize.thumbSize
+
+ // 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입니다.
+ trackWidth - thumbSize - trackPadding
+ } else {
+ // Switch의 Thumb이 Track의 왼쪽 끝에 위치하도록 하는 Offset입니다.
+ trackPadding
+ }
+ }
+
+ Surface(
+ checked = switchState == SwitchState.Selected,
+ onCheckedChange = { onToggle(if (switchState == SwitchState.Selected) SwitchState.Unselected else SwitchState.Selected) },
+ modifier = modifier
+ .padding(trackPadding)
+ .width(trackWidth)
+ .height(trackHeight),
+ enabled = switchState != SwitchState.Disabled,
+ shape = CircleShape,
+ 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의 애니메이션 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) {
+ Box(
+ modifier = modifier
+ .background(HandyTheme.colors.switchThumb, CircleShape)
+ .clip(CircleShape)
+ .size(switchSize.thumbSize),
+ )
+}
+
+/**
+ * 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
+}
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt
index e04a0bed..7b2ce281 100644
--- a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt
@@ -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()