diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt index 69af8875..72fdebf9 100644 --- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt +++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilProgressBar.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.tooling.preview.Preview @@ -59,11 +58,7 @@ fun BitnagilProgressBar( if (animatedProgress > 0) { val progressEndX = size.width * animatedProgress drawLine( - brush = Brush.horizontalGradient( - colors = listOf(color.gradientStartColor, color.gradientEndColor), - startX = 0f, - endX = progressEndX, - ), + color = color.progressColor, start = Offset(0f, size.height / 2), end = Offset(progressEndX, size.height / 2), strokeWidth = strokeWidth, @@ -75,16 +70,14 @@ fun BitnagilProgressBar( @Immutable data class BitnagilProgressBarColor( - val gradientStartColor: Color, - val gradientEndColor: Color, + val progressColor: Color, val backgroundColor: Color, ) { companion object { @Composable fun default(): BitnagilProgressBarColor = BitnagilProgressBarColor( - gradientStartColor = BitnagilTheme.colors.progressBarGradientStartColor, - gradientEndColor = BitnagilTheme.colors.progressBarGradientEndColor, - backgroundColor = BitnagilTheme.colors.white, + progressColor = BitnagilTheme.colors.orange500, + backgroundColor = BitnagilTheme.colors.coolGray97, ) } } diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt index 11c9f50b..6621a8fb 100644 --- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt +++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilProgressTopBar.kt @@ -20,6 +20,7 @@ fun BitnagilProgressTopBar( progress: Float, onBackClick: () -> Unit, modifier: Modifier = Modifier, + showProgress: Boolean, ) { Row( modifier = modifier @@ -35,10 +36,12 @@ fun BitnagilProgressTopBar( tint = BitnagilTheme.colors.coolGray10, ) - BitnagilProgressBar( - progress = progress, - modifier = Modifier, - ) + if (showProgress) { + BitnagilProgressBar( + progress = progress, + modifier = Modifier, + ) + } } } @@ -48,5 +51,6 @@ private fun BitnagilProgressTopBarPreview() { BitnagilProgressTopBar( progress = 1f, onBackClick = {}, + showProgress = true, ) } diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_pomo_flower.png b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_flower.png new file mode 100644 index 00000000..c28fd26d Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_flower.png differ diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_pomo_memo.png b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_memo.png new file mode 100644 index 00000000..5ca98c06 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_memo.png differ diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_1.9.png b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_1.9.png new file mode 100644 index 00000000..5928b0a9 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_1.9.png differ diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_2.9.png b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_2.9.png new file mode 100644 index 00000000..cf372566 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_2.9.png differ diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_3.9.png b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_3.9.png new file mode 100644 index 00000000..3c365430 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_3.9.png differ diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_4.9.png b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_4.9.png new file mode 100644 index 00000000..1b30ca04 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_texture_rectangle_4.9.png differ diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_pomo_flower.png b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_flower.png new file mode 100644 index 00000000..10559a97 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_flower.png differ diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_pomo_memo.png b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_memo.png new file mode 100644 index 00000000..72be726f Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_memo.png differ diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_1.9.png b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_1.9.png new file mode 100644 index 00000000..1c9c4167 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_1.9.png differ diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_2.9.png b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_2.9.png new file mode 100644 index 00000000..e4d896bd Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_2.9.png differ diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_3.9.png b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_3.9.png new file mode 100644 index 00000000..ec3d4761 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_3.9.png differ diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_4.9.png b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_4.9.png new file mode 100644 index 00000000..ab193793 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_texture_rectangle_4.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_flower.png b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_flower.png new file mode 100644 index 00000000..70ce9e53 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_flower.png differ diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_memo.png b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_memo.png new file mode 100644 index 00000000..6b7f8296 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_memo.png differ diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_1.9.png b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_1.9.png new file mode 100644 index 00000000..2c546a4a Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_1.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_2.9.png b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_2.9.png new file mode 100644 index 00000000..987e2ce2 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_2.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_3.9.png b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_3.9.png new file mode 100644 index 00000000..e271ed07 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_3.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_4.9.png b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_4.9.png new file mode 100644 index 00000000..72e6c399 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_texture_rectangle_4.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_flower.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_flower.png new file mode 100644 index 00000000..e34aa10f Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_flower.png differ diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_memo.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_memo.png new file mode 100644 index 00000000..9550794f Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_memo.png differ diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_1.9.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_1.9.png new file mode 100644 index 00000000..7c13cc41 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_1.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_2.9.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_2.9.png new file mode 100644 index 00000000..87bdcf9c Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_2.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_3.9.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_3.9.png new file mode 100644 index 00000000..ff030d92 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_3.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_4.9.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_4.9.png new file mode 100644 index 00000000..7f661581 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_texture_rectangle_4.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_flower.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_flower.png new file mode 100644 index 00000000..cc486c8f Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_flower.png differ diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_memo.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_memo.png new file mode 100644 index 00000000..901e865a Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_memo.png differ diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_1.9.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_1.9.png new file mode 100644 index 00000000..2893dcc7 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_1.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_2.9.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_2.9.png new file mode 100644 index 00000000..365151f6 Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_2.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_3.9.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_3.9.png new file mode 100644 index 00000000..37bd123c Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_3.9.png differ diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_4.9.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_4.9.png new file mode 100644 index 00000000..e8f67b6d Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_texture_rectangle_4.9.png differ diff --git a/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt index 7cb69bb7..8e84075f 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt @@ -4,10 +4,12 @@ import com.threegap.bitnagil.data.onboarding.model.dto.OnBoardingAbstractDto import com.threegap.bitnagil.data.onboarding.model.dto.OnBoardingDto import com.threegap.bitnagil.data.onboarding.model.request.GetOnBoardingRecommendRoutinesRequest import com.threegap.bitnagil.data.onboarding.model.response.GetOnBoardingRecommendRoutinesResponse +import com.threegap.bitnagil.data.onboarding.model.response.GetUserOnBoardingResponse interface OnBoardingDataSource { suspend fun getOnBoardingList(): List suspend fun getOnBoardingRecommendRoutines(request: GetOnBoardingRecommendRoutinesRequest): Result suspend fun getOnBoardingAbstract(selectedOnBoardingItemIdList: List>>): OnBoardingAbstractDto suspend fun registerRecommendRoutineList(selectedRecommendRoutineIds: List): Result + suspend fun getUserOnBoarding(): Result } diff --git a/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt index 284788c3..375cab14 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt @@ -11,6 +11,7 @@ import com.threegap.bitnagil.data.onboarding.model.dto.OnBoardingItemDto import com.threegap.bitnagil.data.onboarding.model.request.GetOnBoardingRecommendRoutinesRequest import com.threegap.bitnagil.data.onboarding.model.request.RegisterOnBoardingRecommendRoutinesRequest import com.threegap.bitnagil.data.onboarding.model.response.GetOnBoardingRecommendRoutinesResponse +import com.threegap.bitnagil.data.onboarding.model.response.GetUserOnBoardingResponse import com.threegap.bitnagil.data.onboarding.service.OnBoardingService import javax.inject.Inject @@ -57,6 +58,12 @@ class OnBoardingDataSourceImpl @Inject constructor( } } + override suspend fun getUserOnBoarding(): Result { + return safeApiCall { + onBoardingService.getOnBoarding() + } + } + private fun getOnBoardingAbstractText(onBoardingId: String, selectedOnBoardingDetailIdList: List): OnBoardingAbstractTextDto? { if (selectedOnBoardingDetailIdList.isEmpty()) return null val onBoarding = onBoardingDtoList.find { it.id == onBoardingId } diff --git a/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt b/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt index 02736d75..17fd06fb 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt @@ -8,7 +8,7 @@ data class GetOnBoardingRecommendRoutinesRequest( @SerialName("timeSlot") val timeSlot: String, @SerialName("emotionType") - val emotionType: String, + val emotionType: List, @SerialName("realOutingFrequency") val realOutingFrequency: String, @SerialName("targetOutingFrequency") diff --git a/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/response/GetUserOnBoardingResponse.kt b/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/response/GetUserOnBoardingResponse.kt new file mode 100644 index 00000000..5533af4b --- /dev/null +++ b/data/src/main/java/com/threegap/bitnagil/data/onboarding/model/response/GetUserOnBoardingResponse.kt @@ -0,0 +1,14 @@ +package com.threegap.bitnagil.data.onboarding.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetUserOnBoardingResponse( + @SerialName("timeSlot") + val timeSlot: String, + @SerialName("emotionTypes") + val emotionTypes: List, + @SerialName("targetOutingFrequency") + val targetOutingFrequency: String, +) diff --git a/data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt index 2a70a7a7..83fafa21 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt @@ -34,13 +34,13 @@ class OnBoardingRepositoryImpl @Inject constructor( selectedItemIdsWithOnBoardingId: List>>, ): Result> { val timeSlot = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.TimeSlot.id }?.second?.first() - val emotionType = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.EmotionType.id }?.second?.first() + val emotionType = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.EmotionType.id }?.second val realOutingFrequency = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.RealOutingFrequency.id }?.second?.first() val targetOutingFrequency = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.TargetOutingFrequency.id }?.second?.first() val request = GetOnBoardingRecommendRoutinesRequest( timeSlot = timeSlot ?: "", - emotionType = emotionType ?: "", + emotionType = emotionType ?: listOf(), realOutingFrequency = realOutingFrequency ?: "", targetOutingFrequency = targetOutingFrequency ?: "", ) @@ -64,6 +64,16 @@ class OnBoardingRepositoryImpl @Inject constructor( } } + override suspend fun getUserOnBoarding(): Result>>> { + return onBoardingDataSource.getUserOnBoarding().map { + listOf( + OnBoardingDto.TimeSlot.id to listOf(it.timeSlot), + OnBoardingDto.EmotionType.id to it.emotionTypes, + OnBoardingDto.TargetOutingFrequency.id to listOf(it.targetOutingFrequency), + ) + } + } + private val _onBoardingRecommendRoutineEventFlow = MutableSharedFlow( extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, diff --git a/data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt b/data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt index 50b62c9a..98c27b67 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt @@ -3,18 +3,23 @@ package com.threegap.bitnagil.data.onboarding.service import com.threegap.bitnagil.data.onboarding.model.request.GetOnBoardingRecommendRoutinesRequest import com.threegap.bitnagil.data.onboarding.model.request.RegisterOnBoardingRecommendRoutinesRequest import com.threegap.bitnagil.data.onboarding.model.response.GetOnBoardingRecommendRoutinesResponse +import com.threegap.bitnagil.data.onboarding.model.response.GetUserOnBoardingResponse import com.threegap.bitnagil.network.model.BaseResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST interface OnBoardingService { - @POST("/api/v1/onboardings") + @POST("/api/v2/onboardings") suspend fun postOnBoarding( @Body onBoardingRecommendRoutinesRequest: GetOnBoardingRecommendRoutinesRequest, ): BaseResponse - @POST("/api/v1/onboardings/routines") + @POST("/api/v2/onboardings/routines") suspend fun postOnBoardingRoutines( @Body registerOnBoardingRecommendRoutinesRequest: RegisterOnBoardingRecommendRoutinesRequest, ): BaseResponse + + @GET("/api/v2/onboardings") + suspend fun getOnBoarding(): BaseResponse } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt index 445c2a69..bb768a0c 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt @@ -12,4 +12,5 @@ interface OnBoardingRepository { suspend fun getRecommendOnBoardingRouteList(selectedItemIdsWithOnBoardingId: List>>): Result> suspend fun registerRecommendRoutineList(selectedRecommendRoutineIds: List): Result suspend fun getOnBoardingRecommendRoutineEventFlow(): Flow + suspend fun getUserOnBoarding(): Result>>> } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetUserOnBoardingUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetUserOnBoardingUseCase.kt new file mode 100644 index 00000000..ab65bd05 --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetUserOnBoardingUseCase.kt @@ -0,0 +1,12 @@ +package com.threegap.bitnagil.domain.onboarding.usecase + +import com.threegap.bitnagil.domain.onboarding.repository.OnBoardingRepository +import javax.inject.Inject + +class GetUserOnBoardingUseCase @Inject constructor( + private val onBoardingRepository: OnBoardingRepository, +) { + suspend operator fun invoke(): Result>>> { + return onBoardingRepository.getUserOnBoarding() + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/common/ninepatch/NinePatchBackground.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/ninepatch/NinePatchBackground.kt new file mode 100644 index 00000000..bf9b538b --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/ninepatch/NinePatchBackground.kt @@ -0,0 +1,30 @@ +package com.threegap.bitnagil.presentation.common.ninepatch + +import android.graphics.drawable.NinePatchDrawable +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.updateBounds + +@Composable +fun Modifier.ninePatchBackgroundNode(drawableResId: Int): Modifier { + val context = LocalContext.current + val ninePatchDrawable = remember(drawableResId) { + ContextCompat.getDrawable(context, drawableResId) as? NinePatchDrawable + } + + return if (ninePatchDrawable != null) { + this.then( + Modifier.drawBehind { + ninePatchDrawable.updateBounds(0, 0, size.width.toInt(), size.height.toInt()) + ninePatchDrawable.draw(drawContext.canvas.nativeCanvas) + }, + ) + } else { + this + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt index ad8a5c98..f55e36cf 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt @@ -13,7 +13,9 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.component.block.BitnagilProgressTopBar import com.threegap.bitnagil.presentation.common.flow.collectAsEffect +import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast import com.threegap.bitnagil.presentation.onboarding.component.template.OnBoardingAbstractTemplate +import com.threegap.bitnagil.presentation.onboarding.component.template.OnBoardingIntroTemplate import com.threegap.bitnagil.presentation.onboarding.component.template.OnBoardingSelectTemplate import com.threegap.bitnagil.presentation.onboarding.model.OnBoardingItem import com.threegap.bitnagil.presentation.onboarding.model.OnBoardingPageInfo @@ -37,12 +39,17 @@ fun OnBoardingScreenContainer( OnBoardingSideEffect.NavigateToHomeScreen -> { navigateToHome() } + + is OnBoardingSideEffect.ShowToast -> { + GlobalBitnagilToast.showWarning(sideEffect.message) + } } } OnBoardingScreen( state = state, onClickNext = onBoardingViewModel::selectNext, + onClickLoadOnBoarding = onBoardingViewModel::loadOnBoardingItems, onClickPreviousInSelectOnBoarding = onBoardingViewModel::selectPrevious, onClickItem = onBoardingViewModel::selectItem, onClickRoutine = onBoardingViewModel::selectRoutine, @@ -57,6 +64,7 @@ fun OnBoardingScreenContainer( private fun OnBoardingScreen( state: OnBoardingState, onClickNext: () -> Unit, + onClickLoadOnBoarding: () -> Unit, onClickPreviousInSelectOnBoarding: () -> Unit, onClickItem: (String) -> Unit, onClickRoutine: (String) -> Unit, @@ -67,24 +75,36 @@ private fun OnBoardingScreen( ) { Column( modifier = Modifier - .background(BitnagilTheme.colors.coolGray99) + .background(BitnagilTheme.colors.white) .statusBarsPadding(), ) { - BitnagilProgressTopBar( - onBackClick = onClickPreviousInSelectOnBoarding, - progress = state.progress, - ) + if (!state.hideToolbar) { + BitnagilProgressTopBar( + modifier = Modifier, + onBackClick = onClickPreviousInSelectOnBoarding, + progress = state.progress, + showProgress = state.showProgress, + ) + } when (state) { is OnBoardingState.Idle -> { when (val currentOnBoardingPageInfo = state.currentOnBoardingPageInfo) { + OnBoardingPageInfo.Intro -> { + OnBoardingIntroTemplate( + userName = state.userName, + onClickNextButton = onClickLoadOnBoarding, + ) + } is OnBoardingPageInfo.Abstract -> { OnBoardingAbstractTemplate( modifier = Modifier.weight(1f), - title = "이제 당신에게\n꼭 맞는 루틴을 제안해드릴게요.", - onInit = loadRecommendRoutines, + title = "이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄거에요.", onBoardingAbstractTexts = currentOnBoardingPageInfo.abstractTexts, onDispose = cancelRecommendRoutines, + onClickNextButton = loadRecommendRoutines, + nextButtonEnable = state.nextButtonEnable, + userName = state.userName, ) } is OnBoardingPageInfo.RecommendRoutines -> { @@ -95,11 +115,7 @@ private fun OnBoardingScreen( items = currentOnBoardingPageInfo.routines, nextButtonEnable = state.nextButtonEnable, onClickNextButton = onClickRegister, - onClickItem = if (state.onBoardingSetType.canSelectRoutine) { - onClickRoutine - } else { - null - }, + onClickItem = onClickRoutine, onClickSkip = if (state.onBoardingSetType.canSkip) { onClickSkip } else { @@ -118,6 +134,18 @@ private fun OnBoardingScreen( onClickItem = onClickItem, ) } + + is OnBoardingPageInfo.ExistedOnBoardingAbstract -> { + OnBoardingAbstractTemplate( + modifier = Modifier.weight(1f), + title = "이전에 설정한 목표예요!\n변경하시겠어요?", + onBoardingAbstractTexts = currentOnBoardingPageInfo.abstractTexts, + onDispose = cancelRecommendRoutines, + onClickNextButton = onClickLoadOnBoarding, + nextButtonEnable = true, + userName = state.userName, + ) + } } } OnBoardingState.Loading -> { @@ -141,8 +169,10 @@ fun OnBoardingScreenPreview() { totalStep = 5, currentStep = 1, onBoardingSetType = OnBoardingSetType.RESET, + userName = "안드로이드", ), onClickNext = {}, + onClickLoadOnBoarding = {}, onClickPreviousInSelectOnBoarding = {}, onClickItem = {}, onClickRoutine = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt index 07f96d4c..52d28cec 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt @@ -5,7 +5,9 @@ import androidx.lifecycle.viewModelScope import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingAbstractUseCase import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingsUseCase import com.threegap.bitnagil.domain.onboarding.usecase.GetRecommendOnBoardingRoutinesUseCase +import com.threegap.bitnagil.domain.onboarding.usecase.GetUserOnBoardingUseCase import com.threegap.bitnagil.domain.onboarding.usecase.RegisterRecommendOnBoardingRoutinesUseCase +import com.threegap.bitnagil.domain.user.usecase.FetchUserProfileUseCase import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel import com.threegap.bitnagil.presentation.onboarding.model.OnBoardingAbstractTextItem import com.threegap.bitnagil.presentation.onboarding.model.OnBoardingItem @@ -21,7 +23,6 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.async -import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.orbitmvi.orbit.syntax.simple.SimpleSyntax @@ -33,6 +34,8 @@ class OnBoardingViewModel @AssistedInject constructor( private val getRecommendOnBoardingRoutinesUseCase: GetRecommendOnBoardingRoutinesUseCase, private val getOnBoardingAbstractUseCase: GetOnBoardingAbstractUseCase, private val registerRecommendOnBoardingRoutinesUseCase: RegisterRecommendOnBoardingRoutinesUseCase, + private val fetchUserProfileUseCase: FetchUserProfileUseCase, + private val getUserOnBoardingUseCase: GetUserOnBoardingUseCase, @Assisted private val onBoardingArg: OnBoardingScreenArg, ) : MviViewModel( initState = OnBoardingState.Loading, @@ -43,18 +46,71 @@ class OnBoardingViewModel @AssistedInject constructor( } // 내부에 전체 온보딩 항목 저장 - private val onBoardingPageInfos = mutableListOf() + private val selectOnBoardingPageInfos = mutableListOf() + private var existedOnBoardingAbstract: OnBoardingPageInfo.ExistedOnBoardingAbstract? = null private var loadRecommendRoutinesJob: Job? = null init { - loadOnBoardingItems() + loadInitData() } - private fun loadOnBoardingItems() { + private fun loadInitData() { + val onBoardingSetType = OnBoardingSetType.fromOnBoardingScreenArg(onBoardingArg) + + when (onBoardingSetType) { + OnBoardingSetType.NEW -> { + loadIntro() + } + OnBoardingSetType.RESET -> { + loadUserOnBoarding() + } + } + } + + private fun loadIntro() { viewModelScope.launch { - val onBoardings = getOnBoardingsUseCase() + val userName = fetchUserProfileUseCase().getOrNull()?.nickname ?: "-" + + sendIntent(OnBoardingIntent.LoadIntroSuccess(userName = userName)) + } + } + + private fun loadUserOnBoarding() { + viewModelScope.launch { + val userName = fetchUserProfileUseCase().getOrNull()?.nickname ?: "-" + val userOnBoarding = getUserOnBoardingUseCase().fold( + onSuccess = { it }, + onFailure = { + sendIntent(OnBoardingIntent.LoadUserOnBoardingFailure(message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요.")) + return@launch + }, + ) + val onBoardingAbstract = getOnBoardingAbstractUseCase(selectedItemIdsWithOnBoardingId = userOnBoarding) + + val abstractPagePrefixText = onBoardingAbstract.prefix + val abstractTexts = onBoardingAbstract.abstractTexts.map { onBoardingAbstractText -> + onBoardingAbstractText.textItems.map { onBoardingAbstractTextItem -> + OnBoardingAbstractTextItem.fromOnBoardingAbstractTextItem(onBoardingAbstractTextItem) + } + } + + sendIntent( + OnBoardingIntent.LoadUserOnBoardingSuccess( + onBoardingAbstract = OnBoardingPageInfo.ExistedOnBoardingAbstract( + prefix = abstractPagePrefixText, + abstractTexts = abstractTexts, + ), + userName = userName, + ), + ) + } + } + + fun loadOnBoardingItems() { + viewModelScope.launch { + val onBoardings = getOnBoardingsUseCase() val onBoardingPages = onBoardings.map { onBoarding -> OnBoardingPageInfo.SelectOnBoarding.fromOnBoarding(onBoarding = onBoarding) } @@ -68,16 +124,42 @@ class OnBoardingViewModel @AssistedInject constructor( state: OnBoardingState, ): OnBoardingState? { when (intent) { - is OnBoardingIntent.LoadOnBoardingSuccess -> { - onBoardingPageInfos.clear() - onBoardingPageInfos.addAll(intent.onBoardingPageInfos) + is OnBoardingIntent.LoadUserOnBoardingSuccess -> { + existedOnBoardingAbstract = intent.onBoardingAbstract return OnBoardingState.Idle( + nextButtonEnable = true, + currentOnBoardingPageInfo = intent.onBoardingAbstract, + totalStep = 1, + currentStep = 0, + onBoardingSetType = OnBoardingSetType.RESET, + userName = intent.userName, + ) + } + + is OnBoardingIntent.LoadIntroSuccess -> { + return OnBoardingState.Idle( + nextButtonEnable = true, + currentOnBoardingPageInfo = OnBoardingPageInfo.Intro, + totalStep = 1, + currentStep = 0, + onBoardingSetType = OnBoardingSetType.NEW, + userName = intent.userName, + ) + } + + is OnBoardingIntent.LoadOnBoardingSuccess -> { + val currentState = state + if (currentState !is OnBoardingState.Idle) return null + + selectOnBoardingPageInfos.clear() + selectOnBoardingPageInfos.addAll(intent.onBoardingPageInfos) + + return currentState.copy( nextButtonEnable = false, - currentOnBoardingPageInfo = onBoardingPageInfos.first(), - totalStep = onBoardingPageInfos.size + 2, + currentOnBoardingPageInfo = intent.onBoardingPageInfos.first(), + totalStep = selectOnBoardingPageInfos.size + 2, currentStep = 1, - onBoardingSetType = OnBoardingSetType.fromOnBoardingScreenArg(onBoardingArg), ) } @@ -89,7 +171,7 @@ class OnBoardingViewModel @AssistedInject constructor( if (currentPageInfo !is OnBoardingPageInfo.SelectOnBoarding) return null val selectChangedCurrentPageInfo = currentPageInfo.selectItem(itemId = intent.itemId) - onBoardingPageInfos[currentState.currentStep - 1] = selectChangedCurrentPageInfo + selectOnBoardingPageInfos[currentState.currentStep - 1] = selectChangedCurrentPageInfo return currentState.copy( currentOnBoardingPageInfo = selectChangedCurrentPageInfo, nextButtonEnable = selectChangedCurrentPageInfo.isItemSelected, @@ -100,10 +182,10 @@ class OnBoardingViewModel @AssistedInject constructor( val currentState = state if (currentState !is OnBoardingState.Idle) return null - val isLastSelectOnBoarding = currentState.currentStep >= onBoardingPageInfos.size - if (isLastSelectOnBoarding) return null + val isLastPageOfSelectOnBoarding = currentState.currentStep >= selectOnBoardingPageInfos.size + if (isLastPageOfSelectOnBoarding) return null - val nextOnBoardingPageInfo = onBoardingPageInfos[currentState.currentStep] + val nextOnBoardingPageInfo = selectOnBoardingPageInfos[currentState.currentStep] val nextButtonEnable = nextOnBoardingPageInfo.isItemSelected return currentState.copy( currentOnBoardingPageInfo = nextOnBoardingPageInfo, @@ -114,14 +196,30 @@ class OnBoardingViewModel @AssistedInject constructor( is OnBoardingIntent.SelectPrevious -> { val currentState = state - if (currentState !is OnBoardingState.Idle || currentState.currentStep == 1) { + if (currentState !is OnBoardingState.Idle || currentState.currentStep == 0) { sendSideEffect(sideEffect = OnBoardingSideEffect.MoveToPreviousScreen) return null } - val isSelectOnBoardingStep = currentState.currentStep <= onBoardingPageInfos.size + if (currentState.currentStep == 1) { + return if (currentState.onBoardingSetType == OnBoardingSetType.RESET && existedOnBoardingAbstract != null) { + currentState.copy( + currentStep = 0, + currentOnBoardingPageInfo = existedOnBoardingAbstract!!, + nextButtonEnable = true, + ) + } else { + currentState.copy( + currentStep = 0, + currentOnBoardingPageInfo = OnBoardingPageInfo.Intro, + nextButtonEnable = true, + ) + } + } + + val isSelectOnBoardingStep = currentState.currentStep <= selectOnBoardingPageInfos.size if (isSelectOnBoardingStep) { - val previousOnBoardingPageInfo = onBoardingPageInfos[currentState.currentStep - 2] + val previousOnBoardingPageInfo = selectOnBoardingPageInfos[currentState.currentStep - 2] val nextButtonEnable = previousOnBoardingPageInfo.isItemSelected return currentState.copy( currentOnBoardingPageInfo = previousOnBoardingPageInfo, @@ -129,12 +227,12 @@ class OnBoardingViewModel @AssistedInject constructor( currentStep = currentState.currentStep - 1, ) } else { - val selectOnBoardingPageInfo = onBoardingPageInfos.last() + val selectOnBoardingPageInfo = selectOnBoardingPageInfos.last() val nextButtonEnable = selectOnBoardingPageInfo.isItemSelected return currentState.copy( currentOnBoardingPageInfo = selectOnBoardingPageInfo, nextButtonEnable = nextButtonEnable, - currentStep = onBoardingPageInfos.size, + currentStep = selectOnBoardingPageInfos.size, ) } } @@ -147,7 +245,7 @@ class OnBoardingViewModel @AssistedInject constructor( return currentState.copy( currentOnBoardingPageInfo = recommendRoutinePageInfo, currentStep = currentState.currentStep + 1, - nextButtonEnable = !currentState.onBoardingSetType.canSelectRoutine, + nextButtonEnable = !currentState.onBoardingSetType.mustSelectRecommendRoutine, ) } @@ -179,6 +277,12 @@ class OnBoardingViewModel @AssistedInject constructor( currentStep = currentState.currentStep + 1, ) } + + is OnBoardingIntent.LoadUserOnBoardingFailure -> { + sendSideEffect(sideEffect = OnBoardingSideEffect.MoveToPreviousScreen) + sendSideEffect(sideEffect = OnBoardingSideEffect.ShowToast(message = intent.message)) + return null + } } } @@ -193,9 +297,9 @@ class OnBoardingViewModel @AssistedInject constructor( val currentState = stateFlow.value if (currentState !is OnBoardingState.Idle) return@launch - val isLastSelectOnBoarding = currentState.currentStep >= onBoardingPageInfos.size + val isLastSelectOnBoarding = currentState.currentStep >= selectOnBoardingPageInfos.size if (isLastSelectOnBoarding) { - val selectedItemIdsWithOnBoardingId = getSelectedOnBoardingItemIdsWithId(onBoardingPageInfos) + val selectedItemIdsWithOnBoardingId = getSelectedOnBoardingItemIdsWithId(selectOnBoardingPageInfos) val onBoardingAbstract = getOnBoardingAbstractUseCase(selectedItemIdsWithOnBoardingId = selectedItemIdsWithOnBoardingId) @@ -239,12 +343,9 @@ class OnBoardingViewModel @AssistedInject constructor( } fun loadRecommendRoutines() { + loadRecommendRoutinesJob?.cancel() loadRecommendRoutinesJob = viewModelScope.async { - val minimumDelayDeferred = async { - delay(2000L) - } - - val selectedItems = onBoardingPageInfos + val selectedItems = selectOnBoardingPageInfos .map { onBoardingPage -> val id = onBoardingPage.id val selectedItemIds = onBoardingPage.items.filter { onBoardingItem -> @@ -259,7 +360,6 @@ class OnBoardingViewModel @AssistedInject constructor( getRecommendOnBoardingRoutinesUseCase(selectedItems).fold( onSuccess = { recommendRoutines -> - minimumDelayDeferred.await() if (isActive) { sendIntent( intent = OnBoardingIntent.LoadRecommendRoutinesSuccess( diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt index a61bf22d..d205c9c9 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingAbstractTemplate.kt @@ -1,45 +1,63 @@ package com.threegap.bitnagil.presentation.onboarding.component.template import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box +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.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.Alignment +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton +import com.threegap.bitnagil.presentation.common.ninepatch.ninePatchBackgroundNode import com.threegap.bitnagil.presentation.onboarding.model.OnBoardingAbstractTextItem @Composable fun OnBoardingAbstractTemplate( modifier: Modifier = Modifier, title: String, + userName: String, onBoardingAbstractTexts: List>, - onInit: () -> Unit, onDispose: () -> Unit, + onClickNextButton: () -> Unit, + nextButtonEnable: Boolean, ) { DisposableEffect(Unit) { - onInit() - onDispose { onDispose() } } Column( - modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 20.dp, top = 32.dp), + modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 20.dp, top = 53.dp), ) { Text( text = title, @@ -47,48 +65,71 @@ fun OnBoardingAbstractTemplate( style = BitnagilTheme.typography.title2Bold, ) - Spacer(modifier = Modifier.height(10.dp)) - - onBoardingAbstractTexts.forEach { onBoardingAbstractTextItemList -> - Spacer(modifier = Modifier.height(2.dp)) - OnBoardingAbstractText(onBoardingAbstractTextList = onBoardingAbstractTextItemList) - } + Spacer(modifier = Modifier.height(32.dp)) - Spacer(modifier = Modifier.height(83.dp)) + Image( + painter = painterResource(R.drawable.img_pomo_flower), + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .zIndex(1f) + .widthIn(min = 311.dp) + .offset(y = 2.dp), + ) - Box( + Column( modifier = Modifier .fillMaxWidth() - .weight(1f), - contentAlignment = Alignment.TopCenter, + .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_4) + .padding(30.dp), + verticalArrangement = Arrangement.spacedBy(13.dp), ) { - Image( - painter = painterResource(R.drawable.onboarding_character), - contentDescription = null, - contentScale = ContentScale.Fit, - modifier = Modifier - .padding(horizontal = 15.dp) - .aspectRatio(295f / 280f), + Text( + text = "${userName}님은 ...", + style = BitnagilTheme.typography.title3SemiBold, + color = BitnagilTheme.colors.coolGray10, ) + + onBoardingAbstractTexts.forEachIndexed { index, onBoardingAbstractTextItemList -> + OnBoardingAbstractText( + onBoardingAbstractTextList = onBoardingAbstractTextItemList, + iconResourceId = getIndexIconResourceId(index), + ) + } } + + Spacer(modifier = Modifier.weight(2f)) + + BitnagilTextButton( + text = "다음", + onClick = onClickNextButton, + enabled = nextButtonEnable, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +private fun getIndexIconResourceId(index: Int): Int { + return when (index) { + 0 -> R.drawable.img_circle_1 + 1 -> R.drawable.img_circle_2 + 2 -> R.drawable.img_circle_3 + else -> R.drawable.img_circle_1 } } @Composable private fun OnBoardingAbstractText( onBoardingAbstractTextList: List, + iconResourceId: Int, ) { val annotatedString = buildAnnotatedString { - withStyle(style = BitnagilTheme.typography.body2SemiBold.toSpanStyle()) { - append("• ") - } - onBoardingAbstractTextList.forEach { withStyle( style = if (it.isBold) { - BitnagilTheme.typography.body2SemiBold.toSpanStyle() + BitnagilTheme.typography.subtitle1SemiBold.toSpanStyle().copy(color = BitnagilTheme.colors.orange500) } else { - BitnagilTheme.typography.body2Regular.toSpanStyle() + BitnagilTheme.typography.subtitle1Medium.toSpanStyle().copy(color = BitnagilTheme.colors.coolGray10) }, ) { append(it.text) @@ -96,9 +137,116 @@ private fun OnBoardingAbstractText( } } + Row( + modifier = Modifier.fillMaxWidth(), + ) { + Image( + painter = painterResource(iconResourceId), + modifier = Modifier.padding(top = 8.dp).size(24.dp), + contentDescription = null, + ) + + Spacer(modifier = Modifier.width(14.dp)) + + UnderLinedText( + text = annotatedString, + dividerColor = BitnagilTheme.colors.coolGray90, + modifier = Modifier.weight(1f), + ) + } +} + +@Composable +private fun UnderLinedText( + text: AnnotatedString, + dividerColor: Color, + modifier: Modifier = Modifier, +) { + var textLayoutResult by remember { mutableStateOf(null) } + val density = LocalDensity.current + val lineHeight = with(density) { + val extraSpacingInDp = 39.dp + return@with (extraSpacingInDp.value).dp.toSp() + } + Text( - text = annotatedString, + text = text, color = BitnagilTheme.colors.coolGray30, - style = BitnagilTheme.typography.body2Regular, + style = BitnagilTheme.typography.body2Regular.copy( + lineHeight = lineHeight, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ), + lineHeight = lineHeight, + onTextLayout = { result -> + textLayoutResult = result + }, + modifier = modifier.drawBehind { + textLayoutResult?.let { + for (i in 0 until it.lineCount) { + val lineStart = 0f + val lineBottom = it.getLineBottom(i) + val lineEnd = size.width + + val yOffset = lineBottom + + drawLine( + color = dividerColor, + start = Offset(lineStart, yOffset), + end = Offset(lineEnd, yOffset), + strokeWidth = 2.dp.toPx(), + ) + } + } + }, ) } + +@Composable +@Preview(showBackground = true) +private fun OnBoardingAbstractTemplatePreview() { + BitnagilTheme { + OnBoardingAbstractTemplate( + modifier = Modifier.fillMaxSize(), + title = "이제 포모가 당신에게\n꼭 맞는 루틴을 찾아줄거에요.", + userName = "안드로이드", + onBoardingAbstractTexts = listOf( + listOf( + OnBoardingAbstractTextItem( + text = "텍스트1", + isBold = true, + ), + OnBoardingAbstractTextItem( + text = "텍스트2 아아아아아아아아아ㅏ아앙아아ㅏ아아아아아", + isBold = false, + ), + ), + listOf( + OnBoardingAbstractTextItem( + text = "텍스트1", + isBold = true, + ), + OnBoardingAbstractTextItem( + text = "텍스트2", + isBold = false, + ), + ), + listOf( + OnBoardingAbstractTextItem( + text = "텍스트1", + isBold = true, + ), + OnBoardingAbstractTextItem( + text = "텍스트2", + isBold = false, + ), + ), + ), + onDispose = {}, + onClickNextButton = {}, + nextButtonEnable = true, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt new file mode 100644 index 00000000..f54c46ec --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/component/template/OnBoardingIntroTemplate.kt @@ -0,0 +1,104 @@ +package com.threegap.bitnagil.presentation.onboarding.component.template + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton +import com.threegap.bitnagil.presentation.common.ninepatch.ninePatchBackgroundNode + +@Composable +fun OnBoardingIntroTemplate( + userName: String, + onClickNextButton: () -> Unit, +) { + Column( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 20.dp), + ) { + Spacer(modifier = Modifier.height(64.dp)) + + Text( + text = "포모는 ${userName}님을 알고 싶어요!", + style = BitnagilTheme.typography.title1Bold, + color = BitnagilTheme.colors.coolGray10, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Column( + modifier = Modifier.fillMaxWidth(), + ) { + Text( + modifier = Modifier + .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_1) + .padding(horizontal = 27.dp, vertical = 20.dp), + text = "어떤 시간대를 더 잘보내고 싶은지", + style = BitnagilTheme.typography.button1, + color = BitnagilTheme.colors.coolGray10, + ) + Text( + modifier = Modifier + .align(Alignment.End) + .offset(y = (-10).dp) + .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_2) + .padding(horizontal = 27.dp, vertical = 20.dp), + text = "요즘 어떤 회복이 필요한지", + style = BitnagilTheme.typography.button1, + color = BitnagilTheme.colors.coolGray10, + ) + Text( + modifier = Modifier + .offset(y = (-18).dp) + .padding(start = 22.dp) + .ninePatchBackgroundNode(R.drawable.img_texture_rectangle_3) + .padding(horizontal = 27.dp, vertical = 20.dp), + text = "얼마나 바깥 바람을 쐬고 싶은지", + style = BitnagilTheme.typography.button1, + color = BitnagilTheme.colors.coolGray10, + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + + Image( + painter = painterResource(R.drawable.img_pomo_memo), + contentDescription = null, + modifier = Modifier + .widthIn(min = 263.dp) + .align(Alignment.End), + ) + + Spacer(modifier = Modifier.height(40.dp)) + + BitnagilTextButton( + text = "포모에게 알려주기", + onClick = onClickNextButton, + enabled = true, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +@Preview(showBackground = true, heightDp = 800, widthDp = 360) +private fun OnBoardingIntroTemplatePreview() { + BitnagilTheme { + OnBoardingIntroTemplate( + userName = "안드로이드", + onClickNextButton = {}, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingPageInfo.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingPageInfo.kt index dd8bf39f..72eee88c 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingPageInfo.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingPageInfo.kt @@ -7,6 +7,15 @@ import kotlinx.parcelize.Parcelize @Parcelize sealed class OnBoardingPageInfo : Parcelable { + @Parcelize + data object Intro : OnBoardingPageInfo() + + @Parcelize + data class ExistedOnBoardingAbstract( + val prefix: String, + @Stable val abstractTexts: List>, + ) : OnBoardingPageInfo() + @Parcelize data class SelectOnBoarding( val id: String, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingSetType.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingSetType.kt index d76da889..181aacd4 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingSetType.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/OnBoardingSetType.kt @@ -5,17 +5,17 @@ import com.threegap.bitnagil.presentation.onboarding.model.navarg.OnBoardingScre enum class OnBoardingSetType( val subText: String, val canSkip: Boolean, - val canSelectRoutine: Boolean, + val mustSelectRecommendRoutine: Boolean, ) { NEW( subText = "당신의 생활 패턴과 목표에 맞춰 구성된 맞춤 루틴이에요.\n지금부터 가볍게 시작해보세요.", canSkip = true, - canSelectRoutine = true, + mustSelectRecommendRoutine = true, ), RESET( subText = "생활 패턴과 목표에 맞춰 다시 구성된 맞춤 루틴이에요.\n원하는 루틴을 선택해서 가볍게 시작해보세요.", canSkip = false, - canSelectRoutine = false, + mustSelectRecommendRoutine = false, ), ; diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt index 6c6f8b72..57c013eb 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt @@ -13,4 +13,7 @@ sealed class OnBoardingIntent : MviIntent { data object SelectNext : OnBoardingIntent() data object SelectPrevious : OnBoardingIntent() data object NavigateToHome : OnBoardingIntent() + data class LoadUserOnBoardingSuccess(val onBoardingAbstract: OnBoardingPageInfo.ExistedOnBoardingAbstract, val userName: String) : OnBoardingIntent() + data class LoadUserOnBoardingFailure(val message: String) : OnBoardingIntent() + data class LoadIntroSuccess(val userName: String) : OnBoardingIntent() } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt index c6bf2e62..aa16667f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt @@ -5,4 +5,5 @@ import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect sealed class OnBoardingSideEffect : MviSideEffect { data object MoveToPreviousScreen : OnBoardingSideEffect() data object NavigateToHomeScreen : OnBoardingSideEffect() + data class ShowToast(val message: String) : OnBoardingSideEffect() } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt index 75910ffe..b097e4d0 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt @@ -19,5 +19,9 @@ sealed class OnBoardingState(val progress: Float) : Parcelable, MviState { val totalStep: Int, val currentStep: Int, val onBoardingSetType: OnBoardingSetType, + val userName: String, ) : OnBoardingState(progress = currentStep.toFloat() / totalStep.toFloat()) + + val showProgress: Boolean get() = progress > 0 + val hideToolbar: Boolean get() = (this is Idle && onBoardingSetType == OnBoardingSetType.NEW && progress <= 0) }