From 298f689459296965f4cd3f84fe42213df9d9239e Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 20 Sep 2024 02:50:35 +0900 Subject: [PATCH 01/25] feat : make snackbar file --- .idea/inspectionProfiles/Project_Default.xml | 24 ++ .idea/other.xml | 318 ------------------ .../com/yourssu/handy/compose/SnackBar.kt | 11 + 3 files changed, 35 insertions(+), 318 deletions(-) delete mode 100644 .idea/other.xml create mode 100644 compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 44ca2d9..b67486e 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,30 @@ diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 4804e49..0d72954 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -58,21 +58,21 @@ fun InfoSnackBarItem( @Composable fun InfoSnackBar( text: String, - delay: Long = DURATION, - onDismiss: () -> Unit + onDismiss: () -> Unit, + duration: Long = SNACK_BAR_DURATION, ) { var visible by remember { mutableStateOf(false) } LaunchedEffect(Unit) { visible = true - delay(delay) + delay(duration) visible = false delay(FADE_OUT_DURATION) onDismiss() } Popup( - alignment = Alignment.BottomCenter + alignment = Alignment.BottomCenter, ) { AnimatedVisibility( visible = visible, @@ -83,10 +83,8 @@ fun InfoSnackBar( ), exit = fadeOut( animationSpec = tween(durationMillis = 300) - ) + shrinkVertically( - shrinkTowards = Alignment.Bottom ) + slideOutVertically( - targetOffsetY = { fullHeight -> fullHeight } + targetOffsetY = { fullHeight -> fullHeight }, ) ) { InfoSnackBarItem( @@ -99,8 +97,8 @@ fun InfoSnackBar( @Composable fun ErrorSnackBarItem( text: String, + onClick: () -> Unit, modifier: Modifier = Modifier, - onClick: () -> Unit ) { Row( modifier = modifier @@ -161,10 +159,9 @@ fun ErrorSnackBar( ) + expandVertically( expandFrom = Alignment.Top ), - exit = fadeOut( + exit = shrinkVertically( + shrinkTowards = Alignment.Bottom, animationSpec = tween(durationMillis = 300) - ) + shrinkVertically( - shrinkTowards = Alignment.Bottom ) + slideOutVertically( targetOffsetY = { fullHeight -> fullHeight } ) @@ -177,5 +174,5 @@ fun ErrorSnackBar( } } -private const val DURATION = 5000L +private const val SNACK_BAR_DURATION = 5000L private const val FADE_OUT_DURATION = 300L \ No newline at end of file From dc8b1435ba59e28b1dcc0b7084fee973389ca9e5 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Sun, 22 Sep 2024 11:39:56 +0900 Subject: [PATCH 09/25] =?UTF-8?q?feat=20:=20=EC=A0=95=EB=B3=B4=EC=84=B1=20?= =?UTF-8?q?=EC=8A=A4=EB=82=B5=EB=B0=94=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/yourssu/handy/compose/SnackBar.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 0d72954..7003619 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -33,6 +33,12 @@ import com.yourssu.handy.compose.foundation.ColorStatusRedSub import com.yourssu.handy.compose.foundation.HandyTypography import kotlinx.coroutines.delay +/** + * 정보성 스낵바의 UI를 그린 함수입니다. + * + * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 + * @param modifier Modifier + */ @Composable fun InfoSnackBarItem( text: String, @@ -55,6 +61,16 @@ fun InfoSnackBarItem( } } +/** + * 정보성 스낵바를 구현한 함수입니다. + * + * 유저의 행동에 대한 단순 결과를 나타낼 때 사용합니다. + * 특정 시간 노출 후에 사라집니다. + * + * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 + * @param onDismiss 스낵바가 사라질 때 호출되는 함수 + * @param duration 스낵바가 지속되어 있는 시간 (기본값은 5초) + */ @Composable fun InfoSnackBar( text: String, From ccd35a84f78f4cae7e8de4d1690a2ba5be65ab8c Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Mon, 23 Sep 2024 12:35:08 +0900 Subject: [PATCH 10/25] =?UTF-8?q?feat=20:=20=EC=97=90=EB=9F=AC=20=EC=8A=A4?= =?UTF-8?q?=EB=82=B5=EB=B0=94=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/SnackBar.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 7003619..40496f9 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -110,6 +110,13 @@ fun InfoSnackBar( } } +/** + * 에러 스낵바의 UI를 그린 함수입니다. + * + * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 + * @param onClick 스낵바의 X 버튼을 눌렀을 때 호출되는 함수 + * @param modifier Modifier + */ @Composable fun ErrorSnackBarItem( text: String, @@ -147,6 +154,17 @@ fun ErrorSnackBarItem( } } +/** + * 에러 스낵바를 구현한 함수입니다. + * + * 사용자의 수행 과정에 부정적인 결과가 발생하거나 + * 정보성 스낵바보다 강조해야 할 메시지를 담아야 할 때 사용합니다. + * + * X 버튼을 눌러야만 사라집니다. + * + * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 + * @param onDismiss 스낵바가 사라질 때 호출되는 함수 + */ @Composable fun ErrorSnackBar( text: String, From a7c3700f7d00a6283d3db152058950732b16d44f Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Mon, 23 Sep 2024 12:39:30 +0900 Subject: [PATCH 11/25] =?UTF-8?q?feat=20:=20=EC=97=90=EB=9F=AC=20=EC=8A=A4?= =?UTF-8?q?=EB=82=B5=EB=B0=94=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 40496f9..d0e09e1 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -135,7 +135,8 @@ fun ErrorSnackBarItem( ) { Icon( painter = painterResource(id = R.drawable.ic_alert_triangle), - tint = HandyTheme.colors.bgStatusNegative + tint = HandyTheme.colors.bgStatusNegative, + modifier = Modifier.align(Alignment.Top) ) Text( text = text, From e65c0638746a205129ec1a61fc3fea1a9c470b46 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Mon, 23 Sep 2024 14:06:43 +0900 Subject: [PATCH 12/25] =?UTF-8?q?feat=20:=20=EC=8A=A4=EB=82=B5=EB=B0=94=20?= =?UTF-8?q?=EC=A7=80=EC=86=8D=EC=8B=9C=EA=B0=84=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/yourssu/handy/compose/SnackBar.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index d0e09e1..a10141e 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -83,7 +83,7 @@ fun InfoSnackBar( visible = true delay(duration) visible = false - delay(FADE_OUT_DURATION) + delay(FADE_OUT_DELAY) onDismiss() } @@ -93,12 +93,12 @@ fun InfoSnackBar( AnimatedVisibility( visible = visible, enter = fadeIn( - animationSpec = tween(durationMillis = 500) + animationSpec = tween(durationMillis = FADE_IN_DURATION) ) + expandVertically( expandFrom = Alignment.Top ), exit = fadeOut( - animationSpec = tween(durationMillis = 300) + animationSpec = tween(durationMillis = FADE_OUT_DURATION) ) + slideOutVertically( targetOffsetY = { fullHeight -> fullHeight }, ) @@ -179,7 +179,7 @@ fun ErrorSnackBar( LaunchedEffect(visible) { if (!visible) { - delay(FADE_OUT_DURATION) + delay(FADE_OUT_DELAY) onDismiss() } } @@ -190,13 +190,13 @@ fun ErrorSnackBar( AnimatedVisibility( visible = visible, enter = fadeIn( - animationSpec = tween(durationMillis = 500) + animationSpec = tween(durationMillis = FADE_IN_DURATION) ) + expandVertically( expandFrom = Alignment.Top ), exit = shrinkVertically( shrinkTowards = Alignment.Bottom, - animationSpec = tween(durationMillis = 300) + animationSpec = tween(durationMillis = FADE_OUT_DURATION) ) + slideOutVertically( targetOffsetY = { fullHeight -> fullHeight } ) @@ -210,4 +210,6 @@ fun ErrorSnackBar( } private const val SNACK_BAR_DURATION = 5000L -private const val FADE_OUT_DURATION = 300L \ No newline at end of file +private const val FADE_OUT_DELAY = 300L +private const val FADE_IN_DURATION = 500 +private const val FADE_OUT_DURATION = 300 From 8f96b18350b7b15e509efdc411bea69621e29891 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 27 Sep 2024 14:29:17 +0900 Subject: [PATCH 13/25] =?UTF-8?q?fix=20:=20=ED=95=B8=EB=94=94=20=EC=8A=A4?= =?UTF-8?q?=EB=82=B5=EB=B0=94=20=EC=BB=AC=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt | 4 ++-- .../yourssu/handy/compose/foundation/SemanticColors.kt | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index a10141e..93f6023 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -49,7 +49,7 @@ fun InfoSnackBarItem( .padding(horizontal = 16.dp) .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) - .background(ColorGray800) + .background(HandyTheme.colors.snackBarInfo) .padding(16.dp) ) { Text( @@ -128,7 +128,7 @@ fun ErrorSnackBarItem( .padding(horizontal = 16.dp) .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) - .background(ColorStatusRedSub) + .background(HandyTheme.colors.snackBarError) .padding(16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically 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 d668f4f..e0d3bd9 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, + + + // SnackBar + val snackBarInfo: Color = ColorGray800, + val snackBarError: Color = ColorStatusRedSub + ) val lightColorScheme = ColorScheme() From 3955b5dbbbf65a24e1fed21fd6c60aad9be98e6d Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Thu, 3 Oct 2024 09:45:32 +0900 Subject: [PATCH 14/25] fix : modify to Handy Icon --- .../main/kotlin/com/yourssu/handy/compose/SnackBar.kt | 10 +++++----- compose/src/main/res/drawable/ic_alert_triangle.xml | 10 ---------- compose/src/main/res/drawable/ic_cancel.xml | 9 --------- 3 files changed, 5 insertions(+), 24 deletions(-) delete mode 100644 compose/src/main/res/drawable/ic_alert_triangle.xml delete mode 100644 compose/src/main/res/drawable/ic_cancel.xml diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 93f6023..e3da93b 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -25,12 +25,12 @@ 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.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup -import com.yourssu.handy.compose.foundation.ColorGray800 -import com.yourssu.handy.compose.foundation.ColorStatusRedSub import com.yourssu.handy.compose.foundation.HandyTypography +import com.yourssu.handy.compose.icons.HandyIcons +import com.yourssu.handy.compose.icons.filled.AlertTriangle +import com.yourssu.handy.compose.icons.line.Close import kotlinx.coroutines.delay /** @@ -134,7 +134,7 @@ fun ErrorSnackBarItem( verticalAlignment = Alignment.CenterVertically ) { Icon( - painter = painterResource(id = R.drawable.ic_alert_triangle), + imageVector = HandyIcons.Filled.AlertTriangle, tint = HandyTheme.colors.bgStatusNegative, modifier = Modifier.align(Alignment.Top) ) @@ -146,7 +146,7 @@ fun ErrorSnackBarItem( ) Spacer(modifier = Modifier.weight(1f)) Icon( - painter = painterResource(id = R.drawable.ic_cancel), + imageVector = HandyIcons.Line.Close, tint = HandyTheme.colors.textBasicTertiary, modifier = Modifier .clickable(onClick = onClick) diff --git a/compose/src/main/res/drawable/ic_alert_triangle.xml b/compose/src/main/res/drawable/ic_alert_triangle.xml deleted file mode 100644 index ca08709..0000000 --- a/compose/src/main/res/drawable/ic_alert_triangle.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/compose/src/main/res/drawable/ic_cancel.xml b/compose/src/main/res/drawable/ic_cancel.xml deleted file mode 100644 index 724ce1b..0000000 --- a/compose/src/main/res/drawable/ic_cancel.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - From 7ab14c56acb2da181eb3ace8ebea15ca3f44d0f8 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 4 Oct 2024 03:35:48 +0900 Subject: [PATCH 15/25] refactor : refactor snackBar code --- .../com/yourssu/handy/demo/SnackBarPreview.kt | 12 +- .../com/yourssu/handy/compose/SnackBar.kt | 139 ++---------------- .../handy/compose/SnackBarAnimation.kt | 138 +++++++++++++++++ .../com/yourssu/handy/compose/SnackData.kt | 87 +++++++++++ 4 files changed, 247 insertions(+), 129 deletions(-) create mode 100644 compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt create mode 100644 compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt diff --git a/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt b/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt index 383450f..c02b395 100644 --- a/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt +++ b/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt @@ -8,9 +8,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.yourssu.handy.compose.ErrorSnackBarItem +import com.yourssu.handy.compose.ErrorSnackBar import com.yourssu.handy.compose.HandyTheme -import com.yourssu.handy.compose.InfoSnackBarItem +import com.yourssu.handy.compose.InfoSnackBar @Preview(showBackground = true) @Composable @@ -19,11 +19,11 @@ fun InfoSnackBarPreview() { Column( modifier = Modifier.padding(10.dp) ) { - InfoSnackBarItem( + InfoSnackBar( text = "한 줄짜리 정보성 메세지가 들어갑니다." ) Spacer(modifier = Modifier.height(10.dp)) - InfoSnackBarItem( + InfoSnackBar( text = "줄 수가 두 줄 이상이 되는 스낵바 메시지입니다.\n좌측 정렬을 해주세요." ) } @@ -37,12 +37,12 @@ fun ErrorSnackBarPreview() { Column( modifier = Modifier.padding(10.dp) ) { - ErrorSnackBarItem( + ErrorSnackBar( text = "에러 메세지가 들어갑니다", onClick = {} ) Spacer(modifier = Modifier.height(10.dp)) - ErrorSnackBarItem( + ErrorSnackBar( text = "두 줄 이상의 에러 메세지가 들어갈 경우\n 아이콘은 모두 위로 정렬해주세요.", onClick = {} ) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index e3da93b..6f854f9 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -1,12 +1,5 @@ package com.yourssu.handy.compose -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -17,30 +10,26 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -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.unit.dp -import androidx.compose.ui.window.Popup import com.yourssu.handy.compose.foundation.HandyTypography import com.yourssu.handy.compose.icons.HandyIcons import com.yourssu.handy.compose.icons.filled.AlertTriangle import com.yourssu.handy.compose.icons.line.Close -import kotlinx.coroutines.delay /** * 정보성 스낵바의 UI를 그린 함수입니다. * + * 유저의 행동에 대한 단순 결과를 나타낼 때 사용합니다. + * 특정 시간 노출 후에 사라집니다. + * * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 * @param modifier Modifier */ @Composable -fun InfoSnackBarItem( +fun InfoSnackBar( text: String, modifier: Modifier = Modifier, ) { @@ -62,63 +51,19 @@ fun InfoSnackBarItem( } /** - * 정보성 스낵바를 구현한 함수입니다. + * 에러 스낵바의 UI를 그린 함수입니다. * - * 유저의 행동에 대한 단순 결과를 나타낼 때 사용합니다. - * 특정 시간 노출 후에 사라집니다. + * 사용자의 수행 과정에 부정적인 결과가 발생하거나 + * 정보성 스낵바보다 강조해야 할 메시지를 담아야 할 때 사용합니다. * - * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 - * @param onDismiss 스낵바가 사라질 때 호출되는 함수 - * @param duration 스낵바가 지속되어 있는 시간 (기본값은 5초) - */ -@Composable -fun InfoSnackBar( - text: String, - onDismiss: () -> Unit, - duration: Long = SNACK_BAR_DURATION, -) { - var visible by remember { mutableStateOf(false) } - - LaunchedEffect(Unit) { - visible = true - delay(duration) - visible = false - delay(FADE_OUT_DELAY) - onDismiss() - } - - Popup( - alignment = Alignment.BottomCenter, - ) { - AnimatedVisibility( - visible = visible, - enter = fadeIn( - animationSpec = tween(durationMillis = FADE_IN_DURATION) - ) + expandVertically( - expandFrom = Alignment.Top - ), - exit = fadeOut( - animationSpec = tween(durationMillis = FADE_OUT_DURATION) - ) + slideOutVertically( - targetOffsetY = { fullHeight -> fullHeight }, - ) - ) { - InfoSnackBarItem( - text = text, - ) - } - } -} - -/** - * 에러 스낵바의 UI를 그린 함수입니다. + * X 버튼을 눌러야만 사라집니다. * * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 * @param onClick 스낵바의 X 버튼을 눌렀을 때 호출되는 함수 * @param modifier Modifier */ @Composable -fun ErrorSnackBarItem( +fun ErrorSnackBar( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, @@ -146,7 +91,7 @@ fun ErrorSnackBarItem( ) Spacer(modifier = Modifier.weight(1f)) Icon( - imageVector = HandyIcons.Line.Close, + imageVector = HandyIcons.Line.Close, tint = HandyTheme.colors.textBasicTertiary, modifier = Modifier .clickable(onClick = onClick) @@ -155,61 +100,9 @@ fun ErrorSnackBarItem( } } -/** - * 에러 스낵바를 구현한 함수입니다. - * - * 사용자의 수행 과정에 부정적인 결과가 발생하거나 - * 정보성 스낵바보다 강조해야 할 메시지를 담아야 할 때 사용합니다. - * - * X 버튼을 눌러야만 사라집니다. - * - * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 - * @param onDismiss 스낵바가 사라질 때 호출되는 함수 - */ -@Composable -fun ErrorSnackBar( - text: String, - onDismiss: () -> Unit -) { - var visible by remember { mutableStateOf(false) } - - LaunchedEffect(Unit) { - visible = true - } - - LaunchedEffect(visible) { - if (!visible) { - delay(FADE_OUT_DELAY) - onDismiss() - } - } - - Popup( - alignment = Alignment.BottomCenter - ) { - AnimatedVisibility( - visible = visible, - enter = fadeIn( - animationSpec = tween(durationMillis = FADE_IN_DURATION) - ) + expandVertically( - expandFrom = Alignment.Top - ), - exit = shrinkVertically( - shrinkTowards = Alignment.Bottom, - animationSpec = tween(durationMillis = FADE_OUT_DURATION) - ) + slideOutVertically( - targetOffsetY = { fullHeight -> fullHeight } - ) - ) { - ErrorSnackBarItem( - text = text, - onClick = { visible = false } - ) - } - } -} - -private const val SNACK_BAR_DURATION = 5000L -private const val FADE_OUT_DELAY = 300L -private const val FADE_IN_DURATION = 500 -private const val FADE_OUT_DURATION = 300 +object SnackBarDefaults { + const val SNACK_BAR_DURATION = 5000L + const val FADE_IN_DURATION = 500 + const val FADE_OUT_DURATION = 300 + const val TARGET_VALUE = -16f +} \ No newline at end of file diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt new file mode 100644 index 0000000..8a0ab91 --- /dev/null +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt @@ -0,0 +1,138 @@ +package com.yourssu.handy.compose + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.EaseOut +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.RecomposeScope +import androidx.compose.runtime.State +import androidx.compose.runtime.currentRecomposeScope +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.unit.dp +import com.yourssu.handy.compose.SnackBarDefaults.FADE_IN_DURATION +import com.yourssu.handy.compose.SnackBarDefaults.FADE_OUT_DURATION +import com.yourssu.handy.compose.SnackBarDefaults.TARGET_VALUE + +data class SnackBarTransitionItem( + val snackBarData: SnackBarData?, + val opacityTransition: OpacityTransition +) + +typealias OpacityTransition = @Composable (snackBar: @Composable () -> Unit) -> Unit + +@Composable +fun FadeInFadeOut( + newSnackBarData: SnackBarData?, + modifier: Modifier = Modifier, + snackBar: @Composable (SnackBarData) -> Unit +) { + var scheduledSnackBarData by remember { mutableStateOf(null) } + val snackBarTransitions = remember { mutableListOf() } + var scope by remember { mutableStateOf(null) } + + if (newSnackBarData != scheduledSnackBarData) { + scheduledSnackBarData = newSnackBarData + + val snackBarDataList = snackBarTransitions.map { it.snackBarData }.toMutableList() + + snackBarDataList.add(newSnackBarData) + + snackBarTransitions.clear() + + snackBarDataList.filterNotNull() + .mapTo(destination = snackBarTransitions) { appearedSnackBarData -> + SnackBarTransitionItem(appearedSnackBarData) { snackBar -> + val isVisible = appearedSnackBarData == newSnackBarData + val animateInSpec: AnimationSpec = + tween(durationMillis = FADE_IN_DURATION) + val animateOutSpec: AnimationSpec = + tween(durationMillis = FADE_OUT_DURATION, easing = EaseOut) + + val opacity = animatedOpacity( + visible = isVisible, + animateInSpec = animateInSpec, + animateOutSpec = animateOutSpec + ) + + val offsetY = animatedOffset( + visible = isVisible, + animateInSpec = animateInSpec, + animateOutSpec = animateOutSpec + ) + + Box( + modifier = Modifier + .offset(y = offsetY.value.dp) + .alpha(opacity.value) + ) { + snackBar() + } + } + } + } + + Box( + modifier = modifier + ) { + scope = currentRecomposeScope + snackBarTransitions.forEach { (snackBarData, opacity) -> + key(snackBarData) { + opacity { + snackBar(snackBarData ?: return@opacity) + } + } + } + } +} + +@Composable +private fun animatedOpacity( + visible: Boolean, + animateInSpec: AnimationSpec, + animateOutSpec: AnimationSpec, +): State { + val alpha = remember { Animatable(0f) } + + LaunchedEffect(visible) { + alpha.animateTo( + if (visible) 1f else 0f, + animationSpec = if (visible) animateInSpec else animateOutSpec + ) + } + return alpha.asState() +} + +@Composable +private fun animatedOffset( + visible: Boolean, + animateInSpec: AnimationSpec, + animateOutSpec: AnimationSpec, +): State { + val offsetY = remember { Animatable(0f) } + + LaunchedEffect(visible) { + if (visible) { + offsetY.animateTo( + targetValue = TARGET_VALUE, + animationSpec = animateInSpec + ) + } else { + offsetY.animateTo( + targetValue = 0f, + animationSpec = animateOutSpec + ) + } + } + + return offsetY.asState() +} diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt new file mode 100644 index 0000000..38644f6 --- /dev/null +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt @@ -0,0 +1,87 @@ +package com.yourssu.handy.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +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 com.yourssu.handy.compose.SnackBarDefaults.SNACK_BAR_DURATION +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.delay +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.coroutines.resume + +interface SnackBarData { + val message: String + + fun dismiss() +} + +enum class SnackBarResult { + Dismissed, +} + +@Stable +class SnackBarHostState { + private val mutex = Mutex() + + internal var currentSnackBarData by mutableStateOf(null) + private set + + suspend fun showToast( + message: String, + ): SnackBarResult = mutex.withLock { + try { + return suspendCancellableCoroutine { continuation -> + currentSnackBarData = SnackBarDataImpl(message, continuation) + } + } finally { + currentSnackBarData = null + } + } +} + +@Composable +fun rememberSnackBarHostState(): SnackBarHostState = remember { SnackBarHostState() } + +@Stable +private class SnackBarDataImpl( + override val message: String, + private val continuation: CancellableContinuation +) : SnackBarData { + + override fun dismiss() { + if (continuation.isActive) { + continuation.resume(SnackBarResult.Dismissed) + } + } +} + +@Composable +fun SnackBarHost( + snackBarHostState: SnackBarHostState, + modifier: Modifier = Modifier, + snackBar: @Composable (SnackBarData) -> Unit = { + InfoSnackBar( + text = snackBarHostState.currentSnackBarData?.message ?: "" + ) + } +) { + val currentSnackBarData = snackBarHostState.currentSnackBarData + LaunchedEffect(currentSnackBarData) { + if (currentSnackBarData != null) { + delay(SNACK_BAR_DURATION) + currentSnackBarData.dismiss() + } + } + FadeInFadeOut( + newSnackBarData = snackBarHostState.currentSnackBarData, + modifier = modifier, + snackBar = snackBar + ) +} From 90f60c933cdef0741e416f8dc052715999bfdfe5 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 30 Oct 2024 22:56:12 +0900 Subject: [PATCH 16/25] feat : add error SnackBar type --- .../com/yourssu/handy/compose/SnackData.kt | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt index 38644f6..5ed8e89 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt @@ -18,6 +18,7 @@ import kotlin.coroutines.resume interface SnackBarData { val message: String + val type: SnackBarType fun dismiss() } @@ -26,6 +27,11 @@ enum class SnackBarResult { Dismissed, } +enum class SnackBarType { + Info, + Error, +} + @Stable class SnackBarHostState { private val mutex = Mutex() @@ -33,12 +39,17 @@ class SnackBarHostState { internal var currentSnackBarData by mutableStateOf(null) private set - suspend fun showToast( + suspend fun showSnackBar( message: String, + type: SnackBarType ): SnackBarResult = mutex.withLock { try { return suspendCancellableCoroutine { continuation -> - currentSnackBarData = SnackBarDataImpl(message, continuation) + currentSnackBarData = SnackBarDataImpl( + message = message, + type = type, + continuation = continuation + ) } } finally { currentSnackBarData = null @@ -52,6 +63,7 @@ fun rememberSnackBarHostState(): SnackBarHostState = remember { SnackBarHostStat @Stable private class SnackBarDataImpl( override val message: String, + override val type: SnackBarType, private val continuation: CancellableContinuation ) : SnackBarData { @@ -66,19 +78,28 @@ private class SnackBarDataImpl( fun SnackBarHost( snackBarHostState: SnackBarHostState, modifier: Modifier = Modifier, - snackBar: @Composable (SnackBarData) -> Unit = { - InfoSnackBar( - text = snackBarHostState.currentSnackBarData?.message ?: "" - ) - } + snackBar: @Composable (SnackBarData) -> Unit = { snackBarData -> + when (snackBarData.type) { + SnackBarType.Info -> InfoSnackBar( + text = snackBarHostState.currentSnackBarData?.message.orEmpty() + ) + + SnackBarType.Error -> ErrorSnackBar( + text = snackBarHostState.currentSnackBarData?.message.orEmpty(), + onClick = snackBarData::dismiss + ) + } + }, ) { val currentSnackBarData = snackBarHostState.currentSnackBarData - LaunchedEffect(currentSnackBarData) { - if (currentSnackBarData != null) { + + if (currentSnackBarData?.type == SnackBarType.Info) { + LaunchedEffect(currentSnackBarData) { delay(SNACK_BAR_DURATION) currentSnackBarData.dismiss() } } + FadeInFadeOut( newSnackBarData = snackBarHostState.currentSnackBarData, modifier = modifier, From b342e266b648582654f26242dbd1a03ed306c357 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 30 Oct 2024 23:30:51 +0900 Subject: [PATCH 17/25] =?UTF-8?q?feat=20:=20currentSnackBarData=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt index 5ed8e89..01688d3 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt @@ -101,7 +101,7 @@ fun SnackBarHost( } FadeInFadeOut( - newSnackBarData = snackBarHostState.currentSnackBarData, + newSnackBarData = currentSnackBarData, modifier = modifier, snackBar = snackBar ) From 3b5b99686d1a342cecb4b0e239e0b2b9c23e0be6 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Thu, 31 Oct 2024 10:47:02 +0900 Subject: [PATCH 18/25] =?UTF-8?q?feat=20:=20=EC=8A=A4=EC=99=80=EC=9D=B4?= =?UTF-8?q?=ED=94=84=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/demo/SnackBarPreview.kt | 6 ++- .../com/yourssu/handy/compose/SnackBar.kt | 49 +++++++++++++++++++ .../com/yourssu/handy/compose/SnackData.kt | 3 +- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt b/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt index c02b395..3b89559 100644 --- a/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt +++ b/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt @@ -20,11 +20,13 @@ fun InfoSnackBarPreview() { modifier = Modifier.padding(10.dp) ) { InfoSnackBar( - text = "한 줄짜리 정보성 메세지가 들어갑니다." + text = "한 줄짜리 정보성 메세지가 들어갑니다.", + onDismiss = {} ) Spacer(modifier = Modifier.height(10.dp)) InfoSnackBar( - text = "줄 수가 두 줄 이상이 되는 스낵바 메시지입니다.\n좌측 정렬을 해주세요." + text = "줄 수가 두 줄 이상이 되는 스낵바 메시지입니다.\n좌측 정렬을 해주세요.", + onDismiss = {} ) } } diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 6f854f9..f1b2015 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -1,23 +1,39 @@ package com.yourssu.handy.compose +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.AnchoredDraggableState +import androidx.compose.foundation.gestures.DraggableAnchors +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.layout.Arrangement 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.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable +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.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.yourssu.handy.compose.foundation.HandyTypography import com.yourssu.handy.compose.icons.HandyIcons import com.yourssu.handy.compose.icons.filled.AlertTriangle import com.yourssu.handy.compose.icons.line.Close +import kotlin.math.roundToInt + +enum class DragValue { + Start, End +} /** * 정보성 스낵바의 UI를 그린 함수입니다. @@ -28,18 +44,51 @@ import com.yourssu.handy.compose.icons.line.Close * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 * @param modifier Modifier */ +@OptIn(ExperimentalFoundationApi::class) @Composable fun InfoSnackBar( text: String, + onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { + val density = LocalDensity.current + val state = remember { + AnchoredDraggableState( + initialValue = DragValue.Start, + anchors = DraggableAnchors { + with(density) { + DragValue.Start at 0f + DragValue.End at 20.dp.toPx() + } + }, + positionalThreshold = { distance: Float -> distance * 0.5f }, + velocityThreshold = { with(density) { 50.dp.toPx() } }, + animationSpec = tween(), + ) + } + val offsetY = remember { Animatable(0f) } + + if (state.currentValue == DragValue.End) { + onDismiss() + } + Column( modifier = modifier + .offset { + IntOffset( + x = 0, + y = offsetY.value.roundToInt() + ) + } .padding(horizontal = 16.dp) .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(HandyTheme.colors.snackBarInfo) .padding(16.dp) + .anchoredDraggable( + state = state, + orientation = Orientation.Vertical, + ) ) { Text( text = text, diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt index 01688d3..ccbd762 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt @@ -81,7 +81,8 @@ fun SnackBarHost( snackBar: @Composable (SnackBarData) -> Unit = { snackBarData -> when (snackBarData.type) { SnackBarType.Info -> InfoSnackBar( - text = snackBarHostState.currentSnackBarData?.message.orEmpty() + text = snackBarHostState.currentSnackBarData?.message.orEmpty(), + onDismiss = snackBarData::dismiss ) SnackBarType.Error -> ErrorSnackBar( From 5334b9a470287130d1f153b61b1d11ca38ce62b1 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 1 Nov 2024 16:57:54 +0900 Subject: [PATCH 19/25] =?UTF-8?q?feat=20:=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/SnackBar.kt | 8 ++++++- .../handy/compose/SnackBarAnimation.kt | 23 +++++++++++++++++++ .../com/yourssu/handy/compose/SnackData.kt | 8 +++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index f1b2015..b2c3245 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -31,6 +31,10 @@ import com.yourssu.handy.compose.icons.filled.AlertTriangle import com.yourssu.handy.compose.icons.line.Close import kotlin.math.roundToInt +/** + * 드래그 위치를 나타냅니다. + * Start는 초기 위치, End는 스낵바가 사라질 위치를 나타냅니다. + */ enum class DragValue { Start, End } @@ -39,7 +43,9 @@ enum class DragValue { * 정보성 스낵바의 UI를 그린 함수입니다. * * 유저의 행동에 대한 단순 결과를 나타낼 때 사용합니다. - * 특정 시간 노출 후에 사라집니다. + * + * 특정 시간(기본 5초) 노출 후에 자동으로 사라집니다. + * 아래로 스와이프 할 경우에도 사라집니다. * * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 * @param modifier Modifier diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt index 8a0ab91..085fe6a 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBarAnimation.kt @@ -30,6 +30,13 @@ data class SnackBarTransitionItem( typealias OpacityTransition = @Composable (snackBar: @Composable () -> Unit) -> Unit +/** + * 스낵바에 애니메이션을 적용합니다. + * + * @param newSnackBarData 새로운 스낵바 데이터 + * @param modifier Modifier + * @param snackBar 스낵바 UI의 컴포저블 함수 + */ @Composable fun FadeInFadeOut( newSnackBarData: SnackBarData?, @@ -95,6 +102,14 @@ fun FadeInFadeOut( } } +/** + * 투명도 애니메이션을 위한 함수입니다. + * + * @param visible 애니메이션의 시작 여부 + * @param animateInSpec 애니메이션이 시작될 때 적용되는 스펙 + * @param animateOutSpec 애니메이션이 종료될 때 적용되는 스펙 + * @return 현재 투명도 상태 + */ @Composable private fun animatedOpacity( visible: Boolean, @@ -112,6 +127,14 @@ private fun animatedOpacity( return alpha.asState() } +/** + * Y축 오프셋 애니메이션을 위한 함수입니다. + * + * @param visible 애니메이션 시작 여부 + * @param animateInSpec 애니메이션이 시작될 때 적용되는 스펙 + * @param animateOutSpec 애니메이션이 종료될 때 적용되는 스펙 + * @return 현재 오프셋 상태 + */ @Composable private fun animatedOffset( visible: Boolean, diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt index ccbd762..c64b8ab 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackData.kt @@ -74,6 +74,14 @@ private class SnackBarDataImpl( } } +/** + * SnackBar를 보여주기 위해선 SnackBarHost를 통해 상태를 관리해야 합니다. + * 스낵바의 타입에 따라 정보성 스낵바또는 에러 스낵바를 표시합니다. + * + * @param snackBarHostState 스낵바 상태 관리 객체 + * @param modifier Modifier + * @param snackBar 스낵바 UI의 컴포저블 함수 + */ @Composable fun SnackBarHost( snackBarHostState: SnackBarHostState, From b26f97851061c136742fe1f7e68b36a6d95564f7 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Thu, 21 Nov 2024 13:09:54 +0900 Subject: [PATCH 20/25] =?UTF-8?q?feat=20:=20=EC=97=90=EB=9F=AC=20=EC=8A=A4?= =?UTF-8?q?=EB=82=B5=EB=B0=94=20UI=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20Line?= =?UTF-8?q?Break=20=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/SnackBar.kt | 21 +++++++++++++++---- .../handy/compose/foundation/Typography.kt | 10 ++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index b2c3245..5ac905a 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.layout.Arrangement 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.offset import androidx.compose.foundation.layout.padding @@ -23,6 +22,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.yourssu.handy.compose.foundation.HandyTypography @@ -100,7 +100,14 @@ fun InfoSnackBar( text = text, color = HandyTheme.colors.textBasicWhite, maxLines = 2, - style = HandyTypography.B3Sb14 + style = HandyTypography.B3Sb14.copy( + lineBreak = LineBreak + ( + strategy = LineBreak.Strategy.Simple, + strictness = LineBreak.Strictness.Strict, + wordBreak = LineBreak.WordBreak.Default + ) + ), ) } } @@ -139,12 +146,18 @@ fun ErrorSnackBar( modifier = Modifier.align(Alignment.Top) ) Text( + modifier = Modifier.weight(1f), text = text, color = HandyTheme.colors.textStatusNegative, maxLines = 2, - style = HandyTypography.B3Sb14 + style = HandyTypography.B3Sb14.copy( + lineBreak = LineBreak( + strategy = LineBreak.Strategy.Simple, + strictness = LineBreak.Strictness.Strict, + wordBreak = LineBreak.WordBreak.Phrase + ) + ) ) - Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = HandyIcons.Line.Close, tint = HandyTheme.colors.textBasicTertiary, diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt index c594f21..2bed157 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp @@ -44,7 +45,8 @@ data class HandyTextStyle internal constructor( val fontWeight: FontWeight? = FontWeight.Normal, val letterSpacing: TextUnit = (-0.02).em, val lineHeight: Dp = Dp.Unspecified, - val textAlign: TextAlign = TextAlign.Start + val textAlign: TextAlign = TextAlign.Start, + val lineBreak: LineBreak = LineBreak.Unspecified ) { private val fontFamily = fonts @@ -70,7 +72,8 @@ data class HandyTextStyle internal constructor( lineHeightStyle = LineHeightStyle( alignment = LineHeightStyle.Alignment.Center, trim = LineHeightStyle.Trim.None - ) + ), + lineBreak = lineBreak ) /** @@ -87,7 +90,8 @@ data class HandyTextStyle internal constructor( fontWeight = other.fontWeight ?: fontWeight, letterSpacing = other.letterSpacing.takeOrElse { letterSpacing }, lineHeight = other.lineHeight.takeOrElse { lineHeight }, - textAlign = if (other.textAlign == TextAlign.Unspecified) textAlign else other.textAlign + textAlign = if (other.textAlign == TextAlign.Unspecified) textAlign else other.textAlign, + lineBreak = if (other.lineBreak == LineBreak.Unspecified) lineBreak else other.lineBreak ) } From ac45fb4f9489d025b48f1d5cd69f425d1f0265c1 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 22 Nov 2024 15:15:04 +0900 Subject: [PATCH 21/25] =?UTF-8?q?feat=20:=20=EC=A0=95=EB=B3=B4=EC=84=B1=20?= =?UTF-8?q?=EC=8A=A4=EB=82=B5=EB=B0=94=20=EB=8B=A8=EC=96=B4=20=EA=B0=9C?= =?UTF-8?q?=ED=96=89=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt b/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt index 3b89559..66dc6d3 100644 --- a/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt +++ b/app/src/main/kotlin/com/yourssu/handy/demo/SnackBarPreview.kt @@ -25,7 +25,7 @@ fun InfoSnackBarPreview() { ) Spacer(modifier = Modifier.height(10.dp)) InfoSnackBar( - text = "줄 수가 두 줄 이상이 되는 스낵바 메시지입니다.\n좌측 정렬을 해주세요.", + text = "줄 수가 두 줄 이상이 되는 스낵바 메시지입니다. 좌측 정렬을 해주세요.", onDismiss = {} ) } @@ -45,7 +45,7 @@ fun ErrorSnackBarPreview() { ) Spacer(modifier = Modifier.height(10.dp)) ErrorSnackBar( - text = "두 줄 이상의 에러 메세지가 들어갈 경우\n 아이콘은 모두 위로 정렬해주세요.", + text = "두 줄 이상의 에러 메세지가 들어갈 경우 아이콘은 모두 위로 정렬해주세요.", onClick = {} ) } From c55e5259682e7cc97784009ed176a0880b3ffe19 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 22 Nov 2024 15:15:08 +0900 Subject: [PATCH 22/25] =?UTF-8?q?feat=20:=20=EC=A0=95=EB=B3=B4=EC=84=B1=20?= =?UTF-8?q?=EC=8A=A4=EB=82=B5=EB=B0=94=20=EB=8B=A8=EC=96=B4=20=EA=B0=9C?= =?UTF-8?q?=ED=96=89=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/SnackBar.kt | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 5ac905a..c598ac4 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset @@ -50,7 +52,7 @@ enum class DragValue { * @param text 스낵바의 문구를 나타내는 텍스트, 최대 두 줄까지 입력 가능 * @param modifier Modifier */ -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) @Composable fun InfoSnackBar( text: String, @@ -96,19 +98,29 @@ fun InfoSnackBar( orientation = Orientation.Vertical, ) ) { - Text( - text = text, - color = HandyTheme.colors.textBasicWhite, - maxLines = 2, - style = HandyTypography.B3Sb14.copy( - lineBreak = LineBreak - ( - strategy = LineBreak.Strategy.Simple, - strictness = LineBreak.Strictness.Strict, - wordBreak = LineBreak.WordBreak.Default - ) - ), - ) + Column { + text.split("\n").forEach { line -> + FlowRow( + modifier = Modifier.fillMaxWidth(), + ) { + line.split(" ").forEachIndexed { index, word -> + Text( + text = word, + color = HandyTheme.colors.textBasicWhite, + maxLines = 2, + style = HandyTypography.B3Sb14 + ) + if (index != line.split(" ").lastIndex) { + Text( + text = " ", + color = HandyTheme.colors.textBasicWhite, + style = HandyTypography.B3Sb14 + ) + } + } + } + } + } } } From fc05bf79eae926dee4bd7b8e28a2c31e12440b4b Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 22 Nov 2024 15:17:34 +0900 Subject: [PATCH 23/25] =?UTF-8?q?feat=20:=20Text=20lineBreak=20=EC=86=8D?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/foundation/Typography.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt index 2bed157..c594f21 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/Typography.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp @@ -45,8 +44,7 @@ data class HandyTextStyle internal constructor( val fontWeight: FontWeight? = FontWeight.Normal, val letterSpacing: TextUnit = (-0.02).em, val lineHeight: Dp = Dp.Unspecified, - val textAlign: TextAlign = TextAlign.Start, - val lineBreak: LineBreak = LineBreak.Unspecified + val textAlign: TextAlign = TextAlign.Start ) { private val fontFamily = fonts @@ -72,8 +70,7 @@ data class HandyTextStyle internal constructor( lineHeightStyle = LineHeightStyle( alignment = LineHeightStyle.Alignment.Center, trim = LineHeightStyle.Trim.None - ), - lineBreak = lineBreak + ) ) /** @@ -90,8 +87,7 @@ data class HandyTextStyle internal constructor( fontWeight = other.fontWeight ?: fontWeight, letterSpacing = other.letterSpacing.takeOrElse { letterSpacing }, lineHeight = other.lineHeight.takeOrElse { lineHeight }, - textAlign = if (other.textAlign == TextAlign.Unspecified) textAlign else other.textAlign, - lineBreak = if (other.lineBreak == LineBreak.Unspecified) lineBreak else other.lineBreak + textAlign = if (other.textAlign == TextAlign.Unspecified) textAlign else other.textAlign ) } From 6af901d02e80efe9edd0f85c619f4594bf9520b7 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 22 Nov 2024 15:25:45 +0900 Subject: [PATCH 24/25] =?UTF-8?q?feat=20:=20=EC=97=90=EB=9F=AC=20=EC=8A=A4?= =?UTF-8?q?=EB=82=B5=EB=B0=94=20=EB=8B=A8=EC=96=B4=20=EA=B0=9C=ED=96=89=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/SnackBar.kt | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index c598ac4..7b16142 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -24,7 +24,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.yourssu.handy.compose.foundation.HandyTypography @@ -136,6 +135,7 @@ fun InfoSnackBar( * @param onClick 스낵바의 X 버튼을 눌렀을 때 호출되는 함수 * @param modifier Modifier */ +@OptIn(ExperimentalLayoutApi::class) @Composable fun ErrorSnackBar( text: String, @@ -157,19 +157,31 @@ fun ErrorSnackBar( tint = HandyTheme.colors.bgStatusNegative, modifier = Modifier.align(Alignment.Top) ) - Text( - modifier = Modifier.weight(1f), - text = text, - color = HandyTheme.colors.textStatusNegative, - maxLines = 2, - style = HandyTypography.B3Sb14.copy( - lineBreak = LineBreak( - strategy = LineBreak.Strategy.Simple, - strictness = LineBreak.Strictness.Strict, - wordBreak = LineBreak.WordBreak.Phrase - ) - ) - ) + Column( + modifier = Modifier.weight(1f) + ) { + text.split("\n").forEach { line -> + FlowRow( + modifier = Modifier.fillMaxWidth(), + ) { + line.split(" ").forEachIndexed { index, word -> + Text( + text = word, + color = HandyTheme.colors.textStatusNegative, + maxLines = 2, + style = HandyTypography.B3Sb14 + ) + if (index != line.split(" ").lastIndex) { + Text( + text = " ", + color = HandyTheme.colors.textStatusNegative, + style = HandyTypography.B3Sb14 + ) + } + } + } + } + } Icon( imageVector = HandyIcons.Line.Close, tint = HandyTheme.colors.textBasicTertiary, From e2c7cb57447dd042c0072f461a431fbb2988b4c5 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Fri, 22 Nov 2024 15:30:50 +0900 Subject: [PATCH 25/25] =?UTF-8?q?feat=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Column=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yourssu/handy/compose/SnackBar.kt | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt index 7b16142..8a9dfcc 100644 --- a/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt +++ b/compose/src/main/kotlin/com/yourssu/handy/compose/SnackBar.kt @@ -97,25 +97,21 @@ fun InfoSnackBar( orientation = Orientation.Vertical, ) ) { - Column { - text.split("\n").forEach { line -> - FlowRow( - modifier = Modifier.fillMaxWidth(), - ) { - line.split(" ").forEachIndexed { index, word -> + text.split("\n").forEach { line -> + FlowRow { + line.split(" ").forEachIndexed { index, word -> + Text( + text = word, + color = HandyTheme.colors.textBasicWhite, + maxLines = 2, + style = HandyTypography.B3Sb14 + ) + if (index != line.split(" ").lastIndex) { Text( - text = word, + text = " ", color = HandyTheme.colors.textBasicWhite, - maxLines = 2, style = HandyTypography.B3Sb14 ) - if (index != line.split(" ").lastIndex) { - Text( - text = " ", - color = HandyTheme.colors.textBasicWhite, - style = HandyTypography.B3Sb14 - ) - } } } } @@ -157,27 +153,21 @@ fun ErrorSnackBar( tint = HandyTheme.colors.bgStatusNegative, modifier = Modifier.align(Alignment.Top) ) - Column( - modifier = Modifier.weight(1f) - ) { - text.split("\n").forEach { line -> - FlowRow( - modifier = Modifier.fillMaxWidth(), - ) { - line.split(" ").forEachIndexed { index, word -> + text.split("\n").forEach { line -> + FlowRow(modifier = Modifier.weight(1f)) { + line.split(" ").forEachIndexed { index, word -> + Text( + text = word, + color = HandyTheme.colors.textStatusNegative, + maxLines = 2, + style = HandyTypography.B3Sb14 + ) + if (index != line.split(" ").lastIndex) { Text( - text = word, + text = " ", color = HandyTheme.colors.textStatusNegative, - maxLines = 2, style = HandyTypography.B3Sb14 ) - if (index != line.split(" ").lastIndex) { - Text( - text = " ", - color = HandyTheme.colors.textStatusNegative, - style = HandyTypography.B3Sb14 - ) - } } } }