Skip to content

Commit

Permalink
FloatingActionButton 구현 (#12)
Browse files Browse the repository at this point in the history
* fix: 주석 오타 수정

* feat: button pressed 상태 위한 interactionSource 추가

* feat: BaseButton 구현

* feat: BoxButton 구현

* feat: BoxButton horizontalPadding 파라미터 추가

* feat: BoxButton Preview 추가

* feat: Tertiary BoxButton border 색 지정

* feat: TextButtonSize, BoxButtonSize 분리

* feat: TextButtonSize, BoxButtonSize 분리

* feat: TextButton 구현

* feat: TextButton Preview

* feat: 주석 추가

* fix: preview 정렬방식 수정

* fix: indication 기본 파라미터 추가

* fix: 주석 설명 수정

* del: 불필요한 코드 삭제

* del: isDisabled -> enable로 수정

* fix: ButtonSizeState data class 명ButtonStyleProperties로 수정

* del: 최소 사이즈 옵션 삭제

* del: delete FAB file

* del: ButtonState와 FloatingActionButtonState 분리

* feat: FloatingActionButton 구현

* feat: FloatingActionButton Preview 파일 생성

* del: 불필요한 주석 삭제

* fix: solved conflict

* del: delete FAB file

* del: ButtonState와 FloatingActionButtonState 분리

* feat: FloatingActionButton 구현

* feat: FloatingActionButton Preview 파일 생성

* del: 불필요한 주석 삭제
  • Loading branch information
kangyuri1114 authored Oct 28, 2024
1 parent d769cab commit 8667f4a
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 11 deletions.
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,
) {
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()

val containerSize = boxButtonSizeStateBySize(size = sizeType)

Surface(
onClick = onClick,
modifier = modifier.semantics { role = Role.Button },
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,
)
}

@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

0 comments on commit 8667f4a

Please sign in to comment.