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

FloatingActionButton 구현 #12

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d357199
fix: 주석 오타 수정
kangyuri1114 Aug 21, 2024
08eaa50
feat: button pressed 상태 위한 interactionSource 추가
kangyuri1114 Aug 21, 2024
ca724c9
feat: BaseButton 구현
kangyuri1114 Aug 21, 2024
1d14bd1
feat: BoxButton 구현
kangyuri1114 Aug 21, 2024
4951925
feat: BoxButton horizontalPadding 파라미터 추가
kangyuri1114 Aug 21, 2024
e41f364
feat: BoxButton Preview 추가
kangyuri1114 Aug 21, 2024
521a1be
feat: Tertiary BoxButton border 색 지정
kangyuri1114 Aug 21, 2024
1f73d62
feat: TextButtonSize, BoxButtonSize 분리
kangyuri1114 Aug 21, 2024
847a6b4
feat: TextButtonSize, BoxButtonSize 분리
kangyuri1114 Aug 21, 2024
41beaff
feat: TextButton 구현
kangyuri1114 Aug 21, 2024
cbf5371
feat: TextButton Preview
kangyuri1114 Aug 21, 2024
8a63ab7
feat: 주석 추가
kangyuri1114 Aug 21, 2024
30f7294
fix: preview 정렬방식 수정
kangyuri1114 Sep 6, 2024
22cc9da
fix: indication 기본 파라미터 추가
kangyuri1114 Sep 6, 2024
495d3c7
fix: 주석 설명 수정
kangyuri1114 Sep 6, 2024
ebb6f67
del: 불필요한 코드 삭제
kangyuri1114 Sep 6, 2024
e38ff0b
del: isDisabled -> enable로 수정
kangyuri1114 Sep 6, 2024
3969695
fix: ButtonSizeState data class 명ButtonStyleProperties로 수정
kangyuri1114 Sep 6, 2024
deb1a82
del: 최소 사이즈 옵션 삭제
kangyuri1114 Sep 6, 2024
2e5bfb6
del: delete FAB file
kangyuri1114 Sep 6, 2024
8c2f943
del: ButtonState와 FloatingActionButtonState 분리
kangyuri1114 Sep 7, 2024
1f5792b
feat: FloatingActionButton 구현
kangyuri1114 Sep 7, 2024
e3601cf
feat: FloatingActionButton Preview 파일 생성
kangyuri1114 Sep 7, 2024
340d1d1
del: 불필요한 주석 삭제
kangyuri1114 Sep 19, 2024
6ac9a43
fix: solved conflict
kangyuri1114 Sep 6, 2024
a5562de
del: delete FAB file
kangyuri1114 Sep 6, 2024
5f3bcc6
del: ButtonState와 FloatingActionButtonState 분리
kangyuri1114 Sep 7, 2024
ad3a43e
feat: FloatingActionButton 구현
kangyuri1114 Sep 7, 2024
6ed94de
feat: FloatingActionButton Preview 파일 생성
kangyuri1114 Sep 7, 2024
69a5ca7
del: 불필요한 주석 삭제
kangyuri1114 Sep 19, 2024
fa8eae1
Merge remote-tracking branch 'origin/feature/rein/FAB' into feature/r…
kangyuri1114 Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.yourssu.handy.demo

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yourssu.handy.compose.FloatingActionButton
import com.yourssu.handy.compose.FloatingActionButtonSize
import com.yourssu.handy.compose.FloatingActionButtonType
import com.yourssu.handy.compose.HandyTheme
import com.yourssu.handy.compose.icons.HandyIcons
import com.yourssu.handy.compose.icons.line.ArrowsChevronUp
import com.yourssu.handy.compose.icons.line.ExternalLink

@Composable
@Preview
fun FloatingActionButtonPreview() {
HandyTheme {
Column(
modifier = Modifier
.background(Color.White)
.wrapContentSize()
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ArrowsChevronUp,
sizeType = FloatingActionButtonSize.S,
floatingActionButtonType = FloatingActionButtonType.Primary
)
FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.S,
floatingActionButtonType = FloatingActionButtonType.Primary,
enabled = false
)
}

Row(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.S,
floatingActionButtonType = FloatingActionButtonType.Secondary,
)

FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.S,
floatingActionButtonType = FloatingActionButtonType.Secondary,
enabled = false
)
}
}

Row(
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.L,
floatingActionButtonType = FloatingActionButtonType.Primary
)

FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.L,
floatingActionButtonType = FloatingActionButtonType.Primary,
enabled = false
)
}

Row(
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.L,
floatingActionButtonType = FloatingActionButtonType.Secondary,
)

FloatingActionButton(
onClick = {},
icon = HandyIcons.Line.ExternalLink,
sizeType = FloatingActionButtonSize.L,
floatingActionButtonType = FloatingActionButtonType.Secondary,
enabled = false
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package com.yourssu.handy.compose

import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.yourssu.handy.compose.foundation.ColorFabPrimaryShadow
import com.yourssu.handy.compose.foundation.ColorFabSecondaryShadow
import com.yourssu.handy.compose.icons.HandyIcons
import com.yourssu.handy.compose.icons.line.ExternalLink

enum class FloatingActionButtonSize(internal val value: Dp) {
S(40.dp),
L(56.dp),
}

enum class FloatingActionButtonType {
Primary,
Secondary;
}

@Composable
fun FloatingActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = CircleShape,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
icon: ImageVector = HandyIcons.Line.ExternalLink,
sizeType: FloatingActionButtonSize = FloatingActionButtonSize.S,
floatingActionButtonType: FloatingActionButtonType = FloatingActionButtonType.Primary,
) {
Comment on lines +42 to +52
Copy link
Member

Choose a reason for hiding this comment

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

엇 그리구 컴포넌트에 대한 설명이 빠진 것 같아요..!

val colors = floatingActionButtonColorByType(type = floatingActionButtonType)
val localPressed by interactionSource.collectIsPressedAsState()
val containerColor = colors.apply { pressed = localPressed }
val contentColor by containerColor.contentColor(enabled)
val borderColor = containerColor.borderColor()

Comment on lines +56 to +58
Copy link
Member

Choose a reason for hiding this comment

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

위 변수에는 by 넣어주고 아래에는 안 넣어준 이유가 있나용..?

val containerSize = boxButtonSizeStateBySize(size = sizeType)

Surface(
onClick = onClick,
modifier = modifier.semantics { role = Role.Button },
Comment on lines +61 to +63
Copy link
Member

Choose a reason for hiding this comment

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

semantics를 사용한 이유는 버튼임을 알리기 위함인가요.....??

shape = shape,
backgroundColor = containerColor.backgroundColor(enabled).value,
contentColor = contentColor,
interactionSource = interactionSource,
shadowColor = containerColor.shadowColor(enabled).value,
shadowElevation = FloatingActionButtonDefaults.FloatingActionButtonShadowElevation,
) {
Box(
modifier = Modifier
.size(containerSize.containerSize.value)
.border(
width = FloatingActionButtonDefaults.FloatingActionButtonBorderSize,
color = borderColor.value,
shape = shape
),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = icon,
iconSize = FloatingActionButtonDefaults.iconSize,
)
}
}
}


@Immutable
data class FloatingActionButtonSizeState(
val containerSize: FloatingActionButtonSize = FloatingActionButtonSize.S,
)


private fun boxButtonSizeStateBySize(
size: FloatingActionButtonSize,
): FloatingActionButtonSizeState = when (size) {
FloatingActionButtonSize.S -> FloatingActionButtonSizeState(
containerSize = FloatingActionButtonSize.S,
)

FloatingActionButtonSize.L -> FloatingActionButtonSizeState(
containerSize = FloatingActionButtonSize.L,
)
}
Comment on lines +96 to +106
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 따로 함수로 빼신 이유가 있을까요?

아마 YDS를 참고해서 만드신 것 같은데, YDS에서는 이 구조가 최선이었어서.. 여기서는 딱히 이렇게 해야 할 이유가 없어보여요.
그 이유라는 건 별거 없긴 한데 말씀드린 적이 없는 것 같아서 다음 핸디 회의 때 말씀드릴게요.

더 간단하게, 직관적으로 상태를 관리/표현할 방법은 없는지 고민해주시면 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

저도 구현하면서 똑같은 생각이 들긴했었는데요 ..

사실 YDS, Mateiral 에서의 디자인 시스템 보다 핸디가 속성과 옵션이 적어서 대부분 함수화를 하지 않는 것이 더 직관적이긴 했어요
그런데 최종적으로 함수화를 한 이유는 확장 가능성을 생각하긴 했었습니다..!
추후에 사이즈나 종류가 추가된다거나 같은 부분이요,,
FloatingActionButtonSizeState도 마찬가지로 오히려 data class를 생성하면서 조금 복잡한 구조를 가지게 되긴 해서

상태가 적지만 확장가능성이 있는.. Handy상태 관리에 대해서 좀 더 고민해보겠습니당


@Composable
private fun floatingActionButtonColorByType(
type: FloatingActionButtonType,
): FloatingActionButtonColorState = when (type) {
FloatingActionButtonType.Primary -> FloatingActionButtonColorState(
contentColor = HandyTheme.colors.iconBasicWhite,
disabledContentColor = HandyTheme.colors.iconBasicWhite,
bgColor = HandyTheme.colors.buttonFabPrimaryEnabled,
disabledBgColor = HandyTheme.colors.buttonFabPrimaryDisabled,
shadowColor = ColorFabPrimaryShadow,
borderColor = Color.Transparent
)

FloatingActionButtonType.Secondary -> FloatingActionButtonColorState(
contentColor = HandyTheme.colors.iconBasicTertiary,
disabledContentColor = HandyTheme.colors.iconBasicDisabled,
bgColor = HandyTheme.colors.buttonFabSecondaryEnabled,
disabledBgColor = HandyTheme.colors.buttonFabSecondaryDisabled,
shadowColor = ColorFabSecondaryShadow,
borderColor = HandyTheme.colors.lineBasicLight
)
}

@Composable
private fun pressedFloatingActionButtonColorFor(color: Color): Color {
return when (color) {
HandyTheme.colors.buttonFabPrimaryEnabled -> HandyTheme.colors.buttonFabPrimaryPressed
HandyTheme.colors.buttonFabSecondaryEnabled -> HandyTheme.colors.buttonFabSecondaryPressed
else -> color
}
}

@Immutable
class FloatingActionButtonColorState(
val contentColor: Color = Color.Unspecified,
val disabledContentColor: Color = Color.Unspecified,
val bgColor: Color = Color.Transparent,
val disabledBgColor: Color = Color.Transparent,
val shadowColor: Color = Color.Transparent,
val borderColor: Color = Color.Transparent,
pressed: Boolean = false,
) {
var pressed by mutableStateOf(pressed)
internal set

@Composable
fun contentColor(enabled: Boolean): State<Color> =
rememberUpdatedState(
when {
!enabled -> disabledContentColor
pressed -> pressedFloatingActionButtonColorFor(contentColor)
else -> contentColor
}
)

@Composable
fun backgroundColor(enabled: Boolean): State<Color> =
rememberUpdatedState(
when {
!enabled -> disabledBgColor
pressed -> pressedFloatingActionButtonColorFor(bgColor)
else -> bgColor
}
)

@Composable
fun shadowColor(enabled: Boolean): State<Color> =
rememberUpdatedState(
when {
!enabled -> Color.Transparent
else -> shadowColor
}
)

@Composable
fun borderColor(): State<Color> =
rememberUpdatedState(borderColor)
}

object FloatingActionButtonDefaults {
val iconSize: IconSize = IconSize.M // 24.dp
val FloatingActionButtonBorderSize = 1.dp
val FloatingActionButtonShadowElevation = 6.dp
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ class ButtonColorState(
else -> bgColor
}
)

@Composable
fun shadowColor(enabled: Boolean): State<Color> =
rememberUpdatedState(
when {
!enabled -> Color.Transparent
else -> shadowColor
}
)
}

@Composable
Expand All @@ -64,8 +55,6 @@ private fun pressedColorFor(color: Color): Color {
HandyTheme.colors.buttonBoxTertiaryEnabled -> HandyTheme.colors.buttonBoxTertiaryPressed
HandyTheme.colors.buttonTextPrimaryEnabled -> HandyTheme.colors.buttonTextPrimaryPressed
HandyTheme.colors.buttonTextSecondaryEnabled -> HandyTheme.colors.buttonTextSecondaryPressed
HandyTheme.colors.buttonFabPrimaryEnabled -> HandyTheme.colors.buttonFabPrimaryPressed
HandyTheme.colors.buttonFabSecondaryEnabled -> HandyTheme.colors.buttonFabSecondaryPressed
else -> color
}
}
Expand Down