diff --git a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt index 8a4ef10..a61aebd 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.utils.toPx import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlin.math.abs @@ -51,19 +52,28 @@ fun OrbitPickerItem( visibleItemsMiddle, startIndex, ) - - fun getItem(index: Int) = items.getOrNull(index % items.size) ?: "" - val listState = rememberLazyListState(initialFirstVisibleItemIndex = listStartIndex) val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState) val itemHeightPixels = remember { mutableIntStateOf(0) } val itemHeightDp = with(LocalDensity.current) { itemHeightPixels.intValue.toDp() } LaunchedEffect(listState) { - snapshotFlow { listState.firstVisibleItemIndex } - .map { index -> getItem(index + visibleItemsMiddle) } + snapshotFlow { listState.layoutInfo } + .map { layoutInfo -> + val centerOffset = layoutInfo.viewportStartOffset + (layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset) / 2 + + layoutInfo.visibleItemsInfo.minByOrNull { item -> + val itemCenter = item.offset + (item.size / 2) + abs(itemCenter - centerOffset) + }?.index + } .distinctUntilChanged() - .collect { item -> state.selectedItem = item } + .collect { centerIndex -> + if (centerIndex != null) { + val adjustedIndex = centerIndex % items.size + state.selectedItem = items[adjustedIndex] + } + } } val totalItemHeight = itemHeightDp + itemSpacing @@ -79,17 +89,24 @@ fun OrbitPickerItem( .pointerInput(Unit) { detectVerticalDragGestures { change, _ -> change.consume() } }, ) { items(listScrollCount) { index -> - val centerIndex = listState.firstVisibleItemIndex + visibleItemsMiddle - val distanceFromCenter = abs(index - centerIndex) - val maxDistance = visibleItemsMiddle.toFloat() + val layoutInfo = listState.layoutInfo + val viewportCenterOffset = layoutInfo.viewportStartOffset + + (layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset) / 2 + + val itemInfo = layoutInfo.visibleItemsInfo.find { it.index == index } + val itemCenterOffset = itemInfo?.offset?.let { it + (itemInfo.size / 2) } ?: 0 + + val distanceFromCenter = abs(viewportCenterOffset - itemCenterOffset) + val maxDistance = totalItemHeight.toPx() * visibleItemsMiddle val alpha = ((maxDistance - distanceFromCenter) / maxDistance).coerceIn(0.2f, 1f) val scaleY = 1f - (0.4f * (distanceFromCenter / maxDistance)).coerceIn(0f, 0.4f) + val isSelected = getItemForIndex(index, items, infiniteScroll, visibleItemsMiddle) == state.selectedItem Text( text = getItemForIndex(index, items, infiniteScroll, visibleItemsMiddle), maxLines = 1, style = textStyle, - color = OrbitTheme.colors.white.copy(alpha = alpha), + color = if (isSelected) OrbitTheme.colors.white else OrbitTheme.colors.white.copy(alpha = alpha), modifier = Modifier .padding(vertical = itemSpacing / 2) .graphicsLayer(scaleY = scaleY) diff --git a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt index 39599e5..43ddaa9 100644 --- a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt +++ b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt @@ -8,6 +8,7 @@ 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.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -16,6 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -27,6 +29,8 @@ fun OrbitYearMonthPicker( modifier: Modifier = Modifier, itemSpacing: Dp = 12.dp, ) { + val screenWidth = LocalConfiguration.current.screenWidthDp.dp + Surface( modifier = modifier .fillMaxWidth() @@ -54,18 +58,19 @@ fun OrbitYearMonthPicker( Box( modifier = Modifier.fillMaxWidth(), ) { + val totalItemHeight = screenWidth * 0.15f Box( modifier = Modifier .fillMaxWidth() .align(Alignment.Center) - .padding(horizontal = 20.dp) - .height(50.dp) + .padding(horizontal = screenWidth * 0.05f) + .height(totalItemHeight) .background(OrbitTheme.colors.gray_700, shape = RoundedCornerShape(12.dp)), ) Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 50.dp), + .padding(horizontal = screenWidth * 0.1f), verticalAlignment = Alignment.CenterVertically, ) { OrbitPickerItem( @@ -73,7 +78,7 @@ fun OrbitYearMonthPicker( items = lunarItems, visibleItemsCount = 3, infiniteScroll = false, - modifier = Modifier.weight(1f), + modifier = Modifier.width(screenWidth * 0.2f), textModifier = Modifier.padding(8.dp), textStyle = OrbitTheme.typography.title2SemiBold, itemSpacing = itemSpacing, @@ -84,7 +89,7 @@ fun OrbitYearMonthPicker( visibleItemsCount = 5, infiniteScroll = true, startIndex = 90, - modifier = Modifier.weight(1.5f), + modifier = Modifier.width(screenWidth * 0.25f), textModifier = Modifier.padding(8.dp), textStyle = OrbitTheme.typography.title2SemiBold, itemSpacing = itemSpacing, @@ -94,7 +99,7 @@ fun OrbitYearMonthPicker( items = monthItems, visibleItemsCount = 5, infiniteScroll = true, - modifier = Modifier.weight(1f), + modifier = Modifier.width(screenWidth * 0.16f), textModifier = Modifier.padding(8.dp), textStyle = OrbitTheme.typography.title2SemiBold, itemSpacing = itemSpacing, @@ -104,7 +109,7 @@ fun OrbitYearMonthPicker( items = dayItems, visibleItemsCount = 5, infiniteScroll = true, - modifier = Modifier.weight(1f), + modifier = Modifier.width(screenWidth * 0.16f), textModifier = Modifier.padding(8.dp), textStyle = OrbitTheme.typography.title2SemiBold, itemSpacing = itemSpacing, diff --git a/core/ui/src/main/java/com/yapp/ui/utils/DpToPx.kt b/core/ui/src/main/java/com/yapp/ui/utils/DpToPx.kt new file mode 100644 index 0000000..5733c27 --- /dev/null +++ b/core/ui/src/main/java/com/yapp/ui/utils/DpToPx.kt @@ -0,0 +1,10 @@ +package com.yapp.ui.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp + +@Composable +fun Dp.toPx(): Float { + return with(LocalDensity.current) { this@toPx.toPx() } +} diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnBoardingScreen.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnBoardingScreen.kt index 32c5511..20e87c6 100644 --- a/feature/onboarding/src/main/java/com/kms/onboarding/OnBoardingScreen.kt +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnBoardingScreen.kt @@ -1,22 +1,14 @@ package com.kms.onboarding import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue 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 androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost @@ -25,9 +17,6 @@ import com.kms.onboarding.component.OnboardingBottomBar import com.kms.onboarding.navigation.onboardingNavGraph import com.kms.onboarding.navigation.rememberOnboardingAppState import com.yapp.designsystem.theme.OrbitTheme -import com.yapp.ui.component.timepicker.OrbitPicker -import com.yapp.ui.utils.heightForScreenPercentage -import feature.onboarding.R @Composable fun OnboardingRoute( @@ -105,114 +94,3 @@ fun OnboardingScreen( } } } - -@Composable -fun ExplainScreen( - state: OnboardingContract.State, - onNextClick: () -> Unit, -) { - OnboardingScreen( - currentStep = 0, - totalSteps = 0, - isButtonEnabled = true, - onNextClick = onNextClick, - onBackClick = null, - showTopAppBar = false, - ) { - Column(modifier = Modifier.fillMaxSize()) { - Spacer(modifier = Modifier.heightForScreenPercentage(0.105f)) - Text( - text = stringResource(id = R.string.onboarding_step1_text_title), - style = OrbitTheme.typography.body1Regular, - color = OrbitTheme.colors.gray_100, - modifier = Modifier - .padding(horizontal = 20.dp) - .fillMaxWidth(), - textAlign = TextAlign.Center, - ) - Text( - text = stringResource(id = R.string.onboarding_step1_text_subtitle), - style = OrbitTheme.typography.heading1SemiBold, - color = OrbitTheme.colors.white, - modifier = Modifier - .padding(horizontal = 20.dp) - .padding(top = 12.dp) - .fillMaxWidth(), - textAlign = TextAlign.Center, - ) - } - } -} - -@Composable -fun AlarmTimeSelectionScreen( - state: OnboardingContract.State, - currentStep: Int, - totalSteps: Int, - onNextClick: () -> Unit, - onBackClick: () -> Unit, -) { - OnboardingScreen( - currentStep = currentStep, - totalSteps = totalSteps, - isButtonEnabled = true, - onNextClick = onNextClick, - onBackClick = onBackClick, - ) { - Column(modifier = Modifier.fillMaxSize()) { - Spacer(modifier = Modifier.heightForScreenPercentage(0.05f)) - Text( - text = stringResource(id = R.string.onboarding_step2_text_title), - style = OrbitTheme.typography.heading1SemiBold, - color = OrbitTheme.colors.white, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - ) - Text( - text = stringResource(id = R.string.onboarding_step2_text_subtitle), - style = OrbitTheme.typography.body1Regular, - color = OrbitTheme.colors.gray_100, - modifier = Modifier - .padding(top = 4.dp) - .fillMaxWidth(), - textAlign = TextAlign.Center, - ) - OrbitPicker( - modifier = Modifier.padding(top = 90.dp), - ) - } - } -} - -@Composable -fun BirthdayScreen( - state: OnboardingContract.State, - currentStep: Int, - totalSteps: Int, - onNextClick: () -> Unit, - onBackClick: () -> Unit, -) { - OnboardingScreen( - currentStep = currentStep, - totalSteps = totalSteps, - isButtonEnabled = true, - onNextClick = onNextClick, - onBackClick = onBackClick, - ) { - Column { } - } -} - -@Composable -@Preview -fun OnboardingPreview() { - OrbitTheme { - AlarmTimeSelectionScreen( - state = OnboardingContract.State(), - currentStep = 1, - totalSteps = 2, - onNextClick = {}, - onBackClick = {}, - ) - } -} diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingAlarmTimeSelectionScreen.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingAlarmTimeSelectionScreen.kt new file mode 100644 index 0000000..6a2aeab --- /dev/null +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingAlarmTimeSelectionScreen.kt @@ -0,0 +1,57 @@ +package com.kms.onboarding + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.component.timepicker.OrbitPicker +import com.yapp.ui.utils.heightForScreenPercentage +import feature.onboarding.R + +@Composable +fun OnboardingAlarmTimeSelectionScreen( + state: OnboardingContract.State, + currentStep: Int, + totalSteps: Int, + onNextClick: () -> Unit, + onBackClick: () -> Unit, +) { + OnboardingScreen( + currentStep = currentStep, + totalSteps = totalSteps, + isButtonEnabled = true, + onNextClick = onNextClick, + onBackClick = onBackClick, + ) { + Column(modifier = Modifier.fillMaxSize()) { + Spacer(modifier = Modifier.heightForScreenPercentage(0.05f)) + Text( + text = stringResource(id = R.string.onboarding_step2_text_title), + style = OrbitTheme.typography.heading1SemiBold, + color = OrbitTheme.colors.white, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + Text( + text = stringResource(id = R.string.onboarding_step2_text_subtitle), + style = OrbitTheme.typography.body1Regular, + color = OrbitTheme.colors.gray_100, + modifier = Modifier + .padding(top = 4.dp) + .fillMaxWidth(), + textAlign = TextAlign.Center, + ) + OrbitPicker( + modifier = Modifier.padding(top = 90.dp), + ) + } + } +} diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingBirthdayScreen.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingBirthdayScreen.kt new file mode 100644 index 0000000..5c6a3f1 --- /dev/null +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingBirthdayScreen.kt @@ -0,0 +1,63 @@ +package com.kms.onboarding + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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 androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.component.timepicker.OrbitYearMonthPicker +import com.yapp.ui.utils.heightForScreenPercentage +import feature.onboarding.R + +@Composable +fun OnboardingBirthdayScreen( + state: OnboardingContract.State, + currentStep: Int, + totalSteps: Int, + onNextClick: () -> Unit, + onBackClick: () -> Unit, +) { + OnboardingScreen( + currentStep = currentStep, + totalSteps = totalSteps, + isButtonEnabled = true, + onNextClick = onNextClick, + onBackClick = onBackClick, + ) { + Column(modifier = Modifier.fillMaxSize()) { + Spacer(modifier = Modifier.heightForScreenPercentage(0.05f)) + Text( + text = stringResource(id = R.string.onboarding_step3_text_title), + style = OrbitTheme.typography.heading1SemiBold, + color = OrbitTheme.colors.white, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + OrbitYearMonthPicker( + modifier = Modifier.padding(top = 60.dp), + ) + } + } +} + +@Composable +@Preview +fun OnboardingBirthdayScreenPreview() { + OrbitTheme { + OnboardingBirthdayScreen( + state = OnboardingContract.State(), + currentStep = 3, + totalSteps = 3, + onNextClick = {}, + onBackClick = {}, + ) + } +} diff --git a/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingExplainScreen.kt b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingExplainScreen.kt new file mode 100644 index 0000000..d3d737c --- /dev/null +++ b/feature/onboarding/src/main/java/com/kms/onboarding/OnboardingExplainScreen.kt @@ -0,0 +1,54 @@ +package com.kms.onboarding + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.yapp.designsystem.theme.OrbitTheme +import com.yapp.ui.utils.heightForScreenPercentage +import feature.onboarding.R + +@Composable +fun OnboardingExplainScreen( + state: OnboardingContract.State, + onNextClick: () -> Unit, +) { + OnboardingScreen( + currentStep = 0, + totalSteps = 0, + isButtonEnabled = true, + onNextClick = onNextClick, + onBackClick = null, + showTopAppBar = false, + ) { + Column(modifier = Modifier.fillMaxSize()) { + Spacer(modifier = Modifier.heightForScreenPercentage(0.105f)) + Text( + text = stringResource(id = R.string.onboarding_step1_text_title), + style = OrbitTheme.typography.body1Regular, + color = OrbitTheme.colors.gray_100, + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth(), + textAlign = TextAlign.Center, + ) + Text( + text = stringResource(id = R.string.onboarding_step1_text_subtitle), + style = OrbitTheme.typography.heading1SemiBold, + color = OrbitTheme.colors.white, + modifier = Modifier + .padding(horizontal = 20.dp) + .padding(top = 12.dp) + .fillMaxWidth(), + textAlign = TextAlign.Center, + ) + } + } +} 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 54dc47f..e5ca984 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 @@ -2,10 +2,10 @@ package com.kms.onboarding.navigation import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.kms.onboarding.AlarmTimeSelectionScreen -import com.kms.onboarding.BirthdayScreen -import com.kms.onboarding.ExplainScreen +import com.kms.onboarding.OnboardingAlarmTimeSelectionScreen +import com.kms.onboarding.OnboardingBirthdayScreen import com.kms.onboarding.OnboardingContract +import com.kms.onboarding.OnboardingExplainScreen fun NavGraphBuilder.onboardingNavGraph( stateProvider: () -> OnboardingContract.State, @@ -13,7 +13,7 @@ fun NavGraphBuilder.onboardingNavGraph( onFinishOnboarding: () -> Unit, ) { composable(OnboardingDestination.Explain.route) { - ExplainScreen( + OnboardingExplainScreen( state = stateProvider(), onNextClick = { eventDispatcher(OnboardingContract.Action.NextStep) @@ -22,7 +22,7 @@ fun NavGraphBuilder.onboardingNavGraph( } composable(OnboardingDestination.AlarmTimeSelection.route) { - AlarmTimeSelectionScreen( + OnboardingAlarmTimeSelectionScreen( state = stateProvider(), currentStep = 1, totalSteps = 2, @@ -36,7 +36,7 @@ fun NavGraphBuilder.onboardingNavGraph( } composable(OnboardingDestination.Birthday.route) { - BirthdayScreen( + OnboardingBirthdayScreen( state = stateProvider(), currentStep = 2, totalSteps = 2,