diff --git a/core/designsystem/src/main/java/com/yapp/designsystem/theme/OrbitTypography.kt b/core/designsystem/src/main/java/com/yapp/designsystem/theme/OrbitTypography.kt index b5b86613..f4b22e5e 100644 --- a/core/designsystem/src/main/java/com/yapp/designsystem/theme/OrbitTypography.kt +++ b/core/designsystem/src/main/java/com/yapp/designsystem/theme/OrbitTypography.kt @@ -154,6 +154,13 @@ data class OrbitTypography( lineHeight = 18.sp, letterSpacing = (-0.13).sp, ), + val label2Regular: TextStyle = TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Normal, + fontSize = 13.sp, + lineHeight = 18.sp, + letterSpacing = (-0.13).sp, + ), val caption1Regular: TextStyle = TextStyle( fontFamily = Pretendard, fontWeight = FontWeight.Normal, diff --git a/core/ui/src/main/java/com/yapp/ui/utils/ScreenPercentage.kt b/core/ui/src/main/java/com/yapp/ui/utils/ScreenPercentage.kt index 6956e813..476153ed 100644 --- a/core/ui/src/main/java/com/yapp/ui/utils/ScreenPercentage.kt +++ b/core/ui/src/main/java/com/yapp/ui/utils/ScreenPercentage.kt @@ -22,6 +22,7 @@ fun Modifier.widthForScreenPercentage(percentage: Float): Modifier { @Composable fun Modifier.paddingForScreenPercentage( + allPercentage: Float = 0f, horizontalPercentage: Float = 0f, verticalPercentage: Float = 0f, topPercentage: Float = 0f, @@ -31,10 +32,11 @@ fun Modifier.paddingForScreenPercentage( val screenWidth = configuration.screenWidthDp.dp val screenHeight = configuration.screenHeightDp.dp - val horizontalPadding = screenWidth * horizontalPercentage - val verticalPadding = screenHeight * verticalPercentage - val topPadding = screenHeight * topPercentage + verticalPadding - val bottomPadding = screenHeight * bottomPercentage + verticalPadding + val allPadding = screenHeight * allPercentage + val horizontalPadding = if (allPercentage > 0f) allPadding else screenWidth * horizontalPercentage + val verticalPadding = if (allPercentage > 0f) allPadding else screenHeight * verticalPercentage + val topPadding = if (allPercentage > 0f) allPadding else screenHeight * topPercentage + verticalPadding + val bottomPadding = if (allPercentage > 0f) allPadding else screenHeight * bottomPercentage + verticalPadding return this.padding( start = horizontalPadding, diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingContract.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingContract.kt index fe26b235..019124ae 100644 --- a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingContract.kt +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingContract.kt @@ -9,6 +9,7 @@ sealed class OnboardingContract { val textFieldValue: String = "", val showWarning: Boolean = false, val isButtonEnabled: Boolean = false, + val selectedGender: String? = null, ) : UiState sealed class Action { @@ -17,6 +18,7 @@ sealed class OnboardingContract { data class UpdateField(val value: String, val fieldType: FieldType) : Action() data object Reset : Action() data class Submit(val stepData: Map) : Action() + data class UpdateGender(val gender: String) : Action() } enum class FieldType(val validationRegex: Regex) { diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingGenderScreen.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingGenderScreen.kt new file mode 100644 index 00000000..a0df7d5c --- /dev/null +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingGenderScreen.kt @@ -0,0 +1,89 @@ +package com.kms.onboarding + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.kms.onboarding.component.OnboardingGenderToggle +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.utils.heightForScreenPercentage +import com.yapp.ui.utils.paddingForScreenPercentage +import com.yapp.ui.utils.widthForScreenPercentage +import feature.onboarding.R + +@Composable +fun OnboardingGenderScreen( + state: OnboardingContract.State, + currentStep: Int, + totalSteps: Int, + onNextClick: () -> Unit, + onBackClick: () -> Unit, + onGenderSelect: (String) -> Unit, +) { + OnboardingScreen( + currentStep = currentStep, + totalSteps = totalSteps, + isButtonEnabled = state.selectedGender != null, + onNextClick = {}, + onBackClick = onBackClick, + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.heightForScreenPercentage(0.05f)) + Text( + text = stringResource(id = R.string.onboarding_step6_text_title), + style = OrbitTheme.typography.heading1SemiBold, + color = OrbitTheme.colors.white, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .paddingForScreenPercentage(topPercentage = 0.11f), + horizontalArrangement = Arrangement.Center, + ) { + OnboardingGenderToggle( + label = "남성", + isSelected = state.selectedGender == "남성", + onToggle = { onGenderSelect("남성") }, + ) + Spacer(modifier = Modifier.widthForScreenPercentage(0.04f)) + OnboardingGenderToggle( + label = "여성", + isSelected = state.selectedGender == "여성", + onToggle = { onGenderSelect("여성") }, + ) + } + } + } +} + +@Composable +@Preview +fun OnboardingGenderScreenPreview() { + OnboardingGenderScreen( + state = OnboardingContract.State( + isButtonEnabled = true, + ), + currentStep = 0, + totalSteps = 0, + onNextClick = {}, + onBackClick = {}, + onGenderSelect = {}, + ) +} diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingViewModel.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingViewModel.kt index 27fd8e29..eac17fdc 100644 --- a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingViewModel.kt @@ -24,6 +24,7 @@ class OnboardingViewModel @Inject constructor( is OnboardingContract.Action.UpdateField -> updateField(action.value, action.fieldType) is OnboardingContract.Action.Reset -> resetFields() is OnboardingContract.Action.Submit -> handleSubmission(action.stepData) + is OnboardingContract.Action.UpdateGender -> updateGender(action.gender) } } @@ -69,6 +70,10 @@ class OnboardingViewModel @Inject constructor( } } + private fun updateGender(gender: String) { + updateState { copy(selectedGender = gender, isButtonEnabled = true) } + } + private fun handleSubmission(stepData: Map) { emitSideEffect(OnboardingContract.SideEffect.OnboardingCompleted) } diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/component/OnboardingGenderToggle.kt b/feature/onboarding/src/main/java/com/kms/onboarding/component/OnboardingGenderToggle.kt new file mode 100644 index 00000000..1d95f2fc --- /dev/null +++ b/feature/onboarding/src/main/java/com/kms/onboarding/component/OnboardingGenderToggle.kt @@ -0,0 +1,104 @@ +package com.kms.onboarding.component + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +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.Box +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +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.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.utils.paddingForScreenPercentage + +@Composable +fun OnboardingGenderToggle( + label: String, + isSelected: Boolean, + onToggle: () -> Unit, + modifier: Modifier = Modifier, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + val backgroundColor = when { + isPressed -> OrbitTheme.colors.gray_700 + isSelected -> OrbitTheme.colors.main.copy(alpha = 0.1f) + else -> OrbitTheme.colors.gray_800 + } + val borderColor = when { + isSelected -> OrbitTheme.colors.main.copy(alpha = 0.4f) + isPressed -> Color.Transparent + else -> OrbitTheme.colors.gray_600 + } + val contentColor = when { + isPressed -> OrbitTheme.colors.white + isSelected -> OrbitTheme.colors.sub_main + else -> OrbitTheme.colors.white + } + + val innerSizeScale by animateFloatAsState( + targetValue = if (isPressed) 0.95f else 1f, + animationSpec = tween(durationMillis = 100), + label = "SizeAnimation", + ) + + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .wrapContentSize() + .border( + width = if (isPressed) 0.dp else 1.dp, + color = borderColor, + shape = RoundedCornerShape(16.dp), + ) + .background(backgroundColor, shape = RoundedCornerShape(16.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = !isSelected, + ) { + onToggle() + }, + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .paddingForScreenPercentage(verticalPercentage = 0.074f, horizontalPercentage = 0.144f) + .wrapContentSize() + .scale(innerSizeScale), + ) { + Text( + text = label, + color = contentColor, + style = OrbitTheme.typography.heading2SemiBold, + ) + } + } +} + +@Preview +@Composable +fun PreviewSquareToggleButton() { + var isSelected by remember { mutableStateOf(false) } + + OnboardingGenderToggle( + label = "남성", + isSelected = isSelected, + onToggle = { isSelected = !isSelected }, + ) +} diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingDestination.kt b/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingDestination.kt index d33f5f92..fd9ac72b 100644 --- a/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingDestination.kt +++ b/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingDestination.kt @@ -6,9 +6,10 @@ sealed class OnboardingDestination(val route: String) { data object Birthday : OnboardingDestination(Routes.BIRTHDAY) data object TimeOfBirth : OnboardingDestination(Routes.TIME_OF_BIRTH) data object Name : OnboardingDestination(Routes.NAME) + data object Gender : OnboardingDestination(Routes.GENDER) companion object { - private val routes = listOf(Explain, AlarmTimeSelection, Birthday, TimeOfBirth, Name) + private val routes = listOf(Explain, AlarmTimeSelection, Birthday, TimeOfBirth, Name, Gender) fun nextRoute(currentStep: Int): String? { return routes.getOrNull(currentStep)?.route diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingNavGraph.kt b/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingNavGraph.kt index f685a2a5..2dcdbb74 100644 --- a/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingNavGraph.kt +++ b/feature/onboarding/src/main/java/com/kms/onboarding/navigation/OnboardingNavGraph.kt @@ -7,6 +7,7 @@ import com.kms.onboarding.OnboardingAlarmTimeSelectionScreen import com.kms.onboarding.OnboardingBirthdayScreen import com.kms.onboarding.OnboardingContract import com.kms.onboarding.OnboardingExplainScreen +import com.kms.onboarding.OnboardingGenderScreen import com.kms.onboarding.OnboardingNameScreen import com.kms.onboarding.OnboardingTimeOfBirthScreen @@ -28,7 +29,7 @@ fun NavGraphBuilder.onboardingNavGraph( OnboardingAlarmTimeSelectionScreen( state = stateProvider(), currentStep = 1, - totalSteps = 4, + totalSteps = 5, onNextClick = { eventDispatcher(OnboardingContract.Action.NextStep) }, @@ -42,7 +43,7 @@ fun NavGraphBuilder.onboardingNavGraph( OnboardingBirthdayScreen( state = stateProvider(), currentStep = 2, - totalSteps = 4, + totalSteps = 5, onNextClick = { eventDispatcher(OnboardingContract.Action.NextStep) }, @@ -57,7 +58,7 @@ fun NavGraphBuilder.onboardingNavGraph( OnboardingTimeOfBirthScreen( state = stateProvider(), currentStep = 3, - totalSteps = 4, + totalSteps = 5, onNextClick = { eventDispatcher(OnboardingContract.Action.NextStep) eventDispatcher(OnboardingContract.Action.Reset) @@ -75,7 +76,7 @@ fun NavGraphBuilder.onboardingNavGraph( OnboardingNameScreen( state = stateProvider(), currentStep = 4, - totalSteps = 4, + totalSteps = 5, onNextClick = { eventDispatcher(OnboardingContract.Action.NextStep) }, @@ -87,4 +88,19 @@ fun NavGraphBuilder.onboardingNavGraph( }, ) } + composable(OnboardingDestination.Gender.route) { + OnboardingGenderScreen( + state = stateProvider(), + currentStep = 5, + totalSteps = 5, + onNextClick = { + }, + onBackClick = { + eventDispatcher(OnboardingContract.Action.PreviousStep) + }, + onGenderSelect = { gender -> + eventDispatcher(OnboardingContract.Action.UpdateGender(gender)) + }, + ) + } } diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/navigation/Routes.kt b/feature/onboarding/src/main/java/com/kms/onboarding/navigation/Routes.kt index 1b911499..d470bf58 100644 --- a/feature/onboarding/src/main/java/com/kms/onboarding/navigation/Routes.kt +++ b/feature/onboarding/src/main/java/com/kms/onboarding/navigation/Routes.kt @@ -6,4 +6,5 @@ object Routes { const val BIRTHDAY = "onboarding_birthday" const val TIME_OF_BIRTH = "onboarding_time_of_birth" const val NAME = "onboarding_name" + const val GENDER = "onboarding_gender" } diff --git a/feature/onboarding/src/main/res/values/values.xml b/feature/onboarding/src/main/res/values/values.xml index a2b74cab..c3be2efc 100644 --- a/feature/onboarding/src/main/res/values/values.xml +++ b/feature/onboarding/src/main/res/values/values.xml @@ -16,6 +16,22 @@ 어떤 이름으로 불리길\n원하시나요? 입력한 내용을 확인해 주세요 + 성별을 알려주세요 + + 입력하신 정보가 맞나요? + 이름 + 성별 + 생년월일 + 태어난 시간 + 맞아요 + 아니에요 + + 알람을 받으려면 꼭 필요해요\n다음 화면에서 ‘허용’을 눌러주세요 + ‘오르비’에서 알림을 보내고자 합니다 + 알림 기능이 꺼져있으면\n알람이 울릴 때 확인에 어려움이 있어요 + 허용 + 허용안함 +