Skip to content
This repository has been archived by the owner on Apr 27, 2023. It is now read-only.

[Feature/mission component] 미션 컴포넌트 제작 #27

Merged
merged 17 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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,59 @@
package org.sopt.stamp.designsystem.component.button

import androidx.compose.foundation.layout.Row
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import org.sopt.stamp.R
import org.sopt.stamp.designsystem.component.util.noRippleClickable
import org.sopt.stamp.designsystem.style.SoptTheme

/**
* 앱 디자인 시스템 Icon Button 입니다.
* Icon에 들어갈 ImageVector 는 32*32로 통일된 크기를 가정합니다.
*
* @param imageVector [ImageVector] to draw inside this Icon
*
* @param contentDescription text used by accessibility services to describe what this icon
* represents. This should always be provided unless this icon is used for decorative purposes,
* and does not represent a meaningful action that a user can take. This text should be
* localized, such as by using [androidx.compose.ui.res.stringResource] or similar
*
* @param tint tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no
* tint is applied
*
* @author jinsu4755
* */
@Composable
fun SoptampIconButton(
imageVector: ImageVector,
contentDescription: String? = null,
tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
onClick: () -> Unit = {}
) {
Row(
modifier = Modifier.noRippleClickable { onClick() }
jinsu4755 marked this conversation as resolved.
Show resolved Hide resolved
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
tint = tint
)
}
}

@Preview(showBackground = true)
@Composable
fun PreviewTopBarIconButton() {
SoptTheme {
SoptampIconButton(
imageVector = ImageVector.vectorResource(id = R.drawable.up_expand)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.sopt.stamp.designsystem.component.mission

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.rememberLottieComposition

@Composable
fun CompletedStamp(
stamp: Stamp,
modifier: Modifier
) {
jinsu4755 marked this conversation as resolved.
Show resolved Hide resolved
val completedStamp by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(stamp.lottie))
LottieAnimation(
composition = completedStamp,
modifier = modifier
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.sopt.stamp.designsystem.component.mission

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import org.sopt.stamp.R
import org.sopt.stamp.domain.MissionLevel

@Composable
fun LevelOfMission(stamp: Stamp, spaceSize: Dp) {
Row(
horizontalArrangement = Arrangement.spacedBy(spaceSize)
) {
MissionLevelOfStar(stamp = stamp)
}
}

@Composable
private fun MissionLevelOfStar(stamp: Stamp) {
repeat(MissionLevel.MAXIMUM_LEVEL) {
val starColor = if (it <= stamp.missionLevel.value) {
stamp.starColor
} else {
Stamp.defaultStarColor
}
Icon(
painter = painterResource(id = R.drawable.level_star),
contentDescription = "Star Of Mission Level",
tint = starColor
)
}
}
Comment on lines +12 to +35
Copy link
Member

Choose a reason for hiding this comment

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

이게 Ui 패키지 내에서 이런 컴포넌트를 제작하는 것에는 동의를 하지만, designsystem 패키지에 있다는 것은 전역에서 사용할 수 있는 컴포넌트를 넣어야 한다고 생각해. 디자인시스템이란 것 자체가 사용자에게 보여지는 뷰에 연관된 것만 정의를 해야지 이걸 도메인과 연관시켜서 구현하는 것은 살짝 오바인 것 같아.

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.sopt.stamp.designsystem.component.mission

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.sopt.stamp.designsystem.component.mission.model.MissionUiModel
import org.sopt.stamp.designsystem.component.util.noRippleClickable
import org.sopt.stamp.designsystem.style.SoptTheme
import org.sopt.stamp.domain.MissionLevel

@Composable
fun MissionComponent(
mission: MissionUiModel,
modifier: Modifier = Modifier,
onClick: (() -> Unit) = {}
) {
val shape = MissionShape.DEFAULT_WAVE
val stamp = Stamp.findStampByLevel(mission.level)
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column(
modifier = modifier.defaultMinSize(160.dp, 200.dp).background(
color = if (mission.isCompleted) stamp.background else SoptTheme.colors.onSurface5,
Copy link
Member

Choose a reason for hiding this comment

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

// 이런식으로 isCompleted만 바뀌면 값 바뀔 수 있게...!!
var backgroundColor by remember(isCompleted) {
     if (mission.isCompleted) stamp.background else SoptTheme.colors.onSurface5
}
color = backgroundColor

이런식으로 recomposition이 일어나도 isCompleted 값만 바뀔 때 업데이트 할 수 있게 명시적으로 표시해주면 좋을 것 같음

shape = shape
).noRippleClickable { onClick() },
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if (mission.isCompleted) {
CompletedStamp(
stamp = stamp,
modifier = Modifier.size(104.dp)
)
Spacer(modifier = Modifier.size(8.dp))
} else {
LevelOfMission(stamp = stamp, spaceSize = 10.dp)
Spacer(modifier = Modifier.size(16.dp))
}
TitleOfMission(missionTitle = mission.title)
}
}
}

@Composable
private fun TitleOfMission(missionTitle: String) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = missionTitle,
style = SoptTheme.typography.sub3,
textAlign = TextAlign.Center,
maxLines = 2,
modifier = Modifier.fillMaxWidth(0.8875f)
)
}
}

@Preview(showBackground = true, backgroundColor = 0xff000000)
@Composable
fun PreviewMissionComponent() {
SoptTheme {
val previewMission = MissionUiModel(
id = 1,
title = "일이삼사오육칠팔구십일일이삼사오육칠팔구십일",
level = MissionLevel.of(1),
isCompleted = !false
)
MissionComponent(
modifier = Modifier.width(160.dp),
mission = previewMission
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package org.sopt.stamp.designsystem.component.mission

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.sopt.stamp.designsystem.component.mission.model.MissionPattern
import org.sopt.stamp.designsystem.style.SoptTheme

internal class MissionShape(
private val patternCount: Int
) : Shape {

override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline = Outline.Generic(drawMissionPatternPath(size))

private fun drawMissionPatternPath(size: Size): Path {
val pattern = MissionPattern(calculatePatternLength(size))
return drawMissionPatternPath(size, pattern)
}

private fun calculatePatternLength(size: Size): Float {
return size.width * TOTAL_PATTERN_LENGTH / patternCount
}

private fun drawMissionPatternPath(size: Size, pattern: MissionPattern): Path = Path().apply {
val sideSize = size.width * SIDE_SIZE_RATIO
reset()
moveTo(0f, 0f)
lineTo(sideSize, 0f)
drawTopMissionPattern(size, pattern)
lineTo(size.width, 0f)
lineTo(size.width, size.height)
lineTo(size.width - sideSize, size.height)
drawBottomMissionPattern(size, pattern)
lineTo(0f, size.height)
lineTo(0f, 0f)
close()
}

private fun Path.drawTopMissionPattern(size: Size, pattern: MissionPattern) {
val sideSize = size.width * SIDE_SIZE_RATIO
val rectHeight = pattern.diameter / 2
for (i in 1..patternCount) {
lineTo(sideSize + pattern.length * (i - 1) + pattern.gap, 0f)
arcTo(
rect = Rect(
topLeft = Offset(
x = sideSize + pattern.length * (i - 1) + pattern.gap,
y = -rectHeight
),
bottomRight = Offset(
x = sideSize + pattern.length * (i - 1) + pattern.gap + pattern.diameter,
y = rectHeight
)
),
startAngleDegrees = 180f,
sweepAngleDegrees = -180f,
forceMoveTo = false
)
lineTo(sideSize + pattern.length * i, 0f)
}
}

private fun Path.drawBottomMissionPattern(size: Size, pattern: MissionPattern) {
val sideSize = size.width * SIDE_SIZE_RATIO
val rectHeight = pattern.diameter / 2
for (i in 1..patternCount) {
lineTo(size.width - (sideSize + pattern.length * (i - 1) + pattern.gap), size.height)
arcTo(
rect = Rect(
topLeft = Offset(
x = size.width - (sideSize + pattern.gap + pattern.length * (i - 1) + pattern.diameter),
y = size.height - rectHeight
),
bottomRight = Offset(
x = size.width - (sideSize + pattern.gap + pattern.length * (i - 1)),
y = size.height + rectHeight
)
),
startAngleDegrees = 0f,
sweepAngleDegrees = -180f,
forceMoveTo = false
)
lineTo(size.width - (sideSize + pattern.length * i), size.height)
}
}

companion object {
val DEFAULT_WAVE: MissionShape = MissionShape(6)
private const val TOTAL_PATTERN_LENGTH = 0.9f
private const val SIDE_SIZE_RATIO: Float = 0.05f
}
}

@Preview(showBackground = true)
@Composable
fun PreviewMissionShape() {
SoptTheme {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
val shape = MissionShape.DEFAULT_WAVE
Card(
modifier = Modifier
.fillMaxWidth(0.8f)
.fillMaxHeight(0.6f),
shape = shape
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(shape = shape, color = Color.Green),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Ticket Price : 20$",
modifier = Modifier
.padding(30.dp),
color = Color.Black,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}
}
Loading