Skip to content

Commit 1a27496

Browse files
committed
FEAT: 사용자가 이전에 설정한 온보딩 조회 UI 구현 및 온보딩 조회 API 연결
1 parent 9e75a0b commit 1a27496

13 files changed

Lines changed: 181 additions & 31 deletions

File tree

data/src/main/java/com/threegap/bitnagil/data/onboarding/datasource/OnBoardingDataSource.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import com.threegap.bitnagil.data.onboarding.model.dto.OnBoardingAbstractDto
44
import com.threegap.bitnagil.data.onboarding.model.dto.OnBoardingDto
55
import com.threegap.bitnagil.data.onboarding.model.request.GetOnBoardingRecommendRoutinesRequest
66
import com.threegap.bitnagil.data.onboarding.model.response.GetOnBoardingRecommendRoutinesResponse
7+
import com.threegap.bitnagil.data.onboarding.model.response.GetUserOnBoardingResponse
78

89
interface OnBoardingDataSource {
910
suspend fun getOnBoardingList(): List<OnBoardingDto>
1011
suspend fun getOnBoardingRecommendRoutines(request: GetOnBoardingRecommendRoutinesRequest): Result<GetOnBoardingRecommendRoutinesResponse>
1112
suspend fun getOnBoardingAbstract(selectedOnBoardingItemIdList: List<Pair<String, List<String>>>): OnBoardingAbstractDto
1213
suspend fun registerRecommendRoutineList(selectedRecommendRoutineIds: List<Int>): Result<Unit>
14+
suspend fun getUserOnBoarding(): Result<GetUserOnBoardingResponse>
1315
}

data/src/main/java/com/threegap/bitnagil/data/onboarding/datasourceImpl/OnBoardingDataSourceImpl.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.threegap.bitnagil.data.onboarding.model.dto.OnBoardingItemDto
1111
import com.threegap.bitnagil.data.onboarding.model.request.GetOnBoardingRecommendRoutinesRequest
1212
import com.threegap.bitnagil.data.onboarding.model.request.RegisterOnBoardingRecommendRoutinesRequest
1313
import com.threegap.bitnagil.data.onboarding.model.response.GetOnBoardingRecommendRoutinesResponse
14+
import com.threegap.bitnagil.data.onboarding.model.response.GetUserOnBoardingResponse
1415
import com.threegap.bitnagil.data.onboarding.service.OnBoardingService
1516
import javax.inject.Inject
1617

@@ -57,6 +58,12 @@ class OnBoardingDataSourceImpl @Inject constructor(
5758
}
5859
}
5960

61+
override suspend fun getUserOnBoarding(): Result<GetUserOnBoardingResponse> {
62+
return safeApiCall {
63+
onBoardingService.getOnBoarding()
64+
}
65+
}
66+
6067
private fun getOnBoardingAbstractText(onBoardingId: String, selectedOnBoardingDetailIdList: List<String>): OnBoardingAbstractTextDto? {
6168
if (selectedOnBoardingDetailIdList.isEmpty()) return null
6269
val onBoarding = onBoardingDtoList.find { it.id == onBoardingId }

data/src/main/java/com/threegap/bitnagil/data/onboarding/model/request/GetOnBoardingRecommendRoutinesRequest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ data class GetOnBoardingRecommendRoutinesRequest(
88
@SerialName("timeSlot")
99
val timeSlot: String,
1010
@SerialName("emotionType")
11-
val emotionType: String,
11+
val emotionType: List<String>,
1212
@SerialName("realOutingFrequency")
1313
val realOutingFrequency: String,
1414
@SerialName("targetOutingFrequency")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.threegap.bitnagil.data.onboarding.model.response
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class GetUserOnBoardingResponse(
8+
@SerialName("timeSlot")
9+
val timeSlot: String,
10+
@SerialName("emotionTypes")
11+
val emotionTypes: List<String>,
12+
@SerialName("targetOutingFrequency")
13+
val targetOutingFrequency: String,
14+
)

data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ class OnBoardingRepositoryImpl @Inject constructor(
3434
selectedItemIdsWithOnBoardingId: List<Pair<String, List<String>>>,
3535
): Result<List<OnBoardingRecommendRoutine>> {
3636
val timeSlot = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.TimeSlot.id }?.second?.first()
37-
val emotionType = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.EmotionType.id }?.second?.first()
37+
val emotionType = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.EmotionType.id }?.second
3838
val realOutingFrequency = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.RealOutingFrequency.id }?.second?.first()
3939
val targetOutingFrequency = selectedItemIdsWithOnBoardingId.find { it.first == OnBoardingDto.TargetOutingFrequency.id }?.second?.first()
4040

4141
val request = GetOnBoardingRecommendRoutinesRequest(
4242
timeSlot = timeSlot ?: "",
43-
emotionType = emotionType ?: "",
43+
emotionType = emotionType ?: listOf(),
4444
realOutingFrequency = realOutingFrequency ?: "",
4545
targetOutingFrequency = targetOutingFrequency ?: "",
4646
)
@@ -64,6 +64,16 @@ class OnBoardingRepositoryImpl @Inject constructor(
6464
}
6565
}
6666

67+
override suspend fun getUserOnBoarding(): Result<List<Pair<String, List<String>>>> {
68+
return onBoardingDataSource.getUserOnBoarding().map {
69+
listOf(
70+
OnBoardingDto.TimeSlot.id to listOf(it.timeSlot),
71+
OnBoardingDto.EmotionType.id to it.emotionTypes,
72+
OnBoardingDto.TargetOutingFrequency.id to listOf(it.targetOutingFrequency),
73+
)
74+
}
75+
}
76+
6777
private val _onBoardingRecommendRoutineEventFlow = MutableSharedFlow<OnBoardingRecommendRoutineEvent>(
6878
extraBufferCapacity = 1,
6979
onBufferOverflow = BufferOverflow.DROP_OLDEST,

data/src/main/java/com/threegap/bitnagil/data/onboarding/service/OnBoardingService.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@ package com.threegap.bitnagil.data.onboarding.service
33
import com.threegap.bitnagil.data.onboarding.model.request.GetOnBoardingRecommendRoutinesRequest
44
import com.threegap.bitnagil.data.onboarding.model.request.RegisterOnBoardingRecommendRoutinesRequest
55
import com.threegap.bitnagil.data.onboarding.model.response.GetOnBoardingRecommendRoutinesResponse
6+
import com.threegap.bitnagil.data.onboarding.model.response.GetUserOnBoardingResponse
67
import com.threegap.bitnagil.network.model.BaseResponse
78
import retrofit2.http.Body
9+
import retrofit2.http.GET
810
import retrofit2.http.POST
911

1012
interface OnBoardingService {
11-
@POST("/api/v1/onboardings")
13+
@POST("/api/v2/onboardings")
1214
suspend fun postOnBoarding(
1315
@Body onBoardingRecommendRoutinesRequest: GetOnBoardingRecommendRoutinesRequest,
1416
): BaseResponse<GetOnBoardingRecommendRoutinesResponse>
1517

16-
@POST("/api/v1/onboardings/routines")
18+
@POST("/api/v2/onboardings/routines")
1719
suspend fun postOnBoardingRoutines(
1820
@Body registerOnBoardingRecommendRoutinesRequest: RegisterOnBoardingRecommendRoutinesRequest,
1921
): BaseResponse<Unit>
22+
23+
@GET("/api/v2/onboardings")
24+
suspend fun getOnBoarding(): BaseResponse<GetUserOnBoardingResponse>
2025
}

domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ interface OnBoardingRepository {
1212
suspend fun getRecommendOnBoardingRouteList(selectedItemIdsWithOnBoardingId: List<Pair<String, List<String>>>): Result<List<OnBoardingRecommendRoutine>>
1313
suspend fun registerRecommendRoutineList(selectedRecommendRoutineIds: List<String>): Result<Unit>
1414
suspend fun getOnBoardingRecommendRoutineEventFlow(): Flow<OnBoardingRecommendRoutineEvent>
15+
suspend fun getUserOnBoarding(): Result<List<Pair<String, List<String>>>>
1516
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.threegap.bitnagil.domain.onboarding.usecase
2+
3+
import com.threegap.bitnagil.domain.onboarding.repository.OnBoardingRepository
4+
import javax.inject.Inject
5+
6+
class GetUserOnBoardingUseCase @Inject constructor(
7+
private val onBoardingRepository: OnBoardingRepository,
8+
) {
9+
suspend operator fun invoke(): Result<List<Pair<String, List<String>>>> {
10+
return onBoardingRepository.getUserOnBoarding()
11+
}
12+
}

presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
1313
import com.threegap.bitnagil.designsystem.BitnagilTheme
1414
import com.threegap.bitnagil.designsystem.component.block.BitnagilProgressTopBar
1515
import com.threegap.bitnagil.presentation.common.flow.collectAsEffect
16+
import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast
1617
import com.threegap.bitnagil.presentation.onboarding.component.template.OnBoardingAbstractTemplate
1718
import com.threegap.bitnagil.presentation.onboarding.component.template.OnBoardingIntroTemplate
1819
import com.threegap.bitnagil.presentation.onboarding.component.template.OnBoardingSelectTemplate
@@ -38,12 +39,17 @@ fun OnBoardingScreenContainer(
3839
OnBoardingSideEffect.NavigateToHomeScreen -> {
3940
navigateToHome()
4041
}
42+
43+
is OnBoardingSideEffect.ShowToast -> {
44+
GlobalBitnagilToast.showWarning(sideEffect.message)
45+
}
4146
}
4247
}
4348

4449
OnBoardingScreen(
4550
state = state,
4651
onClickNext = onBoardingViewModel::selectNext,
52+
onClickLoadOnBoarding = onBoardingViewModel::loadOnBoardingItems,
4753
onClickPreviousInSelectOnBoarding = onBoardingViewModel::selectPrevious,
4854
onClickItem = onBoardingViewModel::selectItem,
4955
onClickRoutine = onBoardingViewModel::selectRoutine,
@@ -58,6 +64,7 @@ fun OnBoardingScreenContainer(
5864
private fun OnBoardingScreen(
5965
state: OnBoardingState,
6066
onClickNext: () -> Unit,
67+
onClickLoadOnBoarding: () -> Unit,
6168
onClickPreviousInSelectOnBoarding: () -> Unit,
6269
onClickItem: (String) -> Unit,
6370
onClickRoutine: (String) -> Unit,
@@ -84,7 +91,7 @@ private fun OnBoardingScreen(
8491
OnBoardingPageInfo.Intro -> {
8592
OnBoardingIntroTemplate(
8693
userName = state.userName,
87-
onClickNextButton = onClickNext
94+
onClickNextButton = onClickLoadOnBoarding
8895
)
8996
}
9097
is OnBoardingPageInfo.Abstract -> {
@@ -129,6 +136,18 @@ private fun OnBoardingScreen(
129136
onClickItem = onClickItem,
130137
)
131138
}
139+
140+
is OnBoardingPageInfo.ExistedOnBoardingAbstract -> {
141+
OnBoardingAbstractTemplate(
142+
modifier = Modifier.weight(1f),
143+
title = "이전에 설정한 목표예요!\n변경하시겠어요?",
144+
onBoardingAbstractTexts = currentOnBoardingPageInfo.abstractTexts,
145+
onDispose = cancelRecommendRoutines,
146+
onClickNextButton = onClickLoadOnBoarding,
147+
nextButtonEnable = true,
148+
userName = state.userName,
149+
)
150+
}
132151
}
133152
}
134153
OnBoardingState.Loading -> {
@@ -155,6 +174,7 @@ fun OnBoardingScreenPreview() {
155174
userName = "안드로이드"
156175
),
157176
onClickNext = {},
177+
onClickLoadOnBoarding = {},
158178
onClickPreviousInSelectOnBoarding = {},
159179
onClickItem = {},
160180
onClickRoutine = {},

presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
55
import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingAbstractUseCase
66
import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingsUseCase
77
import com.threegap.bitnagil.domain.onboarding.usecase.GetRecommendOnBoardingRoutinesUseCase
8+
import com.threegap.bitnagil.domain.onboarding.usecase.GetUserOnBoardingUseCase
89
import com.threegap.bitnagil.domain.onboarding.usecase.RegisterRecommendOnBoardingRoutinesUseCase
910
import com.threegap.bitnagil.domain.user.usecase.FetchUserProfileUseCase
1011
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
@@ -34,6 +35,7 @@ class OnBoardingViewModel @AssistedInject constructor(
3435
private val getOnBoardingAbstractUseCase: GetOnBoardingAbstractUseCase,
3536
private val registerRecommendOnBoardingRoutinesUseCase: RegisterRecommendOnBoardingRoutinesUseCase,
3637
private val fetchUserProfileUseCase: FetchUserProfileUseCase,
38+
private val getUserOnBoardingUseCase: GetUserOnBoardingUseCase,
3739
@Assisted private val onBoardingArg: OnBoardingScreenArg,
3840
) : MviViewModel<OnBoardingState, OnBoardingSideEffect, OnBoardingIntent>(
3941
initState = OnBoardingState.Loading,
@@ -45,31 +47,73 @@ class OnBoardingViewModel @AssistedInject constructor(
4547

4648
// 내부에 전체 온보딩 항목 저장
4749
private val selectOnBoardingPageInfos = mutableListOf<OnBoardingPageInfo.SelectOnBoarding>()
50+
private var existedOnBoardingAbstract: OnBoardingPageInfo.ExistedOnBoardingAbstract? = null
4851

4952
private var loadRecommendRoutinesJob: Job? = null
5053

5154
init {
52-
loadOnBoardingItems()
55+
loadInitData()
5356
}
5457

55-
private fun loadOnBoardingItems() {
58+
private fun loadInitData() {
59+
val onBoardingSetType = OnBoardingSetType.fromOnBoardingScreenArg(onBoardingArg)
60+
61+
when(onBoardingSetType) {
62+
OnBoardingSetType.NEW -> {
63+
loadIntro()
64+
}
65+
OnBoardingSetType.RESET -> {
66+
loadUserOnBoarding()
67+
}
68+
}
69+
}
70+
71+
private fun loadIntro() {
72+
viewModelScope.launch {
73+
val userName = fetchUserProfileUseCase().getOrNull()?.nickname ?: "-"
74+
75+
sendIntent(OnBoardingIntent.LoadIntroSuccess(userName = userName))
76+
}
77+
}
78+
79+
private fun loadUserOnBoarding() {
80+
viewModelScope.launch {
81+
val userName = fetchUserProfileUseCase().getOrNull()?.nickname ?: "-"
82+
val userOnBoarding = getUserOnBoardingUseCase().fold(
83+
onSuccess = { it },
84+
onFailure = {
85+
sendIntent(OnBoardingIntent.LoadUserOnBoardingFailure(message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요."))
86+
return@launch
87+
}
88+
)
89+
90+
val onBoardingAbstract = getOnBoardingAbstractUseCase(selectedItemIdsWithOnBoardingId = userOnBoarding)
91+
92+
val abstractPagePrefixText = onBoardingAbstract.prefix
93+
val abstractTexts = onBoardingAbstract.abstractTexts.map { onBoardingAbstractText ->
94+
onBoardingAbstractText.textItems.map { onBoardingAbstractTextItem ->
95+
OnBoardingAbstractTextItem.fromOnBoardingAbstractTextItem(onBoardingAbstractTextItem)
96+
}
97+
}
98+
99+
sendIntent(OnBoardingIntent.LoadUserOnBoardingSuccess(
100+
onBoardingAbstract = OnBoardingPageInfo.ExistedOnBoardingAbstract(
101+
prefix = abstractPagePrefixText,
102+
abstractTexts = abstractTexts,
103+
),
104+
userName = userName,
105+
))
106+
}
107+
}
108+
109+
fun loadOnBoardingItems() {
56110
viewModelScope.launch {
57111
val onBoardings = getOnBoardingsUseCase()
58112
val onBoardingPages = onBoardings.map { onBoarding ->
59113
OnBoardingPageInfo.SelectOnBoarding.fromOnBoarding(onBoarding = onBoarding)
60114
}
61115

62-
val userProfile = fetchUserProfileUseCase()
63-
val userName = userProfile.fold(
64-
onSuccess = {
65-
return@fold it.nickname
66-
},
67-
onFailure = {
68-
return@fold "-"
69-
},
70-
)
71-
72-
sendIntent(intent = OnBoardingIntent.LoadOnBoardingSuccess(onBoardingPageInfos = onBoardingPages, userName = userName))
116+
sendIntent(intent = OnBoardingIntent.LoadOnBoardingSuccess(onBoardingPageInfos = onBoardingPages))
73117
}
74118
}
75119

@@ -78,26 +122,45 @@ class OnBoardingViewModel @AssistedInject constructor(
78122
state: OnBoardingState,
79123
): OnBoardingState? {
80124
when (intent) {
81-
is OnBoardingIntent.LoadOnBoardingSuccess -> {
82-
selectOnBoardingPageInfos.clear()
83-
selectOnBoardingPageInfos.addAll(intent.onBoardingPageInfos)
125+
is OnBoardingIntent.LoadUserOnBoardingSuccess -> {
126+
existedOnBoardingAbstract = intent.onBoardingAbstract
84127

85-
val onBoardingSetType = OnBoardingSetType.fromOnBoardingScreenArg(onBoardingArg)
86-
val firstPage = when(onBoardingSetType) {
87-
OnBoardingSetType.NEW -> OnBoardingPageInfo.Intro
88-
OnBoardingSetType.RESET -> OnBoardingPageInfo.Intro
89-
}
128+
return OnBoardingState.Idle(
129+
nextButtonEnable = true,
130+
currentOnBoardingPageInfo = intent.onBoardingAbstract,
131+
totalStep = 1,
132+
currentStep = 0,
133+
onBoardingSetType = OnBoardingSetType.RESET,
134+
userName = intent.userName
135+
)
136+
}
90137

138+
is OnBoardingIntent.LoadIntroSuccess -> {
91139
return OnBoardingState.Idle(
92140
nextButtonEnable = true,
93-
currentOnBoardingPageInfo = firstPage,
94-
totalStep = selectOnBoardingPageInfos.size + 2,
141+
currentOnBoardingPageInfo = OnBoardingPageInfo.Intro,
142+
totalStep = 1,
95143
currentStep = 0,
96-
onBoardingSetType = onBoardingSetType,
144+
onBoardingSetType = OnBoardingSetType.NEW,
97145
userName = intent.userName
98146
)
99147
}
100148

149+
is OnBoardingIntent.LoadOnBoardingSuccess -> {
150+
val currentState = state
151+
if (currentState !is OnBoardingState.Idle) return null
152+
153+
selectOnBoardingPageInfos.clear()
154+
selectOnBoardingPageInfos.addAll(intent.onBoardingPageInfos)
155+
156+
return currentState.copy(
157+
nextButtonEnable = false,
158+
currentOnBoardingPageInfo = intent.onBoardingPageInfos.first(),
159+
totalStep = selectOnBoardingPageInfos.size + 2,
160+
currentStep = 1,
161+
)
162+
}
163+
101164
is OnBoardingIntent.SelectItem -> {
102165
val currentState = state
103166
if (currentState !is OnBoardingState.Idle) return null
@@ -204,6 +267,12 @@ class OnBoardingViewModel @AssistedInject constructor(
204267
currentStep = currentState.currentStep + 1,
205268
)
206269
}
270+
271+
is OnBoardingIntent.LoadUserOnBoardingFailure -> {
272+
sendSideEffect(sideEffect = OnBoardingSideEffect.MoveToPreviousScreen)
273+
sendSideEffect(sideEffect = OnBoardingSideEffect.ShowToast(message = intent.message))
274+
return null
275+
}
207276
}
208277
}
209278

0 commit comments

Comments
 (0)