diff --git a/build-logic/src/main/kotlin/NDGLFeaturePlugin.kt b/build-logic/src/main/kotlin/NDGLFeaturePlugin.kt index da84f274..98940d2e 100644 --- a/build-logic/src/main/kotlin/NDGLFeaturePlugin.kt +++ b/build-logic/src/main/kotlin/NDGLFeaturePlugin.kt @@ -1,6 +1,4 @@ -import convention.configureComposeAndroid import convention.configureCoroutineAndroid -import convention.configureFirebase import convention.configureHiltAndroid import org.gradle.api.Plugin import org.gradle.api.Project @@ -18,6 +16,7 @@ class NDGLFeaturePlugin : Plugin { dependencies { "implementation"(project(":navigation")) + "implementation"(project(":core:base")) "implementation"(project(":core:ui")) "implementation"(project(":core:util")) diff --git a/core/ui/src/main/java/com/yapp/ui/base/BaseContract.kt b/core/base/src/main/java/com/yapp/ndgl/core/base/BaseContract.kt similarity index 66% rename from core/ui/src/main/java/com/yapp/ui/base/BaseContract.kt rename to core/base/src/main/java/com/yapp/ndgl/core/base/BaseContract.kt index 4a50b78c..2c4baa4f 100644 --- a/core/ui/src/main/java/com/yapp/ui/base/BaseContract.kt +++ b/core/base/src/main/java/com/yapp/ndgl/core/base/BaseContract.kt @@ -1,4 +1,4 @@ -package com.yapp.ui.base +package com.yapp.ndgl.core.base interface UiState diff --git a/core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt b/core/base/src/main/java/com/yapp/ndgl/core/base/BaseViewModel.kt similarity index 98% rename from core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt rename to core/base/src/main/java/com/yapp/ndgl/core/base/BaseViewModel.kt index 8201ee2a..61fb3978 100644 --- a/core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt +++ b/core/base/src/main/java/com/yapp/ndgl/core/base/BaseViewModel.kt @@ -1,4 +1,4 @@ -package com.yapp.ui.base +package com.yapp.ndgl.core.base import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLCTAButton.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLCTAButton.kt new file mode 100644 index 00000000..0d405d7f --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLCTAButton.kt @@ -0,0 +1,215 @@ +package com.yapp.ndgl.core.ui.designsystem + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.yapp.ndgl.core.ui.R +import com.yapp.ndgl.core.ui.theme.NDGLTheme + +object NDGLCTAButtonAttr { + enum class Type { + PRIMARY, + SECONDARY, + DESTRUCTIVE, + } + + enum class Size( + val height: Dp, + val horizontalPadding: Dp, + val horizontalSpacing: Dp, + val iconSize: Dp, + ) { + LARGE( + height = 56.dp, + horizontalPadding = 24.dp, + horizontalSpacing = 8.dp, + iconSize = 24.dp, + ), + MEDIUM( + height = 40.dp, + horizontalPadding = 24.dp, + horizontalSpacing = 8.dp, + iconSize = 20.dp, + ), + SMALL( + height = 32.dp, + horizontalPadding = 12.dp, + horizontalSpacing = 4.dp, + iconSize = 16.dp, + ), + } + + enum class Status { + ACTIVE, + DISABLED, + } +} + +@Composable +fun NDGLCTAButton( + type: NDGLCTAButtonAttr.Type, + size: NDGLCTAButtonAttr.Size, + status: NDGLCTAButtonAttr.Status, + label: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + @DrawableRes leadingIcon: Int? = null, + @DrawableRes trailingIcon: Int? = null, +) { + val contentColor = type.contentColor(status) + + Row( + modifier = modifier + .height(size.height) + .clip(RoundedCornerShape(8.dp)) + .background(type.containerColor(status)) + .clickable( + enabled = status != NDGLCTAButtonAttr.Status.DISABLED, + onClick = onClick, + ) + .padding(horizontal = size.horizontalPadding, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy( + space = size.horizontalSpacing, + alignment = Alignment.CenterHorizontally, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + leadingIcon?.let { icon -> + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + modifier = Modifier.size(size.iconSize), + tint = contentColor, + ) + } + + Text( + text = label, + style = size.labelStyle(), + color = contentColor, + ) + + trailingIcon?.let { icon -> + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + modifier = Modifier.size(size.iconSize), + tint = contentColor, + ) + } + } +} + +@Composable +private fun NDGLCTAButtonAttr.Type.containerColor( + status: NDGLCTAButtonAttr.Status, +): Color { + if (status == NDGLCTAButtonAttr.Status.DISABLED) return NDGLTheme.colors.black100 + return when (this) { + NDGLCTAButtonAttr.Type.PRIMARY -> NDGLTheme.colors.black900 + NDGLCTAButtonAttr.Type.SECONDARY -> NDGLTheme.colors.black50 + NDGLCTAButtonAttr.Type.DESTRUCTIVE -> NDGLTheme.colors.red50 + } +} + +@Composable +private fun NDGLCTAButtonAttr.Type.contentColor( + status: NDGLCTAButtonAttr.Status, +): Color { + if (status == NDGLCTAButtonAttr.Status.DISABLED) return NDGLTheme.colors.black300 + return when (this) { + NDGLCTAButtonAttr.Type.PRIMARY -> NDGLTheme.colors.white + NDGLCTAButtonAttr.Type.SECONDARY -> NDGLTheme.colors.black700 + NDGLCTAButtonAttr.Type.DESTRUCTIVE -> NDGLTheme.colors.red500 + } +} + +@Composable +private fun NDGLCTAButtonAttr.Size.labelStyle(): TextStyle { + return when (this) { + NDGLCTAButtonAttr.Size.LARGE -> NDGLTheme.typography.bodyLgSemiBold + NDGLCTAButtonAttr.Size.MEDIUM -> NDGLTheme.typography.bodyMdSemiBold + NDGLCTAButtonAttr.Size.SMALL -> NDGLTheme.typography.bodySmSemiBold + } +} + +@Preview(showBackground = true) +@Composable +private fun NDGLCTAButtonPrimaryLargePreview() { + NDGLTheme { + NDGLCTAButton( + type = NDGLCTAButtonAttr.Type.PRIMARY, + size = NDGLCTAButtonAttr.Size.LARGE, + status = NDGLCTAButtonAttr.Status.ACTIVE, + label = "Primary Large", + leadingIcon = R.drawable.ic_24_pin, + onClick = {}, + ) + } +} + +@Preview +@Composable +private fun NDGLCTAButtonSecondaryMediumPreview() { + NDGLTheme { + NDGLCTAButton( + type = NDGLCTAButtonAttr.Type.SECONDARY, + size = NDGLCTAButtonAttr.Size.MEDIUM, + status = NDGLCTAButtonAttr.Status.ACTIVE, + label = "Secondary Medium", + leadingIcon = R.drawable.ic_20_tv, + onClick = {}, + ) + } +} + +@Preview +@Composable +private fun NDGLCTAButtonDestructiveSmallPreview() { + NDGLTheme { + NDGLCTAButton( + type = NDGLCTAButtonAttr.Type.DESTRUCTIVE, + size = NDGLCTAButtonAttr.Size.SMALL, + status = NDGLCTAButtonAttr.Status.ACTIVE, + label = "Destructive Small", + leadingIcon = R.drawable.ic_20_tv, + onClick = {}, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Preview +@Composable +private fun NDGLCTAButtonDisabledPreview() { + NDGLTheme { + NDGLCTAButton( + type = NDGLCTAButtonAttr.Type.PRIMARY, + size = NDGLCTAButtonAttr.Size.LARGE, + status = NDGLCTAButtonAttr.Status.DISABLED, + label = "Disabled", + leadingIcon = R.drawable.ic_24_pin, + onClick = {}, + ) + } +} diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLChipTab.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLChipTab.kt new file mode 100644 index 00000000..276f9e49 --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLChipTab.kt @@ -0,0 +1,154 @@ +package com.yapp.ndgl.core.ui.designsystem + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.ndgl.core.ui.R +import com.yapp.ndgl.core.ui.theme.NDGLTheme +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf + +object NDGLChipTabAttr { + data class Tab( + val tag: String, + val name: String, + @param:DrawableRes val leadingIcon: Int? = null, + ) +} + +@Composable +fun NDGLChipTab( + tabs: PersistentList, + selectedIndex: Int, + onTabSelected: (Int) -> Unit, + modifier: Modifier = Modifier, +) { + val scrollState = rememberScrollState() + + Row( + modifier = modifier.horizontalScroll(scrollState), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + tabs.forEachIndexed { index, tab -> + NDGLChipTabItem( + isSelected = index == selectedIndex, + name = tab.name, + leadingIcon = tab.leadingIcon, + onTabSelected = { onTabSelected(index) }, + ) + } + } +} + +@Composable +private fun NDGLChipTabItem( + isSelected: Boolean, + name: String, + onTabSelected: () -> Unit, + @DrawableRes leadingIcon: Int?, +) { + Row( + modifier = Modifier + .clip(CircleShape) + .chipStyle(isSelected) + .clickable(onClick = onTabSelected) + .widthIn(min = 72.dp) + .padding(horizontal = 14.dp, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy( + space = 4.dp, + alignment = Alignment.CenterHorizontally, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + leadingIcon?.let { icon -> + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = isSelected.chipContentColor(), + ) + } + + Text( + text = name, + style = NDGLTheme.typography.bodyMdMedium, + color = isSelected.chipContentColor(), + ) + } +} + +@Composable +private fun Modifier.chipStyle( + isSelected: Boolean, +): Modifier = this.then( + if (isSelected) { + Modifier.background(NDGLTheme.colors.black900, CircleShape) + } else { + Modifier + .background(NDGLTheme.colors.white, CircleShape) + .border( + width = 1.dp, + color = NDGLTheme.colors.black200, + shape = CircleShape, + ) + }, +) + +@Composable +private fun Boolean.chipContentColor() = if (this) { + NDGLTheme.colors.white +} else { + NDGLTheme.colors.black400 +} + +@Preview(showBackground = true) +@Composable +private fun NDGLChipTabPreview() { + NDGLTheme { + var selectedIndex by remember { mutableIntStateOf(0) } + + NDGLChipTab( + tabs = persistentListOf( + NDGLChipTabAttr.Tab( + tag = "1", + name = "1일차", + leadingIcon = R.drawable.ic_20_tv, + ), + NDGLChipTabAttr.Tab( + tag = "1", + name = "2일차", + leadingIcon = R.drawable.ic_20_tv, + ), + NDGLChipTabAttr.Tab( + tag = "1", + name = "3일차", + ), + ), + selectedIndex = selectedIndex, + onTabSelected = { selectedIndex = it }, + ) + } +} diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLModal.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLModal.kt new file mode 100644 index 00000000..bace0397 --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLModal.kt @@ -0,0 +1,169 @@ +package com.yapp.ndgl.core.ui.designsystem + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.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 androidx.compose.ui.window.Dialog +import com.yapp.ndgl.core.ui.theme.NDGLTheme + +@Composable +fun NDGLModal( + onDismissRequest: () -> Unit, + title: String, + body: String, + positiveButtonText: String, + onPositiveButtonClick: () -> Unit, + modifier: Modifier = Modifier, + description: String? = null, + negativeButtonText: String? = null, + onNegativeButtonClick: (() -> Unit) = {}, +) { + Dialog(onDismissRequest = onDismissRequest) { + Surface( + modifier = modifier.wrapContentHeight(), + shape = RoundedCornerShape(8.dp), + color = NDGLTheme.colors.white, + shadowElevation = 16.dp, + ) { + Column( + modifier = Modifier + .padding(horizontal = 28.dp) + .padding(top = 28.dp, bottom = 24.dp), + verticalArrangement = Arrangement.spacedBy(28.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + ModalContent( + title = title, + body = body, + description = description, + ) + ModalButtons( + onDismissRequest = onDismissRequest, + positiveButtonText = positiveButtonText, + onPositiveButtonClick = onPositiveButtonClick, + negativeButtonText = negativeButtonText, + onNegativeButtonClick = onNegativeButtonClick, + ) + } + } + } +} + +@Composable +private fun ModalContent( + title: String, + body: String, + description: String?, +) { + Column { + Text( + text = title, + modifier = Modifier.fillMaxWidth(), + color = NDGLTheme.colors.black900, + textAlign = TextAlign.Center, + style = NDGLTheme.typography.subtitleLgSemiBold, + ) + Text( + text = body, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + color = NDGLTheme.colors.black500, + textAlign = TextAlign.Center, + style = NDGLTheme.typography.bodyLgMedium, + ) + description?.let { + Text( + text = it, + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp), + color = NDGLTheme.colors.black400, + textAlign = TextAlign.Center, + style = NDGLTheme.typography.bodyMdMedium, + ) + } + } +} + +@Composable +private fun ModalButtons( + onDismissRequest: () -> Unit, + positiveButtonText: String, + onPositiveButtonClick: () -> Unit, + negativeButtonText: String?, + onNegativeButtonClick: (() -> Unit), +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + if (negativeButtonText != null) { + NDGLCTAButton( + type = NDGLCTAButtonAttr.Type.SECONDARY, + size = NDGLCTAButtonAttr.Size.MEDIUM, + status = NDGLCTAButtonAttr.Status.ACTIVE, + label = negativeButtonText, + onClick = { + onNegativeButtonClick() + onDismissRequest() + }, + modifier = Modifier.weight(1f), + ) + } + NDGLCTAButton( + type = NDGLCTAButtonAttr.Type.PRIMARY, + size = NDGLCTAButtonAttr.Size.MEDIUM, + status = NDGLCTAButtonAttr.Status.ACTIVE, + label = positiveButtonText, + onClick = { + onPositiveButtonClick() + onDismissRequest() + }, + modifier = Modifier.weight(1f), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun NDGLModalWithDescriptionPreview() { + NDGLTheme { + NDGLModal( + onDismissRequest = {}, + title = "장소 변경", + body = "장소를 다음과 같이 변경할까요?", + description = "젤라테리아 파씨 (Gelateria Fassi) → 콜로세움 (colosseo)", + negativeButtonText = "취소", + onNegativeButtonClick = {}, + positiveButtonText = "변경하기", + onPositiveButtonClick = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun NDGLModalWithoutDescriptionPreview() { + NDGLTheme { + NDGLModal( + onDismissRequest = {}, + title = "장소 변경", + body = "장소를 다음과 같이 변경할까요?", + positiveButtonText = "변경하기", + onPositiveButtonClick = {}, + ) + } +} diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLNavigationBar.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLNavigationBar.kt new file mode 100644 index 00000000..2fee6e3f --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLNavigationBar.kt @@ -0,0 +1,143 @@ +package com.yapp.ndgl.core.ui.designsystem + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.ndgl.core.ui.R +import com.yapp.ndgl.core.ui.theme.NDGLTheme + +object NDGLNavigationBarAttr { + enum class TextAlignType { + START, + CENTER, + } +} + +@Composable +fun NDGLNavigationBar( + textAlignType: NDGLNavigationBarAttr.TextAlignType, + modifier: Modifier = Modifier, + headline: String? = null, + @DrawableRes leadingIcon: Int? = null, + onLeadingIconClick: () -> Unit = {}, + trailingContents: (@Composable RowScope.() -> Unit)? = null, +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(48.dp) + .padding(horizontal = 24.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + leadingIcon?.let { icon -> + NDGLNavigationIcon( + icon = icon, + onClick = onLeadingIconClick, + ) + } + + headline?.let { text -> + Text( + text = text, + modifier = Modifier.weight(1f), + style = NDGLTheme.typography.bodyLgMedium, + color = NDGLTheme.colors.black700, + textAlign = when (textAlignType) { + NDGLNavigationBarAttr.TextAlignType.START -> TextAlign.Start + NDGLNavigationBarAttr.TextAlignType.CENTER -> TextAlign.Center + }, + ) + } ?: Spacer(modifier = Modifier.weight(1f)) + + trailingContents?.let { contents -> + contents() + } + } +} + +@Composable +fun NDGLNavigationIcon( + @DrawableRes icon: Int, + onClick: () -> Unit = {}, +) { + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + .clickable(onClick = onClick), + tint = NDGLTheme.colors.black700, + ) +} + +@Preview(showBackground = true) +@Composable +private fun NDGLNavigationBarCenterPreview() { + NDGLTheme { + NDGLNavigationBar( + textAlignType = NDGLNavigationBarAttr.TextAlignType.CENTER, + headline = "미리보기", + leadingIcon = R.drawable.ic_28_chevron_left, + trailingContents = { + NDGLNavigationIcon( + icon = R.drawable.ic_28_search, + onClick = {}, + ) + NDGLNavigationIcon( + icon = R.drawable.ic_28_settings, + onClick = {}, + ) + }, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun NDGLNavigationBarStartPreview() { + NDGLTheme { + NDGLNavigationBar( + textAlignType = NDGLNavigationBarAttr.TextAlignType.START, + headline = "미리보기", + leadingIcon = R.drawable.ic_28_chevron_left, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun NDGLNavigationBarNoHeadlinePreview() { + NDGLTheme { + NDGLNavigationBar( + textAlignType = NDGLNavigationBarAttr.TextAlignType.START, + leadingIcon = R.drawable.ic_28_menu, + trailingContents = { + NDGLNavigationIcon( + icon = R.drawable.ic_28_search, + onClick = {}, + ) + }, + ) + } +} diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLOutlinedButton.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLOutlinedButton.kt new file mode 100644 index 00000000..387db28e --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/designsystem/NDGLOutlinedButton.kt @@ -0,0 +1,132 @@ +package com.yapp.ndgl.core.ui.designsystem + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +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 androidx.compose.ui.unit.dp +import com.yapp.ndgl.core.ui.R +import com.yapp.ndgl.core.ui.theme.NDGLTheme + +object NDGLOutlinedButtonAttr { + enum class Status { + ACTIVE, + DISABLED, + } +} + +@Composable +fun NDGLOutlinedButton( + status: NDGLOutlinedButtonAttr.Status, + label: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + @DrawableRes leadingIcon: Int? = null, + @DrawableRes trailingIcon: Int? = null, +) { + val contentColor = status.contentColor() + + Row( + modifier = modifier + .height(40.dp) + .clip(RoundedCornerShape(8.dp)) + .background(status.containerColor()) + .border( + width = 1.dp, + color = NDGLTheme.colors.black200, + shape = RoundedCornerShape(8.dp), + ) + .clickable( + enabled = status != NDGLOutlinedButtonAttr.Status.DISABLED, + onClick = onClick, + ) + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy( + space = 8.dp, + alignment = Alignment.CenterHorizontally, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + leadingIcon?.let { icon -> + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = contentColor, + ) + } + + Text( + text = label, + style = NDGLTheme.typography.bodyMdSemiBold, + color = contentColor, + ) + + trailingIcon?.let { icon -> + Icon( + imageVector = ImageVector.vectorResource(icon), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = contentColor, + ) + } + } +} + +@Composable +private fun NDGLOutlinedButtonAttr.Status.containerColor(): Color { + return when (this) { + NDGLOutlinedButtonAttr.Status.ACTIVE -> NDGLTheme.colors.white + NDGLOutlinedButtonAttr.Status.DISABLED -> NDGLTheme.colors.black300 + } +} + +@Composable +private fun NDGLOutlinedButtonAttr.Status.contentColor(): Color { + return when (this) { + NDGLOutlinedButtonAttr.Status.ACTIVE -> NDGLTheme.colors.black600 + NDGLOutlinedButtonAttr.Status.DISABLED -> NDGLTheme.colors.black400 + } +} + +@Preview +@Composable +private fun NDGLOutlinedButtonActivePreview() { + NDGLTheme { + NDGLOutlinedButton( + status = NDGLOutlinedButtonAttr.Status.ACTIVE, + label = "Active", + leadingIcon = R.drawable.ic_20_tv, + onClick = {}, + ) + } +} + +@Preview +@Composable +private fun NDGLOutlinedButtonDisabledPreview() { + NDGLTheme { + NDGLOutlinedButton( + status = NDGLOutlinedButtonAttr.Status.DISABLED, + label = "Disabled", + leadingIcon = R.drawable.ic_20_tv, + onClick = {}, + ) + } +} diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Color.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Color.kt index 14a03b0c..57f3c635 100644 --- a/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Color.kt +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Color.kt @@ -8,27 +8,27 @@ import androidx.compose.ui.graphics.Color data class NDGLColors( val white: Color = Color.Unspecified, - val primary50: Color = Color.Unspecified, - val primary100: Color = Color.Unspecified, - val primary200: Color = Color.Unspecified, - val primary300: Color = Color.Unspecified, - val primary400: Color = Color.Unspecified, - val primary500: Color = Color.Unspecified, - val primary600: Color = Color.Unspecified, - val primary700: Color = Color.Unspecified, - val primary800: Color = Color.Unspecified, - val primary900: Color = Color.Unspecified, + val green50: Color = Color.Unspecified, + val green100: Color = Color.Unspecified, + val green200: Color = Color.Unspecified, + val green300: Color = Color.Unspecified, + val green400: Color = Color.Unspecified, + val green500: Color = Color.Unspecified, + val green600: Color = Color.Unspecified, + val green700: Color = Color.Unspecified, + val green800: Color = Color.Unspecified, + val green900: Color = Color.Unspecified, - val secondary50: Color = Color.Unspecified, - val secondary100: Color = Color.Unspecified, - val secondary200: Color = Color.Unspecified, - val secondary300: Color = Color.Unspecified, - val secondary400: Color = Color.Unspecified, - val secondary500: Color = Color.Unspecified, - val secondary600: Color = Color.Unspecified, - val secondary700: Color = Color.Unspecified, - val secondary800: Color = Color.Unspecified, - val secondary900: Color = Color.Unspecified, + val black50: Color = Color.Unspecified, + val black100: Color = Color.Unspecified, + val black200: Color = Color.Unspecified, + val black300: Color = Color.Unspecified, + val black400: Color = Color.Unspecified, + val black500: Color = Color.Unspecified, + val black600: Color = Color.Unspecified, + val black700: Color = Color.Unspecified, + val black800: Color = Color.Unspecified, + val black900: Color = Color.Unspecified, val red50: Color = Color.Unspecified, val red100: Color = Color.Unspecified, @@ -40,32 +40,38 @@ data class NDGLColors( val red700: Color = Color.Unspecified, val red800: Color = Color.Unspecified, val red900: Color = Color.Unspecified, + + val etcGray: Color = Color.Unspecified, + val etcGreen: Color = Color.Unspecified, + val etcOrange: Color = Color.Unspecified, + val etcPurple: Color = Color.Unspecified, + val etcBlue: Color = Color.Unspecified, ) internal val ndglColors = NDGLColors( white = Color(0xFFFFFFFF), - primary50 = Color(0xFFF0FFF4), - primary100 = Color(0xFFDCFFE4), - primary200 = Color(0xFFBEF5CB), - primary300 = Color(0xFF85E89D), - primary400 = Color(0xFF18B368), - primary500 = Color(0xFF28A745), - primary600 = Color(0xFF22863A), - primary700 = Color(0xFF176F2C), - primary800 = Color(0xFF165C26), - primary900 = Color(0xFF144620), + green50 = Color(0xFFE9F8ED), + green100 = Color(0xFFCFF1D8), + green200 = Color(0xFFA3E4B3), + green300 = Color(0xFF73D08B), + green400 = Color(0xFF3EC45B), + green500 = Color(0xFF15C32D), + green600 = Color(0xFF10A425), + green700 = Color(0xFF0C7F1D), + green800 = Color(0xFF085C15), + green900 = Color(0xFF04340C), - secondary50 = Color(0xFFF5F5F5), - secondary100 = Color(0xFFE6E6E6), - secondary200 = Color(0xFFD9D9D9), - secondary300 = Color(0xFFB3B3B3), - secondary400 = Color(0xFF757575), - secondary500 = Color(0xFF444444), - secondary600 = Color(0xFF383838), - secondary700 = Color(0xFF2C2C2C), - secondary800 = Color(0xFF1E1E1E), - secondary900 = Color(0xFF111111), + black50 = Color(0xFFF5F5F5), + black100 = Color(0xFFE6E6E6), + black200 = Color(0xFFD9D9D9), + black300 = Color(0xFFB3B3B3), + black400 = Color(0xFF757575), + black500 = Color(0xFF444444), + black600 = Color(0xFF383838), + black700 = Color(0xFF2C2C2C), + black800 = Color(0xFF1E1E1E), + black900 = Color(0xFF111111), red50 = Color(0xFFFEF2F2), red100 = Color(0xFFFFE2E2), @@ -77,6 +83,12 @@ internal val ndglColors = NDGLColors( red700 = Color(0xFFC10007), red800 = Color(0xFF9F0712), red900 = Color(0xFF82181A), + + etcGray = Color(0xFF444444), + etcGreen = Color(0xFF15CD3F), + etcOrange = Color(0xFFFF6C11), + etcPurple = Color(0xFF5726E7), + etcBlue = Color(0xFF2960EC), ) val LocalNDGLColors = staticCompositionLocalOf { NDGLColors() } diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Theme.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Theme.kt index 919fa328..554850f5 100644 --- a/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Theme.kt +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Theme.kt @@ -28,8 +28,8 @@ fun NDGLTheme( LocalNDGLTypography provides ndglTypography, ) { val colorScheme = lightColorScheme( - primary = NDGLTheme.colors.primary500, - secondary = NDGLTheme.colors.secondary500, + primary = NDGLTheme.colors.green500, + secondary = NDGLTheme.colors.black500, background = NDGLTheme.colors.white, surface = NDGLTheme.colors.white, ) diff --git a/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Typography.kt b/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Typography.kt index 3dd6addf..8fb3e3e3 100644 --- a/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Typography.kt +++ b/core/ui/src/main/java/com/yapp/ndgl/core/ui/theme/Typography.kt @@ -2,18 +2,27 @@ package com.yapp.ndgl.core.ui.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontVariation import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import com.yapp.ndgl.core.ui.R private val Pretendard = FontFamily( - Font(R.font.pretendard, FontWeight.Normal), - Font(R.font.pretendard, FontWeight.Medium), - Font(R.font.pretendard, FontWeight.SemiBold), - Font(R.font.pretendard, FontWeight.Bold), + FontWeight.Normal.pretendardFont(), + FontWeight.Medium.pretendardFont(), + FontWeight.SemiBold.pretendardFont(), + FontWeight.Bold.pretendardFont(), +) + +@OptIn(ExperimentalTextApi::class) +private fun FontWeight.pretendardFont() = Font( + R.font.pretendard, + weight = this, + variationSettings = FontVariation.Settings(FontVariation.weight(weight)), ) @Immutable diff --git a/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelContract.kt b/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelContract.kt index 911dbae5..9d25014b 100644 --- a/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelContract.kt +++ b/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelContract.kt @@ -1,8 +1,8 @@ package com.yapp.ndgl.feature.travel -import com.yapp.ui.base.UiIntent -import com.yapp.ui.base.UiSideEffect -import com.yapp.ui.base.UiState +import com.yapp.ndgl.core.base.UiIntent +import com.yapp.ndgl.core.base.UiSideEffect +import com.yapp.ndgl.core.base.UiState data class TravelState( val displayText: String = "초기 상태", diff --git a/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelViewModel.kt b/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelViewModel.kt index 2838d247..c0121aa6 100644 --- a/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelViewModel.kt +++ b/feature/travel/src/main/java/com/yapp/ndgl/feature/travel/TravelViewModel.kt @@ -1,6 +1,6 @@ package com.yapp.ndgl.feature.travel -import com.yapp.ui.base.BaseViewModel +import com.yapp.ndgl.core.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject