Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feature/profile-ma…
Browse files Browse the repository at this point in the history
…tch-data
  • Loading branch information
murjune committed Feb 9, 2024
2 parents 880bce2 + 9c38b17 commit 6d96486
Show file tree
Hide file tree
Showing 5 changed files with 419 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ object FunchIconAsset {
val arrow_right_android_24 = R.drawable.ic_arrow_right_android_24
val arrow_left_small_24 = R.drawable.ic_arrow_left_small_24
val arrow_right_small_24 = R.drawable.ic_arrow_right_small_24
val arrow_up_24 = R.drawable.ic_arrow_up_24
val arrow_down_24 = R.drawable.ic_arrow_down_24
val arrow_up_limit_24 = R.drawable.ic_arrow_up_limit_24
val left_right_24 = R.drawable.ic_left_right_24
val diagonal_24 = R.drawable.ic_diagonal_24
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.moya.funch.modifier

import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import kotlin.math.max

/**
* @see <a href="https://stackoverflow.com/questions/75035946/how-to-add-scrollbars-to-column">스크롤바 레퍼런스</a>
*/
@Immutable
data class ScrollBarConfig(
val indicatorHeight: Dp = 39.dp,
val indicatorThickness: Dp = 8.dp,
val indicatorColor: Color = Color.LightGray,
val alpha: Float? = null,
val alphaAnimationSpec: AnimationSpec<Float>? = null,
val padding: PaddingValues = PaddingValues(all = 0.dp)
)

fun Modifier.scrollbar(
state: ScrollState,
indicatorHeight: Dp = 39.dp,
indicatorThickness: Dp = 8.dp,
indicatorColor: Color = Color.LightGray,
alpha: Float = if (state.isScrollInProgress) 0.8f else 0f,
alphaAnimationSpec: AnimationSpec<Float> = tween(
delayMillis = if (state.isScrollInProgress) 0 else 1500,
durationMillis = if (state.isScrollInProgress) 150 else 500
),
padding: PaddingValues = PaddingValues(all = 0.dp)
): Modifier = composed {
val scrollbarAlpha by animateFloatAsState(
targetValue = alpha,
animationSpec = alphaAnimationSpec,
label = ""
)

drawWithContent {
drawContent()

val showScrollBar = state.isScrollInProgress || scrollbarAlpha > 0.0f

if (showScrollBar) {
val (topPadding, bottomPadding, startPadding, endPadding) = listOf(
padding.calculateTopPadding().toPx(),
padding.calculateBottomPadding().toPx(),
padding.calculateStartPadding(layoutDirection).toPx(),
padding.calculateEndPadding(layoutDirection).toPx()
)

val viewPortLength = size.height
val viewPortCrossAxisLength = size.width
val contentLength = max(viewPortLength + state.maxValue, 0.001f)
val indicatorThicknessPx = indicatorThickness.toPx()
val scrollbarSizeWithoutInsets = Size(indicatorThicknessPx, indicatorHeight.toPx())
val maxScrollOffset = viewPortLength - scrollbarSizeWithoutInsets.height - topPadding - bottomPadding
val scrollOffsetViewPort =
if (contentLength > viewPortLength) {
topPadding + (state.value / (contentLength - viewPortLength)) * maxScrollOffset
} else {
topPadding
}

drawRoundRect(
color = indicatorColor,
cornerRadius = CornerRadius(
x = indicatorThicknessPx / 2,
y = indicatorThicknessPx / 2
),
topLeft = Offset(
x = if (layoutDirection == LayoutDirection.Ltr) {
viewPortCrossAxisLength - indicatorThicknessPx - endPadding
} else {
startPadding
},
y = scrollOffsetViewPort
),
size = scrollbarSizeWithoutInsets,
alpha = scrollbarAlpha
)
}
}
}

fun Modifier.verticalScrollWithScrollbar(
state: ScrollState,
enabled: Boolean = true,
flingBehavior: FlingBehavior? = null,
reverseScrolling: Boolean = false,
scrollbarConfig: ScrollBarConfig = ScrollBarConfig()
) = this
.scrollbar(
state = state,
indicatorHeight = scrollbarConfig.indicatorHeight,
indicatorThickness = scrollbarConfig.indicatorThickness,
indicatorColor = scrollbarConfig.indicatorColor,
alpha = scrollbarConfig.alpha ?: if (state.isScrollInProgress) 0.8f else 0f,
alphaAnimationSpec = scrollbarConfig.alphaAnimationSpec ?: tween(
delayMillis = if (state.isScrollInProgress) 0 else 1500,
durationMillis = if (state.isScrollInProgress) 150 else 500
),
padding = scrollbarConfig.padding
)
.verticalScroll(state, enabled, flingBehavior, reverseScrolling)
266 changes: 266 additions & 0 deletions core/designsystem/src/main/java/com/moya/funch/ui/FunchDropDown.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package com.moya.funch.ui

import androidx.compose.foundation.Indication
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.geometry.Rect
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup
import com.moya.funch.icon.FunchIconAsset
import com.moya.funch.modifier.ScrollBarConfig
import com.moya.funch.modifier.verticalScrollWithScrollbar
import com.moya.funch.theme.FunchTheme
import com.moya.funch.theme.Gray300
import com.moya.funch.theme.Gray500
import com.moya.funch.theme.Gray800
import com.moya.funch.theme.LocalBackgroundTheme
import com.moya.funch.theme.White
import kotlin.math.roundToInt

@Composable
fun FunchDropDownButton(
modifier: Modifier = Modifier,
placeHolder: String,
onClick: () -> Unit,
isDropDownMenuExpanded: Boolean,
indication: Indication? = LocalIndication.current,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(56.dp)
.background(
color = Gray800,
shape = FunchTheme.shapes.medium
)
.then(
if (isDropDownMenuExpanded) {
Modifier.border(
width = 1.dp,
color = White,
shape = FunchTheme.shapes.medium
)
} else {
Modifier
}
)
.clickable(
onClick = onClick,
indication = indication,
interactionSource = interactionSource
)
.padding(
top = 8.dp,
bottom = 8.dp,
start = 16.dp,
end = 8.dp
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = placeHolder,
style = FunchTheme.typography.b,
color = White
)
Icon(
modifier = Modifier.padding(8.dp),
painter = painterResource(
id = if (isDropDownMenuExpanded) {
FunchIconAsset.Arrow.arrow_up_24
} else {
FunchIconAsset.Arrow.arrow_down_24
}
),
contentDescription = "",
tint = White
)
}
}

@Composable
fun FunchDropDownMenu(
modifier: Modifier = Modifier,
items: List<String>,
buttonBounds: Rect,
onItemSelected: (String) -> Unit,
scrollState: ScrollState = rememberScrollState()
) {
Popup(
alignment = Alignment.TopStart,
offset = IntOffset(
x = 0,
y = with(LocalDensity.current) {
(buttonBounds.height).toInt() + 8.dp.toPx().roundToInt()
}
)
) {
Column(
modifier = modifier
.width(with(LocalDensity.current) { buttonBounds.width.toDp() })
.height(144.dp)
.background(
color = Gray800,
shape = FunchTheme.shapes.medium
)
.clip(FunchTheme.shapes.medium)
.verticalScrollWithScrollbar(
state = scrollState,
scrollbarConfig = ScrollBarConfig(
indicatorHeight = 39.dp,
indicatorThickness = 4.dp,
indicatorColor = Gray300,
padding = PaddingValues(
top = 16.dp,
bottom = 16.dp,
end = 4.dp
)
)
)
) {
items.forEachIndexed { index, option ->
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
FunchDropDownItem(
option = option,
onItemSelected = { onItemSelected(option) },
isPressed = isPressed,
interactionSource = interactionSource
)
if (index < items.lastIndex) {
Divider(
color = Gray500,
thickness = 0.5f.dp
)
}
}
}
}
}

@Composable
fun FunchDropDownItem(
option: String,
onItemSelected: (String) -> Unit,
isPressed: Boolean,
interactionSource: MutableInteractionSource
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(color = if (isPressed) Gray500 else Gray800)
.clickable(
onClick = { onItemSelected(option) },
interactionSource = interactionSource,
indication = null
)
.padding(
start = 16.dp,
end = 8.dp,
top = 13.5f.dp,
bottom = 13.5f.dp
)
) {
Text(
text = option,
color = White,
style = FunchTheme.typography.b
)
}
}

@Preview(
showBackground = true,
widthDp = 360,
heightDp = 640
)
@Composable
private fun Preview1() {
FunchTheme {
val backgroundColor = LocalBackgroundTheme.current.color

Surface(
modifier = Modifier.fillMaxSize(),
color = backgroundColor
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
val bloodTypes = listOf("A형", "B형", "O형", "AB형")
var placeHolder by remember { mutableStateOf(bloodTypes[0]) }
var isDropDownMenuExpanded by remember { mutableStateOf(true) }
val buttonBounds = remember { mutableStateOf(Rect.Zero) }

Text(
text = "Hello, World!",
fontSize = 50.sp,
color = White
)
Box {
FunchDropDownButton(
placeHolder = placeHolder,
onClick = { isDropDownMenuExpanded = !isDropDownMenuExpanded },
isDropDownMenuExpanded = isDropDownMenuExpanded,
indication = null,
modifier = Modifier.onGloballyPositioned { coordinates ->
buttonBounds.value = coordinates.boundsInWindow()
}
)
if (isDropDownMenuExpanded) {
FunchDropDownMenu(
items = bloodTypes,
buttonBounds = buttonBounds.value,
onItemSelected = { text ->
placeHolder = text
isDropDownMenuExpanded = false
}
)
}
}
Text(
text = "Hello, World!",
fontSize = 50.sp,
color = White
)
}
}
}
}
Loading

0 comments on commit 6d96486

Please sign in to comment.