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

[YDS-#228] Component - Toast 구현 #233

Merged
merged 12 commits into from
Jan 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import com.yourssu.design.system.compose.atom.BoxButton
import com.yourssu.design.system.compose.atom.TopBarButton
import com.yourssu.design.system.compose.component.TopBar
import com.yourssu.design.system.compose.foundation.LocalContentColor
import com.yourssu.design.system.compose.foundation.ToastDuration
import com.yourssu.design.system.compose.foundation.ToastHost
import com.yourssu.design.system.compose.foundation.ToastHostState
import com.yourssu.design.system.compose.component.toast.ToastDuration
import com.yourssu.design.system.compose.component.toast.ToastHost
import com.yourssu.design.system.compose.component.toast.ToastHostState
import kotlinx.coroutines.launch

private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, BottomBar }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.yourssu.design.system.compose.component
package com.yourssu.design.system.compose.component.toast

import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -13,17 +12,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yourssu.design.system.compose.YdsTheme
import com.yourssu.design.system.compose.base.YdsText
import com.yourssu.design.system.compose.foundation.ToastData
import com.yourssu.design.system.compose.foundation.ToastDuration
import kotlin.math.max

@Composable
fun Toast(
Expand Down Expand Up @@ -57,20 +50,8 @@ fun Toast(
"text for Snackbar expected to have exactly only one child"
}
val textPlaceable = measurables.first().measure(constraints)
val firstBaseline = textPlaceable[FirstBaseline]
val lastBaseline = textPlaceable[LastBaseline]
require(firstBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
require(lastBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
val containerHeight = textPlaceable.height

Log.d("Toast", "firstBaseline: ${firstBaseline.dp}, lastBaseline: ${lastBaseline.dp}")

val minHeight =
if (firstBaseline == lastBaseline) {
ToastOneLineMinHeight
} else {
ToastTwoLineMinHeight
}
val containerHeight = max(minHeight.roundToPx(), textPlaceable.height)
layout(constraints.maxWidth, containerHeight) {
val textPlaceY = (containerHeight - textPlaceable.height) / 2
textPlaceable.placeRelative(0, textPlaceY)
Copy link
Member

Choose a reason for hiding this comment

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

더이상 Layout으로 위치를 지정해줄 필요 없이 그냥 가운데 정렬만 하면 될 것 같은데 확인 부탁드립니다

(근데 토스트 명세에 텍스트가 한 줄이면 가운데 정렬, 여러 줄이면 왼쪽 정렬해야 하는데 이 부분을 처리하는 로직은 어디에 있는건가요?)

Copy link
Contributor Author

@Gael-Android Gael-Android Dec 30, 2023

Choose a reason for hiding this comment

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

이게 정말 이상한데,
// modifier = Modifier.align(
// Alignment.Center
// )
를 하기만 해도 텍스트가 한줄일때는 중앙정렬이 되고, 2줄이상 넘어갈때 좌측정렬이 자동으로 됩니다.

원래 방법으로는,
// onTextLayout = {
// lineCount = it.lineCount
// },
를 YdsText에 하고,
// if (lineCount == 1)
// Alignment.Center
// else
// Alignment.CenterStart
하면 정해진 로직대로 구현을 하는 방법입니다. 하지만 첫번째 방법이 이해할 수 없지만 작동하니
이건 어떻게 해야할지 잘 모르겠습니다. (이해도 안되고요...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그래도 일단 기획이 코드에 보여야하니까 아래의 방법으로 하겠습니다.

Copy link
Member

Choose a reason for hiding this comment

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

아하 그러게요 희한하네요

텍스트 정렬하는 방법을 찾아보니 보통 modifier가 아니라 textAlign 속성을 지정해주는 방식으로 하더라구요 (https://kotlinworld.com/219)
Modifier.align이랑은 무슨 차이가 있을까요..?

Copy link
Member

Choose a reason for hiding this comment

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

내부 구현을 둘러보니까 일단 align 함수는 Box나 Column, Row 스코프 내에서만 사용할 수 있는 함수예요. 컨테이너 내부의 각 요소에 대해 어떻게 둘거냐를 지정하는거죠. 그래서 텍스트의 속성이라고 보긴 어려울 것 같아요.

그리고 Modifier.align(Center)를 하든 안 하든 텍스트 정렬은 TextAlign.Start(좌측 정렬)가 기본이에요

// 참고
// androidx.compose.ui.text.ParagraphTextStyle

internal val textAlignOrDefault: TextAlign = textAlign ?: TextAlign.Start

근데 왜 말씀하신 것처럼 한 줄일 때는 중앙 정렬이고 여러 줄일 때는 좌측 정렬인 것처럼 보일까요? 그림으로 직관적으로 보여드리자면
image

이렇게 그냥 텍스트가 짧으면 차지하는 영역이 작으니까 Box의 Center 속성 때문에 가운데에 있는 것처럼 보이는 거예요.

Copy link
Member

Choose a reason for hiding this comment

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

위 링크의 textAlign 속성은 머티리얼 Text에만 있는거더라구요. 그래서 의도대로 하기 위해서는 YdsText를 수정해야 하는데 글로 쓰기는 길어지니까 이 PR 머지되고 나서 제가 직접 수정할게요. 지금은 그냥 이 상태로 두셔도 괜찮습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

내부 구현을 둘러보니까 일단 align 함수는 Box나 Column, Row 스코프 내에서만 사용할 수 있는 함수예요. 컨테이너 내부의 각 요소에 대해 어떻게 둘거냐를 지정하는거죠. 그래서 텍스트의 속성이라고 보긴 어려울 것 같아요.

그리고 Modifier.align(Center)를 하든 안 하든 텍스트 정렬은 TextAlign.Start(좌측 정렬)가 기본이에요

// 참고

// androidx.compose.ui.text.ParagraphTextStyle



internal val textAlignOrDefault: TextAlign = textAlign ?: TextAlign.Start

근데 왜 말씀하신 것처럼 한 줄일 때는 중앙 정렬이고 여러 줄일 때는 좌측 정렬인 것처럼 보일까요? 그림으로 직관적으로 보여드리자면

image

이렇게 그냥 텍스트가 짧으면 차지하는 영역이 작으니까 Box의 Center 속성 때문에 가운데에 있는 것처럼 보이는 거예요.

정말 그렇네요!! 저 이미지가 맞는거 같습니다.

Expand All @@ -81,8 +62,6 @@ fun Toast(
private val ToastHorizontalPadding = 24.dp
private val ToastVerticalPadding = 16.dp
private val ToastHorizontalMargin = 8.dp
private val ToastOneLineMinHeight = 53.dp
private val ToastTwoLineMinHeight = 74.dp


@Preview(showBackground = false, showSystemUi = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.yourssu.design.system.compose.foundation
package com.yourssu.design.system.compose.component.toast

import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -20,45 +20,48 @@ import androidx.compose.ui.graphics.graphicsLayer
import com.yourssu.design.system.compose.rule.Duration
import com.yourssu.design.system.compose.rule.YdsInAndOutEasing

data class ToastAnimationItem(
data class ToastTransitionItem(
val toastData: ToastData?,
val opacityTransition: opacityTransition
val opacityTransition: OpacityTransition
)

fun Any?.helpCode() = System.identityHashCode(this) % 1000

typealias opacityTransition = @Composable (toast: @Composable () -> Unit) -> Unit
typealias OpacityTransition = @Composable (toast: @Composable () -> Unit) -> Unit

/**
* 토스트 애니메이션 FadeInFadeOut
*
* 구현과정 이해를 위해 주석을 남겨둘 예정입니다.
* 작동 이해를 위해 주석을 남겨둘 예정입니다.
*
* @param newToastData 새로운 토스트 데이터 입력. 입력 없다면 null
* @param modifier Modifier
* @param toast 토스트 컴포저블
*/
@Composable
fun FadeInFadeOutWithScale(
fun FadeInFadeOut(
newToastData: ToastData?,
modifier: Modifier = Modifier,
toast: @Composable (ToastData) -> Unit // Composable of Toast
) {
// 앞으로 나타나기로 예정된 토스트의 정보
var scheduledToastData by remember { mutableStateOf<ToastData?>(null) }
val toastAnimations = remember { mutableListOf<ToastAnimationItem>() }
// 현재 나타나고 있는 토스트의 정보와 Opacity Composable
val toastTransitions = remember { mutableListOf<ToastTransitionItem>() }
var scope by remember { mutableStateOf<RecomposeScope?>(null) }

if (newToastData != scheduledToastData) {
scheduledToastData = newToastData
val toastDataList = toastAnimations.map {
// 새로 발생한 토스트 != 앞으로 나타나기로 예정된 토스트의 정보 => 새 Toast 발생
scheduledToastData = newToastData // 앞으로 나타날 토스트에 새로운 토스트로 업데이트
val toastDataList = toastTransitions.map {
it.toastData
}.toMutableList()
toastDataList.add(newToastData)
toastAnimations.clear()
}.toMutableList() // 현재 나타난 토스트의 정보로 초기회
Copy link
Contributor

Choose a reason for hiding this comment

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

아주 사소한거지만 주석에 오타가 있네요!
초기회 -> 초기화

toastDataList.add(newToastData) // 새로 발생한 토스트 추가
toastTransitions.clear() // 현재 나타난 토스트 비우기 (새로운 토스트가 발생했으므로)
toastDataList
.filterNotNull()
.mapTo(destination = toastAnimations) { appearedToastData ->
ToastAnimationItem(appearedToastData) { toast ->
.mapTo(destination = toastTransitions) { appearedToastData ->
ToastTransitionItem(appearedToastData) { toast ->
val isVisible = appearedToastData == newToastData
val opacity = animatedOpacity(
animation = tween(
Expand All @@ -67,14 +70,15 @@ fun FadeInFadeOutWithScale(
durationMillis = Duration.Medium.millis
),
visible = isVisible,
onAnimationFinish = {
onAnimationFinish = { // 토스트가 사라질때 발동
if (appearedToastData != scheduledToastData) {
toastAnimations.removeAll { it.toastData == appearedToastData }
toastTransitions.removeAll { it.toastData == appearedToastData }
// 발생한 토스트를 제거
scope?.invalidate()
}
}
)
Box(
Box( // 투명도 그래픽 레이어를 만들어주는 Toast 상위 Composable
Modifier
.graphicsLayer(
alpha = opacity.value
Expand All @@ -88,7 +92,7 @@ fun FadeInFadeOutWithScale(

Box(modifier) {
scope = currentRecomposeScope
toastAnimations.forEach { (toastData, opacity) ->
toastTransitions.forEach { (toastData, opacity) ->
key(toastData) {
opacity {
toast(toastData!!)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.yourssu.design.system.compose.foundation
package com.yourssu.design.system.compose.component.toast

import android.util.Log
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.setValue
import androidx.compose.ui.Modifier
import com.yourssu.design.system.compose.component.Toast
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
Expand Down Expand Up @@ -92,7 +90,7 @@ fun ToastHost(
currentToastData.dismiss()
}
}
FadeInFadeOutWithScale(
FadeInFadeOut(
newToastData = toastHostState.currentToastData,
modifier = modifier,
toast = toast
Expand Down
2 changes: 1 addition & 1 deletion version.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
versionName=2.3.1
versionName=2.4.0
#자동 배포를 위해서 버전은 여기 한 군데에서 관리하면 된다